blob: a8e632d1c6d28555352cf7e8252ccf88102b8981 [file] [log] [blame]
// Copyright 2018 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.rules.android;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.CommandLineItem.ParametrizedMapFn;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.errorprone.annotations.CompileTimeConstant;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
* Factory for functions to convert a {@code T} to a commandline argument. Uses a certain convention
* for commandline arguments (e.g., separators, and ordering of container elements).
*
* <p>Should only need to be created statically, and in limited quantity.
*/
public class AndroidDataConverter<T> extends ParametrizedMapFn<T> {
/**
* Converts parsed Android resources to the "SerializedAndroidData" format used by the Android
* data processing actions.
*/
@AutoCodec
static final AndroidDataConverter<ParsedAndroidResources> PARSED_RESOURCE_CONVERTER =
AndroidDataConverter.<ParsedAndroidResources>builder(JoinerType.SEMICOLON_AMPERSAND)
.withRoots(ParsedAndroidResources::getResourceRoots)
.withEmpty()
.withLabel(ParsedAndroidResources::getLabel)
.maybeWithArtifact(ParsedAndroidResources::getSymbols)
.build();
/**
* Converts compiled Android resources to the "SerializedAndroidData" format used by the Android
* data processing actions.
*/
@AutoCodec
static final AndroidDataConverter<ParsedAndroidResources> COMPILED_RESOURCE_CONVERTER =
AndroidDataConverter.<ParsedAndroidResources>builder(JoinerType.SEMICOLON_AMPERSAND)
.withEmpty() // resourceDirs
.withEmpty() // assetDirs
.withLabel(ParsedAndroidResources::getLabel)
.maybeWithArtifact(ParsedAndroidResources::getCompiledSymbols)
.build();
/**
* Converts processed Android resources produced by aapt to the "DependencyAndroidData" format
* used by the Android data processing actions.
*/
@AutoCodec
static final AndroidDataConverter<ValidatedAndroidResources>
AAPT_RESOURCES_AND_MANIFEST_CONVERTER =
AndroidDataConverter.<ValidatedAndroidResources>builder(JoinerType.COLON_COMMA)
.withRoots(ValidatedAndroidResources::getResourceRoots)
.withEmpty()
.withArtifact(ValidatedAndroidResources::getManifest)
.maybeWithArtifact(ValidatedAndroidResources::getRTxt)
.maybeWithArtifact(ValidatedAndroidResources::getSymbols)
.build();
/**
* Converts processed Android resources produced by aapt2 to the "DependencyAndroidData" format
* used by the Android data processing actions.
*/
@AutoCodec
static final AndroidDataConverter<ValidatedAndroidResources>
AAPT2_RESOURCES_AND_MANIFEST_CONVERTER =
AndroidDataConverter.<ValidatedAndroidResources>builder(JoinerType.COLON_COMMA)
.withRoots(ValidatedAndroidResources::getResourceRoots)
.withEmpty()
.withArtifact(ValidatedAndroidResources::getManifest)
.maybeWithArtifact(ValidatedAndroidResources::getAapt2RTxt)
.maybeWithArtifact(ValidatedAndroidResources::getCompiledSymbols)
.maybeWithArtifact(ValidatedAndroidResources::getSymbols)
.build();
/**
* Converts parsed Android assets to the "SerializedAndroidData" format used by the Android data
* processing actions.
*/
@AutoCodec
static final AndroidDataConverter<ParsedAndroidAssets> PARSED_ASSET_CONVERTER =
AndroidDataConverter.<ParsedAndroidAssets>builder(JoinerType.SEMICOLON_AMPERSAND)
.withEmpty()
.withRoots(ParsedAndroidAssets::getAssetRoots)
.withLabel(ParsedAndroidAssets::getLabel)
.maybeWithArtifact(ParsedAndroidAssets::getSymbols)
.build();
/**
* Converts compiled Android assets to the "SerializedAndroidData" format used by the Android data
* processing actions.
*/
@AutoCodec
static final AndroidDataConverter<ParsedAndroidAssets> COMPILED_ASSET_CONVERTER =
AndroidDataConverter.<ParsedAndroidAssets>builder(JoinerType.SEMICOLON_AMPERSAND)
.withEmpty()
.withRoots(ParsedAndroidAssets::getAssetRoots)
.withLabel(ParsedAndroidAssets::getLabel)
.maybeWithArtifact(ParsedAndroidAssets::getCompiledSymbols)
.build();
/** Indicates the type of joiner between options expected by the command line. */
public enum JoinerType {
COLON_COMMA(":", ","),
SEMICOLON_AMPERSAND(";", "&");
private final String itemSeparator;
private final String listSeparator;
JoinerType(String itemSeparator, String listSeparator) {
this.itemSeparator = itemSeparator;
this.listSeparator = listSeparator;
}
private String escape(String string) {
return string
.replace(itemSeparator, "\\" + itemSeparator)
.replace(listSeparator, "\\" + listSeparator);
}
}
private final ImmutableList<Function<T, String>> suppliers;
private final JoinerType joinerType;
private AndroidDataConverter(
ImmutableList<Function<T, String>> suppliers, JoinerType joinerType) {
this.suppliers = suppliers;
this.joinerType = joinerType;
}
// We must override equals and hashCode as per the contract of ParametrizedMapFn, but we
// statically create a very small number of these objects, so we know that reference equality is
// enough.
@Override
public boolean equals(Object obj) {
return this == obj;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
public int maxInstancesAllowed() {
// This is the max number of resource converters we expect to statically
// construct for any given blaze instance.
// Do not increase recklessly.
return 10;
}
@Override
public void expandToCommandLine(T t, Consumer<String> args) {
args.accept(map(t));
}
public String map(T t) {
return suppliers
.stream()
.map(s -> (s.apply(t)))
.collect(Collectors.joining(joinerType.itemSeparator));
}
/**
* Creates a builder for a new {@link AndroidDataConverter}.
*
* <p>Because of how Bazel handles these objects, call this method *only* as part of creating a
* static final field.
*
* <p>Additionally, the resulting {@link AndroidDataConverter} object should be annotated with
* {@link AutoCodec} (and, if relevant, {@link
* com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization}.
*/
public static <T> Builder<T> builder(JoinerType joinerType) {
return new Builder<>(joinerType);
}
public VectorArg<String> getVectorArg(NestedSet<? extends T> values) {
return VectorArg.join(joinerType.listSeparator).each(values).mapped(this);
}
public VectorArg<String> getVectorArgForEach(
@CompileTimeConstant String arg, NestedSet<? extends T> values) {
return VectorArg.addBefore(arg).each(values).mapped(this);
}
static class Builder<T> {
private final ImmutableList.Builder<Function<T, String>> inner = ImmutableList.builder();
private final JoinerType joinerType;
private Builder(JoinerType joinerType) {
this.joinerType = joinerType;
}
Builder<T> withRoots(Function<T, ImmutableList<PathFragment>> rootsFunction) {
return with(t -> rootsToString(rootsFunction.apply(t)));
}
Builder<T> withArtifact(Function<T, Artifact> artifactFunction) {
return with(t -> artifactFunction.apply(t).getExecPathString());
}
Builder<T> withEmpty() {
return with(t -> "");
}
Builder<T> maybeWithArtifact(Function<T, Artifact> nullableArtifactFunction) {
return with(
t -> {
@Nullable Artifact artifact = nullableArtifactFunction.apply(t);
return artifact == null ? "" : artifact.getExecPathString();
});
}
Builder<T> withLabel(Function<T, Label> labelFunction) {
// Escape labels, since they are known to contain separating characters (specifically, ':').
// Anonymous inner class for serialization.
return with(t -> joinerType.escape(labelFunction.apply(t).toString()));
}
Builder<T> with(Function<T, String> stringFunction) {
inner.add(stringFunction);
return this;
}
AndroidDataConverter<T> build() {
return new AndroidDataConverter<>(inner.build(), joinerType);
}
}
@VisibleForTesting
public static String rootsToString(ImmutableList<PathFragment> roots) {
return roots.stream().map(PathFragment::toString).collect(Collectors.joining("#"));
}
}