kmi_defines: Removed need for .o.d.keep files

This is done in get_src_ccline_deps(), function extract_c_src() was
removed, its work is now done by get_src_ccline_deps(). Also remove
all_ko_and_vmlinux() which is no longer used.

Changed uses of empty strings to None in various functions and the
callers when they test their return values. Function that returned
a Tuple[] now returns an Optional[Tuple[]] caller now tests for None
before cracking the tuple.

Bug: 139885116
Change-Id: If130851ddf7e2027d31d413bc09eeb18616acee3
Signed-off-by: Matthias Maennich <maennich@google.com>
diff --git a/abi/kmi_defines.py b/abi/kmi_defines.py
index 009700d..f3fbd37 100755
--- a/abi/kmi_defines.py
+++ b/abi/kmi_defines.py
@@ -32,7 +32,7 @@
 import re
 import subprocess
 import sys
-from typing import List, Tuple, Iterable
+from typing import List, Optional, Tuple
 from typing import Set  # pytype needs this, pylint: disable=unused-import
 
 COMPILER = "clang"  # TODO(pantin): should be determined at run-time
@@ -40,6 +40,24 @@
 INDENT = 4  # number of spaces to indent for each depth level
 PROGRAM = os.path.basename(sys.argv[0])
 
+#   Dependency that is hidden by the transformation of the .o.d file into
+#   the .o.cmd file as part of the Linux build environment.  This header is
+#   purposely removed and replaced by fictitious set of empty header files
+#   that were never part of the actual compilation of the .o files.  Those
+#   fictitious empty files are generated under the build environment output
+#   directory in this subdirectory:
+#       include/config
+#
+#   This is the actual header file that was part of the compilation of every
+#   .o file, the HIDDEN_DEP are added to the dependencies of every .o file.
+#
+#   It is important that this file be added because it is unknowable whether
+#   the #defines in it were depended upon by a module to alter its behaviour
+#   at compile time.  For example to pass some flags or not pass some flags
+#   to a function.
+
+HIDDEN_DEP = "include/generated/autoconf.h"
+
 
 class StopError(Exception):
     """Exception raised to stop work when an unexpected error occurs."""
@@ -144,55 +162,57 @@
     return result[0], result[1]  # left, right
 
 
-def extract_c_src(obj: str, dependencies: List[str]) -> Tuple[str, List[str]]:
-    """Separate the C source file from the other dependencies.
-
-    Returns a tuple with the C source code file and a list with the remaining
-    dependencies.  If the source file is not a C source file:
-        return "", []
-    an ealier implementation used:
-         returne None, []
-    but that caused type errors from ptype. Instead of obfuscating the type
-    of the value of this function, returning "", [] is appropriate.
-    """
-    if not dependencies:
-        raise StopError("empty dependencies for: " + obj)
-    src = dependencies[0]
-    if not src.endswith(".c"):
-        return "", []
-    return src, dependencies[1:]
-
-
-def get_src_ccline_deps(obj: str) -> Tuple[str, str, List[str]]:
+def get_src_ccline_deps(obj: str) -> Optional[Tuple[str, str, List[str]]]:
     """Get the C source file, its cc_line, and non C source dependencies.
 
     If the tool used to produce the object is not the compiler, or if the
-    source file is not a C source file then it returns:
-        "", "" []
+    source file is not a C source file None is returned.
 
     Otherwise it returns a triplet with the C source file name, its cc_line,
     the remaining dependencies.
     """
