diff --git a/README.md b/README.md index d2905a42c..f5379e643 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Builders' and Angular **major** versions **must** match. - [Custom webpack builders](./packages/custom-webpack) (an alternative to `ng eject`) [![npm version](https://img.shields.io/npm/v/@angular-builders/custom-webpack.svg) ![npm (tag)](https://img.shields.io/npm/v/@angular-builders/custom-webpack/next.svg) ![npm](https://img.shields.io/npm/dm/@angular-builders/custom-webpack.svg)](https://www.npmjs.com/package/@angular-builders/custom-webpack) - [Jest builder](./packages/jest) (allows running `ng test` with Jest) [![npm version](https://img.shields.io/npm/v/@angular-builders/jest.svg) ![npm (tag)](https://img.shields.io/npm/v/@angular-builders/jest/next.svg) ![npm](https://img.shields.io/npm/dm/@angular-builders/jest.svg)](https://www.npmjs.com/package/@angular-builders/jest) +- [Bazel builder](./packages/bazel) (`ng` wrapper for `Bazel` build) [![npm version](https://img.shields.io/npm/v/@angular-builders/bazel.svg) ![npm (tag)](https://img.shields.io/npm/v/@angular-builders/bazel/next.svg) ![npm](https://img.shields.io/npm/dm/@angular-builders/bazel.svg)](https://www.npmjs.com/package/@angular-builders/bazek) - [Timestamp builder](./packages/timestamp) (an example builder from [this](https://medium.com/@justjeb/angular-cli-6-under-the-hood-builders-demystified-f0690ebcf01) article) [![npm version](https://img.shields.io/npm/v/@angular-builders/timestamp.svg) ![npm (tag)](https://img.shields.io/npm/v/@angular-builders/timestamp/next.svg) ![npm](https://img.shields.io/npm/dm/@angular-builders/timestamp.svg)](https://www.npmjs.com/package/@angular-builders/timestamp) # Get in touch diff --git a/lerna.json b/lerna.json index b2e0a7aa1..29e9b17c8 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ "packages": [ "packages/*", "packages/*/examples/*", - "packages/timestamp/example" + "packages/*/example" ], "command": { "publish": { diff --git a/packages/bazel/.gitignore b/packages/bazel/.gitignore new file mode 100644 index 000000000..205abf263 --- /dev/null +++ b/packages/bazel/.gitignore @@ -0,0 +1,15 @@ +*.iml +.idea +node_modules + +## this is generated by `npm pack` +*.tgz +package + +dist +src/schema.ts +*.d.ts +**/*.js +!scripts/*.js +*.js.map +example/bazel-* diff --git a/packages/bazel/README.md b/packages/bazel/README.md new file mode 100644 index 000000000..df9190aed --- /dev/null +++ b/packages/bazel/README.md @@ -0,0 +1,33 @@ +# Angular CLI support for Bazel + +Provides an Angular CLI Builder, which can execute Bazel when triggered by `ng build`, `ng test`, etc. +See https://angular.io/guide/cli-builder for more info about Builders. + +> This package is a replacement for parts of the deprecated @angular/bazel package previously maintained by the Angular team. + +This builder assumes you have already created Bazel configurations (WORKSPACE and BUILD files). +There is presently no tooling to generate these automatically that's supported by either Angular team or rules_nodejs maintainers. +See the [`@bazel/create`](https://www.npmjs.com/package/@bazel/create) +package for a quickstart to creating a Bazel workspace, or look at examples in [rules_nodejs]. + +To use it, you would just install this package (it doesn't hook into `ng add` because it has no schematics): + +```sh +$ npm install --save-dev @angular-builders/bazel +``` + +Then edit your `angular.json` to invoke Bazel. For example, to have `ng build` do `bazel build //:all` you would edit the `architect` block to have: + +```json +"architect": { + "build": { + "builder": "@angular-builders/bazel:build", + "options": { + "targetLabel": "//:all", + "bazelCommand": "build" + } + } +} +``` + +[rules_nodejs]: https://github.com/bazelbuild/rules_nodejs diff --git a/packages/bazel/builders.json b/packages/bazel/builders.json new file mode 100644 index 000000000..4142cf0fe --- /dev/null +++ b/packages/bazel/builders.json @@ -0,0 +1,9 @@ +{ + "builders": { + "build": { + "implementation": "./dist", + "schema": "./dist/schema.json", + "description": "Executes Bazel on a target." + } + } +} diff --git a/packages/bazel/example/BUILD.bazel b/packages/bazel/example/BUILD.bazel new file mode 100644 index 000000000..66e0a88d3 --- /dev/null +++ b/packages/bazel/example/BUILD.bazel @@ -0,0 +1,6 @@ +# Writes "hello world" to ./bazel-bin/out +genrule( + name = "hello_world", + outs = ["out"], + cmd = "echo 'hello world' >$@", +) diff --git a/packages/bazel/example/WORKSPACE b/packages/bazel/example/WORKSPACE new file mode 100644 index 000000000..e69de29bb diff --git a/packages/bazel/example/angular.json b/packages/bazel/example/angular.json new file mode 100644 index 000000000..72b60551f --- /dev/null +++ b/packages/bazel/example/angular.json @@ -0,0 +1,18 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "bazel-build": { + "architect": { + "build": { + "builder": "@angular-builders/bazel:build", + "options": { + "targetLabel": "//:all", + "bazelCommand": "build" + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/bazel/example/package.json b/packages/bazel/example/package.json new file mode 100644 index 000000000..c8092e965 --- /dev/null +++ b/packages/bazel/example/package.json @@ -0,0 +1,12 @@ +{ + "name": "bazel-example", + "version": "13.0.0", + "scripts": { + "build": "ng build" + }, + "private": true, + "devDependencies": { + "@angular/cli": "^13.0.1", + "@angular-builders/bazel": "latest" + } +} diff --git a/packages/bazel/package.json b/packages/bazel/package.json new file mode 100644 index 000000000..7731b093d --- /dev/null +++ b/packages/bazel/package.json @@ -0,0 +1,41 @@ +{ + "name": "@angular-builders/bazel", + "description": "Run Bazel under the Angular CLI", + "license": "Apache-2.0", + "version": "13.0.0", + "repository": { + "type": "git", + "url": "https://github.com/just-jeb/angular-builders.git", + "directory": "packages/bazel" + }, + "bugs": { + "url": "https://github.com/just-jeb/angular-builders/issues" + }, + "keywords": [ + "angular", + "bazel" + ], + "builders": "./builders.json", + "main": "dist/index.js", + "files": [ + "dist", + "builders.json" + ], + "scripts": { + "prebuild": "yarn clean && yarn generate", + "build": "../../node_modules/.bin/tsc", + "postbuild": "yarn copy", + "clean": "../../node_modules/.bin/rimraf dist src/schema.ts", + "copy": "../../node_modules/.bin/cpy src/schema.json dist", + "generate": "../../node_modules/.bin/quicktype -s schema src/schema.json -o src/schema.ts", + "ci": "node ./scripts/ci.js" + }, + "dependencies": { + "@angular-devkit/architect": ">=0.1300.0 < 0.1400.0", + "@bazel/bazelisk": "^1.4.0", + "@bazel/ibazel": "^0.13.1" + }, + "devDependencies": { + "@angular-devkit/core": "^13.0.0" + } +} diff --git a/packages/bazel/scripts/ci.js b/packages/bazel/scripts/ci.js new file mode 100644 index 000000000..7f775c4e4 --- /dev/null +++ b/packages/bazel/scripts/ci.js @@ -0,0 +1,24 @@ +const { execSync } = require('child_process'); +const { readFileSync } = require('fs'); +const { exit } = require('process'); + +runTests(); + +function runTests() { + process.chdir('./example'); + runBuild(); + compareResults(); +} + +function runBuild() { + execSync('yarn build'); +} + +function compareResults() { + const expectedOut = 'hello world\n'; + const bazelOut = readFileSync('./bazel-bin/out', { encoding: 'utf-8' }); + if (bazelOut !== expectedOut) { + console.log(`ERROR: Expected bazel output is ${expectedOut}, actual is ${bazelOut}`); + exit(1); + } +} diff --git a/packages/bazel/src/index.ts b/packages/bazel/src/index.ts new file mode 100644 index 000000000..d835fe00f --- /dev/null +++ b/packages/bazel/src/index.ts @@ -0,0 +1,38 @@ +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import type { JsonObject } from '@angular-devkit/core'; +import { getNativeBinary as bazeliskBin } from '@bazel/bazelisk/bazelisk'; +import { getNativeBinary as ibazelBin } from '@bazel/ibazel'; +import { spawn } from 'child_process'; +import { Schema } from './schema'; + +async function _bazelBuilder( + options: JsonObject & Schema, + context: BuilderContext +): Promise { + const { bazelCommand, targetLabel, watch } = options; + const binary = watch ? ibazelBin() : bazeliskBin(); + if (typeof binary !== 'string') { + // this happens if no binary is located for the current platform + context.logger.error('No Bazel binary detected'); + return { success: false }; + } else { + try { + const ps = spawn(binary, [bazelCommand, targetLabel], { stdio: 'inherit' }); + + function shutdown() { + ps.kill('SIGTERM'); + } + + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + return new Promise(resolve => { + ps.on('close', e => resolve({ success: e === 0 })); + }); + } catch (err: any) { + context.logger.error(err.message); + return { success: false }; + } + } +} + +export default createBuilder(_bazelBuilder); diff --git a/packages/bazel/src/schema.json b/packages/bazel/src/schema.json new file mode 100644 index 000000000..fc73a59d9 --- /dev/null +++ b/packages/bazel/src/schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "Bazel builder schema", + "description": "Options for Bazel Builder", + "type": "object", + "properties": { + "targetLabel": { + "type": "string", + "description": "Target to be executed under Bazel." + }, + "bazelCommand": { + "type": "string", + "description": "Common commands supported by Bazel.", + "enum": [ + "run", + "build", + "test" + ] + }, + "watch": { + "type": "boolean", + "description": "If true, watch the filesystem using ibazel.", + "default": false + } + }, + "additionalProperties": false, + "required": [ + "targetLabel", + "bazelCommand" + ] + } diff --git a/packages/bazel/tsconfig.json b/packages/bazel/tsconfig.json new file mode 100644 index 000000000..75d8b2be3 --- /dev/null +++ b/packages/bazel/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "noImplicitAny": false + }, + "files": [ + "src/index.ts" + ] +} \ No newline at end of file diff --git a/packages/bazel/types.d.ts b/packages/bazel/types.d.ts new file mode 100644 index 000000000..05b92012c --- /dev/null +++ b/packages/bazel/types.d.ts @@ -0,0 +1,7 @@ +declare module '@bazel/bazelisk/bazelisk' { + function getNativeBinary(): Promise | string; +} + +declare module '@bazel/ibazel' { + function getNativeBinary(): Promise | string; +} diff --git a/packages/timestamp/example/package.json b/packages/timestamp/example/package.json index a733ecf20..5681d57b7 100644 --- a/packages/timestamp/example/package.json +++ b/packages/timestamp/example/package.json @@ -1,5 +1,5 @@ { - "name": "example", + "name": "timestamp-example", "version": "13.0.0-beta.0", "scripts": { "ng": "ng", diff --git a/scripts/run-ci.sh b/scripts/run-ci.sh index 0698b859d..219c96a55 100755 --- a/scripts/run-ci.sh +++ b/scripts/run-ci.sh @@ -46,10 +46,9 @@ yarn bootstrap:examples # Get travis's chrome version and download the appropriate webdriver-manager for protractor CHROME_VERSION=`google-chrome --version | egrep -o '[0-9.]+' | head -1` -npx lerna exec --ignore '@angular-builders/*' -- ./node_modules/protractor/bin/webdriver-manager update --versions.chrome $CHROME_VERSION +WEBDRIVER_MANAGER_BIN=./node_modules/protractor/bin/webdriver-manager; +yarn lerna exec --ignore '@angular-builders/*' "[ -f $WEBDRIVER_MANAGER_BIN ] && $WEBDRIVER_MANAGER_BIN update --versions.chrome $CHROME_VERSION || echo \`pwd\`: No webdriver-manager found" -npx lerna run ci +yarn lerna run ci cleanup - -