Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Add native support for Android App Bundles / Dynamic Delivery #11497

Closed
nkoroste opened this issue May 26, 2020 · 26 comments
Closed
Labels
P1 I'll work on this now. (Assignee required) team-Android Issues for Android team type: feature request

Comments

@nkoroste
Copy link
Contributor

Description of the problem / feature request:

Add the ability to build and publish Android APKs using the new App Bundles / Dynamic Delivery android features. See:

https://developer.android.com/platform/technology/app-bundle
https://developer.android.com/guide/app-bundle/dynamic-delivery

Feature requests: what underlying problem are you trying to solve with this feature?

Use Bazel with App Bundles / Dynamic Delivery

What operating system are you running Bazel on?

MacOs or Linux

What's the output of bazel info release?

Any

Have you found anything relevant by searching the web?

https://developer.android.com/platform/technology/app-bundle
https://developer.android.com/guide/app-bundle/dynamic-delivery

@ahumesky ahumesky added P2 We'll consider working on this in future. (Assignee optional) and removed untriaged labels Dec 3, 2020
@rockwotj
Copy link
Contributor

Seems like this is going to be very important come this August, as without this you can't publish to the play store: https://android-developers.googleblog.com/2020/11/new-android-app-bundle-and-target-api.html

@rockwotj
Copy link
Contributor

I asked about this issue on bazel discuss and didn't get anything back:

https://groups.google.com/g/bazel-discuss/c/z8Z3-tLcaCI/m/TCKUppYHBQAJ

@ahumesky
Copy link
Contributor

Hi all,

Our approach to solving this issue is for us to provide a rule called android_application that replaces android_binary and supports building Android App bundles. The android_application rule will support configuration splits, as well as feature splits for assets and native libraries. We anticipate publishing it with enough time for adoption before the Play Store deadline.

If you would like to switch to using android_application and Bazel to generate an Android App Bundle from an existing Bazel target that uses android_binary:

  1. Replace android_binary with android_application.
  2. Move srcs (if any) to a new android_library target, then add that library to the deps of the android_application (android_applcation does not accept source files directly).
  3. Set bundle_config_file to a .pb.json bundle configuration, as per https://developer.android.com/studio/build/building-cmdline#bundleconfig to configure the splits for the application.

We'll publish additional documentation and examples when we publish the android_applcation rule.

@styurin
Copy link
Contributor

styurin commented May 19, 2021

@ahumesky How would this work for generating APKs from AAB files? Would we need to use bundletool or keep android_binary alongside android_application to be able to create APKs?

@ahumesky
Copy link
Contributor

Good question. Yes, you'd keep android_binary targets for development. To clarify, the "Replace android_binary with android_application" step was about android_binary targets you might be using for deployment to Play store.

Also, android_application will support bazel run, which will use bundletool to install an APK to a connected device.

It should also be possible to invoke bundletool in a genrule that depends on the android_application and specifies the configuration you want for the APK.

@ajanuar
Copy link

ajanuar commented May 24, 2021

@ahumesky any target when android_application will be available?

@ahumesky
Copy link
Contributor

@ajanuar I aim to publish at least a preview of the code later this week or next week.

@ajanuar
Copy link

ajanuar commented Jun 3, 2021

Any update?

@ahumesky
Copy link
Contributor

ahumesky commented Jun 3, 2021

I have the code for android_application working and building bundles (for at least a very simple app), and I think we'll have everything in position to publish a preview tomorrow (i.e. to the pre-alpha branch), but there's a chance that might slip to Monday

@ahumesky
Copy link
Contributor

ahumesky commented Jun 4, 2021

Hi all, I've published the preview to the pre-alpha branch here:
https://github.com/bazelbuild/rules_android/tree/pre-alpha

Note that to get the bundletool dependencies, you'll need to add rules_jvm_external to your workspace file if it's not there already, and there's some workspace setup now in @rules_android//:defs.bzl:

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-fa73b1a8e4846cee88240d0019b8f80d39feb1c3",
    url = "https://github.com/bazelbuild/rules_jvm_external/archive/fa73b1a8e4846cee88240d0019b8f80d39feb1c3.zip",
)

