From 46d7daf511a3b26a95f51f63461aada0bb4538d9 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Tue, 14 Aug 2018 09:30:25 -0700 Subject: [PATCH] Fine-grained npm_install & yarn_install deps --- .bazelci/presubmit.yml | 7 +- README.md | 71 ++- WORKSPACE | 28 +- docs/BUILD.bazel | 3 + examples/bazel_managed_deps/BUILD.bazel | 32 ++ examples/bazel_managed_deps/README.md | 19 + examples/bazel_managed_deps/WORKSPACE | 22 + examples/bazel_managed_deps/coarse.spec.js | 6 + examples/bazel_managed_deps/common.spec.js | 23 + examples/bazel_managed_deps/fine.spec.js | 11 + examples/bazel_managed_deps/package.json | 13 + examples/bazel_managed_deps/yarn.lock | 93 ++++ internal/e2e/fine_grained_deps/BUILD.bazel | 58 +++ internal/e2e/fine_grained_deps/coarse.spec.js | 6 + internal/e2e/fine_grained_deps/common.spec.js | 23 + internal/e2e/fine_grained_deps/fine.spec.js | 11 + .../e2e/fine_grained_deps/package-lock.json | 137 ++++++ internal/e2e/fine_grained_deps/package.json | 12 + internal/e2e/fine_grained_deps/yarn.lock | 93 ++++ internal/jasmine_node_test/jasmine_runner.js | 3 + internal/node/node.bzl | 120 ++++- internal/node/node_loader.js | 2 +- internal/npm_install/BUILD.bazel | 6 + internal/npm_install/generate_build_file.js | 290 ++++++++++++ internal/npm_install/npm_install.bzl | 40 +- internal/npm_install/test/BUILD.bazel | 33 ++ internal/npm_install/test/BUILD.bazel.golden | 444 ++++++++++++++++++ internal/npm_install/test/check.js | 51 ++ .../test/generate_build_file.spec.js | 7 + internal/npm_install/test/package.json | 11 + internal/npm_install/test/update_golden.js | 3 + internal/npm_install/test/yarn.lock | 103 ++++ internal/test/node_resolve_dep/BUILD.bazel | 2 + package.json | 4 +- 34 files changed, 1728 insertions(+), 59 deletions(-) create mode 100644 examples/bazel_managed_deps/BUILD.bazel create mode 100644 examples/bazel_managed_deps/README.md create mode 100644 examples/bazel_managed_deps/WORKSPACE create mode 100644 examples/bazel_managed_deps/coarse.spec.js create mode 100644 examples/bazel_managed_deps/common.spec.js create mode 100644 examples/bazel_managed_deps/fine.spec.js create mode 100644 examples/bazel_managed_deps/package.json create mode 100644 examples/bazel_managed_deps/yarn.lock create mode 100644 internal/e2e/fine_grained_deps/BUILD.bazel create mode 100644 internal/e2e/fine_grained_deps/coarse.spec.js create mode 100644 internal/e2e/fine_grained_deps/common.spec.js create mode 100644 internal/e2e/fine_grained_deps/fine.spec.js create mode 100644 internal/e2e/fine_grained_deps/package-lock.json create mode 100644 internal/e2e/fine_grained_deps/package.json create mode 100644 internal/e2e/fine_grained_deps/yarn.lock create mode 100644 internal/npm_install/generate_build_file.js create mode 100644 internal/npm_install/test/BUILD.bazel create mode 100644 internal/npm_install/test/BUILD.bazel.golden create mode 100644 internal/npm_install/test/check.js create mode 100644 internal/npm_install/test/generate_build_file.spec.js create mode 100644 internal/npm_install/test/package.json create mode 100644 internal/npm_install/test/update_golden.js create mode 100644 internal/npm_install/test/yarn.lock diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index d7e7cc7ce0..76cf22d120 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -24,13 +24,14 @@ platforms: windows: run_targets: - "@nodejs//:yarn" + build_flags: + - "--build_tag_filters=-no_windows" build_targets: # Escape hyphens on later args - "--" - "//..." - # Cannot build the docsite on Windows, see - # https://github.com/bazelbuild/skydoc/issues/58 - - "-//docs" + test_flags: + - "--test_tag_filters=-no_windows" test_targets: - "//..." rbe_ubuntu1604: diff --git a/README.md b/README.md index 1cb2e463a8..d9945a3577 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,17 @@ then next you will create a `BUILD.bazel` file in your project root containing: ```python package(default_visibility = ["//visibility:public"]) -# NOTE: this may move to node_modules/BUILD in a later release -filegroup(name = "node_modules", srcs = glob(["node_modules/**/*"])) +filegroup( + name = "node_modules", + srcs = glob( + include = ["node_modules/**/*"], + # Files with spaces in the name are not legal Bazel labels + exclude = [ + "node_modules/**/* */**", + "node_modules/**/* *", + ], + ), +) ``` We recommend using the version of the package management tools installed by @@ -208,7 +217,7 @@ Using Yarn (preferred): load("@build_bazel_rules_nodejs//:defs.bzl", "yarn_install") yarn_install( - name = "foo", + name = "npm", package_json = "//:package.json", yarn_lock = "//:yarn.lock", ) @@ -220,26 +229,72 @@ Using NPM: load("@build_bazel_rules_nodejs//:defs.bzl", "npm_install") npm_install( - name = "foo", + name = "npm", package_json = "//:package.json", package_lock_json = "//:package-lock.json", ) ``` -You can then reference this version of `node_modules` in your `BUILD` rules via: +#### Fine-grained npm package dependencies + +You can then reference individual npm packages in your `BUILD` rules via: ```python load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") nodejs_binary( name = "bar", - # Ordinarily this defaults to //:node_modules - node_modules = "@foo//:node_modules", + data = [ + "@npm//:foo", + "@npm//:baz", + ] + ... +) +``` + +In this case, the `bar` nodejs_binary depends only the `foo` and `baz` npm packages +and all of their transitive deps. + +For other rules such as `jasmine_node_test`, fine grained +npm dependencies are specified in the `deps` attribute: + +```python +jasmine_node_test( + name = "test", ... + deps = [ + "@npm//:jasmine", + "@npm//:foo", + "@npm//:baz", + ... + ], ) ``` -With this approach, Bazel is responsible for making sure that `node_modules` is +#### Coarse node_modules dependencies + +Using fine grained npm dependencies is recommended to minimize +the number of inputs to your rules. However, for backward compatibility +there are also filegroups defined by `yarn_install` and `npm_install` +that include all packages under `node_modules` and which can be used +with the `node_modules` attribute of nodejs rules. + +* `@npm//:node_modules` is includes **all** files under `node_modules` +* `@npm//:node_modules_lite` is includes only `.js`, `.d.ts`, `.json` and `./bin/*` under `node_modules` (this reduces the number of input files) +* `@npm//:node_modules_none` is an empty file group which is useful when a nodejs_binary has no node_module dependencies but it still requires its `node_modules` attribute set + +```python +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") + +nodejs_binary( + name = "bar", + node_modules = "@npm//:node_modules_lite", +) +``` + +#### Why use Bazel-managed dependencies? + +With the Bazel-managed dependencies approach, Bazel is responsible for making sure that `node_modules` is up to date with `package[-lock].json` or `yarn.lock`. This means Bazel will set it up when the repo is first cloned, and rebuild it whenever it changes. diff --git a/WORKSPACE b/WORKSPACE index 043886c519..eaab36c91e 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -32,6 +32,11 @@ local_repository( path = "examples/packages", ) +local_repository( + name = "bazel_managed_deps", + path = "examples/bazel_managed_deps", +) + local_repository( name = "node_loader_e2e_no_preserve_symlinks", path = "internal/e2e/node_loader_no_preserve_symlinks", @@ -47,7 +52,7 @@ local_repository( path = "internal/test/node_resolve_dep", ) -load("//:defs.bzl", "node_repositories") +load("//:defs.bzl", "node_repositories", "yarn_install", "npm_install") # Install a hermetic version of node. # After this is run, these labels will be available: @@ -62,7 +67,8 @@ node_repositories( "//:package.json", "//examples/rollup:package.json", "@program_example//:package.json", - "//internal/test:package.json" + "//internal/test:package.json", + "//internal/npm_install/test:package.json", ], preserve_symlinks = True, ) @@ -87,4 +93,20 @@ skydoc_repositories() load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") go_rules_dependencies() -go_register_toolchains() \ No newline at end of file +go_register_toolchains() + +# +# Install npm dependencies for tests +# + +yarn_install( + name = "fine_grained_deps_yarn", + package_json = "//internal/e2e/fine_grained_deps:package.json", + yarn_lock = "//internal/e2e/fine_grained_deps:yarn.lock", +) + +npm_install( + name = "fine_grained_deps_npm", + package_json = "//internal/e2e/fine_grained_deps:package.json", + package_lock_json = "//internal/e2e/fine_grained_deps:package-lock.json", +) diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index f06d75da59..ba5b1a2d98 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -30,4 +30,7 @@ skylark_doc( format = "html", site_root = "/rules_nodejs", strip_prefix = "internal/", + # Cannot build the docsite on Windows, see + # https://github.com/bazelbuild/skydoc/issues/58 + tags = ["no_windows"], ) diff --git a/examples/bazel_managed_deps/BUILD.bazel b/examples/bazel_managed_deps/BUILD.bazel new file mode 100644 index 0000000000..0429582e87 --- /dev/null +++ b/examples/bazel_managed_deps/BUILD.bazel @@ -0,0 +1,32 @@ +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") + +# Test what happens when we depend on the catch-all "node_modules" rule rather +# than declare our dependencies on individual npm packages. +# This is the legacy behavior, so it also proves our backwards-compatibility +# story. +jasmine_node_test( + name = "test", + srcs = [ + "coarse.spec.js", + "common.spec.js", + ], + node_modules = "@npm//:node_modules", +) + +# Test what happens when only certain NPM packages are in our dependencies. +# These packages and their dependencies are copied to the execroot, but +# the rest are not. +jasmine_node_test( + name = "fine_grained_test", + srcs = [ + "common.spec.js", + "fine.spec.js", + ], + deps = [ + "@npm//:jasmine", + "@npm//:typescript", + # Note, test-b depends on test-a@0.0.1 which should get + # hoisted to node_modules/test-b/node_modules/test-a + "@npm//:@gregmagolan/test-b", + ], +) diff --git a/examples/bazel_managed_deps/README.md b/examples/bazel_managed_deps/README.md new file mode 100644 index 0000000000..71fb856c81 --- /dev/null +++ b/examples/bazel_managed_deps/README.md @@ -0,0 +1,19 @@ +# Fine-grained dependencies + +Declaring the entire `node_modules` directory as an input to every nodejs action +has performance problems. When using local sandboxing, every file is set up in +the kernel container for the sandboxed disk, this is slow on Mac. With remote +execution, we guarantee these files all need to be copied to the worker machine. + +Instead, we can declare individual npm packages as dependencies, e.g.: +``` +nodejs_binary( + name = "fast", + data = ["@my_npm_packages//:jasmine"] +) +``` + +and only the contents of `node_modules/jasmine/` will be copied to workers. + +See design doc: +https://docs.google.com/document/d/1BmQfTKhKMIsd27YKzWIhU7on-OVgRTSnUi1EHtHcceo/edit# diff --git a/examples/bazel_managed_deps/WORKSPACE b/examples/bazel_managed_deps/WORKSPACE new file mode 100644 index 0000000000..9fb84a827c --- /dev/null +++ b/examples/bazel_managed_deps/WORKSPACE @@ -0,0 +1,22 @@ +# BEGIN BORING BOILERPLATE +workspace(name = "bazel_managed_deps") +local_repository( + name = "build_bazel_rules_nodejs", + path = "../..", +) +load("@build_bazel_rules_nodejs//:package.bzl", "rules_nodejs_dependencies") +rules_nodejs_dependencies() + +load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories", "yarn_install") +node_repositories() +# END BORING BOILERPLATE + +# This runs yarn install, then our generate_build_file.js to create BUILD files +# inside the resulting node_modules directory. +# The name "npm" here means the resulting modules are referenced like +# @npm//:jasmine +yarn_install( + name = "npm", + package_json = "//:package.json", + yarn_lock = "//:yarn.lock", +) diff --git a/examples/bazel_managed_deps/coarse.spec.js b/examples/bazel_managed_deps/coarse.spec.js new file mode 100644 index 0000000000..f19cf3bc93 --- /dev/null +++ b/examples/bazel_managed_deps/coarse.spec.js @@ -0,0 +1,6 @@ +describe('dependencies', () => { + it('it should resolve test-a@0.0.2 which is under node_modules/test-a', () => { + const testA = require('@gregmagolan/test-a'); + expect(testA).toEqual('test-a-0.0.2'); + }); +}); diff --git a/examples/bazel_managed_deps/common.spec.js b/examples/bazel_managed_deps/common.spec.js new file mode 100644 index 0000000000..9ae34dc3fe --- /dev/null +++ b/examples/bazel_managed_deps/common.spec.js @@ -0,0 +1,23 @@ +const ts = require('typescript'); + +describe('dependencies', () => { + it('should get the typescript library', () => { + expect(ts.version).toBe('3.0.1'); + }); + + it(`should resolve transitive dependencies + Note that jasmine-core is not listed in our deps[] + but it is a transitive dependency of jasmine, which is in our deps.`, + () => { + require('jasmine-core'); + }); + + it(`should resolve @gregmagolan/test-b to version 0.0.2 with a @gregmagolan/test-a dependency of 0.0.1 + Note that @gregmagolan/test-a@0.0.2 is an explicit devDependency of this project, + so we are really testing that test-b will get the version it depends on, not + the hoisted one.`, + () => { + const testB = require('@gregmagolan/test-b'); + expect(testB).toEqual('test-b-0.0.2/test-a-0.0.1'); + }); +}); diff --git a/examples/bazel_managed_deps/fine.spec.js b/examples/bazel_managed_deps/fine.spec.js new file mode 100644 index 0000000000..e6b03be013 --- /dev/null +++ b/examples/bazel_managed_deps/fine.spec.js @@ -0,0 +1,11 @@ +describe('dependencies', () => { + it('should fail to resolve test-a since it has not been specified as a fine-grained direct dependency', + () => { + try { + require('@gregmagolan/test-a'); + expect(true).toBe(false); + } catch (err) { + expect(err.code).toBe('MODULE_NOT_FOUND'); + } + }); +}); diff --git a/examples/bazel_managed_deps/package.json b/examples/bazel_managed_deps/package.json new file mode 100644 index 0000000000..6bb49b9410 --- /dev/null +++ b/examples/bazel_managed_deps/package.json @@ -0,0 +1,13 @@ +{ + "name": "bazel_managed_deps", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "jasmine": "^3.2.0", + "typescript": "^3.0.1" + }, + "dependencies": { + "@gregmagolan/test-a": "^0.0.2", + "@gregmagolan/test-b": "^0.0.2" + } +} diff --git a/examples/bazel_managed_deps/yarn.lock b/examples/bazel_managed_deps/yarn.lock new file mode 100644 index 0000000000..622e118f57 --- /dev/null +++ b/examples/bazel_managed_deps/yarn.lock @@ -0,0 +1,93 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@gregmagolan/test-a@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-a/-/test-a-0.0.1.tgz#c90ebc0676f13b34400da8d1e55ffe5aa76655b4" + +"@gregmagolan/test-a@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-a/-/test-a-0.0.2.tgz#86ab79a55f44860b02d8275e22d282a1f82b8768" + +"@gregmagolan/test-b@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-b/-/test-b-0.0.2.tgz#7549fb9f39895c0d744c2c24c98b0e5817e46bd7" + dependencies: + "@gregmagolan/test-a" "0.0.1" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +glob@^7.0.6: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +jasmine-core@~3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.2.1.tgz#8e4ff5b861603ee83343f2b49eee6a0ffe9650ce" + +jasmine@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.2.0.tgz#b3a018454781805650e46578803d08e7cfdd7b3d" + dependencies: + glob "^7.0.6" + jasmine-core "~3.2.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +typescript@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.1.tgz#43738f29585d3a87575520a4b93ab6026ef11fdb" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/internal/e2e/fine_grained_deps/BUILD.bazel b/internal/e2e/fine_grained_deps/BUILD.bazel new file mode 100644 index 0000000000..389c84ffd6 --- /dev/null +++ b/internal/e2e/fine_grained_deps/BUILD.bazel @@ -0,0 +1,58 @@ +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") + +# Test what happens when we depend on the catch-all "node_modules" rule rather +# than declare our dependencies on individual npm packages. +# This is the legacy behavior, so it also proves our backwards-compatibility +# story. +jasmine_node_test( + name = "test_yarn", + srcs = [ + "coarse.spec.js", + "common.spec.js", + ], + node_modules = "@fine_grained_deps_yarn//:node_modules", +) + +# Also test for npm_install +jasmine_node_test( + name = "test_npm", + srcs = [ + "coarse.spec.js", + "common.spec.js", + ], + node_modules = "@fine_grained_deps_npm//:node_modules", +) + +# Test what happens when only certain NPM packages are in our dependencies. +# These packages and their dependencies are copied to the execroot, but +# the rest are not. +jasmine_node_test( + name = "fine_grained_test_yarn", + srcs = [ + "common.spec.js", + "fine.spec.js", + ], + deps = [ + "@fine_grained_deps_yarn//:jasmine", + "@fine_grained_deps_yarn//:typescript", + # Note, test-b depends on test-a@0.0.1 which should get + # hoisted to node_modules/test-b/node_modules/test-a + "@fine_grained_deps_yarn//:@gregmagolan/test-b", + ], +) + +# Also test for npm_install +jasmine_node_test( + name = "fine_grained_test_npm", + srcs = [ + "common.spec.js", + "fine.spec.js", + ], + deps = [ + "@fine_grained_deps_npm//:jasmine", + "@fine_grained_deps_npm//:typescript", + # Note, test-b depends on test-a@0.0.1 which should get + # hoisted to node_modules/test-b/node_modules/test-a + "@fine_grained_deps_npm//:@gregmagolan/test-b", + ], +) diff --git a/internal/e2e/fine_grained_deps/coarse.spec.js b/internal/e2e/fine_grained_deps/coarse.spec.js new file mode 100644 index 0000000000..f19cf3bc93 --- /dev/null +++ b/internal/e2e/fine_grained_deps/coarse.spec.js @@ -0,0 +1,6 @@ +describe('dependencies', () => { + it('it should resolve test-a@0.0.2 which is under node_modules/test-a', () => { + const testA = require('@gregmagolan/test-a'); + expect(testA).toEqual('test-a-0.0.2'); + }); +}); diff --git a/internal/e2e/fine_grained_deps/common.spec.js b/internal/e2e/fine_grained_deps/common.spec.js new file mode 100644 index 0000000000..586a970e3f --- /dev/null +++ b/internal/e2e/fine_grained_deps/common.spec.js @@ -0,0 +1,23 @@ +const ts = require('typescript'); + +describe('dependencies', () => { + it('should get the typescript library', () => { + expect(ts.version).toBe('3.0.3'); + }); + + it(`should resolve transitive dependencies + Note that jasmine-core is not listed in our deps[] + but it is a transitive dependency of jasmine, which is in our deps.`, + () => { + require('jasmine-core'); + }); + + it(`should resolve @gregmagolan/test-b to version 0.0.2 with a @gregmagolan/test-a dependency of 0.0.1 + Note that @gregmagolan/test-a@0.0.2 is an explicit devDependency of this project, + so we are really testing that test-b will get the version it depends on, not + the hoisted one.`, + () => { + const testB = require('@gregmagolan/test-b'); + expect(testB).toEqual('test-b-0.0.2/test-a-0.0.1'); + }); +}); diff --git a/internal/e2e/fine_grained_deps/fine.spec.js b/internal/e2e/fine_grained_deps/fine.spec.js new file mode 100644 index 0000000000..e6b03be013 --- /dev/null +++ b/internal/e2e/fine_grained_deps/fine.spec.js @@ -0,0 +1,11 @@ +describe('dependencies', () => { + it('should fail to resolve test-a since it has not been specified as a fine-grained direct dependency', + () => { + try { + require('@gregmagolan/test-a'); + expect(true).toBe(false); + } catch (err) { + expect(err.code).toBe('MODULE_NOT_FOUND'); + } + }); +}); diff --git a/internal/e2e/fine_grained_deps/package-lock.json b/internal/e2e/fine_grained_deps/package-lock.json new file mode 100644 index 0000000000..69bad5c853 --- /dev/null +++ b/internal/e2e/fine_grained_deps/package-lock.json @@ -0,0 +1,137 @@ +{ + "name": "fine_grained_deps", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@gregmagolan/test-a": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@gregmagolan/test-a/-/test-a-0.0.2.tgz", + "integrity": "sha512-u8pW4cm5Xk58fQeA1tGER3KwCfHZ1sBEx0YDWs5Spdi7tiw/21DN7btDvGqx4sG3d2UnUdAnvXPsdwArjNGdmg==" + }, + "@gregmagolan/test-b": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@gregmagolan/test-b/-/test-b-0.0.2.tgz", + "integrity": "sha512-h+LeJUbUued9XyQwxKMUdklGiGxPYJ1RvTAK9612ctCiMS2Fn0wu/Au5kHsMHxm8l4bOfpgAWmQ0OQQy7wUBCg==", + "requires": { + "@gregmagolan/test-a": "0.0.1" + }, + "dependencies": { + "@gregmagolan/test-a": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@gregmagolan/test-a/-/test-a-0.0.1.tgz", + "integrity": "sha512-nMZ3MKkXZ+uYbrm8R3dfdt3v1gOOLtf88CdDciWxMYGLr29oVjQG11y2fz4IRBR6R7hI2Gj+G9sHZ69wLTnjfA==" + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "jasmine": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.2.0.tgz", + "integrity": "sha512-qv6TZ32r+slrQz8fbx2EhGbD9zlJo3NwPrpLK1nE8inILtZO9Fap52pyHk7mNTh4tG50a+1+tOiWVT3jO5I0Sg==", + "dev": true, + "requires": { + "glob": "7.1.3", + "jasmine-core": "3.2.1" + } + }, + "jasmine-core": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.2.1.tgz", + "integrity": "sha512-pa9tbBWgU0EE4SWgc85T4sa886ufuQdsgruQANhECYjwqgV4z7Vw/499aCaP8ZH79JDS4vhm8doDG9HO4+e4sA==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "typescript": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.3.tgz", + "integrity": "sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/internal/e2e/fine_grained_deps/package.json b/internal/e2e/fine_grained_deps/package.json new file mode 100644 index 0000000000..e0851b1ae7 --- /dev/null +++ b/internal/e2e/fine_grained_deps/package.json @@ -0,0 +1,12 @@ +{ + "name": "fine_grained_deps", + "private": true, + "devDependencies": { + "jasmine": "^3.2.0", + "typescript": "^3.0.1" + }, + "dependencies": { + "@gregmagolan/test-a": "^0.0.2", + "@gregmagolan/test-b": "^0.0.2" + } +} diff --git a/internal/e2e/fine_grained_deps/yarn.lock b/internal/e2e/fine_grained_deps/yarn.lock new file mode 100644 index 0000000000..a31c01e074 --- /dev/null +++ b/internal/e2e/fine_grained_deps/yarn.lock @@ -0,0 +1,93 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@gregmagolan/test-a@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-a/-/test-a-0.0.1.tgz#c90ebc0676f13b34400da8d1e55ffe5aa76655b4" + +"@gregmagolan/test-a@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-a/-/test-a-0.0.2.tgz#86ab79a55f44860b02d8275e22d282a1f82b8768" + +"@gregmagolan/test-b@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-b/-/test-b-0.0.2.tgz#7549fb9f39895c0d744c2c24c98b0e5817e46bd7" + dependencies: + "@gregmagolan/test-a" "0.0.1" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +glob@^7.0.6: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +jasmine-core@~3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.2.1.tgz#8e4ff5b861603ee83343f2b49eee6a0ffe9650ce" + +jasmine@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.2.0.tgz#b3a018454781805650e46578803d08e7cfdd7b3d" + dependencies: + glob "^7.0.6" + jasmine-core "~3.2.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +typescript@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.3.tgz#4853b3e275ecdaa27f78fda46dc273a7eb7fc1c8" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/internal/jasmine_node_test/jasmine_runner.js b/internal/jasmine_node_test/jasmine_runner.js index c87e8fb0c8..4815293bf7 100644 --- a/internal/jasmine_node_test/jasmine_runner.js +++ b/internal/jasmine_node_test/jasmine_runner.js @@ -28,6 +28,9 @@ function main(args) { fs.readFileSync(manifest, UTF8) .split('\n') .filter(l => l.length > 0) + // OOPS! If we don't do any filtering, and you have deps=["@npm//:typescript"] + // then we will try to execute tsc, print its help, and process.exit(1) + .filter(f => f.endsWith('spec.js') || f.endsWith('test.js')) .forEach(f => jrunner.addSpecFile(f)); var noSpecsFound = true; diff --git a/internal/node/node.bzl b/internal/node/node.bzl index 2224e48a9d..6b0cc4b7be 100644 --- a/internal/node/node.bzl +++ b/internal/node/node.bzl @@ -23,6 +23,12 @@ load("//internal/common:module_mappings.bzl", "module_mappings_runtime_aspect") load("//internal/common:sources_aspect.bzl", "sources_aspect") load("//internal/common:expand_into_runfiles.bzl", "expand_location_into_runfiles") +NodeModuleInfo = provider( + doc = "This provider contains information about npm dependencies installed with yarn_install and npm_install rules", + fields = { + "workspace": "The workspace name that the npm dependencies are provided from" + }) + def _write_loader_script(ctx): # Generates the JavaScript snippet of module roots mappings, with each entry # in the form: @@ -34,11 +40,27 @@ def _write_loader_script(ctx): escaped = mn.replace("/", r"\/").replace(".", r"\.") mapping = r"{module_name: /^%s\b/, module_root: '%s'}" % (escaped, mr) module_mappings.append(mapping) - workspace = ctx.attr.node_modules.label.workspace_root.split("/")[1] if ctx.attr.node_modules.label.workspace_root else ctx.workspace_name - node_modules_root = "/".join([f for f in [ - workspace, - ctx.attr.node_modules.label.package, - "node_modules"] if f]) + node_modules_root = None + if ctx.attr.node_modules: + workspace = ctx.attr.node_modules.label.workspace_root.split("/")[1] if ctx.attr.node_modules.label.workspace_root else ctx.workspace_name + node_modules_root = "/".join([f for f in [ + workspace, + ctx.attr.node_modules.label.package, + "node_modules"] if f]) + for d in ctx.attr.data: + if NodeModuleInfo in d: + possible_root = "/".join([d[NodeModuleInfo].workspace, "node_modules"]) + if not node_modules_root: + node_modules_root = possible_root + elif node_modules_root != possible_root: + fail("All npm dependencies need to come from a single workspace. found", [node_modules_root, possible_root]) + if not node_modules_root: + fail(""" + Due to a breaking change in rules_nodejs, target %s + must now declare either an explicit node_modules attribute, or + list explicit deps[] or data[] dependencies on npm labels. + + See http://FIXME""" % ctx.label) ctx.actions.expand_template( template=ctx.file._loader_template, output=ctx.outputs.loader, @@ -113,6 +135,20 @@ def _nodejs_binary_impl(ctx): ), )] +def _collect_node_modules_aspect_impl(target, ctx): + nm_wksp = None + + if hasattr(ctx.rule.attr, "tags") and "FILEGROUP_IS_NODE_MODULE" in ctx.rule.attr.tags: + nm_wksp = target.label.workspace_root.split("/")[1] if target.label.workspace_root else ctx.workspace_name + return [NodeModuleInfo(workspace = nm_wksp)] + + return [] + +_collect_node_modules_aspect = aspect( + implementation = _collect_node_modules_aspect_impl, + attr_aspects = ["deps"], +) + _NODEJS_EXECUTABLE_ATTRS = { "entry_point": attr.string( doc = """The script which should be executed first, usually containing a main function. @@ -135,7 +171,7 @@ _NODEJS_EXECUTABLE_ATTRS = { doc = """Runtime dependencies which may be loaded during execution.""", allow_files = True, cfg = "data", - aspects=[sources_aspect, module_mappings_runtime_aspect]), + aspects = [sources_aspect, module_mappings_runtime_aspect, _collect_node_modules_aspect]), "templated_args": attr.string_list( doc = """Arguments which are passed to every execution of the program. To pass a node startup option, prepend it with `--node_options=`, e.g. @@ -144,13 +180,71 @@ _NODEJS_EXECUTABLE_ATTRS = { ), "node_modules": attr.label( doc = """The npm packages which should be available to `require()` during - execution.""", - # By default, binaries use the node_modules in the workspace - # where the bazel command is run. This assumes that any needed - # dependencies are installed there, commonly due to a transitive - # dependency on a package like @bazel/typescript. - # See discussion: https://github.com/bazelbuild/rules_typescript/issues/13 - default = Label("@//:node_modules")), + execution. + + # TODO(gregmagolan): update the version here before release + This attribute is DEPRECATED. As of version X.X.X the recommended approach + to npm dependencies is to use fine grained npm dependencies which are setup + with the `yarn_install` or `npm_install` rules. For example, in targets + that used a `//:node_modules` filegroup, + + ``` + nodejs_binary( + name = "my_binary", + ... + node_modules = "//:node_modules", + ) + ``` + + which specifies all files within the `//:node_modules` filegroup + to be inputs to the `my_binary`. Using fine grained npm dependencies, + `my_binary` is defined with only the npm dependencies that are + needed: + + ``` + nodejs_binary( + name = "my_binary", + ... + data = [ + "@npm//:foo", + "@npm//:bar", + ... + ], + ) + ``` + + In this case, only the `foo` and `bar` npm packages and their + transitive deps are includes as inputs to the `my_binary` target + which reduces the time required to setup the runfiles for this + target (see https://github.com/bazelbuild/bazel/issues/5153). + + The @npm external repository and the fine grained npm package + targets are setup using the `yarn_install` or `npm_install` rule + in your WORKSPACE file: + + yarn_install( + name = "npm", + package_json = "//:package.json", + yarn_lock = "//:yarn.lock", + ) + + For other rules such as `jasmine_node_test`, fine grained + npm dependencies are specified in the `deps` attribute: + + ``` + jasmine_node_test( + name = "my_test", + ... + deps = [ + "@npm//:jasmine", + "@npm//:foo", + "@npm//:bar", + ... + ], + ) + ``` + """, + ), "node": attr.label( doc = """The node entry point target.""", default = Label("@nodejs//:node"), diff --git a/internal/node/node_loader.js b/internal/node/node_loader.js index 17746e393e..c4a79f27b4 100644 --- a/internal/node/node_loader.js +++ b/internal/node/node_loader.js @@ -435,7 +435,7 @@ module.constructor._resolveFilename = function(request, parent) { } const error = new Error( - `TEMPLATED_target cannot find module '${request}'\n looked in:` + + `TEMPLATED_target cannot find module '${request}' required by '${parentFilename}'\n looked in:` + failedResolutions.map(r => `\n ${r}\n`)); error.code = 'MODULE_NOT_FOUND'; throw error; diff --git a/internal/npm_install/BUILD.bazel b/internal/npm_install/BUILD.bazel index e90a2321f6..820db4e6c4 100644 --- a/internal/npm_install/BUILD.bazel +++ b/internal/npm_install/BUILD.bazel @@ -1,2 +1,8 @@ # Exported to be consumed for generating skydoc. exports_files(["npm_install.bzl"]) + +filegroup( + name = "generate_build_file", + srcs = ["generate_build_file.js"], + visibility = ["//internal:__subpackages__"], +) diff --git a/internal/npm_install/generate_build_file.js b/internal/npm_install/generate_build_file.js new file mode 100644 index 0000000000..5d940a759e --- /dev/null +++ b/internal/npm_install/generate_build_file.js @@ -0,0 +1,290 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const BUILD_FILE_HEADER = `# Generated file from yarn_install rule. +# See $(bazel info output_base)/external/build_bazel_rules_nodejs/internal/npm_install/generate_build_file.js + +# All rules in other repositories can use these targets +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") + +# Provide the entire node_modules directory in one catch-all rule. +# NB. this has bad performance implications if there are many files. +filegroup( + name = "node_modules", + srcs = glob( + include = ["node_modules/**/*"], + exclude = [ + # Files under test & docs may contain file names that + # are not legal Bazel labels (e.g., + # node_modules/ecstatic/test/public/中文/檔案.html) + "node_modules/**/test/**", + "node_modules/**/docs/**", + # Files with spaces in the name are not legal Bazel labels + "node_modules/**/* */**", + "node_modules/**/* *", + ], + ), +) + +# Provide a lite version of the node_modules filegroup that includes +# only js, d.ts and json files as well as the .bin folder. This can +# be used in some cases to improve performance by reducing the number +# of runfiles. The recommended approach to reducing performance +# is to use fine grained deps such as ["@npm//:a", "@npm://b", ...] +filegroup( + name = "node_modules_lite", + srcs = glob( + include = [ + "node_modules/**/*.js", + "node_modules/**/*.d.ts", + "node_modules/**/*.json", + "node_modules/.bin/*", + ], + exclude = [ + # Files under test & docs may contain file names that + # are not legal Bazel labels (e.g., + # node_modules/ecstatic/test/public/中文/檔案.html) + "node_modules/**/test/**", + "node_modules/**/docs/**", + # Files with spaces in the name are not legal Bazel labels + "node_modules/**/* */**", + "node_modules/**/* *", + ], + ), +) + +# Provide a node_modules_none filegroup which is useful +# for nodejs_binary targets with no npm dependencies +# but that still need to specify a node_modules attribute +filegroup( + name = "node_modules_none", + srcs = [], +) + +` + +if (require.main === module) { + main(); +} + +/** + * Main entrypoint. + * Write BUILD file. + */ +function main() { + // find all packages (including packages in nested node_modules) + const pkgs = findPackages(); + + // flatten dependencies + const pkgsMap = new Map(); + pkgs.forEach(pkg => pkgsMap.set(pkg._dir, pkg)); + pkgs.forEach(pkg => flattenDependencies(pkg, pkg, pkgsMap)); + + // generate the BUILD file + let buildFile = BUILD_FILE_HEADER; + pkgs.filter(pkg => !pkg._isNested).forEach(pkg => buildFile += printPackage(pkg)); + fs.writeFileSync('BUILD.bazel', buildFile); +} + +module.exports = {main}; + +/** + * Checks if a path is an npm package which is is a directory with a package.json file. + */ +function isPackage(p) { + // + const packageJson = path.posix.join(p, 'package.json'); + return fs.statSync(p).isDirectory() && fs.existsSync(packageJson) && + fs.statSync(packageJson).isFile(); +} + +/** + * Finds and returns an array of all packages under a given path. + */ +function findPackages(p = 'node_modules') { + if (!fs.existsSync(p) || !fs.statSync(p).isDirectory()) { + return []; + } + + const result = []; + + const listing = fs.readdirSync(p); + + const packages = listing.filter(f => !f.startsWith('@')) + .map(f => path.posix.join(p, f)) + .filter(f => isPackage(f)); + packages.forEach( + f => result.push(parsePackage(f), ...findPackages(path.posix.join(f, 'node_modules')))); + + const scopes = listing.filter(f => f.startsWith('@')) + .map(f => path.posix.join(p, f)) + .filter(f => fs.statSync(f).isDirectory()); + scopes.forEach(f => result.push(...findPackages(f))); + + return result; +} + +/** + * Given the name of a top-level folder in node_modules, parse the + * package json and return it as an object along with + * some additional internal attributes prefixed with '_'. + */ +function parsePackage(p) { + // Parse the package.json file of this package + const pkg = JSON.parse(fs.readFileSync(`${p}/package.json`, {encoding: 'utf8'})); + + // Trim the leading node_modules from the path and + // assign to _dir for future use + pkg._dir = p.replace(/^node_modules\//, ''); + + // Keep track of whether or not this is a nested package + pkg._isNested = p.match(/\/node_modules\//); + + // Initialize _dependencies to an empty array + // which is later filled with the flattened dependency list + pkg._dependencies = []; + + // For root packages, transform the pkg.bin entries + // into a new Map called _executables + pkg._executables = new Map(); + if (!pkg._isNested) { + if (Array.isArray(pkg.bin)) { + // should not happen, but ignore it if present + } else if (typeof pkg.bin === 'string') { + pkg._executables.set(pkg._dir, cleanupBinPath(pkg.bin)); + } else if (typeof pkg.bin === 'object') { + for (let key in pkg.bin) { + pkg._executables.set(key, cleanupBinPath(pkg.bin[key])); + } + } + } + + return pkg; +} + +/** + * Given a path, remove './' if it exists. + */ +function cleanupBinPath(path) { + // Bin paths usually come in 2 flavors: './bin/foo' or 'bin/foo', + // sometimes other stuff like 'lib/foo'. Remove prefix './' if it + // exists. + path = path.replace(/\\/g, '/'); + if (path.indexOf('./') === 0) { + path = path.slice(2); + } + return path; +} + +/** + * Flattens all transitive dependencies of a package + * into a _dependencies array. + */ +function flattenDependencies(pkg, dep, pkgsMap) { + if (pkg._dependencies.indexOf(dep) !== -1) { + // circular dependency + return; + } + pkg._dependencies.push(dep); + const findDeps = function(targetDeps, required) { + Object.keys(targetDeps || {}) + .map(targetDep => { + // look for matching nested package + const dirSegments = dep._dir.split('/'); + while (dirSegments.length) { + const maybe = path.posix.join(...dirSegments, 'node_modules', targetDep); + if (pkgsMap.has(maybe)) { + return pkgsMap.get(maybe); + } + dirSegments.pop(); + } + // look for matching root package + if (pkgsMap.has(targetDep)) { + return pkgsMap.get(targetDep); + } + // dependency not found + if (required) { + throw new Error(`Could not find required dep ${targetDep} of ${dep._dir}`) + } + return null; + }) + .filter(dep => !!dep) + .map(dep => flattenDependencies(pkg, dep, pkgsMap)); + }; + findDeps(dep.dependencies, true); + findDeps(dep.peerDependencies, true); + findDeps(dep.optionalDependencies, false); +} + +/** + * Reformat/pretty-print a json object as a skylark comment (each line + * starts with '# '). + */ +function printJson(pkg) { + // Clone and modify _dependencies to avoid circular issues when JSONifying + const cloned = {...pkg}; + cloned._dependencies = cloned._dependencies.map(dep => dep._dir); + return JSON.stringify(cloned, null, 2).split('\n').map(line => `# ${line}`).join('\n'); +} + +/** + * Given a pkg, print a skylark `node_module` rule. + */ +function printPackage(pkg) { + let result = `# Generated target for npm package "${pkg._dir}" +${printJson(pkg)} +filegroup( + name = "${pkg._dir}", + srcs = [ + # ${pkg._dir} package contents (and contents of nested node_modules) + ":${pkg._dir}__files", + # direct or transitive dependencies hoisted to root by the package manager + ${ + pkg._dependencies.filter(dep => dep != pkg) + .filter(dep => !dep._isNested) + .map(dep => `":${dep._dir}__files",`) + .join('\n ')} + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) + +filegroup( + name = "${pkg._dir}__files", + srcs = glob( + include = ["node_modules/${pkg._dir}/**/*"], + exclude = [ + # Files under test & docs may contain file names that + # are not legal Bazel labels (e.g., + # node_modules/ecstatic/test/public/中文/檔案.html) + "node_modules/${pkg._dir}/test/**", + "node_modules/${pkg._dir}/docs/**", + # Files with spaces in the name are not legal Bazel labels + "node_modules/${pkg._dir}/**/* */**", + "node_modules/${pkg._dir}/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) + +`; + + if (pkg._executables) { + for (const [name, path] of pkg._executables.entries()) { + result += `# Wire up the \`bin\` entry \`${name}\` +nodejs_binary( + name = "${pkg._dir}/${name}", + entry_point = "${pkg._dir}/${path}", + install_source_map_support = False, + data = [":${pkg._dir}"], +) + +`; + } + } + + return result; +} diff --git a/internal/npm_install/npm_install.bzl b/internal/npm_install/npm_install.bzl index fa1d59bb17..5be8889914 100644 --- a/internal/npm_install/npm_install.bzl +++ b/internal/npm_install/npm_install.bzl @@ -25,32 +25,12 @@ load("//internal/node:node_labels.bzl", "get_node_label", "get_npm_label", "get_ load("//internal/common:os_name.bzl", "os_name") def _create_build_file(repository_ctx): - if repository_ctx.attr.node_modules_filegroup: - repository_ctx.file("BUILD", """ -package(default_visibility = ["//visibility:public"]) -""" + repository_ctx.attr.node_modules_filegroup) - else: - repository_ctx.file("BUILD", """ -package(default_visibility = ["//visibility:public"]) -filegroup( - name = "node_modules", - srcs = glob(["node_modules/**/*"], - # Exclude directories that commonly contain filenames which are - # illegal bazel labels - exclude = [ - # e.g. node_modules/adm-zip/test/assets/attributes_test/New folder/hidden.txt - "node_modules/**/test/**", - # e.g. node_modules/xpath/docs/function resolvers.md - "node_modules/**/docs/**", - # e.g. node_modules/puppeteer/.local-chromium/mac-536395/chrome-mac/Chromium.app/Contents/Versions/66.0.3347.0/Chromium Framework.framework/Chromium Framework - "node_modules/**/.*/**", - # Exclude files with spaces in their name; these are not legal labels - "node_modules/**/* */**", - "node_modules/**/* *", - ], - ) + glob(["node_modules/.bin/*"]), -) -""") + node = repository_ctx.path(get_node_label(repository_ctx)) + repository_ctx.template("internal/generate_build_file.js", + repository_ctx.path(Label("//internal/npm_install:generate_build_file.js")), {}) + result = repository_ctx.execute([node, "internal/generate_build_file.js"]) + if result.return_code: + fail("node failed: \nSTDOUT:\n%s\nSTDERR:\n%s" % (result.stdout, result.stderr)) def _add_data_dependencies(repository_ctx): """Add data dependencies to the repository.""" @@ -64,8 +44,6 @@ def _add_data_dependencies(repository_ctx): def _npm_install_impl(repository_ctx): """Core implementation of npm_install.""" - _create_build_file(repository_ctx) - is_windows = os_name(repository_ctx).find("windows") != -1 node = get_node_label(repository_ctx) npm = get_npm_label(repository_ctx) @@ -124,6 +102,8 @@ cd "{root}" && "{npm}" {npm_args} if result.return_code: fail("remove_npm_absolute_paths failed: %s (%s)" % (result.stdout, result.stderr)) + _create_build_file(repository_ctx) + npm_install = repository_rule( attrs = { "package_json": attr.label( @@ -156,8 +136,6 @@ npm_install = repository_rule( def _yarn_install_impl(repository_ctx): """Core implementation of yarn_install.""" - _create_build_file(repository_ctx) - # Put our package descriptors in the right place. repository_ctx.symlink( repository_ctx.attr.package_json, @@ -190,6 +168,8 @@ def _yarn_install_impl(repository_ctx): if result.return_code: fail("yarn_install failed: %s (%s)" % (result.stdout, result.stderr)) + _create_build_file(repository_ctx) + yarn_install = repository_rule( attrs = { "package_json": attr.label( diff --git a/internal/npm_install/test/BUILD.bazel b/internal/npm_install/test/BUILD.bazel new file mode 100644 index 0000000000..a61e8a8ae4 --- /dev/null +++ b/internal/npm_install/test/BUILD.bazel @@ -0,0 +1,33 @@ +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test", "nodejs_binary") + +filegroup( + name = "node_modules", + srcs = glob(["node_modules/**"]), +) + +jasmine_node_test( + name = "test", + srcs = glob(["*.spec.js"]), + data = [ + ":BUILD.bazel.golden", + ":check.js", + ":node_modules", + ":yarn.lock", + ], + node_modules = ":node_modules", + deps = ["//internal/npm_install:generate_build_file"], +) + +nodejs_binary( + name = "test-accept", + data = [ + ":BUILD.bazel.golden", + ":check.js", + ":node_modules", + ":update_golden.js", + ":yarn.lock", + "//internal/npm_install:generate_build_file", + ], + entry_point = "build_bazel_rules_nodejs/internal/npm_install/test/update_golden.js", + node_modules = ":node_modules", +) diff --git a/internal/npm_install/test/BUILD.bazel.golden b/internal/npm_install/test/BUILD.bazel.golden new file mode 100644 index 0000000000..c511ba7e25 --- /dev/null +++ b/internal/npm_install/test/BUILD.bazel.golden @@ -0,0 +1,444 @@ + +package(default_visibility = ["//visibility:public"]) +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +filegroup( + name = "node_modules", + srcs = glob( + include = ["node_modules/**/*"], + exclude = [ + "node_modules/**/test/**", + "node_modules/**/docs/**", + "node_modules/**/* */**", + "node_modules/**/* *", + ], + ), +) +filegroup( + name = "node_modules_lite", + srcs = glob( + include = [ + "node_modules/**/*.js", + "node_modules/**/*.d.ts", + "node_modules/**/*.json", + "node_modules/.bin/*", + ], + exclude = [ + "node_modules/**/test/**", + "node_modules/**/docs/**", + "node_modules/**/* */**", + "node_modules/**/* *", + ], + ), +) +filegroup( + name = "node_modules_none", + srcs = [], +) +filegroup( + name = "balanced-match", + srcs = [ + ":balanced-match__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "balanced-match__files", + srcs = glob( + include = ["node_modules/balanced-match/**/*"], + exclude = [ + "node_modules/balanced-match/test/**", + "node_modules/balanced-match/docs/**", + "node_modules/balanced-match/**/* */**", + "node_modules/balanced-match/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "brace-expansion", + srcs = [ + ":brace-expansion__files", + ":balanced-match__files", + ":concat-map__files", + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "brace-expansion__files", + srcs = glob( + include = ["node_modules/brace-expansion/**/*"], + exclude = [ + "node_modules/brace-expansion/test/**", + "node_modules/brace-expansion/docs/**", + "node_modules/brace-expansion/**/* */**", + "node_modules/brace-expansion/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "concat-map", + srcs = [ + ":concat-map__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "concat-map__files", + srcs = glob( + include = ["node_modules/concat-map/**/*"], + exclude = [ + "node_modules/concat-map/test/**", + "node_modules/concat-map/docs/**", + "node_modules/concat-map/**/* */**", + "node_modules/concat-map/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "diff", + srcs = [ + ":diff__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "diff__files", + srcs = glob( + include = ["node_modules/diff/**/*"], + exclude = [ + "node_modules/diff/test/**", + "node_modules/diff/docs/**", + "node_modules/diff/**/* */**", + "node_modules/diff/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "fs.realpath", + srcs = [ + ":fs.realpath__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "fs.realpath__files", + srcs = glob( + include = ["node_modules/fs.realpath/**/*"], + exclude = [ + "node_modules/fs.realpath/test/**", + "node_modules/fs.realpath/docs/**", + "node_modules/fs.realpath/**/* */**", + "node_modules/fs.realpath/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "glob", + srcs = [ + ":glob__files", + ":fs.realpath__files", + ":inflight__files", + ":once__files", + ":wrappy__files", + ":inherits__files", + ":minimatch__files", + ":brace-expansion__files", + ":balanced-match__files", + ":concat-map__files", + ":path-is-absolute__files", + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "glob__files", + srcs = glob( + include = ["node_modules/glob/**/*"], + exclude = [ + "node_modules/glob/test/**", + "node_modules/glob/docs/**", + "node_modules/glob/**/* */**", + "node_modules/glob/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "inflight", + srcs = [ + ":inflight__files", + ":once__files", + ":wrappy__files", + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "inflight__files", + srcs = glob( + include = ["node_modules/inflight/**/*"], + exclude = [ + "node_modules/inflight/test/**", + "node_modules/inflight/docs/**", + "node_modules/inflight/**/* */**", + "node_modules/inflight/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "inherits", + srcs = [ + ":inherits__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "inherits__files", + srcs = glob( + include = ["node_modules/inherits/**/*"], + exclude = [ + "node_modules/inherits/test/**", + "node_modules/inherits/docs/**", + "node_modules/inherits/**/* */**", + "node_modules/inherits/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "jasmine", + srcs = [ + ":jasmine__files", + ":glob__files", + ":fs.realpath__files", + ":inflight__files", + ":once__files", + ":wrappy__files", + ":inherits__files", + ":minimatch__files", + ":brace-expansion__files", + ":balanced-match__files", + ":concat-map__files", + ":path-is-absolute__files", + ":jasmine-core__files", + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "jasmine__files", + srcs = glob( + include = ["node_modules/jasmine/**/*"], + exclude = [ + "node_modules/jasmine/test/**", + "node_modules/jasmine/docs/**", + "node_modules/jasmine/**/* */**", + "node_modules/jasmine/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +nodejs_binary( + name = "jasmine/jasmine", + entry_point = "jasmine/bin/jasmine.js", + install_source_map_support = False, + data = [":jasmine"], +) +filegroup( + name = "jasmine-core", + srcs = [ + ":jasmine-core__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "jasmine-core__files", + srcs = glob( + include = ["node_modules/jasmine-core/**/*"], + exclude = [ + "node_modules/jasmine-core/test/**", + "node_modules/jasmine-core/docs/**", + "node_modules/jasmine-core/**/* */**", + "node_modules/jasmine-core/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "minimatch", + srcs = [ + ":minimatch__files", + ":brace-expansion__files", + ":balanced-match__files", + ":concat-map__files", + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "minimatch__files", + srcs = glob( + include = ["node_modules/minimatch/**/*"], + exclude = [ + "node_modules/minimatch/test/**", + "node_modules/minimatch/docs/**", + "node_modules/minimatch/**/* */**", + "node_modules/minimatch/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "once", + srcs = [ + ":once__files", + ":wrappy__files", + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "once__files", + srcs = glob( + include = ["node_modules/once/**/*"], + exclude = [ + "node_modules/once/test/**", + "node_modules/once/docs/**", + "node_modules/once/**/* */**", + "node_modules/once/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "path-is-absolute", + srcs = [ + ":path-is-absolute__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "path-is-absolute__files", + srcs = glob( + include = ["node_modules/path-is-absolute/**/*"], + exclude = [ + "node_modules/path-is-absolute/test/**", + "node_modules/path-is-absolute/docs/**", + "node_modules/path-is-absolute/**/* */**", + "node_modules/path-is-absolute/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "unidiff", + srcs = [ + ":unidiff__files", + ":diff__files", + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "unidiff__files", + srcs = glob( + include = ["node_modules/unidiff/**/*"], + exclude = [ + "node_modules/unidiff/test/**", + "node_modules/unidiff/docs/**", + "node_modules/unidiff/**/* */**", + "node_modules/unidiff/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "wrappy", + srcs = [ + ":wrappy__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "wrappy__files", + srcs = glob( + include = ["node_modules/wrappy/**/*"], + exclude = [ + "node_modules/wrappy/test/**", + "node_modules/wrappy/docs/**", + "node_modules/wrappy/**/* */**", + "node_modules/wrappy/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "@gregmagolan/test-a", + srcs = [ + ":@gregmagolan/test-a__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "@gregmagolan/test-a__files", + srcs = glob( + include = ["node_modules/@gregmagolan/test-a/**/*"], + exclude = [ + "node_modules/@gregmagolan/test-a/test/**", + "node_modules/@gregmagolan/test-a/docs/**", + "node_modules/@gregmagolan/test-a/**/* */**", + "node_modules/@gregmagolan/test-a/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "@gregmagolan/test-b", + srcs = [ + ":@gregmagolan/test-b__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "@gregmagolan/test-b__files", + srcs = glob( + include = ["node_modules/@gregmagolan/test-b/**/*"], + exclude = [ + "node_modules/@gregmagolan/test-b/test/**", + "node_modules/@gregmagolan/test-b/docs/**", + "node_modules/@gregmagolan/test-b/**/* */**", + "node_modules/@gregmagolan/test-b/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "@yarnpkg/lockfile", + srcs = [ + ":@yarnpkg/lockfile__files", + + ], + tags = ["FILEGROUP_IS_NODE_MODULE"], +) +filegroup( + name = "@yarnpkg/lockfile__files", + srcs = glob( + include = ["node_modules/@yarnpkg/lockfile/**/*"], + exclude = [ + "node_modules/@yarnpkg/lockfile/test/**", + "node_modules/@yarnpkg/lockfile/docs/**", + "node_modules/@yarnpkg/lockfile/**/* */**", + "node_modules/@yarnpkg/lockfile/**/* *", + ], + ), + tags = ["FILEGROUP_IS_NODE_MODULE"], +) diff --git a/internal/npm_install/test/check.js b/internal/npm_install/test/check.js new file mode 100644 index 0000000000..c5ad990796 --- /dev/null +++ b/internal/npm_install/test/check.js @@ -0,0 +1,51 @@ +const generator = require('../generate_build_file'); +const fs = require('fs'); +const path = require('path'); +const unidiff = require('unidiff') + +const actual = 'BUILD.bazel'; +const golden = 'BUILD.bazel.golden'; + +function check(updateGolden = false) { + // We must change the directory to the BUILD file path + // so the generator is able to run + process.chdir(path.dirname(__filename)); + + // Run the BUILD file generator + generator.main(); + + // Strip comments from generated file for comparison to golden + // to make comparison less brittle + const actualContents = fs.readFileSync(actual, {encoding: 'utf-8'}) + .replace(/\r\n/g, '\n') + .split('\n') + .filter(l => !l.trimLeft().startsWith('#')) + .join('\n') + .replace(/[\n]+/g, '\n'); + + // Load the golden file for comparison + const goldenContents = fs.readFileSync(golden, {encoding: 'utf-8'}).replace(/\r\n/g, '\n'); + + // Check if actualContents matches golden file + if (actualContents !== goldenContents) { + if (updateGolden) { + // Write to golden file + fs.writeFileSync(golden, actualContents); + console.error(`Replaced ${path.join(process.cwd(), golden)}`); + } else { + // Generated does not match golden + const diff = unidiff.diffLines(goldenContents, actualContents); + const prettyDiff = unidiff.formatLines(diff); + throw new Error(`Actual output in ${actual} doesn't match golden file ${golden}. + +Diff: +${prettyDiff} + +Update the golden file: + + bazel run ${process.env['BAZEL_TARGET']}-accept`); + } + } +} + +module.exports = check; \ No newline at end of file diff --git a/internal/npm_install/test/generate_build_file.spec.js b/internal/npm_install/test/generate_build_file.spec.js new file mode 100644 index 0000000000..dd1fa56e9b --- /dev/null +++ b/internal/npm_install/test/generate_build_file.spec.js @@ -0,0 +1,7 @@ +const check = require('./check'); + +describe('build file generator', () => { + it('should produce a BUILD file from the node_modules file structure', () => { + check(); + }); +}); diff --git a/internal/npm_install/test/package.json b/internal/npm_install/test/package.json new file mode 100644 index 0000000000..c9531ca778 --- /dev/null +++ b/internal/npm_install/test/package.json @@ -0,0 +1,11 @@ +{ + "name": "test", + "private": true, + "devDependencies": { + "@gregmagolan/test-a": "^0.0.2", + "@gregmagolan/test-b": "^0.0.2", + "@yarnpkg/lockfile": "^1.0.2", + "jasmine": "^3.2.0", + "unidiff": "^0.0.4" + } +} diff --git a/internal/npm_install/test/update_golden.js b/internal/npm_install/test/update_golden.js new file mode 100644 index 0000000000..f2224532c4 --- /dev/null +++ b/internal/npm_install/test/update_golden.js @@ -0,0 +1,3 @@ +const check = require('./check'); + +check(true); diff --git a/internal/npm_install/test/yarn.lock b/internal/npm_install/test/yarn.lock new file mode 100644 index 0000000000..1c91ad5093 --- /dev/null +++ b/internal/npm_install/test/yarn.lock @@ -0,0 +1,103 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@gregmagolan/test-a@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-a/-/test-a-0.0.1.tgz#c90ebc0676f13b34400da8d1e55ffe5aa76655b4" + +"@gregmagolan/test-a@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-a/-/test-a-0.0.2.tgz#86ab79a55f44860b02d8275e22d282a1f82b8768" + +"@gregmagolan/test-b@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@gregmagolan/test-b/-/test-b-0.0.2.tgz#7549fb9f39895c0d744c2c24c98b0e5817e46bd7" + dependencies: + "@gregmagolan/test-a" "0.0.1" + +"@yarnpkg/lockfile@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.0.2.tgz#833d163680a151d2441a2489f5fe5fa87ac87726" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +diff@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +glob@^7.0.6: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +jasmine-core@~3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.2.1.tgz#8e4ff5b861603ee83343f2b49eee6a0ffe9650ce" + +jasmine@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.2.0.tgz#b3a018454781805650e46578803d08e7cfdd7b3d" + dependencies: + glob "^7.0.6" + jasmine-core "~3.2.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +unidiff@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/unidiff/-/unidiff-0.0.4.tgz#257fc346ac6f134b0b75d08895320872a737e222" + dependencies: + diff "^2.2.2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/internal/test/node_resolve_dep/BUILD.bazel b/internal/test/node_resolve_dep/BUILD.bazel index 66ed7e8879..baed4c45ac 100644 --- a/internal/test/node_resolve_dep/BUILD.bazel +++ b/internal/test/node_resolve_dep/BUILD.bazel @@ -18,10 +18,12 @@ nodejs_binary( name = "run", data = ["index.js"], entry_point = "node_resolve_dep/index.js", + node_modules = "@//:node_modules", ) jasmine_node_test( name = "test", srcs = ["test.spec.js"], data = ["index.js"], + node_modules = "@//:node_modules", ) diff --git a/package.json b/package.json index 3c491380f2..3bf2f757cf 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "shelljs": "^0.8.2" }, "scripts": { + "//examples-bazel_managed_deps": "Run bazel from within the folder containing the WORKSPACE so that the WORKSPACE files is tested.", + "examples-bazel_managed_deps": "cd examples/bazel_managed_deps && bazel test //...", "//examples-program": "Run bazel from within the folder containing the WORKSPACE so that the WORKSPACE files is tested.", "examples-program": "cd examples/program && bazel run @nodejs//:yarn && bazel test --test_output=errors :all", "//examples-packages": "Run bazel from within the folder containing the WORKSPACE so that the WORKSPACE files is tested.", @@ -22,7 +24,7 @@ "internal-e2e-node_loader_no_preserve_symlinks": "cd internal/e2e/node_loader_no_preserve_symlinks && bazel run @nodejs//:yarn && bazel test ... && bazel test @node_resolve_dep//:test", "//internal-e2e-node_loader_preserve_symlinks": "Run bazel from within the folder containing the WORKSPACE so that the WORKSPACE files is tested & its preserve-symlinks value is used.", "internal-e2e-node_loader_preserve_symlinks": "cd internal/e2e/node_loader_preserve_symlinks && bazel run @nodejs//:yarn && bazel test ... && bazel test @node_resolve_dep//:test", - "test": "yarn internal-e2e-node_loader_no_preserve_symlinks && yarn internal-e2e-node_loader_preserve_symlinks && yarn examples-packages && yarn examples-program", + "test": "yarn examples-bazel_managed_deps && yarn examples-packages && yarn examples-program && yarn internal-e2e-node_loader_no_preserve_symlinks && yarn internal-e2e-node_loader_preserve_symlinks", "prebuildifier": "bazel build --noshow_progress @com_github_bazelbuild_buildtools//buildifier", "buildifier": "find . -type f \\( -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/com_github_bazelbuild_buildtools/buildifier/*/buildifier", "preskylint": "bazel build --noshow_progress @io_bazel//src/tools/skylark/java/com/google/devtools/skylark/skylint:Skylint",