Skip to content

Commit

Permalink
feat: add link_workspace_root to nodejs_binary, npm_package_bin, roll…
Browse files Browse the repository at this point in the history
…up_bundle, terser_minified, ts_project

Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.
  • Loading branch information
gregmagolan authored and alexeagle committed Sep 9, 2020
1 parent df18c61 commit 4dcb37f
Show file tree
Hide file tree
Showing 23 changed files with 196 additions and 5 deletions.
6 changes: 4 additions & 2 deletions internal/linker/link_node_modules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,18 @@ def _link_mapping(label, mappings, k, v):
else:
return True

def write_node_modules_manifest(ctx, extra_data = [], mnemonic = None):
def write_node_modules_manifest(ctx, extra_data = [], mnemonic = None, link_workspace_root = False):
"""Writes a manifest file read by the linker, containing info about resolving runtime dependencies
Args:
ctx: starlark rule execution context
extra_data: labels to search for npm packages that need to be linked (ctx.attr.deps and ctx.attr.data will always be searched)
mnemonic: optional action mnemonic, used to differentiate module mapping files from the same rule context
link_workspace_root: Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.
"""

mappings = {}
mappings = {ctx.workspace_name: ["execroot", ctx.bin_dir.path]} if link_workspace_root else {}
node_modules_root = ""

# Look through data/deps attributes to find...
Expand Down
12 changes: 12 additions & 0 deletions internal/linker/test/workspace_link/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
load("//packages/jasmine:index.bzl", "jasmine_node_test")

jasmine_node_test(
name = "test",
srcs = ["test.js"],
link_workspace_root = True,
templated_args = ["--nobazel_patch_module_resolver"],
deps = [
"//internal/linker/test/workspace_link/bar",
"//internal/linker/test/workspace_link/foo",
],
)
10 changes: 10 additions & 0 deletions internal/linker/test/workspace_link/bar/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")

copy_to_bin(
name = "bar",
srcs = [
"main.js",
"package.json",
],
visibility = ["//internal/linker/test/workspace_link:__pkg__"],
)
3 changes: 3 additions & 0 deletions internal/linker/test/workspace_link/bar/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
bar: 'bar',
}
5 changes: 5 additions & 0 deletions internal/linker/test/workspace_link/bar/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "bar",
"main": "main.js",
"typings": "main.d.ts"
}
35 changes: 35 additions & 0 deletions internal/linker/test/workspace_link/foo/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
load("@npm//typescript:index.bzl", "tsc")

tsc(
name = "foo_lib",
outs = [
"main.d.ts",
"main.js",
],
args = [
"-p",
"$(execpath tsconfig.json)",
"--outDir",
# $(RULEDIR) is a shorthand for the dist/bin directory where Bazel requires we write outputs
"$(RULEDIR)",
],
data = [
"main.ts",
"tsconfig.json",
],
)

copy_to_bin(
name = "foo_files",
srcs = ["package.json"],
)

filegroup(
name = "foo",
srcs = [
":foo_files",
":foo_lib",
],
visibility = ["//internal/linker/test/workspace_link:__pkg__"],
)
1 change: 1 addition & 0 deletions internal/linker/test/workspace_link/foo/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo: string = 'foo';
5 changes: 5 additions & 0 deletions internal/linker/test/workspace_link/foo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "foo",
"main": "main.js",
"typings": "main.d.ts"
}
6 changes: 6 additions & 0 deletions internal/linker/test/workspace_link/foo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"declaration": true,
"types": []
}
}
8 changes: 8 additions & 0 deletions internal/linker/test/workspace_link/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
describe('linker', () => {
it('should be able to require by absolute path when link_workspace_root is True', () => {
const foo = require('build_bazel_rules_nodejs/internal/linker/test/workspace_link/foo');
expect(foo.foo).toBe('foo');
const bar = require('build_bazel_rules_nodejs/internal/linker/test/workspace_link/bar');
expect(bar.bar).toBe('bar');
});
});
6 changes: 5 additions & 1 deletion internal/node/node.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def _to_execroot_path(ctx, file):
return file.path