http_archive(
    name = "rules_android",
    urls = ["https://github.com/bazelbuild/rules_android/archive/5c8bdd44193894594fa01fd527c5cf3af17e7652.zip"],
    strip_prefix = "rules_android-5c8bdd44193894594fa01fd527c5cf3af17e7652",
)
load("@rules_android//:defs.bzl", "rules_android_workspace")
rules_android_workspace()

@hicksjoseph
Copy link

Thanks Alex! I am glad to see that and am looking forward to seeing how this evolves. I hope that this is useful for people. Has anyone tried it yet?

@ahumesky
Copy link
Contributor

ahumesky commented Jun 9, 2021

One more note on how to use these, the starlark code relies on some features and other changes in bazel that haven't been released yet, but should be in the next rolling release (#13505). (otherwise something like name 'JavaPluginInfo' is not defined will happen). For now, a bazel built at head should work.

@brentleyjones
Copy link
Contributor

Are those changes able to be cherry-picked into 4.2.0?

@ahumesky
Copy link
Contributor

ahumesky commented Jun 9, 2021

Good question, I'm not sure. The immediate error is from refactorings to the Java rules, so I'm not sure off-hand what it would take to cherry pick those. In general though, the Android rules are being developed internally against head, and then exported here, so for the time being the Android rules will need a fairly recent version of Bazel.

@ahumesky ahumesky added P1 I'll work on this now. (Assignee required) and removed P2 We'll consider working on this in future. (Assignee optional) labels Jun 18, 2021
@ahumesky
Copy link
Contributor

Has anyone had a chance to try building a bundle with android_application?

@Bencodes
Copy link
Contributor

@ahumesky We tested out the pre-alpha branch and hit this issue immediately so we are blocked at trying it out until we can find a workaround.

@ahumesky
Copy link
Contributor

Thanks for testing this, I'll post an update on that issue.

ahumesky added a commit to bazelbuild/rules_android that referenced this issue Jul 29, 2021
@Bencodes
Copy link
Contributor

@ahumesky We did another round of testing against the data bindings patches with the alpha branch of rules_android. We got further but hit a new failure case where custom styles that references Android resource attributes provided by 3rd party dependencies aren't being found during static linking.

Tested using rules_jvm_external and bazel HEAD.

Exception in thread "main" java.lang.RuntimeException: Error during Statically linking com.google.devtools.build.android.aapt2.CompiledResources@35aea049:
Command: bazel-out/host/bin/external/androidsdk/aapt2_binary\
        link\
        --static-lib\
        --manifest\
        /var/folders/ts/25b588x11_n7f1k_2361ybr80000gp/T/android_resources_tmp17478694338343542457/manifest-aapt-dummy/AndroidManifest.xml\
        --no-static-lib-packages\
        --no-version-vectors\
        -R\
        /var/folders/ts/25b588x11_n7f1k_2361ybr80000gp/T/android_resources_tmp17478694338343542457/filtered/bazel-out/darwin-fastbuild/bin/_migrated/lib_symbols/symbols.zip\
        -I\
        external/androidsdk/platforms/android-30/android.jar\
        --auto-add-overlay\
        -o\
        /var/folders/ts/25b588x11_n7f1k_2361ybr80000gp/T/android_resources_tmp17478694338343542457/lib.apk\
        --java\
        /var/folders/ts/25b588x11_n7f1k_2361ybr80000gp/T/android_resources_tmp17478694338343542457/java\
        --output-text-symbols\
        /var/folders/ts/25b588x11_n7f1k_2361ybr80000gp/T/android_resources_tmp17478694338343542457/R.txt
Output:
res/values/styles.xml:5: error: style attribute 'attr/autoSizeMinTextSize (aka com.demo.app:attr/autoSizeMinTextSize)' not found.
error: failed linking references.

        at com.google.devtools.build.android.CommandHelper.execute(CommandHelper.java:42)
        at com.google.devtools.build.android.AaptCommandBuilder.execute(AaptCommandBuilder.java:297)
        at com.google.devtools.build.android.aapt2.ResourceLinker.linkStatically(ResourceLinker.java:272)
        at com.google.devtools.build.android.ValidateAndLinkResourcesAction.main(ValidateAndLinkResourcesAction.java:207)
        at com.google.devtools.build.android.ResourceProcessorBusyBox$Tool$7.call(ResourceProcessorBusyBox.java:105)
        at com.google.devtools.build.android.ResourceProcessorBusyBox.processRequest(ResourceProcessorBusyBox.java:234)
        at com.google.devtools.build.android.ResourceProcessorBusyBox.main(ResourceProcessorBusyBox.java:177)
