Skip to content

Commit 5ac51e4

Browse files
author
Staging script
committed
Staging PR 3037
1 parent 37da279 commit 5ac51e4

File tree

2 files changed

+189
-45
lines changed

2 files changed

+189
-45
lines changed

config/docker/fragment/kernelci.jinja2

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ RUN cp -R config /etc/kernelci/
1919
WORKDIR /root
2020
RUN rm -rf /tmp/kernelci-core
2121

22+
# Install tuxmake kernel build tool
23+
RUN pip3 install --break-system-packages --no-cache-dir tuxmake==1.34.0
24+
2225
# Set up kernelci user
2326
RUN useradd kernelci -u 1000 -d /home/kernelci -s /bin/bash
2427
RUN mkdir -p /home/kernelci

kernelci/kbuild.py

Lines changed: 186 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ def __init__(self, node=None, jobname=None, params=None, jsonobj=None, apiconfig
165165
# if defconfig contains '+', it means it is a list
166166
if isinstance(self._defconfig, str) and '+' in self._defconfig:
167167
self._defconfig = self._defconfig.split('+')
168+
self._backend = params.get('backend', 'make')
169+
# Support USE_TUXMAKE environment variable for backward compatibility
170+
if os.environ.get('USE_TUXMAKE') == '1':
171+
self._backend = 'tuxmake'
168172
self._fragments = params['fragments']
169173
self._fragment_configs = fragment_configs or {}
170174
if 'coverage' in self._fragments:
@@ -220,6 +224,7 @@ def __init__(self, node=None, jobname=None, params=None, jsonobj=None, apiconfig
220224
self._defconfig = jsonobj['defconfig']
221225
self._fragments = jsonobj['fragments']
222226
self._fragment_configs = jsonobj.get('fragment_configs', {})
227+
self._backend = jsonobj.get('backend', 'make')
223228
self._cross_compile = jsonobj['cross_compile']
224229
self._cross_compile_compat = jsonobj['cross_compile_compat']
225230
self._steps = jsonobj['steps']
@@ -565,12 +570,19 @@ def add_fragment(self, fragname):
565570
return self.extract_config(frag)
566571

567572
def _parse_fragments(self, firmware=False):
568-
""" Parse fragments kbuild config and create config fragments """
569-
num = 0
570-
for fragment in self._fragments:
573+
""" Parse fragments kbuild config and create config fragments
574+
575+
Returns:
576+
list: List of fragment file paths
577+
"""
578+
fragment_files = []
579+
580+
for idx, fragment in enumerate(self._fragments):
571581
content = ''
582+
fragment_name = fragment
583+
572584
if fragment.startswith("cros://"):
573-
(content, fragment) = self._getcrosfragment(fragment)
585+
(content, fragment_name) = self._getcrosfragment(fragment)
574586
elif fragment.startswith("cip://"):
575587
content = self._getcipfragment(fragment)
576588
elif fragment.startswith("CONFIG_"):
@@ -579,27 +591,50 @@ def _parse_fragments(self, firmware=False):
579591
# Use fragment configs passed from scheduler
580592
content = self.add_fragment(fragment)
581593

582-
fragfile = os.path.join(self._fragments_dir, f"{num}.config")
594+
if not content:
595+
print(f"[_parse_fragments] WARNING: Fragment {fragment} has no content")
596+
continue
597+
598+
fragfile = os.path.join(self._fragments_dir, f"{idx}.config")
583599
with open(fragfile, 'w') as f:
584600
f.write(content)
601+
602+
if not os.path.exists(fragfile):
603+
print(f"[_parse_fragments] ERROR: Failed to create fragment file: {fragfile}")
604+
continue
605+
606+
config_count = len([line for line in content.split('\n') if line.strip()])
607+
print(f"[_parse_fragments] Created {fragfile} ({config_count} configs)")
608+
609+
fragment_files.append(fragfile)
610+
585611
# add fragment to artifacts but relative to artifacts dir
586612
frag_rel = os.path.relpath(fragfile, self._af_dir)
587-
self._config_full += '+' + fragment
613+
self._config_full += '+' + fragment_name
588614
self._artifacts.append(frag_rel)
589-
num += 1
615+
590616
if firmware:
591617
content = 'CONFIG_EXTRA_FIRMWARE_DIR="'+self._firmware_dir+'"\n'
592-
fragfile = os.path.join(self._fragments_dir, f"{num}.config")
618+
fragfile = os.path.join(self._fragments_dir, f"{len(self._fragments)}.config")
593619
with open(fragfile, 'w') as f:
594620
f.write(content)
595-
# add fragment to artifacts but relative to artifacts dir
596-
frag_rel = os.path.relpath(fragfile, self._af_dir)
597-
self._artifacts.append(frag_rel)
598-
num += 1
599-
return num
600621

601-
def _merge_frags(self, fragnum):
602-
""" Merge config fragments to .config """
622+
if os.path.exists(fragfile):
623+
fragment_files.append(fragfile)
624+
frag_rel = os.path.relpath(fragfile, self._af_dir)
625+
self._artifacts.append(frag_rel)
626+
else:
627+
print("[_parse_fragments] ERROR: Failed to create firmware fragment")
628+
629+
print(f"[_parse_fragments] Created {len(fragment_files)} fragment files")
630+
return fragment_files
631+
632+
def _merge_frags(self, fragment_files):
633+
""" Merge config fragments to .config
634+
635+
Args:
636+
fragment_files: List of fragment file paths to merge
637+
"""
603638
self.startjob("config_defconfig")
604639
self.addcmd("cd " + self._srcdir)
605640
if isinstance(self._defconfig, str) and self._defconfig.startswith('cros://'):
@@ -623,9 +658,8 @@ def _merge_frags(self, fragnum):
623658
self._config_full = defconfigs + self._config_full
624659
# fragments
625660
self.startjob("config_fragments")
626-
for i in range(0, fragnum):
627-
self.addcmd("./scripts/kconfig/merge_config.sh" +
628-
f" -m .config {self._fragments_dir}/{i}.config")
661+
for fragfile in fragment_files:
662+
self.addcmd(f"./scripts/kconfig/merge_config.sh -m .config {fragfile}")
629663
# TODO: olddefconfig should be optional/configurable
630664
# TODO: log all warnings/errors of olddefconfig to separate file
631665
self.addcmd("make olddefconfig")
@@ -635,30 +669,15 @@ def _merge_frags(self, fragnum):
635669

636670
def _generate_script(self):
637671
""" Generate shell script for complete build """
638-
# TODO(nuclearcat): Fetch firmware only if needed
639672
print("Generating shell script")
640-
fragnum = self._parse_fragments(firmware=True)
641-
self._merge_frags(fragnum)
642-
if not self._dtbs_check:
643-
# TODO: verify if CONFIG_EXTRA_FIRMWARE have any files
644-
# We can check that if fragments have CONFIG_EXTRA_FIRMWARE
645-
self._fetch_firmware()
646-
self._build_kernel()
647-
self._build_modules()
648-
if self._kfselftest:
649-
self._build_kselftest()
650-
if self._arch not in DTBS_DISABLED:
651-
self._build_dtbs()
652-
self._package_kimage()
653-
self._package_modules()
654-
if self._coverage:
655-
self._package_coverage()
656-
if self._kfselftest:
657-
self._package_kselftest()
658-
if self._arch not in DTBS_DISABLED:
659-
self._package_dtbs()
673+
self._fragment_files = self._parse_fragments(firmware=True)
674+
675+
if self._backend == 'tuxmake':
676+
self._build_with_tuxmake()
660677
else:
661-
self._build_dtbs_check()
678+
self._merge_frags(self._fragment_files)
679+
self._build_with_make()
680+
662681
self._write_metadata()
663682
# terminate all active jobs
664683
self.startjob(None)
@@ -697,6 +716,113 @@ def write_script(self, filename):
697716
# copy to artifacts dir
698717
os.system(f"cp {filename} {self._af_dir}/build.sh")
699718

719+
def _build_with_make(self):
720+
""" Build kernel using make """
721+
if not self._dtbs_check:
722+
self._fetch_firmware()
723+
self._build_kernel()
724+
self._build_modules()
725+
if self._kfselftest:
726+
self._build_kselftest()
727+
if self._arch not in DTBS_DISABLED:
728+
self._build_dtbs()
729+
self._package_kimage()
730+
self._package_modules()
731+
if self._coverage:
732+
self._package_coverage()
733+
if self._kfselftest:
734+
self._package_kselftest()
735+
if self._arch not in DTBS_DISABLED:
736+
self._package_dtbs()
737+
else:
738+
self._build_dtbs_check()
739+
740+
def _build_with_tuxmake(self):
741+
""" Build kernel using tuxmake with native fragment support """
742+
print("[_build_with_tuxmake] Starting tuxmake build")
743+
744+
if not hasattr(self, '_fragment_files'):
745+
print("[_build_with_tuxmake] ERROR: No fragment files available")
746+
self._fragment_files = []
747+
748+
print(f"[_build_with_tuxmake] Using {len(self._fragment_files)} fragment files")
749+
750+
# Handle multiple defconfigs - tuxmake only supports one
751+
if isinstance(self._defconfig, list):
752+
if len(self._defconfig) > 1:
753+
raise ValueError(
754+
f"TuxMake backend does not support multiple defconfigs: "
755+
f"{self._defconfig}. Use backend=make or specify a single defconfig."
756+
)
757+
defconfig = self._defconfig[0]
758+
self._config_full = self._defconfig[0] + self._config_full
759+
elif isinstance(self._defconfig, str):
760+
defconfig = self._defconfig
761+
self._config_full = self._defconfig + self._config_full
762+
else:
763+
defconfig = 'defconfig'
764+
print("[_build_with_tuxmake] WARNING: No defconfig specified, using 'defconfig'")
765+
766+
# Check for unsupported kselftest
767+
if self._kfselftest:
768+
print("[_build_with_tuxmake] WARNING: kselftest is not supported with "
769+
"tuxmake backend, skipping. Use backend=make for kselftest builds.")
770+
771+
# Fetch firmware only for normal builds, not dtbs_check
772+
if not self._dtbs_check:
773+
self._fetch_firmware()
774+
775+
self.startjob("build_tuxmake")
776+
self.addcmd("cd " + self._srcdir)
777+
778+
use_kconfig_flag = True
779+
780+
# Handle ChromeOS defconfig
781+
if defconfig.startswith('cros://'):
782+
print(f"[_build_with_tuxmake] Handling ChromeOS defconfig: {defconfig}")
783+
dotconfig = os.path.join(self._srcdir, ".config")
784+
content, defconfig_name = self._getcrosfragment(defconfig)
785+
with open(dotconfig, 'w') as f:
786+
f.write(content)
787+
self.addcmd("make olddefconfig")
788+
use_kconfig_flag = False
789+
790+
cmd_parts = [
791+
"tuxmake --runtime=null",
792+
f"--target-arch={self._arch}",
793+
f"--toolchain={self._compiler}",
794+
f"--output-dir={self._af_dir}",
795+
]
796+
797+
if use_kconfig_flag:
798+
cmd_parts.append(f"--kconfig={defconfig}")
799+
800+
for fragfile in self._fragment_files:
801+
if os.path.exists(fragfile):
802+
cmd_parts.append(f"--kconfig-add={fragfile}")
803+
print(f"[_build_with_tuxmake] Adding fragment: {os.path.basename(fragfile)}")
804+
else:
805+
print(f"[_build_with_tuxmake] WARNING: Fragment file not found: {fragfile}")
806+
807+
# Build targets depend on mode
808+
if self._dtbs_check:
809+
# dtbs_check mode: run ONLY dtbs_check, like make backend
810+
targets = ["dtbs_check"]
811+
else:
812+
# Normal build: kernel, modules, plus dtbs if arch supports it
813+
targets = ["kernel", "modules"]
814+
if self._arch not in DTBS_DISABLED:
815+
targets.append("dtbs")
816+
cmd_parts.append(" ".join(targets))
817+
print(f"[_build_with_tuxmake] Building targets: {' '.join(targets)}")
818+
819+
tuxmake_cmd = " ".join(cmd_parts)
820+
print(f"[_build_with_tuxmake] Command: {tuxmake_cmd}")
821+
print(f"[_build_with_tuxmake] Output directory: {self._af_dir}")
822+
self.addcmd(tuxmake_cmd)
823+
824+
self.addcmd("cd ..")
825+
700826
def _build_kernel(self):
701827
""" Add kernel build steps """
702828
self.startjob("build_kernel")
@@ -859,13 +985,18 @@ def _write_metadata(self):
859985
metadata['build']['fragments'] = self._fragments
860986
metadata['build']['srcdir'] = self._srcdir
861987
metadata['build']['config_full'] = self._config_full
988+
metadata['build']['backend'] = self._backend
862989

863990
with open(os.path.join(self._af_dir, "metadata.json"), 'w') as f:
864991
json.dump(metadata, f, indent=4)
865992

866993
def serialize(self, filename):
867-
""" Serialize class to json """
868-
# TODO(nuclearcat): Implement to_json method?
994+
""" Serialize class to json
995+
996+
Note: Uses __dict__ to serialize all instance attributes (including
997+
_backend, _arch, etc). The from_json() method strips underscore
998+
prefixes when loading, so _backend becomes 'backend' in jsonobj.
999+
"""
8691000
data = json.dumps(self, default=lambda o: o.__dict__,
8701001
sort_keys=True, indent=4)
8711002
with open(filename, 'w') as f:
@@ -939,9 +1070,19 @@ def upload_artifacts(self):
9391070

9401071
# Prepare all artifacts for upload
9411072
upload_tasks = []
942-
for artifact in self._artifacts:
943-
artifact_path = os.path.join(self._af_dir, artifact)
944-
upload_tasks.append((artifact, artifact_path))
1073+
if self._backend == 'tuxmake':
1074+
# For TuxMake, upload everything in artifacts directory
1075+
print("[_upload_artifacts] TuxMake backend: discovering files in artifacts dir")
1076+
for root, dirs, files in os.walk(self._af_dir):
1077+
for file in files:
1078+
file_rel = os.path.relpath(os.path.join(root, file), self._af_dir)
1079+
artifact_path = os.path.join(self._af_dir, file_rel)
1080+
upload_tasks.append((file_rel, artifact_path))
1081+
else:
1082+
# For make backend, upload only listed artifacts
1083+
for artifact in self._artifacts:
1084+
artifact_path = os.path.join(self._af_dir, artifact)
1085+
upload_tasks.append((artifact, artifact_path))
9451086

9461087
# Function to handle a single artifact upload
9471088
# args: (artifact, artifact_path)

0 commit comments

Comments
 (0)