diff --git a/README.md b/README.md index b50ef3196..ee655e6ae 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,7 @@ This is a fork of rules_nodejs, starting at [v3.8.0](https://github.com/bazelbuild/rules_nodejs/tree/3.8.0). It contains a few fixes that we need for our internal use of the ruleset. This is not designed for use outside of Canva's internal repos. We offer no guarantees about stability, support, or backwards compatibility. If you need similar fixes, we recommend that you fork the repo. + +## Building + +Tooling does not support Apple Silicon, use `--host_platform=//toolchains/node:darwin_amd64` if on macOS. diff --git a/internal/linker/README.md b/internal/linker/README.md index bb9c17bb8..913dee8d0 100644 --- a/internal/linker/README.md +++ b/internal/linker/README.md @@ -1,25 +1,13 @@ -# node package linker +# NodeJS Linker for Bazel Runfiles -It's not obvious why a "linker" is needed in nodejs. -After all, programs use dynamic lookups at runtime so we expect no need for static linking. +`yarn_install` and `npm_install` do not place `node_modules` adjacent to `package.json`, and there +is currently no rule to copy outputs into the correct location. To make up for this limitation a +"linker" is required. -However, in the monorepo case, you develop a package and also reference it by name in the same repo. -This means you need a workflow like `npm link` to symlink the package from the `node_modules/name` directory to `packages/name` or wherever the sources live. -[lerna] does a similar thing, but at a wider scale: it links together a bunch of packages using a descriptor file to understand how to map from the source tree to the runtime locations. - -Under Bazel, we have exactly this monorepo feature. But, we want users to have a better experience than lerna: they shouldn't need to run any tool other than `bazel test` or `bazel run` and they expect programs to work, even when they `require()` some local package from the monorepo. - -To make this seamless, we run a linker as a separate program inside the Bazel action, right before node. -It does essentially the same job as Lerna: make sure there is a `$PWD/node_modules` tree and that all the semantics from Bazel (such as LinkablePackageInfo provider) are mapped to the node module resolution algorithm, so that the node runtime behaves the same way as if the packages had been installed from npm. - -Note that the behavior of the linker depends on whether the package to link was declared as: - -1. a runtime dependency of a binary run by Bazel, which we call "statically linked", and which is resolved from Bazel's Runfiles tree or manifest -1. a dependency declared by a user of that binary, which we call "dynamically linked", and which is resolved from the execution root - -In the future the linker should also generate `package.json` files so that things like `main` and `typings` fields are present and reflect the Bazel semantics, so that we can entirely eliminate custom loading and pathmapping logic from binaries we execute. - -[lerna]: https://github.com/lerna/lerna +A critical piece of this is linker setup is the script run as part of `nodejs_binary` and +`nodejs_test` executables. This script creates a symlink adjacent to `package.json` pointing to +`node_modules` in the external repository generated by `(yarn|npm)_install`. Care has been taken +to ensure it is thread safe (across both instances of the same executable and a mix). # Developing diff --git a/internal/linker/index.js b/internal/linker/index.js index c4d6a6f69..2c2672312 100644 --- a/internal/linker/index.js +++ b/internal/linker/index.js @@ -1,524 +1,134 @@ /* THIS FILE GENERATED FROM .ts; see BUILD.bazel */ /* clang-format off */"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.main = exports.reduceModules = void 0; +exports.tryCreateSymlink = exports.getProposedSymlinks = exports.inferRunfilesDirFromPath = void 0; const fs = require("fs"); const path = require("path"); -const { runfiles: _defaultRunfiles, _BAZEL_OUT_REGEX } = require('../runfiles/index.js'); const VERBOSE_LOGS = !!process.env['VERBOSE_LOGS']; -function log_verbose(...m) { - if (VERBOSE_LOGS) - console.error('[link_node_modules.js]', ...m); -} -function log_error(error) { - console.error('[link_node_modules.js] An error has been reported:', error, error.stack); -} -function mkdirp(p) { - return __awaiter(this, void 0, void 0, function* () { - if (p && !(yield exists(p))) { - yield mkdirp(path.dirname(p)); - log_verbose(`creating directory ${p} in ${process.cwd()}`); - try { - yield fs.promises.mkdir(p); - } - catch (e) { - if (e.code !== 'EEXIST') { - throw e; - } - } - } - }); -} -function gracefulLstat(path) { - return __awaiter(this, void 0, void 0, function* () { - try { - return yield fs.promises.lstat(path); - } - catch (e) { - if (e.code === 'ENOENT') { - return null; - } - throw e; - } - }); -} -function gracefulReadlink(path) { - try { - return fs.readlinkSync(path); - } - catch (e) { - if (e.code === 'ENOENT') { - return null; - } - throw e; +function log(...m) { + if (VERBOSE_LOGS) { + console.error(loggingPrefix, ...m); } } -function gracefulReaddir(path) { - return __awaiter(this, void 0, void 0, function* () { - try { - return yield fs.promises.readdir(path); - } - catch (e) { - if (e.code === 'ENOENT') { - return []; - } - throw e; - } - }); -} -function unlink(moduleName) { - return __awaiter(this, void 0, void 0, function* () { - const stat = yield gracefulLstat(moduleName); - if (stat === null) { - return; - } - log_verbose(`unlink( ${moduleName} )`); - if (stat.isDirectory()) { - yield deleteDirectory(moduleName); - } - else { - log_verbose("Deleting file: ", moduleName); - yield fs.promises.unlink(moduleName); - } - }); -} -function deleteDirectory(p) { - return __awaiter(this, void 0, void 0, function* () { - log_verbose("Deleting children of", p); - for (let entry of yield gracefulReaddir(p)) { - const childPath = path.join(p, entry); - const stat = yield gracefulLstat(childPath); - if (stat === null) { - log_verbose(`File does not exist, but is listed as directory entry: ${childPath}`); - continue; - } - if (stat.isDirectory()) { - yield deleteDirectory(childPath); - } - else { - log_verbose("Deleting file", childPath); - yield fs.promises.unlink(childPath); - } - } - log_verbose("Cleaning up dir", p); - yield fs.promises.rmdir(p); - }); -} -function symlink(target, p) { - return __awaiter(this, void 0, void 0, function* () { - if (!path.isAbsolute(target)) { - target = path.resolve(process.cwd(), target); - } - log_verbose(`creating symlink ${p} -> ${target}`); - try { - yield fs.promises.symlink(target, p, 'junction'); - return true; - } - catch (e) { - if (e.code !== 'EEXIST') { - throw e; - } - if (VERBOSE_LOGS) { - if (!(yield exists(p))) { - log_verbose('ERROR\n***\nLooks like we created a bad symlink:' + - `\n pwd ${process.cwd()}\n target ${target}\n path ${p}\n***`); - } - } - return false; - } - }); -} -function resolveWorkspaceNodeModules(workspace, startCwd, isExecroot, execroot, runfiles) { - return __awaiter(this, void 0, void 0, function* () { - const targetManifestPath = `${workspace}/node_modules`; - if (isExecroot) { - return `${execroot}/external/${targetManifestPath}`; - } - if (!execroot) { - return path.resolve(`${startCwd}/../${targetManifestPath}`); - } - const fromManifest = runfiles.lookupDirectory(targetManifestPath); - if (fromManifest) { - return fromManifest; - } - else { - const maybe = path.resolve(`${execroot}/external/${targetManifestPath}`); - if (yield exists(maybe)) { - return maybe; - } - return path.resolve(`${startCwd}/../${targetManifestPath}`); - } - }); -} -function exists(p) { - return __awaiter(this, void 0, void 0, function* () { - return ((yield gracefulLstat(p)) !== null); - }); -} -function existsSync(p) { - if (!p) { - return false; +function fatal(context, errors) { + console.error(loggingPrefix, 'Error(s) were reported.'); + console.error(loggingPrefix, 'Context:', context); + for (const error of errors) { + console.error(error); } - try { - fs.lstatSync(p); - return true; + console.error(loggingPrefix, 'Exiting'); + process.exit(1); +} +const runfilesPathMatcher = '.runfiles'; +const nodeModulesDir = 'node_modules'; +const loggingPrefix = '[node_modules-linker]'; +function inferRunfilesDirFromPath(maybeRunfilesSource) { + while (maybeRunfilesSource !== '/') { + if (maybeRunfilesSource.endsWith(runfilesPathMatcher)) { + return (maybeRunfilesSource + '/'); + } + maybeRunfilesSource = path.dirname(maybeRunfilesSource); } - catch (e) { - if (e.code === 'ENOENT') { - return false; - } - throw e; + throw new Error('Path does not contain a runfiles parent directory.'); +} +exports.inferRunfilesDirFromPath = inferRunfilesDirFromPath; +function getProposedSymlinks(modulesManifest, runfilesDirPath) { + const symlinks = []; + let relativeMountParent; + for (relativeMountParent in modulesManifest.roots) { + const repositoryName = modulesManifest.roots[relativeMountParent]; + const filePath = path.join(runfilesDirPath, modulesManifest.workspace, relativeMountParent, nodeModulesDir); + const targetPath = path.join(runfilesDirPath, repositoryName, nodeModulesDir); + symlinks.push({ filePath, targetPath }); } + return symlinks; } -function reduceModules(modules) { - return buildModuleHierarchy(Object.keys(modules).sort(), modules, '/').children || []; +exports.getProposedSymlinks = getProposedSymlinks; +function ensureErrorInstance(err) { + return err instanceof Error + ? err + : new Error(`Non-error thrown, value was "${err !== null && err !== void 0 ? err : 'NULL_OR_UNDEFINED'}"`); } -exports.reduceModules = reduceModules; -function buildModuleHierarchy(moduleNames, modules, elementPath) { - let element = { - name: elementPath.slice(0, -1), - link: modules[elementPath.slice(0, -1)], - children: [], - }; - for (let i = 0; i < moduleNames.length;) { - const moduleName = moduleNames[i]; - const next = moduleName.indexOf('/', elementPath.length + 1); - const moduleGroup = (next === -1) ? (moduleName + '/') : moduleName.slice(0, next + 1); - if (next === -1) { - i++; - } - const siblings = []; - while (i < moduleNames.length && moduleNames[i].startsWith(moduleGroup)) { - siblings.push(moduleNames[i++]); - } - let childElement = buildModuleHierarchy(siblings, modules, moduleGroup); - for (let cur = childElement; (cur = liftElement(childElement)) !== childElement;) { - childElement = cur; - } - element.children.push(childElement); - } - if (!element.link) { - delete element.link; +function tryRun(func) { + try { + return func(); } - if (!element.children || element.children.length === 0) { - delete element.children; + catch (err) { + return ensureErrorInstance(err); } - return element; } -function liftElement(element) { - let { name, link, children } = element; - if (!children || !children.length) { - return element; - } - if (link && allElementsAlignUnder(name, link, children)) { - return { name, link }; +function definedOrThrow(value, throwReason) { + if (value == null) { + throw new Error(throwReason); } - return element; + return value; } -function allElementsAlignUnder(parentName, parentLink, elements) { - for (const { name, link, children } of elements) { - if (!link || children) { - return false; +function tryCreateSymlink(filePath, targetPath) { + log(`attempting to create symlink "${filePath}" -> "${targetPath}"`); + fs.mkdirSync(path.join(filePath, '..'), { recursive: true }); + const symlinkResult = tryRun(() => fs.symlinkSync(targetPath, filePath)); + if (symlinkResult instanceof Error) { + const readlinkResult = tryRun(() => fs.readlinkSync(filePath)); + if (readlinkResult instanceof Error) { + fatal('symlink creation', [symlinkResult, readlinkResult]); } - if (!isDirectChildPath(parentName, name)) { - return false; - } - if (!isDirectChildLink(parentLink, link)) { - return false; - } - if (!isNameLinkPathTopAligned(name, link)) { - return false; + if (readlinkResult !== targetPath) { + throw new Error(`Invalid symlink target path "${readlinkResult}" detected, wanted "${targetPath}" for symlink at "${filePath}"`); } + log('symlink already exists'); } - return true; -} -function isDirectChildPath(parent, child) { - return parent === path.dirname(child); -} -function isDirectChildLink(parentLink, childLink) { - return parentLink === path.dirname(childLink); -} -function isNameLinkPathTopAligned(namePath, linkPath) { - return path.basename(namePath) === path.basename(linkPath); } -function visitDirectoryPreserveLinks(dirPath, visit) { - return __awaiter(this, void 0, void 0, function* () { - for (const entry of yield fs.promises.readdir(dirPath)) { - const childPath = path.join(dirPath, entry); - const stat = yield gracefulLstat(childPath); - if (stat === null) { - continue; - } - if (stat.isDirectory()) { - yield visitDirectoryPreserveLinks(childPath, visit); - } - else { - yield visit(childPath, stat); - } - } +exports.tryCreateSymlink = tryCreateSymlink; +const removeNulls = (value) => value != null; +function main() { + var _a, _b; + log('Linking started'); + const cwd = process.cwd(); + log('cwd:', cwd); + log('RUNFILES_DIR environment variable:', (_a = process.env.RUNFILES_DIR) !== null && _a !== void 0 ? _a : '(unset)'); + log('RUNFILES environment variable:', (_b = process.env.RUNFILES) !== null && _b !== void 0 ? _b : '(unset)'); + const envRunfilesCanidates = [process.env.RUNFILES_DIR, process.env.RUNFILES] + .filter(removeNulls) + .map(runfilesDir => { + const adjustedRunfilesDir = fs.realpathSync(runfilesDir); + if (runfilesDir !== adjustedRunfilesDir) { + log(`Symlink dereferenced from runfiles path. Was "${runfilesDir}" now "${adjustedRunfilesDir}"`); + return adjustedRunfilesDir; + } + return runfilesDir; }); -} -function findExecroot(startCwd) { - if (existsSync(`${startCwd}/bazel-out`)) { - return startCwd; - } - const bazelOutMatch = startCwd.match(_BAZEL_OUT_REGEX); - return bazelOutMatch ? startCwd.slice(0, bazelOutMatch.index) : undefined; -} -function main(args, runfiles) { - return __awaiter(this, void 0, void 0, function* () { - if (!args || args.length < 1) - throw new Error('requires one argument: modulesManifest path'); - const [modulesManifest] = args; - log_verbose('manifest file:', modulesManifest); - let { workspace, bin, roots, module_sets } = JSON.parse(fs.readFileSync(modulesManifest)); - log_verbose('manifest contents:', JSON.stringify({ workspace, bin, roots, module_sets }, null, 2)); - roots = roots || {}; - module_sets = module_sets || {}; - const startCwd = process.cwd().replace(/\\/g, '/'); - log_verbose('startCwd:', startCwd); - const execroot = findExecroot(startCwd); - log_verbose('execroot:', execroot ? execroot : 'not found'); - const isExecroot = startCwd == execroot; - log_verbose('isExecroot:', isExecroot.toString()); - if (!isExecroot && execroot) { - process.chdir(execroot); - log_verbose('changed directory to execroot', execroot); - } - function symlinkWithUnlink(target, p, stats = null) { - return __awaiter(this, void 0, void 0, function* () { - if (!path.isAbsolute(target)) { - target = path.resolve(process.cwd(), target); - } - if (stats === null) { - stats = yield gracefulLstat(p); - } - if (runfiles.manifest && execroot && stats !== null && stats.isSymbolicLink()) { - const symlinkPathRaw = gracefulReadlink(p); - if (symlinkPathRaw !== null) { - const symlinkPath = symlinkPathRaw.replace(/\\/g, '/'); - if (path.relative(symlinkPath, target) != '' && - !path.relative(execroot, symlinkPath).startsWith('..')) { - log_verbose(`Out-of-date symlink for ${p} to ${symlinkPath} detected. Target should be ${target}. Unlinking.`); - yield unlink(p); - } - else { - log_verbose(`The symlink at ${p} no longer exists, so no need to unlink it.`); - } - } - } - return symlink(target, p); - }); - } - for (const packagePath of Object.keys(roots)) { - const workspace = roots[packagePath]; - if (workspace) { - const workspaceNodeModules = yield resolveWorkspaceNodeModules(workspace, startCwd, isExecroot, execroot, runfiles); - log_verbose(`resolved ${workspace} workspace node modules path to ${workspaceNodeModules}`); - if (packagePath) { - if (yield exists(workspaceNodeModules)) { - yield mkdirp(packagePath); - yield symlinkWithUnlink(workspaceNodeModules, `${packagePath}/node_modules`); - if (!isExecroot) { - const runfilesPackagePath = `${startCwd}/${packagePath}`; - if (yield exists(runfilesPackagePath)) { - yield symlinkWithUnlink(`${packagePath}/node_modules`, `${runfilesPackagePath}/node_modules`); - } - } - const packagePathBin = `${bin}/${packagePath}`; - if (yield exists(packagePathBin)) { - yield symlinkWithUnlink(`${packagePath}/node_modules`, `${packagePathBin}/node_modules`); - } - } - else { - log_verbose(`no npm workspace node_modules folder under ${packagePath} to link to; creating node_modules directories in ${process.cwd()} for ${packagePath} 1p deps`); - yield mkdirp(`${packagePath}/node_modules`); - if (!isExecroot) { - const runfilesPackagePath = `${startCwd}/${packagePath}`; - yield mkdirp(`${runfilesPackagePath}/node_modules`); - yield symlinkWithUnlink(`${packagePath}/node_modules`, `${runfilesPackagePath}/node_modules`); - } - const packagePathBin = `${bin}/${packagePath}`; - yield mkdirp(`${packagePathBin}/node_modules`); - yield symlinkWithUnlink(`${packagePath}/node_modules`, `${packagePathBin}/node_modules`); - } - } - else { - if (yield exists(workspaceNodeModules)) { - yield symlinkWithUnlink(workspaceNodeModules, `node_modules`); - } - else { - log_verbose(`no root npm workspace node_modules folder to link to; creating node_modules directory in ${process.cwd()}`); - yield mkdirp('node_modules'); - } - } + const runfilesDirPath = (() => { + for (const maybeRunfilesSource of [...envRunfilesCanidates, cwd]) { + try { + log(`Attempting to infer runfiles directory from "${maybeRunfilesSource}"`); + return inferRunfilesDirFromPath(maybeRunfilesSource); } - else { - if (packagePath) { - log_verbose(`no 3p deps at ${packagePath}; creating node_modules directories in ${process.cwd()} for ${packagePath} 1p deps`); - yield mkdirp(`${packagePath}/node_modules`); - if (!isExecroot) { - const runfilesPackagePath = `${startCwd}/${packagePath}`; - yield mkdirp(`${runfilesPackagePath}/node_modules`); - yield symlinkWithUnlink(`${packagePath}/node_modules`, `${runfilesPackagePath}/node_modules`); - } - const packagePathBin = `${bin}/${packagePath}`; - yield mkdirp(`${packagePathBin}/node_modules`); - yield symlinkWithUnlink(`${packagePath}/node_modules`, `${packagePathBin}/node_modules`); - } - else { - log_verbose(`no 3p deps at root; creating node_modules directory in ${process.cwd()} for root 1p deps`); - yield mkdirp('node_modules'); - } + catch (err) { + log(`Could not resolve runfiles directory from "${maybeRunfilesSource}"`, ensureErrorInstance(err).message); } } - function isLeftoverDirectoryFromLinker(stats, modulePath) { - return __awaiter(this, void 0, void 0, function* () { - if (runfiles.manifest === undefined) { - return false; - } - if (!stats.isDirectory()) { - return false; - } - let isLeftoverFromPreviousLink = true; - yield visitDirectoryPreserveLinks(modulePath, (childPath, childStats) => __awaiter(this, void 0, void 0, function* () { - if (!childStats.isSymbolicLink()) { - isLeftoverFromPreviousLink = false; - } - })); - return isLeftoverFromPreviousLink; - }); - } - function createSymlinkAndPreserveContents(stats, modulePath, target) { - return __awaiter(this, void 0, void 0, function* () { - const tmpPath = `${modulePath}__linker_tmp`; - log_verbose(`createSymlinkAndPreserveContents( ${modulePath} )`); - yield symlink(target, tmpPath); - yield visitDirectoryPreserveLinks(modulePath, (childPath, stat) => __awaiter(this, void 0, void 0, function* () { - if (stat.isSymbolicLink()) { - const targetPath = path.join(tmpPath, path.relative(modulePath, childPath)); - log_verbose(`Cloning symlink into temporary created link ( ${childPath} )`); - yield mkdirp(path.dirname(targetPath)); - yield symlink(targetPath, yield fs.promises.realpath(childPath)); - } - })); - log_verbose(`Removing existing module so that new link can take place ( ${modulePath} )`); - yield unlink(modulePath); - yield fs.promises.rename(tmpPath, modulePath); - }); - } - function linkModules(package_path, m) { - return __awaiter(this, void 0, void 0, function* () { - const symlinkIn = package_path ? `${package_path}/node_modules` : 'node_modules'; - if (path.dirname(m.name)) { - yield mkdirp(`${symlinkIn}/${path.dirname(m.name)}`); - } - if (m.link) { - const modulePath = m.link; - let target; - if (isExecroot) { - target = `${startCwd}/${modulePath}`; - } - if (!isExecroot || !existsSync(target)) { - let runfilesPath = modulePath; - if (runfilesPath.startsWith(`${bin}/`)) { - runfilesPath = runfilesPath.slice(bin.length + 1); - } - else if (runfilesPath === bin) { - runfilesPath = ''; - } - const externalPrefix = 'external/'; - if (runfilesPath.startsWith(externalPrefix)) { - runfilesPath = runfilesPath.slice(externalPrefix.length); - } - else { - runfilesPath = `${workspace}/${runfilesPath}`; - } - try { - target = runfiles.resolve(runfilesPath); - if (runfiles.manifest && modulePath.startsWith(`${bin}/`)) { - if (!target.match(_BAZEL_OUT_REGEX)) { - const e = new Error(`could not resolve module ${runfilesPath} in output tree`); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - } - } - catch (err) { - target = undefined; - log_verbose(`runfiles resolve failed for module '${m.name}': ${err.message}`); - } - } - if (target && !path.isAbsolute(target)) { - target = path.resolve(process.cwd(), target); - } - const symlinkFile = `${symlinkIn}/${m.name}`; - const stats = yield gracefulLstat(symlinkFile); - const isLeftOver = (stats !== null && (yield isLeftoverDirectoryFromLinker(stats, symlinkFile))); - if (target && (yield exists(target))) { - if (stats !== null && isLeftOver) { - yield createSymlinkAndPreserveContents(stats, symlinkFile, target); - } - else { - yield symlinkWithUnlink(target, symlinkFile, stats); - } - } - else { - if (!target) { - log_verbose(`no symlink target found for module ${m.name}`); - } - else { - log_verbose(`potential target ${target} does not exists for module ${m.name}`); - } - if (isLeftOver) { - yield unlink(symlinkFile); - } - } - } - if (m.children) { - yield Promise.all(m.children.map(m => linkModules(package_path, m))); - } - }); - } - const links = []; - for (const package_path of Object.keys(module_sets)) { - const modules = module_sets[package_path]; - log_verbose(`modules for package path '${package_path}':\n${JSON.stringify(modules, null, 2)}`); - const moduleHierarchy = reduceModules(modules); - log_verbose(`mapping hierarchy for package path '${package_path}':\n${JSON.stringify(moduleHierarchy)}`); - links.push(...moduleHierarchy.map(m => linkModules(package_path, m))); - } - let code = 0; - yield Promise.all(links).catch(e => { - log_error(e); - code = 1; - }); - return code; - }); + throw new Error('Could not resolve runfiles directory from any data sources.'); + })(); + log('Resolved runfiles path:', runfilesDirPath); + const modulesManifestPath = definedOrThrow(process.argv[2], 'argv[2] is required to locate modules manifest but is missing.'); + log('Modules manifest path:', modulesManifestPath); + const modulesManifestContent = fs.readFileSync(modulesManifestPath, 'utf-8'); + const modulesManifest = JSON.parse(modulesManifestContent); + log('Modules manifest contents:', JSON.stringify(modulesManifest, null, 2)); + log('Inferring symlink paths...'); + const symlinks = getProposedSymlinks(modulesManifest, runfilesDirPath); + for (const { filePath, targetPath } of symlinks) { + tryCreateSymlink(filePath, targetPath); + } + log('Saving symlink paths...'); + const nmSymlinks = definedOrThrow(process.env.NM_SYMLINKS, 'expected $NM_SYMLINKS to be set in the environment'); + fs.writeFileSync(nmSymlinks, JSON.stringify(symlinks), 'utf-8'); + log('Linking finished'); } -exports.main = main; if (require.main === module) { - if (Number(process.versions.node.split('.')[0]) < 10) { - console.error(`ERROR: rules_nodejs linker requires Node v10 or greater, but is running on ${process.versions.node}`); - console.error('Note that earlier Node versions are no longer in long-term-support, see'); - console.error('https://nodejs.org/en/about/releases/'); - process.exit(1); + try { + main(); + } + catch (err) { + fatal('unhandled exception', [ensureErrorInstance(err)]); } - (() => __awaiter(void 0, void 0, void 0, function* () { - try { - process.exitCode = yield main(process.argv.slice(2), _defaultRunfiles); - } - catch (e) { - log_error(e); - process.exitCode = 1; - } - }))(); } diff --git a/internal/linker/link_node_modules.bzl b/internal/linker/link_node_modules.bzl index 5d30429c9..ea04f1827 100644 --- a/internal/linker/link_node_modules.bzl +++ b/internal/linker/link_node_modules.bzl @@ -101,24 +101,6 @@ def write_node_modules_manifest(ctx, extra_data = [], mnemonic = None, link_work _debug(ctx.var, "Linking %s: %s" % (k, v)) mappings[k] = v - # Convert mappings to a module sets (modules per package package_path) - # { - # "package_path": { - # "package_name": "source_path", - # ... - # }, - # ... - # } - module_sets = {} - for k, v in mappings.items(): - map_key_split = k.split(":") - package_name = map_key_split[0] - package_path = map_key_split[1] if len(map_key_split) > 1 else "" - source_path = v[1] - if package_path not in module_sets: - module_sets[package_path] = {} - module_sets[package_path][package_name] = source_path - # Write the result to a file, and use the magic node option --bazel_node_modules_manifest # The launcher.sh will peel off this argument and pass it to the linker rather than the program. prefix = ctx.label.name @@ -126,8 +108,6 @@ def write_node_modules_manifest(ctx, extra_data = [], mnemonic = None, link_work prefix += "_%s" % mnemonic modules_manifest = ctx.actions.declare_file("_%s.module_mappings.json" % prefix) content = { - "bin": ctx.bin_dir.path, - "module_sets": module_sets, "roots": node_modules_roots, "workspace": ctx.workspace_name, } diff --git a/internal/linker/link_node_modules.ts b/internal/linker/link_node_modules.ts index 1ca96a134..d095a93bc 100644 --- a/internal/linker/link_node_modules.ts +++ b/internal/linker/link_node_modules.ts @@ -1,764 +1,232 @@ -/** - * @fileoverview Creates a node_modules directory in the current working directory - * and symlinks in the node modules needed to run a program. - * This replaces the need for custom module resolution logic inside the process. - */ import * as fs from 'fs'; import * as path from 'path'; -// We cannot rely from the linker on the `@bazel/runfiles` package, hence we import from -// the runfile helper through a checked-in file from `internal/runfiles`. In order to still -// have typings we use a type-only import to the `@bazel/runfiles` package that is the source -// of truth for the checked-in file. -const {runfiles: _defaultRunfiles, _BAZEL_OUT_REGEX}: - typeof import('@bazel/runfiles') = require('../runfiles/index.js') -import {Runfiles} from '@bazel/runfiles'; - // Run Bazel with --define=VERBOSE_LOGS=1 to enable this logging const VERBOSE_LOGS = !!process.env['VERBOSE_LOGS']; -function log_verbose(...m: string[]) { - if (VERBOSE_LOGS) console.error('[link_node_modules.js]', ...m); -} - -function log_error(error: Error) { - console.error('[link_node_modules.js] An error has been reported:', error, error.stack); +function log(...m: string[]): void { + if (VERBOSE_LOGS) { + console.error(loggingPrefix, ...m); + } } -/** - * Create a new directory and any necessary subdirectories - * if they do not exist. - */ -async function mkdirp(p: string) { - if (p && !await exists(p)) { - await mkdirp(path.dirname(p)); - log_verbose(`creating directory ${p} in ${process.cwd()}`); - try { - await fs.promises.mkdir(p); - } catch (e) { - if (e.code !== 'EEXIST') { - // can happen if path being created exists via a symlink - throw e; - } - } +function fatal(context: string, errors: Error[]): never { + console.error(loggingPrefix, 'Error(s) were reported.'); + console.error(loggingPrefix, 'Context:', context); + for (const error of errors) { + console.error(error); } + console.error(loggingPrefix, 'Exiting'); + process.exit(1); } +/** Runfiles directory path. e.g. `/__output_base__/execroot/com_canva_canva/bazel-out/___/bin/___/___.runfiles/web_node_modules/node_modules` */ +export type RunfilesDirPath = string & { __RunfilesDirPath: never }; +/** Mount point for `node_modules` relative to workspace root. e.g. `web`, `web/tools/cloudflare` */ +export type RelativeMountPath = string & { __RelativeMountPath: never }; +/** A workspace name. e.g. `com_canva_canva`, `rules_nodejs`, `web_node_modules` */ +export type WorkspaceName = string & { __RepositoryName: never }; + /** - * Gets the `lstat` results for a given path. Returns `null` if the path - * does not exist on disk. + * Manifest defining `node_modules` mounts that need to be created, and for which workspace. + * File defined in `@rules_nodejs//internal/linker/link_node_modules.bzl` */ -async function gracefulLstat(path: string): Promise { - try { - return await fs.promises.lstat(path); - } catch (e) { - if (e.code === 'ENOENT') { - return null; - } - throw e; - } -} +type ModulesManifest = { + roots: Record, + workspace: WorkspaceName, +}; + +/** Path for symlink file (aka link name). */ +type SymlinkFilePath = string & { __SymlinkFilePath: never }; +/** Path for symlink target. */ +type SymlinkTargetPath = string & { __SymlinkTargetPath: never }; +/** Describes potential symlinks. */ +type ProposedSymlink = { filePath: SymlinkFilePath, targetPath: SymlinkTargetPath }; + +// NOTE Trailing '/' not included in matcher to cover all scenarios (e.g. RUNFILES_DIR environment variable) +const runfilesPathMatcher = '.runfiles'; +const nodeModulesDir = 'node_modules'; +const loggingPrefix = '[node_modules-linker]'; /** - * Resolves a symlink to its linked path for a given path. Returns `null` if the path - * does not exist on disk. + * Infers a runfiles directory from the given path, throwing on failure. + * @param maybeRunfilesSource Path to inspect. */ -function gracefulReadlink(path: string): string|null { - try { - return fs.readlinkSync(path); - } catch (e) { - if (e.code === 'ENOENT') { - return null; +export function inferRunfilesDirFromPath(maybeRunfilesSource: string): RunfilesDirPath { + while (maybeRunfilesSource !== '/') { + if (maybeRunfilesSource.endsWith(runfilesPathMatcher)) { + return (maybeRunfilesSource + '/') as RunfilesDirPath; } - throw e; + + maybeRunfilesSource = path.dirname(maybeRunfilesSource); } + + throw new Error('Path does not contain a runfiles parent directory.'); } /** - * Lists the names of files and directories that exist in the given path. Returns an empty - * array if the path does not exist on disk. + * Gets the symlinks required to satisfy `node_modules` linking requirements. + * @param modulesManifest Modules manifest to read `node_modules` roots from. + * @param runfilesDirPath Runfiles directory symlinks should operate within. */ -async function gracefulReaddir(path: string): Promise { - try { - return await fs.promises.readdir(path); - } catch (e) { - if (e.code === 'ENOENT') { - return []; - } - throw e; - } +export function getProposedSymlinks( + modulesManifest: ModulesManifest, + runfilesDirPath: RunfilesDirPath, +): ProposedSymlink[] { + const symlinks: ProposedSymlink[] = []; + + let relativeMountParent: RelativeMountPath; + for (relativeMountParent in modulesManifest.roots) { + const repositoryName = modulesManifest.roots[relativeMountParent]; + const filePath = path.join( + runfilesDirPath, + modulesManifest.workspace, + relativeMountParent, + nodeModulesDir, + ) as SymlinkFilePath; + const targetPath = path.join( + runfilesDirPath, + repositoryName, + nodeModulesDir, + ) as SymlinkTargetPath; + symlinks.push({ filePath, targetPath }); + } + + return symlinks; } /** - * Deletes the given module name from the current working directory (i.e. symlink root). - * If the module name resolves to a directory, the directory is deleted. Otherwise the - * existing file or junction is unlinked. + * @todo Replace with `error.cause` once on NodeJS >=16.9 */ -async function unlink(moduleName: string) { - const stat = await gracefulLstat(moduleName); - if (stat === null) { - return; - } - log_verbose(`unlink( ${moduleName} )`); - if (stat.isDirectory()) { - await deleteDirectory(moduleName); - } else { - log_verbose("Deleting file: ", moduleName); - await fs.promises.unlink(moduleName); - } -} - -/** Asynchronously deletes a given directory (with contents). */ -async function deleteDirectory(p: string) { - log_verbose("Deleting children of", p); - for (let entry of await gracefulReaddir(p)) { - const childPath = path.join(p, entry); - const stat = await gracefulLstat(childPath); - if (stat === null) { - log_verbose(`File does not exist, but is listed as directory entry: ${childPath}`); - continue; - } - if (stat.isDirectory()) { - await deleteDirectory(childPath); - } else { - log_verbose("Deleting file", childPath); - await fs.promises.unlink(childPath); - } - } - log_verbose("Cleaning up dir", p); - await fs.promises.rmdir(p); +function ensureErrorInstance(err: unknown): Error { + return err instanceof Error + ? err + : new Error(`Non-error thrown, value was "${err ?? 'NULL_OR_UNDEFINED'}"`); } -async function symlink(target: string, p: string): Promise { - if (!path.isAbsolute(target)) { - target = path.resolve(process.cwd(), target); - } - log_verbose(`creating symlink ${p} -> ${target}`); - - // Use junction on Windows since symlinks require elevated permissions. - // We only link to directories so junctions work for us. +function tryRun(func: () => T): Error | T { try { - await fs.promises.symlink(target, p, 'junction'); - return true; - } catch (e) { - if (e.code !== 'EEXIST') { - throw e; - } - // We assume here that the path is already linked to the correct target. - // Could add some logic that asserts it here, but we want to avoid an extra - // filesystem access so we should only do it under some kind of strict mode. - - if (VERBOSE_LOGS) { - // Be verbose about creating a bad symlink - // Maybe this should fail in production as well, but again we want to avoid - // any unneeded file I/O - if (!await exists(p)) { - log_verbose( - 'ERROR\n***\nLooks like we created a bad symlink:' + - `\n pwd ${process.cwd()}\n target ${target}\n path ${p}\n***`); - } - } - return false; - } -} - -/** Determines an absolute path to the given workspace if it contains node modules. */ -async function resolveWorkspaceNodeModules( - workspace: string, startCwd: string, isExecroot: boolean, execroot: string|undefined, - runfiles: Runfiles) { - const targetManifestPath = `${workspace}/node_modules`; - - if (isExecroot) { - // Under execroot, the npm workspace will be under an external folder from the startCwd - // `execroot/my_wksp`. For example, `execroot/my_wksp/external/npm/node_modules`. If there is no - // npm workspace, which will be the case if there are no third-party modules dependencies for - // this target, npmWorkspace the root to `execroot/my_wksp/node_modules`. - return `${execroot}/external/${targetManifestPath}`; - } - - if (!execroot) { - // This can happen if we are inside a nodejs_image or a nodejs_binary is run manually. - // Resolve as if we are in runfiles in a sandbox. - return path.resolve(`${startCwd}/../${targetManifestPath}`) - } - - // Under runfiles, the linker should symlink node_modules at `execroot/my_wksp` - // so that when there are no runfiles (default on Windows) and scripts run out of - // `execroot/my_wksp` they can resolve node_modules with standard node_module resolution - - // If we got a runfilesManifest map, look through it for a resolution - // This will happen if we are running a binary that had some npm packages - // "statically linked" into its runfiles - const fromManifest = runfiles.lookupDirectory(targetManifestPath); - if (fromManifest) { - return fromManifest; - } else { - const maybe = path.resolve(`${execroot}/external/${targetManifestPath}`); - if (await exists(maybe)) { - // Under runfiles, when not in the sandbox we must symlink node_modules down at the execroot - // `execroot/my_wksp/external/npm/node_modules` since `runfiles/npm/node_modules` will be a - // directory and not a symlink back to the root node_modules where we expect - // to resolve from. This case is tested in internal/linker/test/local. - return maybe; - } - // However, when in the sandbox, `execroot/my_wksp/external/npm/node_modules` does not exist, - // so we must symlink into `runfiles/npm/node_modules`. This directory exists whether legacy - // external runfiles are on or off. - return path.resolve(`${startCwd}/../${targetManifestPath}`) + return func(); + } catch (err) { + return ensureErrorInstance(err); } } -// TypeScript lib.es5.d.ts has a mistake: JSON.parse does accept Buffer. -declare global { - interface JSON { - parse(b: {toString: () => string}): any; +function definedOrThrow(value: T, throwReason: string): Exclude { + if (value == null) { + throw new Error(throwReason); } -} -// There is no fs.promises.exists function because -// node core is of the opinion that exists is always too racey to rely on. -async function exists(p: string) { - return (await gracefulLstat(p) !== null); -} - -function existsSync(p: string|undefined) { - if (!p) { - return false; - } - try { - fs.lstatSync(p); - return true; - } catch (e) { - if (e.code === 'ENOENT') { - return false; - } - throw e; - } + return value as Exclude; } /** - * Given a set of module aliases returns an array of recursive `LinkerTreeElement`. - * - * The tree nodes represent the FS links required to represent the module aliases. - * Each node of the tree hierarchy depends on its parent node having been setup first. - * Each sibling node can be processed concurrently. - * - * The number of symlinks is minimized in situations such as: - * - * Shared parent path to lowest common denominator: - * `@foo/b/c => /path/to/a/b/c` - * - * can be represented as - * - * `@foo => /path/to/a` - * - * Shared parent path across multiple module names: - * `@foo/p/a => /path/to/x/a` - * `@foo/p/c => /path/to/x/a` - * - * can be represented as a single parent - * - * `@foo/p => /path/to/x` + * Creates symlink using a "try-else-check" approach to safeguard against concurrent creation. + * On failure the (assumed to exist) symlink is inspected, an error is thrown if the target path + * differs. */ -export function reduceModules(modules: LinkerAliases): LinkerTreeElement[] { - return buildModuleHierarchy(Object.keys(modules).sort(), modules, '/').children || []; -} - -function buildModuleHierarchy( - moduleNames: string[], modules: LinkerAliases, elementPath: string): LinkerTreeElement { - let element: LinkerTreeElement = { - name: elementPath.slice(0, -1), - link: modules[elementPath.slice(0, -1)], - children: [], - }; - - for (let i = 0; i < moduleNames.length;) { - const moduleName = moduleNames[i]; - const next = moduleName.indexOf('/', elementPath.length + 1); - const moduleGroup = (next === -1) ? (moduleName + '/') : moduleName.slice(0, next + 1); - - // An exact match (direct child of element) then it is the element parent, skip it - if (next === -1) { - i++; - } - - const siblings: string[] = []; - while (i < moduleNames.length && moduleNames[i].startsWith(moduleGroup)) { - siblings.push(moduleNames[i++]); - } - - let childElement = buildModuleHierarchy(siblings, modules, moduleGroup); - - for (let cur = childElement; (cur = liftElement(childElement)) !== childElement;) { - childElement = cur; - } - - element.children!.push(childElement); - } - - // Cleanup empty children+link - if (!element.link) { - delete element.link; - } - if (!element.children || element.children.length === 0) { - delete element.children; - } - - return element; -} - -function liftElement(element: LinkerTreeElement): LinkerTreeElement { - let {name, link, children} = element; +export function tryCreateSymlink(filePath: SymlinkFilePath, targetPath: SymlinkTargetPath): void { + log(`attempting to create symlink "${filePath}" -> "${targetPath}"`); + // Ensure parent directories exist, e.g. for @renderer_node_modules//rollup/bin:rollup + fs.mkdirSync(path.join(filePath, '..'), { recursive: true }); + const symlinkResult = tryRun(() => fs.symlinkSync(targetPath, filePath)); + if (symlinkResult instanceof Error) { + // Attempt failed, link likely already exists + const readlinkResult = tryRun(() => fs.readlinkSync(filePath)); - if (!children || !children.length) { - return element; - } - - // This element has a link and all the child elements have aligning links - // => this link alone represents that structure - if (link && allElementsAlignUnder(name, link, children)) { - return {name, link}; - } - - return element; -} - -function allElementsAlignUnder( - parentName: string, parentLink: Link, elements: LinkerTreeElement[]) { - for (const {name, link, children} of elements) { - if (!link || children) { - return false; + if (readlinkResult instanceof Error) { + // Very bad state, time to abort + fatal('symlink creation', [symlinkResult, readlinkResult]); } - if (!isDirectChildPath(parentName, name)) { - return false; + // Ensure symlink target matches requirements, or bail + if (readlinkResult !== targetPath) { + throw new Error( + `Invalid symlink target path "${readlinkResult}" detected, wanted "${targetPath}" for symlink at "${filePath}"`, + ); } - if (!isDirectChildLink(parentLink, link)) { - return false; - } - - if (!isNameLinkPathTopAligned(name, link)) { - return false; - } + log('symlink already exists'); } - - return true; -} - -function isDirectChildPath(parent: string, child: string) { - return parent === path.dirname(child); -} - -function isDirectChildLink(parentLink: Link, childLink: Link) { - return parentLink === path.dirname(childLink); } -function isNameLinkPathTopAligned(namePath: string, linkPath: Link) { - return path.basename(namePath) === path.basename(linkPath); -} - -async function visitDirectoryPreserveLinks( - dirPath: string, visit: (filePath: string, stat: fs.Stats) => Promise) { - for (const entry of await fs.promises.readdir(dirPath)) { - const childPath = path.join(dirPath, entry); - const stat = await gracefulLstat(childPath); - if (stat === null) { - continue; - } - if (stat.isDirectory()) { - await visitDirectoryPreserveLinks(childPath, visit); - } else { - await visit(childPath, stat); - } - } -} - -export type Link = string; -export type LinkerTreeElement = { - name: string, - link?: Link, - children?: LinkerTreeElement[], -}; -export type LinkerAliases = { - [name: string]: Link -}; - -function findExecroot(startCwd: string): string|undefined { - // We can derive if the process is being run in the execroot if there is a bazel-out folder - if (existsSync(`${startCwd}/bazel-out`)) { - return startCwd; - } - - // Look for bazel-out which is used to determine the the path to `execroot/my_wksp`. This works in - // all cases including on rbe where the execroot is a path such as `/b/f/w`. For example, when in - // runfiles on rbe, bazel runs the process in a directory such as - // `/b/f/w/bazel-out/k8-fastbuild/bin/path/to/pkg/some_test.sh.runfiles/my_wksp`. From here we can - // determine the execroot `b/f/w` by finding the first instance of bazel-out. - // NB: If we are inside nodejs_image or a nodejs_binary run manually there may be no execroot - // found. - const bazelOutMatch = startCwd.match(_BAZEL_OUT_REGEX); - return bazelOutMatch ? startCwd.slice(0, bazelOutMatch.index) : undefined; -} - -export async function main(args: string[], runfiles: Runfiles) { - if (!args || args.length < 1) throw new Error('requires one argument: modulesManifest path'); - - const [modulesManifest] = args; - log_verbose('manifest file:', modulesManifest); - - let {workspace, bin, roots, module_sets} = JSON.parse(fs.readFileSync(modulesManifest)); - log_verbose('manifest contents:', JSON.stringify({workspace, bin, roots, module_sets}, null, 2)); - roots = roots || {}; - module_sets = module_sets || {}; - - // Bazel starts actions with pwd=execroot/my_wksp when under execroot or pwd=runfiles/my_wksp - // when under runfiles. - // Normalize the slashes in startCwd for easier matching and manipulation. - const startCwd = process.cwd().replace(/\\/g, '/'); - log_verbose('startCwd:', startCwd); +const removeNulls = (value: S | undefined): value is S => value != null; - const execroot = findExecroot(startCwd); - log_verbose('execroot:', execroot ? execroot : 'not found'); +function main() { + log('Linking started'); + // Collect potential runfiles dir sources - const isExecroot = startCwd == execroot; - log_verbose('isExecroot:', isExecroot.toString()); + // Sometimes cwd is under runfiles + const cwd = process.cwd(); + log('cwd:', cwd); - if (!isExecroot && execroot) { - // If we're not in the execroot and we've found one then change to the execroot - // directory to create the node_modules symlinks - process.chdir(execroot); - log_verbose('changed directory to execroot', execroot); - } - - async function symlinkWithUnlink( - target: string, p: string, stats: fs.Stats|null = null): Promise { - if (!path.isAbsolute(target)) { - target = path.resolve(process.cwd(), target); - } - if (stats === null) { - stats = await gracefulLstat(p); - } - // Check if this an an old out-of-date symlink - // If we are running without a runfiles manifest (i.e. in sandbox or with symlinked runfiles), - // then this is guaranteed to be not an artifact from a previous linker run. If not we need to - // check. - if (runfiles.manifest && execroot && stats !== null && stats.isSymbolicLink()) { - // Although `stats` suggests that the file exists as a symlink, it may have been deleted by - // another process. Only proceed unlinking if the file actually still exists. - const symlinkPathRaw = gracefulReadlink(p); - if (symlinkPathRaw !== null) { - const symlinkPath = symlinkPathRaw.replace(/\\/g, '/'); - if (path.relative(symlinkPath, target) != '' && - !path.relative(execroot, symlinkPath).startsWith('..')) { - // Left-over out-of-date symlink from previous run. This can happen if switching between - // root configuration options such as `--noenable_runfiles` and/or - // `--spawn_strategy=standalone`. It can also happen if two different targets link the - // same module name to different targets in a non-sandboxed environment. The latter will - // lead to undeterministic behavior. - // TODO: can we detect the latter case and throw an apprioriate error? - log_verbose(`Out-of-date symlink for ${p} to ${symlinkPath} detected. Target should be ${ - target}. Unlinking.`); - await unlink(p); - } else { - log_verbose(`The symlink at ${p} no longer exists, so no need to unlink it.`); - } - } - } - return symlink(target, p); - } - - // Symlink all node_modules roots defined. These are 3rd party deps in external npm workspaces - // lined to node_modules folders at the root or in sub-directories - for (const packagePath of Object.keys(roots)) { - const workspace = roots[packagePath]; - if (workspace) { - const workspaceNodeModules = await resolveWorkspaceNodeModules( - workspace, startCwd, isExecroot, execroot, runfiles); - log_verbose(`resolved ${workspace} workspace node modules path to ${workspaceNodeModules}`); - - if (packagePath) { - // sub-directory node_modules - if (await exists(workspaceNodeModules)) { - // in some cases packagePath may not exist in sandbox if there are no source deps - // and only generated file deps. we create it so that we that we can link to - // packagePath/node_modules since packagePathBin/node_modules is a symlink to - // packagePath/node_modules and is unguarded in launcher.sh as we allow symlinks to fall - // through to from output tree to source tree to prevent resolving the same npm package to - // multiple locations on disk - await mkdirp(packagePath); - await symlinkWithUnlink(workspaceNodeModules, `${packagePath}/node_modules`); - if (!isExecroot) { - // Under runfiles, we symlink into the package in runfiles as well. - // When inside the sandbox, the execroot location will not exist to symlink to. - const runfilesPackagePath = `${startCwd}/${packagePath}`; - if (await exists(runfilesPackagePath)) { - await symlinkWithUnlink( - `${packagePath}/node_modules`, `${runfilesPackagePath}/node_modules`); - } - } - const packagePathBin = `${bin}/${packagePath}`; - if (await exists(packagePathBin)) { - // if bin path exists, symlink bin/package/node_modules -> package/node_modules - // NB: this location is unguarded in launcher.sh to allow symlinks to fall-throught - // package/node_modules to prevent resolving the same npm package to multiple locations - // on disk - await symlinkWithUnlink( - `${packagePath}/node_modules`, `${packagePathBin}/node_modules`); - } - } else { - // Special case if there no target to symlink to for the root workspace, create a - // root node_modules folder for 1st party deps - log_verbose(`no npm workspace node_modules folder under ${ - packagePath} to link to; creating node_modules directories in ${process.cwd()} for ${ - packagePath} 1p deps`); - await mkdirp(`${packagePath}/node_modules`); - if (!isExecroot) { - // Under runfiles, we symlink into the package in runfiles as well. - // When inside the sandbox, the execroot location will not exist to symlink to. - const runfilesPackagePath = `${startCwd}/${packagePath}`; - await mkdirp(`${runfilesPackagePath}/node_modules`); - await symlinkWithUnlink( - `${packagePath}/node_modules`, `${runfilesPackagePath}/node_modules`); - } - const packagePathBin = `${bin}/${packagePath}`; - await mkdirp(`${packagePathBin}/node_modules`); - await symlinkWithUnlink(`${packagePath}/node_modules`, `${packagePathBin}/node_modules`); - } - } else { - // root node_modules - if (await exists(workspaceNodeModules)) { - await symlinkWithUnlink(workspaceNodeModules, `node_modules`); - } else { - // Special case if there no target to symlink to for the root workspace, create a - // root node_modules folder for 1st party deps - log_verbose( - `no root npm workspace node_modules folder to link to; creating node_modules directory in ${ - process.cwd()}`); - await mkdirp('node_modules'); - } - } - } else { - if (packagePath) { - // Special case if there for first party node_modules at root only - log_verbose(`no 3p deps at ${packagePath}; creating node_modules directories in ${ - process.cwd()} for ${packagePath} 1p deps`); - await mkdirp(`${packagePath}/node_modules`); - if (!isExecroot) { - // Under runfiles, we symlink into the package in runfiles as well. - // When inside the sandbox, the execroot location will not exist to symlink to. - const runfilesPackagePath = `${startCwd}/${packagePath}`; - await mkdirp(`${runfilesPackagePath}/node_modules`); - await symlinkWithUnlink( - `${packagePath}/node_modules`, `${runfilesPackagePath}/node_modules`); - } - const packagePathBin = `${bin}/${packagePath}`; - await mkdirp(`${packagePathBin}/node_modules`); - await symlinkWithUnlink(`${packagePath}/node_modules`, `${packagePathBin}/node_modules`); - } else { - // Special case if there for first party node_modules at root only - log_verbose(`no 3p deps at root; creating node_modules directory in ${ - process.cwd()} for root 1p deps`); - await mkdirp('node_modules'); - } - } - } - - /** - * Whether the given module resolves to a directory that has been created by a previous linker - * run purely to make space for deep module links. e.g. consider a mapping for `my-pkg/a11y`. - * The linker will create folders like `node_modules/my-pkg/` so that the `a11y` symbolic - * junction can be created. The `my-pkg` folder is then considered a leftover from a previous - * linker run as it only contains symbolic links and no actual source files. - */ - async function isLeftoverDirectoryFromLinker(stats: fs.Stats, modulePath: string) { - // If we are running without a runfiles manifest (i.e. in sandbox or with symlinked runfiles), - // then this is guaranteed to be not an artifact from a previous linker run. - if (runfiles.manifest === undefined) { - return false; - } - if (!stats.isDirectory()) { - return false; - } - let isLeftoverFromPreviousLink = true; - // If the directory contains actual files, this cannot be a leftover from a previous - // linker run. The linker only creates directories in the node modules that hold - // symbolic links for configured module mappings. - await visitDirectoryPreserveLinks(modulePath, async (childPath, childStats) => { - if (!childStats.isSymbolicLink()) { - isLeftoverFromPreviousLink = false; + // Runfiles environment variables are the preferred reference point, but can fail + log('RUNFILES_DIR environment variable:', process.env.RUNFILES_DIR ?? '(unset)'); + log('RUNFILES environment variable:', process.env.RUNFILES ?? '(unset)'); + const envRunfilesCanidates = [process.env.RUNFILES_DIR, process.env.RUNFILES] + .filter(removeNulls) + .map(runfilesDir => { + const adjustedRunfilesDir = fs.realpathSync(runfilesDir); + if (runfilesDir !== adjustedRunfilesDir) { + log( + `Symlink dereferenced from runfiles path. Was "${runfilesDir}" now "${adjustedRunfilesDir}"`, + ); + return adjustedRunfilesDir; } + return runfilesDir; }); - return isLeftoverFromPreviousLink; - } - /** - * Creates a symlink for the given module. Existing child symlinks which are part of - * the module are preserved in order to not cause race conditions in non-sandbox - * environments where multiple actions rely on the same node modules root. - * - * To avoid unexpected resource removal, a new temporary link for the target is created. - * Then all symlinks from the existing module are cloned. Once done, the existing module - * is unlinked while the temporary link takes place for the given module. This ensures - * that the module link is never removed at any time (causing race condition failures). - */ - async function createSymlinkAndPreserveContents(stats: fs.Stats, modulePath: string, - target: string) { - const tmpPath = `${modulePath}__linker_tmp`; - log_verbose(`createSymlinkAndPreserveContents( ${modulePath} )`); - - await symlink(target, tmpPath); - await visitDirectoryPreserveLinks(modulePath, async (childPath, stat) => { - if (stat.isSymbolicLink()) { - const targetPath = path.join(tmpPath, path.relative(modulePath, childPath)); - log_verbose(`Cloning symlink into temporary created link ( ${childPath} )`); - await mkdirp(path.dirname(targetPath)); - await symlink(targetPath, await fs.promises.realpath(childPath)); + // Infer runfiles dir + + const runfilesDirPath: RunfilesDirPath = (() => { + for (const maybeRunfilesSource of [...envRunfilesCanidates, cwd]) { + try { + log(`Attempting to infer runfiles directory from "${maybeRunfilesSource}"`); + return inferRunfilesDirFromPath(maybeRunfilesSource); + } catch (err) { + log( + `Could not resolve runfiles directory from "${maybeRunfilesSource}"`, + ensureErrorInstance(err).message, + ); } - }); - - log_verbose(`Removing existing module so that new link can take place ( ${modulePath} )`); - await unlink(modulePath); - await fs.promises.rename(tmpPath, modulePath); - } + } + throw new Error('Could not resolve runfiles directory from any data sources.'); + })(); + log('Resolved runfiles path:', runfilesDirPath); - async function linkModules(package_path: string, m: LinkerTreeElement) { - const symlinkIn = package_path ? `${package_path}/node_modules` : 'node_modules'; + // Get required links from modules manifest - // ensure the parent directory exist - if (path.dirname(m.name)) { - await mkdirp(`${symlinkIn}/${path.dirname(m.name)}`); - } + const modulesManifestPath = definedOrThrow( + process.argv[2], + 'argv[2] is required to locate modules manifest but is missing.', + ); + log('Modules manifest path:', modulesManifestPath); - if (m.link) { - const modulePath = m.link; - let target: string|undefined; - if (isExecroot) { - // If we're running out of the execroot, try the execroot path first. - // If the dependency came in exclusively from a transitive binary target - // then the module won't be at this path but in the runfiles of the binary. - // In that case we'll fallback to resolving via runfiles below. - target = `${startCwd}/${modulePath}`; - } - if (!isExecroot || !existsSync(target)) { - // Transform execroot path to the runfiles manifest path so that - // it can be resolved with runfiles.resolve() - let runfilesPath = modulePath; - if (runfilesPath.startsWith(`${bin}/`)) { - runfilesPath = runfilesPath.slice(bin.length + 1); - } else if (runfilesPath === bin) { - runfilesPath = ''; - } - const externalPrefix = 'external/'; - if (runfilesPath.startsWith(externalPrefix)) { - runfilesPath = runfilesPath.slice(externalPrefix.length); - } else { - runfilesPath = `${workspace}/${runfilesPath}`; - } - try { - target = runfiles.resolve(runfilesPath); - // if we're resolving from a manifest then make sure we don't resolve - // into the source tree when we are expecting the output tree - if (runfiles.manifest && modulePath.startsWith(`${bin}/`)) { - // Check for BAZEL_OUT_REGEX and not /${bin}/ since resolution - // may be in the `/bazel-out/host` if cfg = "host" - if (!target.match(_BAZEL_OUT_REGEX)) { - const e = new Error(`could not resolve module ${runfilesPath} in output tree`); - (e as any).code = 'MODULE_NOT_FOUND'; - throw e; - } - } - } catch (err) { - target = undefined; - log_verbose(`runfiles resolve failed for module '${m.name}': ${err.message}`); - } - } - // Ensure target path absolute for consistency - if (target && !path.isAbsolute(target)) { - target = path.resolve(process.cwd(), target); - } + const modulesManifestContent = fs.readFileSync(modulesManifestPath, 'utf-8'); + const modulesManifest = JSON.parse(modulesManifestContent) as ModulesManifest; + log('Modules manifest contents:', JSON.stringify(modulesManifest, null, 2)); - const symlinkFile = `${symlinkIn}/${m.name}`; - - // In environments where runfiles are not symlinked (e.g. Windows), existing linked - // modules are preserved. This could cause issues when a link is created at higher level - // as a conflicting directory is already on disk. e.g. consider in a previous run, we - // linked the modules `my-pkg/overlay`. Later on, in another run, we have a module mapping - // for `my-pkg` itself. The linker cannot create `my-pkg` because the directory `my-pkg` - // already exists. To ensure that the desired link is generated, we create the new desired - // link and move all previous nested links from the old module into the new link. Read more - // about this in the description of `createSymlinkAndPreserveContents`. - const stats = await gracefulLstat(symlinkFile); - const isLeftOver = - (stats !== null && await isLeftoverDirectoryFromLinker(stats, symlinkFile)); - - // Check if the target exists before creating the symlink. - // This is an extra filesystem access on top of the symlink but - // it is necessary for the time being. - if (target && await exists(target)) { - if (stats !== null && isLeftOver) { - await createSymlinkAndPreserveContents(stats, symlinkFile, target); - } else { - await symlinkWithUnlink(target, symlinkFile, stats); - } - } else { - if (!target) { - log_verbose(`no symlink target found for module ${m.name}`); - } else { - // This can happen if a module mapping is propogated from a dependency - // but the target that generated the mapping in not in the deps. We don't - // want to create symlinks to non-existant targets as this will - // break any nested symlinks that may be created under the module name - // after this. - log_verbose(`potential target ${target} does not exists for module ${m.name}`); - } - if (isLeftOver) { - // Remove left over directory if it exists - await unlink(symlinkFile); - } - } - } + // Create links - // Process each child branch concurrently - if (m.children) { - await Promise.all(m.children.map(m => linkModules(package_path, m))); - } - } + log('Inferring symlink paths...'); + const symlinks = getProposedSymlinks(modulesManifest, runfilesDirPath); - const links = []; - for (const package_path of Object.keys(module_sets)) { - const modules = module_sets[package_path] - log_verbose(`modules for package path '${package_path}':\n${JSON.stringify(modules, null, 2)}`); - const moduleHierarchy = reduceModules(modules); - log_verbose(`mapping hierarchy for package path '${package_path}':\n${ - JSON.stringify(moduleHierarchy)}`); - // Process each root branch concurrently - links.push(...moduleHierarchy.map(m => linkModules(package_path, m))) + for (const { filePath, targetPath } of symlinks) { + tryCreateSymlink(filePath, targetPath); } - let code = 0; - await Promise.all(links).catch(e => { - log_error(e); - code = 1; - }); + // RBE HACK Advertise links + log('Saving symlink paths...'); + const nmSymlinks = definedOrThrow(process.env.NM_SYMLINKS, 'expected $NM_SYMLINKS to be set in the environment'); + fs.writeFileSync(nmSymlinks, JSON.stringify(symlinks), 'utf-8'); - return code; + log('Linking finished'); } if (require.main === module) { - if (Number(process.versions.node.split('.')[0]) < 10) { - console.error(`ERROR: rules_nodejs linker requires Node v10 or greater, but is running on ${ - process.versions.node}`); - console.error('Note that earlier Node versions are no longer in long-term-support, see'); - console.error('https://nodejs.org/en/about/releases/'); - process.exit(1); + try { + main(); + } catch (err) { + fatal('unhandled exception', [ensureErrorInstance(err)]); } - (async () => { - try { - process.exitCode = await main(process.argv.slice(2), _defaultRunfiles); - } catch (e) { - log_error(e); - process.exitCode = 1; - } - })(); } diff --git a/internal/linker/test/BUILD.bazel b/internal/linker/test/BUILD.bazel index d14bc1982..1a3b39c3f 100644 --- a/internal/linker/test/BUILD.bazel +++ b/internal/linker/test/BUILD.bazel @@ -1,21 +1 @@ -load("//packages/jasmine:index.bzl", "jasmine_node_test") -load("//packages/typescript:index.bzl", "ts_library") - -ts_library( - name = "test_lib", - srcs = glob(["*.ts"]), - deps = [ - "//internal/linker:linker_lib", - "//packages/runfiles:bazel_runfiles", - "@npm//@types/jasmine", - "@npm//@types/node", - ], -) - -jasmine_node_test( - name = "unit_tests", - srcs = ["test_lib"], - data = [ - "//internal/linker:linker_js", - ], -) +# TODO Wire up linker unit tests diff --git a/internal/linker/test/integration/BUILD.bazel b/internal/linker/test/integration/BUILD.bazel deleted file mode 100644 index 3bd092b08..000000000 --- a/internal/linker/test/integration/BUILD.bazel +++ /dev/null @@ -1,150 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "generated_file_test") -load(":rule.bzl", "linked") - -# Use the node binary supplied by the bazel toolchain -# Normally the nodejs_binary rule would do this, -# but we want to have a more minimal test fixture -genrule( - name = "replace_node_path", - srcs = [":run_program.sh"], - outs = ["run_program_with_node.sh"], - cmd = "sed s#NODE_PATH#$(NODE_PATH)# $< > $@", - toolchains = ["@build_bazel_rules_nodejs//toolchains/node:toolchain"], -) - -# A program from a *_test rule with a module mapped package that will -# by linked as "runfiles" mapping by downstream rules -sh_test( - name = "some_program", - srcs = ["some_program.sh"], - data = [ - "//internal/linker/test/integration/transitive_static_linked_pkg", - ], -) - -# Make our program executable and include the linker -# The runfiles here are only the ones included with the program itself -sh_binary( - name = "run_program", - testonly = True, - srcs = ["run_program_with_node.sh"], - data = [ - ":program.js", - # Include `:some_program` as a data dep so we get the - # transitive_static_linked_pkg "runfiles" mapping transitively. This test verifies that - # `bazel-out/darwin-fastbuild/bin/internal/linker/test/integration/run_program.runfiles` - # also contains the runfiles from - # `bazel-out/darwin-fastbuild/bin/internal/linker/test/integration/some_program.runfiles` - # as it has `:some_program` as a data dep and that the linker will correctly link - # "runfiles" module mappings from `:some_program` in `:run_program` under the - ":some_program", - "//internal/linker:index.js", - "//internal/runfiles:index.js", - "//internal/runfiles:runfile_helper_main.js", - "//internal/linker/test/integration/static_linked_pkg", - "//internal/linker/test/integration/static_linked_scoped_pkg", - "//internal/linker/test/integration/absolute_import:index.js", - "//third_party/github.com/bazelbuild/bazel/tools/bash/runfiles", - "//toolchains/node:node_bin", - ], -) - -# How a users rule might want to run a node program -linked( - name = "example", - testonly = True, - out = "actual", - program = ":run_program", - deps = [ - ":run_program", - # NB: Verify that a direct dep on :some_program and a transitive dep on - # the same via :run_program works with the linker. These should both - # bring in the transitive_static_linked mapping to - # ["runfiles", "build_bazel_rules_nodejs/internal/linker/test/integration/transitive_static_linked_pkg"]. - # This tests that the linker does "runfiles" mappings for both *_binary target - # and *_test targets. - ":some_program", - "//internal/linker/test/integration/dynamic_linked_pkg", - "//internal/linker/test/integration/dynamic_linked_scoped_pkg", - "@npm//semver", - ], -) - -# Test that the linker can handle duplicate module mappings. "src" and "bin" -# mappings will win over the "runfiles" mapping that comes from a dep on a -# *_binary or *_test rule. -linked( - name = "example_with_conflicts", - testonly = True, - out = "actual_with_conflicts", - program = ":run_program", - # do not sort - deps = [ - # NB: reference the copy of index.js in the output folder - # Intentinally include this before static_linked_pkg as order matters for the linker. - # The order here exercises a different code path in the linker conflict resolution logic - # than `example_with_conflicts_alt`. - ":run_program", - # NB: static_linked maps to both - # ["runfiles", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_pkg"] and - # ["bin", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_pkg"] - # as the "runfiles" mapping comes from `:run_program` - "//internal/linker/test/integration/static_linked_pkg", - # NB: @linker_scoped/static_linked maps to both - # ["runfiles", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_scoped_pkg"] and - # ["src", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_scoped_pkg"] - # as the "runfiles" mapping comes from `:run_program` - "//internal/linker/test/integration/static_linked_scoped_pkg", - "//internal/linker/test/integration/dynamic_linked_pkg", - "//internal/linker/test/integration/dynamic_linked_scoped_pkg", - "@npm//semver", - ], -) - -linked( - name = "example_with_conflicts_alt", - testonly = True, - out = "actual_with_conflicts_alt", - program = ":run_program", - # do not sort - deps = [ - # NB: static_linked maps to both - # ["runfiles", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_pkg"] and - # ["bin", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_pkg"] - # as the "runfiles" mapping comes from `:run_program` - "//internal/linker/test/integration/static_linked_pkg", - # NB: @linker_scoped/static_linked maps to both - # ["runfiles", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_scoped_pkg"] and - # ["src", "build_bazel_rules_nodejs/internal/linker/test/integration/static_linked_scoped_pkg"] - # as the "runfiles" mapping comes from `:run_program` - "//internal/linker/test/integration/static_linked_scoped_pkg", - # Intentinally include this before static_linked_pkg as order matters for the linker. - # The order here exercises a different code path in the linker conflict resolution logic - # than `example_with_conflicts`. - ":run_program", - "//internal/linker/test/integration/dynamic_linked_pkg", - "//internal/linker/test/integration/dynamic_linked_scoped_pkg", - "@npm//semver", - ], -) - -generated_file_test( - # default rule in this package - name = "integration", - src = "golden.txt", - generated = "actual", -) - -generated_file_test( - # default rule in this package - name = "integration_conflicts", - src = "golden.txt", - generated = "actual_with_conflicts", -) - -generated_file_test( - # default rule in this package - name = "integration_conflicts_alt", - src = "golden.txt", - generated = "actual_with_conflicts_alt", -) diff --git a/internal/linker/test/integration/absolute_import/BUILD.bazel b/internal/linker/test/integration/absolute_import/BUILD.bazel deleted file mode 100644 index 3d2f1d6cc..000000000 --- a/internal/linker/test/integration/absolute_import/BUILD.bazel +++ /dev/null @@ -1,3 +0,0 @@ -package(default_visibility = ["//internal/linker/test:__subpackages__"]) - -exports_files(["index.js"]) diff --git a/internal/linker/test/integration/absolute_import/index.js b/internal/linker/test/integration/absolute_import/index.js deleted file mode 100644 index 435240965..000000000 --- a/internal/linker/test/integration/absolute_import/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function addC(str) { - return `${str}_c`; -} - -exports.addC = addC; \ No newline at end of file diff --git a/internal/linker/test/integration/dynamic_linked_pkg/BUILD.bazel b/internal/linker/test/integration/dynamic_linked_pkg/BUILD.bazel deleted file mode 100644 index e2a0663e1..000000000 --- a/internal/linker/test/integration/dynamic_linked_pkg/BUILD.bazel +++ /dev/null @@ -1,9 +0,0 @@ -load("//:index.bzl", "js_library") - -package(default_visibility = ["//internal/linker/test:__subpackages__"]) - -js_library( - name = "dynamic_linked_pkg", - package_name = "dynamic_linked", - srcs = ["index.js"], -) diff --git a/internal/linker/test/integration/dynamic_linked_pkg/index.js b/internal/linker/test/integration/dynamic_linked_pkg/index.js deleted file mode 100644 index 4923d5e6c..000000000 --- a/internal/linker/test/integration/dynamic_linked_pkg/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function addB(str) { - return `${str}_b`; -} - -exports.addB = addB; \ No newline at end of file diff --git a/internal/linker/test/integration/dynamic_linked_scoped_pkg/BUILD.bazel b/internal/linker/test/integration/dynamic_linked_scoped_pkg/BUILD.bazel deleted file mode 100644 index dbc3be1d9..000000000 --- a/internal/linker/test/integration/dynamic_linked_scoped_pkg/BUILD.bazel +++ /dev/null @@ -1,9 +0,0 @@ -load("//:index.bzl", "js_library") - -package(default_visibility = ["//internal/linker/test:__subpackages__"]) - -js_library( - name = "dynamic_linked_scoped_pkg", - package_name = "@linker_scoped/dynamic_linked", - srcs = ["index.js"], -) diff --git a/internal/linker/test/integration/dynamic_linked_scoped_pkg/index.js b/internal/linker/test/integration/dynamic_linked_scoped_pkg/index.js deleted file mode 100644 index a88998bdb..000000000 --- a/internal/linker/test/integration/dynamic_linked_scoped_pkg/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function addD(str) { - return `${str}_d`; -} - -exports.addD = addD; \ No newline at end of file diff --git a/internal/linker/test/integration/golden.txt b/internal/linker/test/integration/golden.txt deleted file mode 100644 index b0d8a5dd9..000000000 --- a/internal/linker/test/integration/golden.txt +++ /dev/null @@ -1 +0,0 @@ -1.2.3_a_b_c_d_e_t diff --git a/internal/linker/test/integration/program.js b/internal/linker/test/integration/program.js deleted file mode 100644 index 3a56cb913..000000000 --- a/internal/linker/test/integration/program.js +++ /dev/null @@ -1,29 +0,0 @@ -// First-party "static linked" packages -// they should get resolved through runfiles -const a = require('static_linked'); -const e = require('@linker_scoped/static_linked'); -const t = require('transitive_static_linked'); -// First-party "dynamic linked" packages -// they should get resolved from the execroot -const b = require('dynamic_linked'); -const d = require('@linker_scoped/dynamic_linked'); - -let c; -try { - // As of 2.0, we no longer support `require('my_workspace/path/to/output/file.js')` for absolute - // imports - c = require('build_bazel_rules_nodejs/internal/linker/test/integration/absolute_import'); - console.error('should have failed'); - process.exit(1); -} catch (_) { - // You now need to use the runfiles helper library to resolve absolute workspace imports - const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']); - c = require(runfiles.resolve( - 'build_bazel_rules_nodejs/internal/linker/test/integration/absolute_import')); -} - -// Third-party package installed in the root node_modules -const semver = require('semver'); - -// This output should match what's in the golden.txt file -console.log(t.addT(e.addE(d.addD(c.addC(b.addB(a.addA(semver.clean(' =v1.2.3 ')))))))); diff --git a/internal/linker/test/integration/rule.bzl b/internal/linker/test/integration/rule.bzl deleted file mode 100644 index d146c78d5..000000000 --- a/internal/linker/test/integration/rule.bzl +++ /dev/null @@ -1,24 +0,0 @@ -"Minimal fixture for executing the linker's starlark code" - -load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "module_mappings_aspect", "write_node_modules_manifest") - -def _linked(ctx): - modules_manifest = write_node_modules_manifest(ctx) - ctx.actions.run( - inputs = ctx.files.deps + [modules_manifest], - outputs = [ctx.outputs.out], - executable = ctx.executable.program, - arguments = [ - "--bazel_node_modules_manifest=%s" % modules_manifest.path, - ctx.outputs.out.path, - ], - ) - -linked = rule(_linked, attrs = { - "deps": attr.label_list( - allow_files = True, - aspects = [module_mappings_aspect], - ), - "out": attr.output(), - "program": attr.label(executable = True, cfg = "host", mandatory = True), -}) diff --git a/internal/linker/test/integration/run_program.sh b/internal/linker/test/integration/run_program.sh deleted file mode 100755 index b5526cc91..000000000 --- a/internal/linker/test/integration/run_program.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2019 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This shell script is a minimal fixture for the launcher.sh script -# with a critical difference: instead of calling the loader.js script -# with the users program passed as an argument (allowing us to patch the node -# loader), this one just runs vanilla node with the users program as the argument -# which lets us assert that the linker is the reason the program works. - -# --- begin runfiles.bash initialization v2 --- -# Copy-pasted from the Bazel Bash runfiles library v2. -set -uo pipefail; f=build_bazel_rules_nodejs/third_party/github.com/bazelbuild/bazel/tools/bash/runfiles/runfiles.bash -source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ - source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ - source "$0.runfiles/$f" 2>/dev/null || \ - source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ - source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ - { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e -# --- end runfiles.bash initialization v2 --- - -# Turn on extra logging so that test failures are easier to debug -export VERBOSE_LOGS=1 -# Too spammy for CI logs -# export NODE_DEBUG=module - -# Export the location of the runfiles helpers script -export BAZEL_NODE_RUNFILES_HELPER=$(rlocation "build_bazel_rules_nodejs/internal/runfiles/runfile_helper_main.js") -if [[ "${BAZEL_NODE_RUNFILES_HELPER}" != /* ]] && [[ ! "${BAZEL_NODE_RUNFILES_HELPER}" =~ ^[A-Z]:[\\/] ]]; then - export BAZEL_NODE_RUNFILES_HELPER=$(pwd)/${BAZEL_NODE_RUNFILES_HELPER} -fi - -for ARG in "$@"; do - case "$ARG" in - --bazel_node_modules_manifest=*) MODULES_MANIFEST="${ARG#--bazel_node_modules_manifest=}" ;; - *) OUT="$ARG" - esac -done - -readonly DIR="build_bazel_rules_nodejs/internal/linker" - -$(rlocation NODE_PATH) \ - $(rlocation $DIR/index.js) \ - $MODULES_MANIFEST - -$(rlocation NODE_PATH) \ - --preserve-symlinks-main \ - $(rlocation $DIR/test/integration/program.js) \ - > $OUT diff --git a/internal/linker/test/integration/some_program.sh b/internal/linker/test/integration/some_program.sh deleted file mode 100755 index e7a5b921d..000000000 --- a/internal/linker/test/integration/some_program.sh +++ /dev/null @@ -1 +0,0 @@ -echo "Just some program that is never executed and just used for its static transitive runfiles module mappings" diff --git a/internal/linker/test/integration/static_linked_pkg/BUILD.bazel b/internal/linker/test/integration/static_linked_pkg/BUILD.bazel deleted file mode 100644 index b7f114006..000000000 --- a/internal/linker/test/integration/static_linked_pkg/BUILD.bazel +++ /dev/null @@ -1,9 +0,0 @@ -load("//:index.bzl", "js_library") - -package(default_visibility = ["//internal/linker/test:__subpackages__"]) - -js_library( - name = "static_linked_pkg", - package_name = "static_linked", - srcs = ["index.js"], -) diff --git a/internal/linker/test/integration/static_linked_pkg/index.js b/internal/linker/test/integration/static_linked_pkg/index.js deleted file mode 100644 index 43cefaa0d..000000000 --- a/internal/linker/test/integration/static_linked_pkg/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function addA(str) { - return `${str}_a`; -} - -exports.addA = addA; \ No newline at end of file diff --git a/internal/linker/test/integration/static_linked_scoped_pkg/BUILD.bazel b/internal/linker/test/integration/static_linked_scoped_pkg/BUILD.bazel deleted file mode 100644 index 8b34fd220..000000000 --- a/internal/linker/test/integration/static_linked_scoped_pkg/BUILD.bazel +++ /dev/null @@ -1,9 +0,0 @@ -load("//:index.bzl", "js_library") - -package(default_visibility = ["//internal/linker/test:__subpackages__"]) - -js_library( - name = "static_linked_scoped_pkg", - package_name = "@linker_scoped/static_linked", - srcs = ["index.js"], -) diff --git a/internal/linker/test/integration/static_linked_scoped_pkg/index.js b/internal/linker/test/integration/static_linked_scoped_pkg/index.js deleted file mode 100644 index 37a64cb21..000000000 --- a/internal/linker/test/integration/static_linked_scoped_pkg/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function addE(str) { - return `${str}_e`; -} - -exports.addE = addE; \ No newline at end of file diff --git a/internal/linker/test/integration/transitive_static_linked_pkg/BUILD.bazel b/internal/linker/test/integration/transitive_static_linked_pkg/BUILD.bazel deleted file mode 100644 index 28b85b1eb..000000000 --- a/internal/linker/test/integration/transitive_static_linked_pkg/BUILD.bazel +++ /dev/null @@ -1,9 +0,0 @@ -load("//:index.bzl", "js_library") - -package(default_visibility = ["//internal/linker/test:__subpackages__"]) - -js_library( - name = "transitive_static_linked_pkg", - package_name = "transitive_static_linked", - srcs = ["index.js"], -) diff --git a/internal/linker/test/integration/transitive_static_linked_pkg/index.js b/internal/linker/test/integration/transitive_static_linked_pkg/index.js deleted file mode 100644 index be2d999fe..000000000 --- a/internal/linker/test/integration/transitive_static_linked_pkg/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function addT(str) { - return `${str}_t`; -} - -exports.addT = addT; \ No newline at end of file diff --git a/internal/linker/test/issue_1813/BUILD.bazel b/internal/linker/test/issue_1813/BUILD.bazel deleted file mode 100644 index 445d82551..000000000 --- a/internal/linker/test/issue_1813/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("@npm//jest-cli:index.bzl", "jest_test") - -jest_test( - name = "test", - data = [ - "index.test.js", - "jest.config.js", - "@npm//jest", - "@npm//jest-websocket-mock", - "@npm//mock-socket", - ], - templated_args = [ - "--no-cache", - "--no-watchman", - "--ci", - # On Windows, spawing workers is broken - "--runInBand", - "--config", - "$$(rlocation $(rootpath jest.config.js))", - ], -) diff --git a/internal/linker/test/issue_1813/index.test.js b/internal/linker/test/issue_1813/index.test.js deleted file mode 100644 index 9fb11256f..000000000 --- a/internal/linker/test/issue_1813/index.test.js +++ /dev/null @@ -1,44 +0,0 @@ -// Loaded from common-js format -// node_modules/jest-websocket-mock/lib/jest-websocket-mock.cjs.js -const WS = require('jest-websocket-mock').default; -// Loaded from js format -// node_modules/mock-socket/dist/mock-socket.js" -const Server = require('mock-socket').Server; - -// Fails without node-patches symlink guards fix in -// https://github.com/bazelbuild/rules_nodejs/pull/1800 -// and node_modules execroot fix in -// https://github.com/bazelbuild/rules_nodejs/pull/1805: -// ``` -// ==================== Test output for //internal/linker/test/issue_1813:test: -// FAIL internal/linker/test/issue_1813/index.test.js -// ✕ jest-websocket-mock (12ms) -// -// ● jest-websocket-mock -// -// expect(received).toBeInstanceOf(expected) -// -// Expected constructor: Server -// Received constructor: Server -// -// 12 | test('jest-websocket-mock', () => { -// 13 | const ws = new WS('ws://localhost:1234'); -// > 14 | expect(ws.server).toBeInstanceOf(Server); -// | ^ -// 15 | }); -// 16 | -// -// at Object. (index.test.js:14:21) -// -// Test Suites: 1 failed, 1 total -// Tests: 1 failed, 1 total -// Snapshots: 0 total -// Time: 1.453s -// Ran all test suites. -// ================================================================================ -// ``` -// See https://github.com/bazelbuild/rules_nodejs/issues/1813 -test('jest-websocket-mock', () => { - const ws = new WS('ws://localhost:1234'); - expect(ws.server).toBeInstanceOf(Server); -}); diff --git a/internal/linker/test/issue_1813/jest.config.js b/internal/linker/test/issue_1813/jest.config.js deleted file mode 100644 index e0a0d1d3f..000000000 --- a/internal/linker/test/issue_1813/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - haste: { - enableSymlinks: true, - }, - testEnvironment: 'node', - testMatch: ['**/*.test.js'], -}; diff --git a/internal/linker/test/link_node_modules.spec.ts b/internal/linker/test/link_node_modules.spec.ts deleted file mode 100644 index b26b16b12..000000000 --- a/internal/linker/test/link_node_modules.spec.ts +++ /dev/null @@ -1,611 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -import {LinkerAliases, LinkerTreeElement} from '../link_node_modules'; - -// The linker is imported through it's checked-in file. We do this because the import to -// the runfile helpers from the linker file will always resolve to the checked-in file too. -const linker: typeof import('../link_node_modules') = require('../index.js'); -// As seen above, the linker file always loads the checked-in runfile helpers. We don't want -// to have a mix of checked-in files and sources, so we import runfile helpers from the -// checked-in file too, but use the types provided by the `@bazel/runfiles` package. -const {Runfiles}: typeof import('@bazel/runfiles') = require('../../runfiles/index.js'); - -const BIN_DIR = `bazel-out/my-platform-fastbuild/bin`; - -function mkdirp(p: string) { - if (!fs.existsSync(p)) { - mkdirp(path.dirname(p)); - fs.mkdirSync(p); - } -} - -// Mock out the creation of the modules manifest -function writeManifest(o: {}) { - fs.writeFileSync('manifest.json', JSON.stringify(o, null, 2), 'utf-8'); -} - -function writeRunfiles(manifest: string[]) { - fs.writeFileSync('runfiles.mf', manifest.join('\n'), 'utf-8'); -} - -describe('link_node_modules', () => { - let workspace: string; - let runfilesWorkspace: string; - - beforeEach(() => { - process.chdir(process.env['TEST_TMPDIR']!); - // Prevent test isolation failures: each spec gets its own workspace - workspace = `wksp_${Date.now()}`; - runfilesWorkspace = `${workspace}/${BIN_DIR}/runfiles/${workspace}`; - // Create our local workspace where the build is running - mkdirp(runfilesWorkspace); - }); - - function getWorkspaceModulePath(...parts: string[]): string { - return path.join(process.env['TEST_TMPDIR']!, workspace, 'node_modules', ...parts); - } - - function readWorkspaceNodeModules(...parts: string[]): string { - return fs.readFileSync(getWorkspaceModulePath(...parts), 'utf-8') - } - - function hasWorkspaceNodeModule(...parts: string[]): boolean { - return fs.existsSync(getWorkspaceModulePath(...parts)) - } - - describe('reduceModules', () => { - it('should support no links', () => { - expect(linker.reduceModules({})).toEqual([]); - }); - - it('should pull aligned child paths up', () => { - const IN: LinkerAliases = { - '@foo/a': `${BIN_DIR}/root/sub/a`, - '@foo/a/b': `${BIN_DIR}/root/sub/a/b`, - '@foo/a/1': `${BIN_DIR}/root/sub/a/1`, - '@foo/a/2': `${BIN_DIR}/root/sub/a/2`, - }; - const OUT: LinkerTreeElement[] = - [{name: '@foo', children: [{name: '@foo/a', link: `${BIN_DIR}/root/sub/a`}]}]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should pull deep aligned child paths up', () => { - const IN: LinkerAliases = { - '@foo/a': `${BIN_DIR}/root/sub/a`, - '@foo/a/b': `${BIN_DIR}/root/sub/a/b`, - '@foo/a/b/1': `${BIN_DIR}/root/sub/a/b/1`, - '@foo/a/b/2': `${BIN_DIR}/root/sub/a/b/2`, - }; - const OUT: LinkerTreeElement[] = - [{name: '@foo', children: [{name: '@foo/a', link: `${BIN_DIR}/root/sub/a`}]}]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should not change aligned paths with a misaligned parent', () => { - const IN: LinkerAliases = { - '@foo/a': `${BIN_DIR}/root/sub/a`, - '@foo/a/b': `${BIN_DIR}/root/sub/a/b`, - '@foo/a/b/1': `${BIN_DIR}/root/sub/other/a/b/1`, - '@foo/a/b/2': `${BIN_DIR}/root/sub/a/b/2`, - }; - const OUT: LinkerTreeElement[] = [{ - name: '@foo', - children: [{ - name: '@foo/a', - link: `${BIN_DIR}/root/sub/a`, - children: [{ - name: '@foo/a/b', - link: `${BIN_DIR}/root/sub/a/b`, - children: [ - {name: '@foo/a/b/1', link: `${BIN_DIR}/root/sub/other/a/b/1`}, - {name: '@foo/a/b/2', link: `${BIN_DIR}/root/sub/a/b/2`} - ] - }] - }] - }]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should not reduce parent/child when parent linking to different path', () => { - const IN: LinkerAliases = { - '@foo/a': `${BIN_DIR}/root/foo`, - '@foo/a/b': `${BIN_DIR}/root/sub/a/b`, - '@foo/a/b/1': `${BIN_DIR}/root/sub/a/b/1`, - '@foo/a/b/2': `${BIN_DIR}/root/sub/a/b/2`, - }; - const OUT: LinkerTreeElement[] = [{ - name: '@foo', - children: [{ - name: '@foo/a', - link: `${BIN_DIR}/root/foo`, - children: [{name: '@foo/a/b', link: `${BIN_DIR}/root/sub/a/b`}] - }] - }]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should reduce aligned parent+children aliases to single parent alias', () => { - const IN: LinkerAliases = { - '@foo/a': `${BIN_DIR}/root/sub/a`, - '@foo/a/1': `${BIN_DIR}/root/sub/a/1`, - '@foo/a/2': `${BIN_DIR}/root/sub/a/2`, - }; - const OUT: LinkerTreeElement[] = - [{name: '@foo', children: [{name: '@foo/a', link: `${BIN_DIR}/root/sub/a`}]}]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should reduce deeply-aligned siblings', () => { - const IN: LinkerAliases = { - '@foo/a': `${BIN_DIR}/root/sub/a`, - '@foo/a/b': `${BIN_DIR}/root/sub/a/b`, - '@foo/a/b/c': `${BIN_DIR}/root/sub/a/b/c`, - '@foo/a/b/c/d1': `${BIN_DIR}/root/sub/a/b/c/d1`, - '@foo/a/b/c/d2': `${BIN_DIR}/root/sub/a/b/c/d2`, - }; - const OUT: LinkerTreeElement[] = - [{name: '@foo', children: [{name: '@foo/a', link: `${BIN_DIR}/root/sub/a`}]}]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should not reduce parent/child with different ancestor link paths', () => { - const IN: LinkerAliases = { - 'b/b': 'external/other_wksp/path/to/lib_bb', - 'b': 'external/other_wksp/path/to/lib_b', - }; - const OUT: LinkerTreeElement[] = [{ - name: 'b', - link: 'external/other_wksp/path/to/lib_b', - children: [{ - name: 'b/b', - link: 'external/other_wksp/path/to/lib_bb', - }], - }]; - - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should not reduce aligned paths when link has extra dir', () => { - const IN: LinkerAliases = { - '@foo/lib': `${BIN_DIR}/path/to/lib`, - '@foo/lib/a': `${BIN_DIR}/path/to/lib/noseeme/a`, - '@foo/lib/b': `${BIN_DIR}/path/to/lib/noseeme/b`, - '@foo/lib/c': `${BIN_DIR}/path/to/lib/noseeme/c`, - }; - const OUT: LinkerTreeElement[] = [{ - name: '@foo', - children: [{ - name: '@foo/lib', - link: `${BIN_DIR}/path/to/lib`, - children: [ - {name: '@foo/lib/a', link: `${BIN_DIR}/path/to/lib/noseeme/a`}, - {name: '@foo/lib/b', link: `${BIN_DIR}/path/to/lib/noseeme/b`}, - {name: '@foo/lib/c', link: `${BIN_DIR}/path/to/lib/noseeme/c`} - ] - }] - }]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should not reduce sibling mappings with inconsistent paths', () => { - const IN: LinkerAliases = { - '@foo/lib': `${BIN_DIR}/path/to/lib`, - '@foo/lib/a': `${BIN_DIR}/path/to/lib/x`, - '@foo/lib/b': `${BIN_DIR}/path/to/lib/b`, - }; - const OUT: LinkerTreeElement[] = [{ - name: '@foo', - children: [{ - name: '@foo/lib', - link: `${BIN_DIR}/path/to/lib`, - children: [ - {name: '@foo/lib/a', link: `${BIN_DIR}/path/to/lib/x`}, - {name: '@foo/lib/b', link: `${BIN_DIR}/path/to/lib/b`}, - ] - }] - }]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should not reduce sibling mappings with inconsistent path parents', () => { - const IN: LinkerAliases = { - '@foo/lib': `${BIN_DIR}/path/to/lib`, - '@foo/lib/a': `${BIN_DIR}/path/to/lib/x/a`, - '@foo/lib/b': `${BIN_DIR}/path/to/lib/b`, - }; - const OUT: LinkerTreeElement[] = [{ - name: '@foo', - children: [{ - name: '@foo/lib', - link: `${BIN_DIR}/path/to/lib`, - children: [ - {name: '@foo/lib/a', link: `${BIN_DIR}/path/to/lib/x/a`}, - {name: '@foo/lib/b', link: `${BIN_DIR}/path/to/lib/b`} - ] - }] - }]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should not reduce to parent mapping with inconcsisten parent link path', () => { - const IN: LinkerAliases = { - '@foo/lib': `${BIN_DIR}/path/to/other/lib`, - '@foo/lib/a': `${BIN_DIR}/path/to/lib/a`, - '@foo/lib/b': `${BIN_DIR}/path/to/lib/b`, - }; - const OUT: LinkerTreeElement[] = [{ - name: '@foo', - children: [{ - name: '@foo/lib', - link: `${BIN_DIR}/path/to/other/lib`, - children: [ - { - name: '@foo/lib/a', - link: `${BIN_DIR}/path/to/lib/a`, - }, - { - name: '@foo/lib/b', - link: `${BIN_DIR}/path/to/lib/b`, - } - ] - }] - }]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - - it('should reduce complicated example', () => { - const IN: LinkerAliases = { - 'a': `path/to/lib_a`, - '@foo/c/c/c/c': `${BIN_DIR}/path/to/foo_cccc`, - 'b/b': 'external/other_wksp/path/to/lib_bb', - 'b': 'external/other_wksp/path/to/lib_b', - '@foo/c': `${BIN_DIR}/path/to/foo_c`, - '@foo/c/c': `${BIN_DIR}/path/to/foo_cc`, - '@foo/d/bar/fum/far': `${BIN_DIR}/path/to/foo_d/bar/fum/far`, - '@foo/d/bar': `${BIN_DIR}/path/to/foo_d/bar`, - // don't include `@foo/d` as the linker should derive that symlink - // from the lowest common denominator of the module name & module path - }; - const OUT: LinkerTreeElement[] = [ - { - name: '@foo', - children: [ - { - name: '@foo/c', - link: `${BIN_DIR}/path/to/foo_c`, - children: [{ - name: '@foo/c/c', - link: `${BIN_DIR}/path/to/foo_cc`, - children: [{ - name: '@foo/c/c/c', - children: [{ - name: '@foo/c/c/c/c', - link: `${BIN_DIR}/path/to/foo_cccc`, - }] - }] - }] - }, - { - name: '@foo/d', - children: [{ - name: '@foo/d/bar', - link: `${BIN_DIR}/path/to/foo_d/bar`, - children: [{ - name: '@foo/d/bar/fum', - children: [{ - name: '@foo/d/bar/fum/far', - link: `${BIN_DIR}/path/to/foo_d/bar/fum/far`, - }] - }] - }] - } - ] - }, - {name: 'a', link: 'path/to/lib_a'}, { - name: 'b', - link: 'external/other_wksp/path/to/lib_b', - children: [{name: 'b/b', link: 'external/other_wksp/path/to/lib_bb'}] - } - ]; - expect(linker.reduceModules(IN)).toEqual(OUT); - }); - }); - - it('should report when modules manifest absent', async () => { - try { - await (linker as any).main(); - } catch (expected) { - expect(expected.message).toContain('requires one argument'); - } - try { - await (linker as any).main([]); - } catch (expected) { - expect(expected.message).toContain('requires one argument'); - } - try { - await (linker as any).main(['bad_path']); - } catch (expected) { - expect(expected.message).toContain('ENOENT'); - } - }); - - it('should handle first-party packages in workspace', async () => { - // Set the cwd() like Bazel would in the execroot - process.chdir(workspace); - - // Create a package in the user workspace - mkdirp('path/to/lib_a'); - fs.writeFileSync('path/to/lib_a/index.js', '/*a*/exports = {}', 'utf-8'); - - // Create a nested package in a different workspace - mkdirp('external/other_wksp/path/to/lib_b'); - fs.writeFileSync('external/other_wksp/path/to/lib_b/index.js', '/*b*/exports = {}', 'utf-8'); - mkdirp('external/other_wksp/path/to/lib_bb'); - fs.writeFileSync('external/other_wksp/path/to/lib_bb/index.js', '/*b/b*/exports = {}', 'utf-8'); - - // Create a nested package in bazel-bin where module names don't match directory structure - mkdirp(`${BIN_DIR}/path/to/foo_c`); - fs.writeFileSync(`${BIN_DIR}/path/to/foo_c/index.js`, '/*@foo/c*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/foo_cc`); - fs.writeFileSync(`${BIN_DIR}/path/to/foo_cc/index.js`, '/*@foo/c/c*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/foo_cccc`); - fs.writeFileSync( - `${BIN_DIR}/path/to/foo_cccc/index.js`, '/*@foo/c/c/c/c*/exports = {}', 'utf-8'); - - // Create a nested package in bazel-bin where module names match directory structure - mkdirp(`${BIN_DIR}/path/to/foo_d`); - fs.writeFileSync(`${BIN_DIR}/path/to/foo_d/index.js`, '/*@foo/d*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/foo_d/bar`); - fs.writeFileSync( - `${BIN_DIR}/path/to/foo_d/bar/index.js`, '/*@foo/d/bar*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/foo_d/bar/fum/far`); - fs.writeFileSync( - `${BIN_DIR}/path/to/foo_d/bar/fum/far/index.js`, '/*@foo/d/bar/fum/far*/exports = {}', - 'utf-8'); - - writeManifest({ - bin: BIN_DIR, - // intentionally out of order so that linker has to sort - // and create nested modules in the correct order - module_sets: { - '': { - 'a': `path/to/lib_a`, - '@foo/c/c/c/c': `${BIN_DIR}/path/to/foo_cccc`, - 'b/b': 'external/other_wksp/path/to/lib_bb', - 'b': 'external/other_wksp/path/to/lib_b', - '@foo/c': `${BIN_DIR}/path/to/foo_c`, - '@foo/c/c': `${BIN_DIR}/path/to/foo_cc`, - '@foo/d/bar/fum/far': `${BIN_DIR}/path/to/foo_d/bar/fum/far`, - '@foo/d/bar': `${BIN_DIR}/path/to/foo_d/bar`, - '@foo/d': `${BIN_DIR}/path/to/foo_d`, - }, - }, - workspace: workspace, - roots: {}, - }); - - // TODO(alexeagle): test should control the environment, not just pass through - await linker.main(['manifest.json'], new Runfiles(process.env)); - - // The linker expects to run as its own process, so it changes the wd - process.chdir(path.join()); - expect(readWorkspaceNodeModules('a', 'index.js')).toEqual('/*a*/exports = {}'); - expect(readWorkspaceNodeModules('b', 'index.js')).toEqual('/*b*/exports = {}'); - expect(readWorkspaceNodeModules('b', 'b', 'index.js')).toEqual('/*b/b*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'c', 'index.js')).toEqual('/*@foo/c*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'c', 'c', 'index.js')) - .toEqual('/*@foo/c/c*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'c', 'c', 'c', 'c', 'index.js')) - .toEqual('/*@foo/c/c/c/c*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'd', 'index.js')).toEqual('/*@foo/d*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'd', 'bar', 'index.js')) - .toEqual('/*@foo/d/bar*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'd', 'bar', 'fum', 'far', 'index.js')) - .toEqual('/*@foo/d/bar/fum/far*/exports = {}'); - }); - - it('should create link at higher level if directory already exists from past run', async () => { - const workspacePath = path.join(process.env['TEST_TMPDIR']!, workspace); - // Set the cwd() like Bazel would in the execroot - process.chdir(workspacePath); - - // Create a package in the user workspace - mkdirp(`${BIN_DIR}/src/cdk/bidi`); - mkdirp(`${BIN_DIR}/src/other-core-folder`); - fs.writeFileSync(`${BIN_DIR}/src/cdk/index.js`, '/*cdk/index*/exports = {}', 'utf-8'); - fs.writeFileSync(`${BIN_DIR}/src/cdk/bidi/index.js`, '/*cdk/bidi/index*/exports = {}', 'utf-8'); - fs.writeFileSync(`${BIN_DIR}/src/other-core-folder/index.js`, `/*other-core*/`, 'utf-8'); - - writeRunfiles([]); - writeManifest({ - bin: BIN_DIR, - workspace: workspace, - module_sets: { - '': { - '@angular/cdk/bidi': `${BIN_DIR}/src/cdk/bidi`, - '@angular/cdk/core': `${BIN_DIR}/src/other-core-folder`, - }, - }, - roots: {} - }); - - await linker.main(['manifest.json'], new Runfiles({ - // This test assumes an environment where runfiles are not symlinked. Hence - // we pass a runfile manifest file. - 'RUNFILES_MANIFEST_FILE': 'runfiles.mf', - })); - - expect(readWorkspaceNodeModules('@angular', 'cdk', 'bidi', 'index.js')) - .toEqual('/*cdk/bidi/index*/exports = {}'); - expect(readWorkspaceNodeModules('@angular', 'cdk', 'core', 'index.js')) - .toEqual('/*other-core*/'); - expect(hasWorkspaceNodeModule('@angular', 'cdk', 'index.js')).toBe(false); - - // Set the cwd() like Bazel would in the execroot - process.chdir(workspacePath); - - writeManifest({ - bin: BIN_DIR, - workspace: workspace, - module_sets: { - '': { - '@angular/cdk': `${BIN_DIR}/src/cdk`, - '@angular/cdk/bidi': `${BIN_DIR}/src/cdk/bidi`, - '@angular/cdk/core': `${BIN_DIR}/src/other-core-folder`, - }, - }, - roots: {} - }); - - // In the second run, we added a mapping for `@angular/cdk`. This means - // that the linker would need to clean up the previous `cdk/bidi` link - // in order to be able to create a link for `@angular/cdk`. - await linker.main(['manifest.json'], new Runfiles({ - // This test assumes an environment where runfiles are not symlinked. Hence - // we pass a runfile manifest file. - 'RUNFILES_MANIFEST_FILE': 'runfiles.mf', - })); - - expect(readWorkspaceNodeModules('@angular', 'cdk', 'bidi', 'index.js')) - .toEqual('/*cdk/bidi/index*/exports = {}'); - expect(readWorkspaceNodeModules('@angular', 'cdk', 'core', 'index.js')) - .toEqual('/*other-core*/'); - expect(readWorkspaceNodeModules('@angular', 'cdk', 'index.js')) - .toEqual('/*cdk/index*/exports = {}'); - }); - - it('should handle first-party packages with single parent link', async () => { - // Set the cwd() like Bazel would in the execroot - process.chdir(workspace); - - // Create sub-packages to a lib in the user workspace - mkdirp(`${BIN_DIR}/path/to/lib/a`); - fs.writeFileSync(`${BIN_DIR}/path/to/lib/a/index.js`, '/*a*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/lib/b`); - fs.writeFileSync(`${BIN_DIR}/path/to/lib/b/index.js`, '/*b*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/lib/c`); - fs.writeFileSync(`${BIN_DIR}/path/to/lib/c/index.js`, '/*c*/exports = {}', 'utf-8'); - - writeManifest({ - bin: BIN_DIR, - module_sets: { - '': { - '@foo/lib/a': `${BIN_DIR}/path/to/lib/a`, - '@foo/lib/b': `${BIN_DIR}/path/to/lib/b`, - '@foo/lib/c': `${BIN_DIR}/path/to/lib/c`, - }, - }, - workspace: workspace, - roots: {}, - }); - - // TODO(alexeagle): test should control the environment, not just pass through - await linker.main(['manifest.json'], new Runfiles(process.env)); - - // The linker expects to run as its own process, so it changes the wd - process.chdir(path.join()); - expect(readWorkspaceNodeModules('@foo', 'lib', 'a', 'index.js')).toEqual('/*a*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'lib', 'b', 'index.js')).toEqual('/*b*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'lib', 'c', 'index.js')).toEqual('/*c*/exports = {}'); - }); - - it('should handle first-party packages with sibling links in shared parent', async () => { - // Set the cwd() like Bazel would in the execroot - process.chdir(workspace); - - // Create sub-packages to a lib in the user workspace - mkdirp(`${BIN_DIR}/path/to/lib/x`); - fs.writeFileSync(`${BIN_DIR}/path/to/lib/x/index.js`, '/*a*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/lib/b`); - fs.writeFileSync(`${BIN_DIR}/path/to/lib/b/index.js`, '/*b*/exports = {}', 'utf-8'); - mkdirp('path/to/lib/c'); - fs.writeFileSync('path/to/lib/c/index.js', '/*c*/exports = {}', 'utf-8'); - - writeManifest({ - bin: BIN_DIR, - module_sets: { - '': { - '@foo/lib/a': `${BIN_DIR}/path/to/lib/x`, - '@foo/lib/b': `${BIN_DIR}/path/to/lib/b`, - '@foo/lib/c': `path/to/lib/c`, - }, - }, - workspace: workspace, - roots: {}, - }); - - // TODO(alexeagle): test should control the environment, not just pass through - await linker.main(['manifest.json'], new Runfiles(process.env)); - - // The linker expects to run as its own process, so it changes the wd - process.chdir(path.join()); - expect(readWorkspaceNodeModules('@foo', 'lib', 'a', 'index.js')).toEqual('/*a*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'lib', 'b', 'index.js')).toEqual('/*b*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'lib', 'c', 'index.js')).toEqual('/*c*/exports = {}'); - }); - - it('should handle first-party packages with miss-aligned nested links', async () => { - // Set the cwd() like Bazel would in the execroot - process.chdir(workspace); - - // Create sub-packages to a lib in the user workspace - mkdirp(`${BIN_DIR}/path/to/other/lib`); - fs.writeFileSync(`${BIN_DIR}/path/to/other/lib/index.js`, '/*root*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/foo/a`); - fs.writeFileSync(`${BIN_DIR}/path/to/foo/a/index.js`, '/*a*/exports = {}', 'utf-8'); - mkdirp(`${BIN_DIR}/path/to/bar/b`); - fs.writeFileSync(`${BIN_DIR}/path/to/bar/b/index.js`, '/*ab*/exports = {}', 'utf-8'); - - writeManifest({ - bin: BIN_DIR, - module_sets: { - '': { - '@foo/lib': `${BIN_DIR}/path/to/other/lib`, - '@foo/lib/a': `${BIN_DIR}/path/to/foo/a`, - '@foo/lib/a/b': `${BIN_DIR}/path/to/bar/b`, - }, - }, - workspace: workspace, - roots: {}, - }); - - // TODO(alexeagle): test should control the environment, not just pass through - await linker.main(['manifest.json'], new Runfiles(process.env)); - - // The linker expects to run as its own process, so it changes the wd - process.chdir(path.join()); - expect(readWorkspaceNodeModules('@foo', 'lib', 'index.js')).toEqual('/*root*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'lib', 'a', 'index.js')).toEqual('/*a*/exports = {}'); - expect(readWorkspaceNodeModules('@foo', 'lib', 'a', 'b', 'index.js')) - .toEqual('/*ab*/exports = {}'); - }); - - it('should handle third-party packages in runfiles', async () => { - mkdirp('npm/node_modules/some-package'); - const idx = 'npm/node_modules/some-package/index.js'; - fs.writeFileSync(idx, 'exports = {}', 'utf-8'); - const runfilesManifest = [`${idx} ${path.resolve(idx)}`]; - - // Set the cwd() like Bazel would in the runfiles - process.chdir(runfilesWorkspace); - - // No first-party packages - writeManifest({ - bin: BIN_DIR, - roots: {'': 'npm'}, - }); - writeRunfiles(runfilesManifest); - - await linker.main(['manifest.json'], new Runfiles({ - 'RUNFILES_MANIFEST_FILE': 'runfiles.mf', - })); - - // We expect the linker to symlink node_modules to `execroot/my_wksp/node_modules` when - // under runfiles - expect(fs.readdirSync( - path.join(process.env['TEST_TMPDIR']!, workspace, 'node_modules', 'some-package'))) - .toContain('index.js'); - }); -}); diff --git a/internal/linker/test/link_node_modules.test.ts b/internal/linker/test/link_node_modules.test.ts new file mode 100644 index 000000000..4ffd9358c --- /dev/null +++ b/internal/linker/test/link_node_modules.test.ts @@ -0,0 +1,63 @@ +import type { RelativeMountPath, RunfilesDirPath, WorkspaceName } from '../link_node_modules'; +import { getProposedSymlinks, inferRunfilesDirFromPath } from '../link_node_modules'; + +describe(inferRunfilesDirFromPath.name, () => { + it('infer from linker script referenced via runfiles directory', () => { + const runfilesDir = inferRunfilesDirFromPath( + '/__output_base__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/build_bazel_rules_nodejs/internal/linker/index.js', + ); + expect(runfilesDir).toBe( + '/__output_base__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/', + ); + }); + it('infer from RUNFILES environment variable', () => { + const runfilesDir = inferRunfilesDirFromPath( + '/__output_base__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles', + ); + expect(runfilesDir).toBe( + '/__output_base__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/', + ); + }); + it('infer from cwd', () => { + const runfilesDir = inferRunfilesDirFromPath( + '/__output_base__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fig/fib.sh.runfiles/com_canva_canva', + ); + expect(runfilesDir).toBe( + '/__output_base__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fig/fib.sh.runfiles/', + ); + }); + it('no runfiles directory present', () => { + expect(() => inferRunfilesDirFromPath('/')).toThrow(); + }); +}); + +describe(getProposedSymlinks.name, () => { + it('produces expected results', () => { + const symlinks = getProposedSymlinks( + { + roots: { + ['foo' as RelativeMountPath]: 'foo_node_modules' as WorkspaceName, + ['foo/bar' as RelativeMountPath]: 'foobar_node_modules' as WorkspaceName, + ['baz' as RelativeMountPath]: 'baz_node_modules' as WorkspaceName, + }, + workspace: 'com_canva_canva' as WorkspaceName, + }, + '/__bazel__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/' as RunfilesDirPath, + ); + + expect(symlinks).toEqual([ + [ + '/__bazel__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/com_canva_canva/foo/node_modules', + '/__bazel__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/foo_node_modules/node_modules', + ], + [ + '/__bazel__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/com_canva_canva/foo/bar/node_modules', + '/__bazel__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/foobar_node_modules/node_modules', + ], + [ + '/__bazel__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/com_canva_canva/baz/node_modules', + '/__bazel__/execroot/com_canva_canva/bazel-out/darwin-dbg/bin/fib/fib.sh.runfiles/baz_node_modules/node_modules', + ], + ]); + }); +}); diff --git a/internal/linker/test/local/BUILD.bazel b/internal/linker/test/local/BUILD.bazel deleted file mode 100644 index 041d7b419..000000000 --- a/internal/linker/test/local/BUILD.bazel +++ /dev/null @@ -1,8 +0,0 @@ -load("//packages/jasmine:index.bzl", "jasmine_node_test") - -jasmine_node_test( - name = "test", - srcs = ["test.js"], - tags = ["local"], - deps = ["//internal/linker/test/local/fit"], -) diff --git a/internal/linker/test/local/fit/BUILD.bazel b/internal/linker/test/local/fit/BUILD.bazel deleted file mode 100644 index a7a7ee799..000000000 --- a/internal/linker/test/local/fit/BUILD.bazel +++ /dev/null @@ -1,29 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -load("@npm//typescript:index.bzl", "tsc") - -tsc( - name = "fit_lib", - outs = [ - "main.d.ts", - "main.js", - ], - args = [ - "-p", - "$(execpath tsconfig.json)", - "--outDir", - # $(RULEDIR) is a shorthand for the dist/bin directory where Bazel requires we write outputs - "$(RULEDIR)", - ], - data = [ - "main.ts", - "tsconfig.json", - ], -) - -pkg_npm( - name = "fit", - package_name = "fit", - srcs = ["package.json"], - visibility = ["//internal/linker/test/local:__pkg__"], - deps = [":fit_lib"], -) diff --git a/internal/linker/test/local/fit/main.ts b/internal/linker/test/local/fit/main.ts deleted file mode 100644 index e7dc44f51..000000000 --- a/internal/linker/test/local/fit/main.ts +++ /dev/null @@ -1 +0,0 @@ -export const fit: string = 'fit'; diff --git a/internal/linker/test/local/fit/package.json b/internal/linker/test/local/fit/package.json deleted file mode 100644 index 1cc0e5c17..000000000 --- a/internal/linker/test/local/fit/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "fit", - "main": "main.js", - "typings": "main.d.ts" -} diff --git a/internal/linker/test/local/fit/tsconfig.json b/internal/linker/test/local/fit/tsconfig.json deleted file mode 100644 index 8f1cc9cec..000000000 --- a/internal/linker/test/local/fit/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "compilerOptions": { - "declaration": true - } -} \ No newline at end of file diff --git a/internal/linker/test/local/test.js b/internal/linker/test/local/test.js deleted file mode 100644 index 481989f02..000000000 --- a/internal/linker/test/local/test.js +++ /dev/null @@ -1,6 +0,0 @@ -describe('linker', () => { - it('should work when job run with "local" tag', () => { - const fit = require('fit'); - expect(fit.fit).toBe('fit'); - }); -}); diff --git a/internal/linker/test/multi_linker/BUILD.bazel b/internal/linker/test/multi_linker/BUILD.bazel deleted file mode 100644 index 295e8e63a..000000000 --- a/internal/linker/test/multi_linker/BUILD.bazel +++ /dev/null @@ -1,38 +0,0 @@ -load("//packages/jasmine:index.bzl", "jasmine_node_test") -load("//packages/typescript:checked_in_ts_project.bzl", "checked_in_ts_project") - -checked_in_ts_project( - name = "checked_in_test", - src = "test.ts", - checked_in_js = "checked_in_test.js", - deps = [ - "@npm//@types/jasmine", - "@npm//@types/node", - ], -) - -# Test with a .js file in the output-tree to ensure that we can -# resolve multi-linked node_modules from that context -jasmine_node_test( - name = "test", - srcs = ["test.js"], - deps = [ - "//internal/linker/test/multi_linker/onep_a", - "//internal/linker/test/multi_linker/onep_b", - "@internal_test_multi_linker_deps//semver", - "@npm//semver", - ], -) - -# Test with a .js file from the source tree to ensure that we can -# resolve multi-linked node_modules from that context -jasmine_node_test( - name = "from_sources_test", - srcs = ["checked_in_test.js"], - deps = [ - "//internal/linker/test/multi_linker/onep_a", - "//internal/linker/test/multi_linker/onep_b", - "@internal_test_multi_linker_deps//semver", - "@npm//semver", - ], -) diff --git a/internal/linker/test/multi_linker/checked_in_test.js b/internal/linker/test/multi_linker/checked_in_test.js deleted file mode 100644 index 079522c86..000000000 --- a/internal/linker/test/multi_linker/checked_in_test.js +++ /dev/null @@ -1,16 +0,0 @@ -/* THIS FILE GENERATED FROM .ts; see BUILD.bazel */ /* clang-format off */"use strict"; -describe('linker', () => { - it('should link nested node modules', () => { - const semverVersion = require(require.resolve('semver/package.json')).version; - expect(semverVersion).toBe('1.0.0'); - }); - it('should get transitive nested node_modules from 1p dep', () => { - const onepa = require('@test_multi_linker/onep-a'); - expect(onepa.semverVersion()).toBe('1.0.1'); - }); - it('should get semver from root pacakge.json (which is currently 5.6.0) if the is no transitive in 1p dep', () => { - const onepb = require('@test_multi_linker/onep-b'); - const major = onepb.semver().major(onepb.semverVersion()); - expect(major).toBeGreaterThanOrEqual(5); - }); -}); diff --git a/internal/linker/test/multi_linker/lib_a/BUILD.bazel b/internal/linker/test/multi_linker/lib_a/BUILD.bazel deleted file mode 100644 index 973c12495..000000000 --- a/internal/linker/test/multi_linker/lib_a/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("//:index.bzl", "js_library") - -js_library( - name = "lib_a", - package_name = "@test_multi_linker/lib-a", - srcs = [ - "index.js", - "package.json", - ], - visibility = [ - "//internal/linker/test/multi_linker:__subpackages__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-a:__pkg__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-a2:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-a:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-a2:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-a:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-a2:__pkg__", - "@npm//@test_multi_linker/lib-a:__pkg__", - "@npm//@test_multi_linker/lib-a2:__pkg__", - ], -) diff --git a/internal/linker/test/multi_linker/lib_a/index.js b/internal/linker/test/multi_linker/lib_a/index.js deleted file mode 100644 index 842e5c1a2..000000000 --- a/internal/linker/test/multi_linker/lib_a/index.js +++ /dev/null @@ -1,6 +0,0 @@ - -module.exports = { - whichSemver: function() { - return require(require.resolve('semver/package.json')).version; - } -} diff --git a/internal/linker/test/multi_linker/lib_a/package.json b/internal/linker/test/multi_linker/lib_a/package.json deleted file mode 100644 index 8b8b8571d..000000000 --- a/internal/linker/test/multi_linker/lib_a/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "//": "no direct dep on semver to test which peer version is resolved", - "peerDependencies": { - "semver": ">=1.0.0" - }, - "devDependencies": { - "smallest": "1.0.1" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/lib_a/yarn.lock b/internal/linker/test/multi_linker/lib_a/yarn.lock deleted file mode 100644 index c95aacae9..000000000 --- a/internal/linker/test/multi_linker/lib_a/yarn.lock +++ /dev/null @@ -1,15 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -smallest@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/smallest/-/smallest-1.0.1.tgz#59ddee13546911b2177f36af61a866bf3b3bfb81" - integrity sha1-Wd3uE1RpEbIXfzavYahmvzs7+4E= - dependencies: - to-array "~0.1.4" - -to-array@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= diff --git a/internal/linker/test/multi_linker/lib_b/BUILD.bazel b/internal/linker/test/multi_linker/lib_b/BUILD.bazel deleted file mode 100644 index 0b6d2c60e..000000000 --- a/internal/linker/test/multi_linker/lib_b/BUILD.bazel +++ /dev/null @@ -1,25 +0,0 @@ -load("//:index.bzl", "js_library") - -js_library( - name = "lib_b", - package_name = "@test_multi_linker/lib-b", - srcs = [ - "index.js", - "package.json", - ], - visibility = [ - "//internal/linker/test/multi_linker:__subpackages__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-b:__pkg__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-b2:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-b:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-b2:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-b:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-b2:__pkg__", - "@npm//@test_multi_linker/lib-b:__pkg__", - "@npm//@test_multi_linker/lib-b2:__pkg__", - ], - deps = [ - # transitive dep on semver - "@internal_test_multi_linker_lib_b_deps//semver", - ], -) diff --git a/internal/linker/test/multi_linker/lib_b/index.js b/internal/linker/test/multi_linker/lib_b/index.js deleted file mode 100644 index 842e5c1a2..000000000 --- a/internal/linker/test/multi_linker/lib_b/index.js +++ /dev/null @@ -1,6 +0,0 @@ - -module.exports = { - whichSemver: function() { - return require(require.resolve('semver/package.json')).version; - } -} diff --git a/internal/linker/test/multi_linker/lib_b/package.json b/internal/linker/test/multi_linker/lib_b/package.json deleted file mode 100644 index 4fece2080..000000000 --- a/internal/linker/test/multi_linker/lib_b/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.2" - }, - "devDependencies": { - "smallest": "1.0.1" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/lib_b/yarn.lock b/internal/linker/test/multi_linker/lib_b/yarn.lock deleted file mode 100644 index 58a9f6fa6..000000000 --- a/internal/linker/test/multi_linker/lib_b/yarn.lock +++ /dev/null @@ -1,20 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.2.tgz#57e4e1460c0f1abc2c2c6273457abc04e309706c" - integrity sha1-V+ThRgwPGrwsLGJzRXq8BOMJcGw= - -smallest@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/smallest/-/smallest-1.0.1.tgz#59ddee13546911b2177f36af61a866bf3b3bfb81" - integrity sha1-Wd3uE1RpEbIXfzavYahmvzs7+4E= - dependencies: - to-array "~0.1.4" - -to-array@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= diff --git a/internal/linker/test/multi_linker/lib_c/BUILD.bazel b/internal/linker/test/multi_linker/lib_c/BUILD.bazel deleted file mode 100644 index 882d1b955..000000000 --- a/internal/linker/test/multi_linker/lib_c/BUILD.bazel +++ /dev/null @@ -1,26 +0,0 @@ -load("//:index.bzl", "js_library") - -js_library( - name = "lib_c", - # No package_name specified to test this case - srcs = [ - "lib/index.js", - "lib/package.json", - ], - strip_prefix = "lib", - visibility = [ - "//internal/linker/test/multi_linker:__subpackages__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-c:__pkg__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-c2:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-c:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-c2:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-c:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-c2:__pkg__", - "@npm//@test_multi_linker/lib-c:__pkg__", - "@npm//@test_multi_linker/lib-c2:__pkg__", - ], - deps = [ - # transitive dep on semver - "@internal_test_multi_linker_lib_c_deps//semver", - ], -) diff --git a/internal/linker/test/multi_linker/lib_c/lib/index.js b/internal/linker/test/multi_linker/lib_c/lib/index.js deleted file mode 100644 index 842e5c1a2..000000000 --- a/internal/linker/test/multi_linker/lib_c/lib/index.js +++ /dev/null @@ -1,6 +0,0 @@ - -module.exports = { - whichSemver: function() { - return require(require.resolve('semver/package.json')).version; - } -} diff --git a/internal/linker/test/multi_linker/lib_c/lib/package.json b/internal/linker/test/multi_linker/lib_c/lib/package.json deleted file mode 100644 index c32a595b4..000000000 --- a/internal/linker/test/multi_linker/lib_c/lib/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.8" - }, - "devDependencies": { - "smallest": "1.0.1" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/lib_c/lib/yarn.lock b/internal/linker/test/multi_linker/lib_c/lib/yarn.lock deleted file mode 100644 index 76aa0da84..000000000 --- a/internal/linker/test/multi_linker/lib_c/lib/yarn.lock +++ /dev/null @@ -1,20 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.8.tgz#8a78a9bfad863a0660683c33c91f08b6cd2cfa98" - integrity sha1-inipv62GOgZgaDwzyR8Its0s+pg= - -smallest@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/smallest/-/smallest-1.0.1.tgz#59ddee13546911b2177f36af61a866bf3b3bfb81" - integrity sha1-Wd3uE1RpEbIXfzavYahmvzs7+4E= - dependencies: - to-array "~0.1.4" - -to-array@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= diff --git a/internal/linker/test/multi_linker/lib_d/BUILD.bazel b/internal/linker/test/multi_linker/lib_d/BUILD.bazel deleted file mode 100644 index 9c7911296..000000000 --- a/internal/linker/test/multi_linker/lib_d/BUILD.bazel +++ /dev/null @@ -1,27 +0,0 @@ -load("//:index.bzl", "copy_to_bin") - -# Verify that npm_link handles a filegroup with mixed source files & generated files -copy_to_bin( - name = "index", - srcs = ["index.js"], -) - -filegroup( - name = "lib_d", - srcs = [ - "other.js", - "package.json", - ":index", - ], - visibility = [ - "//internal/linker/test/multi_linker:__subpackages__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-d:__pkg__", - "@internal_test_multi_linker_deps//@test_multi_linker/lib-d2:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-d:__pkg__", - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-d2:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-d:__pkg__", - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-d2:__pkg__", - "@npm//@test_multi_linker/lib-d:__pkg__", - "@npm//@test_multi_linker/lib-d2:__pkg__", - ], -) diff --git a/internal/linker/test/multi_linker/lib_d/index.js b/internal/linker/test/multi_linker/lib_d/index.js deleted file mode 100644 index f4b901d87..000000000 --- a/internal/linker/test/multi_linker/lib_d/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const other = require('./other') - -module.exports = other; diff --git a/internal/linker/test/multi_linker/lib_d/other.js b/internal/linker/test/multi_linker/lib_d/other.js deleted file mode 100644 index b374b9864..000000000 --- a/internal/linker/test/multi_linker/lib_d/other.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - whichSemver: function() { - return require(require.resolve('semver/package.json')).version; - } -} diff --git a/internal/linker/test/multi_linker/lib_d/package.json b/internal/linker/test/multi_linker/lib_d/package.json deleted file mode 100644 index 8b8b8571d..000000000 --- a/internal/linker/test/multi_linker/lib_d/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "//": "no direct dep on semver to test which peer version is resolved", - "peerDependencies": { - "semver": ">=1.0.0" - }, - "devDependencies": { - "smallest": "1.0.1" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/lib_d/yarn.lock b/internal/linker/test/multi_linker/lib_d/yarn.lock deleted file mode 100644 index c95aacae9..000000000 --- a/internal/linker/test/multi_linker/lib_d/yarn.lock +++ /dev/null @@ -1,15 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -smallest@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/smallest/-/smallest-1.0.1.tgz#59ddee13546911b2177f36af61a866bf3b3bfb81" - integrity sha1-Wd3uE1RpEbIXfzavYahmvzs7+4E= - dependencies: - to-array "~0.1.4" - -to-array@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= diff --git a/internal/linker/test/multi_linker/onep_a/BUILD.bazel b/internal/linker/test/multi_linker/onep_a/BUILD.bazel deleted file mode 100644 index 9653feea6..000000000 --- a/internal/linker/test/multi_linker/onep_a/BUILD.bazel +++ /dev/null @@ -1,14 +0,0 @@ -load("//:index.bzl", "js_library") - -js_library( - name = "onep_a", - package_name = "@test_multi_linker/onep-a", - srcs = [ - "index.js", - "package.json", - ], - visibility = ["//internal/linker/test/multi_linker:__subpackages__"], - deps = [ - "@internal_test_multi_linker_onep_a_deps//:node_modules", - ], -) diff --git a/internal/linker/test/multi_linker/onep_a/index.js b/internal/linker/test/multi_linker/onep_a/index.js deleted file mode 100644 index b8d91601f..000000000 --- a/internal/linker/test/multi_linker/onep_a/index.js +++ /dev/null @@ -1,6 +0,0 @@ -function semverVersion() { - const semverVersion = require(require.resolve('semver/package.json')).version; - return semverVersion; -} - -module.exports = {semverVersion} diff --git a/internal/linker/test/multi_linker/onep_a/package.json b/internal/linker/test/multi_linker/onep_a/package.json deleted file mode 100644 index 54ccbc9e6..000000000 --- a/internal/linker/test/multi_linker/onep_a/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.1" - }, - "devDependencies": { - "smallest": "1.0.1" - } -} diff --git a/internal/linker/test/multi_linker/onep_a/yarn.lock b/internal/linker/test/multi_linker/onep_a/yarn.lock deleted file mode 100644 index b89435104..000000000 --- a/internal/linker/test/multi_linker/onep_a/yarn.lock +++ /dev/null @@ -1,20 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.1.tgz#93b90b9a3e00c7a143f2e49f6e2b32fd72237cdb" - integrity sha1-k7kLmj4Ax6FD8uSfbisy/XIjfNs= - -smallest@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/smallest/-/smallest-1.0.1.tgz#59ddee13546911b2177f36af61a866bf3b3bfb81" - integrity sha1-Wd3uE1RpEbIXfzavYahmvzs7+4E= - dependencies: - to-array "~0.1.4" - -to-array@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= diff --git a/internal/linker/test/multi_linker/onep_b/BUILD.bazel b/internal/linker/test/multi_linker/onep_b/BUILD.bazel deleted file mode 100644 index 9ce35a59c..000000000 --- a/internal/linker/test/multi_linker/onep_b/BUILD.bazel +++ /dev/null @@ -1,14 +0,0 @@ -load("//:index.bzl", "js_library") - -# For negative testing, an alternative to 'onep-a' -# that doesn't include the transitive semver npm dep -# that @internal_test_multi_linker_onep_a_deps//:node_modules does -js_library( - name = "onep_b", - package_name = "@test_multi_linker/onep-b", - srcs = [ - "index.js", - "package.json", - ], - visibility = ["//internal/linker/test/multi_linker:__subpackages__"], -) diff --git a/internal/linker/test/multi_linker/onep_b/index.js b/internal/linker/test/multi_linker/onep_b/index.js deleted file mode 100644 index 3be604676..000000000 --- a/internal/linker/test/multi_linker/onep_b/index.js +++ /dev/null @@ -1,13 +0,0 @@ -function semverVersion() { - const semverVersion = require(require.resolve('semver/package.json')).version; - return semverVersion; -} - -function semver() { - return require('semver'); -} - -module.exports = { - semverVersion, - semver -} diff --git a/internal/linker/test/multi_linker/onep_b/package.json b/internal/linker/test/multi_linker/onep_b/package.json deleted file mode 100644 index 51d635fe7..000000000 --- a/internal/linker/test/multi_linker/onep_b/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "private": true, - "peerDependency": { - "semver": ">=5.0.0" - }, - "devDependencies": { - "smallest": "1.0.1" - } -} diff --git a/internal/linker/test/multi_linker/onep_b/yarn.lock b/internal/linker/test/multi_linker/onep_b/yarn.lock deleted file mode 100644 index c95aacae9..000000000 --- a/internal/linker/test/multi_linker/onep_b/yarn.lock +++ /dev/null @@ -1,15 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -smallest@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/smallest/-/smallest-1.0.1.tgz#59ddee13546911b2177f36af61a866bf3b3bfb81" - integrity sha1-Wd3uE1RpEbIXfzavYahmvzs7+4E= - dependencies: - to-array "~0.1.4" - -to-array@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= diff --git a/internal/linker/test/multi_linker/package.json b/internal/linker/test/multi_linker/package.json deleted file mode 100644 index b6f72f944..000000000 --- a/internal/linker/test/multi_linker/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "devDependencies": { - "semver": "1.0.0" - } -} diff --git a/internal/linker/test/multi_linker/sub/BUILD.bazel b/internal/linker/test/multi_linker/sub/BUILD.bazel deleted file mode 100644 index 9a2a7a96f..000000000 --- a/internal/linker/test/multi_linker/sub/BUILD.bazel +++ /dev/null @@ -1,85 +0,0 @@ -load("//:index.bzl", "nodejs_test") - -nodejs_test( - name = "single_test", - data = [ - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-a - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-a", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-a2 - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-a2", - - # links to root /node_modules/@test_multi_linker/lib-b (default if not dep'd on via generated yarn_install target) - "//internal/linker/test/multi_linker/lib_b", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-c - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-c", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-c - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-c2", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-d - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-d", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-d - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-d2", - - # third-party deps - "@npm//semver", - "@internal_test_multi_linker_deps//semver", - "@internal_test_multi_linker_sub_deps//semver", - ], - entry_point = "single_test.js", -) - -nodejs_test( - name = "duplicate_test", - data = [ - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-a - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-a", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-a2 - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-a2", - - # links to root /node_modules/@test_multi_linker/lib-b (default if not dep'd on via generated yarn_install target) - "//internal/linker/test/multi_linker/lib_b", - - # can dep on the same 1p dep multiple times if they link to different folders; - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-b - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-b", - - # can dep on the same 1p dep multiple times if they link to different folders; - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-b2 - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-b2", - - # does not link since it does not provide a package name - "//internal/linker/test/multi_linker/lib_c", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-c - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-c", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-c2 - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-c2", - - # does not link since it does not provide a package name - "//internal/linker/test/multi_linker/lib_d", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-d - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-d", - - # links to .../internal/linker/test/multi_linker/sub/node_modules/@test_multi_linker/lib-d2 - "@internal_test_multi_linker_sub_deps//@test_multi_linker/lib-d2", - - # third-party deps - "@npm//semver", - "@internal_test_multi_linker_deps//semver", - "@internal_test_multi_linker_sub_deps//semver", - ], - entry_point = "duplicate_test.js", -) - -nodejs_test( - name = "prod_deps_test", - data = ["@internal_test_multi_linker_sub_deps//:node_modules"], - entry_point = "prod_deps_test.js", -) diff --git a/internal/linker/test/multi_linker/sub/dev/BUILD.bazel b/internal/linker/test/multi_linker/sub/dev/BUILD.bazel deleted file mode 100644 index 38f560b30..000000000 --- a/internal/linker/test/multi_linker/sub/dev/BUILD.bazel +++ /dev/null @@ -1,7 +0,0 @@ -load("//:index.bzl", "nodejs_test") - -nodejs_test( - name = "dev_deps_test", - data = ["@internal_test_multi_linker_sub_dev_deps//:node_modules"], - entry_point = "dev_deps_test.js", -) diff --git a/internal/linker/test/multi_linker/sub/dev/dev_deps_test.js b/internal/linker/test/multi_linker/sub/dev/dev_deps_test.js deleted file mode 100644 index e27b48a76..000000000 --- a/internal/linker/test/multi_linker/sub/dev/dev_deps_test.js +++ /dev/null @@ -1,13 +0,0 @@ -const assert = require('assert') - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -assert.equal( - pkgVersion('semver'), '1.0.3', - `expected local semver version '${pkgVersion('semver')}' to match sub/package.json version`) - -assert.equal( - pkgVersion('smallest'), '1.0.1', - `expected local smallest version '${pkgVersion('smallest')}' to match sub/package.json version`) \ No newline at end of file diff --git a/internal/linker/test/multi_linker/sub/duplicate_test.js b/internal/linker/test/multi_linker/sub/duplicate_test.js deleted file mode 100644 index 55b1a9f3a..000000000 --- a/internal/linker/test/multi_linker/sub/duplicate_test.js +++ /dev/null @@ -1,53 +0,0 @@ -const assert = require('assert') - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -assert.equal( - pkgVersion('semver'), '1.0.3', - `expected local semver version '${pkgVersion('semver')}' to match sub/package.json version`) - -const libd = require('@test_multi_linker/lib-d') -assert.equal( - libd.whichSemver(), '1.0.3', - `expected libd.whichSemver() to be sub/package.json version but got ${ - libd.whichSemver()} instead`) - -const libd2 = require('@test_multi_linker/lib-d2') -assert.equal( - libd2.whichSemver(), '1.0.3', - `expected libd2.whichSemver() to be sub/package.json version but got ${ - libd2.whichSemver()} instead`) - -const libc = require('@test_multi_linker/lib-c') -assert.equal( - libc.whichSemver(), '1.0.8', - 'expected libc.whichSemver() to be its transitive lib_c/package.json version') - -const libc2 = require('@test_multi_linker/lib-c2') -assert.equal( - libc2.whichSemver(), '1.0.8', - 'expected libc2.whichSemver() to be its transitive lib_c/package.json version') - -const libb = require('@test_multi_linker/lib-b') -assert.equal( - libb.whichSemver(), '1.0.2', - 'expected libb.whichSemver() to be its transitive lib_b/package.json version') - -const libb2 = require('@test_multi_linker/lib-b2') -assert.equal( - libb2.whichSemver(), '1.0.2', - 'expected libb.whichSemver() to be its transitive lib_b/package.json version') - -const liba = require('@test_multi_linker/lib-a') -assert.equal( - liba.whichSemver(), '1.0.3', - `expected liba.whichSemver() to be sub/package.json version but got ${ - liba.whichSemver()} instead`) - -const liba2 = require('@test_multi_linker/lib-a2') -assert.equal( - liba2.whichSemver(), '1.0.3', - `expected liba.whichSemver() to be sub/package.json version but got ${ - liba2.whichSemver()} instead`) diff --git a/internal/linker/test/multi_linker/sub/package.json b/internal/linker/test/multi_linker/sub/package.json deleted file mode 100644 index d32983087..000000000 --- a/internal/linker/test/multi_linker/sub/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.3" - }, - "devDependencies": { - "smallest": "1.0.1" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/sub/prod_deps_test.js b/internal/linker/test/multi_linker/sub/prod_deps_test.js deleted file mode 100644 index 9650daa49..000000000 --- a/internal/linker/test/multi_linker/sub/prod_deps_test.js +++ /dev/null @@ -1,21 +0,0 @@ -const assert = require('assert') -const isWindows = /^win/i.test(process.platform); - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -assert.equal( - pkgVersion('semver'), '1.0.3', - `expected local semver version '${pkgVersion('semver')}' to match sub/package.json version`) - -if (!isWindows) { - // We can't assert the following on Windows since it doesn't run in a sandbox - try { - const p = require.resolve('smallest'); - assert.fail(`require \'smallest\' should have thrown but resolved to ${p} instead`) - } catch (e) { - assert.equal(e.name, 'Error', `Should have thrown Error but threw ${e}`) - assert.equal(e.code, 'MODULE_NOT_FOUND', 'Should have thrown MODULE_NOT_FOUND') - } -} diff --git a/internal/linker/test/multi_linker/sub/single_test.js b/internal/linker/test/multi_linker/sub/single_test.js deleted file mode 100644 index e59dff2c5..000000000 --- a/internal/linker/test/multi_linker/sub/single_test.js +++ /dev/null @@ -1,48 +0,0 @@ -const assert = require('assert') - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -assert.equal( - pkgVersion('semver'), '1.0.3', - `expected local semver version '${pkgVersion('semver')}' to match sub/package.json version`) - -const libd = require('@test_multi_linker/lib-d') -assert.equal( - libd.whichSemver(), '1.0.3', - `expected libd.whichSemver() to be sub/package.json version but got ${ - libd.whichSemver()} instead`) - -const libd2 = require('@test_multi_linker/lib-d2') -assert.equal( - libd2.whichSemver(), '1.0.3', - `expected libd.whichSemver() to be sub/package.json version but got ${ - libd2.whichSemver()} instead`) - -const libc = require('@test_multi_linker/lib-c') -assert.equal( - libc.whichSemver(), '1.0.8', - 'expected libc.whichSemver() to be its transitive lib_c/package.json version') - -const libc2 = require('@test_multi_linker/lib-c2') -assert.equal( - libc2.whichSemver(), '1.0.8', - 'expected libc2.whichSemver() to be its transitive lib_c/package.json version') - -const libb = require('@test_multi_linker/lib-b') -assert.equal( - libb.whichSemver(), '1.0.2', - 'expected libb.whichSemver() to be its transitive lib_b/package.json version') - -const liba = require('@test_multi_linker/lib-a') -assert.equal( - liba.whichSemver(), '1.0.3', - `expected liba.whichSemver() to be sub/package.json version but got ${ - liba.whichSemver()} instead`) - -const liba2 = require('@test_multi_linker/lib-a2') -assert.equal( - liba2.whichSemver(), '1.0.3', - `expected liba2.whichSemver() to be sub/package.json version but got ${ - liba2.whichSemver()} instead`) diff --git a/internal/linker/test/multi_linker/sub/yarn.lock b/internal/linker/test/multi_linker/sub/yarn.lock deleted file mode 100644 index 934874074..000000000 --- a/internal/linker/test/multi_linker/sub/yarn.lock +++ /dev/null @@ -1,20 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.3.tgz#453f40adadf8ce23ff4eb937972c6a007d52ef0d" - integrity sha1-RT9Ara34ziP/Trk3lyxqAH1S7w0= - -smallest@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/smallest/-/smallest-1.0.1.tgz#59ddee13546911b2177f36af61a866bf3b3bfb81" - integrity sha1-Wd3uE1RpEbIXfzavYahmvzs7+4E= - dependencies: - to-array "~0.1.4" - -to-array@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= diff --git a/internal/linker/test/multi_linker/test.ts b/internal/linker/test/multi_linker/test.ts deleted file mode 100644 index 22ff3c4ed..000000000 --- a/internal/linker/test/multi_linker/test.ts +++ /dev/null @@ -1,26 +0,0 @@ -describe('linker', () => { - it('should link nested node modules', () => { - // The nested internal/linker/test/multi_linker/package.json file pulls in semver 1.0.0 - // require('semver') should resolve to that version and not the root package.json version - const semverVersion = require(require.resolve('semver/package.json')).version; - expect(semverVersion).toBe('1.0.0'); - }); - - it('should get transitive nested node_modules from 1p dep', () => { - // The nested transitive - // internal/linker/test/multi_linker/onep_a/package.json file pulls in - // semver 1.0.1 into @internal_test_multi_linker_onep_a_deps//:node_modules. - // onep-a should find that version as - // @internal_test_multi_linker_onep_a_deps//:node_modules is pulled in - // transitively via //internal/linker/test/multi_linker/onep_a - const onepa = require('@test_multi_linker/onep-a'); - expect(onepa.semverVersion()).toBe('1.0.1'); - }); - - it('should get semver from root pacakge.json (which is currently 5.6.0) if the is no transitive in 1p dep', - () => { - const onepb = require('@test_multi_linker/onep-b'); - const major = onepb.semver().major(onepb.semverVersion()); - expect(major).toBeGreaterThanOrEqual(5); - }); -}); diff --git a/internal/linker/test/multi_linker/test_a/BUILD.bazel b/internal/linker/test/multi_linker/test_a/BUILD.bazel deleted file mode 100644 index 75f539702..000000000 --- a/internal/linker/test/multi_linker/test_a/BUILD.bazel +++ /dev/null @@ -1,36 +0,0 @@ -load("//:index.bzl", "nodejs_test") - -nodejs_test( - name = "test", - data = [ - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-a - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-a", - - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-a2 - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-a2", - - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-b - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-b", - - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-b2 - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-b2", - - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-c - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-c", - - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-c2 - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-c2", - - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-d - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-d", - - # links to .../internal/linker/test/multi_linker/test_a/node_modules/@test_multi_linker/lib-d2 - "@internal_test_multi_linker_test_a_deps//@test_multi_linker/lib-d2", - - # third-party deps - "@npm//semver", - "@internal_test_multi_linker_deps//semver", - "@internal_test_multi_linker_test_a_deps//semver", - ], - entry_point = "test.js", -) diff --git a/internal/linker/test/multi_linker/test_a/package.json b/internal/linker/test/multi_linker/test_a/package.json deleted file mode 100644 index dadac1c69..000000000 --- a/internal/linker/test/multi_linker/test_a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.4" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/test_a/test.js b/internal/linker/test/multi_linker/test_a/test.js deleted file mode 100644 index 17ca19b2f..000000000 --- a/internal/linker/test/multi_linker/test_a/test.js +++ /dev/null @@ -1,63 +0,0 @@ -const assert = require('assert') -const isWindows = /^win/i.test(process.platform); - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -if (!isWindows) { - // We can't assert the following on Windows since it doesn't run in a sandbox - assert.equal( - pkgVersion('semver'), '1.0.4', - 'expected pkgVersion("semver") to match test_a/package.json version') - - // We can't assert the following on Windows since it doesn't run in a sandbox - const libd = require('@test_multi_linker/lib-d') - assert.equal( - libd.whichSemver(), '1.0.4', - `expected libd.whichSemver() to be test_a/package.json version but got ${ - libd.whichSemver()} instead`) - - // We can't assert the following on Windows since it doesn't run in a sandbox - const libd2 = require('@test_multi_linker/lib-d2') - assert.equal( - libd2.whichSemver(), '1.0.4', - `expected libd.whichSemver() to be test_a/package.json version but got ${ - libd2.whichSemver()} instead`) -} - -const libc = require('@test_multi_linker/lib-c') -assert.equal( - libc.whichSemver(), '1.0.8', - 'expected libc.whichSemver() to be its transitive lib_c/package.json version') - -const libc2 = require('@test_multi_linker/lib-c2') -assert.equal( - libc2.whichSemver(), '1.0.8', - 'expected libc2.whichSemver() to be its transitive lib_c/package.json version') - -const libb = require('@test_multi_linker/lib-b') -assert.equal( - libb.whichSemver(), '1.0.2', - 'expected libb.whichSemver() to be its transitive lib_b/package.json version') - -const libb2 = require('@test_multi_linker/lib-b2') -assert.equal( - libb2.whichSemver(), '1.0.2', - 'expected libb2.whichSemver() to be its transitive lib_b/package.json version') - -if (!isWindows) { - // We can't assert the following on Windows since it doesn't run in a sandbox - const liba = require('@test_multi_linker/lib-a') - assert.equal( - liba.whichSemver(), '1.0.4', - `expected liba.whichSemver() to be test_a/package.json version but got ${ - liba.whichSemver()} instead`) - - // We can't assert the following on Windows since it doesn't run in a sandbox - const liba2 = require('@test_multi_linker/lib-a2') - assert.equal( - liba2.whichSemver(), '1.0.4', - `expected liba.whichSemver() to be test_a/package.json version but got ${ - liba2.whichSemver()} instead`) -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/test_a/yarn.lock b/internal/linker/test/multi_linker/test_a/yarn.lock deleted file mode 100644 index 765c1e3d8..000000000 --- a/internal/linker/test/multi_linker/test_a/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.4.tgz#41c0da40706d0defe763998281fc616a2a5d1e46" - integrity sha1-QcDaQHBtDe/nY5mCgfxhaipdHkY= diff --git a/internal/linker/test/multi_linker/test_b/BUILD.bazel b/internal/linker/test/multi_linker/test_b/BUILD.bazel deleted file mode 100644 index ae133d1b6..000000000 --- a/internal/linker/test/multi_linker/test_b/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -load("//:index.bzl", "nodejs_test") - -nodejs_test( - name = "test", - data = [ - # links to root /node_modules/@test_multi_linker/lib-a - "//internal/linker/test/multi_linker/lib_a", - - # links to root /node_modules/@test_multi_linker/lib-b - "//internal/linker/test/multi_linker/lib_b", - - # does not link since it does not provide a package name - "//internal/linker/test/multi_linker/lib_c", - - # does not link since it does not provide a package name - "//internal/linker/test/multi_linker/lib_d", - - # third-party deps - "@npm//semver", - "@internal_test_multi_linker_deps//semver", - "@internal_test_multi_linker_test_b_deps//semver", - ], - entry_point = "test.js", -) diff --git a/internal/linker/test/multi_linker/test_b/package.json b/internal/linker/test/multi_linker/test_b/package.json deleted file mode 100644 index 368cc1af2..000000000 --- a/internal/linker/test/multi_linker/test_b/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.5" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/test_b/test.js b/internal/linker/test/multi_linker/test_b/test.js deleted file mode 100644 index dbb436ad1..000000000 --- a/internal/linker/test/multi_linker/test_b/test.js +++ /dev/null @@ -1,44 +0,0 @@ -const assert = require('assert') -const semver = require('semver') -const isWindows = /^win/i.test(process.platform); - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -assert.equal( - pkgVersion('semver'), '1.0.5', - 'expected pkgVersion("semver") to match test_b/package.json version') - -if (!isWindows) { - // We can't assert the following on Windows since it doesn't run in a sandbox - try { - require('@test_multi_linker/lib-d') - assert.fail('require \'@test_multi_linker/lib-d\' should have thrown') - } catch (e) { - assert.equal(e.name, 'Error', 'Should have thrown Error') - assert.equal(e.code, 'MODULE_NOT_FOUND', 'Should have thrown MODULE_NOT_FOUND') - } - - // We can't assert the following on Windows since it doesn't run in a sandbox - try { - require('@test_multi_linker/lib-c') - assert.fail('require \'@test_multi_linker/lib-c\' should have thrown') - } catch (e) { - assert.equal(e.name, 'Error', 'Should have thrown Error') - assert.equal(e.code, 'MODULE_NOT_FOUND', 'Should have thrown MODULE_NOT_FOUND') - } -} - -const libb = require('@test_multi_linker/lib-b') -assert.equal( - libb.whichSemver(), '1.0.2', - 'expected libb.whichSemver() to be its transitive lib_b/package.json version') - -if (!isWindows) { - // We can't assert the following on Windows since it doesn't run in a sandbox - const liba = require('@test_multi_linker/lib-a') - assert.ok( - semver.gte(liba.whichSemver(), '5.0.0'), - `expected liba.whichSemver() ${liba.whichSemver()} to be the root @npm version >= 5.0.0`) -} diff --git a/internal/linker/test/multi_linker/test_b/yarn.lock b/internal/linker/test/multi_linker/test_b/yarn.lock deleted file mode 100644 index 2dc6839b8..000000000 --- a/internal/linker/test/multi_linker/test_b/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.5.tgz#7abca9337d64408ec2d42ffd974858c04dca3bdb" - integrity sha1-erypM31kQI7C1C/9l0hYwE3KO9s= diff --git a/internal/linker/test/multi_linker/test_c/BUILD.bazel b/internal/linker/test/multi_linker/test_c/BUILD.bazel deleted file mode 100644 index f5541b2ff..000000000 --- a/internal/linker/test/multi_linker/test_c/BUILD.bazel +++ /dev/null @@ -1,36 +0,0 @@ -load("//:index.bzl", "nodejs_test") - -nodejs_test( - name = "test", - data = [ - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-a - "@internal_test_multi_linker_deps//@test_multi_linker/lib-a", - - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-a2 - "@internal_test_multi_linker_deps//@test_multi_linker/lib-a2", - - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-b - "@internal_test_multi_linker_deps//@test_multi_linker/lib-b", - - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-b2 - "@internal_test_multi_linker_deps//@test_multi_linker/lib-b2", - - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-c - "@internal_test_multi_linker_deps//@test_multi_linker/lib-c", - - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-c2 - "@internal_test_multi_linker_deps//@test_multi_linker/lib-c2", - - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-d - "@internal_test_multi_linker_deps//@test_multi_linker/lib-d", - - # links to .../internal/linker/test/multi_linker/node_modules/@test_multi_linker/lib-d2 - "@internal_test_multi_linker_deps//@test_multi_linker/lib-d2", - - # third-party deps - "@npm//semver", - "@internal_test_multi_linker_deps//semver", - "@internal_test_multi_linker_test_c_deps//semver", - ], - entry_point = "test.js", -) diff --git a/internal/linker/test/multi_linker/test_c/package.json b/internal/linker/test/multi_linker/test_c/package.json deleted file mode 100644 index 32bc6c212..000000000 --- a/internal/linker/test/multi_linker/test_c/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.6" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/test_c/test.js b/internal/linker/test/multi_linker/test_c/test.js deleted file mode 100644 index 94166bb35..000000000 --- a/internal/linker/test/multi_linker/test_c/test.js +++ /dev/null @@ -1,54 +0,0 @@ -const assert = require('assert') -const semver = require('semver') - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -assert.equal( - pkgVersion('semver'), '1.0.6', - 'expected pkgVersion("semver") to match test_c/package.json version') - -const libd = require('@test_multi_linker/lib-d') -assert.equal( - libd.whichSemver(), '1.0.0', - `expected libd.whichSemver() to be multi_linker/package.json version but got ${ - libd.whichSemver()} instead`) - -const libd2 = require('@test_multi_linker/lib-d2') -assert.equal( - libd2.whichSemver(), '1.0.0', - `expected libd.whichSemver() to be multi_linker/package.json version but got ${ - libd2.whichSemver()} instead`) - -const libc = require('@test_multi_linker/lib-c') -assert.equal( - libc.whichSemver(), '1.0.8', - 'expected libc.whichSemver() to be its transitive lib_c/package.json version') - -const libc2 = require('@test_multi_linker/lib-c2') -assert.equal( - libc2.whichSemver(), '1.0.8', - 'expected libc2.whichSemver() to be its transitive lib_c/package.json version') - -const libb = require('@test_multi_linker/lib-b') -assert.equal( - libb.whichSemver(), '1.0.2', - 'expected libb.whichSemver() to be its transitive lib_b/package.json version') - -const libb2 = require('@test_multi_linker/lib-b2') -assert.equal( - libb2.whichSemver(), '1.0.2', - 'expected libb2.whichSemver() to be its transitive lib_b/package.json version') - -const liba = require('@test_multi_linker/lib-a') -assert.equal( - liba.whichSemver(), '1.0.0', - `expected liba.whichSemver() to be multi_linker/package.json version but got ${ - liba.whichSemver()} instead`) - -const liba2 = require('@test_multi_linker/lib-a2') -assert.equal( - liba2.whichSemver(), '1.0.0', - `expected liba.whichSemver() to be multi_linker/package.json version but got ${ - liba2.whichSemver()} instead`) diff --git a/internal/linker/test/multi_linker/test_c/yarn.lock b/internal/linker/test/multi_linker/test_c/yarn.lock deleted file mode 100644 index c0d197fb6..000000000 --- a/internal/linker/test/multi_linker/test_c/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.6.tgz#697b6bff4b3eca86f35dc037c9ab2f1eb7af1a9e" - integrity sha1-aXtr/0s+yobzXcA3yasvHrevGp4= diff --git a/internal/linker/test/multi_linker/test_d/BUILD.bazel b/internal/linker/test/multi_linker/test_d/BUILD.bazel deleted file mode 100644 index aed2e5848..000000000 --- a/internal/linker/test/multi_linker/test_d/BUILD.bazel +++ /dev/null @@ -1,36 +0,0 @@ -load("//:index.bzl", "nodejs_test") - -nodejs_test( - name = "test", - data = [ - # links to root /node_modules/@test_multi_linker/lib-a - "@npm//@test_multi_linker/lib-a", - - # links to root /node_modules/@test_multi_linker/lib-a2 - "@npm//@test_multi_linker/lib-a2", - - # links to root /node_modules/@test_multi_linker/lib-b - "@npm//@test_multi_linker/lib-b", - - # links to root /node_modules/@test_multi_linker/lib-b2 - "@npm//@test_multi_linker/lib-b2", - - # links to root /node_modules/@test_multi_linker/lib-c - "@npm//@test_multi_linker/lib-c", - - # links to root /node_modules/@test_multi_linker/lib-c2 - "@npm//@test_multi_linker/lib-c2", - - # links to root /node_modules/@test_multi_linker/lib-d - "@npm//@test_multi_linker/lib-d", - - # links to root /node_modules/@test_multi_linker/lib-d2 - "@npm//@test_multi_linker/lib-d2", - - # third-party deps - "@npm//semver", - "@internal_test_multi_linker_deps//semver", - "@internal_test_multi_linker_test_d_deps//semver", - ], - entry_point = "test.js", -) diff --git a/internal/linker/test/multi_linker/test_d/package.json b/internal/linker/test/multi_linker/test_d/package.json deleted file mode 100644 index 46aa854ba..000000000 --- a/internal/linker/test/multi_linker/test_d/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "private": true, - "//": "semver intentionally pinned to unique version within multi_linker tests to assert the version in this node_modules is found", - "dependencies": { - "semver": "1.0.7" - } -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/test_d/test.js b/internal/linker/test/multi_linker/test_d/test.js deleted file mode 100644 index 73293f724..000000000 --- a/internal/linker/test/multi_linker/test_d/test.js +++ /dev/null @@ -1,59 +0,0 @@ -const assert = require('assert') -const semver = require('semver') -const isWindows = /^win/i.test(process.platform); - -function pkgVersion(pkg) { - return require(require.resolve(`${pkg}/package.json`)).version -} - -assert.equal( - pkgVersion('semver'), '1.0.7', - 'expected pkgVersion("semver") to match test_d/package.json version') - -if (!isWindows) { - // We can't assert the following on Windows since it doesn't run in a sandbox - const libd = require('@test_multi_linker/lib-d') - assert.ok( - semver.gte(libd.whichSemver(), '5.0.0'), - `expected libd.whichSemver() ${libd.whichSemver()} to be the root @npm version >= 5.0.0`) - - // We can't assert the following on Windows since it doesn't run in a sandbox - const libd2 = require('@test_multi_linker/lib-d2') - assert.ok( - semver.gte(libd2.whichSemver(), '5.0.0'), - `expected libd2.whichSemver() ${libd2.whichSemver()} to be the root @npm version >= 5.0.0`) -} - -const libc = require('@test_multi_linker/lib-c') -assert.equal( - libc.whichSemver(), '1.0.8', - 'expected libc.whichSemver() to be its transitive lib_c/package.json version') - -const libc2 = require('@test_multi_linker/lib-c2') -assert.equal( - libc2.whichSemver(), '1.0.8', - 'expected libc2.whichSemver() to be its transitive lib_c/package.json version') - -const libb = require('@test_multi_linker/lib-b') -assert.equal( - libb.whichSemver(), '1.0.2', - 'expected libb.whichSemver() to be its transitive lib_b/package.json version') - -const libb2 = require('@test_multi_linker/lib-b2') -assert.equal( - libb2.whichSemver(), '1.0.2', - 'expected libb2.whichSemver() to be its transitive lib_b/package.json version') - -if (!isWindows) { - // We can't assert the following on Windows since it doesn't run in a sandbox - const liba = require('@test_multi_linker/lib-a') - assert.ok( - semver.gte(liba.whichSemver(), '5.0.0'), - `expected liba.whichSemver() ${liba.whichSemver()} to be the root @npm version >= 5.0.0`) - - // We can't assert the following on Windows since it doesn't run in a sandbox - const liba2 = require('@test_multi_linker/lib-a2') - assert.ok( - semver.gte(liba2.whichSemver(), '5.0.0'), - `expected liba2.whichSemver() ${liba2.whichSemver()} to be the root @npm version >= 5.0.0`) -} \ No newline at end of file diff --git a/internal/linker/test/multi_linker/test_d/yarn.lock b/internal/linker/test/multi_linker/test_d/yarn.lock deleted file mode 100644 index e17926fbe..000000000 --- a/internal/linker/test/multi_linker/test_d/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.7.tgz#668e127e81e81e0954d25a6d2c1cb20a1538b2e3" - integrity sha1-Zo4SfoHoHglU0lptLByyChU4suM= diff --git a/internal/linker/test/multi_linker/tsconfig.json b/internal/linker/test/multi_linker/tsconfig.json deleted file mode 100644 index d80d283ba..000000000 --- a/internal/linker/test/multi_linker/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "compilerOptions": { - "types": ["node", "jasmine"] - } -} diff --git a/internal/linker/test/multi_linker/yarn.lock b/internal/linker/test/multi_linker/yarn.lock deleted file mode 100644 index 49b7a00fa..000000000 --- a/internal/linker/test/multi_linker/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -semver@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-1.0.0.tgz#11f18a0c08ed21c988fc2b0257f1951969816615" - integrity sha1-EfGKDAjtIcmI/CsCV/GVGWmBZhU= diff --git a/internal/linker/test/no_npm_deps/BUILD.bazel b/internal/linker/test/no_npm_deps/BUILD.bazel deleted file mode 100644 index bc7466585..000000000 --- a/internal/linker/test/no_npm_deps/BUILD.bazel +++ /dev/null @@ -1,11 +0,0 @@ -load(":no_npm_deps.bzl", "no_npm_deps") - -# Tests the -# ``` -# [link_node_modules.js] no third-party packages; mkdir node_modules at npm/node_modules -# ``` -# path in the linker -no_npm_deps( - name = "no_npm_deps_linker_test", - src = "input.js", -) diff --git a/internal/linker/test/no_npm_deps/input.js b/internal/linker/test/no_npm_deps/input.js deleted file mode 100644 index 60d0eb835..000000000 --- a/internal/linker/test/no_npm_deps/input.js +++ /dev/null @@ -1 +0,0 @@ -console.log('here'); \ No newline at end of file diff --git a/internal/linker/test/no_npm_deps/no_npm_deps.bzl b/internal/linker/test/no_npm_deps/no_npm_deps.bzl deleted file mode 100644 index 7328acc93..000000000 --- a/internal/linker/test/no_npm_deps/no_npm_deps.bzl +++ /dev/null @@ -1,42 +0,0 @@ -""" -This rule runs terser executable directly via ctx.actions.run instead of using the run_node function. -This ensures that there are no third-party npm deps in runfiles since run_node will add any -NodeRuntimeDepsInfo deps from the executable terser. -""" - -_ATTRS = { - "src": attr.label( - allow_single_file = True, - mandatory = True, - ), - "terser": attr.label( - executable = True, - cfg = "host", - default = Label("@npm//terser/bin:terser"), - ), -} - -_OUTPUTS = { - "minified": "%{name}.js", -} - -def _impl(ctx): - args = ctx.actions.args() - args.add(ctx.file.src.path) - args.add_all(["--output", ctx.outputs.minified.path]) - - ctx.actions.run( - progress_message = "Optimizing JavaScript %s [terser]" % ctx.outputs.minified.short_path, - executable = ctx.executable.terser, - inputs = [ctx.file.src], - outputs = [ctx.outputs.minified], - arguments = [args], - ) - - return [DefaultInfo()] - -no_npm_deps = rule( - implementation = _impl, - attrs = _ATTRS, - outputs = _OUTPUTS, -) diff --git a/internal/linker/test/workspace_link/BUILD.bazel b/internal/linker/test/workspace_link/BUILD.bazel deleted file mode 100644 index 560829fd3..000000000 --- a/internal/linker/test/workspace_link/BUILD.bazel +++ /dev/null @@ -1,11 +0,0 @@ -load("//packages/jasmine:index.bzl", "jasmine_node_test") - -jasmine_node_test( - name = "test", - srcs = ["test.js"], - link_workspace_root = True, - deps = [ - "//internal/linker/test/workspace_link/bar", - "//internal/linker/test/workspace_link/foo", - ], -) diff --git a/internal/linker/test/workspace_link/bar/BUILD.bazel b/internal/linker/test/workspace_link/bar/BUILD.bazel deleted file mode 100644 index 14a157618..000000000 --- a/internal/linker/test/workspace_link/bar/BUILD.bazel +++ /dev/null @@ -1,10 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") - -copy_to_bin( - name = "bar", - srcs = [ - "main.js", - "package.json", - ], - visibility = ["//internal/linker/test/workspace_link:__pkg__"], -) diff --git a/internal/linker/test/workspace_link/bar/main.js b/internal/linker/test/workspace_link/bar/main.js deleted file mode 100644 index 282afcefd..000000000 --- a/internal/linker/test/workspace_link/bar/main.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - bar: 'bar', -} diff --git a/internal/linker/test/workspace_link/bar/package.json b/internal/linker/test/workspace_link/bar/package.json deleted file mode 100644 index fdc36a9ed..000000000 --- a/internal/linker/test/workspace_link/bar/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "bar", - "main": "main.js", - "typings": "main.d.ts" -} diff --git a/internal/linker/test/workspace_link/foo/BUILD.bazel b/internal/linker/test/workspace_link/foo/BUILD.bazel deleted file mode 100644 index 27ecf978f..000000000 --- a/internal/linker/test/workspace_link/foo/BUILD.bazel +++ /dev/null @@ -1,35 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") -load("@npm//typescript:index.bzl", "tsc") - -tsc( - name = "foo_lib", - outs = [ - "main.d.ts", - "main.js", - ], - args = [ - "-p", - "$(execpath tsconfig.json)", - "--outDir", - # $(RULEDIR) is a shorthand for the dist/bin directory where Bazel requires we write outputs - "$(RULEDIR)", - ], - data = [ - "main.ts", - "tsconfig.json", - ], -) - -copy_to_bin( - name = "foo_files", - srcs = ["package.json"], -) - -filegroup( - name = "foo", - srcs = [ - ":foo_files", - ":foo_lib", - ], - visibility = ["//internal/linker/test/workspace_link:__pkg__"], -) diff --git a/internal/linker/test/workspace_link/foo/main.ts b/internal/linker/test/workspace_link/foo/main.ts deleted file mode 100644 index e6f163ae0..000000000 --- a/internal/linker/test/workspace_link/foo/main.ts +++ /dev/null @@ -1 +0,0 @@ -export const foo: string = 'foo'; diff --git a/internal/linker/test/workspace_link/foo/package.json b/internal/linker/test/workspace_link/foo/package.json deleted file mode 100644 index 9f06a3c4a..000000000 --- a/internal/linker/test/workspace_link/foo/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "foo", - "main": "main.js", - "typings": "main.d.ts" -} diff --git a/internal/linker/test/workspace_link/foo/tsconfig.json b/internal/linker/test/workspace_link/foo/tsconfig.json deleted file mode 100644 index 67dcdc9d6..000000000 --- a/internal/linker/test/workspace_link/foo/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "types": [] - } -} \ No newline at end of file diff --git a/internal/linker/test/workspace_link/test.js b/internal/linker/test/workspace_link/test.js deleted file mode 100644 index 9a44d4668..000000000 --- a/internal/linker/test/workspace_link/test.js +++ /dev/null @@ -1,8 +0,0 @@ -describe('linker', () => { - it('should be able to require by absolute path when link_workspace_root is True', () => { - const foo = require('build_bazel_rules_nodejs/internal/linker/test/workspace_link/foo'); - expect(foo.foo).toBe('foo'); - const bar = require('build_bazel_rules_nodejs/internal/linker/test/workspace_link/bar'); - expect(bar.bar).toBe('bar'); - }); -}); diff --git a/internal/node/launcher.sh b/internal/node/launcher.sh index 18bb865d6..219ed97d7 100644 --- a/internal/node/launcher.sh +++ b/internal/node/launcher.sh @@ -222,11 +222,15 @@ for ARG in ${ALL_ARGS[@]+"${ALL_ARGS[@]}"}; do # Resolves to file under Bazel's execroot directory --bazel_use_legacy_execroot_paths__variant_execroot_main) \ MAIN="${PWD}/"TEMPLATED_entry_point_execroot_path ;; + --bazel_arg_runfiles_prefix=*) \ + ARGS+=( "${RUNFILES}/${ARG#--bazel_arg_runfiles_prefix=}" ) ;; # Remaining argv is collected to pass to the program *) ARGS+=( "$ARG" ) esac done +echo "args" "${ARGS[@]}" + # Link the first-party modules into node_modules directory before running the actual program if [ "$RUN_LINKER" = true ]; then link_modules_script=$(rlocation "TEMPLATED_link_modules_script") diff --git a/internal/node/node.bzl b/internal/node/node.bzl index 70cbb3fc9..6d3e992eb 100644 --- a/internal/node/node.bzl +++ b/internal/node/node.bzl @@ -194,6 +194,9 @@ def _nodejs_binary_impl(ctx): # Provide the target name as an environment variable avaiable to all actions for the # runfiles helpers to use. env_vars = "export BAZEL_TARGET=%s\n" % ctx.label + # Ensure JS-based persistent workers can recreate `node_modules` symlinks if required by + # providing a place with the information. + env_vars += """export NM_SYMLINKS="$(mktemp -d)/nm-symlinks.json"\n""" # Add all env vars from the ctx attr for [key, value] in ctx.attr.env.items(): diff --git a/internal/npm_install/npm_install.bzl b/internal/npm_install/npm_install.bzl index 420b16089..fc4e3fad8 100644 --- a/internal/npm_install/npm_install.bzl +++ b/internal/npm_install/npm_install.bzl @@ -578,7 +578,7 @@ def _npm_install_impl(repository_ctx): # Mark inputs as dependencies with repository_ctx.path to reduce repo fetch restart costs repository_ctx.path(repository_ctx.attr.package_json) - repository_ctx.path(repository_ctx.attr.yarn_lock) + repository_ctx.path(repository_ctx.attr.package_lock_json) for f in repository_ctx.attr.data: repository_ctx.path(f) node = repository_ctx.path(get_node_label(repository_ctx)) diff --git a/package.bzl b/package.bzl index a1a20a8a6..703731f8e 100644 --- a/package.bzl +++ b/package.bzl @@ -66,11 +66,10 @@ def rules_nodejs_dev_dependencies(): _maybe( http_archive, name = "bazel_skylib", - sha256 = "afbe4d9d033c007940acd24bb9becf1580a0280ae0b2ebbb5a7cb12912d2c115", - strip_prefix = "bazel-skylib-ffad33e9bfc60bdfa98292ca655a4e7035792046", + sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/archive/ffad33e9bfc60bdfa98292ca655a4e7035792046.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/archive/ffad33e9bfc60bdfa98292ca655a4e7035792046.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", ], ) diff --git a/package.json b/package.json index 06a1356f7..b8e8c2d3e 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "tsickle": "0.38.0", "tsutils": "2.27.2", "typeorm": "0.2.18", - "typescript": "~4.3.2", + "typescript": "~5.3.2", "unidiff": "1.0.1", "yarn": "1.22.0", "zone.js": "^0.11.4" diff --git a/packages/node-patches/BUILD.bazel b/packages/node-patches/BUILD.bazel index 69d188f61..b1a0c5a9a 100644 --- a/packages/node-patches/BUILD.bazel +++ b/packages/node-patches/BUILD.bazel @@ -13,8 +13,9 @@ # limitations under the License. load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") -load("@npm_node_patches//typescript:index.bzl", "tsc") +#load("@npm_node_patches//typescript:index.bzl", "tsc") load("//packages/rollup:index.bzl", "rollup_bundle") +load("//packages/typescript:index.bzl", "ts_project") package(default_visibility = ["//:__subpackages__"]) @@ -35,20 +36,32 @@ tests = glob( test_js = [t.replace(".ts", ".js") for t in tests] -tsc( +# tsc( +# name = "compile", +# # TODO: we ought to compile tests separately? +# outs = js + test_js, +# args = [ +# "-p", +# "$(execpath tsconfig-bazel.json)", +# "--outDir", +# "$(RULEDIR)", +# ], +# data = sources + tests + [ +# "tsconfig.json", +# "tsconfig-bazel.json", +# "@npm_node_patches//:node_modules", +# ], +# ) + +ts_project( name = "compile", - # TODO: we ought to compile tests separately? - outs = js + test_js, - args = [ - "-p", - "$(execpath tsconfig-bazel.json)", - "--outDir", - "$(RULEDIR)", - ], - data = sources + tests + [ - "tsconfig.json", - "tsconfig-bazel.json", - "@npm_node_patches//:node_modules", + srcs = sources + tests, + declaration = False, + tsconfig = "tsconfig-bazel.json", + extends = "tsconfig.json", + tsc = "@npm_node_patches//typescript/bin:tsc", + deps = [ + "@npm_node_patches//@types/node", ], ) diff --git a/packages/rollup/rollup_bundle.bzl b/packages/rollup/rollup_bundle.bzl index d7303f7dc..96806e697 100644 --- a/packages/rollup/rollup_bundle.bzl +++ b/packages/rollup/rollup_bundle.bzl @@ -1,27 +1,32 @@ "Rules for running Rollup under Bazel" -load("@build_bazel_rules_nodejs//:providers.bzl", "ExternalNpmPackageInfo", "JSEcmaScriptModuleInfo", "JSModuleInfo", "NODE_CONTEXT_ATTRS", "NodeContextInfo", "node_modules_aspect", "run_node") +load("@bazel_skylib//rules:run_binary.bzl", "run_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//:providers.bzl", "NODE_CONTEXT_ATTRS", "node_modules_aspect") load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "module_mappings_aspect") _DOC = "Runs the rollup.js CLI under Bazel." _ROLLUP_ATTRS = dict(NODE_CONTEXT_ATTRS, **{ "args": attr.string_list( - doc = """Command line arguments to pass to Rollup. Can be used to override config file settings. + doc = """ + Command line arguments to pass to Rollup. Can be used to override config file settings. -These argument passed on the command line before arguments that are added by the rule. -Run `bazel` with `--subcommands` to see what Rollup CLI command line was invoked. + These argument passed on the command line before arguments that are added by the rule. + Run `bazel` with `--subcommands` to see what Rollup CLI command line was invoked. -See the Rollup CLI docs for a complete list of supported arguments.""", + See the Rollup CLI docs for a complete list of supported arguments. + """, default = [], ), "config_file": attr.label( - doc = """A `rollup.config.js` file + doc = """ + A `rollup.config.js` file -Passed to the `--config` option, see [the config doc](https://rollupjs.org/guide/en/#configuration-files) + Passed to the `--config` option, see [the config doc](https://rollupjs.org/guide/en/#configuration-files) -If not set, a default basic Rollup config is used. -""", + If not set, a default basic Rollup config is used. + """, allow_single_file = True, default = "//packages/rollup:rollup.config.js", ), @@ -30,94 +35,100 @@ If not set, a default basic Rollup config is used. doc = """Other libraries that are required by the code, or by the rollup.config.js""", ), "entry_point": attr.label( - doc = """The bundle's entry point (e.g. your main.js or app.js or index.js). - -This is just a shortcut for the `entry_points` attribute with a single output chunk named the same as the rule. - -For example, these are equivalent: - -```python -rollup_bundle( - name = "bundle", - entry_point = "index.js", -) -``` - -```python -rollup_bundle( - name = "bundle", - entry_points = { - "index.js": "bundle" - } -) -``` - -If `rollup_bundle` is used on a `ts_library`, the `rollup_bundle` rule handles selecting the correct outputs from `ts_library`. -In this case, `entry_point` can be specified as the `.ts` file and `rollup_bundle` will handle the mapping to the `.mjs` output file. - -For example: - -```python -ts_library( - name = "foo", - srcs = [ - "foo.ts", - "index.ts", - ], -) - -rollup_bundle( - name = "bundle", - deps = [ "foo" ], - entry_point = "index.ts", -) -``` -""", + doc = """ + The bundle's entry point (e.g. your main.js or app.js or index.js). + + This is just a shortcut for the `entry_points` attribute with a single output chunk named the same as the rule. + + For example, these are equivalent: + + ```python + rollup_bundle( + name = "bundle", + entry_point = "index.js", + ) + ``` + + ```python + rollup_bundle( + name = "bundle", + entry_points = { + "index.js": "bundle" + } + ) + ``` + + If `rollup_bundle` is used on a `ts_library`, the `rollup_bundle` rule handles selecting the correct outputs from `ts_library`. + In this case, `entry_point` can be specified as the `.ts` file and `rollup_bundle` will handle the mapping to the `.mjs` output file. + + For example: + + ```python + ts_library( + name = "foo", + srcs = [ + "foo.ts", + "index.ts", + ], + ) + + rollup_bundle( + name = "bundle", + deps = [ "foo" ], + entry_point = "index.ts", + ) + ``` + """, allow_single_file = True, ), "entry_points": attr.label_keyed_string_dict( - doc = """The bundle's entry points (e.g. your main.js or app.js or index.js). + doc = """ + The bundle's entry points (e.g. your main.js or app.js or index.js). -Passed to the [`--input` option](https://github.com/rollup/rollup/blob/master/docs/999-big-list-of-options.md#input) in Rollup. + Passed to the [`--input` option](https://github.com/rollup/rollup/blob/master/docs/999-big-list-of-options.md#input) in Rollup. -Keys in this dictionary are labels pointing to .js entry point files. -Values are the name to be given to the corresponding output chunk. + Keys in this dictionary are labels pointing to .js entry point files. + Values are the name to be given to the corresponding output chunk. -Either this attribute or `entry_point` must be specified, but not both. -""", + Either this attribute or `entry_point` must be specified, but not both. + """, allow_files = True, ), "format": attr.string( - doc = """Specifies the format of the generated bundle. One of the following: - -- `amd`: Asynchronous Module Definition, used with module loaders like RequireJS -- `cjs`: CommonJS, suitable for Node and other bundlers -- `esm`: Keep the bundle as an ES module file, suitable for other bundlers and inclusion as a `