/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ 'use strict'; // Build the dist/packages-dist directory in the same fashion as the legacy // /build.sh script, by building the npm packages with Bazel and copying files. // This is needed for scripts and tests which are not updated to the Bazel output // layout (which always matches the input layout). // Do not add new dependencies on this script, instead adapt scripts to use the // new layout, and write new tests as Bazel targets. // // Ideally integration tests should run under bazel, and just consume the npm // packages via `deps`. Until that works, we manually build the npm packages and then // copy the results to the appropriate `dist` location. // NOTE: this script may be run from any directory. The actions should be written to be independent // of the current working directory. For example, use absolute paths wherever possible, and pass a // working directory to tools like `yarn`. const {execSync} = require('child_process'); const {resolve, relative} = require('path'); const {chmod, cp, mkdir, rm, set, test} = require('shelljs'); set('-e'); /** @type {string} The absolute path to the project root directory. */ const baseDir = resolve(`${__dirname}/../..`); /** @type {string} The command to use for running bazel. */ const bazelCmd = `yarn --cwd "${baseDir}" --silent bazel`; /** @type {string} The absolute path to the bazel-bin directory. */ const bazelBin = exec(`${bazelCmd} info bazel-bin`, true); /** * @type {string} * The relative path to the entry script (i.e. the one loaded when the Node.js process launched). * It is relative to `baseDir`. */ const scriptPath = relative(baseDir, require.main.filename); module.exports = { baseDir, bazelBin, bazelCmd, buildTargetPackages, exec, scriptPath, }; /** * Build the Angular packages. * * @param {string} destDir Path to the output directory into which we copy the npm packages. * This path should either be absolute or relative to the project root. * @param {boolean} enableIvy True, if Ivy should be used. * @param {string} description Human-readable description of the build. * @param {boolean?} isRelease True, if the build should be stamped for a release. * @returns {Array<{name: string, outputPath: string}} A list of packages built. */ function buildTargetPackages(destDir, enableIvy, description, isRelease = false) { console.info('##################################'); console.info(`${scriptPath}:`); console.info(' Building @angular/* npm packages'); console.info(` Mode: ${description}`); console.info('##################################'); /** The list of packages which were built. */ const builtPackages = []; // List of targets to build, e.g. core, common, compiler, etc. Note that we want to also remove // all carriage return (`\r`) characters form the query output, because otherwise the carriage // return is part of the bazel target name and bazel will complain. const getTargetsCmd = `${ bazelCmd} query --output=label "attr('tags', '\\[.*release-with-framework.*\\]', //packages/...) intersect kind('ng_package|pkg_npm', //packages/...)"`; const targets = exec(getTargetsCmd, true).split(/\r?\n/); // If we are in release mode, run `bazel clean` to ensure the execroot and action cache // are not populated. This is necessary because targets using `npm_package` rely on // workspace status variables for the package version. Such NPM package targets are not // rebuilt if only the workspace status variables change. This could result in accidental // re-use of previously built package output with a different `version` in the `package.json`. if (isRelease) { console.info('Building in release mode. Resetting the Bazel execroot and action cache..'); exec(`${bazelCmd} clean`); } // Use either `--config=snapshot` or `--config=release` so that builds are created with the // correct embedded version info. exec(`${bazelCmd} build --config=${isRelease ? 'release' : 'snapshot'} --config=${ enableIvy ? 'ivy' : 'view-engine'} ${targets.join(' ')}`); // Create the output directory. const absDestDir = resolve(baseDir, destDir); if (!test('-d', absDestDir)) { mkdir('-p', absDestDir); } targets.forEach(target => { const pkg = target.replace(/\/\/packages\/(.*):npm_package/, '$1'); // Skip any that don't have an "npm_package" target. const srcDir = `${bazelBin}/packages/${pkg}/npm_package`; const destDir = `${absDestDir}/${pkg}`; if (test('-d', srcDir)) { console.info(`# Copy artifacts to ${destDir}`); rm('-rf', destDir); cp('-R', srcDir, destDir); chmod('-R', 'u+w', destDir); builtPackages.push({name: `@angular/${pkg}`, outputPath: destDir}); } }); console.info(''); return builtPackages; } /** * Execute a command synchronously. * * By default, the current process' stdout is used (and thus the output is not captured and returned * to the caller). This is necessary for showing colors and modifying already printed output, for * example to show progress. * * If the caller requests the output (via `captureStdout: true`), the command is run without * printing anything to stdout and then (once the command has completed) the whole output is printed * to stdout and returned to the caller. * * @param {string} cmd The command to run. * @param {boolean} [captureStdout=false] Whether to return the output of the command. * @param {import('child_process').ExecSyncOptions} [options] The options to pass to `execSync()`. * @return {string | undefined} The captured stdout output if `captureStdout: true` or `undefined`. */ function exec(cmd, captureStdout, options) { const output = execSync(cmd, { stdio: [ /* stdin */ 'inherit', /* stdout */ captureStdout ? 'pipe' : 'inherit', /* stderr */ 'inherit', ], ...options, }); if (captureStdout) { process.stdout.write(output); return output.toString().trim(); } }