blob: d1f67a2ff881bd97e834d4b42cea28a47c8ec647 [file] [log] [blame]
// Copyright 2019 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.analysis;
import static java.util.Objects.requireNonNull;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.lib.util.ClassName;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Provides a user-friendly list of the {@link Fragment}s and {@link
* com.google.devtools.build.lib.analysis.config.FragmentOptions} required by this target and its
* transitive dependencies.
*
* <p>See {@link com.google.devtools.build.lib.analysis.config.RequiredFragmentsUtil} for details.
*/
@Immutable
@AutoCodec
public record RequiredConfigFragmentsProvider(
ImmutableSet<Class<? extends FragmentOptions>> optionsClasses,
ImmutableSet<Class<? extends Fragment>> fragmentClasses,
ImmutableSet<String> defines,
ImmutableSet<Label> starlarkOptions)
implements TransitiveInfoProvider {
public RequiredConfigFragmentsProvider {
requireNonNull(optionsClasses, "optionsClasses");
requireNonNull(fragmentClasses, "fragmentClasses");
requireNonNull(defines, "defines");
requireNonNull(starlarkOptions, "starlarkOptions");
}
private static final Interner<RequiredConfigFragmentsProvider> interner =
BlazeInterners.newWeakInterner();
@SerializationConstant
public static final RequiredConfigFragmentsProvider EMPTY =
new RequiredConfigFragmentsProvider(
ImmutableSet.of(), ImmutableSet.of(), ImmutableSet.of(), ImmutableSet.of());
@Override
public final String toString() {
return MoreObjects.toStringHelper(RequiredConfigFragmentsProvider.class)
.add(
"optionsClasses",
Collections2.transform(optionsClasses(), ClassName::getSimpleNameWithOuter))
.add(
"fragmentClasses",
Collections2.transform(fragmentClasses(), ClassName::getSimpleNameWithOuter))
.add("defines", defines())
.add("starlarkOptions", starlarkOptions())
.toString();
}
/** Merges the values of one or more {@link RequiredConfigFragmentsProvider} instances. */
public static RequiredConfigFragmentsProvider merge(
List<RequiredConfigFragmentsProvider> providers) {
if (providers.isEmpty()) {
return EMPTY;
}
RequiredConfigFragmentsProvider.Builder merged = null;
RequiredConfigFragmentsProvider candidate = EMPTY;
for (RequiredConfigFragmentsProvider provider : providers) {
if (provider == EMPTY) {
continue;
}
if (merged != null) {
merged.merge(provider);
} else if (candidate == EMPTY) {
candidate = provider;
} else {
merged = builder().merge(candidate).merge(provider);
}
}
return merged == null ? candidate : merged.build();
}
public static Builder builder() {
return new Builder();
}
/**
* Builder for required config fragments.
*
* <p>The builder uses a merging strategy that favors reuse of {@link ImmutableSet} instances and
* avoids copying data if possible (i.e. when adding elements that are already present). For this
* reason, adding transitively required fragments <em>before</em> directly required fragments is
* likely to result in better performance, as it promotes reuse of existing sets from
* dependencies.
*/
public static final class Builder {
private Set<Class<? extends FragmentOptions>> optionsClasses = ImmutableSet.of();
private Set<Class<? extends Fragment>> fragmentClasses = ImmutableSet.of();
private Set<String> defines = ImmutableSet.of();
private Set<Label> starlarkOptions = ImmutableSet.of();
private Builder() {}
@CanIgnoreReturnValue
public Builder addOptionsClass(Class<? extends FragmentOptions> optionsClass) {
optionsClasses = append(optionsClasses, optionsClass);
return this;
}
@CanIgnoreReturnValue
public Builder addOptionsClasses(Collection<Class<? extends FragmentOptions>> optionsClasses) {
this.optionsClasses = appendAll(this.optionsClasses, optionsClasses);
return this;
}
@CanIgnoreReturnValue
public Builder addFragmentClasses(Collection<Class<? extends Fragment>> fragmentClasses) {
this.fragmentClasses = appendAll(this.fragmentClasses, fragmentClasses);
return this;
}
@CanIgnoreReturnValue
public Builder addDefine(String define) {
defines = append(defines, define);
return this;
}
@CanIgnoreReturnValue
public Builder addDefines(Collection<String> defines) {
this.defines = appendAll(this.defines, defines);
return this;
}
@CanIgnoreReturnValue
public Builder addStarlarkOption(Label starlarkOption) {
starlarkOptions = append(starlarkOptions, starlarkOption);
return this;
}
@CanIgnoreReturnValue
public Builder addStarlarkOptions(Collection<Label> starlarkOptions) {
this.starlarkOptions = appendAll(this.starlarkOptions, starlarkOptions);
return this;
}
@CanIgnoreReturnValue
public Builder merge(RequiredConfigFragmentsProvider provider) {
if (provider != null) {
optionsClasses = appendAll(optionsClasses, provider.optionsClasses());
fragmentClasses = appendAll(fragmentClasses, provider.fragmentClasses());
defines = appendAll(defines, provider.defines());
starlarkOptions = appendAll(starlarkOptions, provider.starlarkOptions());
}
return this;
}
private static <T> Set<T> append(Set<T> set, T t) {
if (set instanceof ImmutableSet) {
if (set.contains(t)) {
return set;
}
set = new HashSet<>(set);
}
set.add(t);
return set;
}
private static <T> Set<T> appendAll(Set<T> set, Collection<T> ts) {
if (ts instanceof Set) {
return appendAll(set, (Set<T>) ts);
}
if (set instanceof ImmutableSet) {
if (set.containsAll(ts)) {
return set;
}
set = new HashSet<>(set);
}
set.addAll(ts);
return set;
}
private static <T> Set<T> appendAll(Set<T> set, Set<T> ts) {
if (set.size() > ts.size()) {
if (set instanceof ImmutableSet && set.containsAll(ts)) {
return set;
}
} else if (ts.size() > set.size()) {
if (ts instanceof ImmutableSet && ts.containsAll(set)) {
return ts;
}
} else { // Sizes equal.
if (set instanceof ImmutableSet) {
if (set.equals(ts)) {
return set;
}
} else if (ts instanceof ImmutableSet && ts.equals(set)) {
return ts;
}
}
if (set instanceof ImmutableSet) {
set = new HashSet<>(set);
}
set.addAll(ts);
return set;
}
public RequiredConfigFragmentsProvider build() {
if (optionsClasses.isEmpty()
&& fragmentClasses.isEmpty()
&& defines.isEmpty()
&& starlarkOptions.isEmpty()) {
return EMPTY;
}
return interner.intern(
new RequiredConfigFragmentsProvider(
ImmutableSet.copyOf(optionsClasses),
ImmutableSet.copyOf(fragmentClasses),
ImmutableSet.copyOf(defines),
ImmutableSet.copyOf(starlarkOptions)));
}
}
}