Target //:lib failed to build

BUILD

load("@rules_android//rules:rules.bzl", "android_library")

android_library(
    name = "lib",
    custom_package = "com.demo.app",
    # ... manifest, visibility, ...
    resource_files = glob(["res/**/*.xml"]),
    deps = [
        "@maven//:androidx_appcompat_appcompat",
        "@maven//:androidx_appcompat_appcompat_resources",
    ],
)

res/values/styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="FooTextAppearance">
        <item name="autoSizeMinTextSize">1sp</item>
    </style>

</resources>

@ahumesky
Copy link
Contributor

ahumesky commented Aug 3, 2021

Thanks for testing this out. I was able to repro the problem with the style resource, could you file an issue about this? I'll investigate from there.

@Bencodes
Copy link
Contributor

Bencodes commented Aug 3, 2021

@ahumesky moved the issue over to here bazelbuild/rules_android#39

@BenHenning
Copy link

BenHenning commented Sep 1, 2021

FYI with help from @ahumesky I ended up rebuilding a minimalistic version of android_application for my team that works with Bazel 4.0 since we were having some difficulty getting the new rules_android changes working. Feel free to take a look if it's helpful: oppia/oppia-android#3750. Note that there's a bunch of extra stuff in there, but folks in this thread will probably be specifically interested in: https://github.com/oppia/oppia-android/blob/d7945d56de9fd7a1ad7ff131953de296ffcf35cd/oppia_android_application.bzl.

I tried to build this in a way that would be easily swappable with rules_android once it's ready.

@ya332
Copy link

ya332 commented Jan 13, 2022

Hello, are there any updates to this aab support? It has been 3 months since last update. Are folks with APKs migrating off Bazel or simply waiting for the bazel android_application rule to publish their aps?

@rockwotj
Copy link
Contributor

We've been using https://github.com/oppia/oppia-android/blob/develop/oppia_android_application.bzl successfully to build aabs

@ya332
Copy link

ya332 commented Jan 14, 2022

@rockwotj Thanks for the link. I am using a modified version of the oppia_android_application.bzl. But I am getting NullPointerException. Did you encounter a similar issue? If so, how did you solve it?

wsluser@DESKTOP-1:/mnt/c/Users/user1/Documents/Misc/Dev/custom-mediapipe-fork$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/hairsegmentationgpu:hairlight_application
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'com_google_absl' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'com_google_benchmark' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'pybind11_bazel' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'com_google_protobuf' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'com_google_googletest' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'com_github_gflags_gflags' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'build_bazel_rules_apple' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'build_bazel_rules_swift' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'build_bazel_apple_support' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'xctestrunner' because it already exists.
DEBUG: /home/wsluser/.cache/bazel/_bazel_wsluser/27576fa3ed0872646a64b44d0d0f4d1d/external/org_tensorflow/third_party/repo.bzl:122:14:
Warning: skipping import of repository 'pybind11' because it already exists.
WARNING: /mnt/c/Users/user1/Documents/Misc/Dev/custom-mediapipe-fork/mediapipe/framework/tool/BUILD:182:24: in cc_library rule //mediapipe/framework/tool:field_data_cc_proto: target '//mediapipe/framework/tool:field_data_cc_proto' depends on deprecated target '@com_google_protobuf//:cc_wkt_protos': Only for backward compatibility. Do not use.
WARNING: /mnt/c/Users/user1/Documents/Misc/Dev/custom-mediapipe-fork/mediapipe/framework/BUILD:54:24: in cc_library rule //mediapipe/framework:calculator_cc_proto: target '//mediapipe/framework:calculator_cc_proto' depends on deprecated target '@com_google_protobuf//:cc_wkt_protos': Only for backward compatibility. Do not use.
Analyzing: target //mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/hairsegmentationgpu:hairlight_application (171 packages loaded, 12409 targets configured)
    currently loading: @org_tensorflow//tensorflow/core/kernels
