11from  __future__ import  annotations 
22
33import  contextlib 
4+ import  os .path 
45import  re 
56from  typing  import  TYPE_CHECKING 
67
8+ from  .._logging  import  logger 
9+ 
710if  TYPE_CHECKING :
811    from  pathlib  import  Path 
912
10- __all__  =  ["process_script_dir" ]
13+     from  .._vendor .pyproject_metadata  import  StandardMetadata 
14+     from  ..builder .builder  import  Builder 
15+     from  ..settings .skbuild_model  import  ScikitBuildSettings 
16+ 
17+ __all__  =  ["add_dynamic_scripts" , "process_script_dir" ]
1118
1219
1320def  __dir__ () ->  list [str ]:
1421    return  __all__ 
1522
1623
1724SHEBANG_PATTERN  =  re .compile (r"^#!.*(?:python|pythonw|pypy)[0-9.]*([ \t].*)?$" )
25+ SCRIPT_PATTERN  =  re .compile (r"^(?P<module>[\w\\.]+)(?::(?P<function>\w+))?$" )
1826
1927
2028def  process_script_dir (script_dir : Path ) ->  None :
@@ -33,3 +41,152 @@ def process_script_dir(script_dir: Path) -> None:
3341        if  content :
3442            with  item .open ("w" , encoding = "utf-8" ) as  f :
3543                f .writelines (content )
44+ 
45+ 
46+ WRAPPER  =  """ 
47+ import subprocess 
48+ import sys 
49+ 
50+ DIR = os.path.abspath(os.path.dirname(__file__)) 
51+ 
52+ def {function}() -> None: 
53+     exe_path = os.path.join(DIR, "{rel_exe_path}") 
54+     sys.exit(subprocess.call([str(exe_path), *sys.argv[2:]])) 
55+ 
56+ """ 
57+ 
58+ WRAPPER_MODULE_EXTRA  =  """ 
59+ 
60+ if __name__ == "__main__" 
61+     {function}() 
62+ 
63+ """ 
64+ 
65+ 
66+ def  add_dynamic_scripts (
67+     metadata : StandardMetadata ,
68+     settings : ScikitBuildSettings ,
69+     builder : Builder  |  None ,
70+     wheel_dirs : dict [str , Path ],
71+     install_dir : Path ,
72+ ) ->  None :
73+     """ 
74+     Add and create the dynamic ``project.scripts`` from the ``tool.scikit-build.scripts``. 
75+     """ 
76+     targetlib  =  "platlib"  if  "platlib"  in  wheel_dirs  else  "purelib" 
77+     targetlib_dir  =  wheel_dirs [targetlib ]
78+     if  builder :
79+         if  not  (file_api  :=  builder .config .file_api ):
80+             logger .warning ("CMake file-api was not generated." )
81+             return 
82+         build_type  =  builder .config .build_type 
83+         assert  file_api .reply .codemodel_v2 
84+         configuration  =  next (
85+             conf 
86+             for  conf  in  file_api .reply .codemodel_v2 .configurations 
87+             if  conf .name  ==  build_type 
88+         )
89+     else :
90+         configuration  =  None 
91+     for  script , script_info  in  settings .scripts .items ():
92+         if  script_info .target  is  None :
93+             # Early exit if we do not need to create a wrapper 
94+             metadata .scripts [script ] =  script_info .path 
95+             continue 
96+         if  not  configuration :
97+             continue 
98+         python_file_match  =  SCRIPT_PATTERN .match (script_info .path )
99+         if  not  python_file_match :
100+             logger .warning (
101+                 "scripts.{script}.path is not a valid entrypoint" ,
102+                 script = script ,
103+             )
104+             continue 
105+         function  =  python_file_match .group ("function" ) or  "main" 
106+         # Try to find the python file 
107+         pkg_mod  =  python_file_match .group ("module" ).rsplit ("." , maxsplit = 1 )
108+         if  len (pkg_mod ) ==  1 :
109+             pkg  =  None 
110+             mod  =  pkg_mod [0 ]
111+         else :
112+             pkg , mod  =  pkg_mod 
113+ 
114+         pkg_dir  =  targetlib_dir 
115+         if  pkg :
116+             # Make sure all intermediate package files are populated 
117+             for  pkg_part  in  pkg .split ("." ):
118+                 pkg_dir  =  pkg_dir  /  pkg_part 
119+                 pkg_file  =  pkg_dir  /  "__init__.py" 
120+                 pkg_dir .mkdir (exist_ok = True )
121+                 pkg_file .touch (exist_ok = True )
122+         # Check if module is a module or a package 
123+         if  (pkg_dir  /  mod ).is_dir ():
124+             mod_file  =  pkg_dir  /  mod  /  "__init__.py" 
125+         else :
126+             mod_file  =  pkg_dir  /  f"{ mod }  .py" 
127+         if  mod_file .exists ():
128+             logger .warning (
129+                 "Wrapper file already exists: {mod_file}" ,
130+                 mod_file = mod_file ,
131+             )
132+             continue 
133+         # Get the requested target 
134+         for  target  in  configuration .targets :
135+             if  target .type  !=  "EXECUTABLE" :
136+                 continue 
137+             if  target .name  ==  script_info .target :
138+                 break 
139+         else :
140+             logger .warning (
141+                 "Could not find target: {target}" ,
142+                 target = script_info .target ,
143+             )
144+             continue 
145+         # Find the installed artifact 
146+         if  len (target .artifacts ) >  1 :
147+             logger .warning (
148+                 "Multiple target artifacts is not supported: {artifacts}" ,
149+                 artifacts = target .artifacts ,
150+             )
151+             continue 
152+         if  not  target .install :
153+             logger .warning (
154+                 "Target is not installed: {target}" ,
155+                 target = target .name ,
156+             )
157+             continue 
158+         target_artifact  =  target .artifacts [0 ].path 
159+         for  dest  in  target .install .destinations :
160+             install_path  =  dest .path 
161+             if  install_path .is_absolute ():
162+                 try :
163+                     install_path  =  install_path .relative_to (targetlib_dir )
164+                 except  ValueError :
165+                     continue 
166+             else :
167+                 install_path  =  install_dir  /  install_path 
168+             install_artifact  =  targetlib_dir  /  install_path  /  target_artifact .name 
169+             if  not  install_artifact .exists ():
170+                 logger .warning (
171+                     "Did not find installed executable: {artifact}" ,
172+                     artifact = install_artifact ,
173+                 )
174+                 continue 
175+             break 
176+         else :
177+             logger .warning (
178+                 "Did not find installed files for target: {target}" ,
179+                 target = target .name ,
180+             )
181+             continue 
182+         # Generate the content 
183+         content  =  WRAPPER .format (
184+             function = function ,
185+             rel_exe_path = os .path .relpath (install_artifact , mod_file .parent ),
186+         )
187+         if  script_info .as_module :
188+             content  +=  WRAPPER_MODULE_EXTRA .format (function = function )
189+         with  mod_file .open ("w" , encoding = "utf-8" ) as  f :
190+             f .write (content )
191+         # Finally register this as a script 
192+         metadata .scripts [script ] =  script_info .path 
0 commit comments