Repository navigation
Expand file tree
/
Copy pathlink_node_modules.js
More file actions
156 lines (135 loc) · 5.86 KB
/
Copy pathlink_node_modules.js
File metadata and controls
156 lines (135 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* @fileoverview Creates a node_modules directory in the current working directory
* and symlinks in the node modules needed to run a program.
* This replaces the need for custom module resolution logic inside the process.
*/
const fs = require('fs');
const path = require('path');
const VERBOSE_LOGS = !!process.env['VERBOSE_LOGS'];
function log_verbose(...m) {
// This is a template file so we use __filename to output the actual filename
if (VERBOSE_LOGS) console.error('[link_node_modules.js]', ...m);
}
function symlink(target, path) {
if (fs.existsSync(path)) {
// We assume here that the path is already linked to the correct target.
// Could add some logic that asserts it here, but we want to avoid an extra
// filesystem access so we should only do it under some kind of strict mode.
return;
}
log_verbose(`symlink( ${path} -> ${target} )`);
// Use junction on Windows since symlinks require elevated permissions
// we only link to directories so junctions work for us.
fs.symlinkSync(target, path, 'junction');
}
/**
* The runfiles manifest maps from short_path
* https://docs.bazel.build/versions/master/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
*/
function loadRunfilesManifest(manifestPath) {
log_verbose(`using runfiles manifest ${manifestPath}`);
// Create the manifest and reverse manifest maps.
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;
}
function lookupDirectory(dir, runfilesManifest) {
for (const [k, v] of runfilesManifest) {
// Entry looks like
// k: npm/node_modules/semver/LICENSE
// v: /path/to/external/npm/node_modules/semver/LICENSE
// calculate l = length(`/semver/LICENSE`)
if (k.startsWith(dir)) {
const l = k.length - dir.length;
return v.substring(0, v.length - l);
}
}
throw new Error(`Internal failure, please report an issue.
RunfilesManifest has no key for ${dir}
`);
}
/**
* Resolve a root directory string to the actual location on disk
* where node_modules was installed
* @param root a string like 'npm/node_modules'
*/
function resolveRoot(root, runfilesManifest) {
// create a node_modules directory if no root
// this will be the case if only first-party modules are installed
if (!root) {
log_verbose('no third-party packages; mkdir node_modules in ', process.cwd);
fs.mkdirSync('node_modules');
return 'node_modules';
}
// If we got a runfilesManifest map, look through it for a resolution
if (runfilesManifest) {
return lookupDirectory(root, runfilesManifest);
}
// Account for Bazel --legacy_external_runfiles
// which look like 'my_wksp/external/npm/node_modules'
if (fs.existsSync(path.join('external', root))) {
log_verbose('Found legacy_external_runfiles, switching root to', path.join('external', root));
return path.join('external', root);
}
// The repository should be layed out in the parent directory
// since bazel sets our working directory to the repository where the build is happening
return path.join('..', root);
}
function main(args, runfilesManifestPath) {
if (!args || args.length < 1)
throw new Error('link_node_modules.js requires one argument: modulesManifest path');
const [modulesManifest] = args;
let {root, modules} = JSON.parse(fs.readFileSync(modulesManifest));
modules = modules || {};
log_verbose(
'read module manifest, node_modules root is', root, 'with first-party packages', modules);
const runfilesManifest =
runfilesManifestPath ? loadRunfilesManifest(runfilesManifestPath) : undefined;
const rootDir = resolveRoot(root, runfilesManifest);
log_verbose('resolved root', root, 'to', rootDir);
// Create the execroot/my_wksp/node_modules directory that node will resolve from
symlink(rootDir, 'node_modules');
// Typically, cwd=foo, root=external/npm/node_modules, so we want links to be
// ../../../../foo/path/to/package
const symlinkRelativeTarget = path.relative(rootDir, '..');
process.chdir(rootDir);
// Now add symlinks to each of our first-party packages so they appear under the node_modules tree
for (const m of Object.keys(modules)) {
const target = runfilesManifest ? lookupDirectory(modules[m], runfilesManifest) :
path.join(symlinkRelativeTarget, modules[m]);
symlink(target, m);
}
return 0;
}
exports.main = main;
if (require.main === module) {
// 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.
const runfilesManifestPath = process.env['RUNFILES_MANIFEST_FILE'];
// 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 (process.env['RUNFILES_MANIFEST_ONLY'] === '1' && !runfilesManifestPath) {
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.`);
}
process.exitCode = main(process.argv.slice(2), runfilesManifestPath);
}