diff --git a/core/Makefile b/core/Makefile index 858fb8aca7..c7f2b8c783 100644 --- a/core/Makefile +++ b/core/Makefile @@ -476,7 +476,7 @@ SOONG_CONV := $(sort $(SOONG_CONV)) SOONG_CONV_DATA := $(call intermediates-dir-for,PACKAGING,soong_conversion)/soong_conv_data $(SOONG_CONV_DATA): @rm -f $@ - @$(foreach s,$(SOONG_CONV),echo "$(s),$(SOONG_CONV.$(s).TYPE),$(sort $(SOONG_CONV.$(s).PROBLEMS)),$(sort $(filter-out $(SOONG_ALREADY_CONV),$(SOONG_CONV.$(s).DEPS)))" >>$@;) + @$(foreach s,$(SOONG_CONV),echo "$(s),$(SOONG_CONV.$(s).TYPE),$(sort $(SOONG_CONV.$(s).PROBLEMS)),$(sort $(filter-out $(SOONG_ALREADY_CONV),$(SOONG_CONV.$(s).DEPS))),$(sort $(SOONG_CONV.$(s).MAKEFILES)),$(sort $(SOONG_CONV.$(s).INSTALLED))" >>$@;) SOONG_TO_CONVERT_SCRIPT := build/make/tools/soong_to_convert.py SOONG_TO_CONVERT := $(PRODUCT_OUT)/soong_to_convert.txt @@ -485,6 +485,19 @@ $(SOONG_TO_CONVERT): $(SOONG_CONV_DATA) $(SOONG_TO_CONVERT_SCRIPT) $(hide) $(SOONG_TO_CONVERT_SCRIPT) $< >$@ $(call dist-for-goals,droidcore,$(SOONG_TO_CONVERT)) +MK2BP_CATALOG_SCRIPT := build/make/tools/mk2bp_catalog.py +MK2BP_REMAINING_HTML := $(PRODUCT_OUT)/mk2bp_remaining.html +$(MK2BP_REMAINING_HTML): PRIVATE_CODE_SEARCH_BASE_URL := "https://cs.android.com/android/platform/superproject/+/master:" +$(MK2BP_REMAINING_HTML): $(SOONG_CONV_DATA) $(MK2BP_CATALOG_SCRIPT) + @rm -f $@ + $(hide) $(MK2BP_CATALOG_SCRIPT) \ + --device=$(TARGET_DEVICE) \ + --title="Remaining Android.mk files for $(TARGET_DEVICE)-$(TARGET_BUILD_VARIANT)" \ + --codesearch=$(PRIVATE_CODE_SEARCH_BASE_URL) \ + --out_dir="$(OUT_DIR)" \ + > $@ +$(call dist-for-goals,droidcore,$(MK2BP_REMAINING_HTML)) + # ----------------------------------------------------------------- # Modules use -Wno-error, or added default -Wall -Werror WALL_WERROR := $(PRODUCT_OUT)/wall_werror.txt diff --git a/core/binary.mk b/core/binary.mk index 6c1c4db816..29a78a3984 100644 --- a/core/binary.mk +++ b/core/binary.mk @@ -1818,6 +1818,10 @@ SOONG_CONV.$(LOCAL_MODULE).DEPS := \ $(my_shared_libraries) \ $(my_system_shared_libraries)) SOONG_CONV.$(LOCAL_MODULE).TYPE := native +SOONG_CONV.$(LOCAL_MODULE).MAKEFILES := \ + $(SOONG_CONV.$(LOCAL_MODULE).MAKEFILES) $(LOCAL_MODULE_MAKEFILE) +SOONG_CONV.$(LOCAL_MODULE).INSTALLED:= \ + $(SOONG_CONV.$(LOCAL_MODULE).INSTALLED) $(LOCAL_INSTALLED_MODULE) SOONG_CONV := $(SOONG_CONV) $(LOCAL_MODULE) ########################################################### diff --git a/core/java_common.mk b/core/java_common.mk index 658296d10d..373c5d0a36 100644 --- a/core/java_common.mk +++ b/core/java_common.mk @@ -6,10 +6,6 @@ ifneq ($(filter ../%,$(LOCAL_SRC_FILES)),) my_soong_problems += dotdot_srcs endif -ifneq (,$(LOCAL_JNI_SHARED_LIBRARIES)) -my_soong_problems += jni_libs -endif - ########################################################### ## Java version ########################################################### @@ -546,6 +542,10 @@ SOONG_CONV.$(LOCAL_MODULE).DEPS := \ $(LOCAL_JAVA_LIBRARIES) \ $(LOCAL_JNI_SHARED_LIBRARIES) SOONG_CONV.$(LOCAL_MODULE).TYPE := java +SOONG_CONV.$(LOCAL_MODULE).MAKEFILES := \ + $(SOONG_CONV.$(LOCAL_MODULE).MAKEFILES) $(LOCAL_MODULE_MAKEFILE) +SOONG_CONV.$(LOCAL_MODULE).INSTALLED := \ + $(SOONG_CONV.$(LOCAL_MODULE).INSTALLED) $(LOCAL_INSTALLED_MODULE) SOONG_CONV := $(SOONG_CONV) $(LOCAL_MODULE) endif diff --git a/tools/mk2bp_catalog.py b/tools/mk2bp_catalog.py new file mode 100755 index 0000000000..83abd62512 --- /dev/null +++ b/tools/mk2bp_catalog.py @@ -0,0 +1,892 @@ +#!/usr/bin/env python3 + +""" +Command to print info about makefiles remaining to be converted to soong. + +See usage / argument parsing below for commandline options. +""" + +import argparse +import csv +import itertools +import json +import os +import re +import sys + +DIRECTORY_PATTERNS = [x.split("/") for x in ( + "device/*", + "frameworks/*", + "hardware/*", + "packages/*", + "vendor/*", + "*", +)] + +def match_directory_group(pattern, filename): + match = [] + filename = filename.split("/") + if len(filename) < len(pattern): + return None + for i in range(len(pattern)): + pattern_segment = pattern[i] + filename_segment = filename[i] + if pattern_segment == "*" or pattern_segment == filename_segment: + match.append(filename_segment) + else: + return None + if match: + return os.path.sep.join(match) + else: + return None + +def directory_group(filename): + for pattern in DIRECTORY_PATTERNS: + match = match_directory_group(pattern, filename) + if match: + return match + return os.path.dirname(filename) + +class Analysis(object): + def __init__(self, filename, line_matches): + self.filename = filename; + self.line_matches = line_matches + +def analyze_lines(filename, lines, func): + line_matches = [] + for i in range(len(lines)): + line = lines[i] + stripped = line.strip() + if stripped.startswith("#"): + continue + if func(stripped): + line_matches.append((i+1, line)) + if line_matches: + return Analysis(filename, line_matches); + +def analyze_has_conditional(line): + return (line.startswith("ifeq") or line.startswith("ifneq") + or line.startswith("ifdef") or line.startswith("ifndef")) + +NORMAL_INCLUDES = [re.compile(pattern) for pattern in ( + "include \$+\(CLEAR_VARS\)", # These are in defines which are tagged separately + "include \$+\(BUILD_.*\)", + "include \$\(call first-makefiles-under, *\$\(LOCAL_PATH\)\)", + "include \$\(call all-subdir-makefiles\)", + "include \$\(all-subdir-makefiles\)", + "include \$\(call all-makefiles-under, *\$\(LOCAL_PATH\)\)", + "include \$\(call all-makefiles-under, *\$\(call my-dir\).*\)", + "include \$\(BUILD_SYSTEM\)/base_rules.mk", # called out separately + "include \$\(call all-named-subdir-makefiles,.*\)", + "include \$\(subdirs\)", +)] +def analyze_has_wacky_include(line): + if not (line.startswith("include") or line.startswith("-include") + or line.startswith("sinclude")): + return False + for matcher in NORMAL_INCLUDES: + if matcher.fullmatch(line): + return False + return True + +BASE_RULES_RE = re.compile("include \$\(BUILD_SYSTEM\)/base_rules.mk") + +class Analyzer(object): + def __init__(self, title, func): + self.title = title; + self.func = func + + +ANALYZERS = ( + Analyzer("ifeq / ifneq", analyze_has_conditional), + Analyzer("Wacky Includes", analyze_has_wacky_include), + Analyzer("Calls base_rules", lambda line: BASE_RULES_RE.fullmatch(line)), + Analyzer("Calls define", lambda line: line.startswith("define ")), + Analyzer("Has ../", lambda line: "../" in line), + Analyzer("dist-for-goals", lambda line: "dist-for-goals" in line), + Analyzer(".PHONY", lambda line: ".PHONY" in line), + Analyzer("render-script", lambda line: ".rscript" in line), + Analyzer("vts src", lambda line: ".vts" in line), + Analyzer("COPY_HEADERS", lambda line: "LOCAL_COPY_HEADERS" in line), +) + +class Summary(object): + def __init__(self): + self.makefiles = dict() + self.directories = dict() + + def Add(self, makefile): + self.makefiles[makefile.filename] = makefile + self.directories.setdefault(directory_group(makefile.filename), []).append(makefile) + +class Makefile(object): + def __init__(self, filename): + self.filename = filename + + # Analyze the file + with open(filename, "r", errors="ignore") as f: + try: + lines = f.readlines() + except UnicodeDecodeError as ex: + sys.stderr.write("Filename: %s\n" % filename) + raise ex + lines = [line.strip() for line in lines] + + self.analyses = dict([(analyzer, analyze_lines(filename, lines, analyzer.func)) for analyzer + in ANALYZERS]) + +def find_android_mk(): + cwd = os.getcwd() + for root, dirs, files in os.walk(cwd): + for filename in files: + if filename == "Android.mk": + yield os.path.join(root, filename)[len(cwd) + 1:] + for ignore in (".git", ".repo"): + if ignore in dirs: + dirs.remove(ignore) + +def is_aosp(dirname): + for d in ("device/sample", "hardware/interfaces", "hardware/libhardware", + "hardware/ril"): + if dirname.startswith(d): + return True + for d in ("device/", "hardware/", "vendor/"): + if dirname.startswith(d): + return False + return True + +def is_google(dirname): + for d in ("device/google", + "hardware/google", + "test/sts", + "vendor/auto", + "vendor/google", + "vendor/unbundled_google", + "vendor/widevine", + "vendor/xts"): + if dirname.startswith(d): + return True + return False + +def make_annotation_link(annotations, analysis, modules): + if analysis: + return "%s" % ( + annotations.Add(analysis, modules), + len(analysis) + ) + else: + return ""; + + +def is_clean(makefile): + for analysis in makefile.analyses.values(): + if analysis: + return False + return True + +class Annotations(object): + def __init__(self): + self.entries = [] + self.count = 0 + + def Add(self, makefiles, modules): + self.entries.append((makefiles, modules)) + self.count += 1 + return self.count-1 + +class SoongData(object): + def __init__(self, reader): + """Read the input file and store the modules and dependency mappings. + """ + self.problems = dict() + self.deps = dict() + self.reverse_deps = dict() + self.module_types = dict() + self.makefiles = dict() + self.reverse_makefiles = dict() + self.installed = dict() + self.modules = set() + + for (module, module_type, problem, dependencies, makefiles, installed) in reader: + self.modules.add(module) + makefiles = [f for f in makefiles.strip().split(' ') if f != ""] + self.module_types[module] = module_type + self.problems[module] = problem + self.deps[module] = [d for d in dependencies.strip().split(' ') if d != ""] + for dep in self.deps[module]: + if not dep in self.reverse_deps: + self.reverse_deps[dep] = [] + self.reverse_deps[dep].append(module) + self.makefiles[module] = makefiles + for f in makefiles: + self.reverse_makefiles.setdefault(f, []).append(module) + for f in installed.strip().split(' '): + self.installed[f] = module + +def count_deps(depsdb, module, seen): + """Based on the depsdb, count the number of transitive dependencies. + + You can pass in an reversed dependency graph to count the number of + modules that depend on the module.""" + count = 0 + seen.append(module) + if module in depsdb: + for dep in depsdb[module]: + if dep in seen: + continue + count += 1 + count_deps(depsdb, dep, seen) + return count + +def contains_unblocked_modules(soong, modules): + for m in modules: + if len(soong.deps[m]) == 0: + return True + return False + +def contains_blocked_modules(soong, modules): + for m in modules: + if len(soong.deps[m]) > 0: + return True + return False + +OTHER_PARTITON = "_other" +HOST_PARTITON = "_host" + +def get_partition_from_installed(HOST_OUT_ROOT, PRODUCT_OUT, filename): + host_prefix = HOST_OUT_ROOT + "/" + device_prefix = PRODUCT_OUT + "/" + + if filename.startswith(host_prefix): + return HOST_PARTITON + + elif filename.startswith(device_prefix): + index = filename.find("/", len(device_prefix)) + if index < 0: + return OTHER_PARTITON + return filename[len(device_prefix):index] + + return OTHER_PARTITON + +def format_module_link(module): + return "%s" % (module, module) + +def format_module_list(modules): + return "".join(["
+ This page analyzes the remaining Android.mk files in the Android Source tree. +
+ The modules are first broken down by which of the device filesystem partitions + they are installed to. This also includes host tools and testcases which don't + actually reside in their own partition but convenitely group together. +
+ The makefiles for each partition are further are grouped into a set of directories + aritrarily picked to break down the problem size by owners. +
+
Total | +The total number of makefiles in this each directory. | +
---|---|
Unblocked | +Makefiles containing one or more modules that don't have any + additional dependencies pending before conversion. | +
Blocked | +Makefiles containiong one or more modules which do have + additional prerequesite depenedencies that are not yet converted. | +
Clean | +The number of makefiles that have none of the following warnings. | +
ifeq / ifneq | +Makefiles that use ifeq or ifneq . i.e.
+ conditionals. |
+
Wacky Includes | +Makefiles that include files other than the standard build-system
+ defined template and macros. |
+
Calls base_rules | +Makefiles that include base_rules.mk directly. | +
Calls define | +Makefiles that define their own macros. Some of these are easy to convert
+ to soong defaults , but others are complex. |
+
Has ../ | +Makefiles containing the string "../" outside of a comment. These likely + access files outside their directories. | +
dist-for-goals | +Makefiles that call dist-for-goals directly. |
+
.PHONY | +Makefiles that declare .PHONY targets. | +
renderscript | +Makefiles defining targets that depend on .rscript source files. |
+
vts src | +Makefiles defining targets that depend on .vts source files. |
+
COPY_HEADERS | +Makefiles using LOCAL_COPY_HEADERS. | +
+ Following the list of directories is a list of the modules that are installed on + each partition. Potential issues from their makefiles are listed, as well as the + total number of dependencies (both blocking that module and blocked by that module) + and the list of direct dependencies. Note: The number is the number of all transitive + dependencies and the list of modules is only the direct dependencies. +
Directory | +Total | +Unblocked | +Blocked | +Clean | + """ % { + "partition": partition + }) + + for analyzer in ANALYZERS: + print("""%s | """ % analyzer.title) + + print("
---|---|---|---|---|---|
%(dirname)s | +%(makefiles)s | +%(unblocked)s | +%(blocked)s | +%(clean)s | + """ % { + "rowclass": rowclass, + "dirname": dirname, + "makefiles": make_annotation_link(annotations, all_makefiles, modules), + "unblocked": make_annotation_link(annotations, unblocked_makefiles, modules), + "blocked": make_annotation_link(annotations, blocked_makefiles, modules), + "clean": make_annotation_link(annotations, clean_makefiles, modules), + }) + for analyzer in ANALYZERS: + analyses = [m.analyses.get(analyzer) for m in makefiles if m.analyses.get(analyzer)] + print("""%s | """ + % make_annotation_link(annotations, analyses, modules)) + + print("