Skip to content

Commit

Permalink
Add option to extract proguard configurations in jvm_import
Browse files Browse the repository at this point in the history
Some jars include proguard specs in META-INF/proguard/
META-INF/com.android.tools
We need to extract these files in order to pass them correctly
to R8 for android builds.
Normally R8 should detect these files automatically inside the jar,
but if proguard specs are specified in META-INF/com.android.tools and
META-INF/proguard the files under META-INF/proguard are ignored.
See https://r8.googlesource.com/r8/+/refs/heads/main/src/main/java/com/android/tools/r8/R8Command.java#1394
Given that bazel uses a single deploy jar when running R8 it is likely
that both directories exist and several needed configs are ignored.

see bazelbuild/bazel#14966 (comment)
  • Loading branch information
Mauricio Galindo committed Jun 21, 2023
1 parent 90ad633 commit e3aafe1
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 2 deletions.
31 changes: 30 additions & 1 deletion private/rules/jvm_import.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# [1]: https://github.com/bazelbuild/bazel/issues/4549

load("//settings:stamp_manifest.bzl", "StampManifestProvider")
load("//settings:extract_proguard_config.bzl", "ExtractProguardConfigProvider")

def _jvm_import_impl(ctx):
if len(ctx.files.jars) != 1:
Expand All @@ -30,6 +31,26 @@ def _jvm_import_impl(ctx):
else:
outjar = injar

proguard_info = []
if ctx.attr._extract_proguard_config[ExtractProguardConfigProvider].extract_proguard_config:
spec = ctx.actions.declare_file("%s_proguard.pro" % injar.basename.split(".jar")[0])

args = ctx.actions.args()
args.add("--jar_to_spec", "%s:%s" % (injar.path, spec.path))

ctx.actions.run(
inputs = [injar],
outputs = [spec],
executable = ctx.executable._proguard_config_extractor,
arguments = [args],
mnemonic = "ExtractProguardSpec",
progress_message = "Extracting proguard config of %s" % ctx.label,
)

proguard_info = [
ProguardSpecProvider(depset(direct = [spec])),
]

compilejar = ctx.actions.declare_file("header_" + injar.basename, sibling = injar)
args = ctx.actions.args()
args.add_all(["--source", outjar, "--output", compilejar])
Expand Down Expand Up @@ -69,7 +90,7 @@ def _jvm_import_impl(ctx):
],
neverlink = ctx.attr.neverlink,
),
]
] + proguard_info

jvm_import = rule(
attrs = {
Expand All @@ -95,9 +116,17 @@ jvm_import = rule(
cfg = "exec",
default = "//private/tools/java/com/github/bazelbuild/rules_jvm_external/jar:AddJarManifestEntry",
),
"_proguard_config_extractor": attr.label(
executable = True,
cfg = "exec",
default = "//private/tools/java/com/github/bazelbuild/rules_jvm_external/jar:ExtractProguardConfig",
),
"_stamp_manifest": attr.label(
default = "@rules_jvm_external//settings:stamp_manifest",
),
"_extract_proguard_config": attr.label(
default = "@rules_jvm_external//settings:extract_proguard_config",
),
},
implementation = _jvm_import_impl,
provides = [JavaInfo],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,14 @@ java_binary(
"//private/tools/java/com/github/bazelbuild/rules_jvm_external/zip",
],
)

java_binary(
name = "ExtractProguardConfig",
srcs = [
"ExtractProguardConfig.java",
],
main_class = "com.github.bazelbuild.rules_jvm_external.jar.ExtractProguardConfig",
visibility = [
"//visibility:public",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.github.bazelbuild.rules_jvm_external.jar;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public final class ExtractProguardConfig {

private List<String> jarToSpec;

// Directories to search for proguard configurations.
// The list is sorted from highest priorty to lowest priority
// Only the first one is extracted.
private List<String> proguardDirs = Arrays.asList(
"META-INF/com.android.tools/r8",
"META-INF/com.android.tools/proguard",
"META-INF/proguard"
);

public ExtractProguardConfig(List<String> jarToSpec) {
this.jarToSpec = jarToSpec;
}

private boolean maybeCopySpec(String jarPath, String directoryName, String spec) {
try {
JarFile jarFile = new JarFile(jarPath);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.isDirectory() && entry.getName().startsWith(directoryName)) {
File outputFile = new File(spec);
FileOutputStream outputStream = new FileOutputStream(outputFile);
jarFile.getInputStream(entry).transferTo(outputStream);

outputStream.close();
return true;
}
}
jarFile.close();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}

private void extractSpec(String jar, String spec) {
boolean hadSpec = false;
for (String dir : proguardDirs) {
hadSpec = maybeCopySpec(jar, dir, spec);
if (hadSpec) {
break;
}
}

if (!hadSpec) {
try {
File file = new File(spec);
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}

public void run() {
for (String js : jarToSpec) {
String[] parts = js.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid jar_to_spec value: " + js);
}
extractSpec(parts[0], parts[1]);
}
}

public static void main(String[] args) {
List<String> jarToSpec = new ArrayList();
for (int i = 0; i < args.length; i++) {
if (args[i].equals("--jar_to_spec")) {
if (i + 1 < args.length) {
jarToSpec.add(args[i + 1]);
i++;
}
}
}

new ExtractProguardConfig(jarToSpec).run();
}
}
11 changes: 10 additions & 1 deletion settings/BUILD
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
load("//settings:stamp_manifest.bzl", "stamp_manifest")
load("//settings:extract_proguard_config.bzl", "extract_proguard_config")

package(
default_visibility = ["//visibility:public"],
)

exports_files(["stamp_manifest.bzl"])
exports_files([
"extract_proguard_config.bzl",
"stamp_manifest.bzl",
])

stamp_manifest(
name = "stamp_manifest",
build_setting_default = True,
)

extract_proguard_config(
name = "extract_proguard_config",
build_setting_default = False,
)
9 changes: 9 additions & 0 deletions settings/extract_proguard_config.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ExtractProguardConfigProvider = provider(fields = ["extract_proguard_config"])

def _impl(ctx):
return ExtractProguardConfigProvider(extract_proguard_config = ctx.build_setting_value)

extract_proguard_config = rule(
implementation = _impl,
build_setting = config.bool(flag = True),
)

0 comments on commit e3aafe1

Please sign in to comment.