blob: 5de20c90bb9e566786ff1db5c2faf483c4c45d19 [file] [log] [blame]
// Copyright 2023 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.packages.producers;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.packages.Globber;
import com.google.devtools.build.lib.skyframe.GlobDescriptor;
import com.google.devtools.build.lib.skyframe.IgnoredPackagePrefixesValue;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.state.StateMachine;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.regex.Pattern;
/**
* Serves as the entrance {@link StateMachine} for {@link
* com.google.devtools.build.lib.skyframe.GlobFunctionWithMultipleRecursiveFunctions}.
*
* <p>Gets all ignored package prefix patterns. Starts the {@link FragmentProducer}s chain which
* recursively process each glob pattern fragment. Accepts and aggregates globbing matching {@link
* PathFragment}s result.
*/
public class GlobComputationProducer
implements StateMachine, Consumer<SkyValue>, FragmentProducer.ResultSink {
/**
* Propagates all glob matching {@link PathFragment}s or any {@link Exception}.
*
* <p>If any {@link GlobError} is accepted, the already discovered path fragments are still
* reported. However, {@link
* com.google.devtools.build.lib.skyframe.GlobFunctionWithRecursionInSingleFunction} throws the
* first discovered {@link GlobError} wrapped in a {@link
* com.google.devtools.build.lib.skyframe.GlobFunction.GlobFunctionException}.
*
* <p>The already discovered path fragments should be considered as undefined. Since: (1) there is
* no skyframe restart after glob computation throws an exception, so the discovered path
* fragments can miss some matchings; (2) these discovered path fragments are not used to
* construct a {@link com.google.devtools.build.lib.skyframe.GlobValue}.
*/
public interface ResultSink {
void acceptPathFragmentsWithoutPackageFragment(ImmutableSet<PathFragment> pathFragments);
void acceptGlobError(GlobError error);
}
// -------------------- Input --------------------
private final GlobDescriptor globDescriptor;
private final ResultSink resultSink;
// -------------------- Internal State --------------------
private final ImmutableSet.Builder<PathFragment> pathFragmentsWithPackageFragment;
private ImmutableSet<PathFragment> ignorePackagePrefixPatterns = null;
private final ConcurrentHashMap<String, Pattern> regexPatternCache;
public GlobComputationProducer(
GlobDescriptor globDescriptor,
ConcurrentHashMap<String, Pattern> regexPatternCache,
ResultSink resultSink) {
this.globDescriptor = globDescriptor;
this.regexPatternCache = regexPatternCache;
this.resultSink = resultSink;
this.pathFragmentsWithPackageFragment = ImmutableSet.builder();
}
@Override
public StateMachine step(Tasks tasks) throws InterruptedException {
RepositoryName repositoryName = globDescriptor.getPackageId().getRepository();
tasks.lookUp(IgnoredPackagePrefixesValue.key(repositoryName), (Consumer<SkyValue>) this);
return this::createFragmentProducer;
}
@Override
public void accept(SkyValue skyValue) {
this.ignorePackagePrefixPatterns = ((IgnoredPackagePrefixesValue) skyValue).getPatterns();
}
private StateMachine createFragmentProducer(Tasks tasks) {
Preconditions.checkNotNull(ignorePackagePrefixPatterns);
ImmutableList<String> patterns =
ImmutableList.copyOf(Splitter.on('/').split(globDescriptor.getPattern()));
GlobDetail globDetail =
GlobDetail.create(
globDescriptor.getPackageId(),
globDescriptor.getPackageRoot(),
patterns,
/* containsMultipleDoubleStars= */ Collections.frequency(patterns, "**") > 1,
ignorePackagePrefixPatterns,
regexPatternCache,
globDescriptor.globberOperation());
Set<Pair<PathFragment, Integer>> visitedGlobSubTasks = null;
if (globDetail.containsMultipleDoubleStars()) {
visitedGlobSubTasks = new HashSet<>();
}
tasks.enqueue(
new FragmentProducer(
globDetail,
globDetail
.packageIdentifier()
.getPackageFragment()
.getRelative(globDescriptor.getSubdir()),
/* fragmentIndex= */ 0,
visitedGlobSubTasks,
(FragmentProducer.ResultSink) this));
return this::buildAndBubbleUpResult;
}
@Override
public void acceptPathFragmentWithPackageFragment(PathFragment pathFragment) {
pathFragmentsWithPackageFragment.add(pathFragment);
}
@Override
public void acceptGlobError(GlobError error) {
resultSink.acceptGlobError(error);
}
/**
* Removes the package fragment from all accepted matching path fragments before {@link
* ResultSink} accepts them.
*/
public StateMachine buildAndBubbleUpResult(Tasks tasks) {
resultSink.acceptPathFragmentsWithoutPackageFragment(
pathFragmentsWithPackageFragment.build().parallelStream()
.map(p -> p.relativeTo(globDescriptor.getPackageId().getPackageFragment()))
.collect(toImmutableSet()));
return DONE;
}
/**
* Container which holds all constant information needed for globbing.
*
* <p>This object is created and passed into {@link FragmentProducer} so that we only need one
* reference of {@link GlobDetail} downstream.
*/
@AutoValue
abstract static class GlobDetail {
static GlobDetail create(
PackageIdentifier packageIdentifier,
Root packageRoot,
ImmutableList<String> patternFragments,
boolean containsMultipleDoubleStars,
ImmutableSet<PathFragment> ignoredPackagePrefixesPatterns,
ConcurrentHashMap<String, Pattern> regexPatternCache,
Globber.Operation globOperation) {
return new AutoValue_GlobComputationProducer_GlobDetail(
packageIdentifier,
packageRoot,
patternFragments,
containsMultipleDoubleStars,
ignoredPackagePrefixesPatterns,
regexPatternCache,
globOperation);
}
abstract PackageIdentifier packageIdentifier();
abstract Root packageRoot();
abstract ImmutableList<String> patternFragments();
/**
* When multiple {@code **}s appear in pattern fragments, a set is created to track visited glob
* subtasks in order to prevent duplicate work.
*
* <p>See {@link FragmentProducer#visitedGlobSubTasks} for more details.
*/
abstract boolean containsMultipleDoubleStars();
abstract ImmutableSet<PathFragment> ignoredPackagePrefixesPatterns();
abstract ConcurrentHashMap<String, Pattern> regexPatternCache();
abstract Globber.Operation globOperation();
}
}