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;
}