Skip to content

Commit 859db7d

Browse files
kormidealan-agius4
authored andcommitted
build: add parallel script to build using bazel
(cherry picked from commit ddec41e)
1 parent e0d1e19 commit 859db7d

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"admin": "node ./bin/devkit-admin",
2323
"bazel:test": "bazel test //packages/...",
2424
"build": "node ./bin/devkit-admin build",
25+
"build:bazel": "node ./bin/devkit-admin build-bazel",
2526
"build-tsc": "tsc -p tsconfig.json",
2627
"lint": "eslint --cache --max-warnings=0 \"**/*.ts\"",
2728
"templates": "node ./bin/devkit-admin templates",

scripts/build-bazel.ts

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { logging } from '@angular-devkit/core';
10+
import { spawn } from 'child_process';
11+
import fs from 'fs';
12+
import { dirname, join, relative, resolve } from 'path';
13+
14+
const baseDir = resolve(`${__dirname}/..`);
15+
const bazelCmd = process.env.BAZEL ?? `yarn --cwd "${baseDir}" --silent bazel`;
16+
const distRoot = join(baseDir, '/dist');
17+
18+
type BuildMode = 'local' | 'snapshot' | 'release';
19+
20+
function _copy(from: string, to: string) {
21+
// Create parent folder if necessary.
22+
if (!fs.existsSync(dirname(to))) {
23+
fs.mkdirSync(dirname(to), { recursive: true });
24+
}
25+
26+
// Error out if destination already exists.
27+
if (fs.existsSync(to)) {
28+
throw new Error(`Path ${to} already exist...`);
29+
}
30+
31+
from = relative(process.cwd(), from);
32+
to = relative(process.cwd(), to);
33+
34+
const buffer = fs.readFileSync(from);
35+
fs.writeFileSync(to, buffer);
36+
}
37+
38+
function _recursiveCopy(from: string, to: string, logger: logging.Logger) {
39+
if (!fs.existsSync(from)) {
40+
logger.error(`File "${from}" does not exist.`);
41+
process.exit(4);
42+
}
43+
if (fs.statSync(from).isDirectory()) {
44+
fs.readdirSync(from).forEach((fileName) => {
45+
_recursiveCopy(join(from, fileName), join(to, fileName), logger);
46+
});
47+
} else {
48+
_copy(from, to);
49+
}
50+
}
51+
52+
function rimraf(location: string) {
53+
// The below should be removed and replace with just `rmSync` when support for Node.Js 12 is removed.
54+
const { rmSync, rmdirSync } = fs as typeof fs & {
55+
rmSync?: (
56+
path: fs.PathLike,
57+
options?: {
58+
force?: boolean;
59+
maxRetries?: number;
60+
recursive?: boolean;
61+
retryDelay?: number;
62+
},
63+
) => void;
64+
};
65+
66+
if (rmSync) {
67+
rmSync(location, { force: true, recursive: true, maxRetries: 3 });
68+
} else {
69+
rmdirSync(location, { recursive: true, maxRetries: 3 });
70+
}
71+
}
72+
73+
function _clean(logger: logging.Logger) {
74+
logger.info('Cleaning...');
75+
logger.info(' Removing dist/...');
76+
rimraf(join(__dirname, '../dist'));
77+
}
78+
79+
function _exec(cmd: string, captureStdout: boolean, logger: logging.Logger): Promise<string> {
80+
return new Promise((resolve, reject) => {
81+
const proc = spawn(cmd, {
82+
stdio: 'pipe',
83+
shell: true,
84+
});
85+
86+
let output = '';
87+
proc.stdout.on('data', (data) => {
88+
logger.info(data.toString().trim());
89+
if (captureStdout) {
90+
output += data.toString().trim();
91+
}
92+
});
93+
proc.stderr.on('data', (data) => logger.info(data.toString().trim()));
94+
95+
proc.on('error', (error) => {
96+
logger.error(error.message);
97+
});
98+
99+
proc.on('exit', (status) => {
100+
if (status !== 0) {
101+
reject(`Command failed: ${cmd}`);
102+
} else {
103+
resolve(output);
104+
}
105+
});
106+
});
107+
}
108+
109+
async function _build(logger: logging.Logger, mode: BuildMode): Promise<string[]> {
110+
logger.info(`Building (mode=${mode})...`);
111+
112+
const queryLogger = logger.createChild('query');
113+
const queryTargetsCmd = `${bazelCmd} query --output=label "attr(name, npm_package_archive, //packages/...)"`;
114+
const targets = (await _exec(queryTargetsCmd, true, queryLogger)).split(/\r?\n/);
115+
116+
let configArg = '';
117+
if (mode === 'snapshot') {
118+
configArg = '--config=snapshot';
119+
} else if (mode === 'release') {
120+
configArg = '--config=release';
121+
}
122+
123+
const buildLogger = logger.createChild('build');
124+
125+
// If we are in release mode, run `bazel clean` to ensure the execroot and action cache
126+
// are not populated. This is necessary because targets using `npm_package` rely on
127+
// workspace status variables for the package version. Such NPM package targets are not
128+
// rebuilt if only the workspace status variables change. This could result in accidental
129+
// re-use of previously built package output with a different `version` in the `package.json`.
130+
if (mode == 'release') {
131+
buildLogger.info('Building in release mode. Resetting the Bazel execroot and action cache..');
132+
await _exec(`${bazelCmd} clean`, false, buildLogger);
133+
}
134+
135+
await _exec(`${bazelCmd} build ${configArg} ${targets.join(' ')}`, false, buildLogger);
136+
137+
return targets;
138+
}
139+
140+
export default async function (
141+
argv: { local?: boolean; snapshot?: boolean } = {},
142+
logger: logging.Logger = new logging.Logger('build-logger'),
143+
): Promise<{ name: string; outputPath: string }[]> {
144+
const bazelBin = await _exec(`${bazelCmd} info bazel-bin`, true, logger);
145+
146+
_clean(logger);
147+
148+
let buildMode: BuildMode;
149+
if (argv.local) {
150+
buildMode = 'local';
151+
} else if (argv.snapshot) {
152+
buildMode = 'snapshot';
153+
} else {
154+
buildMode = 'release';
155+
}
156+
157+
const targets = await _build(logger, buildMode);
158+
const output: { name: string; outputPath: string }[] = [];
159+
160+
logger.info('Moving packages and tars to dist/');
161+
const packageLogger = logger.createChild('packages');
162+
163+
for (const target of targets) {
164+
const packageDir = target.replace(/\/\/packages\/(.*):npm_package_archive/, '$1');
165+
const bazelOutDir = join(bazelBin, 'packages', packageDir, 'npm_package');
166+
const tarPath = `${bazelBin}/packages/${packageDir}/npm_package_archive.tar.gz`;
167+
const packageJsonPath = `${bazelOutDir}/package.json`;
168+
const packageName = require(packageJsonPath).name;
169+
const destDir = `${distRoot}/${packageName}`;
170+
171+
packageLogger.info(packageName);
172+
173+
_recursiveCopy(bazelOutDir, destDir, logger);
174+
_copy(tarPath, `${distRoot}/${packageName.replace('@', '_').replace('/', '_')}.tgz`);
175+
176+
output.push({ name: packageDir, outputPath: destDir });
177+
}
178+
179+
return output;
180+
}

0 commit comments

Comments
 (0)