diff options
author | Spandan Das <spandandas@google.com> | 2022-10-24 21:10:14 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2022-10-24 21:10:14 +0000 |
commit | 06b32c18a40f0e86da1606dd7defc0cfed956d0f (patch) | |
tree | 64f4f8905d3257ecb942b28a670a07ca77348e61 /core | |
parent | c2a6363421d912845c782e30500c41b23256fc0f (diff) | |
parent | 9f8a038323c3a0a22ed3d035dbe106ab2a36ec59 (diff) | |
download | orchestrator-06b32c18a40f0e86da1606dd7defc0cfed956d0f.tar.gz |
Merge changes I25fd865f,I9f2d5ffd
* changes:
Orchestrator small fixes
Lightweight Android.bp writer in orchestrator
Diffstat (limited to 'core')
-rw-r--r-- | core/api_assembly.py | 63 | ||||
-rw-r--r-- | core/build_file_generator.py | 349 | ||||
-rw-r--r-- | core/cc/api_assembly.py | 108 | ||||
-rw-r--r-- | core/test_build_file_generator.py | 152 |
4 files changed, 612 insertions, 60 deletions
diff --git a/core/api_assembly.py b/core/api_assembly.py index 9551cab..c3be3c7 100644 --- a/core/api_assembly.py +++ b/core/api_assembly.py @@ -20,14 +20,17 @@ import os import sys from cc.api_assembly import CcApiAssemblyContext +from build_file_generator import BuildFileGenerator import ninja_tools +ContributionData = collections.namedtuple("ContributionData", + ("inner_tree", "json_data")) -ContributionData = collections.namedtuple("ContributionData", ("inner_tree", "json_data")) def assemble_apis(context, inner_trees): # Find all of the contributions from the inner tree - contribution_files_dict = inner_trees.for_each_tree(api_contribution_files_for_inner_tree) + contribution_files_dict = inner_trees.for_each_tree( + api_contribution_files_for_inner_tree) # Load and validate the contribution files # TODO: Check timestamps and skip unnecessary work @@ -39,29 +42,37 @@ def assemble_apis(context, inner_trees): continue # TODO: Validate the configs, especially that the domains match what we asked for # from the lunch config. - contributions.append(ContributionData(inner_trees.get(tree_key), json_data)) + contributions.append( + ContributionData(inner_trees.get(tree_key), json_data)) # Group contributions by language and API surface stub_libraries = collate_contributions(contributions) + # Initialize the build file writer + build_file_generator = BuildFileGenerator() + # Initialize the ninja file writer - with open(context.out.api_ninja_file(), "w", encoding='iso-8859-1') as ninja_file: + with open(context.out.api_ninja_file(), "w", + encoding='iso-8859-1') as ninja_file: ninja = ninja_tools.Ninja(context, ninja_file) - # Initialize the build file writer - build_file = BuildFile() # TODO: parameters? - # Iterate through all of the stub libraries and generate rules to assemble them # and Android.bp/BUILD files to make those available to inner trees. # TODO: Parallelize? Skip unnecessary work? for stub_library in stub_libraries: - STUB_LANGUAGE_HANDLERS[stub_library.language](context, ninja, build_file, stub_library) + STUB_LANGUAGE_HANDLERS[stub_library.language](context, ninja, + build_file_generator, + stub_library) # TODO: Handle host_executables separately or as a StubLibrary language? # Finish writing the ninja file ninja.write() + build_file_generator.write() + build_file_generator.clean( + context.out.api_surfaces_dir()) # delete stale Android.bp files + def api_contribution_files_for_inner_tree(tree_key, inner_tree, cookie): "Scan an inner_tree's out dir for the api contribution files." @@ -88,6 +99,7 @@ def load_contribution_file(context, filename): class StubLibraryContribution(object): + def __init__(self, inner_tree, api_domain, library_contribution): self.inner_tree = inner_tree self.api_domain = api_domain @@ -95,6 +107,7 @@ class StubLibraryContribution(object): class StubLibrary(object): + def __init__(self, language, api_surface, api_surface_version, name): self.language = language self.api_surface = api_surface @@ -115,30 +128,37 @@ def collate_contributions(contributions): for language in STUB_LANGUAGE_HANDLERS.keys(): for library in contribution.json_data.get(language, []): key = (language, contribution.json_data["name"], - contribution.json_data["version"], library["name"]) + contribution.json_data["version"], library["name"]) stub_library = grouped.get(key) if not stub_library: - stub_library = StubLibrary(language, contribution.json_data["name"], - contribution.json_data["version"], library["name"]) + stub_library = StubLibrary( + language, contribution.json_data["name"], + contribution.json_data["version"], library["name"]) grouped[key] = stub_library - stub_library.add_contribution(StubLibraryContribution(contribution.inner_tree, + stub_library.add_contribution( + StubLibraryContribution( + contribution.inner_tree, contribution.json_data["api_domain"], library)) return list(grouped.values()) def assemble_java_api_library(context, ninja, build_file, stub_library): - print("assembling java_api_library %s-%s %s from:" % (stub_library.api_surface, - stub_library.api_surface_version, stub_library.name)) + print("assembling java_api_library %s-%s %s from:" % + (stub_library.api_surface, stub_library.api_surface_version, + stub_library.name)) for contrib in stub_library.contributions: - print(" %s %s" % (contrib.api_domain, contrib.library_contribution["api"])) + print(" %s %s" % + (contrib.api_domain, contrib.library_contribution["api"])) # TODO: Implement me def assemble_resource_api_library(context, ninja, build_file, stub_library): - print("assembling resource_api_library %s-%s %s from:" % (stub_library.api_surface, - stub_library.api_surface_version, stub_library.name)) + print("assembling resource_api_library %s-%s %s from:" % + (stub_library.api_surface, stub_library.api_surface_version, + stub_library.name)) for contrib in stub_library.contributions: - print(" %s %s" % (contrib.api_domain, contrib.library_contribution["api"])) + print(" %s %s" % + (contrib.api_domain, contrib.library_contribution["api"])) # TODO: Implement me @@ -147,10 +167,3 @@ STUB_LANGUAGE_HANDLERS = { "java_libraries": assemble_java_api_library, "resource_libraries": assemble_resource_api_library, } - - -class BuildFile(object): - "Abstract generator for Android.bp files and BUILD files." - pass - - diff --git a/core/build_file_generator.py b/core/build_file_generator.py new file mode 100644 index 0000000..6b17b43 --- /dev/null +++ b/core/build_file_generator.py @@ -0,0 +1,349 @@ +#!/usr/bin/python3 +# +# Copyright (C) 2022 The Android Open Source Project +# +# 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. +"""A module for generating Android.bp/BUILD files for stub libraries""" + +from enum import Enum +import json +import os +import textwrap +from typing import List +from lunch import walk_paths + +TAB = " " # 4 spaces + + +class ConfigAxis(Enum): + """A helper class to manage the configuration axes of stub libraries.""" + NoConfig = "" + ArmConfig = "arm" + Arm64Config = "arm64" + X86Config = "x86" + X86_64Config = "x86_64" + + def is_arch_axis(self): + return self != ConfigAxis.NoConfig + + @classmethod + def get_arch_axes(cls) -> list: + return [a for a in cls if a.is_arch_axis()] + + @classmethod + def get_axis(cls, value): + for axis in cls: + if axis.value == value: + return axis + return None + + +class AndroidBpPropertyKey: + """Unique key identifying an Android.bp property.""" + + def __init__(self, name: str, axis: str): + self.name = name + self.axis = axis + + def __hash__(self): + return hash((self.name, self.axis)) + + def __eq__(self, other): + return (isinstance(other, AndroidBpPropertyKey) + and self.name == other.name and self.axis == other.axis) + + +class AndroidBpProperty: + """Properties of Android.bp modules.""" + + def __init__(self, name, value, axis): + self.name = name + self.value = value + self.axis = axis + + def extend(self, value): + """Extend the existing value of the property. + + Parameters: + value: object to add to the value of + existing property. + + Returns: + None + + `extend` adds the value to the end of `self.value` (list). + If `value` is an iterator, it adds each element piecemeal. + + `self.value` is a Python list, and therefore can contain + elements of different types. + + e.g. + p = AndroidBpProperty("myprop", 1, ConfigAxis.NoConfig) + p.extend("a") + p.extend(["b", "c"]) + p.extend(("d", "2")) + + print(p.value) + [1, 'a', 'b', 'c', 'd', '2'] + """ + + self.value = self._as_list(self.value) + self.value.extend(self._as_list(value)) + + def _as_list(self, value): + """Converts an object into a list.""" + if not value: + return [] + if isinstance(value, (int, bool, str)): + return [value] + if isinstance(value, list): + return value + if isinstance(value, (set, tuple)): + return list(value) + raise TypeError(f"bad type {type(value)}") + + +class AndroidBpModule: + """Soong module of an Android.bp file.""" + + def __init__(self, name: str, module_type: str): + if not name: + raise ValueError("name cannot be empty in SoongModule") + if not module_type: + raise ValueError("module_type cannot be empty in SoongModule") + self.name = name + self.module_type = module_type + self.properties = {} # indexed using AndroidBpPropertyKey + + def add_property(self, prop: str, val: object, axis=ConfigAxis.NoConfig): + """Add a property to the Android.bp module. + + Raises: + ValueError if (`prop`, `axis`) is already registered. + """ + key = AndroidBpPropertyKey(name=prop, axis=axis) + if key in self.properties: + raise ValueError(f"Property: {prop} in axis: {axis} has been" + "registered. Use extend_property method instead.") + + p = AndroidBpProperty(prop, val, axis) + self.properties[key] = p + + def extend_property(self, + prop: str, + val: object, + axis=ConfigAxis.NoConfig): + """Extend the value of a property.""" + key = AndroidBpPropertyKey(name=prop, axis=axis) + if key in self.properties: + self.properties[key].extend(val) + else: + self.add_property(prop, val, axis) + + def get_properties(self, axis) -> List[AndroidBpProperty]: + return [prop for prop in self.properties.values() if prop.axis == axis] + + def string(self, formatter=None) -> None: + """Return the string representation of the module using the provided + formatter.""" + if formatter is None: + formatter = module_formatter + return formatter(self) + + def __str__(self): + return self.string() + + +class AndroidBpComment: + """Comment in an Android.bp file.""" + + def __init__(self, comment: str): + self.comment = comment + + def _add_comment_token(self, raw: str) -> str: + return raw if raw.startswith("//") else "// " + raw + + def split(self) -> List[str]: + """Split along \n tokens.""" + raw_comments = self.comment.split("\n") + return [self._add_comment_token(r) for r in raw_comments] + + def string(self, formatter=None) -> str: + """Return the string representation of the comment using the provided + formatter.""" + if formatter is None: + formatter = comment_formatter + return formatter(self) + + def __str__(self): + return self.string() + + +class AndroidBpFile: + """Android.bp file.""" + + def __init__(self, directory: str): + self._path = os.path.join(directory, "Android.bp") + self.components = [] + + def add_module(self, soong_module: AndroidBpModule): + """Add a Soong module to the file.""" + self.components.append(soong_module) + + def add_comment(self, comment: AndroidBpComment): + """Add a comment to the file.""" + self.components.append(comment) + + def add_comment_string(self, comment: str): + """Add a comment (str) to the file.""" + self.components.append(AndroidBpComment(comment=comment)) + + def add_license(self, lic: str) -> None: + raise NotImplementedError("Addition of License in generated Android.bp" + "files is not supported") + + def string(self, formatter=None) -> None: + """Return the string representation of the file using the provided + formatter.""" + fragments = [ + component.string(formatter) for component in self.components + ] + return "\n".join(fragments) + + def __str__(self): + return self.string() + + def is_stale(self, formatter=None) -> bool: + """Return true if the object is newer than the file on disk.""" + exists = os.path.exists(self._path) + if not exists: + return True + with open(self._path, encoding='iso-8859-1') as f: + # TODO: Avoid wasteful computation using cache + return f.read() != self.string(formatter) + + def write(self, formatter=None) -> None: + """Write the AndroidBpFile object to disk.""" + if not self.is_stale(formatter): + return + os.makedirs(os.path.dirname(self._path), exist_ok=True) + with open(self._path, "w+", encoding='iso-8859-1') as f: + f.write(self.string(formatter)) + + def fullpath(self) -> str: + return self._path + + +# Default formatters +def comment_formatter(comment: AndroidBpComment) -> str: + return "\n".join(comment.split()) + + +def module_properties_formatter(props: List[AndroidBpProperty], + indent=1) -> str: + formatted = "" + for prop in props: + name, val = prop.name, prop.value + if isinstance(val, str): + val_f = f'"{val}"' + # bool is a subclass of int, check it first + elif isinstance(val, bool): + val_f = "true" if val else "false" + elif isinstance(val, int): + val_f = val + elif isinstance(val, list): + # TODO: Align with bpfmt. + # This implementation splits non-singular lists into multiple lines. + if len(val) < 2: + val_f = json.dumps(val) + else: + nested_indent = indent + 1 + val_f = json.dumps(val, indent=f"{nested_indent*TAB}") + # Add TAB before the closing `]` + val_f = val_f[: + -1] + indent * TAB + val_f[ + -1] + else: + raise NotImplementedError( + f"Formatter for {val} of type: {type(val)}" + "has not been implemented") + + formatted += f"""{indent*TAB}{name}: {val_f},\n""" + return formatted + + +def module_formatter(module: AndroidBpModule) -> str: + formatted = textwrap.dedent(f"""\ + {module.module_type} {{ + {TAB}name: "{module.name}", + """) + # Print the no-arch props first. + no_arch_props = module.get_properties(ConfigAxis.NoConfig) + formatted += module_properties_formatter(no_arch_props) + + # Print the arch props if they exist. + contains_arch_props = any( + [prop.axis.is_arch_axis() for prop in module.properties]) + if contains_arch_props: + formatted += f"{TAB}arch: {{\n" + arch_axes = ConfigAxis.get_arch_axes() + for arch_axis in arch_axes: + arch_axis_props = module.get_properties(arch_axis) + if not arch_axis_props: + continue + formatted += f"{2*TAB}{arch_axis.value}: {{\n" + formatted += module_properties_formatter(arch_axis_props, indent=3) + formatted += f"{2*TAB}}},\n" + + formatted += f"{TAB}}},\n" + + formatted += "}" + return formatted + + +class BazelTarget: + pass # TODO + + +class BazelBuildFile: + pass # TODO + + +class BuildFileGenerator: + """Class to maintain state of generated Android.bp/BUILD files.""" + + def __init__(self): + self.android_bp_files = [] + self.bazel_build_files = [] + + def add_android_bp_file(self, file: AndroidBpFile): + self.android_bp_files.append(file) + + def add_bazel_build_file(self, file: BazelBuildFile): + raise NotImplementedError("Bazel BUILD file generation" + "is not supported currently") + + def write(self): + for android_bp_file in self.android_bp_files: + android_bp_file.write() + + def clean(self, staging_dir: str): + """Delete discarded Android.bp files. + + This is necessary when a library is dropped from an API surface.""" + valid_bp_files = set([x.fullpath() for x in self.android_bp_files]) + for bp_file_on_disk in walk_paths(staging_dir, + lambda x: x.endswith("Android.bp")): + if bp_file_on_disk not in valid_bp_files: + # This library has been dropped since the last run + os.remove(bp_file_on_disk) diff --git a/core/cc/api_assembly.py b/core/cc/api_assembly.py index 0bb3611..f14d708 100644 --- a/core/cc/api_assembly.py +++ b/core/cc/api_assembly.py @@ -22,10 +22,21 @@ from cc.library import CompileContext, Compiler, LinkContext, Linker ARCHES = ["arm", "arm64", "x86", "x86_64"] +# Clang triples for cross-compiling for Android +# TODO: Use a class? +DEVICE_CLANG_TRIPLES = { + "arm": "armv7a-linux-androideabi", + "arm64": "aarch64-linux-android", + "x86": "i686-linux-android", + "x86_64": "x86_64-linux-android", +} + ASSEMBLE_PHONY_TARGET = "multitree-sdk" + class CcApiAssemblyContext(object): """Context object for managing global state of CC API Assembly.""" + def __init__(self): self._stub_generator = StubGenerator() self._compiler = Compiler() @@ -39,11 +50,14 @@ class CcApiAssemblyContext(object): and therefore has access to its state.""" return self.assemble_cc_api_library - def assemble_cc_api_library(self, context, ninja, build_file, stub_library): - staging_dir = context.out.api_library_dir(stub_library.api_surface, - stub_library.api_surface_version, stub_library.name) - work_dir = context.out.api_library_work_dir(stub_library.api_surface, - stub_library.api_surface_version, stub_library.name) + def assemble_cc_api_library(self, context, ninja, build_file, + stub_library): + staging_dir = context.out.api_library_dir( + stub_library.api_surface, stub_library.api_surface_version, + stub_library.name) + work_dir = context.out.api_library_work_dir( + stub_library.api_surface, stub_library.api_surface_version, + stub_library.name) # Generate rules to copy headers. api_deps = [] @@ -60,26 +74,30 @@ class CcApiAssemblyContext(object): # e.g. bionic/libc/include/stdio.h --> stdio.h relpath = os.path.relpath(file, root) include = os.path.join(include_dir, relpath) - ninja.add_copy_file(include, os.path.join(contrib.inner_tree.root, file)) + ninja.add_copy_file( + include, os.path.join(contrib.inner_tree.root, file)) api_deps.append(include) api = contrib.library_contribution["api"] api_out = os.path.join(staging_dir, os.path.basename(api)) - ninja.add_copy_file(api_out, os.path.join(contrib.inner_tree.root, api)) + ninja.add_copy_file(api_out, + os.path.join(contrib.inner_tree.root, api)) api_deps.append(api_out) # Generate rules to run ndkstubgen. - extra_args = self._additional_ndkstubgen_args(stub_library.api_surface) + extra_args = self._additional_ndkstubgen_args( + stub_library.api_surface) for arch in ARCHES: inputs = GenCcStubsInput( - arch = arch, - version = stub_library.api_surface_version, - version_map = self._api_levels_file(context), - api = api_out, - additional_args = extra_args, + arch=arch, + version=stub_library.api_surface_version, + version_map=self._api_levels_file(context), + api=api_out, + additional_args=extra_args, ) # Generate stub.c files for each arch. - stub_outputs = self._stub_generator.add_stubgen_action(ninja, inputs, work_dir) + stub_outputs = self._stub_generator.add_stubgen_action( + ninja, inputs, work_dir) # Compile stub .c files to .o files. # The cflags below have been copied as-is from # build/soong/cc/ndk_library.go. @@ -97,13 +115,14 @@ class CcApiAssemblyContext(object): "-Wall", "-Werror", "-fno-unwind-tables", + f"-target {DEVICE_CLANG_TRIPLES[arch]}", # Cross compile for android ]) object_file = stub_outputs.stub + ".o" compile_context = CompileContext( - src = stub_outputs.stub, - flags = c_flags, - out = object_file, - frontend = context.tools.clang(), + src=stub_outputs.stub, + flags=c_flags, + out=object_file, + frontend=context.tools.clang(), ) self._compiler.compile(ninja, compile_context) @@ -114,15 +133,33 @@ class CcApiAssemblyContext(object): "--shared", f"-Wl,-soname,{soname}", f"-Wl,--version-script,{stub_outputs.version_script}", + f"-target {DEVICE_CLANG_TRIPLES[arch]}", # Cross compile for android + "-nostdlib", # Soong uses this option when building using bionic toolchain ]) link_context = LinkContext( - objs = [object_file], - flags = ld_flags, - out = output_so, - frontend = context.tools.clang_cxx(), + objs=[object_file], + flags=ld_flags, + out=output_so, + frontend=context.tools.clang_cxx(), ) self._linker.link(ninja, link_context) + # TODO: Short term hack to make the stub library available to + # vendor's inner tree. + # out/api_surfaces/stub.so is mounted into vendor's inner tree + # as platform/api_surfaces/stub.so. Create a phony edge so that + # vendor binaries can link against the stub via the dep chain + # vendor/out/vendor_bin -> + # vendor/platform/api_surfaces/stub.so -> + # out/api_surfaces/stub.so + src_path_in_vendor_inner_tree = output_so.replace( + context.out.api_surfaces_dir(), + "vendor/platform/api_surfaces") + ninja.add_global_phony(src_path_in_vendor_inner_tree, + [output_so]) + # Assemble the header files in out before compiling the rdeps + ninja.add_global_phony(src_path_in_vendor_inner_tree, api_deps) + # TODO: Generate Android.bp files. # Generate rules to build the API levels map. @@ -130,11 +167,12 @@ class CcApiAssemblyContext(object): self._add_api_levels_file(context, ninja) self._api_levels_file_added = True - # Generate phony rule to build the library. # TODO: This name probably conflictgs with something. - phony = "-".join([stub_library.api_surface, str(stub_library.api_surface_version), - stub_library.name]) + phony = "-".join([ + stub_library.api_surface, + str(stub_library.api_surface_version), stub_library.name + ]) ninja.add_phony(phony, api_deps) # Add a global phony to assemnble all apis ninja.add_global_phony(ASSEMBLE_PHONY_TARGET, [phony]) @@ -165,19 +203,19 @@ class CcApiAssemblyContext(object): "J": 16, "J-MR1": 17, "J-MR2": 18, - "K": 19, - "L": 21, + "K": 19, + "L": 21, "L-MR1": 22, - "M": 23, - "N": 24, + "M": 23, + "N": 24, "N-MR1": 25, - "O": 26, + "O": 26, "O-MR1": 27, - "P": 28, - "Q": 29, - "R": 30, - "S": 31, - "S-V2": 32, + "P": 28, + "Q": 29, + "R": 30, + "S": 31, + "S-V2": 32, "Tiramisu": 33, } for index, codename in enumerate(active_codenames): diff --git a/core/test_build_file_generator.py b/core/test_build_file_generator.py new file mode 100644 index 0000000..19d11ab --- /dev/null +++ b/core/test_build_file_generator.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# +# Copyright (C) 2022 The Android Open Source Project +# +# 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 +# ibuted 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. +"""Unit tests for build_file_generator.py""" + +import unittest +import tempfile + +from build_file_generator import ConfigAxis, \ +AndroidBpModule, AndroidBpComment, AndroidBpFile + + +class TestAndroidBpModule(unittest.TestCase): + + def test_required_properties(self): + with self.assertRaises(ValueError): + AndroidBpModule(name="", module_type="module_type") + with self.assertRaises(ValueError): + AndroidBpModule(name="name", module_type="") + + def test_primitive_properties(self): + module = AndroidBpModule(name="libfoo", module_type="cc_api_library") + module.add_property(prop="string_property", val="libfoo.map.txt") + module.add_property(prop="int_property", val=1) + module.add_property(prop="bool_property", val=True) + self.assertEqual( + module.string(), """cc_api_library { + name: "libfoo", + string_property: "libfoo.map.txt", + int_property: 1, + bool_property: true, +}""") + + def test_list_like_properties(self): + module = AndroidBpModule(name="libfoo", module_type="cc_api_library") + module.add_property(prop="single_element_list", val=["word1"]) + module.add_property(prop="multi_element_list", val=["word1", "word2"]) + self.assertEqual( + module.string(), """cc_api_library { + name: "libfoo", + single_element_list: ["word1"], + multi_element_list: [ + "word1", + "word2" + ], +}""") + + def test_arch_properties(self): + module = AndroidBpModule(name="libfoo", module_type="cc_api_library") + module.add_property("export_include_dirs", ["include"]) + module.add_property("export_include_dirs", ["include_arm"], + ConfigAxis.ArmConfig) + module.add_property("export_include_dirs", + ["include_x86", "include_x86_other"], + ConfigAxis.X86Config) + self.assertEqual( + module.string(), """cc_api_library { + name: "libfoo", + export_include_dirs: ["include"], + arch: { + arm: { + export_include_dirs: ["include_arm"], + }, + x86: { + export_include_dirs: [ + "include_x86", + "include_x86_other" + ], + }, + }, +}""") + + def test_extend_properties(self): + module = AndroidBpModule(name="libfoo", module_type="cc_api_library") + module.extend_property(prop="export_include_dirs", val="dir1") + module.extend_property(prop="export_include_dirs", + val="dir2") # value will be converted to list + module.add_property(prop="export_include_dirs", + val=["include_x86"], + axis=ConfigAxis.X86Config) + self.assertEqual( + module.string(), """cc_api_library { + name: "libfoo", + export_include_dirs: [ + "dir1", + "dir2" + ], + arch: { + x86: { + export_include_dirs: ["include_x86"], + }, + }, +}""") + + +class TestAndroidBpComment(unittest.TestCase): + + def test_single_line_comment(self): + comment = AndroidBpComment("This is a comment") + self.assertEqual(comment.string(), "// This is a comment") + comment = AndroidBpComment("// Do not double-add comment token") + self.assertEqual(comment.string(), + "// Do not double-add comment token") + + def test_line_separator_in_comment(self): + comment = AndroidBpComment("Comment string\nMore comment string") + self.assertEqual(comment.string(), """// Comment string +// More comment string""") + + +class TestAndroidBpFile(unittest.TestCase): + + def test_additions(self): + bp_file = AndroidBpFile("mydir") + bp_file.add_comment_string("WARNING: This is autogenerated") + bp_file.add_module( + AndroidBpModule(name="libfoo", module_type="cc_api_library")) + self.assertEqual( + bp_file.string(), """// WARNING: This is autogenerated +cc_api_library { + name: "libfoo", +}""") + + def test_stale_file(self): + with tempfile.TemporaryDirectory() as tmpdir: + bp_file = AndroidBpFile(tmpdir) + bp_file.add_comment_string("This is a comment") + # bp_file is stale since it has not been created yet + self.assertTrue(bp_file.is_stale()) + # Flush contents to Android.bp file + bp_file.write() + self.assertFalse(bp_file.is_stale()) + # Add more comments + bp_file.add_comment_string( + "This comment should force creation of a new Android.bp") + self.assertTrue(bp_file.is_stale()) + + +if __name__ == "__main__": + unittest.main() |