Skip to content

Commit 1f97eb6

Browse files
devversionalexeagle
authored andcommitted
fix: local workspace path not always properly determined (#417)
Since there can be also runfiles which refer to the current `USER_WORKSPACE`, but actually resolve to the bazel output instead of to the actual local workspace, it can happen that the `node_loader` determines a wrong path to the local workspace. We can safely fix this by just ensuring that the runfile, which potentially resolves to the local workspace, does not refer to the bazel-bin or bazel-genfiles directory (those paths are templated) Fixes #352.
1 parent 96374fd commit 1f97eb6

File tree

3 files changed

+47
-24
lines changed

3 files changed

+47
-24
lines changed

internal/e2e/node/BUILD.bazel

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ jasmine_node_test(
2626
":data_resolution_lib",
2727
":module_resolution_lib",
2828
],
29-
data = ["data/data.json"],
29+
data = [
30+
"data/data.json",
31+
":genfile-runfile",
32+
],
3033
node_modules = "//internal/test:node_modules",
3134
deps = ["//internal/e2e/node/lib1"],
3235
)
@@ -44,3 +47,13 @@ genrule(
4447
outs = ["data_resolution_built.spec.js"],
4548
cmd = "cp $< $@",
4649
)
50+
51+
# This genrule creates a file that alphabetically comes before any source file in this
52+
# package. This genfile can be then set up as runfile to verify that the "node_loader.js"
53+
# properly determines the local workspace path without incorrectly using the genfile as base
54+
# for the local workspace path. See: https://github.com/bazelbuild/rules_nodejs/issues/352
55+
genrule(
56+
name = "genfile-runfile",
57+
outs = ["#LEADING.txt"],
58+
cmd = "touch $@",
59+
)

internal/e2e/node/data_resolution.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ describe('node data resolution', () => {
3030
const thisFilePath = __filename;
3131
const relativePathFromDataToThisFile = path.join('../', path.basename(thisFilePath));
3232
const joinedPathFromDataToThisFile = path.join(path.dirname(resolvedRelativeDataPath),
33-
relativePathFromDataToThisFile)
33+
relativePathFromDataToThisFile);
3434

3535
const resolvedPathFromDataToThisFile = require.resolve(joinedPathFromDataToThisFile);
3636
expect(resolvedPathFromDataToThisFile).toEqual(thisFilePath);
3737
});
38-
});
38+
});

