@@ -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,110 @@ 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+ # Fetch firmware only for normal builds, not dtbs_check
767+ if not self ._dtbs_check :
768+ self ._fetch_firmware ()
769+
770+ self .startjob ("build_tuxmake" )
771+ self .addcmd ("cd " + self ._srcdir )
772+
773+ use_kconfig_flag = True
774+
775+ # Handle ChromeOS defconfig
776+ if defconfig .startswith ('cros://' ):
777+ print (f"[_build_with_tuxmake] Handling ChromeOS defconfig: { defconfig } " )
778+ dotconfig = os .path .join (self ._srcdir , ".config" )
779+ content , defconfig_name = self ._getcrosfragment (defconfig )
780+ with open (dotconfig , 'w' ) as f :
781+ f .write (content )
782+ self .addcmd ("make olddefconfig" )
783+ use_kconfig_flag = False
784+
785+ cmd_parts = [
786+ "tuxmake --runtime=null" ,
787+ f"--target-arch={ self ._arch } " ,
788+ f"--toolchain={ self ._compiler } " ,
789+ f"--output-dir={ self ._af_dir } " ,
790+ ]
791+
792+ if use_kconfig_flag :
793+ cmd_parts .append (f"--kconfig={ defconfig } " )
794+
795+ for fragfile in self ._fragment_files :
796+ if os .path .exists (fragfile ):
797+ cmd_parts .append (f"--kconfig-add={ fragfile } " )
798+ print (f"[_build_with_tuxmake] Adding fragment: { os .path .basename (fragfile )} " )
799+ else :
800+ print (f"[_build_with_tuxmake] WARNING: Fragment file not found: { fragfile } " )
801+
802+ # Build targets depend on mode
803+ if self ._dtbs_check :
804+ # dtbs_check mode: run ONLY dtbs_check, like make backend
805+ targets = ["dtbs_check" ]
806+ else :
807+ # Normal build: kernel, modules, plus dtbs if arch supports it
808+ targets = ["kernel" , "modules" ]
809+ if self ._arch not in DTBS_DISABLED :
810+ targets .append ("dtbs" )
811+ if self ._kfselftest :
812+ targets .append ("kselftest" )
813+ cmd_parts .append (" " .join (targets ))
814+ print (f"[_build_with_tuxmake] Building targets: { ' ' .join (targets )} " )
815+
816+ tuxmake_cmd = " " .join (cmd_parts )
817+ print (f"[_build_with_tuxmake] Command: { tuxmake_cmd } " )
818+ print (f"[_build_with_tuxmake] Output directory: { self ._af_dir } " )
819+ self .addcmd (tuxmake_cmd )
820+
821+ self .addcmd ("cd .." )
822+
700823 def _build_kernel (self ):
701824 """ Add kernel build steps """
702825 self .startjob ("build_kernel" )
@@ -859,13 +982,18 @@ def _write_metadata(self):
859982 metadata ['build' ]['fragments' ] = self ._fragments
860983 metadata ['build' ]['srcdir' ] = self ._srcdir
861984 metadata ['build' ]['config_full' ] = self ._config_full
985+ metadata ['build' ]['backend' ] = self ._backend
862986
863987 with open (os .path .join (self ._af_dir , "metadata.json" ), 'w' ) as f :
864988 json .dump (metadata , f , indent = 4 )
865989
866990 def serialize (self , filename ):
867- """ Serialize class to json """
868- # TODO(nuclearcat): Implement to_json method?
991+ """ Serialize class to json
992+
993+ Note: Uses __dict__ to serialize all instance attributes (including
994+ _backend, _arch, etc). The from_json() method strips underscore
995+ prefixes when loading, so _backend becomes 'backend' in jsonobj.
996+ """
869997 data = json .dumps (self , default = lambda o : o .__dict__ ,
870998 sort_keys = True , indent = 4 )
871999 with open (filename , 'w' ) as f :
@@ -939,9 +1067,19 @@ def upload_artifacts(self):
9391067
9401068 # Prepare all artifacts for upload
9411069 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 ))
1070+ if self ._backend == 'tuxmake' :
1071+ # For TuxMake, upload everything in artifacts directory
1072+ print ("[_upload_artifacts] TuxMake backend: discovering files in artifacts dir" )
1073+ for root , dirs , files in os .walk (self ._af_dir ):
1074+ for file in files :
1075+ file_rel = os .path .relpath (os .path .join (root , file ), self ._af_dir )
1076+ artifact_path = os .path .join (self ._af_dir , file_rel )
1077+ upload_tasks .append ((file_rel , artifact_path ))
1078+ else :
1079+ # For make backend, upload only listed artifacts
1080+ for artifact in self ._artifacts :
1081+ artifact_path = os .path .join (self ._af_dir , artifact )
1082+ upload_tasks .append ((artifact , artifact_path ))
9451083
9461084 # Function to handle a single artifact upload
9471085 # args: (artifact, artifact_path)
0 commit comments