[PATCH v2 1/1] kbuild: record real-prereqs in .cmd files

Luis Augenstein <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.kbuild.devel
Message-ID <[email protected]>
Record $(real-prereqs), the non-phony prerequisites of the target, in a
new metadata field:

    make_prereqs_<target> := <prerequisites>

Write the field from both cmd_and_savecmd and cmd_and_fixdep.

Update scripts/make_fit.py to read only savedcmd_* instead of parsing the
complete .cmd file.

Ignore make_prereqs_* in KernelSbom.

Link: https://lore.kernel.org/r/[email protected]
Assisted-by: Cursor:GPT-5.6 Sol
Co-developed-by: Maximilian Huber <[email protected]>
Signed-off-by: Maximilian Huber <[email protected]>
Signed-off-by: Luis Augenstein <[email protected]>
---
 scripts/Kbuild.include                  |   9 +-
 scripts/basic/fixdep.c                  | 159 ++++++++++++++++++++----
 scripts/make_fit.py                     |   2 +-
 scripts/sbom/sbom/cmd_graph/cmd_file.py |   4 +
 4 files changed, 148 insertions(+), 26 deletions(-)

diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include
index 8c311b997e2..6daa244ba0e 100644
--- a/scripts/Kbuild.include
+++ b/scripts/Kbuild.include
@@ -181,6 +181,9 @@ endif
 # (needed for the shell)
 make-cmd = $(call escsq,$(subst $(pound),$$(pound),$(subst $$,$$$$,$(cmd_$(1)))))
 
+# prerequisites to record in .cmd files, excluding those covered in deps_*
+cmd-prereqs = $(call escsq,$(filter-out $(deps_$@), $(real-prereqs)))
+
 # Find any prerequisites that are newer than target or that do not exist.
 # PHONY targets skipped in both cases.
 # If there is no prerequisite other than phony targets, $(newer-prereqs) becomes
@@ -198,14 +201,16 @@ if_changed = $(if $(if-changed-cond),$(cmd_and_savecmd),@:)
 
 cmd_and_savecmd =                                                            \
 	$(cmd);                                                              \
-	printf '%s\n' 'savedcmd_$@ := $(make-cmd)' > $(dot-target).cmd
+	printf '%s\n\n%s\n' 'savedcmd_$@ := $(make-cmd)'                    \
+		'make_prereqs_$@ := $(cmd-prereqs)' > $(dot-target).cmd
 
 # Execute the command and also postprocess generated .d dependencies file.
 if_changed_dep = $(if $(if-changed-cond),$(cmd_and_fixdep),@:)
 
 cmd_and_fixdep =                                                             \
 	$(cmd);                                                              \
-	$(objtree)/scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\
+	$(objtree)/scripts/basic/fixdep $(depfile) $@ '$(make-cmd)'          \
+		'$(cmd-prereqs)' > $(dot-target).cmd;                        \
 	rm -f $(depfile)
 
 # Usage: $(call if_changed_rule,foo)
diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c
index cdd5da7e009..e644c1590a9 100644
--- a/scripts/basic/fixdep.c
+++ b/scripts/basic/fixdep.c
@@ -62,15 +62,17 @@
  *
  * It is invoked as
  *
- *   fixdep <depfile> <target> <cmdline>
+ *   fixdep <depfile> <target> <cmdline> <prereqs>
  *
  * and will read the dependency file <depfile>
  *
  * The transformed dependency snipped is written to stdout.
  *
- * It first generates a line
+ * It first generates the lines
  *
- *   savedcmd_<target> = <cmdline>
+ *   savedcmd_<target> := <cmdline>
+ *
+ *   make_prereqs_<target> := <prereqs_without_deps>
  *
  * and then basically copies the .<target>.d file to stdout, in the
  * process filtering out the dependency on autoconf.h and adding
@@ -103,7 +105,7 @@
 
 static void usage(void)
 {
-	fprintf(stderr, "Usage: fixdep <depfile> <target> <cmdline>\n");
+	fprintf(stderr, "Usage: fixdep <depfile> <target> <cmdline> <prereqs>\n");
 	exit(1);
 }
 
@@ -114,6 +116,17 @@ struct item {
 	char		name[];
 };
 
