@@ -28,6 +28,7 @@ class Kind(StrEnum):
2828 TARGET = "target"
2929 LIB = "lib"
3030 EXE = "exe"
31+ PORT = "port"
3132
3233
3334# MARK: Manifest ---------------------------------------------------------------
@@ -586,34 +587,6 @@ def use() -> "Project":
586587 return _project
587588
588589
589- @cli .command ("model" , "Manage the model" )
590- def _ ():
591- """
592- Manage the CuteKit model.
593- """
594- pass
595-
596-
597- class InstallArgs :
598- """
599- Arguments for the install command.
600- """
601-
602- update : bool = cli .arg (
603- None , "update" , "Pull latest versions of externs and refresh the lockfile"
604- )
605-
606-
607- @cli .command ("install" , "Install required external packages" )
608- def _ (args : InstallArgs ):
609- """
610- Install required external packages for the project.
611- """
612- project = Project .use ()
613- assert project .lockfile is not None
614- project .fetchExterns (project .lockfile , args .update )
615- project .lockfile .save ()
616-
617590
618591# MARK: Target -----------------------------------------------------------------
619592
@@ -644,6 +617,7 @@ class Tool(DataClassJsonMixin):
644617 "cxx-collect" : Tool ("jq" ),
645618 "cxx-modmap" : Tool ("ck --safemode tools cxx-modmap" ),
646619 "cxx-dyndep" : Tool ("ck --safemode tools cxx-dyndep" ),
620+ "ck-port" : Tool ("ck --safemode tools port" ),
647621}
648622"""Default tools available in all projects."""
649623
@@ -835,11 +809,144 @@ def isEnabled(self, target: Target) -> tuple[bool, str]:
835809 return True , ""
836810
837811
812+ # MARK: Port ------------------------------------------------------------------
813+
814+ class PortArgs (TargetArgs ):
815+ component : str = cli .arg (None , "component" , "Name of the component to port" )
816+ out : str = cli .arg (None , "out" , "Output path" )
817+
818+
819+ @dt .dataclass
820+ class Port (Component ):
821+ _subtype : Kind = dt .field (default = Kind .UNKNOWN )
822+ ctx : Optional [dict [str , Any ]] = dt .field (default = None )
823+
824+ @property
825+ def subtype (self ) -> Kind :
826+ if self ._subtype == Kind .UNKNOWN :
827+ self .parseBuild ()
828+ return self ._subtype
829+
830+ def parseBuild (self ):
831+ with self .subpath ("build.py" ).open ("r" ) as f :
832+ globals : dict [str , Any ] = {}
833+ code = compile (f .read (), str (f"<PORT { self .id } " ), "exec" )
834+
835+ exec (code , globals )
836+ del globals ['__builtins__' ]
837+
838+ self .ctx = globals
839+
840+ assert 'kind' in self .ctx , "Port context must have a 'kind' field"
841+ self ._subtype = Kind (self .ctx ['kind' ])
842+
843+ def fetch (self ):
844+ if self .ctx is None :
845+ self .parseBuild ()
846+
847+ assert self .ctx is not None
848+
849+ srcDir = Path (Project .use ().dirname ()) / const .EXTERN_DIR / self .id
850+ if srcDir .exists ():
851+ _logger .debug (f"Port { self .id } already fetched at { srcDir } " )
852+ return
853+
854+ if self .ctx .get ('git_url' ) is not None :
855+ print (f"Installing { self .id } from { self .ctx ['git_url' ]} ..." )
856+ _logger .info (f"Cloning { self .ctx ['git_url' ]} ..." )
857+ cmd = [
858+ "git" ,
859+ "clone" ,
860+ "--quiet" ,
861+ "--depth=1" ,
862+ self .ctx ['git_url' ],
863+ str (srcDir ),
864+ ]
865+
866+ if self .ctx .get ('commit' ) is not None :
867+ cmd .append (f"--revision={ self .ctx .get ('commit' )} " )
868+
869+ if self .ctx .get ('branch' ) is not None :
870+ cmd .append (f"--branch={ self .ctx .get ('branch' )} " )
871+
872+ shell .exec (* cmd , quiet = True )
873+
874+ def prepare (self , port : "PortScope" ):
875+ if self .ctx is None :
876+ self .parseBuild ()
877+
878+ assert self .ctx is not None
879+
880+ if 'patches' in self .ctx :
881+ if not (port .srcDir / ".git" ).exists ():
882+ raise RuntimeError (f"Port { self .ctx ['name' ]} is not a git repository, cannot apply patches." )
883+
884+ for patch in self .ctx ['patches' ]:
885+ try :
886+ shell .exec (
887+ * ["git" , "-C" , str (port .srcDir ), "apply" , "--check" , str (port .cwd / patch )],
888+ quiet = True
889+ )
890+ shell .exec (* ["git" , "-C" , str (port .srcDir ), "apply" , str (port .cwd / patch )])
891+ except Exception as e :
892+ _logger .warning (f"Could not apply patch { patch } to port { self .ctx ['name' ]} : { e } " )
893+
894+ if 'prepare' in self .ctx :
895+ self .ctx ['prepare' ](port )
896+
897+ def build (self , port : "PortScope" ):
898+ assert self .ctx is not None
899+
900+ if 'build' in self .ctx :
901+ self .ctx ['build' ](port )
902+
903+ def package (self , port : "PortScope" ):
904+ assert self .ctx is not None
905+
906+ if 'package' in self .ctx :
907+ self .ctx ['package' ](port )
908+
909+ if not port .destFile .exists ():
910+ with open (port .destFile , "w" ) as f :
911+ f .write ("" )
912+
913+
914+ @dt .dataclass
915+ class PortScope :
916+ srcDir : Path
917+ destDir : Path
918+ destFile : Path
919+ cwd : Path
920+ target : Target
921+ component : Port
922+ includeDir : Path
923+
924+ @staticmethod
925+ def use (args : PortArgs ) -> "PortScope" :
926+ registry = Registry .use (args )
927+ component = registry .ensure (args .component , Port )
928+
929+ assert isinstance (component , Port ), "Component is not a Port"
930+
931+ return PortScope (
932+ srcDir = Path (const .EXTERN_DIR ) / args .component ,
933+ destDir = Path (args .out ).parent ,
934+ destFile = Path (args .out ),
935+ cwd = Path (component .dirname ()),
936+ target = Target .use (args ),
937+ component = component ,
938+
939+ #FIXME: this is a temporary hack for includes
940+ includeDir = Path (const .GENERATED_DIR ) / args .component
941+ )
942+
943+
838944KINDS : dict [Kind , Type [Manifest ]] = {
839945 Kind .PROJECT : Project ,
840946 Kind .TARGET : Target ,
841947 Kind .LIB : Component ,
842948 Kind .EXE : Component ,
949+ Kind .PORT : Port ,
843950}
844951"""Mapping of manifest kinds to their corresponding classes."""
845952
@@ -1298,6 +1405,35 @@ def load(project: Project, mixins: list[str], props: Props) -> "Registry":
12981405 return r
12991406
13001407
1408+ @cli .command ("model" , "Manage the model" )
1409+ def _ ():
1410+ """
1411+ Manage the CuteKit model.
1412+ """
1413+ pass
1414+
1415+
1416+ class InstallArgs (RegistryArgs ):
1417+ """
1418+ Arguments for the install command.
1419+ """
1420+
1421+ update : bool = cli .arg (
1422+ None , "update" , "Pull latest versions of externs and refresh the lockfile"
1423+ )
1424+
1425+
1426+ @cli .command ("install" , "Install required external packages" )
1427+ def _ (args : InstallArgs ):
1428+ """
1429+ Install required external packages for the project.
1430+ """
1431+ project = Project .use ()
1432+ assert project .lockfile is not None
1433+ project .fetchExterns (project .lockfile , args .update )
1434+ project .lockfile .save ()
1435+
1436+
13011437@cli .command ("model/list" , "List all components and targets" )
13021438def _ (args : TargetArgs ):
13031439 """
0 commit comments