FATAL: bazel crashed due to an internal error. Printing stack trace:
java.lang.RuntimeException: Unrecoverable error while evaluating node 'ConfiguredTargetKey{label=//mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/hairsegmentationgpu:hairlight_application_binary, config=BuildConfigurationValue.Key[24c3d57be09409ec5b3d2c93fae5528ea0f5267d0a2f463c174db0f2ce0825d8]}' (requested by nodes 'ConfiguredTargetKey{label=//mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/hairsegmentationgpu:hairlight_application_binary.apk, config=BuildConfigurationValue.Key[24c3d57be09409ec5b3d2c93fae5528ea0f5267d0a2f463c174db0f2ce0825d8]}')
        at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:563)
        at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:398)
        at java.base/java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(Unknown Source)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
        at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
Caused by: java.lang.NullPointerException
        at com.google.devtools.build.lib.rules.java.JavaCompilationHelper.usesAnnotationProcessing(JavaCompilationHelper.java:530)
        at com.google.devtools.build.lib.rules.java.JavaCompilationHelper.createOutputs(JavaCompilationHelper.java:187)
        at com.google.devtools.build.lib.rules.android.AndroidCommon.initJava(AndroidCommon.java:580)
        at com.google.devtools.build.lib.rules.android.AndroidCommon.init(AndroidCommon.java:517)
        at com.google.devtools.build.lib.rules.android.AndroidBinary.init(AndroidBinary.java:311)
        at com.google.devtools.build.lib.rules.android.AndroidBinary.create(AndroidBinary.java:118)
        at com.google.devtools.build.lib.rules.android.AndroidBinary.create(AndroidBinary.java:96)
        at com.google.devtools.build.lib.analysis.ConfiguredTargetFactory.createRule(ConfiguredTargetFactory.java:385)
        at com.google.devtools.build.lib.analysis.ConfiguredTargetFactory.createConfiguredTarget(ConfiguredTargetFactory.java:195)
        at com.google.devtools.build.lib.skyframe.SkyframeBuildView.createConfiguredTarget(SkyframeBuildView.java:940)
        at com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.createConfiguredTarget(ConfiguredTargetFunction.java:1031)
        at com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.compute(ConfiguredTargetFunction.java:371)
        at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:477)

hairlight_android_application.bzl

"""
Macros pertaining to building & managing Android app bundles.
"""

def _convert_apk_to_aab_module_impl(ctx):
    output_file = ctx.outputs.output_file
    input_file = ctx.attr.input_file.files.to_list()[0]

    # See aapt2 help documentation for details on the arguments passed here.
    arguments = [
        "convert",
        "--output-format",
        "proto",
        "-o",
        output_file.path,
        input_file.path,
    ]

    # Reference: https://docs.bazel.build/versions/master/skylark/lib/actions.html#run.
    ctx.actions.run(
        outputs = [output_file],
        inputs = ctx.files.input_file,
        tools = [ctx.executable._aapt2_tool],
        executable = ctx.executable._aapt2_tool.path,
        arguments = arguments,
        mnemonic = "GenerateAndroidAppBundleModuleFromApk",
        progress_message = "Generating deployable AAB",
    )
    return DefaultInfo(
        files = depset([output_file]),
        runfiles = ctx.runfiles(files = [output_file]),
    )

def _convert_module_aab_to_structured_zip_impl(ctx):
    output_file = ctx.outputs.output_file
    input_file = ctx.attr.input_file.files.to_list()[0]

    command = """
    # Extract AAB to working directory.
    WORKING_DIR=$(mktemp -d)
    unzip -d $WORKING_DIR {0}
    # Create the expected directory structure for an app bundle.
    # Reference for copying all other files to root: https://askubuntu.com/a/951768.
    mkdir -p $WORKING_DIR/assets $WORKING_DIR/dex $WORKING_DIR/manifest $WORKING_DIR/root
    mv $WORKING_DIR/*.dex $WORKING_DIR/dex/
    mv $WORKING_DIR/AndroidManifest.xml $WORKING_DIR/manifest/
    ls -d $WORKING_DIR/* | grep -v -w -E "res|assets|dex|manifest|root|resources.pb" | xargs -I{{}} sh -c "mv \\$0 $WORKING_DIR/root/ || exit 255" {{}} 2>&1 || exit $?
    # Zip up the result--this will be used by bundletool to build a deployable AAB. Note that these
    # strange file path bits are needed because zip will always retain the directory structure
    # passed via arguments (necessitating changing into the working directory).
    DEST_FILE_PATH="$(pwd)/{1}"
    cd $WORKING_DIR
    zip -r $DEST_FILE_PATH .
    """.format(input_file.path, output_file.path)

    # Reference: https://docs.bazel.build/versions/main/skylark/lib/actions.html#run_shell.
    ctx.actions.run_shell(
        outputs = [output_file],
        inputs = ctx.files.input_file,
        tools = [],
        command = command,
        mnemonic = "ConvertModuleAabToStructuredZip",
        progress_message = "Generating deployable AAB",
    )
    return DefaultInfo(
        files = depset([output_file]),
        runfiles = ctx.runfiles(files = [output_file]),
    )

