| /* |
| * 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(); |
| } |
| } |