Skip to content

Commit 7b44c1d

Browse files
author
Staging script
committed
Staging PR 3205
1 parent 1e822b4 commit 7b44c1d

2 files changed

Lines changed: 164 additions & 37 deletions

File tree

‎kernelci/kbuild.py‎

Lines changed: 90 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
LATEST_LTS_MAJOR = 6
5555
LATEST_LTS_MINOR = 12
5656

57+
# Prefix marking a fragment entry as a kernel make target generating
58+
# config (e.g. 'make:kselftest-merge') rather than a config symbol.
59+
MAKE_FRAGMENT_PREFIX = "make:"
60+
5761
DTBS_DISABLED = {
5862
"i386": True,
5963
"x86_64": True,
@@ -618,13 +622,44 @@ def add_fragment(self, fragname):
618622
print(f"Using fragment {fragname} from inline configs")
619623
return self.extract_config(frag)
620624

625+
@staticmethod
626+
def _split_fragment(content):
627+
"""Split fragment content into make targets and config symbols
628+
629+
A fragment entry prefixed with 'make:' names a kernel make target
630+
generating config, such as 'make:kselftest-merge', rather than a
631+
config symbol. Those entries must be kept out of the fragment file:
632+
kconfig does not understand them and merges them as "unexpected
633+
data", silently dropping the config the fragment is meant to add.
634+
635+
Returns:
636+
tuple: (list of make targets, config symbol text)
637+
"""
638+
make_targets = []
639+
config_lines = []
640+
641+
for line in content.splitlines():
642+
entry = line.strip()
643+
if entry.startswith(MAKE_FRAGMENT_PREFIX):
644+
target = entry[len(MAKE_FRAGMENT_PREFIX) :]
645+
if target:
646+
make_targets.append(target)
647+
else:
648+
config_lines.append(line)
649+
650+
config = "\n".join(config_lines).strip()
651+
if config:
652+
config += "\n"
653+
return make_targets, config
654+
621655
def _parse_fragments(self, firmware=False):
622656
"""Parse fragments kbuild config and create config fragments
623657
624658
Returns:
625-
list: List of fragment file paths
659+
list: List of kconfig additions, each either a fragment file
660+
path or a 'make:<target>' directive, in merge order
626661
"""
627-
fragment_files = []
662+
kconfig_adds = []
628663

629664
for idx, fragment in enumerate(self._fragments):
630665
content = ""
@@ -646,23 +681,34 @@ def _parse_fragments(self, firmware=False):
646681
)
647682
continue
648683

649-
fragfile = os.path.join(self._fragments_dir, f"{idx}.config")
650-
with open(fragfile, "w") as f:
651-
f.write(content)
684+
make_targets, config = self._split_fragment(content)
652685

653-
config_count = len(
654-
[line for line in content.split("\n") if line.strip()]
655-
)
656-
print(
657-
f"[_parse_fragments] Created {fragfile} ({config_count} configs)"
658-
)
686+
if config:
687+
fragfile = os.path.join(self._fragments_dir, f"{idx}.config")
688+
with open(fragfile, "w") as f:
689+
f.write(config)
659690

660-
fragment_files.append(fragfile)
691+
config_count = len(
692+
[line for line in config.split("\n") if line.strip()]
693+
)
694+
print(
695+
f"[_parse_fragments] Created {fragfile} ({config_count} configs)"
696+
)
697+
698+
kconfig_adds.append(fragfile)
699+
700+
# add fragment to artifacts but relative to artifacts dir
701+
frag_rel = os.path.relpath(fragfile, self._af_dir)
702+
self._artifacts.append(frag_rel)
703+
704+
for target in make_targets:
705+
print(
706+
f"[_parse_fragments] Fragment {fragment_name} runs "
707+
f"make target {target}"
708+
)
709+
kconfig_adds.append(MAKE_FRAGMENT_PREFIX + target)
661710

662-
# add fragment to artifacts but relative to artifacts dir
663-
frag_rel = os.path.relpath(fragfile, self._af_dir)
664711
self._config_full += "+" + fragment_name
665-
self._artifacts.append(frag_rel)
666712

667713
if firmware:
668714
content = 'CONFIG_EXTRA_FIRMWARE_DIR="' + self._firmware_dir + '"\n'
@@ -672,22 +718,23 @@ def _parse_fragments(self, firmware=False):
672718
with open(fragfile, "w") as f:
673719
f.write(content)
674720