def _bundle_module_zip_into_deployable_aab_impl(ctx):
    output_file = ctx.outputs.output_file
    input_file = ctx.attr.input_file.files.to_list()[0]
    config_file = ctx.attr.config_file.files.to_list()[0]

    # Reference: https://developer.android.com/studio/build/building-cmdline#build_your_app_bundle_using_bundletool.
    arguments = [
        "build-bundle",
        "--modules=%s" % input_file.path,
        "--config=%s" % config_file.path,
        "--output=%s" % output_file.path,
    ]

    # Reference: https://docs.bazel.build/versions/master/skylark/lib/actions.html#run.
    ctx.actions.run(
        outputs = [output_file],
        inputs = ctx.files.input_file + ctx.files.config_file,
        tools = [ctx.executable._bundletool_tool],
        executable = ctx.executable._bundletool_tool.path,
        arguments = arguments,
        mnemonic = "GenerateDeployAabFromModuleZip",
        progress_message = "Generating deployable AAB",
    )
    return DefaultInfo(
        files = depset([output_file]),
        runfiles = ctx.runfiles(files = [output_file]),
    )

def _package_metadata_into_deployable_aab_impl(ctx):
    output_aab_file = ctx.outputs.output_aab_file
    input_aab_file = ctx.attr.input_aab_file.files.to_list()[0]
    proguard_map_file = ctx.attr.proguard_map_file.files.to_list()[0]

    command = """
    # Extract deployable AAB to working directory.
    WORKING_DIR=$(mktemp -d)
    echo $WORKING_DIR
    cp {0} $WORKING_DIR/temp.aab || exit 255
    # Change the permissions of the AAB copy so that it can be overwritten later.
    chmod 755 $WORKING_DIR/temp.aab || exit 255
    # Create directory needed for storing bundle metadata.
    mkdir -p $WORKING_DIR/BUNDLE-METADATA/com.android.tools.build.obfuscation
    # Copy over the Proguard map file.
    cp {1} $WORKING_DIR/BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map || exit 255
    $ Repackage the AAB file into the destination.
    DEST_FILE_PATH="$(pwd)/{2}"
    cd $WORKING_DIR
    zip -Dur temp.aab BUNDLE-METADATA || exit 255
    cp temp.aab $DEST_FILE_PATH || exit 255
    """.format(input_aab_file.path, proguard_map_file.path, output_aab_file.path)

    # Reference: https://docs.bazel.build/versions/main/skylark/lib/actions.html#run_shell.
    ctx.actions.run_shell(
        outputs = [output_aab_file],
        inputs = ctx.files.input_aab_file + ctx.files.proguard_map_file,
        tools = [],
        command = command,
        mnemonic = "PackageMetadataIntoDeployableAAB",
        progress_message = "Generating deployable AAB",
    )
    return DefaultInfo(
        files = depset([output_aab_file]),
        runfiles = ctx.runfiles(files = [output_aab_file]),
    )

