blob: 10ab8aa0992f5c358599499f9b3acae1de706a28 [file] [log] [blame]
/*
* Copyright 2017 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.idea.blaze.base.sync.sharding;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.base.async.FutureUtil;
import com.google.idea.blaze.base.async.process.ExternalTask;
import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
import com.google.idea.blaze.base.bazel.BuildSystemProvider;
import com.google.idea.blaze.base.command.BlazeCommand;
import com.google.idea.blaze.base.command.BlazeCommandName;
import com.google.idea.blaze.base.command.BlazeFlags;
import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
import com.google.idea.blaze.base.model.primitives.Kind;
import com.google.idea.blaze.base.model.primitives.TargetExpression;
import com.google.idea.blaze.base.model.primitives.WorkspacePath;
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
import com.google.idea.blaze.base.prefetch.PrefetchService;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.scope.output.StatusOutput;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.sync.aspects.BuildResult;
import com.google.idea.blaze.base.sync.aspects.BuildResult.Status;
import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
import com.google.idea.blaze.base.sync.sharding.QueryResultLineProcessor.RuleTypeAndLabel;
import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
import com.google.idea.common.experiments.BoolExperiment;
import com.intellij.openapi.project.Project;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** Expands wildcard target patterns into individual blaze targets. */
public class WildcardTargetExpander {
private static final BoolExperiment filterByRuleType =
new BoolExperiment("blaze.build.filter.by.rule.type", true);
static class ExpandedTargetsResult {
final List<TargetExpression> singleTargets;
final BuildResult buildResult;
ExpandedTargetsResult(List<TargetExpression> singleTargets, BuildResult buildResult) {
this.singleTargets = singleTargets;
this.buildResult = buildResult;
}
static ExpandedTargetsResult merge(ExpandedTargetsResult first, ExpandedTargetsResult second) {
BuildResult buildResult = BuildResult.combine(first.buildResult, second.buildResult);
List<TargetExpression> targets =
ImmutableList.<TargetExpression>builder()
.addAll(first.singleTargets)
.addAll(second.singleTargets)
.build();
return new ExpandedTargetsResult(targets, buildResult);
}
}
/**
* Expand recursive wildcard blaze target patterns into single-package wildcard patterns, via a
* file system traversal.
*
* <p>Exclude target patterns (beginning with '-') are not expanded.
*
* <p>Returns null if operation failed or was cancelled.
*/
@Nullable
static Map<TargetExpression, List<TargetExpression>> expandToNonRecursiveWildcardTargets(
Project project,
BlazeContext context,
WorkspacePathResolver pathResolver,
List<WildcardTargetPattern> wildcardPatterns) {
Set<WildcardTargetPattern> excludes =
wildcardPatterns
.stream()
.filter(WildcardTargetPattern::isExcluded)
.collect(Collectors.toSet());
Predicate<WorkspacePath> excludePredicate =
workspacePath ->
excludes.stream().anyMatch(pattern -> pattern.coversPackage(workspacePath));
List<WildcardTargetPattern> includes = new ArrayList<>(wildcardPatterns);
includes.removeAll(excludes);
Set<File> toPrefetch =
PackageLister.getDirectoriesToPrefetch(pathResolver, includes, excludePredicate);
ListenableFuture<?> prefetchFuture =
PrefetchService.getInstance().prefetchFiles(project, toPrefetch, false);
if (!FutureUtil.waitForFuture(context, prefetchFuture)
.withProgressMessage("Prefetching wildcard target pattern directories...")
.timed("PrefetchingWildcardTargetDirectories")
.onError("Prefetching wildcard target directories failed")
.run()
.success()) {
return null;
}
return PackageLister.expandPackageTargets(
Blaze.getBuildSystemProvider(project), context, pathResolver, includes);
}
/** Runs a sharded blaze query to expand wildcard targets to individual blaze targets */
static ExpandedTargetsResult expandToSingleTargets(
Project project,
BlazeContext context,
WorkspaceRoot workspaceRoot,
ProjectViewSet projectViewSet,
List<TargetExpression> allTargets) {
ShardedTargetList shards =
BlazeBuildTargetSharder.shardTargets(
allTargets, BlazeBuildTargetSharder.PACKAGE_SHARD_SIZE);
ImmutableSet<String> handledRuleTypes = handledRuleTypes(projectViewSet);
ExpandedTargetsResult output = null;
for (int i = 0; i < shards.shardedTargets.size(); i++) {
List<TargetExpression> shard = shards.shardedTargets.get(i);
context.output(
new StatusOutput(
String.format(
"Expanding wildcard target patterns, shard %s of %s",
i + 1, shards.shardedTargets.size())));
ExpandedTargetsResult result =
queryIndividualTargets(project, context, workspaceRoot, handledRuleTypes, shard);
output = output == null ? result : ExpandedTargetsResult.merge(output, result);
if (output.buildResult.status == Status.FATAL_ERROR) {
return output;
}
}
return output;
}
/** Runs a blaze query to expand the input target patterns to individual blaze targets. */
private static ExpandedTargetsResult queryIndividualTargets(
Project project,
BlazeContext context,
WorkspaceRoot workspaceRoot,
ImmutableSet<String> handledRuleTypes,
List<TargetExpression> targetPatterns) {
String query = queryString(targetPatterns);
if (query.isEmpty()) {
// will be empty if there are no non-excluded targets
return new ExpandedTargetsResult(ImmutableList.of(), BuildResult.SUCCESS);
}
BlazeCommand.Builder builder =
BlazeCommand.builder(getBinaryPath(project), BlazeCommandName.QUERY)
.addBlazeFlags(BlazeFlags.KEEP_GOING)
.addBlazeFlags("--output=label_kind")
.addBlazeFlags(queryString(targetPatterns));
ImmutableList.Builder<TargetExpression> output = ImmutableList.builder();
// it's fine to include wildcards here; they're guaranteed not to clash with actual labels.
Set<String> explicitTargets =
targetPatterns.stream().map(TargetExpression::toString).collect(Collectors.toSet());
Predicate<RuleTypeAndLabel> filter =
!filterByRuleType.getValue()
? t -> true
: t -> handledRuleTypes.contains(t.ruleType) || explicitTargets.contains(t.label);
int retVal =
ExternalTask.builder(workspaceRoot)
.addBlazeCommand(builder.build())
.context(context)
.stdout(LineProcessingOutputStream.of(new QueryResultLineProcessor(output, filter)))
.stderr(
LineProcessingOutputStream.of(
new IssueOutputLineProcessor(project, context, workspaceRoot)))
.build()
.run();
BuildResult buildResult = BuildResult.fromExitCode(retVal);
return new ExpandedTargetsResult(output.build(), buildResult);
}
private static ImmutableSet<String> handledRuleTypes(ProjectViewSet projectViewSet) {
return ImmutableSet.copyOf(
LanguageSupport.createWorkspaceLanguageSettings(projectViewSet)
.getAvailableTargetKinds()
.stream()
.map(Kind::toString)
.collect(Collectors.toList()));
}
private static String queryString(List<TargetExpression> targets) {
StringBuilder builder = new StringBuilder();
for (TargetExpression target : targets) {
boolean excluded = target.isExcluded();
if (builder.length() == 0) {
if (excluded) {
continue; // an excluded target at the start of the list has no effect
}
builder.append(target);
} else {
if (excluded) {
builder.append(" - ");
// trim leading '-'
String excludedTarget = target.toString();
builder.append(excludedTarget, 1, excludedTarget.length());
} else {
builder.append(" + ");
builder.append(target);
}
}
}
return builder.toString();
}
private static String getBinaryPath(Project project) {
BuildSystemProvider buildSystemProvider = Blaze.getBuildSystemProvider(project);
return buildSystemProvider.getSyncBinaryPath();
}
}