675-
fragment_files.append(fragfile)
721+
kconfig_adds.append(fragfile)
676722

677723
# add fragment to artifacts but relative to artifacts dir
678724
frag_rel = os.path.relpath(fragfile, self._af_dir)
679725
self._artifacts.append(frag_rel)
680726

681727
print(
682-
f"[_parse_fragments] Created {len(fragment_files)} fragment files"
728+
f"[_parse_fragments] Created {len(kconfig_adds)} kconfig additions"
683729
)
684-
return fragment_files
730+
return kconfig_adds
685731

686-
def _merge_frags(self, fragment_files):
732+
def _merge_frags(self, kconfig_adds):
687733
"""Merge config fragments to .config
688734
689735
Args:
690-
fragment_files: List of fragment file paths to merge
736+
kconfig_adds: List of kconfig additions, as returned by
737+
_parse_fragments()
691738
"""
692739
self.startjob("config_defconfig")
693740
self.addcmd("cd " + self._srcdir)
@@ -715,10 +762,14 @@ def _merge_frags(self, fragment_files):
715762
self._config_full = defconfigs + self._config_full
716763
# fragments
717764
self.startjob("config_fragments")
718-
for fragfile in fragment_files:
719-
self.addcmd(
720-
f"./scripts/kconfig/merge_config.sh -m .config {fragfile}"
721-
)
765+
for entry in kconfig_adds:
766+
if entry.startswith(MAKE_FRAGMENT_PREFIX):
767+
# the target merges its own config into the .config built
768+
# so far, so run it in place of a merge_config.sh call
769+
target = entry[len(MAKE_FRAGMENT_PREFIX) :]
770+
self.addcmd(f"make {target}")
771+
continue
772+
self.addcmd(f"./scripts/kconfig/merge_config.sh -m .config {entry}")
722773
# TODO: olddefconfig should be optional/configurable
723774
# TODO: log all warnings/errors of olddefconfig to separate file
724775
self.addcmd("make olddefconfig")
@@ -729,12 +780,12 @@ def _merge_frags(self, fragment_files):
729780
def _generate_script(self):
730781
"""Generate shell script for complete build"""
731782
print("Generating shell script")
732-
self._fragment_files = self._parse_fragments(firmware=True)
783+
self._kconfig_adds = self._parse_fragments(firmware=True)
733784

734785
if self._backend == "tuxmake":
735786
self._build_with_tuxmake()
736787
else:
737-
self._merge_frags(self._fragment_files)
788+
self._merge_frags(self._kconfig_adds)
738789
self._build_with_make()
739790

740791
self._write_metadata()
@@ -800,12 +851,12 @@ def _build_with_tuxmake(self):
800851
"""Build kernel using tuxmake with native fragment support"""
801852
print("[_build_with_tuxmake] Starting tuxmake build")
802853

803-
if not hasattr(self, "_fragment_files"):
804-
print("[_build_with_tuxmake] ERROR: No fragment files available")
805-
self._fragment_files = []
854+
if not hasattr(self, "_kconfig_adds"):
855+
print("[_build_with_tuxmake] ERROR: No kconfig additions available")
856+
self._kconfig_adds = []
806857

807858
print(
808-
f"[_build_with_tuxmake] Using {len(self._fragment_files)} fragment files"
859+
f"[_build_with_tuxmake] Using {len(self._kconfig_adds)} kconfig additions"
809860
)
810861

811862
# Handle defconfigs - first goes to --kconfig, rest to --kconfig-add
@@ -931,17 +982,20 @@ def _tuxmake_base(self, output_dir, defconfig, extra_defconfigs):
931982
for extra in extra_defconfigs:
932983
parts.append(f"--kconfig-add={extra}")
933984
print(f"[_tuxmake_base] Adding extra defconfig: {extra}")
934-
for fragfile in self._fragment_files:
935-
if os.path.exists(fragfile):
936-
parts.append(f"--kconfig-add={fragfile}")
985+
for entry in self._kconfig_adds:
986+
if entry.startswith(MAKE_FRAGMENT_PREFIX):
987+
# tuxmake runs the make target during config preparation
988+
parts.append(f"--kconfig-add={entry}")
989+
print(f"[_tuxmake_base] Adding make target: {entry}")
990+
elif os.path.exists(entry):
991+
parts.append(f"--kconfig-add={entry}")
937992
print(
938993
"[_tuxmake_base] Adding fragment: "
939-
f"{os.path.basename(fragfile)}"
994+
f"{os.path.basename(entry)}"
940995
)
941996
else:
942997
print(
943-
"[_tuxmake_base] WARNING: Fragment file not found: "
944-
f"{fragfile}"
998+
f"[_tuxmake_base] WARNING: Fragment file not found: {entry}"
945999
)
9461000
return parts
9471001

