12
12
13
13
import type { OnResolveResult , Plugin , PluginBuild , ResolveOptions } from 'esbuild' ;
14
14
import { stat } from 'node:fs/promises' ;
15
- import { join } from 'node:path' ;
15
+ import path , { join } from 'node:path' ;
16
16
17
17
export interface CreateBazelSandboxPluginOptions {
18
18
bindir : string ;
19
19
execroot : string ;
20
+ runfiles ?: string ;
20
21
}
21
22
22
23
// Under Bazel, esbuild will follow symlinks out of the sandbox when the sandbox is enabled. See https://github.com/aspect-build/rules_esbuild/issues/58.
@@ -25,6 +26,7 @@ export interface CreateBazelSandboxPluginOptions {
25
26
export function createBazelSandboxPlugin ( {
26
27
bindir,
27
28
execroot,
29
+ runfiles,
28
30
} : CreateBazelSandboxPluginOptions ) : Plugin {
29
31
return {
30
32
name : 'bazel-sandbox' ,
@@ -40,7 +42,14 @@ export function createBazelSandboxPlugin({
40
42
}
41
43
otherOptions . pluginData . executedSandboxPlugin = true ;
42
44
43
- return await resolveInExecroot ( { build, bindir, execroot, importPath, otherOptions } ) ;
45
+ return await resolveInExecroot ( {
46
+ build,
47
+ bindir,
48
+ execroot,
49
+ runfiles,
50
+ importPath,
51
+ otherOptions,
52
+ } ) ;
44
53
} ) ;
45
54
} ,
46
55
} ;
@@ -50,14 +59,30 @@ interface ResolveInExecrootOptions {
50
59
build : PluginBuild ;
51
60
bindir : string ;
52
61
execroot : string ;
62
+ runfiles ?: string ;
53
63
importPath : string ;
54
64
otherOptions : ResolveOptions ;
55
65
}
56
66
67
+ const EXTERNAL_PREFIX = 'external/' ;
68
+
69
+ function removeExternalPathPrefix ( filePath : string ) : string {
70
+ // Normalize to relative path without leading slash.
71
+ if ( filePath . startsWith ( '/' ) ) {
72
+ filePath = filePath . substring ( 1 ) ;
73
+ }
74
+ // Remove the EXTERNAL_PREFIX if present.
75
+ if ( filePath . startsWith ( EXTERNAL_PREFIX ) ) {
76
+ filePath = filePath . substring ( EXTERNAL_PREFIX . length ) ;
77
+ }
78
+ return filePath ;
79
+ }
80
+
57
81
async function resolveInExecroot ( {
58
82
build,
59
83
bindir,
60
84
execroot,
85
+ runfiles,
61
86
importPath,
62
87
otherOptions,
63
88
} : ResolveInExecrootOptions ) : Promise < OnResolveResult > {
@@ -85,8 +110,39 @@ async function resolveInExecroot({
85
110
`Error: esbuild resolved a path outside of BAZEL_BINDIR (${ bindir } ): ${ result . path } ` ,
86
111
) ;
87
112
}
88
- // Otherwise remap the bindir-relative path
89
- const correctedPath = join ( execroot , result . path . substring ( result . path . indexOf ( bindir ) ) ) ;
113
+ // Get the path under the bindir for the file. This allows us to map into
114
+ // the execroot or the runfiles directory (if present).
115
+ // Example:
116
+ // bindir = bazel-out/<arch>/bin
117
+ // result.path = <base>/execroot/bazel-out/<arch>/bin/external/repo+/path/file.ts
118
+ // binDirRelativePath = external/repo+/path/file.ts
119
+ const binDirRelativePath = result . path . substring (
120
+ result . path . indexOf ( bindir ) + bindir . length + 1 ,
121
+ ) ;
122
+ // We usually remap into the bindir. However, when sources are provided
123
+ // as `data` (runfiles), they will be in the runfiles root instead. The
124
+ // runfiles path is absolute and under the bindir, so we don't need to
125
+ // join anything to it. The execroot does not include the bindir, so there
126
+ // we add it again after previously removing it from the result path.
127
+ const remapBase = runfiles ?? path . join ( execroot , bindir ) ;
128
+ // The path relative to the remapBase also differs between runfiles and
129
+ // bindir, but only if the file is in an external repository. External
130
+ // repositories appear under `external/repo+` in the bindir, whereas they
131
+ // are directly under `repo+` in the runfiles tree. This difference needs
132
+ // to be accounted for by removing a potential `external/` prefix when
133
+ // mapping into runfiles.
134
+ const remapBaseRelativePath = runfiles
135
+ ? removeExternalPathPrefix ( binDirRelativePath )
136
+ : binDirRelativePath ;
137
+ // Join the paths back together. The results will look slightly different
138
+ // between runfiles and bindir, but this is intentional.
139
+ // Source path:
140
+ // <bin>/external/repo+/path/file.ts
141
+ // Example in bindir:
142
+ // <sandbox-bin>/external/repo+/path/file.ts
143
+ // Example in runfiles:
144
+ // <sandbox-bin>/path/bin.runfiles/repo+/path/file.ts
145
+ const correctedPath = join ( remapBase , remapBaseRelativePath ) ;
90
146
if ( process . env . JS_BINARY__LOG_DEBUG ) {
91
147
// eslint-disable-next-line no-console
92
148
console . error (
0 commit comments