/*
* Copyright 2013-present Facebook, Inc.
*
* 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.facebook.buck.rules.coercer;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.Pair;
import com.facebook.buck.rules.CellPathResolver;
import com.facebook.buck.rules.VisibilityPattern;
import com.facebook.buck.rules.VisibilityPatternParser;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
/**
* Used to derive information from the constructor args returned by {@link
* com.facebook.buck.rules.Description} instances. There are two major uses this information is put
* to: populating the DTO object from the deserialized JSON maps, which are outputted by the
* functions added to Buck's core build file parsing script. The second function of this class is to
* generate those functions.
*/
public class ConstructorArgMarshaller {
private final TypeCoercerFactory typeCoercerFactory;
/**
* Constructor. {@code pathFromProjectRootToBuildFile} is the path relative to the project root to
* the build file that has called the build rule's function in buck.py. This is used for resolving
* additional paths to ones relative to the project root, and to allow {@link BuildTarget}
* instances to be fully qualified.
*/
public ConstructorArgMarshaller(TypeCoercerFactory typeCoercerFactory) {
this.typeCoercerFactory = typeCoercerFactory;
}
/**
* Use the information contained in the {@code params} to fill in the public fields and settable
* properties of {@code dto}. The following rules are used:
*
* <ul>
* <li>Boolean values are set to true or false.
* <li>{@link BuildTarget}s are resolved and will be fully qualified.
* <li>Numeric values are handled as if being cast from Long.
* <li>{@link com.facebook.buck.rules.SourcePath} instances will be set to the appropriate
* implementation.
* <li>{@link Path} declarations will be set to be relative to the project root.
* <li>Strings will be set "as is".
* <li>{@link List}s and {@link Set}s will be populated with the expected generic type, provided
* the wildcarding allows an upperbound to be determined to be one of the above.
* </ul>
*
* Any property that is marked as being an {@link Optional} field will be set to a default value
* if none is set. This is typically {@link Optional#empty()}, but in the case of collections is
* an empty collection.
*
* @param dtoClass The type of the constructor dto to be populated, either an
* AbstractDescriptionArg or an Immutable.
* @param declaredDeps A builder to be populated with the declared dependencies.
* @return The fully populated DTO.
*/
@CheckReturnValue
public <T> T populate(
CellPathResolver cellRoots,
ProjectFilesystem filesystem,
BuildTarget buildTarget,
Class<T> dtoClass,
ImmutableSet.Builder<BuildTarget> declaredDeps,
Map<String, ?> instance)
throws ParamInfoException {
Pair<Object, Function<Object, T>> dtoAndBuild =
CoercedTypeCache.instantiateSkeleton(dtoClass, buildTarget);
ImmutableMap<String, ParamInfo> allParamInfo =
CoercedTypeCache.INSTANCE.getAllParamInfo(typeCoercerFactory, dtoClass);
for (ParamInfo info : allParamInfo.values()) {
info.setFromParams(cellRoots, filesystem, buildTarget, dtoAndBuild.getFirst(), instance);
}
T dto = dtoAndBuild.getSecond().apply(dtoAndBuild.getFirst());
ParamInfo deps = allParamInfo.get("deps");
if (deps != null && deps.isDep()) {
deps.traverse(
object -> {
if (!(object instanceof BuildTarget)) {
return;
}
declaredDeps.add((BuildTarget) object);
},
dto);
}
return dto;
}
@SuppressWarnings("unchecked")
public static ImmutableSet<VisibilityPattern> populateVisibilityPatterns(
CellPathResolver cellNames, String paramName, @Nullable Object value, BuildTarget target) {
if (value == null) {
return ImmutableSet.of();
}
if (!(value instanceof List)) {
throw new RuntimeException(
String.format("Expected an array for %s but was %s", paramName, value));
}
ImmutableSet.Builder<VisibilityPattern> patterns = new ImmutableSet.Builder<>();
VisibilityPatternParser parser = new VisibilityPatternParser();
for (String visibility : (List<String>) value) {
try {
patterns.add(parser.parse(cellNames, visibility));
} catch (IllegalArgumentException e) {
throw new HumanReadableException(
e,
"Bad visibility expression: %s listed %s in its %s argument, but only %s "
+ "or fully qualified target patterns are allowed (i.e. those starting with "
+ "// or a cell).",
target.getFullyQualifiedName(),
visibility,
paramName,
VisibilityPatternParser.VISIBILITY_PUBLIC);
}
}
return patterns.build();
}
}