Skip to content

Commit

Permalink
feat: introduce package for runfile helpers
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
devversion authored and alexeagle committed Apr 7, 2021
1 parent 49b5e08 commit 2c883d1
Show file tree
Hide file tree
Showing 31 changed files with 630 additions and 352 deletions.
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pkg_npm(
"//internal/pkg_npm:package_contents",
"//internal/pkg_web:package_contents",
"//internal/providers:package_contents",
"//internal/runfiles:package_contents",
"//third_party/github.com/bazelbuild/bazel:package_contents",
"//third_party/github.com/bazelbuild/bazel-skylib:package_contents",
"//third_party/github.com/bazelbuild/bazel/tools/bash/runfiles:package_contents",
Expand Down
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
'labs',
'protractor',
'rollup',
'runfiles',
'terser',
'typescript',
'worker',
Expand Down
6 changes: 6 additions & 0 deletions examples/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ example_integration_test(

example_integration_test(
name = "examples_create-react-app",
npm_packages = {
"//packages/runfiles:npm_package": "@bazel/runfiles",
},
)

example_integration_test(
Expand Down Expand Up @@ -92,6 +95,9 @@ example_integration_test(

example_integration_test(
name = "examples_closure",
npm_packages = {
"//packages/runfiles:npm_package": "@bazel/runfiles",
},
)

example_integration_test(
Expand Down
5 changes: 4 additions & 1 deletion examples/closure/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ google_closure_compiler(

nodejs_test(
name = "test",
data = ["bundle.js"],
data = [
"bundle.js",
"@npm//@bazel/runfiles",
],
entry_point = "test.js",
)
2 changes: 2 additions & 0 deletions examples/closure/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"private": true,
"//comment": "TODO: Change runfiles dependency to an actual version once released.",
"dependencies": {
"@bazel/runfiles": "latest",
"google-closure-compiler": "20190729.0.0"
}
}
2 changes: 1 addition & 1 deletion examples/closure/test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']);
const {runfiles} = require('@bazel/runfiles');

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

Expand Down
5 changes: 4 additions & 1 deletion examples/create-react-app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ react_scripts(

nodejs_test(
name = "build_smoke_test",
data = ["build"],
data = [
"build",
"@npm//@bazel/runfiles",
],
entry_point = "build_smoke_test.js",
)

Expand Down
2 changes: 1 addition & 1 deletion examples/create-react-app/build_smoke_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const assert = require('assert');
const fs = require('fs');
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']);
const {runfiles} = require('@bazel/runfiles');

// Make sure there's a file like build/static/js/main.12345678.chunk.js
const jsDir = runfiles.resolvePackageRelative('build/static/js');
Expand Down
2 changes: 2 additions & 0 deletions examples/create-react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"react-scripts": "3.4.1",
"typescript": "~3.7.2"
},
"//comment": "TODO: Change runfiles dependency to an actual version once released.",
"devDependencies": {
"@bazel/ibazel": "^0.15.6",
"@bazel/runfiles": "latest",
"patch-package": "^6.2.2"
},
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions internal/js_library/js_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ _ATTRS = {
),
"deps": attr.label_list(),
"external_npm_package": attr.bool(
doc = """Internal use only. Indictates that this js_library target is one or more external npm packages in node_modules.
doc = """Internal use only. Indicates that this js_library target is one or more external npm packages in node_modules.
This is used by the yarn_install & npm_install repository rules for npm dependencies installed by
yarn & npm. When true, js_library will provide ExternalNpmPackageInfo.
It can also be used for user-managed npm dependencies if node_modules is layed out outside of bazel.
For example,
Expand Down
14 changes: 12 additions & 2 deletions internal/linker/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# BEGIN-INTERNAL
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("//:index.bzl", "js_library")
load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project")

# We can't bootstrap the ts_library rule using the linker itself,
Expand All @@ -12,7 +13,17 @@ checked_in_ts_project(
src = "link_node_modules.ts",
checked_in_js = "index.js",
visibility = ["//internal/linker:__subpackages__"],
deps = ["@npm//@types/node"],
deps = [
"//packages/runfiles:bazel_runfiles",
"@npm//@types/node",
],
)

js_library(
name = "linker_js",
srcs = ["index.js"],
visibility = ["//internal/linker/test:__pkg__"],
deps = ["//internal/runfiles:runfiles_js"],
)

bzl_library(
Expand All @@ -24,7 +35,6 @@ bzl_library(
# END-INTERNAL
exports_files([
"index.js",
"runfiles_helper.js",
])

filegroup(
Expand Down
138 changes: 4 additions & 134 deletions internal/linker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const { runfiles: _defaultRunfiles, _BAZEL_OUT_REGEX } = require('../runfiles/index.js');
const VERBOSE_LOGS = !!process.env['VERBOSE_LOGS'];
const BAZEL_OUT_REGEX = /(\/bazel-out\/|\/bazel-~1\/x64_wi~1\/)/;
function log_verbose(...m) {
if (VERBOSE_LOGS)
console.error('[link_node_modules.js]', ...m);
}
function log_error(error) {
console.error('[link_node_modules.js] An error has been reported:', error, error.stack);
}
function panic(m) {
throw new Error(`Internal error! Please run again with
--define=VERBOSE_LOG=1
and file an issue: https://github.com/bazelbuild/rules_nodejs/issues/new?template=bug_report.md
Include as much of the build output as you can without disclosing anything confidential.
Error:
${m}
`);
}
function mkdirp(p) {
return __awaiter(this, void 0, void 0, function* () {
if (p && !(yield exists(p))) {
Expand Down Expand Up @@ -141,125 +131,6 @@ function resolveExternalWorkspacePath(workspace, startCwd, isExecroot, execroot,
}
});
}
class Runfiles {
constructor(env) {
if (!!env['RUNFILES_MANIFEST_FILE']) {
this.manifest = this.loadRunfilesManifest(env['RUNFILES_MANIFEST_FILE']);
}
else if (!!env['RUNFILES_DIR']) {
this.dir = path.resolve(env['RUNFILES_DIR']);
}
else {
panic('Every node program run under Bazel must have a $RUNFILES_DIR or $RUNFILES_MANIFEST_FILE environment variable');
}
if (env['RUNFILES_MANIFEST_ONLY'] === '1' && !env['RUNFILES_MANIFEST_FILE']) {
log_verbose(`Workaround https://github.com/bazelbuild/bazel/issues/7994
RUNFILES_MANIFEST_FILE should have been set but wasn't.
falling back to using runfiles symlinks.
If you want to test runfiles manifest behavior, add
--spawn_strategy=standalone to the command line.`);
}
this.workspace = env['BAZEL_WORKSPACE'] || undefined;
const target = env['BAZEL_TARGET'];
if (!!target && !target.startsWith('@')) {
this.package = target.split(':')[0].replace(/^\/\//, '');
}
}
lookupDirectory(dir) {
if (!this.manifest)
return undefined;
let result;
for (const [k, v] of this.manifest) {
if (k.startsWith(`${dir}/external`))
continue;
if (k.startsWith(dir)) {
const l = k.length - dir.length;
const maybe = v.substring(0, v.length - l);
if (maybe.match(BAZEL_OUT_REGEX)) {
return maybe;
}
else {
result = maybe;
}
}
}
return result;
}
loadRunfilesManifest(manifestPath) {
log_verbose(`using runfiles manifest ${manifestPath}`);
const runfilesEntries = new Map();
const input = fs.readFileSync(manifestPath, { encoding: 'utf-8' });
for (const line of input.split('\n')) {
if (!line)
continue;
const [runfilesPath, realPath] = line.split(' ');
runfilesEntries.set(runfilesPath, realPath);
}
return runfilesEntries;
}
resolve(modulePath) {
if (path.isAbsolute(modulePath)) {
return modulePath;
}
const result = this._resolve(modulePath, undefined);
if (result) {
return result;
}
const e = new Error(`could not resolve module ${modulePath}`);
e.code = 'MODULE_NOT_FOUND';
throw e;
}
_resolve(moduleBase, moduleTail) {
if (this.manifest) {
const result = this.lookupDirectory(moduleBase);
if (result) {
if (moduleTail) {
const maybe = path.join(result, moduleTail || '');
if (fs.existsSync(maybe)) {
return maybe;
}
}
else {
return result;
}
}
}
if (exports.runfiles.dir) {
const maybe = path.join(exports.runfiles.dir, moduleBase, moduleTail || '');
if (fs.existsSync(maybe)) {
return maybe;
}
}
const dirname = path.dirname(moduleBase);
if (dirname == '.') {
return undefined;
}
return this._resolve(dirname, path.join(path.basename(moduleBase), moduleTail || ''));
}
resolveWorkspaceRelative(modulePath) {
if (!this.workspace) {
throw new Error('workspace could not be determined from the environment; make sure BAZEL_WORKSPACE is set');
}
return this.resolve(path.posix.join(this.workspace, modulePath));
}
resolvePackageRelative(modulePath) {
if (!this.workspace) {
throw new Error('workspace could not be determined from the environment; make sure BAZEL_WORKSPACE is set');
}
if (this.package === undefined) {
throw new Error('package could not be determined from the environment; make sure BAZEL_TARGET is set');
}
return this.resolve(path.posix.join(this.workspace, this.package, modulePath));
}
patchRequire() {
const requirePatch = process.env['BAZEL_NODE_PATCH_REQUIRE'];
if (!requirePatch) {
throw new Error('require patch location could not be determined from the environment');
}
require(requirePatch);
}
}
exports.Runfiles = Runfiles;
function exists(p) {
return __awaiter(this, void 0, void 0, function* () {
return ((yield gracefulLstat(p)) !== null);
Expand Down Expand Up @@ -372,7 +243,7 @@ function findExecroot(startCwd) {
if (existsSync(`${startCwd}/bazel-out`)) {
return startCwd;
}
const bazelOutMatch = startCwd.match(BAZEL_OUT_REGEX);
const bazelOutMatch = startCwd.match(_BAZEL_OUT_REGEX);
return bazelOutMatch ? startCwd.slice(0, bazelOutMatch.index) : undefined;
}
function main(args, runfiles) {
Expand Down Expand Up @@ -511,7 +382,7 @@ function main(args, runfiles) {
try {
target = runfiles.resolve(runfilesPath);
if (runfiles.manifest && modulePath.startsWith(`${bin}/`)) {
if (!target.match(BAZEL_OUT_REGEX)) {
if (!target.match(_BAZEL_OUT_REGEX)) {
const e = new Error(`could not resolve module ${runfilesPath} in output tree`);
e.code = 'MODULE_NOT_FOUND';
throw e;
Expand Down Expand Up @@ -565,7 +436,6 @@ function main(args, runfiles) {
});
}
exports.main = main;
exports.runfiles = new Runfiles(process.env);
if (require.main === module) {
if (Number(process.versions.node.split('.')[0]) < 10) {
console.error(`ERROR: rules_nodejs linker requires Node v10 or greater, but is running on ${process.versions.node}`);
Expand All @@ -575,7 +445,7 @@ if (require.main === module) {
}
(() => __awaiter(void 0, void 0, void 0, function* () {
try {
process.exitCode = yield main(process.argv.slice(2), exports.runfiles);
process.exitCode = yield main(process.argv.slice(2), _defaultRunfiles);
}
catch (e) {
log_error(e);
Expand Down
Loading

0 comments on commit 2c883d1

Please sign in to comment.