Skip to content

Commit 2c883d1

Browse files
devversionalexeagle
authored andcommitted
feat: introduce package for runfile helpers
Introduces a new package called `@bazel/runfiles` that provides runfile helpers for Bazel with NodeJS. The benefit of providing the helpers as a separate package is that people can explicitly specify a dependency on the helpers and also can get typings. The current best practice of using a require w/ an environment variable is untyped and prone to mistakes (and doesn't fit well into the Node/NPM ecosystem).
1 parent 49b5e08 commit 2c883d1

31 files changed

+630
-352
lines changed

BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ pkg_npm(
8989
"//internal/pkg_npm:package_contents",
9090
"//internal/pkg_web:package_contents",
9191
"//internal/providers:package_contents",
92+
"//internal/runfiles:package_contents",
9293
"//third_party/github.com/bazelbuild/bazel:package_contents",
9394
"//third_party/github.com/bazelbuild/bazel-skylib:package_contents",
9495
"//third_party/github.com/bazelbuild/bazel/tools/bash/runfiles:package_contents",

commitlint.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
'labs',
1818
'protractor',
1919
'rollup',
20+
'runfiles',
2021
'terser',
2122
'typescript',
2223
'worker',

examples/BUILD.bazel

+6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ example_integration_test(
6565

6666
example_integration_test(
6767
name = "examples_create-react-app",
68+
npm_packages = {
69+
"//packages/runfiles:npm_package": "@bazel/runfiles",
70+
},
6871
)
6972

7073
example_integration_test(
@@ -92,6 +95,9 @@ example_integration_test(
9295

9396
example_integration_test(
9497
name = "examples_closure",
98+
npm_packages = {
99+
"//packages/runfiles:npm_package": "@bazel/runfiles",
100+
},
95101
)
96102

97103
example_integration_test(

examples/closure/BUILD.bazel

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ google_closure_compiler(
1616

1717
nodejs_test(
1818
name = "test",
19-
data = ["bundle.js"],
19+
data = [
20+
"bundle.js",
21+
"@npm//@bazel/runfiles",
22+
],
2023
entry_point = "test.js",
2124
)

examples/closure/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"private": true,
3+
"//comment": "TODO: Change runfiles dependency to an actual version once released.",
34
"dependencies": {
5+
"@bazel/runfiles": "latest",
46
"google-closure-compiler": "20190729.0.0"
57
}
68
}

examples/closure/test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']);
1+
const {runfiles} = require('@bazel/runfiles');
22

33
const closureOutput = runfiles.resolve('examples_closure/bundle.js');
44

examples/create-react-app/BUILD.bazel

+4-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ react_scripts(
5959

6060
nodejs_test(
6161
name = "build_smoke_test",
62-
data = ["build"],
62+
data = [
63+
"build",
64+
"@npm//@bazel/runfiles",
65+
],
6366
entry_point = "build_smoke_test.js",
6467
)
6568

examples/create-react-app/build_smoke_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const assert = require('assert');
22
const fs = require('fs');
3-
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']);
3+
const {runfiles} = require('@bazel/runfiles');
44

55
// Make sure there's a file like build/static/js/main.12345678.chunk.js
66
const jsDir = runfiles.resolvePackageRelative('build/static/js');

examples/create-react-app/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
"react-scripts": "3.4.1",
1717
"typescript": "~3.7.2"
1818
},
19+
"//comment": "TODO: Change runfiles dependency to an actual version once released.",
1920
"devDependencies": {
2021
"@bazel/ibazel": "^0.15.6",
22+
"@bazel/runfiles": "latest",
2123
"patch-package": "^6.2.2"
2224
},
2325
"scripts": {

internal/js_library/js_library.bzl

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ _ATTRS = {
3838
),
3939
"deps": attr.label_list(),
4040
"external_npm_package": attr.bool(
41-
doc = """Internal use only. Indictates that this js_library target is one or more external npm packages in node_modules.
41+
doc = """Internal use only. Indicates that this js_library target is one or more external npm packages in node_modules.
4242
This is used by the yarn_install & npm_install repository rules for npm dependencies installed by
4343
yarn & npm. When true, js_library will provide ExternalNpmPackageInfo.
44-
44+
4545
It can also be used for user-managed npm dependencies if node_modules is layed out outside of bazel.
4646
For example,
4747

internal/linker/BUILD.bazel

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# BEGIN-INTERNAL
22
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
3+
load("//:index.bzl", "js_library")
34
load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project")
45

56
# We can't bootstrap the ts_library rule using the linker itself,
@@ -12,7 +13,17 @@ checked_in_ts_project(
1213
src = "link_node_modules.ts",
1314
checked_in_js = "index.js",
1415
visibility = ["//internal/linker:__subpackages__"],
15-
deps = ["@npm//@types/node"],
16+
deps = [
17+
"//packages/runfiles:bazel_runfiles",
18+
"@npm//@types/node",
19+
],
20+
)
21+
22+
js_library(
23+
name = "linker_js",
24+
srcs = ["index.js"],
25+
visibility = ["//internal/linker/test:__pkg__"],
26+
deps = ["//internal/runfiles:runfiles_js"],
1627
)
1728

1829
bzl_library(
@@ -24,7 +35,6 @@ bzl_library(
2435
# END-INTERNAL
2536
exports_files([
2637
"index.js",
27-
"runfiles_helper.js",
2838
])
2939

3040
filegroup(

internal/linker/index.js

+4-134
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
1111
Object.defineProperty(exports, "__esModule", { value: true });
1212
const fs = require("fs");
1313
const path = require("path");
14+
const { runfiles: _defaultRunfiles, _BAZEL_OUT_REGEX } = require('../runfiles/index.js');
1415
const VERBOSE_LOGS = !!process.env['VERBOSE_LOGS'];
15-
const BAZEL_OUT_REGEX = /(\/bazel-out\/|\/bazel-~1\/x64_wi~1\/)/;
1616
function log_verbose(...m) {
1717
if (VERBOSE_LOGS)
1818
console.error('[link_node_modules.js]', ...m);
1919
}
2020
function log_error(error) {
2121
console.error('[link_node_modules.js] An error has been reported:', error, error.stack);
2222
}
23-
function panic(m) {
24-
throw new Error(`Internal error! Please run again with
25-
--define=VERBOSE_LOG=1
26-
and file an issue: https://github.com/bazelbuild/rules_nodejs/issues/new?template=bug_report.md
27-
Include as much of the build output as you can without disclosing anything confidential.
28-
29-
Error:
30-
${m}
31-
`);
32-
}
3323
function mkdirp(p) {
3424
return __awaiter(this, void 0, void 0, function* () {
3525
if (p && !(yield exists(p))) {
@@ -141,125 +131,6 @@ function resolveExternalWorkspacePath(workspace, startCwd, isExecroot, execroot,
141131
}
142132
});
143133
}
144-
class Runfiles {
145-
constructor(env) {
146-
if (!!env['RUNFILES_MANIFEST_FILE']) {
147-
this.manifest = this.loadRunfilesManifest(env['RUNFILES_MANIFEST_FILE']);
148-
}
149-
else if (!!env['RUNFILES_DIR']) {
150-
this.dir = path.resolve(env['RUNFILES_DIR']);
151-
}
152-
else {
153-
panic('Every node program run under Bazel must have a $RUNFILES_DIR or $RUNFILES_MANIFEST_FILE environment variable');
154-
}
155-
if (env['RUNFILES_MANIFEST_ONLY'] === '1' && !env['RUNFILES_MANIFEST_FILE']) {
156-
log_verbose(`Workaround https://github.com/bazelbuild/bazel/issues/7994
157-
RUNFILES_MANIFEST_FILE should have been set but wasn't.
158-
falling back to using runfiles symlinks.
159-
If you want to test runfiles manifest behavior, add
160-
--spawn_strategy=standalone to the command line.`);
161-
}
162-
this.workspace = env['BAZEL_WORKSPACE'] || undefined;
163-
const target = env['BAZEL_TARGET'];
164-
if (!!target && !target.startsWith('@')) {
165-
this.package = target.split(':')[0].replace(/^\/\//, '');
166-
}
167-
}
168-
lookupDirectory(dir) {
169-
if (!this.manifest)
170-
return undefined;
171-
let result;
172-
for (const [k, v] of this.manifest) {
173-
if (k.startsWith(`${dir}/external`))
174-
continue;
175-
if (k.startsWith(dir)) {
176-
const l = k.length - dir.length;
177-
const maybe = v.substring(0, v.length - l);
178-
if (maybe.match(BAZEL_OUT_REGEX)) {
179-
return maybe;
180-
}
181-
else {
182-
result = maybe;
183-
}
184-
}
185-
}
186-
return result;
187-
}
188-
loadRunfilesManifest(manifestPath) {
189-
log_verbose(`using runfiles manifest ${manifestPath}`);
190-
const runfilesEntries = new Map();
191-
const input = fs.readFileSync(manifestPath, { encoding: 'utf-8' });
192-
for (const line of input.split('\n')) {
193-
if (!line)
194-
continue;
195-
const [runfilesPath, realPath] = line.split(' ');
196-
runfilesEntries.set(runfilesPath, realPath);
197-
}
198-
return runfilesEntries;
199-
}
200-
resolve(modulePath) {
201-
if (path.isAbsolute(modulePath)) {
202-
return modulePath;
203-
}
204-
const result = this._resolve(modulePath, undefined);
205-
if (result) {
206-
return result;
207-
}
208-
const e = new Error(`could not resolve module ${modulePath}`);
209-
e.code = 'MODULE_NOT_FOUND';
210-
throw e;
211-
}
212-
_resolve(moduleBase, moduleTail) {
213-
if (this.manifest) {
214-
const result = this.lookupDirectory(moduleBase);
215-
if (result) {
216-
if (moduleTail) {
217-
const maybe = path.join(result, moduleTail || '');
218-
if (fs.existsSync(maybe)) {
219-
return maybe;
220-
}
221-
}
222-
else {
223-
return result;
224-
}
225-
}
226-
}
227-
if (exports.runfiles.dir) {
228-
const maybe = path.join(exports.runfiles.dir, moduleBase, moduleTail || '');
229-
if (fs.existsSync(maybe)) {
230-
return maybe;
231-
}
232-
}
233-
const dirname = path.dirname(moduleBase);
234-
if (dirname == '.') {
235-
return undefined;
236-
}
237-
return this._resolve(dirname, path.join(path.basename(moduleBase), moduleTail || ''));
238-
}
239-
resolveWorkspaceRelative(modulePath) {
240-
if (!this.workspace) {
241-
throw new Error('workspace could not be determined from the environment; make sure BAZEL_WORKSPACE is set');
242-
}
243-
return this.resolve(path.posix.join(this.workspace, modulePath));
244-
}
245-
resolvePackageRelative(modulePath) {
246-
if (!this.workspace) {
247-
throw new Error('workspace could not be determined from the environment; make sure BAZEL_WORKSPACE is set');
248-
}
249-
if (this.package === undefined) {
250-
throw new Error('package could not be determined from the environment; make sure BAZEL_TARGET is set');
251-
}
252-
return this.resolve(path.posix.join(this.workspace, this.package, modulePath));
253-
}
254-
patchRequire() {
255-
const requirePatch = process.env['BAZEL_NODE_PATCH_REQUIRE'];
256-
if (!requirePatch) {
257-
throw new Error('require patch location could not be determined from the environment');
258-
}
259-
require(requirePatch);
260-
}
261-
}
262-
exports.Runfiles = Runfiles;
263134
function exists(p) {
264135
return __awaiter(this, void 0, void 0, function* () {
265136
return ((yield gracefulLstat(p)) !== null);
@@ -372,7 +243,7 @@ function findExecroot(startCwd) {
372243
if (existsSync(`${startCwd}/bazel-out`)) {
373244
return startCwd;
374245
}
375-
const bazelOutMatch = startCwd.match(BAZEL_OUT_REGEX);
246+
const bazelOutMatch = startCwd.match(_BAZEL_OUT_REGEX);
376247
return bazelOutMatch ? startCwd.slice(0, bazelOutMatch.index) : undefined;
377248
}
378249
function main(args, runfiles) {
@@ -511,7 +382,7 @@ function main(args, runfiles) {
511382
try {
512383
target = runfiles.resolve(runfilesPath);
513384
if (runfiles.manifest && modulePath.startsWith(`${bin}/`)) {
514-
if (!target.match(BAZEL_OUT_REGEX)) {
385+
if (!target.match(_BAZEL_OUT_REGEX)) {
515386
const e = new Error(`could not resolve module ${runfilesPath} in output tree`);
516387
e.code = 'MODULE_NOT_FOUND';
517388
throw e;
@@ -565,7 +436,6 @@ function main(args, runfiles) {
565436
});
566437
}
567438
exports.main = main;
568-
exports.runfiles = new Runfiles(process.env);
569439
if (require.main === module) {
570440
if (Number(process.versions.node.split('.')[0]) < 10) {
571441
console.error(`ERROR: rules_nodejs linker requires Node v10 or greater, but is running on ${process.versions.node}`);
@@ -575,7 +445,7 @@ if (require.main === module) {
575445
}
576446
(() => __awaiter(void 0, void 0, void 0, function* () {
577447
try {
578-
process.exitCode = yield main(process.argv.slice(2), exports.runfiles);
448+
process.exitCode = yield main(process.argv.slice(2), _defaultRunfiles);
579449
}
580450
catch (e) {
581451
log_error(e);

0 commit comments

Comments
 (0)