@@ -121,7 +121,50 @@ def _find_on_path(cmd, full_cmd):
121121 }
122122
123123
124+ def _replace_templates (cmd , line , windowed ):
125+ # Override can be the entire line or just the first argument
126+ shebang = re .match (r"#!\s*(.+)(.*)" , line ) or re .match (r"#!\s*([^\s]+)(.*)" , line )
127+
128+ if not shebang or shebang .group (1 ) not in cmd .shebang_templates :
129+ return None , None
130+
131+ new_cmd = cmd .shebang_templates [shebang .group (1 )]
132+ LOGGER .verbose ("Using '%s' from configuration file in place of shebang '%s'" ,
133+ new_cmd , shebang .group (1 ))
134+ install = None
135+ if new_cmd .startswith ("py -V:" ):
136+ install = cmd .get_install_to_run (new_cmd [6 :], windowed = windowed )
137+ elif new_cmd .startswith ("pyw -V:" ):
138+ install = cmd .get_install_to_run (new_cmd [7 :], windowed = True )
139+ elif new_cmd .startswith ("py -3" ):
140+ install = cmd .get_install_to_run (f"PythonCore/{ new_cmd [4 :]} " , windowed = windowed )
141+ elif new_cmd .startswith ("pyw -3" ):
142+ install = cmd .get_install_to_run (f"PythonCore/{ new_cmd [5 :]} " , windowed = True )
143+ elif new_cmd == "py" :
144+ install = cmd .get_install_to_run (windowed = windowed )
145+ elif new_cmd == "pyw" :
146+ install = cmd .get_install_to_run (windowed = True )
147+ else :
148+ # Recreate the shebang with the alternate command and continue.
149+ line = f"#!{ new_cmd } { shebang .group (2 )} "
150+ return install , line
151+
152+
124153def _parse_shebang (cmd , line , * , windowed = None ):
154+ # To silence our warning when we get the path from config file
155+ run_anything_silently = False
156+
157+ # First check the user-provided overrides
158+ if cmd .shebang_templates :
159+ install , new_line = _replace_templates (cmd , line , windowed )
160+ if install :
161+ return install
162+ if new_line :
163+ # We don't warn about custom executables if they've come from
164+ # the config file, unless they don't exist or are disabled.
165+ run_anything_silently = True
166+ line = new_line
167+
125168 # For /usr[/local]/bin, we look for a matching alias name.
126169 shebang = re .match (r"#!\s*/usr/(?:local/)?bin/(?!env\b)([^\\/\s]+).*" , line )
127170 if shebang :
@@ -151,7 +194,7 @@ def _parse_shebang(cmd, line, *, windowed=None):
151194 # If not, warn and do regular PATH search
152195 if cmd .shebang_can_run_anything or cmd .shebang_can_run_anything_silently :
153196 i = _find_on_path (cmd , full_cmd )
154- if not cmd .shebang_can_run_anything_silently :
197+ if not cmd .shebang_can_run_anything_silently and not run_anything_silently :
155198 LOGGER .warn ("A shebang '%s' was found but could not be matched "
156199 "to an installed runtime, so it will be treated as "
157200 "an arbitrary command." , full_cmd )
@@ -181,14 +224,19 @@ def _parse_shebang(cmd, line, *, windowed=None):
181224 except LookupError :
182225 pass
183226 if cmd .shebang_can_run_anything or cmd .shebang_can_run_anything_silently :
184- if not cmd .shebang_can_run_anything_silently :
227+ if not cmd .shebang_can_run_anything_silently and not run_anything_silently :
185228 LOGGER .warn ("A shebang '%s' was found but does not match any "
186- "supported template (e.g. '/usr/bin/python'), so it "
187- "will be treated as an arbitrary command." , full_cmd )
229+ "supported or configured template (e.g. "
230+ "'/usr/bin/python'), so it will be treated as an "
231+ "arbitrary command." , full_cmd )
188232 LOGGER .warn ("To prevent execution of programs that are not "
189233 "Python runtimes, set 'shebang_can_run_anything' to "
190234 "'false' in your configuration file." )
191- return _find_on_path (cmd , full_cmd )
235+ try :
236+ return _find_on_path (cmd , full_cmd )
237+ except LookupError as ex :
238+ LOGGER .error ("Could not launch '%s'. Using default interpreter "
239+ "instead." , full_cmd )
192240 else :
193241 LOGGER .warn ("A shebang '%s' was found, but could not be matched "
194242 "to an installed runtime." , full_cmd )
0 commit comments