+struct dep_item {
+	struct dep_item	*next;
+	char		name[];
+};
+
+struct dep_info {
+	char			*source;
+	struct dep_item		*deps;
+	struct dep_item		*last_dep;
+};
+
 #define HASHSZ 256
 static struct item *config_hashtab[HASHSZ], *file_hashtab[HASHSZ];
 
@@ -163,16 +176,42 @@ static bool in_hashtable(const char *name, int len, struct item *hashtab[])
 	return false;
 }
 
+/*
+ * Append a dependency or include/config/<SYM> path to the info linked list.
+ */
+static void dep_info_add(struct dep_info *info, const char *name, int len)
+{
+	struct dep_item *dep = xmalloc(sizeof(*dep) + len + 1);
+
+	memcpy(dep->name, name, len);
+	dep->name[len] = '\0';
+	dep->next = NULL;
+
+	if (info->last_dep)
+		info->last_dep->next = dep;
+	else
+		info->deps = dep;
+	info->last_dep = dep;
+}
+
 /*
  * Record the use of a CONFIG_* word.
  */
-static void use_config(const char *m, int slen)
+static void use_config(const char *m, int slen, struct dep_info *info)
 {
 	if (in_hashtable(m, slen, config_hashtab))
 		return;
 
-	/* Print out a dependency path from a symbol name. */
-	printf("    $(wildcard include/config/%.*s) \\\n", slen, m);
+	/* Build a dependency path from a symbol name. */
+	static const char config_path[] = "include/config/";
+	char *path;
+	int path_len = sizeof(config_path) - 1 + slen;
+
+	path = xmalloc(path_len);
+	memcpy(path, config_path, sizeof(config_path) - 1);
+	memcpy(path + sizeof(config_path) - 1, m, slen);
+	dep_info_add(info, path, path_len);
+	free(path);
 }
 
 /* test if s ends in sub */
@@ -186,7 +225,12 @@ static int str_ends_with(const char *s, int slen, const char *sub)
 	return !memcmp(s + slen - sublen, sub, sublen);
 }
 
-static void parse_config_file(const char *p)
+/*
+ * Scan dependency p for CONFIG_ words, map each to an
+ * include/config/ path and append to the dep_info list if not
+ * already included.
+ */
+static void parse_config_file(const char *p, struct dep_info *info)
 {
 	const char *q, *r;
 	const char *start = p;
@@ -205,7 +249,7 @@ static void parse_config_file(const char *p)
 		else
 			r = q;
 		if (r > p)
-			use_config(p, r - p);
+			use_config(p, r - p, info);
 		p = q;
 	}
 }
@@ -253,13 +297,9 @@ static int is_no_parse_file(const char *s, int len)
 	       str_ends_with(s, len, ".so");
 }
 
-/*
- * Important: The below generated source_foo.o and deps_foo.o variable
- * assignments are parsed not only by make, but also by the rather simple
- * parser in scripts/mod/sumversion.c.
- */
-static void parse_dep_file(char *p, const char *target)
+static struct dep_info parse_dep_file(char *p)
 {
+	struct dep_info info = {};
 	bool saw_any_target = false;
 	bool is_target = true;
 	bool is_source = false;
@@ -375,13 +415,12 @@ static void parse_dep_file(char *p, const char *target)
 			 */
 			if (!saw_any_target) {
 				saw_any_target = true;
-				printf("source_%s := %s\n\n", target, p);
-				printf("deps_%s := \\\n", target);
+				info.source = xstrdup(p);
 				need_parse = true;
 			}
 		} else if (!is_ignored_file(p, q - p) &&
 			   !in_hashtable(p, q - p, file_hashtab)) {
-			printf("  %s \\\n", p);
+			dep_info_add(&info, p, q - p);
 			need_parse = true;
 		}
 
@@ -389,7 +428,7 @@ static void parse_dep_file(char *p, const char *target)
 			void *buf;
 
 			buf = read_file(p);
-			parse_config_file(buf);
+			parse_config_file(buf, &info);
 			free(buf);
 		}
 
@@ -403,26 +442,100 @@ static void parse_dep_file(char *p, const char *target)
 		exit(1);
 	}
 
