Skip to content

Commit 3c12dfe

Browse files
Greg Magolanalexeagle
authored andcommitted
fix: relative data paths in yarn_install & npm_install when symlink_node_modules=False and package.json is not at root
This fixes `yarn_install` & `npm_install` to copy `package.json` & lock files into the external repository to a folder that corresponds to the package.json's workspace folder and the package manager is then run out that location so that relative paths to `data` files are preserved (same as they would be outside of bazel) if the `package.json` is _not_ at the root of the WORKSPACE. If you had a nested `package.json` and were using `yarn_install` or `npm_install` with `symlink_node_modules = False,` and passing in `data` that you were referencing during install, then you would have had to use the work-around of absolute workspace paths to the `data` files. This fix will allow you to use the same relative paths that you would use if you were running `yarn` or `npm` outside of bazel in this case. For example, if the package.json file is located at `my/nested/package.json` then it will end up at `_/my/nested/package.json` in the external repository. When `symlink_node_modules` is `False`, `data` files are copied into the same tree, so that relative paths in `package.json` that refer to `data` files are preserved. A `"postinstall": "patch-package --patch-dir patches"` in a nested `package.json` file expects a `patches` directory relative to the `package.json` file will now work with `symlink_node_modules = False`. ``` yarn_install( name = "my_nested_npm_deps", package_json = "//my/nested:package.json", yarn_lock = "//my/nested:yarn.lock", data = ["//my/nested:patches/jest-haste-map+24.9.0.patch"], symlink_node_modules = False, ) ``` Additional fix is that `data` files are now copied to external repository with `mkdir -p && cp -f` instead of `rtcx.template({})` trick. The latter is slower & does not copy over binary files correctly. We must copy `data` files and _not_ symlink them since a `package.json` file with `file:path/to/data` will fail if `path/to/data` is a symlink.
1 parent 078243a commit 3c12dfe

File tree

11 files changed

+101
-68
lines changed

11 files changed

+101
-68
lines changed

e2e/packages/npm1/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"tmp": "0.1.0"
99
},
1010
"scripts": {
11-
"postinstall": "node ./postinstall.js"
11+
"postinstall": "node ../postinstall.js"
1212
}
1313
}

e2e/packages/npm2/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"tmp": "0.1.0"
99
},
1010
"scripts": {
11-
"postinstall": "node ./postinstall.js"
11+
"postinstall": "node ../postinstall.js"
1212
}
1313
}

e2e/packages/yarn1/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"tmp": "0.1.0"
99
},
1010
"scripts": {
11-
"postinstall": "node ./postinstall.js"
11+
"postinstall": "node ../postinstall.js"
1212
}
1313
}

e2e/packages/yarn2/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"tmp": "0.1.0"
99
},
1010
"scripts": {
11-
"postinstall": "node ./postinstall.js"
11+
"postinstall": "node ../postinstall.js"
1212
}
1313
}

internal/npm_install/generate_build_file.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ const WORKSPACE = args[0];
5353
const RULE_TYPE = args[1];
5454
const PKG_JSON_FILE_PATH = args[2];
5555
const LOCK_FILE_PATH = args[3];
56-
const STRICT_VISIBILITY = args[4]?.toLowerCase() === 'true';
57-
const INCLUDED_FILES = args[5] ? args[5].split(',') : [];
58-
const BAZEL_VERSION = args[6];
56+
const WORKSPACE_ROOT_PREFIX = args[4];
57+
const WORKSPACE_ROOT_BASE = WORKSPACE_ROOT_PREFIX ?.split('/')[0];
58+
const STRICT_VISIBILITY = args[5]?.toLowerCase() === 'true';
59+
const INCLUDED_FILES = args[6] ? args[6].split(',') : [];
60+
const BAZEL_VERSION = args[7];
5961

6062
const PUBLIC_VISIBILITY = '//visibility:public';
6163
const LIMITED_VISIBILITY = `@${WORKSPACE}//:__subpackages__`;
@@ -122,7 +124,7 @@ export function main() {
122124
generateBuildFiles(pkgs)
123125

124126
// write a .bazelignore file
125-
writeFileSync('.bazelignore', 'node_modules');
127+
writeFileSync('.bazelignore', `node_modules\n${WORKSPACE_ROOT_BASE}`);
126128
}
127129