‎tests/test_kbuild.py‎

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def _kbuild(tmp_path, compiler="clang-21", arch="x86_64"):
1818
kbuild._compiler = compiler
1919
kbuild._defconfig = "defconfig"
2020
kbuild._fragments = []
21-
kbuild._fragment_files = []
21+
kbuild._kconfig_adds = []
2222
kbuild._config_full = ""
2323
kbuild._backend = "tuxmake"
2424
kbuild._dtbs_check = True
@@ -83,6 +83,79 @@ def test_no_probe_for_gcc_without_tuxmake(self, tmp_path, monkeypatch):
8383
assert not any("--version" in s for s in kbuild._steps)
8484

8585

86+
class TestFragments:
87+
@staticmethod
88+
def _fragments(tmp_path, fragments, fragment_configs):
89+
kbuild = _kbuild(tmp_path)
90+
kbuild._fragments = fragments
91+
kbuild._fragment_configs = fragment_configs
92+
kbuild._fragments_dir = os.path.join(kbuild._af_dir, "fragments")
93+
os.makedirs(kbuild._fragments_dir)
94+
return kbuild
95+
96+
def test_make_target_is_not_written_to_a_fragment_file(self, tmp_path):
97+
kbuild = self._fragments(
98+
tmp_path,
99+
["kselftest"],
100+
{"kselftest": {"configs": ["make:kselftest-merge"]}},
101+
)
102+
103+
kconfig_adds = kbuild._parse_fragments()
104+
105+
# kconfig would merge the directive as "unexpected data"
106+
assert kconfig_adds == ["make:kselftest-merge"]
107+
assert os.listdir(kbuild._fragments_dir) == []
108+
assert kbuild._artifacts == []
109+
assert kbuild._config_full == "+kselftest"
110+
111+
def test_make_targets_are_split_from_config_symbols(self, tmp_path):
112+
kbuild = self._fragments(
113+
tmp_path,
114+
["kselftest"],
115+
{
116+
"kselftest": {
117+
"configs": [
118+
"make:kselftest-merge",
119+
"CONFIG_KUNIT=y",
120+
]
121+
}
122+
},
123+
)
124+
125+
kconfig_adds = kbuild._parse_fragments()
126+
127+
fragfile = os.path.join(kbuild._fragments_dir, "0.config")
128+
assert kconfig_adds == [fragfile, "make:kselftest-merge"]
129+
with open(fragfile) as f:
130+
assert f.read() == "CONFIG_KUNIT=y\n"
131+
132+
def test_make_target_is_passed_to_tuxmake(self, tmp_path):
133+
kbuild = _kbuild(tmp_path)
134+
kbuild._kconfig_adds = ["make:kselftest-merge"]
135+
136+
parts = kbuild._tuxmake_base(kbuild._af_dir, "defconfig", [])
137+
138+
assert "--kconfig-add=make:kselftest-merge" in parts
139+
140+
def test_make_target_is_run_by_the_make_backend(self, tmp_path):
141+
kbuild = _kbuild(tmp_path)
142+
kbuild._backend = "make"
143+
fragfile = os.path.join(kbuild._af_dir, "0.config")
144+
145+
kbuild._merge_frags(["make:kselftest-merge", fragfile])
146+
147+
steps = kbuild._steps
148+
merge = steps.index("make kselftest-merge")
149+
assert (
150+
steps.index(
151+
f"./scripts/kconfig/merge_config.sh -m .config {fragfile}"
152+
)
153+
> merge
154+
)
155+
# kselftest-merge needs a .config to merge into
156+
assert steps.index("make defconfig") < merge
157+
158+
86159
class TestKselftestSuiteResults:
87160
def test_names_identify_build_results(self, tmp_path):
88161
kbuild = _kbuild(tmp_path)

0 commit comments

Comments
 (0)