| // Copyright 2015 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 static com.google.common.util.concurrent.Futures.immediateCancelledFuture; |
| import static com.google.common.util.concurrent.Futures.immediateFailedFuture; |
| import static com.google.common.util.concurrent.MoreExecutors.directExecutor; |
| |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import com.google.common.util.concurrent.Futures; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| import com.google.common.util.concurrent.MoreExecutors; |
| import com.google.common.util.concurrent.Uninterruptibles; |
| import com.google.devtools.build.lib.cmdline.BatchCallback; |
| import com.google.devtools.build.lib.cmdline.BatchCallback.SafeBatchCallback; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.cmdline.QueryExceptionMarkerInterface; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.cmdline.ResolvedTargets; |
| import com.google.devtools.build.lib.cmdline.TargetParsingException; |
| import com.google.devtools.build.lib.cmdline.TargetPatternResolver; |
| import com.google.devtools.build.lib.concurrent.MultisetSemaphore; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.ExtendedEventHandler; |
| import com.google.devtools.build.lib.io.InconsistentFilesystemException; |
| import com.google.devtools.build.lib.io.ProcessPackageDirectoryException; |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| import com.google.devtools.build.lib.packages.NoSuchThingException; |
| import com.google.devtools.build.lib.packages.Package; |
| import com.google.devtools.build.lib.packages.Target; |
| import com.google.devtools.build.lib.pkgcache.FilteringPolicies; |
| import com.google.devtools.build.lib.pkgcache.FilteringPolicy; |
| import com.google.devtools.build.lib.pkgcache.RecursivePackageProvider; |
| import com.google.devtools.build.lib.pkgcache.TargetPatternResolverUtil; |
| import com.google.devtools.build.lib.query2.engine.QueryException; |
| import com.google.devtools.build.lib.server.FailureDetails.TargetPatterns; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Future; |
| import javax.annotation.Nullable; |
| |
| /** A {@link TargetPatternResolver} backed by a {@link RecursivePackageProvider}. */ |
| @ThreadCompatible |
| public final class RecursivePackageProviderBackedTargetPatternResolver |
| extends TargetPatternResolver<Target> { |
| |
| // TODO(janakr): Move this to a more generic place and unify with SkyQueryEnvironment's value? |
| static final int MAX_PACKAGES_BULK_GET = 1000; |
| |
| protected final FilteringPolicy policy; |
| private final RecursivePackageProvider recursivePackageProvider; |
| private final ExtendedEventHandler eventHandler; |
| private final MultisetSemaphore<PackageIdentifier> packageSemaphore; |
| private final PackageIdentifierBatchingCallback.Factory packageIdentifierBatchingCallbackFactory; |
| |
| public RecursivePackageProviderBackedTargetPatternResolver( |
| RecursivePackageProvider recursivePackageProvider, |
| ExtendedEventHandler eventHandler, |
| FilteringPolicy policy, |
| MultisetSemaphore<PackageIdentifier> packageSemaphore, |
| PackageIdentifierBatchingCallback.Factory packageIdentifierBatchingCallbackFactory) { |
| this.recursivePackageProvider = recursivePackageProvider; |
| this.eventHandler = eventHandler; |
| this.policy = policy; |
| this.packageSemaphore = packageSemaphore; |
| this.packageIdentifierBatchingCallbackFactory = packageIdentifierBatchingCallbackFactory; |
| } |
| |
| @Override |
| public void warn(String msg) { |
| eventHandler.handle(Event.warn(msg)); |
| } |
| |
| /** |
| * Gets a {@link Package} from the {@link RecursivePackageProvider}. May return a {@link Package} |
| * that has errors. |
| */ |
| private Package getPackage(PackageIdentifier pkgIdentifier) |
| throws NoSuchPackageException, InterruptedException { |
| return recursivePackageProvider.getPackage(eventHandler, pkgIdentifier); |
| } |
| |
| private Map<PackageIdentifier, Package> bulkGetPackages(Iterable<PackageIdentifier> pkgIds) |
| throws NoSuchPackageException, InterruptedException { |
| return recursivePackageProvider.bulkGetPackages(pkgIds); |
| } |
| |
| @Override |
| @Nullable |
| public Target getTargetOrNull(Label label) |
| throws InterruptedException, InconsistentFilesystemException { |
| try { |
| if (!isPackage(label.getPackageIdentifier())) { |
| return null; |
| } |
| return recursivePackageProvider.getTarget(eventHandler, label); |
| } catch (NoSuchThingException e) { |
| return null; |
| } |
| } |
| |
| @Override |
| public ResolvedTargets<Target> getExplicitTarget(Label label) |
| throws TargetParsingException, InterruptedException { |
| try { |
| Target target = recursivePackageProvider.getTarget(eventHandler, label); |
| return policy.shouldRetain(target, true) |
| ? ResolvedTargets.of(target) |
| : ResolvedTargets.empty(); |
| } catch (NoSuchThingException e) { |
| throw new TargetParsingException(e.getMessage(), e, e.getDetailedExitCode()); |
| } |
| } |
| |
| @Override |
| public Collection<Target> getTargetsInPackage( |
| String originalPattern, PackageIdentifier packageIdentifier, boolean rulesOnly) |
| throws TargetParsingException, InterruptedException { |
| FilteringPolicy actualPolicy = |
| rulesOnly ? FilteringPolicies.and(FilteringPolicies.RULES_ONLY, policy) : policy; |
| try { |
| Package pkg = getPackage(packageIdentifier); |
| if (pkg.containsErrors()) { |
| eventHandler.handle( |
| Event.error( |
| "package contains errors: " |
| + pkg.getNameFragment() |
| + ": " |
| + pkg.getFailureDetail().getMessage())); |
| } |
| return TargetPatternResolverUtil.resolvePackageTargets(pkg, actualPolicy); |
| } catch (NoSuchThingException e) { |
| String message = |
| TargetPatternResolverUtil.getParsingErrorMessage(e.getMessage(), originalPattern); |
| throw new TargetParsingException(message, e, e.getDetailedExitCode()); |
| } |
| } |
| |
| private Map<PackageIdentifier, Collection<Target>> bulkGetTargetsInPackage( |
| String originalPattern, Iterable<PackageIdentifier> pkgIds, FilteringPolicy policy) |
| throws InterruptedException { |
| try { |
| Map<PackageIdentifier, Package> pkgs = bulkGetPackages(pkgIds); |
| if (pkgs.size() != Iterables.size(pkgIds)) { |
| throw new IllegalStateException( |
| "Bulk package retrieval missing results: " |
| + Sets.difference(ImmutableSet.copyOf(pkgIds), pkgs.keySet())); |
| } |
| ImmutableMap.Builder<PackageIdentifier, Collection<Target>> result = ImmutableMap.builder(); |
| for (PackageIdentifier pkgId : pkgIds) { |
| Package pkg = pkgs.get(pkgId); |
| if (pkg.containsErrors()) { |
| eventHandler.handle( |
| Event.error( |
| "package contains errors: " |
| + pkg.getNameFragment() |
| + ": " |
| + pkg.getFailureDetail().getMessage())); |
| } |
| result.put(pkgId, TargetPatternResolverUtil.resolvePackageTargets(pkg, policy)); |
| } |
| return result.buildOrThrow(); |
| } catch (NoSuchThingException e) { |
| String message = |
| TargetPatternResolverUtil.getParsingErrorMessage(e.getMessage(), originalPattern); |
| throw new IllegalStateException( |
| "Mismatch: Expected given pkgIds to correspond to valid Packages. " + message, e); |
| } |
| } |
| |
| @Override |
| public boolean isPackage(PackageIdentifier packageIdentifier) |
| throws InterruptedException, InconsistentFilesystemException { |
| return recursivePackageProvider.isPackage(eventHandler, packageIdentifier); |
| } |
| |
| @Override |
| public String getTargetKind(Target target) { |
| return target.getTargetKind(); |
| } |
| |
| @Override |
| public <E extends Exception & QueryExceptionMarkerInterface> void findTargetsBeneathDirectory( |
| final RepositoryName repository, |
| final String originalPattern, |
| String directory, |
| boolean rulesOnly, |
| ImmutableSet<PathFragment> forbiddenSubdirectories, |
| ImmutableSet<PathFragment> excludedSubdirectories, |
| BatchCallback<Target, E> callback, |
| Class<E> exceptionClass) |
| throws TargetParsingException, E, InterruptedException, ProcessPackageDirectoryException { |
| ListenableFuture<Void> future; |
| try { |
| future = |
| findTargetsBeneathDirectoryAsyncImpl( |
| repository, |
| originalPattern, |
| directory, |
| rulesOnly, |
| forbiddenSubdirectories, |
| excludedSubdirectories, |
| callback, |
| MoreExecutors.newDirectExecutorService()); |
| } catch (QueryException e) { |
| Throwables.propagateIfPossible(e, exceptionClass); |
| throw new IllegalStateException(e); |
| } catch (NoSuchPackageException e) { |
| // Can happen during a Skyframe no-keep-going evaluation. |
| throw new TargetParsingException( |
| "error loading package under directory '" + directory + "': " + e.getMessage(), |
| e, |
| e.getDetailedExitCode()); |
| } |
| if (!isSuccessful(future)) { |
| // Don't get the future if it finished successfully: all that will do is throw an |
| // interrupted exception if this thread was interrupted, but that's not helpful for a done |
| // future. |
| try { |
| future.get(); |
| } catch (ExecutionException e) { |
| Throwables.propagateIfPossible(e.getCause(), InterruptedException.class, exceptionClass); |
| throw new IllegalStateException(e.getCause()); |
| } |
| } |
| } |
| |
| @Override |
| public <E extends Exception & QueryExceptionMarkerInterface> |
| ListenableFuture<Void> findTargetsBeneathDirectoryAsync( |
| RepositoryName repository, |
| String originalPattern, |
| String directory, |
| boolean rulesOnly, |
| ImmutableSet<PathFragment> forbiddenSubdirectories, |
| ImmutableSet<PathFragment> excludedSubdirectories, |
| BatchCallback<Target, E> callback, |
| Class<E> exceptionClass, |
| ListeningExecutorService executor) { |
| try { |
| return findTargetsBeneathDirectoryAsyncImpl( |
| repository, |
| originalPattern, |
| directory, |
| rulesOnly, |
| forbiddenSubdirectories, |
| excludedSubdirectories, |
| callback, |
| executor); |
| } catch (TargetParsingException e) { |
| return immediateFailedFuture(e); |
| } catch (InterruptedException e) { |
| return immediateCancelledFuture(); |
| } catch (ProcessPackageDirectoryException | NoSuchPackageException e) { |
| throw new IllegalStateException( |
| "Async find targets beneath directory isn't called from within Skyframe: traversing " |
| + directory |
| + " for " |
| + originalPattern, |
| e); |
| } catch (QueryException e) { |
| if (exceptionClass.isInstance(e)) { |
| return immediateFailedFuture(e); |
| } |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| /** |
| * The returned future may throw {@link QueryException} (if {@code E} is {@link QueryException}) |
| * or {@link InterruptedException} on retrieval, but no other exceptions. |
| */ |
| private <E extends Exception & QueryExceptionMarkerInterface> |
| ListenableFuture<Void> findTargetsBeneathDirectoryAsyncImpl( |
| RepositoryName repository, |
| String pattern, |
| String directory, |
| boolean rulesOnly, |
| ImmutableSet<PathFragment> forbiddenSubdirectories, |
| ImmutableSet<PathFragment> excludedSubdirectories, |
| BatchCallback<Target, E> callback, |
| ListeningExecutorService executor) |
| throws TargetParsingException, QueryException, InterruptedException, |
| ProcessPackageDirectoryException, NoSuchPackageException { |
| FilteringPolicy actualPolicy = |
| rulesOnly ? FilteringPolicies.and(FilteringPolicies.RULES_ONLY, policy) : policy; |
| |
| ArrayList<ListenableFuture<Void>> futures = new ArrayList<>(); |
| SafeBatchCallback<PackageIdentifier> getPackageTargetsCallback = |
| (pkgIdBatch) -> |
| futures.add( |
| executor.submit( |
| new GetTargetsInPackagesTask<>(pkgIdBatch, pattern, actualPolicy, callback))); |
| |
| PathFragment pathFragment = TargetPatternResolverUtil.getPathFragment(directory); |
| try (PackageIdentifierBatchingCallback batchingCallback = |
| packageIdentifierBatchingCallbackFactory.create( |
| getPackageTargetsCallback, MAX_PACKAGES_BULK_GET)) { |
| recursivePackageProvider.streamPackagesUnderDirectory( |
| batchingCallback, |
| eventHandler, |
| repository, |
| pathFragment, |
| forbiddenSubdirectories, |
| excludedSubdirectories); |
| } |
| if (futures.isEmpty()) { |
| throw new TargetParsingException( |
| "no targets found beneath '" + pathFragment + "'", TargetPatterns.Code.TARGETS_MISSING); |
| } |
| return Futures.whenAllSucceed(futures).call(() -> null, directExecutor()); |
| } |
| |
| /** |
| * Task to get all matching targets in the given packages, filter them, and pass them to the |
| * target batch callback. |
| */ |
| private class GetTargetsInPackagesTask<E extends Exception & QueryExceptionMarkerInterface> |
| implements Callable<Void> { |
| |
| private final Iterable<PackageIdentifier> packageIdentifiers; |
| private final String originalPattern; |
| private final FilteringPolicy actualPolicy; |
| private final BatchCallback<Target, E> callback; |
| |
| GetTargetsInPackagesTask( |
| Iterable<PackageIdentifier> packageIdentifiers, |
| String originalPattern, |
| FilteringPolicy actualPolicy, |
| BatchCallback<Target, E> callback) { |
| this.packageIdentifiers = packageIdentifiers; |
| this.originalPattern = originalPattern; |
| this.actualPolicy = actualPolicy; |
| this.callback = callback; |
| } |
| |
| @Override |
| public Void call() throws E, InterruptedException { |
| ImmutableSet<PackageIdentifier> pkgIdBatchSet = ImmutableSet.copyOf(packageIdentifiers); |
| packageSemaphore.acquireAll(pkgIdBatchSet); |
| try { |
| Iterable<Collection<Target>> resolvedTargets = |
| RecursivePackageProviderBackedTargetPatternResolver.this |
| .bulkGetTargetsInPackage(originalPattern, packageIdentifiers, actualPolicy) |
| .values(); |
| List<Target> filteredTargets = new ArrayList<>(calculateSize(resolvedTargets)); |
| for (Collection<Target> targets : resolvedTargets) { |
| filteredTargets.addAll(targets); |
| } |
| // TODO(b/121277360): Invoking the callback while holding onto the package |
| // semaphore can lead to deadlocks. |
| // |
| // Also, if the semaphore has a small count, acquireAll can also lead to problems if we |
| // don't batch appropriately. Note: We default to an unbounded semaphore for SkyQuery. |
| // |
| // TODO(b/168142585): Make this code strictly correct in the situation where the semaphore |
| // is bounded. |
| callback.process(filteredTargets); |
| } finally { |
| packageSemaphore.releaseAll(pkgIdBatchSet); |
| } |
| return null; |
| } |
| } |
| |
| private static <T> int calculateSize(Iterable<Collection<T>> resolvedTargets) { |
| int size = 0; |
| for (Collection<T> targets : resolvedTargets) { |
| size += targets.size(); |
| } |
| return size; |
| } |
| |
| /** Inspired by not-yet-open-source futures code. */ |
| private static boolean isSuccessful(Future<?> future) { |
| if (future.isDone() && !future.isCancelled()) { |
| try { |
| Uninterruptibles.getUninterruptibly(future); |
| return true; |
| } catch (ExecutionException | RuntimeException e) { |
| // Fall through. |
| } |
| } |
| return false; |
| } |
| } |