def _generate_apks_and_install_impl(ctx):
    input_file = ctx.attr.input_file.files.to_list()[0]
    debug_keystore_file = ctx.attr.debug_keystore.files.to_list()[0]
    apks_file = ctx.actions.declare_file("%s_processed.apks" % ctx.label.name)
    deploy_shell = ctx.actions.declare_file("%s_run.sh" % ctx.label.name)

    # Reference: https://developer.android.com/studio/command-line/bundletool#generate_apks.
    # See also the Bazel BUILD file for the keystore for details on its password and alias.
    generate_apks_arguments = [
        "build-apks",
        "--bundle=%s" % input_file.path,
        "--output=%s" % apks_file.path,
        "--ks=%s" % debug_keystore_file.path,
        "--ks-pass=pass:android",
        "--ks-key-alias=androiddebugkey",
        "--key-pass=pass:android",
    ]

    # Reference: https://docs.bazel.build/versions/master/skylark/lib/actions.html#run.
    ctx.actions.run(
        outputs = [apks_file],
        inputs = ctx.files.input_file + ctx.files.debug_keystore,
        tools = [ctx.executable._bundletool_tool],
        executable = ctx.executable._bundletool_tool.path,
        arguments = generate_apks_arguments,
        mnemonic = "BuildApksFromDeployAab",
        progress_message = "Preparing AAB deploy to device",
    )

    # References: https://github.com/bazelbuild/bazel/issues/7390,
    # https://developer.android.com/studio/command-line/bundletool#deploy_with_bundletool, and
    # https://docs.bazel.build/versions/main/skylark/rules.html#executable-rules-and-test-rules.
    # Note that the bundletool can be executed directly since Bazel creates a wrapper script that
    # utilizes its own internal Java toolchain.
    ctx.actions.write(
        output = deploy_shell,
        content = """
        #!/bin/sh
        {0} install-apks --apks={1}
        echo The APK should now be installed
        """.format(ctx.executable._bundletool_tool.short_path, apks_file.short_path),
        is_executable = True,
    )

    # Reference for including necessary runfiles for Java:
    # https://github.com/bazelbuild/bazel/issues/487#issuecomment-178119424.
    runfiles = ctx.runfiles(
        files = [
            ctx.executable._bundletool_tool,
            apks_file,
        ],
    ).merge(ctx.attr._bundletool_tool.default_runfiles)
    return DefaultInfo(
        executable = deploy_shell,
        runfiles = runfiles,
    )

_convert_apk_to_module_aab = rule(
    attrs = {
        "input_file": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "output_file": attr.output(
            mandatory = True,
        ),
        "_aapt2_tool": attr.label(
            executable = True,
            cfg = "host",
            default = "@androidsdk//:aapt2_binary",
        ),
    },
    implementation = _convert_apk_to_aab_module_impl,
)

_convert_module_aab_to_structured_zip = rule(
    attrs = {
        "input_file": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "output_file": attr.output(
            mandatory = True,
        ),
    },
    implementation = _convert_module_aab_to_structured_zip_impl,
)

_bundle_module_zip_into_deployable_aab = rule(
    attrs = {
        "input_file": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "config_file": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "output_file": attr.output(
            mandatory = True,
        ),
        "_bundletool_tool": attr.label(
            executable = True,
            cfg = "host",
            default = "//third_party:android_bundletool",
        ),
    },
    implementation = _bundle_module_zip_into_deployable_aab_impl,
)

_package_metadata_into_deployable_aab = rule(
    attrs = {
        "input_aab_file": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "proguard_map_file": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "output_aab_file": attr.output(
            mandatory = True,
        ),
    },
    implementation = _package_metadata_into_deployable_aab_impl,
)

_generate_apks_and_install = rule(
    attrs = {
        "input_file": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "debug_keystore": attr.label(
            allow_single_file = True,
            mandatory = True,
        ),
        "_bundletool_tool": attr.label(
            executable = True,
            cfg = "host",
            default = "//third_party:android_bundletool",
        ),
    },
    executable = True,
    implementation = _generate_apks_and_install_impl,
)

def hairlight_android_application(name, config_file, proguard_generate_mapping, **kwargs):
    """
    Creates an Android App Bundle (AAB) binary with the specified name and arguments.
    Args:
        name: str. The name of the Android App Bundle to build. This will corresponding to the name
            of the generated .aab file.
        config_file: target. The path to the .pb.json bundle configuration file for this build.
        proguard_generate_mapping: boolean. Whether to perform a Proguard optimization step &
            generate Proguard mapping corresponding to the obfuscation step.
        **kwargs: additional arguments. See android_binary for the exact arguments that are
            available.
    """
    binary_name = "%s_binary" % name
    module_aab_name = "%s_module_aab" % name
    module_zip_name = "%s_module_zip" % name
    deployable_aab_name = "%s_deployable" % name
    native.android_binary(
        name = binary_name,
        tags = ["manual"],
        proguard_generate_mapping = proguard_generate_mapping,
        **kwargs
    )
    _convert_apk_to_module_aab(
        name = module_aab_name,
        input_file = ":%s.apk" % binary_name,
        output_file = "%s.aab" % module_aab_name,
        tags = ["manual"],
    )
    _convert_module_aab_to_structured_zip(
        name = module_zip_name,
        input_file = ":%s.aab" % module_aab_name,
        output_file = "%s.zip" % module_zip_name,
        tags = ["manual"],
    )
    # No extra package step is needed if there's no Proguard map file.
    _bundle_module_zip_into_deployable_aab(
        name = name,
        input_file = ":%s.zip" % module_zip_name,
        config_file = config_file,
        output_file = "%s.aab" % name,
        tags = ["manual"],
    )

