diff options
Diffstat (limited to 'src')
11 files changed, 386 insertions, 40 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java new file mode 100644 index 0000000000..9b6edbaff6 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceParsingActionBuilder.java @@ -0,0 +1,137 @@ +// Copyright 2016 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.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; +import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.ArrayList; +import java.util.List; + +/** + * Builder for creating $android_resource_parser action. + */ +class AndroidResourceParsingActionBuilder { + + private static final ResourceContainerToArtifacts RESOURCE_CONTAINER_TO_ARTIFACTS = + new ResourceContainerToArtifacts(); + + private static final ResourceContainerToArg RESOURCE_CONTAINER_TO_ARG = + new ResourceContainerToArg(); + + private final RuleContext ruleContext; + private LocalResourceContainer primary; + private Artifact output; + + /** + * @param ruleContext The RuleContext that was used to create the SpawnAction.Builder. + */ + public AndroidResourceParsingActionBuilder(RuleContext ruleContext) { + this.ruleContext = ruleContext; + } + + /** + * Set the resource container to parse. + */ + public AndroidResourceParsingActionBuilder setParse(LocalResourceContainer primary) { + this.primary = primary; + return this; + } + + /** + * Set the artifact location for the output protobuf. + */ + public AndroidResourceParsingActionBuilder setOutput(Artifact output) { + this.output = output; + return this; + } + + private static class ResourceContainerToArg implements Function<LocalResourceContainer, String> { + + public ResourceContainerToArg() { + } + + @Override + public String apply(LocalResourceContainer container) { + return new StringBuilder() + .append(convertRoots(container.getResourceRoots())) + .append(":") + .append(convertRoots(container.getAssetRoots())) + .toString(); + } + } + + private static class ResourceContainerToArtifacts + implements Function<LocalResourceContainer, NestedSet<Artifact>> { + + public ResourceContainerToArtifacts() { + } + + @Override + public NestedSet<Artifact> apply(LocalResourceContainer container) { + NestedSetBuilder<Artifact> artifacts = NestedSetBuilder.naiveLinkOrder(); + artifacts.addAll(container.getAssets()); + artifacts.addAll(container.getResources()); + return artifacts.build(); + } + } + + private static String convertRoots(Iterable<PathFragment> roots) { + return Joiner.on("#").join(Iterables.transform(roots, Functions.toStringFunction())); + } + + public Artifact build(ActionConstructionContext context) { + CustomCommandLine.Builder builder = new CustomCommandLine.Builder(); + + NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder(); + inputs.addAll(ruleContext.getExecutablePrerequisite("$android_resource_parser", Mode.HOST) + .getRunfilesSupport() + .getRunfilesArtifactsWithoutMiddlemen()); + + Preconditions.checkNotNull(primary); + builder.add("--primaryData").add(RESOURCE_CONTAINER_TO_ARG.apply(primary)); + inputs.addTransitive(RESOURCE_CONTAINER_TO_ARTIFACTS.apply(primary)); + + List<Artifact> outs = new ArrayList<>(); + Preconditions.checkNotNull(output); + builder.addExecPath("--output", output); + outs.add(output); + + SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder(); + // Create the spawn action. + ruleContext.registerAction( + spawnActionBuilder + .addTransitiveInputs(inputs.build()) + .addOutputs(ImmutableList.copyOf(outs)) + .setCommandLine(builder.build()) + .setExecutable( + ruleContext.getExecutablePrerequisite("$android_resource_parser", Mode.HOST)) + .setProgressMessage("Parsing Android resources for " + ruleContext.getLabel()) + .setMnemonic("AndroidResourceParser") + .build(context)); + return output; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java index 1c2eb2b72c..0212ba2884 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java @@ -171,6 +171,7 @@ public final class AndroidRuleClasses { public static final String DEFAULT_MANIFEST_MERGER = "//tools/android:manifest_merger"; public static final String DEFAULT_RCLASS_GENERATOR = "//tools/android:rclass_generator"; public static final String DEFAULT_RESOURCES_PROCESSOR = "//tools/android:resources_processor"; + public static final String DEFAULT_RESOURCE_PARSER = "//tools/android:resource_parser"; public static final String DEFAULT_RESOURCE_SHRINKER = "//tools/android:resource_shrinker"; public static final String DEFAULT_SDK = "//tools/android:sdk"; @@ -408,6 +409,8 @@ public final class AndroidRuleClasses { env.getToolsLabel(DEFAULT_RCLASS_GENERATOR))) .add(attr("$android_resources_processor", LABEL).cfg(HOST).exec().value( env.getToolsLabel(DEFAULT_RESOURCES_PROCESSOR))) + .add(attr("$android_resource_parser", LABEL).cfg(HOST).exec().value( + env.getToolsLabel(DEFAULT_RESOURCE_PARSER))) .add(attr("$android_resource_shrinker", LABEL).cfg(HOST).exec().value( env.getToolsLabel(DEFAULT_RESOURCE_SHRINKER))) .build(); diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java index 7d8a976ef4..a1b7c013da 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java @@ -160,6 +160,7 @@ public final class BazelAnalysisMock extends AnalysisMock { .add("sh_binary(name = 'manifest_merger', srcs = ['empty.sh'])") .add("sh_binary(name = 'rclass_generator', srcs = ['empty.sh'])") .add("sh_binary(name = 'resources_processor', srcs = ['empty.sh'])") + .add("sh_binary(name = 'resource_parser', srcs = ['empty.sh'])") .add("sh_binary(name = 'resource_shrinker', srcs = ['empty.sh'])") .add("android_library(name = 'incremental_stub_application')") .add("android_library(name = 'incremental_split_stub_application')") diff --git a/src/test/shell/bazel/android/BUILD b/src/test/shell/bazel/android/BUILD index 324cc8ca7c..ac44c592d9 100644 --- a/src/test/shell/bazel/android/BUILD +++ b/src/test/shell/bazel/android/BUILD @@ -17,6 +17,7 @@ sh_test( "//external:android_ndk_for_testing", "//external:android_sdk_for_testing", "//src/tools/android/java/com/google/devtools/build/android:AarGeneratorAction_deploy.jar", + "//src/tools/android/java/com/google/devtools/build/android:AndroidResourceParsingAction_deploy.jar", "//src/tools/android/java/com/google/devtools/build/android:AndroidResourceProcessingAction_deploy.jar", "//src/tools/android/java/com/google/devtools/build/android:RClassGeneratorAction_deploy.jar", "//src/tools/android/java/com/google/devtools/build/android:ResourceShrinkerAction_deploy.jar", diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java new file mode 100644 index 0000000000..e48440db48 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java @@ -0,0 +1,81 @@ +// Copyright 2016 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.android; + +import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.Converters.PathConverter; +import com.google.devtools.build.android.Converters.UnvalidatedAndroidDirectoriesConverter; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParser; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Parses android resource XML files from a directory and serializes the results to a protobuf. + * Other actions that depend on the same resource directory can then load the data from a single + * protobuf instead of reparsing multiple tiny XML files from source (proto loading is faster). + */ +public class AndroidResourceParsingAction { + + private static final Logger logger = + Logger.getLogger(AndroidResourceParsingAction.class.getName()); + + /** + * Flag specifications for this action. + */ + public static final class Options extends OptionsBase { + + @Option(name = "primaryData", + defaultValue = "null", + converter = UnvalidatedAndroidDirectoriesConverter.class, + category = "input", + help = "The resource and asset directories to parse and summarize in a symbols file." + + " The expected format is resources[#resources]:assets[#assets]") + public UnvalidatedAndroidDirectories primaryData; + + @Option(name = "output", + defaultValue = "null", + converter = PathConverter.class, + category = "output", + help = "Path to write the output protobuf.") + public Path output; + } + + public static void main(String[] args) throws Exception { + OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class); + optionsParser.parseAndExitUponError(args); + Options options = optionsParser.getOptions(Options.class); + + Preconditions.checkNotNull(options.primaryData); + Preconditions.checkNotNull(options.output); + + final Stopwatch timer = Stopwatch.createStarted(); + ParsedAndroidData parsedPrimary = ParsedAndroidData.from(options.primaryData); + logger.fine(String.format("Walked XML tree at %dms", timer.elapsed(TimeUnit.MILLISECONDS))); + UnwrittenMergedAndroidData unwrittenData = UnwrittenMergedAndroidData.of( + null, + parsedPrimary, + ParsedAndroidData.from(ImmutableList.<DependencyAndroidData>of())); + AndroidDataSerializer serializer = AndroidDataSerializer.create(); + unwrittenData.serializeTo(serializer); + serializer.flushTo(options.output); + logger.fine(String.format( + "Finished parse + serialize in %dms", timer.elapsed(TimeUnit.MILLISECONDS))); + } + +} diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD index 9a4d1369fd..ac29480278 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD @@ -26,6 +26,14 @@ java_binary( ) java_binary( + name = "AndroidResourceParsingAction", + main_class = "com.google.devtools.build.android.AndroidResourceParsingAction", + runtime_deps = [ + ":android_builder_lib", + ], +) + +java_binary( name = "AndroidResourceProcessingAction", main_class = "com.google.devtools.build.android.AndroidResourceProcessingAction", runtime_deps = [ diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools index abbe55f3fc..612936fd71 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools @@ -14,6 +14,14 @@ java_binary( ) java_binary( + name = "AndroidResourceParsingAction", + main_class = "com.google.devtools.build.android.AndroidResourceParsingAction", + runtime_deps = [ + ":classes", + ], +) + +java_binary( name = "AndroidResourceProcessingAction", main_class = "com.google.devtools.build.android.AndroidResourceProcessingAction", runtime_deps = [ diff --git a/src/tools/android/java/com/google/devtools/build/android/Converters.java b/src/tools/android/java/com/google/devtools/build/android/Converters.java index bcbfa97785..464e793872 100644 --- a/src/tools/android/java/com/google/devtools/build/android/Converters.java +++ b/src/tools/android/java/com/google/devtools/build/android/Converters.java @@ -67,8 +67,30 @@ public final class Converters { @Override public String getTypeDescription() { - return "unvalidated android data in the format " - + "resources[#resources]:assets[#assets]:manifest"; + return "unvalidated android data in the format " + UnvalidatedAndroidData.expectedFormat(); + } + } + + /** + * Converter for {@link UnvalidatedAndroidDirectories}. + */ + public static class UnvalidatedAndroidDirectoriesConverter + implements Converter<UnvalidatedAndroidDirectories> { + + @Override + public UnvalidatedAndroidDirectories convert(String input) throws OptionsParsingException { + try { + return UnvalidatedAndroidDirectories.valueOf(input); + } catch (IllegalArgumentException e) { + throw new OptionsParsingException( + String.format("invalid UnvalidatedAndroidDirectories: %s", e.getMessage()), e); + } + } + + @Override + public String getTypeDescription() { + return "unvalidated android directories in the format " + + UnvalidatedAndroidDirectories.expectedFormat(); } } diff --git a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java index 47d3d30af5..6a06ca33b2 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java @@ -352,7 +352,7 @@ public class ParsedAndroidData { * @throws IOException when there are issues with reading files. * @throws MergingException when there is invalid resource information. */ - public static ParsedAndroidData from(UnvalidatedAndroidData primary) + public static ParsedAndroidData from(UnvalidatedAndroidDirectories primary) throws IOException, MergingException { final ParsedAndroidDataBuildingPathWalker pathWalker = ParsedAndroidDataBuildingPathWalker.create(Builder.newBuilder()); @@ -384,10 +384,10 @@ public class ParsedAndroidData { /** * Parses resource symbols including "@+id/resourceName" (optional for now). * - * @see ParsedAndroidData#from(UnvalidatedAndroidData) + * @see ParsedAndroidData#from(UnvalidatedAndroidDirectories) */ @VisibleForTesting - static ParsedAndroidData parseWithIds(UnvalidatedAndroidData primary) + static ParsedAndroidData parseWithIds(UnvalidatedAndroidDirectories primary) throws IOException, MergingException { Builder builder = Builder.newBuilder().enableIdParsing(); final ParsedAndroidDataBuildingPathWalker pathWalker = diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java index 9f43441692..beeeba7e37 100644 --- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java @@ -15,10 +15,8 @@ package com.google.devtools.build.android; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import java.util.regex.Pattern; @@ -30,8 +28,12 @@ import java.util.regex.Pattern; * {@link UnvalidatedAndroidData} -> {@link MergedAndroidData} -> {@link DensityFilteredAndroidData} * -> {@link DependencyAndroidData} */ -class UnvalidatedAndroidData { - static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+"); +class UnvalidatedAndroidData extends UnvalidatedAndroidDirectories { + private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+"); + + static String expectedFormat() { + return "resources[#resources]:assets[#assets]:manifest"; + } public static UnvalidatedAndroidData valueOf(String text) { return valueOf(text, FileSystems.getDefault()); @@ -41,7 +43,7 @@ class UnvalidatedAndroidData { static UnvalidatedAndroidData valueOf(String text, FileSystem fileSystem) { if (!VALID_REGEX.matcher(text).find()) { throw new IllegalArgumentException( - text + " is not in the format 'resources[#resources]:assets[#assets]:manifest'"); + text + " is not in the format '" + expectedFormat() + "'"); } String[] parts = text.split(":"); return new UnvalidatedAndroidData( @@ -50,32 +52,11 @@ class UnvalidatedAndroidData { exists(fileSystem.getPath(parts[2]))); } - private static ImmutableList<Path> splitPaths(String pathsString, FileSystem fileSystem) { - if (pathsString.length() == 0) { - return ImmutableList.of(); - } - ImmutableList.Builder<Path> paths = new ImmutableList.Builder<>(); - for (String pathString : pathsString.split("#")) { - paths.add(exists(fileSystem.getPath(pathString))); - } - return paths.build(); - } - - private static Path exists(Path path) { - if (!Files.exists(path)) { - throw new IllegalArgumentException(path + " does not exist"); - } - return path; - } - private final Path manifest; - private final ImmutableList<Path> assetDirs; - private final ImmutableList<Path> resourceDirs; public UnvalidatedAndroidData( ImmutableList<Path> resourceDirs, ImmutableList<Path> assetDirs, Path manifest) { - this.resourceDirs = resourceDirs; - this.assetDirs = assetDirs; + super(resourceDirs, assetDirs); this.manifest = manifest; } @@ -107,12 +88,4 @@ class UnvalidatedAndroidData { && Objects.equals(other.manifest, manifest); } - public void walk(final AndroidDataPathWalker pathWalker) throws IOException { - for (Path path : resourceDirs) { - pathWalker.walkResources(path); - } - for (Path path : assetDirs) { - pathWalker.walkAssets(path); - } - } } diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java new file mode 100644 index 0000000000..f87da7fbf0 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java @@ -0,0 +1,112 @@ +// Copyright 2016 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.android; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Android resource and asset directories that can be parsed. + */ +public class UnvalidatedAndroidDirectories { + + private static final Pattern VALID_REGEX = Pattern.compile(".*:.*"); + + static String expectedFormat() { + return "resources[#resources]:assets[#assets]"; + } + + public static UnvalidatedAndroidDirectories valueOf(String text) { + return valueOf(text, FileSystems.getDefault()); + } + + @VisibleForTesting + static UnvalidatedAndroidDirectories valueOf(String text, FileSystem fileSystem) { + if (!VALID_REGEX.matcher(text).find()) { + throw new IllegalArgumentException( + text + " is not in the format '" + expectedFormat() + "'"); + } + String[] parts = text.split(":"); + return new UnvalidatedAndroidDirectories( + parts.length > 0 ? splitPaths(parts[0], fileSystem) : ImmutableList.<Path>of(), + parts.length > 1 ? splitPaths(parts[1], fileSystem) : ImmutableList.<Path>of()); + } + + protected static ImmutableList<Path> splitPaths(String pathsString, FileSystem fileSystem) { + if (pathsString.length() == 0) { + return ImmutableList.of(); + } + ImmutableList.Builder<Path> paths = new ImmutableList.Builder<>(); + for (String pathString : pathsString.split("#")) { + paths.add(exists(fileSystem.getPath(pathString))); + } + return paths.build(); + } + + protected static Path exists(Path path) { + if (!Files.exists(path)) { + throw new IllegalArgumentException(path + " does not exist"); + } + return path; + } + + protected final ImmutableList<Path> assetDirs; + protected final ImmutableList<Path> resourceDirs; + + public UnvalidatedAndroidDirectories( + ImmutableList<Path> resourceDirs, ImmutableList<Path> assetDirs) { + this.resourceDirs = resourceDirs; + this.assetDirs = assetDirs; + } + + void walk(final AndroidDataPathWalker pathWalker) throws IOException { + for (Path path : resourceDirs) { + pathWalker.walkResources(path); + } + for (Path path : assetDirs) { + pathWalker.walkAssets(path); + } + } + + @Override + public String toString() { + return String.format("UnvalidatedAndroidDirectories(%s, %s)", resourceDirs, assetDirs); + } + + @Override + public int hashCode() { + return Objects.hash(resourceDirs, assetDirs); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof UnvalidatedAndroidDirectories)) { + return false; + } + UnvalidatedAndroidDirectories other = (UnvalidatedAndroidDirectories) obj; + return Objects.equals(other.resourceDirs, resourceDirs) + && Objects.equals(other.assetDirs, assetDirs); + } + +} |