Skip to content

Commit f5eed08

Browse files
authored
fix: create a bazel-out node_modules tree using copy_file in the external repo when exports_directories_only is True (#3241)
1 parent 11460e8 commit f5eed08

31 files changed

+926
-843
lines changed

docs/Built-ins.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,15 +591,15 @@ label and the value corresponds to the path within that directory to the entry p
591591
nodejs_binary(
592592
name = "prettier",
593593
data = ["@npm//prettier"],
594-
entry_point = { "@npm//prettier:directory": "bin-prettier.js" },
594+
entry_point = { "@npm//:node_modules/prettier": "bin-prettier.js" },
595595
)
596596
```
597597

598598
For labels that are passed to `$(rootpath)`, `$(execpath)`, or `$(location)` you can simply break these apart into
599599
the directory label that gets passed to the expander & path part to follows it, e.g.
600600

601601
```
602-
$(rootpath @npm///prettier:directory)/bin-prettier.js
602+
$(rootpath @npm///:node_modules/prettier)/bin-prettier.js
603603
```
604604

605605
Defaults to `True`
@@ -1256,15 +1256,15 @@ label and the value corresponds to the path within that directory to the entry p
12561256
nodejs_binary(
12571257
name = "prettier",
12581258
data = ["@npm//prettier"],
1259-
entry_point = { "@npm//prettier:directory": "bin-prettier.js" },
1259+
entry_point = { "@npm//:node_modules/prettier": "bin-prettier.js" },
12601260
)
12611261
```
12621262

12631263
For labels that are passed to `$(rootpath)`, `$(execpath)`, or `$(location)` you can simply break these apart into
12641264
the directory label that gets passed to the expander & path part to follows it, e.g.
12651265

12661266
```
1267-
$(rootpath @npm///prettier:directory)/bin-prettier.js
1267+
$(rootpath @npm///:node_modules/prettier)/bin-prettier.js
12681268
```
12691269

12701270
Defaults to `True`

examples/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ example_integration_test(
117117
"//packages/typescript:npm_package": "@bazel/typescript",
118118
},
119119
owners = ["@mrmeku"],
120+
tags = [
121+
# ERROR: C:/users/b/_bazel_b/sbb5tpjc/external/npm/BUILD.bazel:5053:10: Copying directory external/npm/_/node_modules/jest-snapshot failed: (Exit 4): cmd.exe failed: error executing command cmd.exe /C bazel-out\x64_windows-fastbuild\bin\external\npm\node_modules\jest-snapshot--603629912-cmd.bat
122+
# Insufficient memory
123+
"no-bazelci-windows",
124+
],
120125
)
121126

122127
example_integration_test(

internal/npm_install/generate_build_file.ts

Lines changed: 50 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export async function main() {
144144
// find all packages (including packages in nested node_modules)
145145
const pkgs: Dep[] = [];
146146

147-
await findPackagesAndPush(pkgs, 'node_modules', deps);
147+
await findPackagesAndPush(pkgs, nodeModulesFolder(), deps);
148148

149149
// Sort the files to ensure the order
150150
pkgs.sort(compareDep);
@@ -162,6 +162,12 @@ export async function main() {
162162
await writeFile('.bazelignore', `node_modules\n${config.workspace_rerooted_path}`);
163163
}
164164

165+
function nodeModulesFolder(): string {
166+
return config.exports_directories_only ?
167+
`${config.workspace_rerooted_package_json_dir}/node_modules` :
168+
'node_modules';
169+
}
170+
165171
/**
166172
* Generates all build files
167173
*/
@@ -206,7 +212,7 @@ function flattenDependencies(pkgs: Dep[]) {
206212
async function generateRootBuildFile(pkgs: Dep[]) {
207213
let buildFile = config.exports_directories_only ?
208214
printRootExportsDirectories(pkgs) :
209-
printRoot(pkgs);
215+
printRootExportsAllFiles(pkgs);
210216

211217
// Add the manual build file contents if they exists
212218
try {
@@ -219,15 +225,27 @@ async function generateRootBuildFile(pkgs: Dep[]) {
219225
await writeFile('BUILD.bazel', buildFile);
220226
}
221227

228+
222229
function printRootExportsDirectories(pkgs: Dep[]) {
223230
let filegroupsStarlark = '';
224-
pkgs.forEach(pkg => filegroupsStarlark += `filegroup(
225-
name = "${pkg._dir.replace("/", "_")}__source_directory",
226-
srcs = ["node_modules/${pkg._dir}"],
227-
visibility = ["@${config.workspace}//:__subpackages__"],
231+
pkgs.forEach(pkg => {
232+
filegroupsStarlark += `
233+
copy_file(
234+
name = "node_modules/${pkg._dir}",
235+
src = "${config.workspace_rerooted_package_json_dir}/node_modules/${pkg._dir}",
236+
is_directory = True,
237+
out = "node_modules/${pkg._dir}",
238+
visibility = ["//visibility:public"],
228239
)
229-
230-
`);
240+
js_library(
241+
name = "${pkg._dir.replace("/", "_")}__contents",
242+
package_name = "${pkg._dir}",
243+
package_path = "${config.package_path}",
244+
strip_prefix = "node_modules/${pkg._dir}",
245+
srcs = [":node_modules/${pkg._dir}"],
246+
visibility = ["//:__subpackages__"],
247+
)
248+
`});
231249

232250
let depsStarlark = '';
233251
if (pkgs.length) {
@@ -240,7 +258,11 @@ if (pkgs.length) {
240258

241259
const result =
242260
generateBuildFileHeader() + `load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
261+
load("@build_bazel_rules_nodejs//third_party/github.com/bazelbuild/bazel-skylib:rules/copy_file.bzl", "copy_file")
243262
263+
# To support remote-execution, we must create a tree artifacts from the source directories.
264+
# We make the output node_modules/pkg_dir so that we get a free node_modules
265+
# tree in bazel-out and runfiles for this external repository.
244266
${filegroupsStarlark}
245267
246268
# The node_modules directory in one catch-all js_library
@@ -251,7 +273,7 @@ js_library(
251273
return result
252274
}
253275

254-
function printRoot(pkgs: Dep[]) {
276+
function printRootExportsAllFiles(pkgs: Dep[]) {
255277
let pkgFilesStarlark = '';
256278
if (pkgs.length) {
257279
let list = '';
@@ -312,8 +334,10 @@ async function generatePackageBuildFiles(pkg: Dep) {
312334
if (pkg._files.includes('BUILD')) buildFilePath = 'BUILD';
313335
if (pkg._files.includes('BUILD.bazel')) buildFilePath = 'BUILD.bazel';
314336

337+
const nodeModules = nodeModulesFolder();
338+
315339
// Recreate the pkg dir inside the node_modules folder
316-
const nodeModulesPkgDir = `node_modules/${pkg._dir}`;
340+
const nodeModulesPkgDir = `${nodeModules}/${pkg._dir}`;
317341
// Check if the current package dep dir is a symlink (which happens when we
318342
// install a node_module with link:)
319343
const isPkgDirASymlink = await fs.lstat(nodeModulesPkgDir)
@@ -334,10 +358,10 @@ async function generatePackageBuildFiles(pkg: Dep) {
334358
// The following won't be used in a symlink build file case
335359
let buildFile = config.exports_directories_only ?
336360
printPackageExportsDirectories(pkg) :
337-
printPackage(pkg);
361+
printPackageLegacy(pkg);
338362
if (buildFilePath) {
339363
buildFile = buildFile + '\n' +
340-
await fs.readFile(path.join('node_modules', pkg._dir, buildFilePath), 'utf-8');
364+
await fs.readFile(path.join(nodeModules, pkg._dir, buildFilePath), 'utf-8');
341365
} else {
342366
buildFilePath = 'BUILD.bazel';
343367
}
@@ -377,7 +401,7 @@ async function generatePackageBuildFiles(pkg: Dep) {
377401
if (basenameUc === '_BUILD' || basenameUc === '_BUILD.BAZEL') {
378402
destFile = path.posix.join(path.dirname(destFile), basename.substr(1));
379403
}
380-
const src = path.posix.join('node_modules', pkg._dir, file);
404+
const src = path.posix.join(nodeModules, pkg._dir, file);
381405
await prev;
382406
await mkdirp(path.dirname(destFile));
383407
await fs.copyFile(src, destFile);
@@ -452,6 +476,7 @@ load("@build_bazel_rules_nodejs//internal/copy_repository:copy_repository.bzl",
452476
// Copy all files for this workspace to a folder under _workspaces
453477
// to restore the Bazel files which have be renamed from the npm package
454478
const workspaceSourcePath = path.posix.join('_workspaces', workspace);
479+
const nodeModules = nodeModulesFolder();
455480
await mkdirp(workspaceSourcePath);
456481
await Promise.all(pkg._files.map(async (file) => {
457482
if (/^node_modules[/\\]/.test(file)) {
@@ -471,7 +496,7 @@ load("@build_bazel_rules_nodejs//internal/copy_repository:copy_repository.bzl",
471496
if (basenameUc === '_BUILD' || basenameUc === '_BUILD.BAZEL') {
472497
destFile = path.posix.join(path.dirname(destFile), basename.substr(1));
473498
}
474-
const src = path.posix.join('node_modules', pkg._dir, file);
499+
const src = path.posix.join(nodeModules, pkg._dir, file);
475500
const dest = path.posix.join(workspaceSourcePath, destFile);
476501
await mkdirp(path.dirname(dest));
477502
await fs.copyFile(src, dest);
@@ -517,7 +542,7 @@ async function generateScopeBuildFiles(scope: string, pkgs: Dep[]) {
517542

518543
let buildFile = config.exports_directories_only ?
519544
printScopeExportsDirectories(scope, deps) :
520-
printScope(scope, deps);
545+
printScopeLegacy(scope, deps);
521546
await writeFile(path.posix.join(scope, 'BUILD.bazel'), buildFile);
522547
}
523548

@@ -676,7 +701,7 @@ async function findPackagesAndPush(pkgs: Dep[], p: string, dependencies: Set<str
676701
* Finds and returns an array of all package scopes in node_modules.
677702
*/
678703
async function findScopes() {
679-
const p = 'node_modules';
704+
const p = nodeModulesFolder();
680705
if (!await isDirectory(p)) {
681706
return [];
682707
}
@@ -688,8 +713,8 @@ async function findScopes() {
688713
if (!f.startsWith('@')) return;
689714
f = path.posix.join(p, f);
690715
if (await isDirectory(f)) {
691-
// strip 'node_modules/' from filename
692-
return f.substring('node_modules/'.length);
716+
// strip leading 'node_modules' from filename
717+
return f.substring(p.length + 1);
693718
}
694719
})
695720
))
@@ -709,10 +734,10 @@ export async function parsePackage(p: string, dependencies: Set<string> = new Se
709734
const pkg = (await isFile(packageJson)) ?
710735
JSON.parse(stripBom(await fs.readFile(packageJson, {encoding: 'utf8'}))) :
711736
{version: '0.0.0'};
712-
737+
713738
// Trim the leading node_modules from the path and
714739
// assign to _dir for future use
715-
pkg._dir = p.substring('node_modules/'.length);
740+
pkg._dir = p.substring(nodeModulesFolder().length + 1);
716741

717742
// Stash the package directory name for future use
718743
pkg._name = pkg._dir.split('/').pop();
@@ -1049,64 +1074,27 @@ function printPackageExportsDirectories(pkg: Dep) {
10491074
// Flattened list of direct and transitive dependencies hoisted to root by the package manager
10501075
const deps = [pkg].concat(pkg._dependencies.filter(dep => dep !== pkg && !dep._isNested));
10511076
const depsStarlark =
1052-
deps.map(dep => `"//${dep._dir}:${dep._name}__contents",`).join('\n ');
1077+
deps.map(dep => `"//:${dep._dir.replace("/", "_")}__contents",`).join('\n ');
10531078

10541079
return `load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
1055-
load("@build_bazel_rules_nodejs//third_party/github.com/bazelbuild/bazel-skylib:rules/copy_file.bzl", "copy_file")
10561080
10571081
# Generated targets for npm package "${pkg._dir}"
10581082
${printJson(pkg)}
10591083
1060-
# To support remote-execution, we must create a tree artifact from the source directory
1061-
copy_file(
1062-
name = "directory",
1063-
src = "@${config.workspace}//:${pkg._dir.replace("/", "_")}__source_directory",
1064-
is_directory = True,
1065-
out = "tree",
1066-
)
1067-
10681084
# The primary target for this package for use in rule deps
10691085
js_library(
10701086
name = "${pkg._name}",
10711087
deps = [
10721088
${depsStarlark}
10731089
],
10741090
)
1075-
1076-
# Target is used as dep for main targets to prevent circular dependencies errors
1077-
js_library(
1078-
name = "contents",
1079-
package_name = "${pkg._dir}",
1080-
package_path = "${config.package_path}",
1081-
strip_prefix = "tree",
1082-
srcs = [":directory"],
1083-
visibility = ["//:__subpackages__"],
1084-
)
1085-
1086-
# For ts_library backward compat which uses @npm//typescript:__files
1087-
alias(
1088-
name = "${pkg._name}__files",
1089-
actual = "directory",
1090-
)
1091-
1092-
# For ts_library backward compat which uses @npm//typescript:__files
1093-
alias(
1094-
name = "${pkg._name}__contents",
1095-
actual = "contents",
1096-
)
1097-
1098-
# For ts_library backward compat which uses @npm//typescript:typescript__typings
1099-
alias(
1100-
name = "${pkg._name}__typings",
1101-
actual = "contents",
1102-
)
11031091
`;
11041092
}
11051093

11061094
/**
11071095
* Given a pkg, return the skylark `js_library` targets for the package.
11081096
*/
1109-
function printPackage(pkg: Dep) {
1097+
function printPackageLegacy(pkg: Dep) {
11101098
function starlarkFiles(attr: string, files: string[], comment: string = '') {
11111099
return `
11121100
${comment ? comment + '\n ' : ''}${attr} = [
@@ -1316,7 +1304,7 @@ export function printPackageBin(pkg: Dep) {
13161304

13171305
for (const [name, path] of executables.entries()) {
13181306
const entryPoint = config.exports_directories_only ?
1319-
`{ "@${config.workspace}//${pkg._dir}:directory": "${path}" }` :
1307+
`{ "@${config.workspace}//:node_modules/${pkg._dir}": "${path}" }` :
13201308
`"@${config.workspace}//:node_modules/${pkg._dir}/${path}"`;
13211309
result += `# Wire up the \`bin\` entry \`${name}\`
13221310
nodejs_binary(
@@ -1346,7 +1334,7 @@ export function printIndexBzl(pkg: Dep) {
13461334

13471335
for (const [name, path] of executables.entries()) {
13481336
const entryPoint = config.exports_directories_only ?
1349-
`{ "@${config.workspace}//${pkg._dir}:directory": "${path}" }` :
1337+
`{ "@${config.workspace}//:node_modules/${pkg._dir}": "${path}" }` :
13501338
`"@${config.workspace}//:node_modules/${pkg._dir}/${path}"`;
13511339
result = `${result}
13521340
@@ -1391,7 +1379,7 @@ type Dep = {
13911379
/**
13921380
* Given a scope, return the skylark `js_library` target for the scope.
13931381
*/
1394-
function printScope(scope: string, deps: Dep[]) {
1382+
function printScopeLegacy(scope: string, deps: Dep[]) {
13951383
let pkgFilesStarlark = '';
13961384
if (deps.length) {
13971385
const list = deps.map(dep => `"//${dep._dir}:${dep._name}__files",`).join('\n ');

0 commit comments

Comments
 (0)