Files
vendor_gapps/setup-makefiles.py
2025-06-28 10:52:05 +02:00

342 lines
9.8 KiB
Python
Executable File

#!/usr/bin/env python3
from dataclasses import dataclass
from fnmatch import fnmatch
from pathlib import Path
import textwrap
import typing
@dataclass
class GappsTarget:
name: str
soong_imports: list[str]
additional_namespaces: list[str]
additional_packages: list[str]
additional_makefiles_to_inherit: list[str]
class SoongModule:
def __init__(self):
self.soong_module = type(self).__name__
@staticmethod
def _value_to_str(value: typing.Any) -> str:
match value:
case bool():
return ["false", "true"][value]
case dict():
ret = "{\n"
for key, value in value.items():
ret += f"{indent(SoongModule._prop_to_str(key, value))}\n"
ret += "}"
return ret
case list():
return f'[{", ".join([SoongModule._value_to_str(x) for x in value])}]'
case str():
return f'"{value}"'
case default:
assert False, f"Unhandled value type: {type(value)}"
@staticmethod
def _prop_to_str(key: str, value: typing.Any) -> str:
return f"{key}: {SoongModule._value_to_str(value)},"
def _to_blueprint(self, props: dict) -> str:
ret = f"{self.soong_module} {{\n"
for key, value in props.items():
ret += f"{indent(SoongModule._prop_to_str(key, value))}\n"
ret += "}"
return ret
class SoongPrebuilt(SoongModule):
def __init__(self, install_path: str, flags: dict):
SoongModule.__init__(self)
self.name = Path(install_path).stem
self.install_path = install_path
self.src = f"proprietary/{install_path}"
self.flags = flags
def _has_flag(self, key: str) -> bool:
return key in self.flags
def _flag(self, key: str) -> str | None:
return self.flags.get(key, None)
def _to_blueprint(self, props: dict) -> str:
partition, _ = self.install_path.split("/", maxsplit=1)
match partition:
case "system":
pass
case "product":
props["product_specific"] = True
case "system_ext":
props["system_ext_specific"] = True
case _:
assert False, f"Unhandled partition: {partition}"
return super()._to_blueprint(props)
class android_app_import(SoongPrebuilt):
def to_blueprint(self) -> str:
props = {
"name": self.name,
"owner": "gapps",
"apk": self.src,
"overrides": [self._flag("OVERRIDES")],
"preprocessed": True,
"presigned": True,
"dex_preopt": {
"enabled": False,
},
"privileged": True,
}
_, apk_dst, _ = self.install_path.split("/", maxsplit=2)
if apk_dst != "priv-app":
del props["privileged"]
if not self._has_flag("OVERRIDES"):
del props["overrides"]
if not self._has_flag("PRESIGNED"):
del props["presigned"]
return self._to_blueprint(props)
class cc_prebuilt_library_shared(SoongPrebuilt):
def to_blueprint(self) -> str:
props = {
"name": self.name,
"srcs": [self.src],
"prefer": True,
}
return self._to_blueprint(props)
class dex_import(SoongPrebuilt):
def to_blueprint(self) -> str:
props = {
"name": self.name,
"owner": "gapps",
"jars": [self.src],
}
return self._to_blueprint(props)
class prebuilt_etc(SoongPrebuilt):
def __init__(self, install_path: str, flags: dict):
super().__init__(install_path, flags)
self.name += Path(self.install_path).suffix
def to_blueprint(self) -> str:
props = {
"name": self.name,
"src": self.src,
"relative_install_path": "/".join(self.install_path.split("/")[2:-1]),
"filename_from_src": True,
}
return self._to_blueprint(props)
class soong_namespace(SoongModule):
def __init__(self, imports: list[str]):
super().__init__()
self.imports = imports
def to_blueprint(self) -> str:
props = {
"imports": self.imports,
}
if not self.imports:
del props["imports"]
return self._to_blueprint(props)
def indent(text: str) -> str:
return textwrap.indent(text, " " * 4)
def parse_proprietary_file(line: str) -> SoongPrebuilt:
# Format: ORIG_PATH:INSTALL_PATH;FLAGS|SHA1
if "|" in line:
line, file_hash = line.split("|")
else:
file_hash = None
if ";" in line:
line, flags_str = line.split(";", maxsplit=1)
flags = {}
for flag in flags_str.split(";"):
if "=" in flag:
key, value = flag.split("=", maxsplit=1)
else:
key = flag
value = None
flags[key] = value
else:
flags = {}
if ":" in line:
line, install_path = line.split(":")
else:
install_path = line
for pattern, blob_type in {
"*.apk": android_app_import,
"*.jar": dex_import,
"*.so": cc_prebuilt_library_shared,
"*/etc/*.*": prebuilt_etc,
}.items():
if fnmatch(install_path, pattern):
return blob_type(install_path, flags)
else:
assert False, f"Unhandled install path: {install_path}"
def parse_proprietary_files(path: str) -> list:
packages = []
if Path(path).is_file():
for line in open(path).readlines():
line = line.strip()
if not line:
continue
if package := parse_proprietary_file(line):
packages.append(package)
return sorted(packages, key=lambda x: (x.soong_module, x.name))
def generate(targets: list[GappsTarget]) -> None:
for target in targets:
packages = parse_proprietary_files(f"proprietary-files-{target.name}.txt")
packages_nongrouper = parse_proprietary_files(
f"proprietary-files-{target.name}-nongrouper.txt"
)
packages_tangorpro = parse_proprietary_files(
f"proprietary-files-{target.name}-tangorpro.txt"
)
packages_all = sorted(
packages + packages_nongrouper + packages_tangorpro,
key=lambda x: (x.soong_module, x.name),
)
with open(f"{target.name}/Android.bp", "+wt") as f:
f.write("// Automatically generated file. DO NOT MODIFY\n")
f.write("\n")
f.write(soong_namespace(target.soong_imports).to_blueprint())
f.write("\n")
for package in packages_all:
f.write("\n")
f.write(package.to_blueprint())
f.write("\n")
with open(f"{target.name}/BoardConfigVendor.mk", "+wt") as f:
f.write("# Automatically generated file. DO NOT MODIFY\n")
f.write("#\n")
with open(f"{target.name}/{target.name}-vendor.mk", "+wt") as f:
f.write("# Automatically generated file. DO NOT MODIFY\n")
f.write("#\n")
def write_list(var: str, items: list[str]):
f.write(f"{var} += \\\n")
f.write(" \\\n".join([indent(x) for x in items]))
f.write("\n")
f.write("\n")
write_list("PRODUCT_SOONG_NAMESPACES", [f"$(LOCAL_PATH)"])
if packages:
f.write("\n")
write_list("PRODUCT_PACKAGES", [x.name for x in packages])
if packages_nongrouper:
f.write("\n")
f.write("ifeq ($(TARGET_IS_GROUPER),)\n")
write_list("PRODUCT_PACKAGES", [x.name for x in packages_nongrouper])
f.write("endif\n")
if packages_tangorpro:
f.write("\n")
f.write("ifneq ($(filter %tangorpro,$(TARGET_PRODUCT)),)\n")
write_list("PRODUCT_PACKAGES", [x.name for x in packages_tangorpro])
f.write("endif\n")
if target.additional_namespaces:
f.write("\n")
write_list("PRODUCT_SOONG_NAMESPACES", target.additional_namespaces)
if target.additional_packages:
f.write("\n")
write_list("PRODUCT_PACKAGES", target.additional_packages)
for path in target.additional_makefiles_to_inherit:
f.write("\n")
f.write(f"$(call inherit-product, {path})\n")
if __name__ == "__main__":
generate(
[
GappsTarget(
name="common",
soong_imports=[],
additional_namespaces=["vendor/gapps/overlay"],
additional_packages=[x.name for x in Path("overlay").glob("*Overlay")],
additional_makefiles_to_inherit=[],
),
GappsTarget(
name="arm",
soong_imports=["vendor/gapps/common"],
additional_namespaces=[],
additional_packages=[],
additional_makefiles_to_inherit=[
"vendor/gapps/common/common-vendor.mk",
],
),
GappsTarget(
name="arm64",
soong_imports=["vendor/gapps/common"],
additional_namespaces=[],
additional_packages=[],
additional_makefiles_to_inherit=[
"vendor/gapps/common/common-vendor.mk",
],
),
GappsTarget(
name="x86_64",
soong_imports=["vendor/gapps/common"],
additional_namespaces=[],
additional_packages=[],
additional_makefiles_to_inherit=[
"vendor/gapps/common/common-vendor.mk",
],
),
]
)