| // Copyright 2024 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. | |
| package com.google.devtools.build.lib.skyframe; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.Sets; | |
| import com.google.devtools.build.lib.actions.ThreadStateReceiver; | |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; | |
| import com.google.devtools.build.lib.io.FileSymlinkException; | |
| import com.google.devtools.build.lib.io.InconsistentFilesystemException; | |
| import com.google.devtools.build.lib.packages.BuildFileNotFoundException; | |
| import com.google.devtools.build.lib.packages.CachingPackageLocator; | |
| import com.google.devtools.build.lib.packages.Globber; | |
| import com.google.devtools.build.lib.packages.GlobberUtils; | |
| import com.google.devtools.build.lib.packages.NonSkyframeGlobber; | |
| import com.google.devtools.build.lib.packages.Package; | |
| import com.google.devtools.build.lib.packages.PackageFactory; | |
| import com.google.devtools.build.lib.skyframe.GlobsValue.GlobRequest; | |
| import com.google.devtools.build.lib.vfs.Root; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import java.io.IOException; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.Set; | |
| import java.util.concurrent.Semaphore; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| import java.util.concurrent.atomic.AtomicInteger; | |
| import java.util.concurrent.atomic.AtomicReference; | |
| import java.util.function.Function; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Computes the {@link PackageValue} which depends on a single GLOBS node. | |
| * | |
| * <p>{@link PackageFunctionWithSingleGlobsDep} subclass is created when the globbing strategy is | |
| * {@link | |
| * com.google.devtools.build.lib.skyframe.PackageFunction.GlobbingStrategy#SINGLE_GLOBS_HYBRID}. All | |
| * globs defined in the package's {@code BUILD} file are combined into a single GLOBS node. | |
| */ | |
| final class PackageFunctionWithSingleGlobsDep extends PackageFunction { | |
| PackageFunctionWithSingleGlobsDep( | |
| PackageFactory packageFactory, | |
| CachingPackageLocator pkgLocator, | |
| AtomicBoolean showLoadingProgress, | |
| AtomicInteger numPackagesSuccessfullyLoaded, | |
| @Nullable BzlLoadFunction bzlLoadFunctionForInlining, | |
| @Nullable PackageProgressReceiver packageProgress, | |
| ActionOnIOExceptionReadingBuildFile actionOnIoExceptionReadingBuildFile, | |
| boolean shouldUseRepoDotBazel, | |
| Function<SkyKey, ThreadStateReceiver> threadStateReceiverFactoryForMetrics, | |
| AtomicReference<Semaphore> cpuBoundSemaphore) { | |
| super( | |
| packageFactory, | |
| pkgLocator, | |
| showLoadingProgress, | |
| numPackagesSuccessfullyLoaded, | |
| bzlLoadFunctionForInlining, | |
| packageProgress, | |
| actionOnIoExceptionReadingBuildFile, | |
| shouldUseRepoDotBazel, | |
| threadStateReceiverFactoryForMetrics, | |
| cpuBoundSemaphore); | |
| } | |
| private static final class LoadedPackageWithGlobRequests extends LoadedPackage { | |
| private final ImmutableSet<GlobRequest> globRequests; | |
| private LoadedPackageWithGlobRequests( | |
| Package.Builder builder, long loadTimeNanos, ImmutableSet<GlobRequest> globRequests) { | |
| super(builder, loadTimeNanos); | |
| this.globRequests = globRequests; | |
| } | |
| } | |
| /** | |
| * Performs non-Skyframe globbing operations and prepares the {@link GlobRequest}s set for | |
| * subsequent Skyframe-based globbing. | |
| */ | |
| private static final class GlobsGlobber implements Globber { | |
| private final NonSkyframeGlobber nonSkyframeGlobber; | |
| private final Set<GlobRequest> globRequests = Sets.newConcurrentHashSet(); | |
| private GlobsGlobber(NonSkyframeGlobber nonSkyframeGlobber) { | |
| this.nonSkyframeGlobber = nonSkyframeGlobber; | |
| } | |
| @Override | |
| public Token runAsync( | |
| List<String> includes, List<String> excludes, Operation operation, boolean allowEmpty) | |
| throws BadGlobException { | |
| for (String pattern : includes) { | |
| try { | |
| globRequests.add(GlobRequest.create(pattern, operation)); | |
| } catch (InvalidGlobPatternException e) { | |
| throw new BadGlobException(e.getMessage()); | |
| } | |
| } | |
| NonSkyframeGlobber.Token nonSkyframeGlobToken = | |
| nonSkyframeGlobber.runAsync(includes, excludes, operation, allowEmpty); | |
| return new GlobsToken(nonSkyframeGlobToken, operation, allowEmpty); | |
| } | |
| @Override | |
| public List<String> fetchUnsorted(Token token) | |
| throws BadGlobException, IOException, InterruptedException { | |
| Set<String> matches = Sets.newHashSet(); | |
| matches.addAll( | |
| nonSkyframeGlobber.fetchUnsorted(((GlobsToken) token).nonSkyframeGlobberIncludesToken)); | |
| List<String> result = new ArrayList<>(matches); | |
| if (!((GlobsToken) token).allowEmpty && result.isEmpty()) { | |
| GlobberUtils.throwBadGlobExceptionAllExcluded(((GlobsToken) token).globberOperation); | |
| } | |
| return result; | |
| } | |
| @Override | |
| public void onInterrupt() { | |
| nonSkyframeGlobber.onInterrupt(); | |
| } | |
| @Override | |
| public void onCompletion() { | |
| nonSkyframeGlobber.onCompletion(); | |
| } | |
| /** | |
| * Returns an {@link ImmutableSet} of all package's globs, which will be used to construct | |
| * {@link GlobsValue.Key} to be requested in Skyframe downstream. | |
| * | |
| * <p>An empty {@link ImmutableSet} is returned if there is no glob is defined in the package's | |
| * BUILD file. Hence, requesting GLOBS in Skyframe is skipped downstream. | |
| */ | |
| public ImmutableSet<GlobRequest> getGlobRequests() { | |
| return ImmutableSet.copyOf(globRequests); | |
| } | |
| private static class GlobsToken extends Globber.Token { | |
| private final NonSkyframeGlobber.Token nonSkyframeGlobberIncludesToken; | |
| private final Globber.Operation globberOperation; | |
| private final boolean allowEmpty; | |
| private GlobsToken( | |
| NonSkyframeGlobber.Token nonSkyframeGlobberIncludesToken, | |
| Globber.Operation globberOperation, | |
| boolean allowEmpty) { | |
| this.nonSkyframeGlobberIncludesToken = nonSkyframeGlobberIncludesToken; | |
| this.globberOperation = globberOperation; | |
| this.allowEmpty = allowEmpty; | |
| } | |
| } | |
| } | |
| @Override | |
| protected void handleGlobDepsAndPropagateFilesystemExceptions( | |
| PackageIdentifier packageIdentifier, | |
| Root packageRoot, | |
| LoadedPackage loadedPackage, | |
| Environment env, | |
| boolean packageWasInError) | |
| throws InterruptedException, InternalInconsistentFilesystemException, FileSymlinkException { | |
| ImmutableSet<GlobRequest> globRequests = | |
| ((LoadedPackageWithGlobRequests) loadedPackage).globRequests; | |
| if (globRequests.isEmpty()) { | |
| return; | |
| } | |
| GlobsValue.Key globsKey = GlobsValue.key(packageIdentifier, packageRoot, globRequests); | |
| try { | |
| env.getValueOrThrow(globsKey, IOException.class, BuildFileNotFoundException.class); | |
| } catch (InconsistentFilesystemException e) { | |
| throw new InternalInconsistentFilesystemException(packageIdentifier, e); | |
| } catch (FileSymlinkException e) { | |
| // Please note that GlobsFunction or its deps FileFunction throws the first | |
| // `FileSymlinkException` discovered, which is consistent with how | |
| // PackageFunctionWithMultipleGlobDeps#handleGlobDepsAndPropagateFilesystemExceptions handles | |
| // FileSymlinkException caught. | |
| throw e; | |
| } catch (IOException | BuildFileNotFoundException e) { | |
| maybeThrowFilesystemInconsistency(packageIdentifier, e, packageWasInError); | |
| } | |
| } | |
| @Override | |
| protected GlobsGlobber makeGlobber( | |
| NonSkyframeGlobber nonSkyframeGlobber, | |
| PackageIdentifier packageId, | |
| Root packageRoot, | |
| Environment env) { | |
| return new GlobsGlobber(nonSkyframeGlobber); | |
| } | |
| @Override | |
| protected LoadedPackage newLoadedPackage( | |
| Package.Builder packageBuilder, @Nullable Globber globber, long loadTimeNanos) { | |
| return new LoadedPackageWithGlobRequests( | |
| packageBuilder, loadTimeNanos, ((GlobsGlobber) globber).getGlobRequests()); | |
| } | |
| } |