// 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.MoreExecutors.directExecutor; import static com.google.devtools.build.lib.pkgcache.FilteringPolicies.NO_FILTER; import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; 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.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; 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.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.util.BatchCallback; import com.google.devtools.build.lib.util.ThreadSafeBatchCallback; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; /** * A {@link TargetPatternResolver} backed by a {@link RecursivePackageProvider}. */ @ThreadCompatible public class RecursivePackageProviderBackedTargetPatternResolver extends TargetPatternResolver { // TODO(janakr): Move this to a more generic place and unify with SkyQueryEnvironment's value? private static final int MAX_PACKAGES_BULK_GET = 1000; protected final FilteringPolicy policy; private final RecursivePackageProvider recursivePackageProvider; private final ExtendedEventHandler eventHandler; private final MultisetSemaphore packageSemaphore; public RecursivePackageProviderBackedTargetPatternResolver( RecursivePackageProvider recursivePackageProvider, ExtendedEventHandler eventHandler, FilteringPolicy policy, MultisetSemaphore packageSemaphore) { this.recursivePackageProvider = recursivePackageProvider; this.eventHandler = eventHandler; this.policy = policy; this.packageSemaphore = packageSemaphore; } @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 bulkGetPackages(Iterable pkgIds) throws NoSuchPackageException, InterruptedException { return recursivePackageProvider.bulkGetPackages(pkgIds); } @Override public Target getTargetOrNull(Label label) throws InterruptedException { try { if (!isPackage(label.getPackageIdentifier())) { return null; } return recursivePackageProvider.getTarget(eventHandler, label); } catch (NoSuchThingException e) { return null; } } @Override public ResolvedTargets 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); } } @Override public ResolvedTargets 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); return TargetPatternResolverUtil.resolvePackageTargets(pkg, actualPolicy); } catch (NoSuchThingException e) { String message = TargetPatternResolverUtil.getParsingErrorMessage( e.getMessage(), originalPattern); throw new TargetParsingException(message, e); } } private Map> bulkGetTargetsInPackage( String originalPattern, Iterable pkgIds, FilteringPolicy policy) throws InterruptedException { try { Map 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> result = ImmutableMap.builder(); for (PackageIdentifier pkgId : pkgIds) { Package pkg = pkgs.get(pkgId); result.put(pkgId, TargetPatternResolverUtil.resolvePackageTargets(pkg, policy)); } return result.build(); } 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 { return recursivePackageProvider.isPackage(eventHandler, packageIdentifier); } @Override public String getTargetKind(Target target) { return target.getTargetKind(); } /** * A {@link ThreadSafeBatchCallback} that trivially delegates to a {@link BatchCallback} in a * synchronized manner. */ private static class SynchronizedBatchCallback implements ThreadSafeBatchCallback { private final BatchCallback delegate; public SynchronizedBatchCallback(BatchCallback delegate) { this.delegate = delegate; } @Override public synchronized void process(Iterable partialResult) throws E, InterruptedException { delegate.process(partialResult); } } @Override public void findTargetsBeneathDirectory( final RepositoryName repository, final String originalPattern, String directory, boolean rulesOnly, ImmutableSet blacklistedSubdirectories, ImmutableSet excludedSubdirectories, BatchCallback callback, Class exceptionClass) throws TargetParsingException, E, InterruptedException { try { findTargetsBeneathDirectoryAsyncImpl( repository, originalPattern, directory, rulesOnly, blacklistedSubdirectories, excludedSubdirectories, new SynchronizedBatchCallback(callback), MoreExecutors.newDirectExecutorService()).get(); } catch (ExecutionException e) { Throwable cause = e.getCause(); Throwables.propagateIfPossible(cause, TargetParsingException.class, exceptionClass); throw new IllegalStateException(e.getCause()); } } @Override public ListenableFuture findTargetsBeneathDirectoryAsync( RepositoryName repository, String originalPattern, String directory, boolean rulesOnly, ImmutableSet blacklistedSubdirectories, ImmutableSet excludedSubdirectories, ThreadSafeBatchCallback callback, Class exceptionClass, ListeningExecutorService executor) { return findTargetsBeneathDirectoryAsyncImpl( repository, originalPattern, directory, rulesOnly, blacklistedSubdirectories, excludedSubdirectories, callback, executor); } private ListenableFuture findTargetsBeneathDirectoryAsyncImpl( final RepositoryName repository, final String originalPattern, String directory, boolean rulesOnly, ImmutableSet blacklistedSubdirectories, ImmutableSet excludedSubdirectories, final ThreadSafeBatchCallback callback, ListeningExecutorService executor) { final FilteringPolicy actualPolicy = rulesOnly ? FilteringPolicies.and(FilteringPolicies.RULES_ONLY, policy) : policy; final PathFragment pathFragment; Iterable packagesUnderDirectory; try { pathFragment = TargetPatternResolverUtil.getPathFragment(directory); packagesUnderDirectory = recursivePackageProvider.getPackagesUnderDirectory( eventHandler, repository, pathFragment, blacklistedSubdirectories, excludedSubdirectories); } catch (TargetParsingException e) { return Futures.immediateFailedFuture(e); } catch (InterruptedException e) { return Futures.immediateCancelledFuture(); } Iterable pkgIds = Iterables.transform(packagesUnderDirectory, new Function() { @Override public PackageIdentifier apply(PathFragment path) { return PackageIdentifier.create(repository, path); } }); final AtomicBoolean foundTarget = new AtomicBoolean(false); // For very large sets of packages, we may not want to process all of them at once, so we split // into batches. List> partitions = ImmutableList.copyOf(Iterables.partition(pkgIds, MAX_PACKAGES_BULK_GET)); ArrayList> futures = new ArrayList<>(partitions.size()); for (final Iterable pkgIdBatch : partitions) { futures.add( executor.submit( () -> { ImmutableSet pkgIdBatchSet = ImmutableSet.copyOf(pkgIdBatch); packageSemaphore.acquireAll(pkgIdBatchSet); try { Iterable> resolvedTargets = bulkGetTargetsInPackage(originalPattern, pkgIdBatch, NO_FILTER).values(); List filteredTargets = new ArrayList<>(calculateSize(resolvedTargets)); for (ResolvedTargets targets : resolvedTargets) { for (Target target : targets.getTargets()) { // Perform the no-targets-found check before applying the filtering policy // so we only return the error if the input directory's subtree really // contains no targets. foundTarget.set(true); if (actualPolicy.shouldRetain(target, false)) { filteredTargets.add(target); } } } // TODO(bazel-core): 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. // Although we default to an unbounded semaphore for SkyQuery and this is an // unreported issue, consider refactoring so that the code is strictly correct. callback.process(filteredTargets); } finally { packageSemaphore.releaseAll(pkgIdBatchSet); } return null; })); } return Futures.whenAllSucceed(futures) .call( () -> { if (!foundTarget.get()) { throw new TargetParsingException("no targets found beneath '" + pathFragment + "'"); } return null; }, directExecutor()); } private static int calculateSize(Iterable> resolvedTargets) { int size = 0; for (ResolvedTargets targets : resolvedTargets) { size += targets.getTargets().size(); } return size; } }