+	return info;
+}
+
+static void free_dep_info(struct dep_info *info)
+{
+	struct dep_item *dep, *next;
+
+	for (dep = info->deps; dep; dep = next) {
+		next = dep->next;
+		free(dep);
+	}
+	free(info->source);
+}
+
+/*
+ * Important: The below generated source_foo.o and deps_foo.o variable
+ * assignments are parsed not only by make, but also by the rather simple
+ * parser in scripts/mod/sumversion.c.
+ */
+static void print_dep_file(const char *target, const struct dep_info *info)
+{
+	static const char config_path[] = "include/config/";
+	const struct dep_item *dep;
+
+	printf("source_%s := %s\n\n", target, info->source);
+	printf("deps_%s := \\\n", target);
+
+	for (dep = info->deps; dep; dep = dep->next) {
+		if (!strncmp(dep->name, config_path, sizeof(config_path) - 1))
+			printf("    $(wildcard %s) \\\n", dep->name);
+		else
+			printf("  %s \\\n", dep->name);
+	}
+
 	printf("\n%s: $(deps_%s)\n\n", target, target);
 	printf("$(deps_%s):\n", target);
 }
 
+static void print_make_prereqs(const char *target, const char *prereqs,
+			       const struct dep_info *info)
+{
+	struct item *seen_prereqs_hashtab[HASHSZ] = {};
+	const struct dep_item *dep;
+	const char *p = prereqs;
+
+	for (dep = info->deps; dep; dep = dep->next)
+		in_hashtable(dep->name, strlen(dep->name),
+			     seen_prereqs_hashtab);
+
+	printf("make_prereqs_%s :=", target);
+
+	while (*p) {
+		const char *start;
+		int len;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+		if (!*p)
+			break;
+
+		start = p;
+		while (*p && *p != ' ' && *p != '\t')
+			p++;
+		len = p - start;
+
+		if (!in_hashtable(start, len, seen_prereqs_hashtab))
+			printf(" %.*s", len, start);
+	}
+
+	printf("\n\n");
+}
+
 int main(int argc, char *argv[])
 {
-	const char *depfile, *target, *cmdline;
+	const char *depfile, *target, *cmdline, *prereqs;
+	struct dep_info info;
 	void *buf;
 
-	if (argc != 4)
+	if (argc != 5)
 		usage();
 
 	depfile = argv[1];
 	target = argv[2];
 	cmdline = argv[3];
+	prereqs = argv[4];
+
+	buf = read_file(depfile);
+	info = parse_dep_file(buf);
 
 	printf("savedcmd_%s := %s\n\n", target, cmdline);
+	print_make_prereqs(target, prereqs, &info);
+	print_dep_file(target, &info);
 
-	buf = read_file(depfile);
-	parse_dep_file(buf, target);
+	free_dep_info(&info);
 	free(buf);
 
 	fflush(stdout);
diff --git a/scripts/make_fit.py b/scripts/make_fit.py
index 15ba26974fd..346e8a7ec12 100755
--- a/scripts/make_fit.py
+++ b/scripts/make_fit.py
@@ -288,7 +288,7 @@ def process_dtb(fname, args):
         path, basename = os.path.split(fname)
         cmd_fname = os.path.join(path, f'.{basename}.cmd')
         with open(cmd_fname, 'r', encoding='ascii') as inf:
-            cmd = inf.read()
+            cmd = inf.readline()
 
         if 'scripts/dtc/fdtoverlay' in cmd:
             # This depends on the structure of the composite DTB command
diff --git a/scripts/sbom/sbom/cmd_graph/cmd_file.py b/scripts/sbom/sbom/cmd_graph/cmd_file.py
index dcd63e284a3..08819b4d117 100644
--- a/scripts/sbom/sbom/cmd_graph/cmd_file.py
+++ b/scripts/sbom/sbom/cmd_graph/cmd_file.py
@@ -50,6 +50,10 @@ class CmdFile:
         with open(cmd_file_path, "rt", encoding="utf-8") as f:
             lines = [line.strip() for line in f.readlines() if line.strip() != "" and not line.startswith("#")]
 
+        # make_prereqs_* is recorded for future use. Ignore it for now to
+        # preserve the existing parser behavior.
+        lines = [line for line in lines if not line.startswith("make_prereqs_")]
+
         # savedcmd
         match = SAVEDCMD_PATTERN.match(lines[0] if lines else "")
         if match is None:
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.