function generateCloudbuild()

in scripts/generate_cloudbuild.js [160:293]


function generateCloudbuild(packages, print = true) {
  // Make sure all packages are declared in package_dependencies.json.
  const allPackages = new Set(Object.keys(DEPENDENCY_GRAPH));
  for (const packageName of packages) {
    if (!allPackages.has(packageName)) {
      throw new Error(
          `Package ${packageName} was not declared in ` +
          'package_dependencies.json');
    }
  }

  const deps = findDeps(packages);
  const reverseDeps = findReverseDeps(packages);
  const depsOfReverseDeps = findDeps(reverseDeps);

  const toBuild =
      new Set([...deps, ...packages, ...reverseDeps, ...depsOfReverseDeps]);
  const toTest = new Set([...packages, ...reverseDeps]);

  if (print) {
    // Log what will be built and tested
    const buildTestTable = [];
    for (const packageName of allPackages) {
      buildTestTable.push({
        'Package': packageName,
        'Will Build': toBuild.has(packageName) ? '✔' : '',
        'Will Test': toTest.has(packageName) ? '✔' : ''
      });
    }
    printTable(buildTestTable);
  }

  // Load all the cloudbuild files for the packages
  // that need to be built or tested.
  const packageCloudbuildSteps = new Map();
  for (const packageName of new Set([...toBuild, ...toTest])) {
    const doc = yaml.safeLoad(
        fs.readFileSync(path.join(packageName, 'cloudbuild.yml')));
    packageCloudbuildSteps.set(packageName, new Set(doc.steps));
  }

  // Filter out excluded steps. Also remove test steps if the package is
  // not going to be tested. Change step ids to avoid name conflicts.
  for (const [packageName, steps] of packageCloudbuildSteps.entries()) {
    // TODO(msoulanille): Steps that depend on excluded steps might still
    // need to wait for the steps that the excluded steps wait for.
    for (const step of steps) {
      if (!step.id) {
        throw new Error(`Step from ${packageName} missing id`);
      }

      // Exclude a specific set of steps defined in `excludeSteps`.
      // Only include test steps if the package
      // is to be tested.
      if (EXCLUDE_STEPS.has(step.id) ||
          (!toTest.has(packageName) && isTestStep(step.id))) {
        steps.delete(step);
        continue;
      }

      // Append package name to each step's id.
      if (step.id) {
        // Test steps are not required to have ids.
        step.id = makeStepId(step.id, packageName);
      }

      // Append package names to step ids in the 'waitFor' field.
      if (step.waitFor) {
        step.waitFor = step.waitFor.filter(id => id && !EXCLUDE_STEPS.has(id))
                           .map(id => makeStepId(id, packageName));
      }
    }
  }

  // Set 'waitFor' fields based on dependencies.
  for (const [packageName, steps] of packageCloudbuildSteps.entries()) {
    // Construct the set of step ids that rules in this package must wait for.
    // All packages depend on 'yarn-common' and 'yarn-link-package', so
    // we special-case them here.
    const waitForSteps = new Set(['yarn-common', 'yarn-link-package']);
    for (const dependencyName of (DEPENDENCY_GRAPH[packageName] || new Set())) {
      const cloudbuildSteps =
          packageCloudbuildSteps.get(dependencyName) || new Set();

      for (const step of cloudbuildSteps) {
        if (!isTestStep(step.id)) {
          waitForSteps.add(step.id);
        }
      }
    }

    // Add the above step ids to the `waitFor` field in each step.
    for (const step of steps) {
      step.waitFor = [...new Set([...(step.waitFor || []), ...waitForSteps])]
    }
  }

  // Load the general cloudbuild config
  baseCloudbuild =
      yaml.safeLoad(fs.readFileSync('scripts/cloudbuild_general_config.yml'));

  // Include yarn-common as the first step.
  const steps = [...baseCloudbuild.steps];

  // Arrange steps in dependency order
  for (const packageName of DEPENDENCY_ORDER) {
    const packageSteps = packageCloudbuildSteps.get(packageName);
    if (packageSteps) {
      for (const step of packageSteps) {
        steps.push(step);
      }
    }
  }

  // Remove unused secrets. Cloudbuild fails if extra secrets are included.
  const usedSecrets = new Set();
  for (const step of steps) {
    for (const secret of step.secretEnv || []) {
      usedSecrets.add(secret);
    }
  }
  const secretEnv = baseCloudbuild.secrets[0].secretEnv;
  for (const secret of Object.keys(secretEnv)) {
    if (!usedSecrets.has(secret)) {
      delete secretEnv[secret];
    }
  }
  if (Object.keys(secretEnv).length === 0) {
    delete baseCloudbuild.secrets;
  }

  baseCloudbuild.steps = steps;
  return baseCloudbuild;
}