-    dot_obj = os.path.join(os.path.dirname(obj), "." + os.path.basename(obj))
-    cmd = dot_obj + ".cmd"
-    content = readfile(cmd)
-    line = lines_get_first_line(content)
-    _, cc_line = makefile_assignment_split(line)
+    o_cmd = os.path.join(os.path.dirname(obj),
+                         "." + os.path.basename(obj) + ".cmd")
+
+    contents = readfile(o_cmd)
+    contents = re.sub(r"\$\(wildcard[^)]*\)", " ", contents)
+    contents = re.sub(r"[ \t]*\\\n[ \t]*", " ", contents)
+    lines = lines_to_list(contents)
+
+    cc_line = None
+    deps = None
+    source = None
+    for line in lines:
+        if line.startswith("cmd_"):
+            cc_line = line
+        elif line.startswith("deps_"):
+            deps = line
+        elif line.startswith("source_"):
+            source = line
+
+    if cc_line is None:
+        raise StopError("missing cmd_* variable in: " + o_cmd)
+    _, cc_line = makefile_assignment_split(cc_line)
     if cc_line.split(maxsplit=1)[0] != COMPILER:
         #   The object file was made by strip, symbol renames, etc.
         #   i.e. it was not the result of running the compiler, thus
         #   it can not contribute to #define compile time constants.
-        return "", "", []
+        return None
 
-    odkeep = dot_obj + ".d.keep"
-    file_must_exist(odkeep)
-    content = readfile(odkeep)
-    src, dependendencies = extract_c_src(
-        obj, makefile_depends_get_dependencies(content))
-    if not src:
-        return "", "", []
+    if source is None:
+        raise StopError("missing source_* variable in: " + o_cmd)
+    _, source = makefile_assignment_split(source)
+    source = source.strip()
+    if not source.endswith(".c"):
+        return None
 
-    return src, cc_line, dependendencies
+    if deps is None:
+        raise StopError("missing deps_* variable in: " + o_cmd)
+    _, deps = makefile_assignment_split(deps)
+    dependendencies = deps.split()
+    dependendencies.append(HIDDEN_DEP)
+
+    return source, cc_line, dependendencies
 
 
 def lines_to_list(lines: str) -> List[str]:
@@ -455,9 +475,9 @@
     the risk of invoking member functions at run-time on objects that do not
     provide them.  Having this class makes the code more reliable.
     """
-    def get_error(self) -> str:  # pylint: disable=no-self-use
-        """Return an empty error string, which means there was no error."""
-        return ""
+    def get_error(self) -> Optional[str]:  # pylint: disable=no-self-use
+        """Return None for the error, means there was no error."""
+        return None
 
     def get_deps_set(self) -> Set[str]:  # pylint: disable=no-self-use
         """Return the set of dependencies for the kernel component."""
@@ -480,7 +500,7 @@
         self._error = error
         self._filename = filename
 
-    def get_error(self) -> str:
+    def get_error(self) -> Optional[str]:
         """Return the error."""
         return self._filename + ": " + self._error
 
@@ -511,9 +531,10 @@
         self._targets = []
         for obj in self._files_o:
             file_must_exist(obj)
-            src, cc_line, dependendencies = get_src_ccline_deps(obj)
-            if not cc_line or not src:
+            result = get_src_ccline_deps(obj)
+            if result is None:
                 continue
+            src, cc_line, dependendencies = result
 
             file_must_exist(src)
             depends = []
@@ -566,24 +587,6 @@
                                             " ".join([*stop_error.args]))
 
 
-def all_ko_and_vmlinux() -> Iterable[str]:
-    """Generator that yields vmlinux.o and all the *.ko files."""
-
-    #   TODO(pantin): remove if no measurable slowdown when using (below):
-    #
-    #   components = pool.map(kernel_component_factory, ["vmlinux.o"] +
-    #                         [str(ko) for ko in pathlib.Path().rglob("*.ko")])
-    #
-    #   instead of:
-    #
-    #   components = pool.map(
-    #       kernel_component_factory, all_ko_and_vmlinux())
-    #
-    yield "vmlinux.o"  # yield vmlinux.o so its worked on first
-    for kofile in pathlib.Path().rglob("*.ko"):
-        yield str(kofile)
-
-
 def work_on_all_components() -> List[KernelComponentBase]:
     """Return a list of KernelComponentBase objects."""
 
@@ -605,7 +608,7 @@
     failed = False
     for comp in components:
         error = comp.get_error()
-        if error != "":
+        if error:
             logging.error(error)
             failed = True
             continue
@@ -646,7 +649,7 @@
     comp = kernel_component_factory(args.file)
 
     error = comp.get_error()
-    if error != "":
+    if error:
         logging.error(error)
         return 1
     if DEBUG: