Skip to content

Commit b76cc3a

Browse files
jongerrishcopybara-github
authored andcommitted
Reduce AndroidAssetMerger intermediate outputs
They are not required in the final build. For our builds:- This results in 503MB of outputs (assets.zip files) - each of these includes the transitive closure of all assets. To put in perspective we have only 2.5MB of assets. This is very costly for remote builds with caches especially on slower connections If resource conflict checking is not enabled (it is not for our build) this will also avoid running AndroidAssetMerger all together saving 896 actions ~3mins total CPU time. bazelbuild/rules_android#10 Closes #11303. PiperOrigin-RevId: 463912957 Change-Id: I61b0cdd03ce5bdfd70f17feaf03529a683cddefc
1 parent bc20798 commit b76cc3a

File tree

6 files changed

+137
-89
lines changed

6 files changed

+137
-89
lines changed

src/main/java/com/google/devtools/build/lib/rules/android/AndroidAssets.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,15 @@ ParsedAndroidAssets parse(AndroidDataContext dataContext) throws InterruptedExce
180180
/** Convenience method to do all of asset processing - parsing and merging. */
181181
public MergedAndroidAssets process(AndroidDataContext dataContext, AssetDependencies assetDeps)
182182
throws InterruptedException {
183-
return parse(dataContext).merge(dataContext, assetDeps);
183+
ParsedAndroidAssets parsedAssets = parse(dataContext);
184+
185+
boolean mergeAssets =
186+
dataContext.getAndroidConfig().outputLibraryMergedAssets()
187+
|| dataContext.throwOnResourceConflict();
188+
189+
return mergeAssets
190+
? parsedAssets.merge(dataContext, assetDeps)
191+
: MergedAndroidAssets.of(parsedAssets, null, assetDeps);
184192
}
185193

186194
@Override

src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,14 @@ public static class Options extends FragmentOptions {
936936
help = "Use R.txt from the merging action, instead of from the validation action.")
937937
public boolean useRTxtFromMergedResources;
938938

