diff --git a/packages/runfiles/paths.ts b/packages/runfiles/paths.ts index e43ec09cf2..2ef6c7a102 100755 --- a/packages/runfiles/paths.ts +++ b/packages/runfiles/paths.ts @@ -1,3 +1,5 @@ // NB: on windows thanks to legacy 8-character path segments it might be like // c:/b/ojvxx6nx/execroot/build_~1/bazel-~1/x64_wi~1/bin/internal/npm_in~1/test export const BAZEL_OUT_REGEX = /(\/bazel-out\/|\/bazel-~1\/x64_wi~1\/)/; + +export const REPO_MAPPINGS = "_repo_mapping"; diff --git a/packages/runfiles/runfiles.ts b/packages/runfiles/runfiles.ts index 9b47b762a8..afd83d3af9 100644 --- a/packages/runfiles/runfiles.ts +++ b/packages/runfiles/runfiles.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import {BAZEL_OUT_REGEX} from './paths'; +import {BAZEL_OUT_REGEX, REPO_MAPPINGS} from './paths'; /** * Class that provides methods for resolving Bazel runfiles. @@ -17,6 +17,10 @@ export class Runfiles { * If the environment gives us enough hints, we can know the package path */ package: string|undefined; + /** + * If the environment has repo mappings, we can use them to resolve repo relative paths. + */ + repoMappings: Map|undefined; constructor(private _env: typeof process.env) { // If Bazel sets a variable pointing to a runfiles manifest, @@ -29,8 +33,10 @@ export class Runfiles { this.manifest = this.loadRunfilesManifest(_env['RUNFILES_MANIFEST_FILE']!); } else if (!!_env['RUNFILES_DIR']) { this.runfilesDir = path.resolve(_env['RUNFILES_DIR']!); + this.repoMappings = this.loadRepoMapping(this.runfilesDir); } else if (!!_env['RUNFILES']) { this.runfilesDir = path.resolve(_env['RUNFILES']!); + this.repoMappings = this.loadRepoMapping(this.runfilesDir); } else { throw new Error( 'Every node program run under Bazel must have a $RUNFILES_DIR, $RUNFILES or $RUNFILES_MANIFEST_FILE environment variable'); @@ -116,6 +122,25 @@ export class Runfiles { return runfilesEntries; } + loadRepoMapping(runfilesDir: string): Map|undefined { + const repoMappingPath = path.join(runfilesDir, REPO_MAPPINGS); + + if (fs.existsSync(repoMappingPath)) { + const repoMappings = new Map() + const mappings = fs.readFileSync(repoMappingPath, {encoding: 'utf-8'}); + for (const line of mappings.split('\n')) { + if (!line) continue; + const [from, repoName, repoPath] = line.split(','); + + // TODO: from !== ''? + if (from === '') { + repoMappings.set(repoName, repoPath); + } + } + return repoMappings; + } + } + /** Resolves the given module path. */ resolve(modulePath: string) { // Normalize path by converting to forward slashes and removing all trailing @@ -189,6 +214,15 @@ export class Runfiles { } } } + if (this.repoMappings && this.repoMappings.has(moduleBase)) { + const mappedRepo = this.repoMappings.get(moduleBase) + if (mappedRepo !== moduleBase) { + const maybe = this._resolve(mappedRepo, moduleTail) + if (maybe !== undefined) { + return maybe; + } + } + } if (this.runfilesDir) { const maybe = path.join(this.runfilesDir, moduleBase, moduleTail || ''); if (fs.existsSync(maybe)) {