Initial commit
This commit is contained in:
529
devtools/packaging/scripts/android/add_packs.py
Normal file
529
devtools/packaging/scripts/android/add_packs.py
Normal file
@@ -0,0 +1,529 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2021 Google LLC
|
||||
#
|
||||
# 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.
|
||||
"""Script to add Asset Packs to an already built Android App Bundle."""
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import typing
|
||||
import xml.dom.minidom
|
||||
import zipfile
|
||||
|
||||
import config_pb2
|
||||
|
||||
import google.protobuf.json_format as json_format
|
||||
|
||||
FASTFOLLOW = "dist:fast-follow"
|
||||
ONDEMAND = "dist:on-demand"
|
||||
UPFRONT = "dist:install-time"
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
"""Parse input arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Augments an Android App Bundle with given asset packs.",
|
||||
add_help=True)
|
||||
parser.add_argument(
|
||||
"--androidsdk", required=True, help="Android SDK location")
|
||||
parser.add_argument("--sdkver", required=True, help="Android SDK version")
|
||||
parser.add_argument(
|
||||
"--buildtoolsver", required=True, help="Android Build Tools version")
|
||||
parser.add_argument(
|
||||
"--bundletool", required=True, help="Path to Bundletool jar file")
|
||||
parser.add_argument(
|
||||
"--inputbundle", required=True, help="App Bundle to augment")
|
||||
parser.add_argument(
|
||||
"--packdir", required=True, help="Folder to read assets packs from")
|
||||
parser.add_argument(
|
||||
"--packnames",
|
||||
required=True,
|
||||
help="Comma separated list of asset pack files")
|
||||
parser.add_argument("--output", required=True, help="Output App Bundle")
|
||||
parser.add_argument(
|
||||
"--overwrite",
|
||||
required=False,
|
||||
action="store_true",
|
||||
help="Overwrite existing files")
|
||||
parser.add_argument(
|
||||
"--striptcfsuffixes",
|
||||
required=False,
|
||||
action="store_true",
|
||||
help="Enable removal of #tcf_xxx suffixes in asset pack folder names")
|
||||
parser.add_argument(
|
||||
"--compressinstalltimeassets",
|
||||
required=False,
|
||||
action="store_true",
|
||||
help=("Compress assets within install time asset packs."
|
||||
"This will not apply to on demand or fast follow asset packs"
|
||||
"Setting is overridden for files matched in the uncompressed glob"))
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def abs_expand_all(path: str) -> str:
|
||||
return os.path.abspath(os.path.expandvars(os.path.expanduser(path)))
|
||||
|
||||
|
||||
def get_aapt2_bin_path(args: argparse.Namespace) -> str:
|
||||
"""Retrieve the path for the aapt2 binary."""
|
||||
android_sdk_path = abs_expand_all(args.androidsdk)
|
||||
build_tools_version = args.buildtoolsver
|
||||
|
||||
aapt2_bin_path: str
|
||||
if platform.system() == "Windows":
|
||||
aapt2_bin_path = os.path.join(android_sdk_path, "build-tools",
|
||||
build_tools_version, "aapt2.exe")
|
||||
else:
|
||||
aapt2_bin_path = os.path.join(android_sdk_path, "build-tools",
|
||||
build_tools_version, "aapt2")
|
||||
|
||||
if not os.path.exists(aapt2_bin_path):
|
||||
print(
|
||||
"Cannot find AAPT2 at {aapt2_bin_path}".format(
|
||||
aapt2_bin_path=aapt2_bin_path),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
return aapt2_bin_path
|
||||
|
||||
|
||||
def get_sdk_jar_path(args: argparse.Namespace) -> str:
|
||||
"""Retrieve the path for the android SDK JAR file."""
|
||||
|
||||
android_sdk_path = abs_expand_all(args.androidsdk)
|
||||
sdk_ver = args.sdkver
|
||||
|
||||
sdk_jar_path = os.path.join(android_sdk_path, "platforms",
|
||||
"android-" + sdk_ver, "android.jar")
|
||||
if not os.path.exists(sdk_jar_path):
|
||||
print(
|
||||
"Cannot find android.jar at {sdk_jar_path}".format(
|
||||
sdk_jar_path=sdk_jar_path),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
return sdk_jar_path
|
||||
|
||||
|
||||
def get_bundletool_path(args: argparse.Namespace) -> str:
|
||||
"""Retrieve the path for the BundleTool JAR file."""
|
||||
|
||||
bundletool_path = abs_expand_all(args.bundletool)
|
||||
if not os.path.exists(bundletool_path):
|
||||
print(
|
||||
"Cannot find Bundletool at {bundletool_path}".format(
|
||||
bundletool_path=bundletool_path),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
return bundletool_path
|
||||
|
||||
|
||||
def get_packs(args: argparse.Namespace) -> (str, typing.List[str]):
|
||||
"""Retrieve the pack directory and pack names from the input flags."""
|
||||
|
||||
pack_dir = abs_expand_all(args.packdir)
|
||||
if not os.path.exists(pack_dir):
|
||||
print(
|
||||
"Cannot find asset pack directory at {pack_dir}".format(
|
||||
pack_dir=pack_dir),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
pack_names = args.packnames.split(",")
|
||||
for pack_name in pack_names:
|
||||
pack_path = os.path.join(pack_dir, pack_name)
|
||||
if not os.path.exists(pack_path):
|
||||
print(
|
||||
"Cannot find asset pack {pack_name} at {pack_path}".format(
|
||||
pack_name=pack_name, pack_path=pack_path),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
return (pack_dir, pack_names)
|
||||
|
||||
|
||||
def get_input_bundle_path(args: argparse.Namespace) -> str:
|
||||
bundle_path = abs_expand_all(args.inputbundle)
|
||||
if not os.path.exists(bundle_path):
|
||||
print(
|
||||
"Cannot find input app bundle_path {bundle_path}".format(
|
||||
bundle_path=bundle_path),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
return bundle_path
|
||||
|
||||
|
||||
def get_output_path(args: argparse.Namespace) -> str:
|
||||
"""Retrieve the output file name."""
|
||||
output_path = abs_expand_all(args.output)
|
||||
if os.path.exists(output_path):
|
||||
if os.path.isdir(output_path):
|
||||
print(
|
||||
"Output location {output_path} is a directory. Specify a file path instead."
|
||||
.format(output_path=output_path))
|
||||
sys.exit(-1)
|
||||
if not args.overwrite:
|
||||
print(
|
||||
"Output file {output_path} exists. Specify --overwrite to bypass. Exiting."
|
||||
.format(output_path=output_path))
|
||||
sys.exit(-1)
|
||||
else:
|
||||
os.remove(output_path)
|
||||
return output_path
|
||||
|
||||
|
||||
def purge_files(directory: str, pattern: str) -> None:
|
||||
"""Purge the files inside a directory that match the given pattern."""
|
||||
for root_dir, _, filenames in os.walk(directory):
|
||||
for filename in fnmatch.filter(filenames, pattern):
|
||||
try:
|
||||
os.remove(os.path.join(root_dir, filename))
|
||||
except OSError as e:
|
||||
print(
|
||||
"Error while deleting {filename}: {e}".format(
|
||||
filename=filename, e=e),
|
||||
file=sys.stderr)
|
||||
|
||||
|
||||
def purge_subdirs(directory: str, pattern: str) -> None:
|
||||
"""Purge the subdirectories inside a directory that match the given pattern."""
|
||||
for root_dir, subdirs, _ in os.walk(directory):
|
||||
for filename in fnmatch.filter(subdirs, pattern):
|
||||
try:
|
||||
shutil.rmtree(os.path.join(root_dir, filename))
|
||||
except FileNotFoundError as e:
|
||||
print(
|
||||
"Error while deleting {filename}: {e}".format(
|
||||
filename=filename, e=e),
|
||||
file=sys.stderr)
|
||||
|
||||
|
||||
def parse_bundle_metadata(bundle_folder: str) -> typing.List[str]:
|
||||
"""Parse the Bundle Metadata from the given bundle directory."""
|
||||
metadata = []
|
||||
metadata_folder = os.path.join(bundle_folder, "BUNDLE-METADATA")
|
||||
if not os.path.isdir(metadata_folder):
|
||||
return
|
||||
|
||||
for folder in os.listdir(metadata_folder):
|
||||
inner_directory = os.path.join(metadata_folder, folder)
|
||||
if not os.path.isdir(inner_directory):
|
||||
continue
|
||||
|
||||
for file in os.listdir(inner_directory):
|
||||
entry = "{path_in_bundle}:{physical_file_path}".format(
|
||||
path_in_bundle=os.path.join(folder, file),
|
||||
physical_file_path=os.path.join(inner_directory, file))
|
||||
metadata.append(entry)
|
||||
return metadata
|
||||
|
||||
|
||||
def get_min_sdk_version(bundle_path: str, bundletool: str) -> int:
|
||||
"""Get the minimum supported SDK version from an App Bundle file."""
|
||||
bundletool_cmd = [
|
||||
"java", "-jar", bundletool, "dump", "manifest", "--bundle", bundle_path,
|
||||
"--xpath", "/manifest/uses-sdk/@android:minSdkVersion"
|
||||
]
|
||||
|
||||
print("Running {bundletool_cmd}".format(bundletool_cmd=bundletool_cmd))
|
||||
min_sdk = subprocess.check_output(bundletool_cmd)
|
||||
return int(min_sdk.decode("utf-8").rstrip())
|
||||
|
||||
|
||||
def get_strip_tcf_suffixes(args: argparse.Namespace) -> bool:
|
||||
return args.striptcfsuffixes
|
||||
|
||||
|
||||
def get_compress_install_time_assets(args: argparse.Namespace) -> bool:
|
||||
return args.compressinstalltimeassets
|
||||
|
||||
|
||||
def get_asset_pack_type(path: str) -> str:
|
||||
"""Retrieve the Asset Pack delivery type from an AndroidManifest.xml file."""
|
||||
xmldoc = xml.dom.minidom.parse(path)
|
||||
tags = xmldoc.getElementsByTagName(ONDEMAND)
|
||||
if tags.length:
|
||||
return ONDEMAND
|
||||
|
||||
tags = xmldoc.getElementsByTagName(FASTFOLLOW)
|
||||
if tags.length:
|
||||
return FASTFOLLOW
|
||||
|
||||
tags = xmldoc.getElementsByTagName(UPFRONT)
|
||||
if tags.length:
|
||||
return UPFRONT
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def extract_bundle_config(bundle_folder: str, add_standalone_config: bool,
|
||||
strip_tcf_suffixes: bool,
|
||||
compress_install_time_assets: bool) -> str:
|
||||
"""Extract the BundleConfig contents and optionally add standalone_config."""
|
||||
bundle_config = config_pb2.BundleConfig()
|
||||
with open(os.path.join(bundle_folder, "BundleConfig.pb"), mode="rb") as f:
|
||||
content = f.read()
|
||||
bundle_config.ParseFromString(content)
|
||||
|
||||
if compress_install_time_assets:
|
||||
json_format.ParseDict(
|
||||
{
|
||||
"compression": {
|
||||
"install_time_asset_module_default_compression": "COMPRESSED"
|
||||
}
|
||||
}, bundle_config)
|
||||
|
||||
if add_standalone_config:
|
||||
json_format.ParseDict(
|
||||
{
|
||||
"optimizations": {
|
||||
"standalone_config": {
|
||||
"split_dimension": [{
|
||||
"value": "ABI",
|
||||
"negate": True
|
||||
}, {
|
||||
"value": "TEXTURE_COMPRESSION_FORMAT",
|
||||
"negate": True
|
||||
}, {
|
||||
"value": "LANGUAGE",
|
||||
"negate": True
|
||||
}, {
|
||||
"value": "SCREEN_DENSITY",
|
||||
"negate": True
|
||||
}],
|
||||
"strip_64_bit_libraries": True
|
||||
}
|
||||
}
|
||||
}, bundle_config)
|
||||
|
||||
# Check if game already defines any split_config dimensions
|
||||
dimensions = []
|
||||
try:
|
||||
dimensions = list(json_format.MessageToDict(bundle_config)["optimizations"]
|
||||
["splitsConfig"]["splitDimension"])
|
||||
except KeyError:
|
||||
print("No existing split dimensions")
|
||||
|
||||
tcf_split_dimension = {
|
||||
"value": "TEXTURE_COMPRESSION_FORMAT",
|
||||
"negate": False,
|
||||
"suffix_stripping": {
|
||||
"enabled": True,
|
||||
"default_suffix": ""
|
||||
}
|
||||
}
|
||||
|
||||
# Add the TCF split dimension, if needed
|
||||
if strip_tcf_suffixes:
|
||||
dimensions.append(tcf_split_dimension)
|
||||
|
||||
if strip_tcf_suffixes:
|
||||
json_format.ParseDict(
|
||||
{
|
||||
"optimizations": {
|
||||
"splits_config": {
|
||||
"split_dimension": dimensions
|
||||
}
|
||||
}
|
||||
}, bundle_config)
|
||||
|
||||
output_path = os.path.join(bundle_folder, "BundleConfig.pb.json")
|
||||
with open(output_path, mode="w") as f:
|
||||
print(json_format.MessageToJson(bundle_config), file=f)
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
def aapt_link(input_manifest_path: str, output_manifest_folder: str,
|
||||
aapt2_bin_path: str, sdk_jar_path: str):
|
||||
"""Run aapt link to convert the manifest to proto format."""
|
||||
aapt_cmd = [
|
||||
aapt2_bin_path, "link", "--proto-format", "--output-to-dir", "-o",
|
||||
output_manifest_folder, "--manifest", input_manifest_path, "-I",
|
||||
sdk_jar_path
|
||||
]
|
||||
print(" Running {aapt_cmd}".format(aapt_cmd=aapt_cmd))
|
||||
exit_code = subprocess.call(aapt_cmd)
|
||||
if exit_code != 0:
|
||||
print(
|
||||
"Error executing {aapt_cmd}".format(aapt_cmd=aapt_cmd), file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def process_packs(packs_folder: str, bundle_folder: str,
|
||||
pack_names: typing.List[str], aapt2_bin_path: str,
|
||||
sdk_jar_path: str) -> bool:
|
||||
"""Repackage all packs into modules."""
|
||||
print("Processing packs...")
|
||||
|
||||
has_upfront_pack = False
|
||||
|
||||
for pack_name in pack_names:
|
||||
print(" Pack {pack_name}".format(pack_name=pack_name))
|
||||
pack_basename = os.path.splitext(pack_name)[0]
|
||||
pack_folder = os.path.join(bundle_folder, pack_basename)
|
||||
os.makedirs(pack_folder)
|
||||
|
||||
print(" Extracting pack {pack_name} to {pack_folder}.".format(
|
||||
pack_name=pack_name, pack_folder=pack_folder))
|
||||
pack_zip_path = zipfile.ZipFile(
|
||||
os.path.join(packs_folder, pack_name), "r")
|
||||
pack_zip_path.extractall(path=pack_folder)
|
||||
pack_zip_path.close()
|
||||
|
||||
print(" Processing manifest.")
|
||||
manifest_folder = os.path.join(pack_folder, "manifest")
|
||||
original_manifest_path = os.path.join(manifest_folder,
|
||||
"AndroidManifest.xml")
|
||||
|
||||
has_upfront_pack = has_upfront_pack or (
|
||||
get_asset_pack_type(original_manifest_path) == UPFRONT)
|
||||
|
||||
tmp_manifest_path = os.path.join(bundle_folder, "manifest.xml")
|
||||
shutil.move(original_manifest_path, tmp_manifest_path)
|
||||
|
||||
aapt_link(tmp_manifest_path, manifest_folder,
|
||||
aapt2_bin_path, sdk_jar_path)
|
||||
|
||||
print(" Cleaning up\n")
|
||||
os.remove(os.path.join(manifest_folder, "resources.pb"))
|
||||
os.remove(tmp_manifest_path)
|
||||
return has_upfront_pack
|
||||
|
||||
|
||||
def clear_autogenerated_bundle_files(bundle_folder: str):
|
||||
print("Removing old META_INF and BundleConfig.pb")
|
||||
shutil.rmtree(os.path.join(bundle_folder, "META-INF"), ignore_errors=True)
|
||||
os.remove(os.path.join(bundle_folder, "BundleConfig.pb"))
|
||||
|
||||
print("Removing old __MACOSX folders")
|
||||
purge_subdirs(bundle_folder, "__MACOSX")
|
||||
|
||||
print("Removing old .DS_Store files")
|
||||
purge_files(bundle_folder, ".DS_Store")
|
||||
|
||||
|
||||
def zip_module(module_folder: str, bundle_folder: str) -> str:
|
||||
|
||||
print(" Module {module_folder}".format(module_folder=module_folder))
|
||||
basename = os.path.join(bundle_folder, module_folder)
|
||||
module_zip = shutil.make_archive(basename, "zip", root_dir=basename)
|
||||
return module_zip
|
||||
|
||||
|
||||
def build_bundle(module_zip_files: typing.List[str], output_path: str,
|
||||
bundle_config_path: str, metadata: typing.List[str],
|
||||
bundletool_path: str):
|
||||
"""Build the bundle using bundletool build-bundle."""
|
||||
bundletool_cmd = [
|
||||
"java", "-jar", bundletool_path, "build-bundle", "--modules",
|
||||
",".join(module_zip_files), "--output", output_path, "--config",
|
||||
bundle_config_path
|
||||
]
|
||||
|
||||
for entry in metadata:
|
||||
bundletool_cmd.append("--metadata-file")
|
||||
bundletool_cmd.append(entry)
|
||||
|
||||
print("Running {bundletool_cmd}".format(bundletool_cmd=bundletool_cmd))
|
||||
exit_code = subprocess.call(bundletool_cmd)
|
||||
|
||||
if exit_code != 0:
|
||||
print(
|
||||
"Error executing {bundletool_cmd}".format(
|
||||
bundletool_cmd=bundletool_cmd),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
|
||||
bundle_path = get_input_bundle_path(args)
|
||||
output_path = get_output_path(args)
|
||||
|
||||
aapt2_bin_path = get_aapt2_bin_path(args)
|
||||
sdk_jar_path = get_sdk_jar_path(args)
|
||||
bundletool_path = get_bundletool_path(args)
|
||||
(pack_dir, pack_names) = get_packs(args)
|
||||
strip_tcf_suffixes = get_strip_tcf_suffixes(args)
|
||||
compress_install_time_assets = get_compress_install_time_assets(args)
|
||||
|
||||
with tempfile.TemporaryDirectory() as bundle_folder:
|
||||
print("Extracting input app bundle to {bundle_folder}".format(
|
||||
bundle_folder=bundle_folder))
|
||||
bundle_zip_path = zipfile.ZipFile(bundle_path, "r")
|
||||
bundle_zip_path.extractall(path=bundle_folder)
|
||||
bundle_zip_path.close()
|
||||
|
||||
has_upfront_pack = process_packs(pack_dir, bundle_folder, pack_names,
|
||||
aapt2_bin_path, sdk_jar_path)
|
||||
|
||||
uses_upfront_pre_l = has_upfront_pack and get_min_sdk_version(
|
||||
bundle_path, bundletool_path) < 21
|
||||
|
||||
bundle_config_path = extract_bundle_config(bundle_folder,
|
||||
uses_upfront_pre_l,
|
||||
strip_tcf_suffixes,
|
||||
compress_install_time_assets)
|
||||
|
||||
clear_autogenerated_bundle_files(bundle_folder)
|
||||
|
||||
print("Parsing bundle metadata...")
|
||||
metadata = parse_bundle_metadata(bundle_folder)
|
||||
|
||||
print("Zipping module folders...")
|
||||
metadata = []
|
||||
module_folders = (
|
||||
module_folder for module_folder in os.listdir(bundle_folder)
|
||||
if module_folder != "BUNDLE-METADATA" and
|
||||
os.path.isdir(os.path.join(bundle_folder, module_folder)))
|
||||
module_zip_files = [
|
||||
zip_module(module_folder, bundle_folder)
|
||||
for module_folder in module_folders
|
||||
]
|
||||
|
||||
bundletool_cmd = [
|
||||
"java", "-jar", bundletool_path, "build-bundle", "--modules",
|
||||
",".join(module_zip_files), "--output", output_path, "--config",
|
||||
bundle_config_path
|
||||
]
|
||||
|
||||
for entry in metadata:
|
||||
bundletool_cmd.append("--metadata-file")
|
||||
bundletool_cmd.append(entry)
|
||||
|
||||
print("Running {bundletool_cmd}".format(bundletool_cmd=bundletool_cmd))
|
||||
exit_code = subprocess.call(bundletool_cmd)
|
||||
|
||||
if exit_code != 0:
|
||||
print(
|
||||
"Error executing {bundletool_cmd}".format(
|
||||
bundletool_cmd=bundletool_cmd),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
print("Augmented app bundle is ready at {output_path}".format(
|
||||
output_path=output_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
devtools/packaging/scripts/android/bundletool-all-1.15.1.jar
Normal file
BIN
devtools/packaging/scripts/android/bundletool-all-1.15.1.jar
Normal file
Binary file not shown.
197
devtools/packaging/scripts/android/config.proto
Normal file
197
devtools/packaging/scripts/android/config.proto
Normal file
@@ -0,0 +1,197 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package android.bundle;
|
||||
|
||||
option java_package = "com.android.bundle";
|
||||
|
||||
message BundleConfig {
|
||||
Bundletool bundletool = 1;
|
||||
Optimizations optimizations = 2;
|
||||
Compression compression = 3;
|
||||
// Resources to be always kept in the master split.
|
||||
MasterResources master_resources = 4;
|
||||
ApexConfig apex_config = 5;
|
||||
// APKs to be signed with the same key as generated APKs.
|
||||
repeated UnsignedEmbeddedApkConfig unsigned_embedded_apk_config = 6;
|
||||
AssetModulesConfig asset_modules_config = 7;
|
||||
|
||||
enum BundleType {
|
||||
REGULAR = 0;
|
||||
APEX = 1;
|
||||
ASSET_ONLY = 2;
|
||||
}
|
||||
BundleType type = 8;
|
||||
}
|
||||
|
||||
message Bundletool {
|
||||
reserved 1;
|
||||
// Version of BundleTool used to build the Bundle.
|
||||
string version = 2;
|
||||
}
|
||||
|
||||
message Compression {
|
||||
// Glob matching the list of files to leave uncompressed in the APKs.
|
||||
// The matching is done against the path of files in the APK, thus excluding
|
||||
// the name of the modules, and using forward slash ("/") as a name separator.
|
||||
// Examples: "res/raw/**", "assets/**/*.uncompressed", etc.
|
||||
repeated string uncompressed_glob = 1;
|
||||
|
||||
enum AssetModuleCompression {
|
||||
UNSPECIFIED = 0;
|
||||
// Assets are left uncompressed in the generated asset module.
|
||||
UNCOMPRESSED = 1;
|
||||
// Assets are compressed in the generated asset module.
|
||||
// This option can be overridden at a finer granularity by specifying
|
||||
// files or folders to keep uncompressed in `uncompressed_glob`.
|
||||
// This option should only be used if the app is able to handle compressed
|
||||
// asset module content at runtime (some runtime APIs may misbehave).
|
||||
COMPRESSED = 2;
|
||||
}
|
||||
|
||||
// Default compression strategy for install-time asset modules.
|
||||
// If the compression strategy indicates to compress a file and the same file
|
||||
// matches one of the `uncompressed_glob` values, the `uncompressed_glob`
|
||||
// takes precedence (the file is left uncompressed in the generated APK).
|
||||
//
|
||||
// If unspecified, asset module content is left uncompressed in the
|
||||
// generated asset modules.
|
||||
//
|
||||
// Note: this flag only configures the compression strategy for install-time
|
||||
// asset modules; the content of on-demand and fast-follow asset modules is
|
||||
// always kept uncompressed.
|
||||
AssetModuleCompression install_time_asset_module_default_compression = 2;
|
||||
}
|
||||
|
||||
// Resources to keep in the master split.
|
||||
message MasterResources {
|
||||
// Resource IDs to be kept in master split.
|
||||
repeated int32 resource_ids = 1;
|
||||
// Resource names to be kept in master split.
|
||||
repeated string resource_names = 2;
|
||||
}
|
||||
|
||||
message Optimizations {
|
||||
SplitsConfig splits_config = 1;
|
||||
// This is for uncompressing native libraries on M+ devices (L+ devices on
|
||||
// instant apps).
|
||||
UncompressNativeLibraries uncompress_native_libraries = 2;
|
||||
// This is for uncompressing dex files on P+ devices.
|
||||
UncompressDexFiles uncompress_dex_files = 3;
|
||||
// Configuration for the generation of standalone APKs.
|
||||
// If no StandaloneConfig is set, the configuration is inherited from
|
||||
// splits_config.
|
||||
StandaloneConfig standalone_config = 4;
|
||||
}
|
||||
|
||||
message UncompressNativeLibraries {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
message UncompressDexFiles {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
// Optimization configuration used to generate Split APKs.
|
||||
message SplitsConfig {
|
||||
repeated SplitDimension split_dimension = 1;
|
||||
}
|
||||
|
||||
// Optimization configuration used to generate Standalone APKs.
|
||||
message StandaloneConfig {
|
||||
// Device targeting dimensions to shard.
|
||||
repeated SplitDimension split_dimension = 1;
|
||||
// Whether 64 bit libraries should be stripped from Standalone APKs.
|
||||
bool strip_64_bit_libraries = 2;
|
||||
// Dex merging strategy that should be applied to produce Standalone APKs.
|
||||
DexMergingStrategy dex_merging_strategy = 3;
|
||||
|
||||
enum DexMergingStrategy {
|
||||
// Strategy that does dex merging for applications that have minimum SDK
|
||||
// below 21 to ensure dex files from all modules are merged into one or
|
||||
// mainDexList is applied when merging into one dex is not possible. For
|
||||
// applications with minSdk >= 21 dex files from all modules are copied into
|
||||
// standalone APK as is because Android supports multiple dex files natively
|
||||
// starting from Android 5.0.
|
||||
MERGE_IF_NEEDED = 0;
|
||||
// Requires to copy dex files from all modules into standalone APK as is.
|
||||
// If an application supports SDKs below 21 this strategy puts
|
||||
// responsibility of providing dex files compatible with legacy multidex on
|
||||
// application developers.
|
||||
NEVER_MERGE = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message SplitDimension {
|
||||
enum Value {
|
||||
UNSPECIFIED_VALUE = 0;
|
||||
ABI = 1;
|
||||
SCREEN_DENSITY = 2;
|
||||
LANGUAGE = 3;
|
||||
TEXTURE_COMPRESSION_FORMAT = 4;
|
||||
DEVICE_TIER = 6;
|
||||
}
|
||||
Value value = 1;
|
||||
|
||||
// If set to 'true', indicates that APKs should *not* be split by this
|
||||
// dimension.
|
||||
bool negate = 2;
|
||||
|
||||
// Optional transformation to be applied to asset directories where
|
||||
// the targeting is encoded in the directory name (e.g: assets/foo#tcf_etc1)
|
||||
SuffixStripping suffix_stripping = 3;
|
||||
}
|
||||
|
||||
message SuffixStripping {
|
||||
// If set to 'true', indicates that the targeting suffix should be removed
|
||||
// from assets paths for this dimension when splits (e.g: "asset packs") or
|
||||
// standalone/universal APKs are generated.
|
||||
// This only applies to assets.
|
||||
// For example a folder with path "assets/level1_textures#tcf_etc1"
|
||||
// would be outputted to "assets/level1_textures". File contents are
|
||||
// unchanged.
|
||||
bool enabled = 1;
|
||||
|
||||
// The default suffix to be used for the cases where separate slices can't
|
||||
// be generated for this dimension - typically for standalone or universal
|
||||
// APKs.
|
||||
// This default suffix defines the directories to retain. The others are
|
||||
// discarded: standalone/universal APKs will contain only directories
|
||||
// targeted at this value for the dimension.
|
||||
//
|
||||
// If not set or empty, the fallback directory in each directory group will be
|
||||
// used (for example, if both "assets/level1_textures#tcf_etc1" and
|
||||
// "assets/level1_textures" are present and the default suffix is empty,
|
||||
// then only "assets/level1_textures" will be used).
|
||||
string default_suffix = 2;
|
||||
}
|
||||
|
||||
// Configuration for processing APEX bundles.
|
||||
// https://source.android.com/devices/tech/ota/apex
|
||||
message ApexConfig {
|
||||
// Configuration for processing of APKs embedded in an APEX image.
|
||||
repeated ApexEmbeddedApkConfig apex_embedded_apk_config = 1;
|
||||
}
|
||||
|
||||
message ApexEmbeddedApkConfig {
|
||||
// Android package name of the APK.
|
||||
string package_name = 1;
|
||||
|
||||
// Path to the APK within the APEX system image.
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message UnsignedEmbeddedApkConfig {
|
||||
// Path to the APK inside the module (e.g. if the path inside the bundle
|
||||
// is split/assets/example.apk, this will be assets/example.apk).
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message AssetModulesConfig {
|
||||
// App versionCodes that will be updated with these asset modules.
|
||||
// Only relevant for asset-only bundles.
|
||||
repeated int64 app_version = 1;
|
||||
|
||||
// Version tag for the asset upload.
|
||||
// Only relevant for asset-only bundles.
|
||||
string asset_version_tag = 2;
|
||||
}
|
||||
187
devtools/packaging/scripts/android/config_pb2.py
Normal file
187
devtools/packaging/scripts/android/config_pb2.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: config.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x63onfig.proto\x12\x0e\x61ndroid.bundle\"\x91\x04\n\x0c\x42undleConfig\x12.\n\nbundletool\x18\x01 \x01(\x0b\x32\x1a.android.bundle.Bundletool\x12\x34\n\roptimizations\x18\x02 \x01(\x0b\x32\x1d.android.bundle.Optimizations\x12\x30\n\x0b\x63ompression\x18\x03 \x01(\x0b\x32\x1b.android.bundle.Compression\x12\x39\n\x10master_resources\x18\x04 \x01(\x0b\x32\x1f.android.bundle.MasterResources\x12/\n\x0b\x61pex_config\x18\x05 \x01(\x0b\x32\x1a.android.bundle.ApexConfig\x12O\n\x1cunsigned_embedded_apk_config\x18\x06 \x03(\x0b\x32).android.bundle.UnsignedEmbeddedApkConfig\x12@\n\x14\x61sset_modules_config\x18\x07 \x01(\x0b\x32\".android.bundle.AssetModulesConfig\x12\x35\n\x04type\x18\x08 \x01(\x0e\x32\'.android.bundle.BundleConfig.BundleType\"3\n\nBundleType\x12\x0b\n\x07REGULAR\x10\x00\x12\x08\n\x04\x41PEX\x10\x01\x12\x0e\n\nASSET_ONLY\x10\x02\"#\n\nBundletool\x12\x0f\n\x07version\x18\x02 \x01(\tJ\x04\x08\x01\x10\x02\"\xe0\x01\n\x0b\x43ompression\x12\x19\n\x11uncompressed_glob\x18\x01 \x03(\t\x12i\n-install_time_asset_module_default_compression\x18\x02 \x01(\x0e\x32\x32.android.bundle.Compression.AssetModuleCompression\"K\n\x16\x41ssetModuleCompression\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x10\n\x0cUNCOMPRESSED\x10\x01\x12\x0e\n\nCOMPRESSED\x10\x02\"?\n\x0fMasterResources\x12\x14\n\x0cresource_ids\x18\x01 \x03(\x05\x12\x16\n\x0eresource_names\x18\x02 \x03(\t\"\x93\x02\n\rOptimizations\x12\x33\n\rsplits_config\x18\x01 \x01(\x0b\x32\x1c.android.bundle.SplitsConfig\x12N\n\x1buncompress_native_libraries\x18\x02 \x01(\x0b\x32).android.bundle.UncompressNativeLibraries\x12@\n\x14uncompress_dex_files\x18\x03 \x01(\x0b\x32\".android.bundle.UncompressDexFiles\x12;\n\x11standalone_config\x18\x04 \x01(\x0b\x32 .android.bundle.StandaloneConfig\",\n\x19UncompressNativeLibraries\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"%\n\x12UncompressDexFiles\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\"G\n\x0cSplitsConfig\x12\x37\n\x0fsplit_dimension\x18\x01 \x03(\x0b\x32\x1e.android.bundle.SplitDimension\"\xfa\x01\n\x10StandaloneConfig\x12\x37\n\x0fsplit_dimension\x18\x01 \x03(\x0b\x32\x1e.android.bundle.SplitDimension\x12\x1e\n\x16strip_64_bit_libraries\x18\x02 \x01(\x08\x12Q\n\x14\x64\x65x_merging_strategy\x18\x03 \x01(\x0e\x32\x33.android.bundle.StandaloneConfig.DexMergingStrategy\":\n\x12\x44\x65xMergingStrategy\x12\x13\n\x0fMERGE_IF_NEEDED\x10\x00\x12\x0f\n\x0bNEVER_MERGE\x10\x01\"\x8c\x02\n\x0eSplitDimension\x12\x33\n\x05value\x18\x01 \x01(\x0e\x32$.android.bundle.SplitDimension.Value\x12\x0e\n\x06negate\x18\x02 \x01(\x08\x12\x39\n\x10suffix_stripping\x18\x03 \x01(\x0b\x32\x1f.android.bundle.SuffixStripping\"z\n\x05Value\x12\x15\n\x11UNSPECIFIED_VALUE\x10\x00\x12\x07\n\x03\x41\x42I\x10\x01\x12\x12\n\x0eSCREEN_DENSITY\x10\x02\x12\x0c\n\x08LANGUAGE\x10\x03\x12\x1e\n\x1aTEXTURE_COMPRESSION_FORMAT\x10\x04\x12\x0f\n\x0b\x44\x45VICE_TIER\x10\x06\":\n\x0fSuffixStripping\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x16\n\x0e\x64\x65\x66\x61ult_suffix\x18\x02 \x01(\t\"U\n\nApexConfig\x12G\n\x18\x61pex_embedded_apk_config\x18\x01 \x03(\x0b\x32%.android.bundle.ApexEmbeddedApkConfig\";\n\x15\x41pexEmbeddedApkConfig\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\")\n\x19UnsignedEmbeddedApkConfig\x12\x0c\n\x04path\x18\x01 \x01(\t\"D\n\x12\x41ssetModulesConfig\x12\x13\n\x0b\x61pp_version\x18\x01 \x03(\x03\x12\x19\n\x11\x61sset_version_tag\x18\x02 \x01(\tB\x14\n\x12\x63om.android.bundleb\x06proto3')
|
||||
|
||||
|
||||
|
||||
_BUNDLECONFIG = DESCRIPTOR.message_types_by_name['BundleConfig']
|
||||
_BUNDLETOOL = DESCRIPTOR.message_types_by_name['Bundletool']
|
||||
_COMPRESSION = DESCRIPTOR.message_types_by_name['Compression']
|
||||
_MASTERRESOURCES = DESCRIPTOR.message_types_by_name['MasterResources']
|
||||
_OPTIMIZATIONS = DESCRIPTOR.message_types_by_name['Optimizations']
|
||||
_UNCOMPRESSNATIVELIBRARIES = DESCRIPTOR.message_types_by_name['UncompressNativeLibraries']
|
||||
_UNCOMPRESSDEXFILES = DESCRIPTOR.message_types_by_name['UncompressDexFiles']
|
||||
_SPLITSCONFIG = DESCRIPTOR.message_types_by_name['SplitsConfig']
|
||||
_STANDALONECONFIG = DESCRIPTOR.message_types_by_name['StandaloneConfig']
|
||||
_SPLITDIMENSION = DESCRIPTOR.message_types_by_name['SplitDimension']
|
||||
_SUFFIXSTRIPPING = DESCRIPTOR.message_types_by_name['SuffixStripping']
|
||||
_APEXCONFIG = DESCRIPTOR.message_types_by_name['ApexConfig']
|
||||
_APEXEMBEDDEDAPKCONFIG = DESCRIPTOR.message_types_by_name['ApexEmbeddedApkConfig']
|
||||
_UNSIGNEDEMBEDDEDAPKCONFIG = DESCRIPTOR.message_types_by_name['UnsignedEmbeddedApkConfig']
|
||||
_ASSETMODULESCONFIG = DESCRIPTOR.message_types_by_name['AssetModulesConfig']
|
||||
_BUNDLECONFIG_BUNDLETYPE = _BUNDLECONFIG.enum_types_by_name['BundleType']
|
||||
_COMPRESSION_ASSETMODULECOMPRESSION = _COMPRESSION.enum_types_by_name['AssetModuleCompression']
|
||||
_STANDALONECONFIG_DEXMERGINGSTRATEGY = _STANDALONECONFIG.enum_types_by_name['DexMergingStrategy']
|
||||
_SPLITDIMENSION_VALUE = _SPLITDIMENSION.enum_types_by_name['Value']
|
||||
BundleConfig = _reflection.GeneratedProtocolMessageType('BundleConfig', (_message.Message,), {
|
||||
'DESCRIPTOR' : _BUNDLECONFIG,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.BundleConfig)
|
||||
})
|
||||
_sym_db.RegisterMessage(BundleConfig)
|
||||
|
||||
Bundletool = _reflection.GeneratedProtocolMessageType('Bundletool', (_message.Message,), {
|
||||
'DESCRIPTOR' : _BUNDLETOOL,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.Bundletool)
|
||||
})
|
||||
_sym_db.RegisterMessage(Bundletool)
|
||||
|
||||
Compression = _reflection.GeneratedProtocolMessageType('Compression', (_message.Message,), {
|
||||
'DESCRIPTOR' : _COMPRESSION,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.Compression)
|
||||
})
|
||||
_sym_db.RegisterMessage(Compression)
|
||||
|
||||
MasterResources = _reflection.GeneratedProtocolMessageType('MasterResources', (_message.Message,), {
|
||||
'DESCRIPTOR' : _MASTERRESOURCES,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.MasterResources)
|
||||
})
|
||||
_sym_db.RegisterMessage(MasterResources)
|
||||
|
||||
Optimizations = _reflection.GeneratedProtocolMessageType('Optimizations', (_message.Message,), {
|
||||
'DESCRIPTOR' : _OPTIMIZATIONS,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.Optimizations)
|
||||
})
|
||||
_sym_db.RegisterMessage(Optimizations)
|
||||
|
||||
UncompressNativeLibraries = _reflection.GeneratedProtocolMessageType('UncompressNativeLibraries', (_message.Message,), {
|
||||
'DESCRIPTOR' : _UNCOMPRESSNATIVELIBRARIES,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.UncompressNativeLibraries)
|
||||
})
|
||||
_sym_db.RegisterMessage(UncompressNativeLibraries)
|
||||
|
||||
UncompressDexFiles = _reflection.GeneratedProtocolMessageType('UncompressDexFiles', (_message.Message,), {
|
||||
'DESCRIPTOR' : _UNCOMPRESSDEXFILES,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.UncompressDexFiles)
|
||||
})
|
||||
_sym_db.RegisterMessage(UncompressDexFiles)
|
||||
|
||||
SplitsConfig = _reflection.GeneratedProtocolMessageType('SplitsConfig', (_message.Message,), {
|
||||
'DESCRIPTOR' : _SPLITSCONFIG,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.SplitsConfig)
|
||||
})
|
||||
_sym_db.RegisterMessage(SplitsConfig)
|
||||
|
||||
StandaloneConfig = _reflection.GeneratedProtocolMessageType('StandaloneConfig', (_message.Message,), {
|
||||
'DESCRIPTOR' : _STANDALONECONFIG,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.StandaloneConfig)
|
||||
})
|
||||
_sym_db.RegisterMessage(StandaloneConfig)
|
||||
|
||||
SplitDimension = _reflection.GeneratedProtocolMessageType('SplitDimension', (_message.Message,), {
|
||||
'DESCRIPTOR' : _SPLITDIMENSION,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.SplitDimension)
|
||||
})
|
||||
_sym_db.RegisterMessage(SplitDimension)
|
||||
|
||||
SuffixStripping = _reflection.GeneratedProtocolMessageType('SuffixStripping', (_message.Message,), {
|
||||
'DESCRIPTOR' : _SUFFIXSTRIPPING,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.SuffixStripping)
|
||||
})
|
||||
_sym_db.RegisterMessage(SuffixStripping)
|
||||
|
||||
ApexConfig = _reflection.GeneratedProtocolMessageType('ApexConfig', (_message.Message,), {
|
||||
'DESCRIPTOR' : _APEXCONFIG,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.ApexConfig)
|
||||
})
|
||||
_sym_db.RegisterMessage(ApexConfig)
|
||||
|
||||
ApexEmbeddedApkConfig = _reflection.GeneratedProtocolMessageType('ApexEmbeddedApkConfig', (_message.Message,), {
|
||||
'DESCRIPTOR' : _APEXEMBEDDEDAPKCONFIG,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.ApexEmbeddedApkConfig)
|
||||
})
|
||||
_sym_db.RegisterMessage(ApexEmbeddedApkConfig)
|
||||
|
||||
UnsignedEmbeddedApkConfig = _reflection.GeneratedProtocolMessageType('UnsignedEmbeddedApkConfig', (_message.Message,), {
|
||||
'DESCRIPTOR' : _UNSIGNEDEMBEDDEDAPKCONFIG,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.UnsignedEmbeddedApkConfig)
|
||||
})
|
||||
_sym_db.RegisterMessage(UnsignedEmbeddedApkConfig)
|
||||
|
||||
AssetModulesConfig = _reflection.GeneratedProtocolMessageType('AssetModulesConfig', (_message.Message,), {
|
||||
'DESCRIPTOR' : _ASSETMODULESCONFIG,
|
||||
'__module__' : 'config_pb2'
|
||||
# @@protoc_insertion_point(class_scope:android.bundle.AssetModulesConfig)
|
||||
})
|
||||
_sym_db.RegisterMessage(AssetModulesConfig)
|
||||
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\022com.android.bundle'
|
||||
_BUNDLECONFIG._serialized_start=33
|
||||
_BUNDLECONFIG._serialized_end=562
|
||||
_BUNDLECONFIG_BUNDLETYPE._serialized_start=511
|
||||
_BUNDLECONFIG_BUNDLETYPE._serialized_end=562
|
||||
_BUNDLETOOL._serialized_start=564
|
||||
_BUNDLETOOL._serialized_end=599
|
||||
_COMPRESSION._serialized_start=602
|
||||
_COMPRESSION._serialized_end=826
|
||||
_COMPRESSION_ASSETMODULECOMPRESSION._serialized_start=751
|
||||
_COMPRESSION_ASSETMODULECOMPRESSION._serialized_end=826
|
||||
_MASTERRESOURCES._serialized_start=828
|
||||
_MASTERRESOURCES._serialized_end=891
|
||||
_OPTIMIZATIONS._serialized_start=894
|
||||
_OPTIMIZATIONS._serialized_end=1169
|
||||
_UNCOMPRESSNATIVELIBRARIES._serialized_start=1171
|
||||
_UNCOMPRESSNATIVELIBRARIES._serialized_end=1215
|
||||
_UNCOMPRESSDEXFILES._serialized_start=1217
|
||||
_UNCOMPRESSDEXFILES._serialized_end=1254
|
||||
_SPLITSCONFIG._serialized_start=1256
|
||||
_SPLITSCONFIG._serialized_end=1327
|
||||
_STANDALONECONFIG._serialized_start=1330
|
||||
_STANDALONECONFIG._serialized_end=1580
|
||||
_STANDALONECONFIG_DEXMERGINGSTRATEGY._serialized_start=1522
|
||||
_STANDALONECONFIG_DEXMERGINGSTRATEGY._serialized_end=1580
|
||||
_SPLITDIMENSION._serialized_start=1583
|
||||
_SPLITDIMENSION._serialized_end=1851
|
||||
_SPLITDIMENSION_VALUE._serialized_start=1729
|
||||
_SPLITDIMENSION_VALUE._serialized_end=1851
|
||||
_SUFFIXSTRIPPING._serialized_start=1853
|
||||
_SUFFIXSTRIPPING._serialized_end=1911
|
||||
_APEXCONFIG._serialized_start=1913
|
||||
_APEXCONFIG._serialized_end=1998
|
||||
_APEXEMBEDDEDAPKCONFIG._serialized_start=2000
|
||||
_APEXEMBEDDEDAPKCONFIG._serialized_end=2059
|
||||
_UNSIGNEDEMBEDDEDAPKCONFIG._serialized_start=2061
|
||||
_UNSIGNEDEMBEDDEDAPKCONFIG._serialized_end=2102
|
||||
_ASSETMODULESCONFIG._serialized_start=2104
|
||||
_ASSETMODULESCONFIG._serialized_end=2172
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
176
devtools/packaging/scripts/android/generate_asset_pack.py
Normal file
176
devtools/packaging/scripts/android/generate_asset_pack.py
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2021 Google LLC
|
||||
#
|
||||
# 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.
|
||||
"""Script to generate a valid asset pack from a given assets folder.
|
||||
|
||||
Instant delivery is not supported.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import distutils.dir_util
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
manifest_template = """<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:dist="http://schemas.android.com/apk/distribution" package="{package_name}" split="{asset_pack_name}">
|
||||
<dist:module dist:type="asset-pack">
|
||||
<dist:delivery>
|
||||
<dist:{delivery_mode}/>
|
||||
</dist:delivery>
|
||||
<dist:fusing dist:include="true"/>
|
||||
</dist:module>
|
||||
</manifest>
|
||||
"""
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
"""Parse input arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generates a valid asset pack from a given assets folder",
|
||||
add_help=True)
|
||||
parser.add_argument(
|
||||
"--packagename", required=True, help="Package name of the app")
|
||||
parser.add_argument(
|
||||
"--assetpackname", required=True, help="Name of the asset pack module")
|
||||
parser.add_argument(
|
||||
"--deliverymode",
|
||||
required=True,
|
||||
choices=["install-time", "fast-follow", "on-demand"],
|
||||
help="Delivery mode of the asset pack module")
|
||||
parser.add_argument(
|
||||
"--assetsdir", required=True, help="Folder to read assets from")
|
||||
parser.add_argument("--outdir", required=True, help="Output folder")
|
||||
parser.add_argument(
|
||||
"--overwrite",
|
||||
required=False,
|
||||
action="store_true",
|
||||
help="Overwrite existing files")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def abs_expand_all(path: str) -> str:
|
||||
return os.path.abspath(os.path.expandvars(os.path.expanduser(path)))
|
||||
|
||||
|
||||
def get_assets_dir(args: argparse.Namespace) -> str:
|
||||
assets_dir = abs_expand_all(args.assetsdir)
|
||||
if (not (os.path.isdir(assets_dir) and os.access(assets_dir, os.X_OK) and
|
||||
os.access(assets_dir, os.R_OK))):
|
||||
print(
|
||||
"Assets folder ({assets_dir}) is not accessible. Check permissions."
|
||||
.format(assets_dir=assets_dir),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
return assets_dir
|
||||
|
||||
|
||||
def create_output_dir(args: argparse.Namespace) -> str:
|
||||
"""Get the output directory."""
|
||||
output_dir = abs_expand_all(args.outdir)
|
||||
if not os.path.isdir(output_dir):
|
||||
try:
|
||||
os.makedirs(output_dir)
|
||||
except OSError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
if (not (os.path.isdir(output_dir) and os.access(output_dir, os.X_OK) and
|
||||
os.access(output_dir, os.W_OK))):
|
||||
print(
|
||||
"Output folder ({output_dir}) is not accessible. Check permissions."
|
||||
.format(output_dir=output_dir),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
return output_dir
|
||||
|
||||
|
||||
def get_output_file_name(output_dir: str, args: argparse.Namespace) -> str:
|
||||
output_file_name = os.path.join(output_dir, args.assetpackname)
|
||||
if os.path.exists(output_file_name) and not args.overwrite:
|
||||
print(
|
||||
"Output file {output_file_name} exists. Specify --overwrite to bypass. Exiting."
|
||||
.format(output_file_name=output_file_name))
|
||||
sys.exit(-1)
|
||||
return output_file_name
|
||||
|
||||
|
||||
def make_manifest(package_name: str, asset_pack_name: str, delivery_mode: str,
|
||||
pack_directory: str) -> None:
|
||||
"""Generate the Android Manifest file for the pack."""
|
||||
manifest = manifest_template.format(
|
||||
package_name=package_name,
|
||||
asset_pack_name=asset_pack_name,
|
||||
delivery_mode=delivery_mode)
|
||||
|
||||
manifest_folder = os.path.join(pack_directory, "manifest")
|
||||
try:
|
||||
os.makedirs(manifest_folder)
|
||||
except OSError as e:
|
||||
print("Cannot create manifest folder. {e}".format(
|
||||
e=e), file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
manifest_file_name = os.path.join(manifest_folder, "AndroidManifest.xml")
|
||||
manifest_file = open(manifest_file_name, "w")
|
||||
print(manifest, file=manifest_file)
|
||||
manifest_file.close()
|
||||
print("Generated {manifest}".format(manifest=manifest_file_name))
|
||||
|
||||
|
||||
def copy_assets(src: str, dest: str) -> None:
|
||||
"""Copy assets from one folder to another."""
|
||||
assets_folder = os.path.join(dest, "assets")
|
||||
try:
|
||||
os.makedirs(assets_folder)
|
||||
except OSError as e:
|
||||
print("Cannot create assets folder. {e}".format(e=e), file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
try:
|
||||
distutils.dir_util.copy_tree(src, assets_folder)
|
||||
except FileNotFoundError as e:
|
||||
print(
|
||||
"Cannot copy assets folder into temporary folder. {e}".format(e=e),
|
||||
file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
print(
|
||||
"Copied assets into {assets_folder}".format(assets_folder=assets_folder))
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
assets_dir = get_assets_dir(args)
|
||||
output_dir = create_output_dir(args)
|
||||
output_file_name = get_output_file_name(output_dir, args)
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=output_dir) as pack_dir:
|
||||
print("Created temporary working folder: {pack_dir}".format(
|
||||
pack_dir=pack_dir))
|
||||
|
||||
make_manifest(args.packagename, args.assetpackname, args.deliverymode,
|
||||
pack_dir)
|
||||
copy_assets(assets_dir, pack_dir)
|
||||
|
||||
output_pack_path = shutil.make_archive(
|
||||
os.path.join(output_dir, output_file_name), "zip", pack_dir)
|
||||
print("Asset pack is generated at {output_pack_path}.\nDone.".format(
|
||||
output_pack_path=output_pack_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user