@@ -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" )
@@ -864,8 +990,12 @@ def _write_metadata(self):
864990 json .dump (metadata , f , indent = 4 )
865991
866992 def serialize (self , filename ):
867- """ Serialize class to json """
868- # TODO(nuclearcat): Implement to_json method?
993+ """ Serialize class to json
994+
995+ Note: Uses __dict__ to serialize all instance attributes (including
996+ _backend, _arch, etc). The from_json() method strips underscore
997+ prefixes when loading, so _backend becomes 'backend' in jsonobj.
998+ """
869999 data = json .dumps (self , default = lambda o : o .__dict__ ,
8701000 sort_keys = True , indent = 4 )
8711001 with open (filename , 'w' ) as f :
@@ -939,9 +1069,19 @@ def upload_artifacts(self):
9391069
9401070 # Prepare all artifacts for upload
9411071 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 ))
1072+ if self ._backend == 'tuxmake' :
1073+ # For TuxMake, upload everything in artifacts directory
1074+ print ("[_upload_artifacts] TuxMake backend: discovering files in artifacts dir" )
1075+ for root , dirs , files in os .walk (self ._af_dir ):
1076+ for file in files :
1077+ file_rel = os .path .relpath (os .path .join (root , file ), self ._af_dir )
1078+ artifact_path = os .path .join (self ._af_dir , file_rel )
1079+ upload_tasks .append ((file_rel , artifact_path ))
1080+ else :
1081+ # For make backend, upload only listed artifacts
1082+ for artifact in self ._artifacts :
1083+ artifact_path = os .path .join (self ._af_dir , artifact )
1084+ upload_tasks .append ((artifact , artifact_path ))
9451085
9461086 # Function to handle a single artifact upload
9471087 # args: (artifact, artifact_path)
0 commit comments