diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java index e4977a8d8b3ed8..7f6df0b81fc701 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java @@ -153,7 +153,6 @@ public SkyValue compute(SkyKey skyKey, Environment env) if (env.valuesMissing()) { return null; } - if (rule == null) { return new NoRepositoryDirectoryValue( String.format("Repository '%s' is not defined", repositoryName.getCanonicalForm())); @@ -168,41 +167,23 @@ public SkyValue compute(SkyKey skyKey, Environment env) DigestWriter digestWriter = new DigestWriter(directories, repositoryName, rule); if (shouldUseVendorRepos(env, handler, rule)) { - Path vendorPath = VENDOR_DIRECTORY.get(env).get(); - Path vendorMarker = vendorPath.getChild("@" + repositoryName.getName() + ".marker"); - boolean isVendorRepoUpToDate = - digestWriter.areRepositoryAndMarkerFileConsistent(handler, env, vendorMarker) != null; + RepositoryDirectoryValue repositoryDirectoryValue = + tryGettingValueUsingVendoredRepo( + env, rule, repoRoot, repositoryName, handler, digestWriter); if (env.valuesMissing()) { return null; } - if (isVendorRepoUpToDate) { - PathFragment vendorRepoPath = - vendorPath.getRelative(repositoryName.getName()).asFragment(); - return setupOverride(vendorRepoPath, env, repoRoot, repositoryName.getName()); - } else if (!IS_VENDOR_COMMAND.get(env).booleanValue()) { - // If this is vendor command, proceed with fetching to update the vendor directory. - // Otherwise, for example, the build command, we need to display a warning indicating - // that the vendored repository is out-of-date. - env.getListener() - .handle( - Event.warn( - rule.getLocation(), - String.format( - "Vendored repository '%s' is out-of-date. The up-to-date version will" - + " be fetched into the external cache and used. To update the repo" - + " in the vendor directory, run 'bazel vendor' with the directory" - + " flag", - rule.getName()))); + if (repositoryDirectoryValue != null) { + return repositoryDirectoryValue; } } - if (shouldUseCachedRepos(env, handler, repoRoot, rule)) { // Make sure marker file is up-to-date; correctly describes the current repository state byte[] markerHash = digestWriter.areRepositoryAndMarkerFileConsistent(handler, env); if (env.valuesMissing()) { return null; } - if (markerHash != null) { + if (markerHash != null) { // repo exist & up-to-date return RepositoryDirectoryValue.builder() .setPath(repoRoot) .setDigest(markerHash) @@ -268,6 +249,63 @@ public SkyValue compute(SkyKey skyKey, Environment env) } } + @Nullable + private RepositoryDirectoryValue tryGettingValueUsingVendoredRepo( + Environment env, + Rule rule, + Path repoRoot, + RepositoryName repositoryName, + RepositoryFunction handler, + DigestWriter digestWriter) + throws RepositoryFunctionException, InterruptedException { + Path vendorPath = VENDOR_DIRECTORY.get(env).get(); + Path vendorRepoPath = vendorPath.getRelative(repositoryName.getName()); + if (vendorRepoPath.exists()) { + Path vendorMarker = vendorPath.getChild("@" + repositoryName.getName() + ".marker"); + boolean isVendorRepoUpToDate = + digestWriter.areRepositoryAndMarkerFileConsistent(handler, env, vendorMarker) != null; + if (env.valuesMissing()) { + return null; + } + // If our repo is up-to-date, or this is an offline build (--nofetch), then the vendored repo + // is used. + if (isVendorRepoUpToDate || (!IS_VENDOR_COMMAND.get(env).booleanValue() && !isFetch.get())) { + if (!isVendorRepoUpToDate) { // If the repo is out-of-date, show a warning + env.getListener() + .handle( + Event.warn( + rule.getLocation(), + String.format( + "Vendored repository '%s' is out-of-date and fetching is disabled." + + " Run build without the '--nofetch' option or run" + + " `bazel vendor` to update it", + rule.getName()))); + } + return setupOverride(vendorRepoPath.asFragment(), env, repoRoot, repositoryName.getName()); + } else if (!IS_VENDOR_COMMAND.get(env).booleanValue()) { // build command & fetch enabled + // We will continue fetching but warn the user that we are not using the vendored repo + env.getListener() + .handle( + Event.warn( + rule.getLocation(), + String.format( + "Vendored repository '%s' is out-of-date. The up-to-date version will" + + " be fetched into the external cache and used. To update the repo" + + " in the vendor directory, run 'bazel vendor'", + rule.getName()))); + } + } else if (!isFetch.get()) { // repo not vendored & fetching is disabled (--nofetch) + throw new RepositoryFunctionException( + new IOException( + "Vendored repository " + + repositoryName.getName() + + " not found under the vendor directory and fetching is disabled." + + " To fix run 'bazel vendor' or build without the '--nofetch'"), + Transience.TRANSIENT); + } + return null; + } + @Nullable private Rule getRepositoryRule( Environment env, RepositoryName repositoryName, StarlarkSemantics starlarkSemantics) diff --git a/src/test/py/bazel/bzlmod/bazel_vendor_test.py b/src/test/py/bazel/bzlmod/bazel_vendor_test.py index df7173a0584816..a02743d1b340ed 100644 --- a/src/test/py/bazel/bzlmod/bazel_vendor_test.py +++ b/src/test/py/bazel/bzlmod/bazel_vendor_test.py @@ -179,7 +179,12 @@ def testBuildingWithVendoredRepos(self): # Empty external & build with vendor self.RunBazel(['clean', '--expunge']) - self.RunBazel(['build', '@aaa//:all', '--vendor_dir=vendor']) + _, _, stderr = self.RunBazel(['build', '@aaa//:all', '--vendor_dir=vendor']) + self.assertNotIn( + "Vendored repository '_main~ext~justRepo' is out-of-date.", + '\n'.join(stderr), + ) + # Assert repo aaa in {OUTPUT_BASE}/external is a symlink (junction on # windows, this validates it was created from vendor and not fetched)= _, stdout, _ = self.RunBazel(['info', 'output_base']) @@ -250,7 +255,7 @@ def testIgnoreFromVendoring(self): self.assertNotIn('_main~ext~localRepo', repos_vendored) self.assertNotIn('_main~ext~configRepo', repos_vendored) - def testOutOfDateVendoredRepo(self): + def testBuildingOutOfDateVendoredRepo(self): self.ScratchFile( 'MODULE.bazel', [ @@ -283,7 +288,7 @@ def testOutOfDateVendoredRepo(self): "WARNING: : Vendored repository '_main~ext~justRepo' is" ' out-of-date. The up-to-date version will be fetched into the external' ' cache and used. To update the repo in the vendor directory, run' - " 'bazel vendor' with the directory flag", + " 'bazel vendor'", stderr, ) @@ -313,7 +318,7 @@ def testOutOfDateVendoredRepo(self): "WARNING: : Vendored repository '_main~ext~justRepo' is" ' out-of-date. The up-to-date version will be fetched into the external' ' cache and used. To update the repo in the vendor directory, run' - " 'bazel vendor' with the directory flag", + " 'bazel vendor'", stderr, ) _, stdout, _ = self.RunBazel(['info', 'output_base']) @@ -329,7 +334,87 @@ def testOutOfDateVendoredRepo(self): "WARNING: : Vendored repository '_main~ext~justRepo' is" ' out-of-date. The up-to-date version will be fetched into the external' ' cache and used. To update the repo in the vendor directory, run' - " 'bazel vendor' with the directory flag", + " 'bazel vendor'", + stderr, + ) + + def testBuildingVendoredRepoInOfflineMode(self): + self.ScratchFile( + 'MODULE.bazel', + [ + 'ext = use_extension("extension.bzl", "ext")', + 'use_repo(ext, "venRepo")', + ], + ) + self.ScratchFile( + 'extension.bzl', + [ + 'def _repo_rule_impl(ctx):', + ' ctx.file("WORKSPACE")', + ' ctx.file("BUILD", "filegroup(name=\'lala\')")', + 'repo_rule = repository_rule(implementation=_repo_rule_impl)', + '', + 'def _ext_impl(ctx):', + ' repo_rule(name="venRepo")', + 'ext = module_extension(implementation=_ext_impl)', + ], + ) + self.ScratchFile('BUILD') + + # Vendor, assert and build with no problems + self.RunBazel(['vendor', '--vendor_dir=vendor']) + self.assertIn('_main~ext~venRepo', os.listdir(self._test_cwd + '/vendor')) + + # Make updates in repo definition + self.ScratchFile( + 'MODULE.bazel', + [ + 'ext = use_extension("extension.bzl", "ext")', + 'use_repo(ext, "venRepo")', + 'use_repo(ext, "noVenRepo")', + ], + ) + self.ScratchFile( + 'extension.bzl', + [ + 'def _repo_rule_impl(ctx):', + ' ctx.file("WORKSPACE")', + ' ctx.file("BUILD", "filegroup(name=\'haha\')")', + 'repo_rule = repository_rule(implementation=_repo_rule_impl)', + '', + 'def _ext_impl(ctx):', + ' repo_rule(name="venRepo")', + ' repo_rule(name="noVenRepo")', + 'ext = module_extension(implementation=_ext_impl)', + ], + ) + + # Building a repo that is not vendored in offline mode, should fail + _, _, stderr = self.RunBazel( + ['build', '@noVenRepo//:all', '--vendor_dir=vendor', '--nofetch'], + allow_failure=True, + ) + self.assertIn( + 'ERROR: Vendored repository _main~ext~noVenRepo not found under the' + " vendor directory and fetching is disabled. To fix run 'bazel" + " vendor' or build without the '--nofetch'", + stderr, + ) + + # Building out-of-date repo in offline mode, should build the out-dated one + # and emit a warning + _, _, stderr = self.RunBazel( + ['build', '@venRepo//:all', '--vendor_dir=vendor', '--nofetch'], + ) + self.assertIn( + "WARNING: : Vendored repository '_main~ext~venRepo' is" + ' out-of-date and fetching is disabled. Run build without the' + " '--nofetch' option or run `bazel vendor` to update it", + stderr, + ) + # Assert the out-dated repo is the one built with + self.assertIn( + 'Target @@_main~ext~venRepo//:lala up-to-date (nothing to build)', stderr, )