939+
@Option(
940+
name = "output_library_merged_assets",
941+
defaultValue = "true",
942+
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
943+
effectTags = {OptionEffectTag.UNKNOWN},
944+
help = "If disabled, does not produce merged asset.zip outputs for library targets")
945+
public boolean outputLibraryMergedAssets;
946+
939947
@Option(
940948
name = "legacy_main_dex_list_generator",
941949
// TODO(b/147692286): Update this default value to R8's GenerateMainDexList binary after
@@ -1072,6 +1080,7 @@ public FragmentOptions getHost() {
10721080
private final boolean alwaysFilterDuplicateClassesFromAndroidTest;
10731081
private final boolean filterLibraryJarWithProgramJar;
10741082
private final boolean useRTxtFromMergedResources;
1083+
private final boolean outputLibraryMergedAssets;
10751084
private final Label legacyMainDexListGenerator;
10761085
private final boolean disableInstrumentationManifestMerging;
10771086
private final boolean incompatibleUseToolchainResolution;
@@ -1135,6 +1144,7 @@ public AndroidConfiguration(BuildOptions buildOptions) throws InvalidConfigurati
11351144
options.alwaysFilterDuplicateClassesFromAndroidTest;
11361145
this.filterLibraryJarWithProgramJar = options.filterLibraryJarWithProgramJar;
11371146
this.useRTxtFromMergedResources = options.useRTxtFromMergedResources;
1147+
this.outputLibraryMergedAssets = options.outputLibraryMergedAssets;
11381148
this.legacyMainDexListGenerator = options.legacyMainDexListGenerator;
11391149
this.disableInstrumentationManifestMerging = options.disableInstrumentationManifestMerging;
11401150
this.incompatibleUseToolchainResolution = options.incompatibleUseToolchainResolution;
@@ -1431,6 +1441,10 @@ public boolean includeProguardLocationReferences() {
14311441
return includeProguardLocationReferences;
14321442
}
14331443

1444+
boolean outputLibraryMergedAssets() {
1445+
return outputLibraryMergedAssets;
1446+
}
1447+
14341448
/** Returns the label provided with --legacy_main_dex_list_generator, if any. */
14351449
// TODO(b/147692286): Move R8's main dex list tool into tool repository.
14361450
@StarlarkConfigurationField(

src/main/java/com/google/devtools/build/lib/rules/android/MergedAndroidAssets.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ static MergedAndroidAssets mergeFrom(
2828
throws InterruptedException {
2929

3030
Artifact mergedAssets =
31-
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_ASSETS_ZIP);
31+
dataContext.getAndroidConfig().outputLibraryMergedAssets()
32+
? dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_ASSETS_ZIP)
33+
: null;
3234

3335
BusyBoxActionBuilder builder = BusyBoxActionBuilder.create(dataContext, "MERGE_ASSETS");
3436
if (dataContext.throwOnResourceConflict()) {
3537
builder.addFlag("--throwOnAssetConflict");
3638
}
3739

3840
builder
39-
.addOutput("--assetsOutput", mergedAssets)
41+
.maybeAddOutput("--assetsOutput", mergedAssets)
4042
.addInput(
4143
"--primaryData",
4244
AndroidDataConverter.PARSED_ASSET_CONVERTER.map(parsed),

src/test/shell/bazel/android/android_integration_test.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,61 @@ EOF
343343
assert_multiple_dex_files
344344
}
345345

346+
function test_reduce_merged_assets() {
347+
write_hello_android_files
348+
setup_android_sdk_support
349+
mkdir -p java/com/example/hello/assets
350+
echo hello > java/com/example/hello/assets/hello.txt
351+
cat > java/com/example/hello/BUILD <<'EOF'
352+
android_binary(
353+
name = "hello",
354+
manifest = "AndroidManifest.xml",
355+
deps = [":hello_lib"],
356+
)
357+
android_library(
358+
name = "hello_lib",
359+
manifest = "AndroidManifest.xml",
360+
srcs = glob(["*.java"]),
361+
resource_files = glob(["res/**"]),
362+
assets_dir = "assets",
363+
assets = glob(["assets/**"]),
364+
)
365+
EOF
366+
# The standard behavior is to output the merged assets as assets.zip
367+
bazel build //java/com/example/hello:hello_lib --output_library_merged_assets || fail "build failed"
368+
local tmpdir=$(mktemp -d)
369+
# Expect the assets.zip to exist.
370+
unzip bazel-bin/java/com/example/hello/hello_lib_files/assets.zip -d $tmpdir
371+
# Expect that hello.txt contains hello.
372+
assert_contains "hello" $tmpdir/assets/hello.txt
373+
rm -rf $tmpdir
374+
}
375+
376+
function test_dont_reduce_merged_assets() {
377+
write_hello_android_files
378+
setup_android_sdk_support
379+
mkdir -p java/com/example/hello/assets
380+
echo hello > java/com/example/hello/assets/hello.txt
381+
cat > java/com/example/hello/BUILD <<'EOF'
382+
android_binary(
383+
name = "hello",
384+
manifest = "AndroidManifest.xml",
385+
deps = [":hello_lib"],
386+
)
387+
android_library(
388+
name = "hello_lib",
389+
manifest = "AndroidManifest.xml",
390+
srcs = glob(["*.java"]),
391+
resource_files = glob(["res/**"]),
392+
assets_dir = "assets",
393+
assets = glob(["assets/**"]),
394+
)
395+
EOF
396+
bazel build //java/com/example/hello:hello --nooutput_library_merged_assets || fail "build failed"
397+
# Expect assets.zip to NOT exist.
398+
if [[ -f bazel-bin/java/com/example/hello/hello_lib_files/assets.zip ]]; then
399+
fail "assets.zip should NOT exist!"
400+
fi
401+
}
402+
346403
run_suite "Android integration tests"

src/tools/android/java/com/google/devtools/build/android/AndroidAssetMergingAction.java

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
// limitations under the License.
1414
package com.google.devtools.build.android;
1515

16-
import com.android.builder.core.VariantType;
1716
import com.google.common.annotations.VisibleForTesting;
1817
import com.google.common.base.Preconditions;
18+
import com.google.devtools.build.android.AndroidDataMerger.ContentComparingChecker;
1919
import com.google.devtools.build.android.Converters.PathConverter;
2020
import com.google.devtools.build.android.Converters.SerializedAndroidDataConverter;
2121
import com.google.devtools.build.android.Converters.SerializedAndroidDataListConverter;
@@ -133,29 +133,46 @@ void run(Path tmp, ExecutorServiceCloser executorService) throws Exception {
133133

134134
Preconditions.checkNotNull(options.primary);
135135

136-
MergedAndroidData mergedData =
137-
AndroidResourceMerger.mergeDataAndWrite(
138-
options.primary,
139-
/* primaryManifest = */ null,
140-
options.directData,
136+
final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder();
137+
final AndroidParsedDataDeserializer deserializer = AndroidParsedDataDeserializer.create();
138+
options.primary.deserialize(
139+
DependencyInfo.DependencyType.PRIMARY, deserializer, primaryBuilder.consumers());
140+
ParsedAndroidData primaryData = primaryBuilder.build();
141+
142+
UnwrittenMergedAndroidData unwrittenMergedData =
143+
AndroidResourceMerger.mergeData(
144+
executorService,
141145
options.transitiveData,
142-
/* resourcesOut = */ ignored,
143-
mergedAssets,
144-
VariantType.LIBRARY,
145-
/* symbolsOut = */ null,
146-
/* rclassWriter = */ null,
146+
options.directData,
147+
primaryData,
148+
/* primaryManifest = */ null,
149+
/* allowPrimaryOverrideAll = */ false,
150+
deserializer,
147151
options.throwOnAssetConflict,
148-
executorService);
152+
ContentComparingChecker.create());
149153

150154
logCompletion("Merging");
151155

152-
Preconditions.checkState(
153-
!Files.exists(ignored),
154-
"The asset merging action should not produce non-asset merge results!");
155-
156-
ResourcesZip.from(ignored, mergedData.getAssetDir())
157-
.writeTo(options.assetsOutput, /* compress= */ true);
158-
logCompletion("Create assets zip");
156+
if (options.assetsOutput != null) {
157+
MergedAndroidData writtenMergedData =
158+
AndroidResourceMerger.writeMergedData(
159+
ignored,
160+
mergedAssets,
161+
/* symbolsOut = */ null,
162+
/* rclassWriter = */ null,
163+
executorService,
164+
unwrittenMergedData);
165+
166+
logCompletion("Writing");
167+
168+
Preconditions.checkState(
169+
!Files.exists(ignored),
170+
"The asset merging action should not produce non-asset merge results!");
171+
172+
ResourcesZip.from(ignored, writtenMergedData.getAssetDir())
173+
.writeTo(options.assetsOutput, /* compress= */ true);
174+
logCompletion("Create assets zip");
175+
}
159176
}
160177

161178
@Override

src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java

Lines changed: 17 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -97,87 +97,37 @@ public static MergedAndroidData mergeDataAndWrite(
9797
boolean throwOnResourceConflict) {
9898
try (ExecutorServiceCloser executorService = ExecutorServiceCloser.createWithFixedPoolOf(15)) {
9999
final ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primary);
100-
return mergeDataAndWrite(
101-
parsedPrimary,
102-
primary.getManifest(),
103-
direct,
104-
transitive,
100+
return writeMergedData(
105101
resourcesOut,
106102
assetsOut,
107-
type,
108103
symbolsOut,
109104
/* rclassWriter= */ null,
110-
AndroidParsedDataDeserializer.withFilteredResources(filteredResources),
111-
throwOnResourceConflict,
112-
executorService);
105+
executorService,
106+
mergeData(
107+
executorService,
108+
transitive,
109+
direct,
110+
parsedPrimary,
111+
primary.getManifest(),
112+
type != VariantType.LIBRARY,
113+
AndroidParsedDataDeserializer.withFilteredResources(filteredResources),
114+
throwOnResourceConflict,
115+
ContentComparingChecker.create()));
113116
} catch (IOException e) {
114117
throw MergingException.wrapException(e);
115118
}
116119
}
117120

118-
/**
119-
* Merges all secondary resources with the primary resources, given that the primary resources
120-
* have been separately parsed and serialized.
121-
*/
122-
public static MergedAndroidData mergeDataAndWrite(
123-
final SerializedAndroidData primary,
124-
final Path primaryManifest,
125-
final List<? extends SerializedAndroidData> direct,
126-
final List<? extends SerializedAndroidData> transitive,
121+
/** Writes out merged data. */
122+
static MergedAndroidData writeMergedData(
127123
final Path resourcesOut,
128124
final Path assetsOut,
129-
final VariantType type,
130-
@Nullable final Path symbolsOut,
131-
@Nullable final AndroidResourceClassWriter rclassWriter,
132-
boolean throwOnResourceConflict,
133-
ListeningExecutorService executorService) {
134-
final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder();
135-
final AndroidParsedDataDeserializer deserializer = AndroidParsedDataDeserializer.create();
136-
primary.deserialize(
137-
DependencyInfo.DependencyType.PRIMARY, deserializer, primaryBuilder.consumers());
138-
ParsedAndroidData primaryData = primaryBuilder.build();
139-
return mergeDataAndWrite(
140-
primaryData,
141-
primaryManifest,
142-
direct,
143-
transitive,
144-
resourcesOut,
145-
assetsOut,
146-
type,
147-
symbolsOut,
148-
rclassWriter,
149-
deserializer,
150-
throwOnResourceConflict,
151-
executorService);
152-
}
153-
154-
/** Merges all secondary resources with the primary resources. */
155-
private static MergedAndroidData mergeDataAndWrite(
156-
final ParsedAndroidData primary,
157-
final Path primaryManifest,
158-
final List<? extends SerializedAndroidData> direct,
159-
final List<? extends SerializedAndroidData> transitive,
160-
final Path resourcesOut,
161-
final Path assetsOut,
162-
final VariantType type,
163125
@Nullable final Path symbolsOut,
164126
@Nullable AndroidResourceClassWriter rclassWriter,
165-
AndroidParsedDataDeserializer deserializer,
166-
boolean throwOnResourceConflict,
167-
ListeningExecutorService executorService) {
127+
ListeningExecutorService executorService,
128+
UnwrittenMergedAndroidData merged) {
168129
Stopwatch timer = Stopwatch.createStarted();
169130
try {
170-
UnwrittenMergedAndroidData merged =
171-
mergeData(
172-
executorService,
173-
transitive,
174-
direct,
175-
primary,
176-
primaryManifest,
177-
type != VariantType.LIBRARY,
178-
deserializer,
179-
throwOnResourceConflict,
180-
ContentComparingChecker.create());
181131
timer.reset().start();
182132
if (symbolsOut != null) {
183133
AndroidDataSerializer serializer = AndroidDataSerializer.create();
@@ -206,7 +156,7 @@ private static MergedAndroidData mergeDataAndWrite(
206156
}
207157
}
208158

209-
private static UnwrittenMergedAndroidData mergeData(
159+
static UnwrittenMergedAndroidData mergeData(
210160
ListeningExecutorService executorService,
211161
List<? extends SerializedAndroidData> transitive,
212162
List<? extends SerializedAndroidData> direct,

0 commit comments

Comments
 (0)