def declare_deployable_application(name, aab_target):
    """
    Creates a new target that can be run with 'bazel run' to install an AAB file.
    Example:
        declare_deployable_application(
            name = "install_oppia_prod",
            aab_target = "//:oppia_prod",
        )
        $ bazel run //:install_oppia_prod
    This will build (if necessary) and install the correct APK derived from the Android app bundle
    on the locally attached emulator or device. Note that this command does not support targeting a
    specific device so it will not work if more than one device is available via 'adb devices'.
    Args:
        name: str. The name of the runnable target to install an AAB file on a local device.
        aab_target: target. The target (declared via oppia_android_application) that should be made
            installable.
    """
    _generate_apks_and_install(
        name = name,
        input_file = aab_target,
        debug_keystore = "@bazel_tools//tools/android:debug_keystore",
        tags = ["manual"],
    )

BUILD

# Copyright 2019 The MediaPipe Authors.
#
# 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.

licenses(["notice"])
load("//mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/hairsegmentationgpu:hairlight_android_application.bzl", "declare_deployable_application", "hairlight_android_application")
package(default_visibility = ["//visibility:private"])

cc_binary(
    name = "libmediapipe_jni.so",
    linkshared = 1,
    linkstatic = 1,
    deps = [
        "//mediapipe/graphs/hair_segmentation:mobile_calculators",
        "//mediapipe/java/com/google/mediapipe/framework/jni:mediapipe_framework_jni",
    ],
)

cc_library(
    name = "mediapipe_jni_lib",
    srcs = [":libmediapipe_jni.so"],
    alwayslink = 1,
)

android_binary(
    name = "hairlight_binary",
    srcs = glob(["*.java"]),
    assets = [
        "//mediapipe/graphs/hair_segmentation:mobile_gpu.binarypb",
        "//mediapipe/models:hair_segmentation.tflite",
    ],
    assets_dir = "",
    manifest = "//mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/basic:AndroidManifest.xml",
    manifest_values = {
        "applicationId": "com.hairlight.hairsegmentation.apps.hairsegmentationgpu",
        "mainActivity": "com.hairlight.hairsegmentation.apps.basic.MainActivity"
    },
    multidex = "native",
    deps = [
        ":mediapipe_jni_lib",
        "//mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/basic:basic_lib",
    ]
)

hairlight_android_application(
    name = "hairlight_application",
    custom_package = "org.oppia.android",
    testonly = False,
    enable_data_binding = True,
    config_file = ":bundle_config.pb.json",
    manifest = "//mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/basic:AndroidManifest.xml",
    manifest_values = {
        "applicationId": "com.hairlight.hairsegmentation.apps.hairsegmentationgpu",
        "mainActivity": "com.hairlight.hairsegmentation.apps.basic.MainActivity"
    },
    proguard_generate_mapping = False,
    proguard_specs = [],
    shrink_resources = False,
    multidex = "native",
    deps = [
        ":mediapipe_jni_lib",
        "//mediapipe/examples/android/src/java/com/hairlight/hairsegmentation/apps/basic:basic_lib",
    ],
)

I am on bazel version 4.2.1

@BenHenning
Copy link

@ya332 you should re-run the command with sandbox_debug and verbose_failures to see what the specific sandbox command is that went wrong (you should be able to re-run that specific command and produce the same failure, plus the sandbox directories will stick around with sandbox_debug so you can inspect them to figure out what's going wrong).

@ahumesky
Copy link
Contributor

A minimal android_application is in rules_android, with fixes here bazelbuild/rules_android#271, for any new issues please open them in rules_android

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P1 I'll work on this now. (Assignee required) team-Android Issues for Android team type: feature request
Projects
None yet
Development

No branches or pull requests