// 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). * *

Should only need to be created statically, and in limited quantity. */ public class AndroidDataConverter extends ParametrizedMapFn { /** * Converts parsed Android resources to the "SerializedAndroidData" format used by the Android * data processing actions. */ @AutoCodec static final AndroidDataConverter PARSED_RESOURCE_CONVERTER = AndroidDataConverter.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 COMPILED_RESOURCE_CONVERTER = AndroidDataConverter.builder(JoinerType.SEMICOLON_AMPERSAND) .withRoots(ParsedAndroidResources::getResourceRoots) .withEmpty() .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 AAPT_RESOURCES_AND_MANIFEST_CONVERTER = AndroidDataConverter.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 AAPT2_RESOURCES_AND_MANIFEST_CONVERTER = AndroidDataConverter.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 PARSED_ASSET_CONVERTER = AndroidDataConverter.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 COMPILED_ASSET_CONVERTER = AndroidDataConverter.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> suppliers; private final JoinerType joinerType; private AndroidDataConverter( ImmutableList> 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 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}. * *

Because of how Bazel handles these objects, call this method *only* as part of creating a * static final field. * *

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 Builder builder(JoinerType joinerType) { return new Builder<>(joinerType); } public VectorArg getVectorArg(NestedSet values) { return VectorArg.join(joinerType.listSeparator).each(values).mapped(this); } public VectorArg getVectorArgForEach( @CompileTimeConstant String arg, NestedSet values) { return VectorArg.addBefore(arg).each(values).mapped(this); } static class Builder { private final ImmutableList.Builder> inner = ImmutableList.builder(); private final JoinerType joinerType; private Builder(JoinerType joinerType) { this.joinerType = joinerType; } Builder withRoots(Function> rootsFunction) { return with(t -> rootsToString(rootsFunction.apply(t))); } Builder withArtifact(Function artifactFunction) { return with(t -> artifactFunction.apply(t).getExecPathString()); } Builder withEmpty() { return with(t -> ""); } Builder maybeWithArtifact(Function nullableArtifactFunction) { return with( t -> { @Nullable Artifact artifact = nullableArtifactFunction.apply(t); return artifact == null ? "" : artifact.getExecPathString(); }); } Builder withLabel(Function 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 with(Function stringFunction) { inner.add(stringFunction); return this; } AndroidDataConverter build() { return new AndroidDataConverter<>(inner.build(), joinerType); } } @VisibleForTesting public static String rootsToString(ImmutableList roots) { return roots.stream().map(PathFragment::toString).collect(Collectors.joining("#")); } }