refactor(bazel): Remove NodeJsSyncHost (#29796)
`NodeJsSyncHost` is no longer provided by BuilderContext in architect v2, and its usage caused subtle path resolution issues in Windows. This PR cleans up `@angular/bazel` builder to use all native path and fs methods. PR Close #29796
This commit is contained in:
parent
b2962db4cb
commit
7a7781e925
|
@ -28,24 +28,3 @@ ts_library(
|
|||
"@npm//rxjs",
|
||||
],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"bazel_spec.ts",
|
||||
],
|
||||
deps = [
|
||||
"builders",
|
||||
"@npm//@angular-devkit/core",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["angular/tools/testing/init_node_spec.js"],
|
||||
deps = [
|
||||
":test_lib",
|
||||
"//tools/testing:node",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -8,10 +8,9 @@
|
|||
|
||||
/// <reference types='node'/>
|
||||
|
||||
import {Path, dirname, getSystemPath, join, normalize} from '@angular-devkit/core';
|
||||
import {Host} from '@angular-devkit/core/src/virtual-fs/host';
|
||||
import {spawn} from 'child_process';
|
||||
import * as path from 'path';
|
||||
import {fork} from 'child_process';
|
||||
import {copyFileSync, existsSync, readdirSync, statSync, unlinkSync} from 'fs';
|
||||
import {dirname, join, normalize} from 'path';
|
||||
|
||||
export type Executable = 'bazel' | 'ibazel';
|
||||
export type Command = 'build' | 'test' | 'run' | 'coverage' | 'query';
|
||||
|
@ -20,12 +19,14 @@ export type Command = 'build' | 'test' | 'run' | 'coverage' | 'query';
|
|||
* Spawn the Bazel process. Trap SINGINT to make sure Bazel process is killed.
|
||||
*/
|
||||
export function runBazel(
|
||||
projectDir: Path, binary: string, command: Command, workspaceTarget: string, flags: string[]) {
|
||||
projectDir: string, binary: string, command: Command, workspaceTarget: string,
|
||||
flags: string[]) {
|
||||
projectDir = normalize(projectDir);
|
||||
binary = normalize(binary);
|
||||
return new Promise((resolve, reject) => {
|
||||
const buildProcess = spawn(process.argv[0], [binary, command, workspaceTarget, ...flags], {
|
||||
cwd: getSystemPath(projectDir),
|
||||
const buildProcess = fork(binary, [command, workspaceTarget, ...flags], {
|
||||
cwd: projectDir,
|
||||
stdio: 'inherit',
|
||||
shell: false,
|
||||
});
|
||||
|
||||
process.on('SIGINT', (signal) => {
|
||||
|
@ -48,14 +49,14 @@ export function runBazel(
|
|||
/**
|
||||
* Resolves the path to `@bazel/bazel` or `@bazel/ibazel`.
|
||||
*/
|
||||
export function checkInstallation(name: Executable, projectDir: Path): string {
|
||||
export function checkInstallation(name: Executable, projectDir: string): string {
|
||||
projectDir = normalize(projectDir);
|
||||
const packageName = `@bazel/${name}/package.json`;
|
||||
try {
|
||||
const bazelPath = require.resolve(packageName, {
|
||||
paths: [getSystemPath(projectDir)],
|
||||
paths: [projectDir],
|
||||
});
|
||||
|
||||
return path.dirname(bazelPath);
|
||||
return dirname(bazelPath);
|
||||
} catch (error) {
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
throw new Error(
|
||||
|
@ -70,14 +71,14 @@ export function checkInstallation(name: Executable, projectDir: Path): string {
|
|||
/**
|
||||
* Returns the absolute path to the template directory in `@angular/bazel`.
|
||||
*/
|
||||
export async function getTemplateDir(host: Host, root: Path): Promise<Path> {
|
||||
export function getTemplateDir(root: string): string {
|
||||
root = normalize(root);
|
||||
const packageJson = require.resolve('@angular/bazel/package.json', {
|
||||
paths: [getSystemPath(root)],
|
||||
paths: [root],
|
||||
});
|
||||
|
||||
const packageDir = dirname(normalize(packageJson));
|
||||
const packageDir = dirname(packageJson);
|
||||
const templateDir = join(packageDir, 'src', 'builders', 'files');
|
||||
if (!await host.isDirectory(templateDir).toPromise()) {
|
||||
if (!statSync(templateDir).isDirectory()) {
|
||||
throw new Error('Could not find Bazel template directory in "@angular/bazel".');
|
||||
}
|
||||
return templateDir;
|
||||
|
@ -87,30 +88,22 @@ export async function getTemplateDir(host: Host, root: Path): Promise<Path> {
|
|||
* Recursively list the specified 'dir' using depth-first approach. Paths
|
||||
* returned are relative to 'dir'.
|
||||
*/
|
||||
function listR(host: Host, dir: Path): Promise<Path[]> {
|
||||
async function list(dir: Path, root: Path, results: Path[]) {
|
||||
const paths = await host.list(dir).toPromise();
|
||||
function listR(dir: string): string[] {
|
||||
function list(dir: string, root: string, results: string[]) {
|
||||
const paths = readdirSync(dir);
|
||||
for (const path of paths) {
|
||||
const absPath = join(dir, path);
|
||||
const relPath = join(root, path);
|
||||
if (await host.isFile(absPath).toPromise()) {
|
||||
if (statSync(absPath).isFile()) {
|
||||
results.push(relPath);
|
||||
} else {
|
||||
await list(absPath, relPath, results);
|
||||
list(absPath, relPath, results);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
return list(dir, '' as Path, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the file from 'source' to 'dest'.
|
||||
*/
|
||||
async function copyFile(host: Host, source: Path, dest: Path) {
|
||||
const buffer = await host.read(source).toPromise();
|
||||
await host.write(dest, buffer).toPromise();
|
||||
return list(dir, '', []);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,35 +112,36 @@ async function copyFile(host: Host, source: Path, dest: Path) {
|
|||
* copied, so that they can be deleted later.
|
||||
* Existing files in `root` will not be replaced.
|
||||
*/
|
||||
export async function copyBazelFiles(host: Host, root: Path, templateDir: Path) {
|
||||
const bazelFiles: Path[] = [];
|
||||
const templates = await listR(host, templateDir);
|
||||
export function copyBazelFiles(root: string, templateDir: string) {
|
||||
root = normalize(root);
|
||||
templateDir = normalize(templateDir);
|
||||
const bazelFiles: string[] = [];
|
||||
const templates = listR(templateDir);
|
||||
|
||||
await Promise.all(templates.map(async(template) => {
|
||||
for (const template of templates) {
|
||||
const name = template.replace('__dot__', '.').replace('.template', '');
|
||||
const source = join(templateDir, template);
|
||||
const dest = join(root, name);
|
||||
try {
|
||||
const exists = await host.exists(dest).toPromise();
|
||||
if (!exists) {
|
||||
await copyFile(host, source, dest);
|
||||
if (!existsSync(dest)) {
|
||||
copyFileSync(source, dest);
|
||||
bazelFiles.push(dest);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return bazelFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified 'files' and return a promise that always resolves.
|
||||
* Delete the specified 'files'. This function never throws.
|
||||
*/
|
||||
export function deleteBazelFiles(host: Host, files: Path[]) {
|
||||
return Promise.all(files.map(async(file) => {
|
||||
export function deleteBazelFiles(files: string[]) {
|
||||
for (const file of files) {
|
||||
try {
|
||||
await host.delete(file).toPromise();
|
||||
unlinkSync(file);
|
||||
} catch {
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
import {Path} from '@angular-devkit/core';
|
||||
import {test} from '@angular-devkit/core/src/virtual-fs/host/test';
|
||||
|
||||
import {copyBazelFiles, deleteBazelFiles} from './bazel';
|
||||
|
||||
describe('Bazel builder', () => {
|
||||
it('should copy Bazel files', async() => {
|
||||
const host = new test.TestHost({
|
||||
'/files/WORKSPACE.template': '',
|
||||
'/files/BUILD.bazel.template': '',
|
||||
'/files/__dot__bazelrc.template': '',
|
||||
'/files/__dot__bazelignore.template': '',
|
||||
'/files/e2e/BUILD.bazel.template': '',
|
||||
'/files/src/BUILD.bazel.template': '',
|
||||
});
|
||||
const root = '/' as Path;
|
||||
const templateDir = '/files' as Path;
|
||||
await copyBazelFiles(host, root, templateDir);
|
||||
const {records} = host;
|
||||
expect(records).toContain({kind: 'write', path: '/WORKSPACE' as Path});
|
||||
expect(records).toContain({kind: 'write', path: '/BUILD.bazel' as Path});
|
||||
});
|
||||
|
||||
it('should delete Bazel files', async() => {
|
||||
const host = new test.TestHost({
|
||||
'/WORKSPACE': '',
|
||||
'/BUILD.bazel': '',
|
||||
});
|
||||
await deleteBazelFiles(host, ['/WORKSPACE', '/BUILD.bazel'] as Path[]);
|
||||
const {records} = host;
|
||||
expect(records).toContain({kind: 'delete', path: '/WORKSPACE' as Path});
|
||||
expect(records).toContain({kind: 'delete', path: '/BUILD.bazel' as Path});
|
||||
});
|
||||
});
|
|
@ -9,33 +9,29 @@
|
|||
*/
|
||||
|
||||
import {BuilderContext, BuilderOutput, createBuilder,} from '@angular-devkit/architect/src/index2';
|
||||
import {JsonObject, normalize} from '@angular-devkit/core';
|
||||
import {JsonObject} from '@angular-devkit/core';
|
||||
import {checkInstallation, copyBazelFiles, deleteBazelFiles, getTemplateDir, runBazel} from './bazel';
|
||||
import {Schema} from './schema';
|
||||
import {NodeJsSyncHost} from '@angular-devkit/core/node';
|
||||
|
||||
async function _bazelBuilder(options: JsonObject & Schema, context: BuilderContext, ):
|
||||
Promise<BuilderOutput> {
|
||||
const root = normalize(context.workspaceRoot);
|
||||
const {logger} = context;
|
||||
const {logger, workspaceRoot} = context;
|
||||
const {bazelCommand, leaveBazelFilesOnDisk, targetLabel, watch} = options;
|
||||
const executable = watch ? 'ibazel' : 'bazel';
|
||||
const binary = checkInstallation(executable, root);
|
||||
|
||||
const host = new NodeJsSyncHost();
|
||||
const templateDir = await getTemplateDir(host, root);
|
||||
const bazelFiles = await copyBazelFiles(host, root, templateDir);
|
||||
const binary = checkInstallation(executable, workspaceRoot);
|
||||
const templateDir = getTemplateDir(workspaceRoot);
|
||||
const bazelFiles = copyBazelFiles(workspaceRoot, templateDir);
|
||||
|
||||
try {
|
||||
const flags: string[] = [];
|
||||
await runBazel(root, binary, bazelCommand, targetLabel, flags);
|
||||
await runBazel(workspaceRoot, binary, bazelCommand, targetLabel, flags);
|
||||
return {success: true};
|
||||
} catch (err) {
|
||||
logger.error(err.message);
|
||||
return {success: false};
|
||||
} finally {
|
||||
if (!leaveBazelFilesOnDisk) {
|
||||
await deleteBazelFiles(host, bazelFiles); // this will never throw
|
||||
deleteBazelFiles(bazelFiles); // this will never throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue