]>
jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_vendor/distlib/scripts.py
1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2013-2015 Vinay Sajip.
4 # Licensed to the Python Software Foundation under a contributor agreement.
5 # See LICENSE.txt and CONTRIBUTORS.txt.
14 from zipfile
import ZipInfo
16 from .compat
import sysconfig
, detect_encoding
, ZipFile
17 from .resources
import finder
18 from .util
import (FileOperator
, get_export_entry
, convert_path
,
19 get_executable
, get_platform
, in_venv
)
21 logger
= logging
.getLogger(__name__
)
23 _DEFAULT_MANIFEST
= '''
24 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
25 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
26 <assemblyIdentity version="1.0.0.0"
27 processorArchitecture="X86"
31 <!-- Identify the application security requirements. -->
32 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
35 <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
36 </requestedPrivileges>
39 </assembly>'''.strip()
41 # check if Python is called on the first line with this expression
42 FIRST_LINE_RE
= re
.compile(b
'^#!.*pythonw?[0-9.]*([ \t].*)?$')
43 SCRIPT_TEMPLATE
= r
'''# -*- coding: utf-8 -*-
46 from %(module)s import %(import_name)s
47 if __name__ == '__main__':
48 sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
53 def enquote_executable(executable
):
55 # make sure we quote only the executable in case of env
56 # for example /usr/bin/env "/dir with spaces/bin/jython"
57 # instead of "/usr/bin/env /dir with spaces/bin/jython"
59 if executable
.startswith('/usr/bin/env '):
60 env
, _executable
= executable
.split(' ', 1)
61 if ' ' in _executable
and not _executable
.startswith('"'):
62 executable
= '%s "%s"' % (env
, _executable
)
64 if not executable
.startswith('"'):
65 executable
= '"%s"' % executable
68 # Keep the old name around (for now), as there is at least one project using it!
69 _enquote_executable
= enquote_executable
71 class ScriptMaker(object):
73 A class to copy or create scripts from source scripts or callable
76 script_template
= SCRIPT_TEMPLATE
78 executable
= None # for shebangs
80 def __init__(self
, source_dir
, target_dir
, add_launchers
=True,
81 dry_run
=False, fileop
=None):
82 self
.source_dir
= source_dir
83 self
.target_dir
= target_dir
84 self
.add_launchers
= add_launchers
87 # It only makes sense to set mode bits on POSIX.
88 self
.set_mode
= (os
.name
== 'posix') or (os
.name
== 'java' and
90 self
.variants
= set(('', 'X.Y'))
91 self
._fileop
= fileop
or FileOperator(dry_run
)
93 self
._is
_nt
= os
.name
== 'nt' or (
94 os
.name
== 'java' and os
._name
== 'nt')
95 self
.version_info
= sys
.version_info
97 def _get_alternate_executable(self
, executable
, options
):
98 if options
.get('gui', False) and self
._is
_nt
: # pragma: no cover
99 dn
, fn
= os
.path
.split(executable
)
100 fn
= fn
.replace('python', 'pythonw')
101 executable
= os
.path
.join(dn
, fn
)
104 if sys
.platform
.startswith('java'): # pragma: no cover
105 def _is_shell(self
, executable
):
107 Determine if the specified executable is a script
111 with open(executable
) as fp
:
112 return fp
.read(2) == '#!'
113 except (OSError, IOError):
114 logger
.warning('Failed to open %s', executable
)
117 def _fix_jython_executable(self
, executable
):
118 if self
._is
_shell
(executable
):
119 # Workaround for Jython is not needed on Linux systems.
122 if java
.lang
.System
.getProperty('os.name') == 'Linux':
124 elif executable
.lower().endswith('jython.exe'):
125 # Use wrapper exe for Jython on Windows
127 return '/usr/bin/env %s' % executable
129 def _build_shebang(self
, executable
, post_interp
):
131 Build a shebang line. In the simple case (on Windows, or a shebang line
132 which is not too long or contains spaces) use a simple formulation for
133 the shebang. Otherwise, use /bin/sh as the executable, with a contrived
134 shebang which allows the script to run either under Python or sh, using
135 suitable quoting. Thanks to Harald Nordgren for his input.
137 See also: http://www.in-ulm.de/~mascheck/various/shebang/#length
138 https://hg.mozilla.org/mozilla-central/file/tip/mach
140 if os
.name
!= 'posix':
141 simple_shebang
= True
143 # Add 3 for '#!' prefix and newline suffix.
144 shebang_length
= len(executable
) + len(post_interp
) + 3
145 if sys
.platform
== 'darwin':
146 max_shebang_length
= 512
148 max_shebang_length
= 127
149 simple_shebang
= ((b
' ' not in executable
) and
150 (shebang_length
<= max_shebang_length
))
153 result
= b
'#!' + executable
+ post_interp
+ b
'\n'
155 result
= b
'#!/bin/sh\n'
156 result
+= b
"'''exec' " + executable
+ post_interp
+ b
' "$0" "$@"\n'
160 def _get_shebang(self
, encoding
, post_interp
=b
'', options
=None):
163 executable
= self
.executable
164 enquote
= False # assume this will be taken care of
165 elif not sysconfig
.is_python_build():
166 executable
= get_executable()
167 elif in_venv(): # pragma: no cover
168 executable
= os
.path
.join(sysconfig
.get_path('scripts'),
169 'python%s' % sysconfig
.get_config_var('EXE'))
170 else: # pragma: no cover
171 executable
= os
.path
.join(
172 sysconfig
.get_config_var('BINDIR'),
173 'python%s%s' % (sysconfig
.get_config_var('VERSION'),
174 sysconfig
.get_config_var('EXE')))
175 if not os
.path
.isfile(executable
):
176 # for Python builds from source on Windows, no Python executables with
177 # a version suffix are created, so we use python.exe
178 executable
= os
.path
.join(sysconfig
.get_config_var('BINDIR'),
179 'python%s' % (sysconfig
.get_config_var('EXE')))
181 executable
= self
._get
_alternate
_executable
(executable
, options
)
183 if sys
.platform
.startswith('java'): # pragma: no cover
184 executable
= self
._fix
_jython
_executable
(executable
)
186 # Normalise case for Windows - COMMENTED OUT
187 # executable = os.path.normcase(executable)
188 # N.B. The normalising operation above has been commented out: See
189 # issue #124. Although paths in Windows are generally case-insensitive,
190 # they aren't always. For example, a path containing a ẞ (which is a
191 # LATIN CAPITAL LETTER SHARP S - U+1E9E) is normcased to ß (which is a
192 # LATIN SMALL LETTER SHARP S' - U+00DF). The two are not considered by
193 # Windows as equivalent in path names.
195 # If the user didn't specify an executable, it may be necessary to
196 # cater for executable paths with spaces (not uncommon on Windows)
198 executable
= enquote_executable(executable
)
199 # Issue #51: don't use fsencode, since we later try to
200 # check that the shebang is decodable using utf-8.
201 executable
= executable
.encode('utf-8')
202 # in case of IronPython, play safe and enable frames support
203 if (sys
.platform
== 'cli' and '-X:Frames' not in post_interp
204 and '-X:FullFrames' not in post_interp
): # pragma: no cover
205 post_interp
+= b
' -X:Frames'
206 shebang
= self
._build
_shebang
(executable
, post_interp
)
207 # Python parser starts to read a script using UTF-8 until
208 # it gets a #coding:xxx cookie. The shebang has to be the
209 # first line of a file, the #coding:xxx cookie cannot be
210 # written before. So the shebang has to be decodable from
213 shebang
.decode('utf-8')
214 except UnicodeDecodeError: # pragma: no cover
216 'The shebang (%r) is not decodable from utf-8' % shebang
)
217 # If the script is encoded to a custom encoding (use a
218 # #coding:xxx cookie), the shebang has to be decodable from
219 # the script encoding too.
220 if encoding
!= 'utf-8':
222 shebang
.decode(encoding
)
223 except UnicodeDecodeError: # pragma: no cover
225 'The shebang (%r) is not decodable '
226 'from the script encoding (%r)' % (shebang
, encoding
))
229 def _get_script_text(self
, entry
):
230 return self
.script_template
% dict(module
=entry
.prefix
,
231 import_name
=entry
.suffix
.split('.')[0],
234 manifest
= _DEFAULT_MANIFEST
236 def get_manifest(self
, exename
):
237 base
= os
.path
.basename(exename
)
238 return self
.manifest
% base
240 def _write_script(self
, names
, shebang
, script_bytes
, filenames
, ext
):
241 use_launcher
= self
.add_launchers
and self
._is
_nt
242 linesep
= os
.linesep
.encode('utf-8')
243 if not shebang
.endswith(linesep
):
246 script_bytes
= shebang
+ script_bytes
247 else: # pragma: no cover
249 launcher
= self
._get
_launcher
('t')
251 launcher
= self
._get
_launcher
('w')
253 with ZipFile(stream
, 'w') as zf
:
254 source_date_epoch
= os
.environ
.get('SOURCE_DATE_EPOCH')
255 if source_date_epoch
:
256 date_time
= time
.gmtime(int(source_date_epoch
))[:6]
257 zinfo
= ZipInfo(filename
='__main__.py', date_time
=date_time
)
258 zf
.writestr(zinfo
, script_bytes
)
260 zf
.writestr('__main__.py', script_bytes
)
261 zip_data
= stream
.getvalue()
262 script_bytes
= launcher
+ shebang
+ zip_data
264 outname
= os
.path
.join(self
.target_dir
, name
)
265 if use_launcher
: # pragma: no cover
266 n
, e
= os
.path
.splitext(outname
)
267 if e
.startswith('.py'):
269 outname
= '%s.exe' % outname
271 self
._fileop
.write_binary_file(outname
, script_bytes
)
273 # Failed writing an executable - it might be in use.
274 logger
.warning('Failed to write executable - trying to '
275 'use .deleteme logic')
276 dfname
= '%s.deleteme' % outname
277 if os
.path
.exists(dfname
):
278 os
.remove(dfname
) # Not allowed to fail here
279 os
.rename(outname
, dfname
) # nor here
280 self
._fileop
.write_binary_file(outname
, script_bytes
)
281 logger
.debug('Able to replace executable using '
286 pass # still in use - ignore error
288 if self
._is
_nt
and not outname
.endswith('.' + ext
): # pragma: no cover
289 outname
= '%s.%s' % (outname
, ext
)
290 if os
.path
.exists(outname
) and not self
.clobber
:
291 logger
.warning('Skipping existing file %s', outname
)
293 self
._fileop
.write_binary_file(outname
, script_bytes
)
295 self
._fileop
.set_executable_mode([outname
])
296 filenames
.append(outname
)
298 variant_separator
= '-'
300 def get_script_filenames(self
, name
):
302 if '' in self
.variants
:
304 if 'X' in self
.variants
:
305 result
.add('%s%s' % (name
, self
.version_info
[0]))
306 if 'X.Y' in self
.variants
:
307 result
.add('%s%s%s.%s' % (name
, self
.variant_separator
,
308 self
.version_info
[0], self
.version_info
[1]))
311 def _make_script(self
, entry
, filenames
, options
=None):
314 args
= options
.get('interpreter_args', [])
316 args
= ' %s' % ' '.join(args
)
317 post_interp
= args
.encode('utf-8')
318 shebang
= self
._get
_shebang
('utf-8', post_interp
, options
=options
)
319 script
= self
._get
_script
_text
(entry
).encode('utf-8')
320 scriptnames
= self
.get_script_filenames(entry
.name
)
321 if options
and options
.get('gui', False):
325 self
._write
_script
(scriptnames
, shebang
, script
, filenames
, ext
)
327 def _copy_script(self
, script
, filenames
):
329 script
= os
.path
.join(self
.source_dir
, convert_path(script
))
330 outname
= os
.path
.join(self
.target_dir
, os
.path
.basename(script
))
331 if not self
.force
and not self
._fileop
.newer(script
, outname
):
332 logger
.debug('not copying %s (up-to-date)', script
)
335 # Always open the file, but ignore failures in dry-run mode --
336 # that way, we'll get accurate feedback if we can read the
339 f
= open(script
, 'rb')
340 except IOError: # pragma: no cover
345 first_line
= f
.readline()
346 if not first_line
: # pragma: no cover
347 logger
.warning('%s is an empty file (skipping)', script
)
350 match
= FIRST_LINE_RE
.match(first_line
.replace(b
'\r\n', b
'\n'))
353 post_interp
= match
.group(1) or b
''
358 self
._fileop
.copy_file(script
, outname
)
360 self
._fileop
.set_executable_mode([outname
])
361 filenames
.append(outname
)
363 logger
.info('copying and adjusting %s -> %s', script
,
365 if not self
._fileop
.dry_run
:
366 encoding
, lines
= detect_encoding(f
.readline
)
368 shebang
= self
._get
_shebang
(encoding
, post_interp
)
369 if b
'pythonw' in first_line
: # pragma: no cover
373 n
= os
.path
.basename(outname
)
374 self
._write
_script
([n
], shebang
, f
.read(), filenames
, ext
)
380 return self
._fileop
.dry_run
383 def dry_run(self
, value
):
384 self
._fileop
.dry_run
= value
386 if os
.name
== 'nt' or (os
.name
== 'java' and os
._name
== 'nt'): # pragma: no cover
387 # Executable launcher support.
388 # Launchers are from https://bitbucket.org/vinay.sajip/simple_launcher/
390 def _get_launcher(self
, kind
):
391 if struct
.calcsize('P') == 8: # 64-bit
395 platform_suffix
= '-arm' if get_platform() == 'win-arm64' else ''
396 name
= '%s%s%s.exe' % (kind
, bits
, platform_suffix
)
397 # Issue 31: don't hardcode an absolute package name, but
398 # determine it relative to the current package
399 distlib_package
= __name__
.rsplit('.', 1)[0]
400 resource
= finder(distlib_package
).find(name
)
402 msg
= ('Unable to find resource %s in package %s' % (name
,
404 raise ValueError(msg
)
405 return resource
.bytes
409 def make(self
, specification
, options
=None):
413 :param specification: The specification, which is either a valid export
414 entry specification (to make a script from a
415 callable) or a filename (to make a script by
416 copying from a source location).
417 :param options: A dictionary of options controlling script generation.
418 :return: A list of all absolute pathnames written to.
421 entry
= get_export_entry(specification
)
423 self
._copy
_script
(specification
, filenames
)
425 self
._make
_script
(entry
, filenames
, options
=options
)
428 def make_multiple(self
, specifications
, options
=None):
430 Take a list of specifications and make scripts from them,
431 :param specifications: A list of specifications.
432 :return: A list of all absolute pathnames written to,
435 for specification
in specifications
:
436 filenames
.extend(self
.make(specification
, options
))