def _nodejs_binary_impl(ctx):
node_modules_manifest = write_node_modules_manifest(ctx)
node_modules_manifest = write_node_modules_manifest(ctx, link_workspace_root = ctx.attr.link_workspace_root)
node_modules_depsets = []
node_modules_depsets.append(depset(ctx.files.node_modules))
if NpmPackageInfo in ctx.attr.node_modules:
Expand Down Expand Up @@ -422,6 +422,10 @@ nodejs_binary(
mandatory = True,
allow_single_file = True,
),
"link_workspace_root": attr.bool(
doc = """Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.""",
),
"node_modules": attr.label(
doc = """The npm packages which should be available to `require()` during
execution.
Expand Down
7 changes: 6 additions & 1 deletion internal/node/npm_package_bin.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ _ATTRS = {
"configuration_env_vars": attr.string_list(default = []),
"data": attr.label_list(allow_files = True, aspects = [module_mappings_aspect, node_modules_aspect]),
"exit_code_out": attr.output(),
"link_workspace_root": attr.bool(),
"output_dir": attr.bool(),
"outs": attr.output_list(),
"stderr": attr.output(),
Expand Down Expand Up @@ -78,6 +79,7 @@ def _impl(ctx):
stdout = ctx.outputs.stdout,
stderr = ctx.outputs.stderr,
exit_code_out = ctx.outputs.exit_code_out,
link_workspace_root = ctx.attr.link_workspace_root,
)

return [DefaultInfo(files = depset(outputs + tool_outputs))]
Expand All @@ -87,7 +89,7 @@ _npm_package_bin = rule(
attrs = _ATTRS,
)

def npm_package_bin(tool = None, package = None, package_bin = None, data = [], outs = [], args = [], output_dir = False, **kwargs):
def npm_package_bin(tool = None, package = None, package_bin = None, data = [], outs = [], args = [], output_dir = False, link_workspace_root = False, **kwargs):
"""Run an arbitrary npm package binary (e.g. a program under node_modules/.bin/*) under Bazel.
It must produce outputs. If you just want to run a program with `bazel run`, use the nodejs_binary rule.
Expand Down Expand Up @@ -162,6 +164,8 @@ def npm_package_bin(tool = None, package = None, package_bin = None, data = [],
package_bin: the "bin" entry from `package` that should be run. By default package_bin is the same string as `package`
tool: a label for a binary to run, like `@npm//terser/bin:terser`. This is the longer form of package/package_bin.
Note that you can also refer to a binary in your local workspace.
link_workspace_root: Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.
"""
if not tool:
if not package:
Expand All @@ -175,5 +179,6 @@ def npm_package_bin(tool = None, package = None, package_bin = None, data = [],
args = args,
output_dir = output_dir,
tool = tool,
link_workspace_root = link_workspace_root,
**kwargs
)
12 changes: 11 additions & 1 deletion internal/providers/node_runtime_deps_info.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def run_node(ctx, inputs, arguments, executable, **kwargs):
inputs: list or depset of inputs to the action
arguments: list or ctx.actions.Args object containing arguments to pass to the executable
executable: stringy representation of the executable this action will run, eg eg. "my_executable" rather than ctx.executable.my_executable
mnemonic: optional action mnemonic, used to differentiate module mapping files from the same rule context
link_workspace_root: Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.
kwargs: all other args accepted by ctx.actions.run
"""
if (type(executable) != "string"):
Expand All @@ -82,8 +85,15 @@ def run_node(ctx, inputs, arguments, executable, **kwargs):
extra_inputs = exec_attr[NodeRuntimeDepsInfo].deps
link_data = exec_attr[NodeRuntimeDepsInfo].pkgs

# NB: mnemonic is also passed to ctx.actions.run below
mnemonic = kwargs.get("mnemonic")
modules_manifest = write_node_modules_manifest(ctx, link_data, mnemonic)
link_workspace_root = kwargs.pop("link_workspace_root", False)
modules_manifest = write_node_modules_manifest(
ctx,
extra_data = link_data,
mnemonic = mnemonic,
link_workspace_root = link_workspace_root,
)
add_arg(arguments, "--bazel_node_modules_manifest=%s" % modules_manifest.path)

stdout_file = kwargs.pop("stdout", None)
Expand Down
5 changes: 5 additions & 0 deletions packages/rollup/rollup_bundle.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ Either this attribute or `entry_point` must be specified, but not both.
values = ["amd", "cjs", "esm", "iife", "umd", "system"],
default = "esm",
),
"link_workspace_root": attr.bool(
doc = """Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.""",
),
"output_dir": attr.bool(
doc = """Whether to produce a directory output.
Expand Down Expand Up @@ -349,6 +353,7 @@ def _rollup_bundle(ctx):
mnemonic = "Rollup",
execution_requirements = execution_requirements,
env = {"COMPILATION_MODE": ctx.var["COMPILATION_MODE"]},
link_workspace_root = ctx.attr.link_workspace_root,
)

outputs_depset = depset(outputs)
Expand Down
30 changes: 30 additions & 0 deletions packages/rollup/test/workspace_link/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
load("//packages/jasmine:index.bzl", "jasmine_node_test")
load("//packages/rollup:index.bzl", "rollup_bundle")

copy_to_bin(
name = "foo",
srcs = ["foo.js"],
)

rollup_bundle(
name = "bundle",
srcs = [
"bar.js",
"main.js",
":foo",
],
config_file = "rollup.config.js",
entry_point = "main.js",
link_workspace_root = True,
deps = [
"@npm//@rollup/plugin-commonjs",
"@npm//@rollup/plugin-node-resolve",
],
)

jasmine_node_test(
name = "test",
srcs = ["spec.js"],
deps = ["bundle"],
)
1 change: 1 addition & 0 deletions packages/rollup/test/workspace_link/bar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const bar = 'bar';
1 change: 1 addition & 0 deletions packages/rollup/test/workspace_link/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = 'foo';
5 changes: 5 additions & 0 deletions packages/rollup/test/workspace_link/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as foo from 'build_bazel_rules_nodejs/packages/rollup/test/workspace_link/foo';
import * as bar from './bar';

console.log(foo);
console.log(bar);
15 changes: 15 additions & 0 deletions packages/rollup/test/workspace_link/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import commonjs from '@rollup/plugin-commonjs';
import nodeResolve from '@rollup/plugin-node-resolve';

module.exports = {
onwarn: (warning) => {
// Always fail on warnings, assuming we don't know which are harmless.
// We can add exclusions here based on warning.code, if we discover some
// types of warning should always be ignored under bazel.
throw new Error(warning.message);
},
plugins: [
nodeResolve(),
commonjs(),
],
};
11 changes: 11 additions & 0 deletions packages/rollup/test/workspace_link/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const fs = require('fs');
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']);

describe('rollup', () => {
it('should bundle absolute & relative imports', async () => {
const file = runfiles.resolvePackageRelative('bundle.js');
const bundle = fs.readFileSync(file, 'utf-8');
expect(bundle).toContain(`const foo = 'foo';`);
expect(bundle).toContain(`const bar = 'bar';`);
});
});
5 changes: 5 additions & 0 deletions packages/terser/terser_minified.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ Instead of setting this attribute, consider using debugging compilation mode ins
bazel build --compilation_mode=dbg //my/terser:target
so that it only affects the current build.
""",
),
"link_workspace_root": attr.bool(
doc = """Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.""",
),
"sourcemap": attr.bool(
doc = "Whether to produce a .js.map output",
Expand Down Expand Up @@ -183,6 +187,7 @@ def _terser(ctx):
arguments = [args],
env = {"COMPILATION_MODE": ctx.var["COMPILATION_MODE"]},
progress_message = "Minifying JavaScript %s [terser]" % (outputs[0].short_path),
link_workspace_root = ctx.attr.link_workspace_root,
)

return [
Expand Down
5 changes: 5 additions & 0 deletions packages/typescript/internal/build_defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def _compile_action(ctx, inputs, outputs, tsconfig_file, node_opts, description
arguments = arguments,
executable = "compiler",
env = {"COMPILATION_MODE": ctx.var["COMPILATION_MODE"]},
link_workspace_root = ctx.attr.link_workspace_root,
)

# Enable the replay_params in case an aspect needs to re-build this library.
Expand Down Expand Up @@ -384,6 +385,10 @@ This value will override the `target` option in the user supplied tsconfig.""",
default = _DEVMODE_TARGET_DEFAULT,
),
"internal_testing_type_check_dependencies": attr.bool(default = False, doc = "Testing only, whether to type check inputs that aren't srcs."),
"link_workspace_root": attr.bool(
doc = """Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.""",
),
"node_modules": attr.label(
doc = """The npm packages which should be available during the compile.
Expand Down
7 changes: 7 additions & 0 deletions packages/typescript/internal/ts_project.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _ATTRS = {
"declaration_dir": attr.string(),
"deps": attr.label_list(providers = [DeclarationInfo], aspects = [module_mappings_aspect]),
"extends": attr.label_list(allow_files = [".json"]),
"link_workspace_root": attr.bool(),
"out_dir": attr.string(),
"root_dir": attr.string(),
# NB: no restriction on extensions here, because tsc sometimes adds type-check support
Expand Down Expand Up @@ -153,6 +154,7 @@ def _ts_project_impl(ctx):
ctx.label,
ctx.file.tsconfig.short_path,
),
link_workspace_root = ctx.attr.link_workspace_root,
)

providers = [
Expand Down Expand Up @@ -271,6 +273,7 @@ def ts_project_macro(
declaration_dir = None,
out_dir = None,
root_dir = None,
link_workspace_root = False,
**kwargs):
"""Compiles one TypeScript project using `tsc --project`
Expand Down Expand Up @@ -465,6 +468,9 @@ def ts_project_macro(
ts_build_info_file: the user-specified value of `tsBuildInfoFile` from the tsconfig.
Helps Bazel to predict the path where the .tsbuildinfo output is written.
link_workspace_root: Link the workspace root to the bin_dir to support absolute requires like 'my_wksp/path/to/file'.
If source files need to be required then they can be copied to the bin_dir with copy_to_bin.
**kwargs: passed through to underlying rule, allows eg. visibility, tags
"""

Expand Down Expand Up @@ -550,5 +556,6 @@ def ts_project_macro(
typing_maps_outs = _out_paths(srcs, typings_out_dir, root_dir, ".d.ts.map") if declaration_map else [],
buildinfo_out = tsbuildinfo_path if composite or incremental else None,
tsc = tsc,
link_workspace_root = link_workspace_root,
**kwargs
)

0 comments on commit 4dcb37f

Please sign in to comment.