internal/node/node_loader.js

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -93,27 +93,37 @@ function loadRunfilesManifest(manifestPath) {
9393
const runfilesManifest = Object.create(null);
9494
const reverseRunfilesManifest = Object.create(null);
9595
const input = fs.readFileSync(manifestPath, {encoding: 'utf-8'});
96-
let workspaceRoot;
96+
97+
// Absolute path that refers to the local workspace path. We need to determine the absolute
98+
// path to the local workspace because it allows us to support absolute path resolving
99+
// for runfiles.
100+
let localWorkspacePath = null;
101+
97102
for (const line of input.split('\n')) {
98103
if (!line) continue;
99104
const [runfilesPath, realPath] = line.split(' ');
100105
runfilesManifest[runfilesPath] = realPath;
101106
reverseRunfilesManifest[realPath] = runfilesPath;
102107

103-
// Determine workspace root to convert absolute paths into runfile paths.
104-
// This only works if there is at least one runfile in the workspace root, but that is
105-
// also the only case when we need to map back to the runfiles.
106-
// See https://github.com/bazelbuild/bazel/issues/5926 for more information.
107-
if (!workspaceRoot &&
108-
runfilesPath.startsWith(USER_WORKSPACE_NAME)
109-
// TODO(gregmagolan): should not be needed when --nolegacy_external_runfiles is default
110-
&& !runfilesPath.startsWith(`${USER_WORKSPACE_NAME}/external/`)) {
111-
// Plus one to include the slash at the end.
112-
const runfilesPathRemainder = runfilesPath.slice(USER_WORKSPACE_NAME.length + 1);
113-
if (realPath.endsWith(runfilesPathRemainder)) {
114-
workspaceRoot = realPath.slice(0, realPath.length - runfilesPathRemainder.length);
115-
}
108+
// We don't need to try determining the local workspace path for the current runfile
109+
// mapping in case we already determined the local workspace path, the current
110+
// runfile refers to a different workspace, or the current runfile resolves to a file
111+
// in the bazel-out directory (bin/genfiles directory).
112+
if (localWorkspacePath || !runfilesPath.startsWith(USER_WORKSPACE_NAME) ||
113+
realPath.includes(BIN_DIR) || realPath.includes(GEN_DIR)) {
114+
continue;
115+
}
116+
117+
// Relative path for the runfile. We can compute that path by removing the leading
118+
// workspace name. e.g. `my_workspace/src/my-runfile.js` becomes `src/my-runfile.js`.
119+
const relativeWorkspacePath = runfilesPath.slice(USER_WORKSPACE_NAME.length + 1);
120+
121+
// TODO(gregmagolan): should not be needed when --nolegacy_external_runfiles is default
122+
if (relativeWorkspacePath.startsWith('external/')) {
123+
continue;
116124
}
125+
126+
localWorkspacePath = realPath.slice(0, -relativeWorkspacePath.length);
117127
}
118128

119129
// Determine bin and gen root to convert absolute paths into runfile paths.
@@ -127,11 +137,11 @@ function loadRunfilesManifest(manifestPath) {
127137

128138
if (DEBUG) console.error(`node_loader: using binRoot ${binRoot}`);
129139
if (DEBUG) console.error(`node_loader: using genRoot ${genRoot}`);
130-
if (DEBUG) console.error(`node_loader: using workspaceRoot ${workspaceRoot}`);
140+
if (DEBUG) console.error(`node_loader: using localWorkspacePath ${localWorkspacePath}`);
131141

132-
return { runfilesManifest, reverseRunfilesManifest, binRoot, genRoot, workspaceRoot };
142+
return { runfilesManifest, reverseRunfilesManifest, binRoot, genRoot, localWorkspacePath };
133143
}
134-
const { runfilesManifest, reverseRunfilesManifest, binRoot, genRoot, workspaceRoot } =
144+
const { runfilesManifest, reverseRunfilesManifest, binRoot, genRoot, localWorkspacePath } =
135145
// On Windows, Bazel sets RUNFILES_MANIFEST_ONLY=1.
136146
// On every platform, Bazel also sets RUNFILES_MANIFEST_FILE, but on Linux
137147
// and macOS it's faster to use the symlinks in RUNFILES_DIR rather than resolve
@@ -272,15 +282,15 @@ function resolveRunfiles(parent, ...pathSegments) {
272282
runfilesEntry = path.join(path.dirname(parentRunfile), runfilesEntry);
273283
}
274284
} else if (runfilesEntry.startsWith(binRoot) || runfilesEntry.startsWith(genRoot)
275-
|| runfilesEntry.startsWith(workspaceRoot)) {
276-
// For absolute paths, replace binRoot, genRoot or workspaceRoot with USER_WORKSPACE_NAME
277-
// to enable lookups.
285+
|| runfilesEntry.startsWith(localWorkspacePath)) {
286+
// For absolute paths, replace binRoot, genRoot or localWorkspacePath with
287+
// USER_WORKSPACE_NAME to enable lookups.
278288
// It's OK to do multiple replacements because all of these are absolute paths with drive
279289
// names (e.g. C:\), and on Windows you can't have drive names in the middle of paths.
280290
runfilesEntry = runfilesEntry
281291
.replace(binRoot, `${USER_WORKSPACE_NAME}/`)
282292
.replace(genRoot, `${USER_WORKSPACE_NAME}/`)
283-
.replace(workspaceRoot, `${USER_WORKSPACE_NAME}/`);
293+
.replace(localWorkspacePath, `${USER_WORKSPACE_NAME}/`);
284294
}
285295

286296
// Normalize and replace path separators to conform to the ones in the manifest.

0 commit comments

Comments
 (0)