From 7060d9038b11a7c67cdce6b58464a935e9a340c5 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Mon, 4 Mar 2019 16:18:15 -0800 Subject: [PATCH] feat(bazel): Hide Bazel files in Bazel builder (#29110) This commit modifies the Bazel builder to copy the Bazel WORKSPACE and BUILD.bazel files to the project root directory before invoking Bazel. This hides the Bazel files from users. PR Close #29110 --- integration/bazel-schematics/yarn.lock | 77 ++++----- packages/bazel/package.json | 4 +- packages/bazel/src/builders/BUILD.bazel | 25 ++- packages/bazel/src/builders/bazel.ts | 159 ++++++++++++++---- packages/bazel/src/builders/bazel_spec.ts | 42 +++++ .../src/builders/files/BUILD.bazel.template | 7 + .../src/builders/files/WORKSPACE.template | 60 +++++++ .../files/__dot__bazelignore.template | 2 + .../builders/files/__dot__bazelrc.template | 29 ++++ .../builders/files/e2e/BUILD.bazel.template | 50 ++++++ .../builders/files/src/BUILD.bazel.template | 153 +++++++++++++++++ packages/bazel/src/builders/index.ts | 45 ++--- 12 files changed, 555 insertions(+), 98 deletions(-) create mode 100644 packages/bazel/src/builders/bazel_spec.ts create mode 100644 packages/bazel/src/builders/files/BUILD.bazel.template create mode 100644 packages/bazel/src/builders/files/WORKSPACE.template create mode 100644 packages/bazel/src/builders/files/__dot__bazelignore.template create mode 100644 packages/bazel/src/builders/files/__dot__bazelrc.template create mode 100644 packages/bazel/src/builders/files/e2e/BUILD.bazel.template create mode 100644 packages/bazel/src/builders/files/src/BUILD.bazel.template diff --git a/integration/bazel-schematics/yarn.lock b/integration/bazel-schematics/yarn.lock index b2b87bca5c..b6b57e7865 100644 --- a/integration/bazel-schematics/yarn.lock +++ b/integration/bazel-schematics/yarn.lock @@ -10,25 +10,14 @@ "@angular-devkit/core" "7.3.2" rxjs "6.3.3" -"@angular-devkit/architect@^0.10.6": - version "0.10.7" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.10.7.tgz#c74b9f6b7f1b4261ada2d24c832328aa4c394464" - integrity sha512-S49LSslNRxIflHzrIrEgK7mGQ7HzETr/FU0fyTbB0vubcmfzMoYTsgYdK7SUz583lovc+UvASoUAhPJI3e35ng== +"@angular-devkit/architect@^0.13.4": + version "0.13.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.13.4.tgz#f5db62f028e3c0971db5719be9c17a78fd8a67a2" + integrity sha512-wJF8oz8MurtpFi0ik42bkI2F5gEnuOe79KHPO1i3SYfdhEp5NY8igVKZ6chB/eq4Ml50aHxas8Hh9ke12K+Pxw== dependencies: - "@angular-devkit/core" "7.0.7" + "@angular-devkit/core" "7.3.4" rxjs "6.3.3" -"@angular-devkit/core@7.0.7": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.0.7.tgz#665176ad8421adfd5f3ea0b2c4a9a432a158b1bb" - integrity sha512-M8tTT9r3nUtWI3YyiyynHIQn+lQQgeKkxVZ+rdxvyvgE3U9+wn0yep5HkFLQETTuJetu9ARRRD94sD2XL3F/3A== - dependencies: - ajv "6.5.3" - chokidar "2.0.4" - fast-json-stable-stringify "2.0.0" - rxjs "6.3.3" - source-map "0.7.3" - "@angular-devkit/core@7.3.2", "@angular-devkit/core@^7.0.4": version "7.3.2" resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.3.2.tgz#67ac2cfcbe47f1e457929c19ab1b04c9e061b2e2" @@ -40,6 +29,17 @@ rxjs "6.3.3" source-map "0.7.3" +"@angular-devkit/core@7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.3.4.tgz#fae0521652c7430237025f117531ca3262ef8335" + integrity sha512-MBfen51iOBKfK4tlg5KwmPxePsF1QoFNUMGLuvUUwPkteonrGcupX1Q7NWTpf+HA+i08mOnZGuepeuQkD12IQw== + dependencies: + ajv "6.9.1" + chokidar "2.0.4" + fast-json-stable-stringify "2.0.0" + rxjs "6.3.3" + source-map "0.7.3" + "@angular-devkit/schematics@7.3.2", "@angular-devkit/schematics@^7.3.0-rc.0": version "7.3.2" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.3.2.tgz#e9d3c1f2128a56f66ea846ce3f80c69d4c3a9ae9" @@ -51,11 +51,11 @@ "@angular/bazel@file:../../dist/packages-dist/bazel": version "8.0.0-beta.6" dependencies: - "@angular-devkit/architect" "^0.10.6" + "@angular-devkit/architect" "^0.13.4" "@angular-devkit/core" "^7.0.4" "@angular-devkit/schematics" "^7.3.0-rc.0" "@bazel/typescript" "^0.26.0" - "@microsoft/api-extractor" "^7.0.17" + "@microsoft/api-extractor" "^7.0.21" "@schematics/angular" "^7.0.4" "@types/node" "6.0.84" semver "^5.6.0" @@ -112,26 +112,27 @@ source-map-support "0.5.9" tsutils "2.27.2" -"@microsoft/api-extractor@^7.0.17": - version "7.0.18" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.0.18.tgz#4eb931436495177dfcef8f2d8da3d084d10eebb6" - integrity sha512-puQisjyoYK1A0I8DqyBoLPV9noyFUlxTE3WsjhgJw//TrmegGHYmsRlD3rnHeXcKPM1F7sd/VKJXeXC3IPTf2Q== +"@microsoft/api-extractor@^7.0.21": + version "7.0.22" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.0.22.tgz#9c6eb0500168811ed14194de413ddd310622533a" + integrity sha512-1mNMbmUeqtjNmS9hgrONio2uRZL2eeZk39zCKq04ReImFXnMR+fnMTQG98B+SAkFFGJuOucwke5U0bgWnrG6kw== dependencies: - "@microsoft/node-core-library" "3.10.0" + "@microsoft/node-core-library" "3.12.0" "@microsoft/ts-command-line" "4.2.3" - "@microsoft/tsdoc" "0.12.5" + "@microsoft/tsdoc" "0.12.7" "@types/node" "8.5.8" "@types/z-schema" "3.16.31" colors "~1.2.1" lodash "~4.17.5" resolve "1.8.1" + source-map "~0.6.1" typescript "~3.1.6" z-schema "~3.18.3" -"@microsoft/node-core-library@3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@microsoft/node-core-library/-/node-core-library-3.10.0.tgz#70e089534d8e20f6a0f9c7a4a12a6aeafd6a1ddb" - integrity sha512-1SbU+XNYAabhV9noGXHtsUVPc5ELV+oEuJQtZQoCncbOd6WAMeTgB1xFwh96hmdEXyKQyML/pnByiKocmh/nbQ== +"@microsoft/node-core-library@3.12.0": + version "3.12.0" + resolved "https://registry.yarnpkg.com/@microsoft/node-core-library/-/node-core-library-3.12.0.tgz#f9c27b8bb6b55d60b91d4e1962f42b03b9f8f47f" + integrity sha512-9T2dEXmmxZqnqcpHuIB8mTAOM/DNSi/QcAwKYDjvZvkd+PGT5lCUXjM9GL7SaR2NPa3UrWDGgFhNoqLqLfEPbw== dependencies: "@types/fs-extra" "5.0.4" "@types/jju" "~1.4.0" @@ -152,10 +153,10 @@ argparse "~1.0.9" colors "~1.2.1" -"@microsoft/tsdoc@0.12.5": - version "0.12.5" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.12.5.tgz#c448a38902ccb5601c1b2ef3b1a105012ef7712c" - integrity sha512-xEAyvLXo4Cter/b0EMCWUZTgXOfLOPJ/Xr52WdjVclPx9eDmNTGFtZl8Pn/nqSnZsQBNcHL0eHk/YyRyyXXpiQ== +"@microsoft/tsdoc@0.12.7": + version "0.12.7" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.12.7.tgz#8fb4a9f4fdf01f1469c9fc54b0ad2d36ec57c25d" + integrity sha512-0bqNlQT8aR4Iq9xx/OsY579Zeqon9uTZDIuvl+XXu16TPPN2sASeKojwm366jA2MjgXd9iyTWpJM5/P1QJ4Dxg== "@schematics/angular@7.3.2", "@schematics/angular@^7.0.4": version "7.3.2" @@ -249,16 +250,6 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" -ajv@6.5.3: - version "6.5.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9" - integrity sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - ajv@6.9.1: version "6.9.1" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" @@ -2154,7 +2145,7 @@ source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0: +source-map@^0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== diff --git a/packages/bazel/package.json b/packages/bazel/package.json index c5d758923c..67c794cb93 100644 --- a/packages/bazel/package.json +++ b/packages/bazel/package.json @@ -18,7 +18,7 @@ } }, "dependencies": { - "@angular-devkit/architect": "^0.10.6", + "@angular-devkit/architect": "^0.13.4", "@angular-devkit/core": "^7.0.4", "@angular-devkit/schematics": "^7.3.0-rc.0", "@bazel/typescript": "^0.26.0", @@ -42,4 +42,4 @@ "ng-update": { "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } -} \ No newline at end of file +} diff --git a/packages/bazel/src/builders/BUILD.bazel b/packages/bazel/src/builders/BUILD.bazel index 23e4a0add8..142c80f60a 100644 --- a/packages/bazel/src/builders/BUILD.bazel +++ b/packages/bazel/src/builders/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library") +load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") package(default_visibility = ["//visibility:public"]) @@ -19,7 +19,7 @@ ts_library( ], data = [ "schema.json", - ], + ] + glob(["files/**/*"]), module_name = "@angular/bazel/src/builders", deps = [ "@npm//@angular-devkit/architect", @@ -28,3 +28,24 @@ 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", + ], +) diff --git a/packages/bazel/src/builders/bazel.ts b/packages/bazel/src/builders/bazel.ts index d1fe2cdbfb..90f07a586c 100644 --- a/packages/bazel/src/builders/bazel.ts +++ b/packages/bazel/src/builders/bazel.ts @@ -7,44 +7,145 @@ */ /// -import {spawn, spawnSync} from 'child_process'; -import {Observable, Subject} from 'rxjs'; + +import {Path, basename, dirname, getSystemPath, join} from '@angular-devkit/core'; +import {resolve} from '@angular-devkit/core/node'; +import {Host} from '@angular-devkit/core/src/virtual-fs/host'; +import {spawn} from 'child_process'; export type Executable = 'bazel' | 'ibazel'; 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: string, executable: Executable, command: Command, workspaceTarget: string, - flags: string[]): Observable { - const doneSubject = new Subject(); - const bin = require.resolve(`@bazel/${executable}`); - const buildProcess = spawn(bin, [command, workspaceTarget, ...flags], { - cwd: projectDir, - stdio: 'inherit', - shell: false, - }); + projectDir: Path, binary: Path, command: Command, workspaceTarget: string, flags: string[]) { + return new Promise((resolve, reject) => { + const buildProcess = spawn(getSystemPath(binary), [command, workspaceTarget, ...flags], { + cwd: getSystemPath(projectDir), + stdio: 'inherit', + shell: false, + }); - buildProcess.once('close', (code: number) => { - if (code === 0) { - doneSubject.next(); - } else { - doneSubject.error(`${executable} failed with code ${code}.`); - } - }); + process.on('SIGINT', (signal) => { + if (!buildProcess.killed) { + buildProcess.kill(); + reject(new Error(`Bazel process received ${signal}.`)); + } + }); - return doneSubject.asObservable(); + buildProcess.once('close', (code: number) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`${basename(binary)} failed with code ${code}.`)); + } + }); + }); } -export function checkInstallation(executable: Executable, projectDir: string) { - let bin: string; +/** + * Resolves the path to `@bazel/bazel` or `@bazel/ibazel`. + */ +export function checkInstallation(name: Executable, projectDir: Path): string { + const packageName = `@bazel/${name}`; try { - bin = require.resolve(`@bazel/${executable}`); - } catch { - return false; + return resolve(packageName, { + basedir: projectDir, + }); + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + throw new Error( + `Could not run ${name}. Please make sure that the ` + + `"${name}" command is installed by running ` + + `"npm install ${packageName}" or "yarn install ${packageName}".`); + } + throw error; } - const child = spawnSync(bin, ['version'], { - cwd: projectDir, - shell: false, - }); - return child.status === 0; +} + +/** + * Returns the absolute path to the template directory in `@angular/bazel`. + */ +export async function getTemplateDir(host: Host, root: Path): Promise { + const packageJson = resolve('@angular/bazel', { + basedir: root, + resolvePackageJson: true, + }); + const packageDir = dirname(packageJson as Path); + const templateDir = join(packageDir, 'src', 'builders', 'files'); + if (!await host.isDirectory(templateDir).toPromise()) { + throw new Error('Could not find Bazel template directory in "@angular/bazel".'); + } + return templateDir; +} + +/** + * Recursively list the specified 'dir' using depth-first approach. Paths + * returned are relative to 'dir'. + */ +function listR(host: Host, dir: Path): Promise { + async function list(dir: Path, root: Path, results: Path[]) { + const paths = await host.list(dir).toPromise(); + for (const path of paths) { + const absPath = join(dir, path); + const relPath = join(root, path); + if (await host.isFile(absPath).toPromise()) { + results.push(relPath); + } else { + await 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(); +} + +/** + * Copy Bazel files (WORKSPACE, BUILD.bazel, etc) from the template directory to + * the project `root` directory, and return the absolute paths of the files + * 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); + + await Promise.all(templates.map(async(template) => { + 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); + bazelFiles.push(dest); + } + } catch { + } + })); + + return bazelFiles; +} + +/** + * Delete the specified 'files' and return a promise that always resolves. + */ +export function deleteBazelFiles(host: Host, files: Path[]) { + return Promise.all(files.map(async(file) => { + try { + await host.delete(file).toPromise(); + } catch { + } + })); } diff --git a/packages/bazel/src/builders/bazel_spec.ts b/packages/bazel/src/builders/bazel_spec.ts new file mode 100644 index 0000000000..84bf86ddab --- /dev/null +++ b/packages/bazel/src/builders/bazel_spec.ts @@ -0,0 +1,42 @@ +/** + * @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}); + }); +}); diff --git a/packages/bazel/src/builders/files/BUILD.bazel.template b/packages/bazel/src/builders/files/BUILD.bazel.template new file mode 100644 index 0000000000..27b2fa1432 --- /dev/null +++ b/packages/bazel/src/builders/files/BUILD.bazel.template @@ -0,0 +1,7 @@ +package(default_visibility = ["//visibility:public"]) + +# This export allows targets in other packages to reference files that live +# in this package. +exports_files([ + "tsconfig.json", +]) diff --git a/packages/bazel/src/builders/files/WORKSPACE.template b/packages/bazel/src/builders/files/WORKSPACE.template new file mode 100644 index 0000000000..3d1fc71a93 --- /dev/null +++ b/packages/bazel/src/builders/files/WORKSPACE.template @@ -0,0 +1,60 @@ +# WARNING: This file is generated and it's not meant to be edited. +# Before making any changes, please read Bazel documentation. +# https://docs.bazel.build/versions/master/be/workspace.html +# The WORKSPACE file tells Bazel that this directory is a "workspace", which is like a project root. +# The content of this file specifies all the external dependencies Bazel needs to perform a build. + +#################################### +# ESModule imports (and TypeScript imports) can be absolute starting with the workspace name. +# The name of the workspace should match the npm package where we publish, so that these +# imports also make sense when referencing the published package. +workspace(name = "project") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +RULES_NODEJS_VERSION = "0.26.0" +http_archive( + name = "build_bazel_rules_nodejs", + sha256 = "5c86b055c57e15bf32d9009a15bcd6d8e190c41b1ff2fb18037b75e0012e4e7c", + url = "https://github.com/bazelbuild/rules_nodejs/releases/download/%s/rules_nodejs-%s.tar.gz" % (RULES_NODEJS_VERSION, RULES_NODEJS_VERSION), +) + +# Rules for compiling sass +RULES_SASS_VERSION = "1.17.2" +http_archive( + name = "io_bazel_rules_sass", + sha256 = "e5316ee8a09d1cbb732d3938b400836bf94dba91a27476e9e27706c4c0edae1f", + url = "https://github.com/bazelbuild/rules_sass/archive/%s.zip" % RULES_SASS_VERSION, + strip_prefix = "rules_sass-%s" % RULES_SASS_VERSION, +) + +#################################### +# Load and install our dependencies downloaded above. + +load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install") +check_bazel_version("0.22.0") +node_repositories() +yarn_install( + name = "npm", + data = ["//:angular-metadata.tsconfig.json"], + package_json = "//:package.json", + yarn_lock = "//:yarn.lock", +) + +load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies") +install_bazel_dependencies() + +load("@npm_bazel_karma//:package.bzl", "rules_karma_dependencies") +rules_karma_dependencies() + +load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories") +web_test_repositories() + +load("@npm_bazel_karma//:browser_repositories.bzl", "browser_repositories") +browser_repositories() + +load("@npm_bazel_typescript//:index.bzl", "ts_setup_workspace") +ts_setup_workspace() + +load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories") +sass_repositories() diff --git a/packages/bazel/src/builders/files/__dot__bazelignore.template b/packages/bazel/src/builders/files/__dot__bazelignore.template new file mode 100644 index 0000000000..de4d1f007d --- /dev/null +++ b/packages/bazel/src/builders/files/__dot__bazelignore.template @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/packages/bazel/src/builders/files/__dot__bazelrc.template b/packages/bazel/src/builders/files/__dot__bazelrc.template new file mode 100644 index 0000000000..1b544399da --- /dev/null +++ b/packages/bazel/src/builders/files/__dot__bazelrc.template @@ -0,0 +1,29 @@ +# Make TypeScript and Angular compilation fast, by keeping a few copies of the +# compiler running as daemons, and cache SourceFile AST's to reduce parse time. +build --strategy=TypeScriptCompile=worker +build --strategy=AngularTemplateCompile=worker + +# Don't create bazel-* symlinks in the WORKSPACE directory, except `bazel-out`, +# which is mandatory. +# These require .gitignore and may scare users. +# Also, it's a workaround for https://github.com/bazelbuild/rules_typescript/issues/12 +# which affects the common case of having `tsconfig.json` in the WORKSPACE directory. +# +# Instead, the output will appear in `dist/bin`. You'll need to ignore the +# `bazel-out` directory that is created in the workspace root. +build --symlink_prefix=dist/ + +# Turn on --incompatible_strict_action_env which was on by default +# in Bazel 0.21.0 but turned off again in 0.22.0. Follow +# https://github.com/bazelbuild/bazel/issues/7026 for more details. +# This flag is needed to so that the bazel cache is not invalidated +# when running bazel via `yarn bazel`. +# See https://github.com/angular/angular/issues/27514. +build --incompatible_strict_action_env +run --incompatible_strict_action_env +test --incompatible_strict_action_env + +test --test_output=errors + +# Use the Angular 6 compiler +build --define=compile=legacy diff --git a/packages/bazel/src/builders/files/e2e/BUILD.bazel.template b/packages/bazel/src/builders/files/e2e/BUILD.bazel.template new file mode 100644 index 0000000000..b38c34575f --- /dev/null +++ b/packages/bazel/src/builders/files/e2e/BUILD.bazel.template @@ -0,0 +1,50 @@ +load("@npm_bazel_typescript//:index.bzl", "ts_library") +load("@npm_angular_bazel//:index.bzl", "protractor_web_test_suite") + +ts_library( + name = "e2e_lib", + testonly = 1, + srcs = glob(["src/**/*.ts"]), + tsconfig = ":tsconfig.e2e.json", + deps = [ + "@npm//@types/jasmine", + "@npm//@types/jasminewd2", + "@npm//@types/node", + "@npm//jasmine", + "@npm//protractor", + "@npm//tslib", + ], + data = [ + "//:tsconfig.json", + ], +) + +protractor_web_test_suite( + name = "prodserver_test", + data = [ + "@npm//@angular/bazel", + "@npm//protractor", + ], + on_prepare = ":protractor.on-prepare.js", + server = "//src:prodserver", + deps = [":e2e_lib"], +) + +protractor_web_test_suite( + name = "devserver_test", + data = [ + "@npm//@angular/bazel", + "@npm//protractor", + ], + on_prepare = ":protractor.on-prepare.js", + server = "//src:devserver", + deps = [":e2e_lib"], +) + +# Default target in this package is to run the e2e tests on the devserver. +# This is a faster round-trip but doesn't exercise production optimizations like +# code-splitting and lazy loading. +alias( + name = "e2e", + actual = "devserver_test", +) diff --git a/packages/bazel/src/builders/files/src/BUILD.bazel.template b/packages/bazel/src/builders/files/src/BUILD.bazel.template new file mode 100644 index 0000000000..ab60972ae4 --- /dev/null +++ b/packages/bazel/src/builders/files/src/BUILD.bazel.template @@ -0,0 +1,153 @@ +package(default_visibility = ["//visibility:public"]) + +load("@npm_angular_bazel//:index.bzl", "ng_module") +load("@npm_bazel_karma//:index.bzl", "ts_web_test_suite") +load("@build_bazel_rules_nodejs//:defs.bzl", "rollup_bundle", "history_server") +load("@build_bazel_rules_nodejs//internal/web_package:web_package.bzl", "web_package") +load("@npm_bazel_typescript//:index.bzl", "ts_devserver", "ts_library") +load("@io_bazel_rules_sass//:defs.bzl", "multi_sass_binary") + +multi_sass_binary( + name = "styles", + srcs = glob(["**/*.scss"]), +) + +ng_module( + name = "src", + srcs = glob( + include = ["**/*.ts"], + exclude = [ + "**/*.spec.ts", + "main.ts", + "test.ts", + "initialize_testbed.ts", + ], + ), + assets = glob([ + "**/*.css", + "**/*.html", + ]) + ([":styles"] if len(glob(["**/*.scss"])) else []), + deps = [ + "@npm//@angular/core", + "@npm//@angular/platform-browser", + "@npm//@angular/router", + "@npm//@types", + "@npm//rxjs", + ], +) + +rollup_bundle( + name = "bundle", + entry_point = "src/main.prod", + deps = [ + "//src", + "@npm//rxjs", + ], +) + +web_package( + name = "prodapp", + assets = [ + # do not sort + "@npm//node_modules/zone.js:dist/zone.min.js", + ":bundle.min.js", + ], + data = [ + "favicon.ico", + ], + index_html = "index.html", +) + +history_server( + name = "prodserver", + data = [":prodapp"], + templated_args = ["src/prodapp"], +) + +filegroup( + name = "rxjs_umd_modules", + srcs = [ + # do not sort + "@npm//node_modules/rxjs:bundles/rxjs.umd.js", + ":rxjs_shims.js", + ], +) + +ts_devserver( + name = "devserver", + port = 4200, + entry_module = "project/src/main.dev", + serving_path = "/bundle.min.js", + scripts = [ + "@npm//node_modules/@angular/common:bundles/common.umd.js", + "@npm//node_modules/@angular/common:bundles/common-http.umd.js", + "@npm//node_modules/@angular/core:bundles/core.umd.js", + "@npm//node_modules/@angular/platform-browser:bundles/platform-browser.umd.js", + "@npm//node_modules/tslib:tslib.js", + ":rxjs_umd_modules", + ], + static_files = [ + "@npm//node_modules/zone.js:dist/zone.min.js", + ], + data = [ + "favicon.ico", + ], + index_html = "index.html", + deps = [":src"], +) + +ts_library( + name = "test_lib", + testonly = 1, + srcs = glob(["**/*.spec.ts"]), + deps = [ + ":src", + "@npm//@angular/core", + "@npm//@types", + ], +) + +ts_library( + name = "initialize_testbed", + testonly = 1, + srcs = [ + "initialize_testbed.ts", + ], + deps = [ + "@npm//@angular/core", + "@npm//@angular/platform-browser-dynamic", + "@npm//@types", + ], +) + +ts_web_test_suite( + name = "test", + srcs = [ + "@npm//node_modules/@angular/common:bundles/common.umd.js", + "@npm//node_modules/@angular/compiler:bundles/compiler.umd.js", + "@npm//node_modules/@angular/compiler:bundles/compiler-testing.umd.js", + "@npm//node_modules/@angular/core:bundles/core.umd.js", + "@npm//node_modules/@angular/core:bundles/core-testing.umd.js", + "@npm//node_modules/@angular/platform-browser:bundles/platform-browser.umd.js", + "@npm//node_modules/@angular/platform-browser:bundles/platform-browser-testing.umd.js", + "@npm//node_modules/@angular/platform-browser-dynamic:bundles/platform-browser-dynamic.umd.js", + "@npm//node_modules/@angular/platform-browser-dynamic:bundles/platform-browser-dynamic-testing.umd.js", + "@npm//node_modules/tslib:tslib.js", + ], + runtime_deps = [ + ":initialize_testbed", + ], + # do not sort + bootstrap = [ + "@npm//node_modules/zone.js:dist/zone-testing-bundle.js", + "@npm//node_modules/reflect-metadata:Reflect.js", + ], + browsers = [ + "@io_bazel_rules_webtesting//browsers:chromium-local", + ], + deps = [ + ":rxjs_umd_modules", + ":test_lib", + "@npm//karma-jasmine", + ], +) diff --git a/packages/bazel/src/builders/index.ts b/packages/bazel/src/builders/index.ts index cf268067fc..6df7a4a4ca 100644 --- a/packages/bazel/src/builders/index.ts +++ b/packages/bazel/src/builders/index.ts @@ -9,34 +9,35 @@ */ import {BuildEvent, Builder, BuilderConfiguration, BuilderContext} from '@angular-devkit/architect'; -import {getSystemPath, resolve} from '@angular-devkit/core'; -import {Observable, of } from 'rxjs'; -import {catchError, map} from 'rxjs/operators'; - -import {checkInstallation, runBazel} from './bazel'; +import {Path} from '@angular-devkit/core'; +import {Observable, from} from 'rxjs'; +import {checkInstallation, copyBazelFiles, deleteBazelFiles, getTemplateDir, runBazel} from './bazel'; import {Schema} from './schema'; class BazelBuilder implements Builder { constructor(private context: BuilderContext) {} - run(builderConfig: BuilderConfiguration>): Observable { - const projectRoot = getSystemPath(resolve(this.context.workspace.root, builderConfig.root)); - const targetLabel = builderConfig.options.targetLabel; + run(config: BuilderConfiguration>): Observable { + const {host, logger, workspace} = this.context; + const root: Path = workspace.root; + const {bazelCommand, targetLabel, watch} = config.options as Schema; + const executable = watch ? 'ibazel' : 'bazel'; + const binary = checkInstallation(executable, root) as Path; - const executable = builderConfig.options.watch ? 'ibazel' : 'bazel'; - - if (!checkInstallation(executable, projectRoot)) { - throw new Error( - `Could not run ${executable}. Please make sure that the ` + - `"${executable}" command is installed by running ` + - `"npm install" or "yarn install".`); - } - - // TODO: Support passing flags. - return runBazel( - projectRoot, executable, builderConfig.options.bazelCommand !, targetLabel !, - [] /* flags */) - .pipe(map(() => ({success: true})), catchError(() => of ({success: false})), ); + return from(Promise.resolve().then(async() => { + const templateDir = await getTemplateDir(host, root); + const bazelFiles = await copyBazelFiles(host, root, templateDir); + try { + const flags: string[] = []; + await runBazel(root, binary, bazelCommand, targetLabel, flags); + return {success: true}; + } catch (err) { + logger.error(err.message); + return {success: false}; + } finally { + await deleteBazelFiles(host, bazelFiles); // this will never throw + } + })); } }