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,31 @@ 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
+
79
+ return filePath ;
80
+ }
81
+
57
82
async function resolveInExecroot ( {
58
83
build,
59
84
bindir,
60
85
execroot,
86
+ runfiles,
61
87
importPath,
62
88
otherOptions,
63
89
} : ResolveInExecrootOptions ) : Promise < OnResolveResult > {
@@ -85,8 +111,39 @@ async function resolveInExecroot({
85
111
`Error: esbuild resolved a path outside of BAZEL_BINDIR (${ bindir } ): ${ result . path } ` ,
86
112
) ;
87
113
}
88
- // Otherwise remap the bindir-relative path
89
- const correctedPath = join ( execroot , result . path . substring ( result . path . indexOf ( bindir ) ) ) ;
114
+ // Get the path under the bindir for the file. This allows us to map into
115
+ // the execroot or the runfiles directory (if present).
116
+ // Example:
117
+ // bindir = bazel-out/<arch>/bin
118
+ // result.path = <base>/execroot/bazel-out/<arch>/bin/external/repo+/path/file.ts
119
+ // binDirRelativePath = external/repo+/path/file.ts
120
+ const binDirRelativePath = result . path . substring (
121
+ result . path . indexOf ( bindir ) + bindir . length + 1 ,
122
+ ) ;
123
+ // We usually remap into the bindir. However, when sources are provided
124
+ // as `data` (runfiles), they will be in the runfiles root instead. The
125
+ // runfiles path is absolute and under the bindir, so we don't need to
126
+ // join anything to it. The execroot does not include the bindir, so there
127
+ // we add it again after previously removing it from the result path.
128
+ const remapBase = runfiles ?? path . join ( execroot , bindir ) ;
129
+ // The path relative to the remapBase also differs between runfiles and
130
+ // bindir, but only if the file is in an external repository. External
131
+ // repositories appear under `external/repo+` in the bindir, whereas they
132
+ // are directly under `repo+` in the runfiles tree. This difference needs
133
+ // to be accounted for by removing a potential `external/` prefix when
134
+ // mapping into runfiles.
135
+ const remapBaseRelativePath = runfiles
136
+ ? removeExternalPathPrefix ( binDirRelativePath )
137
+ : binDirRelativePath ;
138
+ // Join the paths back together. The results will look slightly different
139
+ // between runfiles and bindir, but this is intentional.
140
+ // Source path:
141
+ // <bin>/external/repo+/path/file.ts
142
+ // Example in bindir:
143
+ // <sandbox-bin>/external/repo+/path/file.ts
144
+ // Example in runfiles:
145
+ // <sandbox-bin>/path/bin.runfiles/repo+/path/file.ts
146
+ const correctedPath = join ( remapBase , remapBaseRelativePath ) ;
90
147
if ( process . env . JS_BINARY__LOG_DEBUG ) {
91
148
// eslint-disable-next-line no-console
92
149
console . error (
0 commit comments