aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorSpandan Das <spandandas@google.com>2022-10-24 21:10:14 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2022-10-24 21:10:14 +0000
commit06b32c18a40f0e86da1606dd7defc0cfed956d0f (patch)
tree64f4f8905d3257ecb942b28a670a07ca77348e61 /core
parentc2a6363421d912845c782e30500c41b23256fc0f (diff)
parent9f8a038323c3a0a22ed3d035dbe106ab2a36ec59 (diff)
downloadorchestrator-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.py63
-rw-r--r--core/build_file_generator.py349
-rw-r--r--core/cc/api_assembly.py108
-rw-r--r--core/test_build_file_generator.py152
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()