128130
/**
@@ -171,8 +173,7 @@ function generateRootBuildFile(pkgs: Dep[]) {
171173

172174
let exportsStarlark = '';
173175
pkgs.forEach(pkg => {pkg._files.forEach(f => {
174-
exportsStarlark += ` "node_modules/${pkg._dir}/${f}",
175-
`;
176+
exportsStarlark += ` "node_modules/${pkg._dir}/${f}",\n`;
176177
})});
177178

178179
let buildFile =
@@ -556,7 +557,8 @@ function findScopes() {
556557
const scopes = listing.filter(f => f.startsWith('@'))
557558
.map(f => path.posix.join(p, f))
558559
.filter(f => isDirectory(f))
559-
.map(f => f.replace(/^node_modules\//, ''));
560+
// strip 'node_modules/' from filename
561+
.map(f => f.substring('node_modules/'.length));
560562

561563
return scopes;
562564
}
@@ -575,7 +577,7 @@ export function parsePackage(p: string, dependencies: Set<string> = new Set()):
575577

576578
// Trim the leading node_modules from the path and
577579
// assign to _dir for future use
578-
pkg._dir = p.replace(/^node_modules\//, '');
580+
pkg._dir = p.substring('node_modules/'.length)
579581

580582
// Stash the package directory name for future use
581583
pkg._name = pkg._dir.split('/').pop();
@@ -585,7 +587,7 @@ export function parsePackage(p: string, dependencies: Set<string> = new Set()):
585587
pkg._moduleName = pkg.name || `${pkg._dir}/${pkg._name}`;
586588

587589
// Keep track of whether or not this is a nested package
588-
pkg._isNested = /\/node_modules\//.test(p);
590+
pkg._isNested = /\/node_modules\//.test(pkg._dir);
589591

590592
// List all the files in the npm package for later use
591593
pkg._files = listFiles(p);

internal/npm_install/index.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* THIS FILE GENERATED FROM .ts; see BUILD.bazel */ /* clang-format off */'use strict';
2-
var _a;
2+
var _a, _b;
33
Object.defineProperty(exports, "__esModule", { value: true });
44
const fs = require("fs");
55
const path = require("path");
@@ -13,9 +13,11 @@ const WORKSPACE = args[0];
1313
const RULE_TYPE = args[1];
1414
const PKG_JSON_FILE_PATH = args[2];
1515
const LOCK_FILE_PATH = args[3];
16-
const STRICT_VISIBILITY = ((_a = args[4]) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'true';
17-
const INCLUDED_FILES = args[5] ? args[5].split(',') : [];
18-
const BAZEL_VERSION = args[6];
16+
const WORKSPACE_ROOT_PREFIX = args[4];
17+
const WORKSPACE_ROOT_BASE = (_a = WORKSPACE_ROOT_PREFIX) === null || _a === void 0 ? void 0 : _a.split('/')[0];
18+
const STRICT_VISIBILITY = ((_b = args[5]) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'true';
19+
const INCLUDED_FILES = args[6] ? args[6].split(',') : [];
20+
const BAZEL_VERSION = args[7];
1921
const PUBLIC_VISIBILITY = '//visibility:public';
2022
const LIMITED_VISIBILITY = `@${WORKSPACE}//:__subpackages__`;
2123
function generateBuildFileHeader(visibility = PUBLIC_VISIBILITY) {
@@ -49,7 +51,7 @@ function main() {
4951
flattenDependencies(pkgs);
5052
generateBazelWorkspaces(pkgs);
5153
generateBuildFiles(pkgs);
52-
writeFileSync('.bazelignore', 'node_modules');
54+
writeFileSync('.bazelignore', `node_modules\n${WORKSPACE_ROOT_BASE}`);
5355
}
5456
exports.main = main;
5557
function generateBuildFiles(pkgs) {
@@ -86,8 +88,7 @@ function generateRootBuildFile(pkgs) {
8688
let exportsStarlark = '';
8789
pkgs.forEach(pkg => {
8890
pkg._files.forEach(f => {
89-
exportsStarlark += ` "node_modules/${pkg._dir}/${f}",
90-
`;
91+
exportsStarlark += ` "node_modules/${pkg._dir}/${f}",\n`;
9192
});
9293
});
9394
let buildFile = generateBuildFileHeader() + `load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
@@ -327,18 +328,18 @@ function findScopes() {
327328
const scopes = listing.filter(f => f.startsWith('@'))
328329
.map(f => path.posix.join(p, f))
329330
.filter(f => isDirectory(f))
330-
.map(f => f.replace(/^node_modules\//, ''));
331+
.map(f => f.substring('node_modules/'.length));
331332
return scopes;
332333
}
333334
function parsePackage(p, dependencies = new Set()) {
334335
const packageJson = path.posix.join(p, 'package.json');
335336
const pkg = isFile(packageJson) ?
336337
JSON.parse(stripBom(fs.readFileSync(packageJson, { encoding: 'utf8' }))) :
337338
{ version: '0.0.0' };
338-
pkg._dir = p.replace(/^node_modules\//, '');
339+
pkg._dir = p.substring('node_modules/'.length);
339340
pkg._name = pkg._dir.split('/').pop();
340341
pkg._moduleName = pkg.name || `${pkg._dir}/${pkg._name}`;
341-
pkg._isNested = /\/node_modules\//.test(p);
342+
pkg._isNested = /\/node_modules\//.test(pkg._dir);
342343
pkg._files = listFiles(p);
343344
pkg._runfiles = pkg._files.filter((f) => !/[^\x21-\x7E]/.test(f));
344345
pkg._dependencies = [];

internal/npm_install/npm_install.bzl

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def _create_build_files(repository_ctx, rule_type, node, lock_file):
126126
rule_type,
127127
repository_ctx.path(repository_ctx.attr.package_json),
128128
repository_ctx.path(lock_file),
129+
_workspace_root_prefix(repository_ctx),
129130
str(repository_ctx.attr.strict_visibility),
130131
",".join(repository_ctx.attr.included_files),
131132
native.bazel_version,
@@ -147,24 +148,55 @@ def _add_scripts(repository_ctx):
147148
{},
148149
)
149150

150-
def _add_package_json(repository_ctx):
151-
repository_ctx.symlink(
152-
repository_ctx.attr.package_json,
153-
repository_ctx.path("package.json"),
151+
def _workspace_root_path(repository_ctx, f):
152+
segments = ["_"]
153+
if f.package:
154+
segments.append(f.package)
155+
segments.append(f.name)
156+
return "/".join(segments)
157+
158+
def _workspace_root_prefix(repository_ctx):
159+
package_json = repository_ctx.attr.package_json
160+
segments = ["_"]
161+
if package_json.package:
162+
segments.append(package_json.package)
163+
segments.extend(package_json.name.split("/"))
164+
segments.pop()
165+
return "/".join(segments) + "/"
166+
167+
def _copy_file(repository_ctx, f):
168+
to = _workspace_root_path(repository_ctx, f)
169+
170+
# ensure the destination directory exists
171+
to_segments = to.split("/")
172+
if len(to_segments) > 1:
173+
dirname = "/".join(to_segments[:-1])
174+
result = repository_ctx.execute(
175+
["mkdir", "-p", dirname],
176+
quiet = repository_ctx.attr.quiet,
177+
)
178+
if result.return_code:
179+
fail("mkdir -p %s failed: \nSTDOUT:\n%s\nSTDERR:\n%s" % (dirname, result.stdout, result.stderr))
180+
181+
# copy the file; don't use the repository_ctx.template trick with empty substitution as this
182+
# does not copy over binary files properly
183+
result = repository_ctx.execute(
184+
["cp", "-f", repository_ctx.path(f), to],
185+
quiet = repository_ctx.attr.quiet,
154186
)
187+
if result.return_code:
188+
fail("cp -f %s %s failed: \nSTDOUT:\n%s\nSTDERR:\n%s" % (repository_ctx.path(f), to, result.stdout, result.stderr))
189+
190+
def _symlink_file(repository_ctx, f):
191+
repository_ctx.symlink(f, _workspace_root_path(repository_ctx, f))
155192

156-
def _add_data_dependencies(repository_ctx):
193+
def _copy_data_dependencies(repository_ctx):
157194
"""Add data dependencies to the repository."""
158195
for f in repository_ctx.attr.data:
159-
to = []
160-
if f.package:
161-
to.append(f.package)
162-
to.append(f.name)
163-
164196
# Make copies of the data files instead of symlinking
165197
# as yarn under linux will have trouble using symlinked
166198
# files as npm file:// packages
167-
repository_ctx.template("/".join(to), f, {})
199+
_copy_file(repository_ctx, f)
168200

169201
def _add_node_repositories_info_deps(repository_ctx):
170202
# Add a dep to the node_info & yarn_info files from node_repositories
@@ -180,7 +212,16 @@ def _add_node_repositories_info_deps(repository_ctx):
180212

181213
def _symlink_node_modules(repository_ctx):
182214
package_json_dir = repository_ctx.path(repository_ctx.attr.package_json).dirname
183-
repository_ctx.symlink(repository_ctx.path(str(package_json_dir) + "/node_modules"), repository_ctx.path("node_modules"))
215+
if repository_ctx.attr.symlink_node_modules:
216+
repository_ctx.symlink(
217+
repository_ctx.path(str(package_json_dir) + "/node_modules"),
218+
repository_ctx.path("node_modules"),
219+
)
220+
else:
221+
repository_ctx.symlink(
222+
repository_ctx.path(_workspace_root_prefix(repository_ctx) + "node_modules"),
223+
repository_ctx.path("node_modules"),
224+
)
184225

185226
def _check_min_bazel_version(rule, repository_ctx):
186227
if repository_ctx.attr.symlink_node_modules:
@@ -213,13 +254,11 @@ def _npm_install_impl(repository_ctx):
213254

214255
npm_args.extend(repository_ctx.attr.args)
215256

216-
# If symlink_node_modules is true then run the package manager
217-
# in the package.json folder; otherwise, run it in the root of
218-
# the external repository
257+
# Run the package manager in the package.json folder
219258
if repository_ctx.attr.symlink_node_modules:
220-
root = repository_ctx.path(repository_ctx.attr.package_json).dirname
259+
root = str(repository_ctx.path(repository_ctx.attr.package_json).dirname)
221260
else:
222-
root = repository_ctx.path("")
261+
root = str(repository_ctx.path(_workspace_root_prefix(repository_ctx)))
223262

224263
# The entry points for npm install for osx/linux and windows
225264
if not is_windows_host:
@@ -250,12 +289,9 @@ cd /D "{root}" && "{npm}" {npm_args}
250289
executable = True,
251290
)
252291

253-
repository_ctx.symlink(
254-
repository_ctx.attr.package_lock_json,
255-
repository_ctx.path("package-lock.json"),
256-
)
257-
_add_package_json(repository_ctx)
258-
_add_data_dependencies(repository_ctx)
292+
_symlink_file(repository_ctx, repository_ctx.attr.package_lock_json)
293+
_copy_file(repository_ctx, repository_ctx.attr.package_json)
294+
_copy_data_dependencies(repository_ctx)
259295
_add_scripts(repository_ctx)
260296
_add_node_repositories_info_deps(repository_ctx)
261297

@@ -288,15 +324,15 @@ cd /D "{root}" && "{npm}" {npm_args}
288324
# removeNPMAbsolutePaths is run on node_modules after npm install as the package.json files
289325
# generated by npm are non-deterministic. They contain absolute install paths and other private
290326
# information fields starting with "_". removeNPMAbsolutePaths removes all fields starting with "_".
327+
print([node, repository_ctx.path(remove_npm_absolute_paths), root + "/node_modules"])
291328
result = repository_ctx.execute(
292-
[node, repository_ctx.path(remove_npm_absolute_paths), "/".join([str(root), "node_modules"])],
329+
[node, repository_ctx.path(remove_npm_absolute_paths), root + "/node_modules"],
293330
)
294331

295332
if result.return_code:
296333
fail("remove_npm_absolute_paths failed: %s (%s)" % (result.stdout, result.stderr))
297334

298-
if repository_ctx.attr.symlink_node_modules:
299-
_symlink_node_modules(repository_ctx)
335+
_symlink_node_modules(repository_ctx)
300336

301337
_create_build_files(repository_ctx, "npm_install", node, repository_ctx.attr.package_lock_json)
302338

@@ -380,13 +416,11 @@ def _yarn_install_impl(repository_ctx):
380416
yarn_args.extend(["--mutex", "network"])
381417
yarn_args.extend(repository_ctx.attr.args)
382418

383-
# If symlink_node_modules is true then run the package manager
384-
# in the package.json folder; otherwise, run it in the root of
385-
# the external repository
419+
# Run the package manager in the package.json folder
386420
if repository_ctx.attr.symlink_node_modules:
387-
root = repository_ctx.path(repository_ctx.attr.package_json).dirname
421+
root = str(repository_ctx.path(repository_ctx.attr.package_json).dirname)
388422
else:
389-
root = repository_ctx.path("")
423+
root = str(repository_ctx.path(_workspace_root_prefix(repository_ctx)))
390424

391425
# The entry points for npm install for osx/linux and windows
392426
if not is_windows_host:
@@ -424,12 +458,9 @@ cd /D "{root}" && "{yarn}" {yarn_args}
424458
executable = True,
425459
)
426460

427-
repository_ctx.symlink(
428-
repository_ctx.attr.yarn_lock,
429-
repository_ctx.path("yarn.lock"),
430-
)
431-
_add_package_json(repository_ctx)
432-
_add_data_dependencies(repository_ctx)
461+
_symlink_file(repository_ctx, repository_ctx.attr.yarn_lock)
462+
_copy_file(repository_ctx, repository_ctx.attr.package_json)
463+
_copy_data_dependencies(repository_ctx)
433464
_add_scripts(repository_ctx)
434465
_add_node_repositories_info_deps(repository_ctx)
435466

@@ -456,8 +487,7 @@ cd /D "{root}" && "{yarn}" {yarn_args}
456487
if result.return_code:
457488
fail("yarn_install failed: %s (%s)" % (result.stdout, result.stderr))
458489

459-
if repository_ctx.attr.symlink_node_modules:
460-
_symlink_node_modules(repository_ctx)
490+
_symlink_node_modules(repository_ctx)
461491

462492
_create_build_files(repository_ctx, "yarn_install", node, repository_ctx.attr.yarn_lock)
463493

tools/fine_grained_deps_npm/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/fine_grained_deps_npm/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
"chokidar": "2.0.4",
1313
"http-server": "github:alexeagle/http-server#97205e945b69091606ed83aa0c8489e9ce65d282",
1414
"klaw": "1.3.1",
15-
"local-module": "file:tools/npm_packages/local_module/npm",
15+
"local-module": "file:../../tools/npm_packages/local_module/npm",
1616
"rxjs": "6.5.0"
1717
},
1818
"scripts": {
19-
"postinstall": "node internal/npm_install/test/postinstall.js"
19+
"postinstall": "node ../../internal/npm_install/test/postinstall.js"
2020
}
2121
}

tools/fine_grained_deps_yarn/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
"chokidar": "2.0.4",
1313
"http-server": "github:alexeagle/http-server#97205e945b69091606ed83aa0c8489e9ce65d282",
1414
"klaw": "1.3.1",
15-
"local-module": "link:tools/npm_packages/local_module/yarn",
15+
"local-module": "link:../../tools/npm_packages/local_module/yarn",
1616
"rxjs": "6.5.0"
1717
},
1818
"scripts": {
19-
"postinstall": "node internal/npm_install/test/postinstall.js"
19+
"postinstall": "node ../../internal/npm_install/test/postinstall.js"
2020
}
2121
}

0 commit comments

Comments
 (0)