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 + } + })); } }