Merge "Add support for partial update and target library."
diff --git a/scripts/cargo2android.py b/scripts/cargo2android.py
index 64af845..908caf5 100755
--- a/scripts/cargo2android.py
+++ b/scripts/cargo2android.py
@@ -681,8 +681,10 @@
self.dump_android_flags()
if self.externs:
self.dump_android_externs()
- self.dump_android_property_list('static_libs', '"lib%s"', self.static_libs)
- self.dump_android_property_list('shared_libs', '"lib%s"', self.shared_libs)
+ static_libs = [lib for lib in self.static_libs if not lib in self.runner.args.lib_blocklist]
+ self.dump_android_property_list('static_libs', '"lib%s"', static_libs)
+ shared_libs = [lib for lib in self.shared_libs if not lib in self.runner.args.lib_blocklist]
+ self.dump_android_property_list('shared_libs', '"lib%s"', shared_libs)
def main_src_basename_path(self):
return re.sub('/', '_', re.sub('.rs$', '', self.main_src))
@@ -704,6 +706,7 @@
def decide_one_module_type(self, crate_type):
"""Decide which Android module type to use."""
host = '' if self.device_supported else '_host'
+ rlib = '_rlib' if self.runner.args.force_rlib else ''
if crate_type == 'bin': # rust_binary[_host]
self.module_type = 'rust_binary' + host
# In rare cases like protobuf-codegen, the output binary name must
@@ -714,11 +717,11 @@
# TODO(chh): should this be rust_library[_host]?
# Assuming that Cargo.toml do not use both 'lib' and 'rlib',
# because we map them both to rlib.
- self.module_type = 'rust_library' + host
+ self.module_type = 'rust_library' + rlib + host
self.stem = 'lib' + self.crate_name
self.module_name = altered_name(self.stem)
elif crate_type == 'rlib': # rust_library[_host]
- self.module_type = 'rust_library' + host
+ self.module_type = 'rust_library' + rlib + host
self.stem = 'lib' + self.crate_name
self.module_name = altered_name(self.stem)
elif crate_type == 'dylib': # rust_library[_host]_dylib
@@ -1607,11 +1610,21 @@
nargs='*',
help='Mark the main library as apex_available with the given apexes.')
parser.add_argument(
+ '--force-rlib',
+ action='store_true',
+ default=False,
+ help='Make the main library an rlib.')
+ parser.add_argument(
'--dependency-blocklist',
nargs='*',
default=[],
help='Do not emit the given dependencies.')
parser.add_argument(
+ '--lib-blocklist',
+ nargs='*',
+ default=[],
+ help='Do not emit the given C libraries as dependencies.')
+ parser.add_argument(
'--test-blocklist',
nargs='*',
default=[],
diff --git a/scripts/update_crate_tests.py b/scripts/update_crate_tests.py
index 53bfd83..192f50e 100755
--- a/scripts/update_crate_tests.py
+++ b/scripts/update_crate_tests.py
@@ -13,170 +13,251 @@
# 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.
-"""Add tests to TEST_MAPPING. Include tests for reverse dependencies."""
+"""Add or update tests to TEST_MAPPING.
+
+This script uses Bazel to find reverse dependencies on a crate and generates a
+TEST_MAPPING file. It accepts the absolute path to a crate as argument. If no
+argument is provided, it assumes the crate is the current directory.
+
+ Usage:
+ $ . build/envsetup.sh
+ $ lunch aosp_arm64-eng
+ $ update_crate_tests.py $ANDROID_BUILD_TOP/external/rust/crates/libc
+
+This script is automatically called by external_updater.
+"""
+
import json
import os
import platform
import subprocess
import sys
-test_options = {
+# Some tests requires specific options. Consider fixing the upstream crate
+# before updating this dictionary.
+TEST_OPTIONS = {
"ring_device_test_tests_digest_tests": [{"test-timeout": "600000"}],
"ring_device_test_src_lib": [{"test-timeout": "100000"}],
}
-test_exclude = [
+
+# Excluded tests. These tests will be ignored by this script.
+TEST_EXCLUDE = [
"aidl_test_rust_client",
"aidl_test_rust_service"
- ]
-exclude_paths = [
+]
+
+# Excluded modules.
+EXCLUDE_PATHS = [
"//external/adhd",
"//external/crosvm",
"//external/libchromeos-rs",
"//external/vm_tools"
- ]
+]
+
+
+class UpdaterException(Exception):
+ """Exception generated by this script."""
+
class Env(object):
- def __init__(self, path):
+ """Env captures the execution environment.
+
+ It ensures this script is executed within an AOSP repository.
+
+ Attributes:
+ ANDROID_BUILD_TOP: A string representing the absolute path to the top
+ of the repository.
+ """
+ def __init__(self):
try:
self.ANDROID_BUILD_TOP = os.environ['ANDROID_BUILD_TOP']
- except:
- sys.exit('ERROR: this script must be run from an Android tree.')
- if path == None:
- self.cwd = os.getcwd()
- else:
- self.cwd = path
- try:
- self.cwd_relative = self.cwd.split(self.ANDROID_BUILD_TOP)[1]
- self.setup = True
- except:
- # Mark setup as failed if a path to a rust crate is not provided.
- self.setup = False
+ except KeyError:
+ raise UpdaterException('$ANDROID_BUILD_TOP is not defined; you '
+ 'must first source build/envsetup.sh and '
+ 'select a target.')
+
class Bazel(object):
- # set up the Bazel queryview
+ """Bazel wrapper.
+
+ The wrapper is used to call bazel queryview and generate the list of
+ reverse dependencies.
+
+ Attributes:
+ path: The path to the bazel executable.
+ """
def __init__(self, env):
- os.chdir(env.ANDROID_BUILD_TOP)
- print("Building Bazel Queryview. This can take a couple of minutes...")
- cmd = "./build/soong/soong_ui.bash --build-mode --all-modules --dir=. queryview"
- try:
- out = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
- self.setup = True
- except subprocess.CalledProcessError as e:
- print("Error: Unable to update TEST_MAPPING due to the following build error:")
- print(e.output)
- # Mark setup as failed if the Bazel queryview fails to build.
- self.setup = False
- os.chdir(env.cwd)
+ """Constructor.
- def path(self):
- # Only tested on Linux.
+ Note that the current directory is changed to ANDROID_BUILD_TOP.
+
+ Args:
+ env: An instance of Env.
+
+ Raises:
+ UpdaterException: an error occurred while calling soong_ui.
+ """
if platform.system() != 'Linux':
- sys.exit('ERROR: this script has only been tested on Linux.')
- return "/usr/bin/bazel"
+ raise UpdaterException('This script has only been tested on Linux.')
+ self.path = os.path.join(env.ANDROID_BUILD_TOP, "tools", "bazel")
+ soong_ui = os.path.join(env.ANDROID_BUILD_TOP, "build", "soong", "soong_ui.bash")
- # Return all modules for a given path.
+ # soong_ui requires to be at the root of the repository.
+ os.chdir(env.ANDROID_BUILD_TOP)
+ print("Generating Bazel files...")
+ cmd = [soong_ui, "--make-mode", "GENERATE_BAZEL_FILES=1", "nothing"]
+ try:
+ subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
+ except subprocess.CalledProcessError as e:
+ raise UpdaterException('Unable to generate bazel workspace: ' + e.output)
+
+ print("Building Bazel Queryview. This can take a couple of minutes...")
+ cmd = [soong_ui, "--build-mode", "--all-modules", "--dir=.", "queryview"]
+ try:
+ subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
+ except subprocess.CalledProcessError as e:
+ raise UpdaterException('Unable to update TEST_MAPPING: ' + e.output)
+
def query_modules(self, path):
- with open(os.devnull, 'wb') as DEVNULL:
- cmd = self.path() + " query --config=queryview /" + path + ":all"
- out = subprocess.check_output(cmd, shell=True, stderr=DEVNULL, text=True).strip().split("\n")
- modules = set()
- for line in out:
- # speed up by excluding unused modules.
- if "windows_x86" in line:
- continue
- modules.add(line)
- return modules
+ """Returns all modules for a given path."""
+ cmd = self.path + " query --config=queryview /" + path + ":all"
+ out = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True).strip().split("\n")
+ modules = set()
+ for line in out:
+ # speed up by excluding unused modules.
+ if "windows_x86" in line:
+ continue
+ modules.add(line)
+ return modules
- # Return all reverse dependencies for a single module.
def query_rdeps(self, module):
- with open(os.devnull, 'wb') as DEVNULL:
- cmd = (self.path() + " query --config=queryview \'rdeps(//..., " +
- module + ")\' --output=label_kind")
- out = (subprocess.check_output(cmd, shell=True, stderr=DEVNULL, text=True)
- .strip().split("\n"))
- if '' in out:
- out.remove('')
- return out
+ """Returns all reverse dependencies for a single module."""
+ cmd = (self.path + " query --config=queryview \'rdeps(//..., " +
+ module + ")\' --output=label_kind")
+ out = (subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True)
+ .strip().split("\n"))
+ if '' in out:
+ out.remove('')
+ return out
def exclude_module(self, module):
- for path in exclude_paths:
+ for path in EXCLUDE_PATHS:
if module.startswith(path):
return True
return False
- # Return all reverse dependency tests for modules in this package.
def query_rdep_tests(self, modules):
+ """Returns all reverse dependency tests for modules in this package."""
rdep_tests = set()
for module in modules:
for rdep in self.query_rdeps(module):
- rule_type, tmp, mod = rdep.split(" ")
+ rule_type, _, mod = rdep.split(" ")
if rule_type == "rust_test_" or rule_type == "rust_test":
if self.exclude_module(mod) == False:
rdep_tests.add(mod.split(":")[1].split("--")[0])
return rdep_tests
-class Crate(object):
- def __init__(self, path, bazel):
- self.modules = bazel.query_modules(path)
- self.rdep_tests = bazel.query_rdep_tests(self.modules)
+class Package(object):
+ """A Bazel package.
+
+ Attributes:
+ dir: The absolute path to this package.
+ dir_rel: The relative path to this package.
+ rdep_tests: The list of computed reverse dependencies.
+ """
+ def __init__(self, path, env, bazel):
+ """Constructor.
+
+ Note that the current directory is changed to the package location when
+ called.
+
+ Args:
+ path: Path to the package.
+ env: An instance of Env.
+ bazel: An instance of Bazel.
+
+ Raises:
+ UpdaterException: the package does not appear to belong to the
+ current repository.
+ """
+ if path == None:
+ self.dir = os.getcwd()
+ else:
+ self.dir = path
+ try:
+ self.dir_rel = self.dir.split(env.ANDROID_BUILD_TOP)[1]
+ except IndexError:
+ raise UpdaterException('The path ' + self.dir + ' is not under ' +
+ env.ANDROID_BUILD_TOP + '; You must be in the '
+ 'directory of a crate or pass its absolute path '
+ 'as first argument.')
+
+ # Move to the package_directory.
+ os.chdir(self.dir)
+ modules = bazel.query_modules(self.dir_rel)
+ self.rdep_tests = bazel.query_rdep_tests(modules)
def get_rdep_tests(self):
return self.rdep_tests
class TestMapping(object):
+ """A TEST_MAPPING file.
+
+ Attributes:
+ package: The package associated with this TEST_MAPPING file.
+ """
def __init__(self, path):
- self.env = Env(path)
- self.bazel = Bazel(self.env)
+ """Constructor.
- def is_setup(self):
- return self.env.setup and self.bazel.setup
+ Args:
+ path: The absolute path to the package.
+ """
+ env = Env()
+ bazel = Bazel(env)
+ self.package = Package(path, env, bazel)
- def create_test_mapping(self, path):
- if not self.is_setup():
- return
- tests = self.get_tests(path)
+ def create(self):
+ """Generates the TEST_MAPPING file."""
+ tests = self.package.get_rdep_tests()
if not bool(tests):
return
test_mapping = self.tests_to_mapping(tests)
self.write_test_mapping(test_mapping)
- def get_tests(self, path):
- # for each path collect local Rust modules.
- if path is not None and path != "":
- return Crate(self.env.cwd_relative + "/" + path, self.bazel).get_rdep_tests()
- else:
- return Crate(self.env.cwd_relative, self.bazel).get_rdep_tests()
-
def tests_to_mapping(self, tests):
+ """Translate the test list into a dictionary."""
test_mapping = {"presubmit": []}
for test in tests:
- if test in test_exclude:
+ if test in TEST_EXCLUDE:
continue
- if test in test_options:
- test_mapping["presubmit"].append({"name": test, "options": test_options[test]})
+ if test in TEST_OPTIONS:
+ test_mapping["presubmit"].append({"name": test, "options": TEST_OPTIONS[test]})
else:
test_mapping["presubmit"].append({"name": test})
test_mapping["presubmit"] = sorted(test_mapping["presubmit"], key=lambda t: t["name"])
return test_mapping
def write_test_mapping(self, test_mapping):
+ """Writes the TEST_MAPPING file."""
with open("TEST_MAPPING", "w") as json_file:
json_file.write("// Generated by update_crate_tests.py for tests that depend on this crate.\n")
json.dump(test_mapping, json_file, indent=2, separators=(',', ': '), sort_keys=True)
json_file.write("\n")
print("TEST_MAPPING successfully updated!")
+
def main():
if len(sys.argv) == 2:
path = sys.argv[1]
else:
path = None
- test_mapping = TestMapping(path)
- test_mapping.create_test_mapping(None)
- if not test_mapping.is_setup():
- raise ValueError('Error getting crate tests.')
+ try:
+ test_mapping = TestMapping(path)
+ except UpdaterException as err:
+ sys.exit("Error: " + str(err))
+ test_mapping.create()
if __name__ == '__main__':
main()