Skip to content

Commit

Permalink
REVERT: add local copy of runfiles
Browse files Browse the repository at this point in the history
  • Loading branch information
jbedard committed Aug 19, 2024
1 parent fa8411b commit de09339
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# @generated
# Input hashes for repository rule npm_translate_lock(name = "npm", pnpm_lock = "@//:pnpm-lock.yaml").
# Input hashes for repository rule npm_translate_lock(name = "npm", pnpm_lock = "@@//:pnpm-lock.yaml").
# This file should be checked into version control along with the pnpm-lock.yaml file.
.npmrc=-2065072158
bazel_runfiles/package.json=616841026
examples/js_binary/package.json=-41174383
examples/linked_consumer/package.json=-1109186228
examples/linked_empty_node_modules/package.json=-1039372825
Expand All @@ -28,6 +29,6 @@ npm/private/test/package.json=600650131
npm/private/test/vendored/is-odd/package.json=1041695223
npm/private/test/vendored/lodash-4.17.21.tgz=-1206623349
npm/private/test/vendored/semver-max/package.json=578664053
package.json=-275319675
pnpm-lock.yaml=715709952
pnpm-workspace.yaml=-762451270
package.json=-385441671
pnpm-lock.yaml=-301996680
pnpm-workspace.yaml=157507124
1 change: 1 addition & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
bazel_runfiles/node_modules
e2e/
examples/js_binary/node_modules/
examples/linked_consumer/node_modules
Expand Down
1 change: 1 addition & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ npm_translate_lock(
"//:examples/npm_deps/patches/meaning-of-life@1.0.0-pnpm.patch",
"//:package.json",
"//:pnpm-workspace.yaml",
"//bazel_runfiles:package.json",
"//examples/js_binary:package.json",
"//examples/linked_consumer:package.json",
"//examples/linked_empty_node_modules:package.json",
Expand Down
10 changes: 10 additions & 0 deletions bazel_runfiles/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
load("@aspect_rules_js//npm:defs.bzl", "npm_package")
load("@npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages(name = "node_modules")

npm_package(
name = "pkg",
srcs = glob(["*.js", "*.d.ts", "*.json"]),
visibility = ["//visibility:public"],
)
7 changes: 7 additions & 0 deletions bazel_runfiles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @bazel/runfiles

This package provides a basic set of utilities for resolving runfiles within Node.js scripts
executed through `js_binary` or `js_test`.

Runfile resolution is desirable if your workspace intends to support users that cannot rely
on runfile forest symlinking (most commonly affected are Windows machines).
6 changes: 6 additions & 0 deletions bazel_runfiles/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { BAZEL_OUT_REGEX } from './paths';
import { Runfiles } from './runfiles';
export { Runfiles };
export { BAZEL_OUT_REGEX as _BAZEL_OUT_REGEX };
/** Instance of the runfile helpers. */
export declare const runfiles: Runfiles;
9 changes: 9 additions & 0 deletions bazel_runfiles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runfiles = exports._BAZEL_OUT_REGEX = exports.Runfiles = void 0;
const paths_1 = require("./paths");
Object.defineProperty(exports, "_BAZEL_OUT_REGEX", { enumerable: true, get: function () { return paths_1.BAZEL_OUT_REGEX; } });
const runfiles_1 = require("./runfiles");
Object.defineProperty(exports, "Runfiles", { enumerable: true, get: function () { return runfiles_1.Runfiles; } });
/** Instance of the runfile helpers. */
exports.runfiles = new runfiles_1.Runfiles(process.env);
22 changes: 22 additions & 0 deletions bazel_runfiles/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@bazel/runfiles",
"description": "Node.js runfiles helpers for Bazel",
"license": "Apache-2.0",
"version": "0.0.0-PLACEHOLDER",
"repository": {
"type" : "git",
"url" : "https://github.com/bazelbuild/rules_nodejs.git",
"directory": "packages/runfiles"
},
"bugs": {
"url": "https://github.com/bazelbuild/rules_nodejs/issues"
},
"keywords": [
"bazel",
"runfiles",
"runfiles helpers"
],
"main": "index.js",
"types": "index.d.ts",
"dependencies": {}
}
2 changes: 2 additions & 0 deletions bazel_runfiles/paths.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export declare const BAZEL_OUT_REGEX: RegExp;
export declare const REPO_MAPPINGS = "_repo_mapping";
7 changes: 7 additions & 0 deletions bazel_runfiles/paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.REPO_MAPPINGS = exports.BAZEL_OUT_REGEX = void 0;
// NB: on windows thanks to legacy 8-character path segments it might be like
// c:/b/ojvxx6nx/execroot/build_~1/bazel-~1/x64_wi~1/bin/internal/npm_in~1/test
exports.BAZEL_OUT_REGEX = /(\/bazel-out\/|\/bazel-~1\/x64_wi~1\/)/;
exports.REPO_MAPPINGS = "_repo_mapping";
47 changes: 47 additions & 0 deletions bazel_runfiles/runfiles.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Class that provides methods for resolving Bazel runfiles.
*/
export declare class Runfiles {
private _env;
manifest: Map<string, string> | undefined;
runfilesDir: string | undefined;
/**
* If the environment gives us enough hints, we can know the workspace name
*/
workspace: string | undefined;
/**
* If the environment gives us enough hints, we can know the package path
*/
package: string | undefined;
/**
* If the environment has repo mappings, we can use them to resolve repo relative paths.
*/
repoMappings: Map<string, string> | undefined;
constructor(_env: typeof process.env);
/** Resolves the given path from the runfile manifest. */
private _resolveFromManifest;
/**
* The runfiles manifest maps from short_path
* https://docs.bazel.build/versions/main/skylark/lib/File.html#short_path
* to the actual location on disk where the file can be read.
*
* In a sandboxed execution, it does not exist. In that case, runfiles must be
* resolved from a symlink tree under the runfiles dir.
* See https://github.com/bazelbuild/bazel/issues/3726
*/
loadRunfilesManifest(manifestPath: string): Map<any, any>;
loadRepoMapping(runfilesDir: string): Map<string, string>;
/** Resolves the given module path. */
resolve(modulePath: string): string;
/** Resolves the given path relative to the current Bazel workspace. */
resolveWorkspaceRelative(modulePath: string): string;
/** Resolves the given path relative to the current Bazel package. */
resolvePackageRelative(modulePath: string): string;
/**
* Patches the default Node.js resolution to support runfile resolution.
* @deprecated Use the runfile helpers directly instead.
**/
patchRequire(): void;
/** Helper for resolving a given module recursively in the runfiles. */
private _resolve;
}
224 changes: 224 additions & 0 deletions bazel_runfiles/runfiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runfiles = void 0;
const fs = require("fs");
const path = require("path");
const paths_1 = require("./paths");
/**
* Class that provides methods for resolving Bazel runfiles.
*/
class Runfiles {
constructor(_env) {
this._env = _env;
// If Bazel sets a variable pointing to a runfiles manifest,
// we'll always use it.
// Note that this has a slight performance implication on Mac/Linux
// where we could use the runfiles tree already laid out on disk
// but this just costs one file read for the external npm/node_modules
// and one for each first-party module, not one per file.
if (!!_env['RUNFILES_MANIFEST_FILE']) {
this.manifest = this.loadRunfilesManifest(_env['RUNFILES_MANIFEST_FILE']);
}
else if (!!_env['RUNFILES_DIR']) {
this.runfilesDir = path.resolve(_env['RUNFILES_DIR']);
this.repoMappings = this.loadRepoMapping(this.runfilesDir);
}
else if (!!_env['RUNFILES']) {
this.runfilesDir = path.resolve(_env['RUNFILES']);
this.repoMappings = this.loadRepoMapping(this.runfilesDir);
}
else {
throw new Error('Every node program run under Bazel must have a $RUNFILES_DIR, $RUNFILES or $RUNFILES_MANIFEST_FILE environment variable');
}
// Under --noenable_runfiles (in particular on Windows)
// Bazel sets RUNFILES_MANIFEST_ONLY=1.
// When this happens, we need to read the manifest file to locate
// inputs
if (_env['RUNFILES_MANIFEST_ONLY'] === '1' && !_env['RUNFILES_MANIFEST_FILE']) {
console.warn(`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.`);
}
// Bazel starts actions with pwd=execroot/my_wksp or pwd=runfiles/my_wksp
this.workspace = _env['BAZEL_WORKSPACE'] || _env['JS_BINARY__WORKSPACE'] || undefined;
// If target is from an external workspace such as @npm//rollup/bin:rollup
// resolvePackageRelative is not supported since package is in an external
// workspace.
let target = _env['BAZEL_TARGET'] || _env['JS_BINARY__TARGET'];
if (!!target && !target.startsWith('@')) {
// //path/to:target -> path/to
this.package = target.split(':')[0].replace(/^\/\//, '');
}
}
/** Resolves the given path from the runfile manifest. */
_resolveFromManifest(searchPath) {
if (!this.manifest)
return undefined;
let result;
for (const [k, v] of this.manifest) {
// Account for Bazel --legacy_external_runfiles
// which pollutes the workspace with 'my_wksp/external/...'
if (k.startsWith(`${searchPath}/external`))
continue;
// If the manifest entry fully matches, return the value path without
// considering other manifest entries. We already have an exact match.
if (k === searchPath) {
return v;
}
// Consider a case where `npm/node_modules` is resolved, and we have the following
// manifest: `npm/node_modules/semver/LICENSE
// /path/to/external/npm/node_modules/semver/LICENSE` To resolve the directory, we look for
// entries that either fully match, or refer to contents within the directory we are looking
// for. We can then subtract the child path to resolve the directory. e.g. in the case above
// we subtract `length(`/semver/LICENSE`)` from the entry value.
if (k.startsWith(`${searchPath}/`)) {
const l = k.length - searchPath.length;
const maybe = v.substring(0, v.length - l);
if (maybe.match(paths_1.BAZEL_OUT_REGEX)) {
return maybe;
}
else {
result = maybe;
}
}
}
return result;
}
/**
* The runfiles manifest maps from short_path
* https://docs.bazel.build/versions/main/skylark/lib/File.html#short_path
* to the actual location on disk where the file can be read.
*
* In a sandboxed execution, it does not exist. In that case, runfiles must be
* resolved from a symlink tree under the runfiles dir.
* See https://github.com/bazelbuild/bazel/issues/3726
*/
loadRunfilesManifest(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;
}
loadRepoMapping(runfilesDir) {
const repoMappingPath = path.join(runfilesDir, paths_1.REPO_MAPPINGS);
const repoMappings = new Map();
if (fs.existsSync(repoMappingPath)) {
const mappings = fs.readFileSync(repoMappingPath, { encoding: 'utf-8' });
for (const line of mappings.split('\n')) {
if (!line)
continue;
const [from, repoName, repoPath] = line.split(',');
// TODO: from !== ''?
if (from === '') {
repoMappings.set(repoName, repoPath);
}
}
}
return repoMappings;
}
/** Resolves the given module path. */
resolve(modulePath) {
// Normalize path by converting to forward slashes and removing all trailing
// forward slashes
modulePath = modulePath.replace(/\\/g, '/').replace(/\/+$/g, '');
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;
}
/** Resolves the given path relative to the current Bazel workspace. */
resolveWorkspaceRelative(modulePath) {
// Normalize path by converting to forward slashes and removing all trailing
// forward slashes
modulePath = modulePath.replace(/\\/g, '/').replace(/\/+$/g, '');
if (!this.workspace) {
throw new Error('workspace could not be determined from the environment; make sure BAZEL_WORKSPACE is set');
}
let workspace = this.workspace;
if (this.repoMappings && this.repoMappings.has(workspace)) {
workspace = this.repoMappings.get(workspace);
}
return this._resolve(workspace, modulePath);
}
/** Resolves the given path relative to the current Bazel package. */
resolvePackageRelative(modulePath) {
// Normalize path by converting to forward slashes and removing all trailing
// forward slashes
modulePath = modulePath.replace(/\\/g, '/').replace(/\/+$/g, '');
if (!this.workspace) {
throw new Error('workspace could not be determined from the environment; make sure BAZEL_WORKSPACE is set');
}
// NB: this.package may be '' if at the root of the workspace
if (this.package === undefined) {
throw new Error('package could not be determined from the environment; make sure BAZEL_TARGET is set');
}
let workspace = this.workspace;
if (this.repoMappings && this.repoMappings.has(workspace)) {
workspace = this.repoMappings.get(workspace);
}
return this._resolve(workspace, path.posix.join(this.package, modulePath));
}
/**
* Patches the default Node.js resolution to support runfile resolution.
* @deprecated Use the runfile helpers directly instead.
**/
patchRequire() {
const requirePatch = this._env['BAZEL_NODE_PATCH_REQUIRE'];
if (!requirePatch) {
throw new Error('require patch location could not be determined from the environment');
}
require(requirePatch);
}
/** Helper for resolving a given module recursively in the runfiles. */
_resolve(moduleBase, moduleTail) {
if (this.manifest) {
const result = this._resolveFromManifest(moduleBase);
if (result) {
if (moduleTail) {
const maybe = path.join(result, moduleTail || '');
if (fs.existsSync(maybe)) {
return maybe;
}
}
else {
return result;
}
}
}
if (this.repoMappings && this.repoMappings.has(moduleBase)) {
const mappedRepo = this.repoMappings.get(moduleBase);
if (mappedRepo !== moduleBase) {
const maybe = this._resolve(mappedRepo, moduleTail);
if (maybe !== undefined) {
return maybe;
}
}
}
if (this.runfilesDir) {
const maybe = path.join(this.runfilesDir, moduleBase, moduleTail || '');
if (fs.existsSync(maybe)) {
return maybe;
}
}
const dirname = path.dirname(moduleBase);
if (dirname == '.') {
// no match
return undefined;
}
return this._resolve(dirname, path.join(path.basename(moduleBase), moduleTail || ''));
}
}
exports.Runfiles = Runfiles;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"overrides": {
"jsonify": "https://github.com/aspect-build/test-packages/releases/download/0.0.0/@foo-jsonify-0.0.0.tgz",
"semver-max": "file:./npm/private/test/vendored/semver-max",
"is-odd": "file:./npm/private/test/vendored/is-odd"
"is-odd": "file:./npm/private/test/vendored/is-odd",
"@bazel/runfiles": "workspace:*"
},
"patchedDependencies": {
"meaning-of-life@1.0.0": "examples/npm_deps/patches/meaning-of-life@1.0.0-pnpm.patch"
Expand Down
Loading

0 comments on commit de09339

Please sign in to comment.