diff --git a/internal/bazel_integration_test/BUILD.bazel b/internal/bazel_integration_test/BUILD.bazel index bbc085ec1d..41587167eb 100644 --- a/internal/bazel_integration_test/BUILD.bazel +++ b/internal/bazel_integration_test/BUILD.bazel @@ -20,7 +20,10 @@ package(default_visibility = ["//visibility:public"]) nodejs_binary( name = "test_runner", configuration_env_vars = ["BAZEL_INTEGRATION_TEST_DEBUG"], - data = ["@npm//tmp"], + data = [ + "@npm//@yarnpkg/lockfile", + "@npm//tmp", + ], entry_point = ":test_runner.js", templated_args = ["--node_options=--max-old-space-size=1024"], ) diff --git a/internal/bazel_integration_test/test_runner.js b/internal/bazel_integration_test/test_runner.js index 03f1e52e0c..dd3c03f285 100644 --- a/internal/bazel_integration_test/test_runner.js +++ b/internal/bazel_integration_test/test_runner.js @@ -23,6 +23,7 @@ const spawnSync = require('child_process').spawnSync; const fs = require('fs'); const path = require('path'); const tmp = require('tmp'); +const lockfile = require('@yarnpkg/lockfile'); const DEBUG = !!process.env['BAZEL_INTEGRATION_TEST_DEBUG']; const VERBOSE_LOGS = !!process.env['VERBOSE_LOGS']; @@ -230,10 +231,13 @@ if (config.bazelrcAppend) { logFileContents('WORKSPACE file with replacements:', workspaceContents); } -// Handle package.json replacements +// Handle package.json and yarn.lock replacements const packageJsonFile = path.posix.join(workspaceRoot, 'package.json'); +const yarnLockFile = path.posix.join(workspaceRoot, 'yarn.lock'); + if (isFile(packageJsonFile)) { let packageJsonContents = fs.readFileSync(packageJsonFile, {encoding: 'utf-8'}); + let yarnLockContents = isFile(yarnLockFile) ? fs.readFileSync(yarnLockFile, {encoding: 'utf-8'}) : null; const npmPackageKeys = Object.keys(config.npmPackages); if (npmPackageKeys.length) { @@ -242,6 +246,29 @@ if (isFile(packageJsonFile)) { const packagePath = copyNpmPackage(config.npmPackages[packageJsonKey]).replace(/\\/g, '/'); const regex = new RegExp(`\"${packageJsonKey}\"\\s*\:\\s*\"[^"]+`) const replacement = `"${packageJsonKey}": "file:${packagePath}`; + + if (yarnLockContents) { + const {object: json} = lockfile.parse(yarnLockContents) + const lockFileKey = Object.keys(json).find(key => key.startsWith(packageJsonKey)) + const pkg = json[lockFileKey]; + + if (pkg.resolved) { + delete pkg.resolved + } + if (pkg.integrity) { + delete pkg.integrity + } + pkg.version = require(path.posix.join(packagePath, 'package.json')).version; + + // delete old object and replace with the new key + delete json[lockFileKey]; + + const rel = path.posix.join(path.relative(workspaceRoot, '/'), '..', packagePath); + json[`${packageJsonKey}@file:${rel}`] = pkg + + yarnLockContents = lockfile.stringify(json) + } + packageJsonContents = packageJsonContents.replace(regex, replacement); if (!packageJsonContents.includes(packagePath)) { console.error(`bazel_integration_test: package.json replacement for npm package ${ @@ -250,6 +277,9 @@ if (isFile(packageJsonFile)) { } } fs.writeFileSync(packageJsonFile, packageJsonContents); + if (yarnLockContents) { + fs.writeFileSync(yarnLockFile, yarnLockContents); + } } const packageJsonReplacementKeys = Object.keys(config.packageJsonRepacements); diff --git a/internal/npm_install/npm_install.bzl b/internal/npm_install/npm_install.bzl index 17d21b7c53..483657690f 100644 --- a/internal/npm_install/npm_install.bzl +++ b/internal/npm_install/npm_install.bzl @@ -159,8 +159,8 @@ def _add_data_dependencies(repository_ctx): for f in repository_ctx.attr.data: to = [] if f.package: - to += [f.package] - to += [f.name] + to.append(f.package) + to.append(f.name) # Make copies of the data files instead of symlinking # as yarn under linux will have trouble using symlinked @@ -328,6 +328,13 @@ def _yarn_install_impl(repository_ctx): yarn = get_yarn_label(repository_ctx) yarn_args = [] + + # Set frozen lockfile as default install to install the exact version from the yarn.lock + # file. To perform an yarn install use the vendord yarn binary with: + # `bazel run @nodejs//:yarn install` or `bazel run @nodejs//:yarn install -- -D ` + if repository_ctx.attr.frozen_lockfile: + yarn_args.append("--frozen-lockfile") + if not repository_ctx.attr.use_global_yarn_cache: yarn_args.extend(["--cache-folder", str(repository_ctx.path("_yarn_cache"))]) else: @@ -429,6 +436,20 @@ yarn_install = repository_rule( See yarn CLI docs https://yarnpkg.com/en/docs/cli/install for complete list of supported arguments.""", default = [], ), + "frozen_lockfile": attr.bool( + default = True, + doc = """Use the `--frozen-lockfile` flag for yarn. + +Don’t generate a `yarn.lock` lockfile and fail if an update is needed. + +This flag enables an exact install of the version that is specified in the `yarn.lock` +file. This helps to have reproduceable builds across builds. + +To update a dependency or install a new one run the `yarn install` command with the +vendored yarn binary. `bazel run @nodejs//:yarn install`. You can pass the options like +`bazel run @nodejs//:yarn install -- -D `. +""", + ), "use_global_yarn_cache": attr.bool( default = True, doc = """Use the global yarn cache on the system.