]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_vendor/distlib/scripts.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _vendor / distlib / scripts.py
1 # -*- coding: utf-8 -*-
2 #
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.
6 #
7 from io import BytesIO
8 import logging
9 import os
10 import re
11 import struct
12 import sys
13 import time
14 from zipfile import ZipInfo
15
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)
20
21 logger = logging.getLogger(__name__)
22
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"
28 name="%s"
29 type="win32"/>
30
31 <!-- Identify the application security requirements. -->
32 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
33 <security>
34 <requestedPrivileges>
35 <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
36 </requestedPrivileges>
37 </security>
38 </trustInfo>
39 </assembly>'''.strip()
40
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 -*-
44 import re
45 import sys
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])
49 sys.exit(%(func)s())
50 '''
51
52
53 def enquote_executable(executable):
54 if ' ' in 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"
58 # otherwise whole
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)
63 else:
64 if not executable.startswith('"'):
65 executable = '"%s"' % executable
66 return executable
67
68 # Keep the old name around (for now), as there is at least one project using it!
69 _enquote_executable = enquote_executable
70
71 class ScriptMaker(object):
72 """
73 A class to copy or create scripts from source scripts or callable
74 specifications.
75 """
76 script_template = SCRIPT_TEMPLATE
77
78 executable = None # for shebangs
79
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
85 self.force = False
86 self.clobber = False
87 # It only makes sense to set mode bits on POSIX.
88 self.set_mode = (os.name == 'posix') or (os.name == 'java' and
89 os._name == 'posix')
90 self.variants = set(('', 'X.Y'))
91 self._fileop = fileop or FileOperator(dry_run)
92
93 self._is_nt = os.name == 'nt' or (
94 os.name == 'java' and os._name == 'nt')
95 self.version_info = sys.version_info
96
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)
102 return executable
103
104 if sys.platform.startswith('java'): # pragma: no cover
105 def _is_shell(self, executable):
106 """
107 Determine if the specified executable is a script
108 (contains a #! line)
109 """
110 try:
111 with open(executable) as fp:
112 return fp.read(2) == '#!'
113 except (OSError, IOError):
114 logger.warning('Failed to open %s', executable)
115 return False
116
117 def _fix_jython_executable(self, executable):
118 if self._is_shell(executable):
119 # Workaround for Jython is not needed on Linux systems.
120 import java
121
122 if java.lang.System.getProperty('os.name') == 'Linux':
123 return executable
124 elif executable.lower().endswith('jython.exe'):
125 # Use wrapper exe for Jython on Windows
126 return executable
127 return '/usr/bin/env %s' % executable
128
129 def _build_shebang(self, executable, post_interp):
130 """
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.
136
137 See also: http://www.in-ulm.de/~mascheck/various/shebang/#length
138 https://hg.mozilla.org/mozilla-central/file/tip/mach
139 """
140 if os.name != 'posix':
141 simple_shebang = True
142 else:
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
147 else:
148 max_shebang_length = 127
149 simple_shebang = ((b' ' not in executable) and
150 (shebang_length <= max_shebang_length))
151
152 if simple_shebang:
153 result = b'#!' + executable + post_interp + b'\n'
154 else:
155 result = b'#!/bin/sh\n'
156 result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n'
157 result += b"' '''"
158 return result
159
160 def _get_shebang(self, encoding, post_interp=b'', options=None):
161 enquote = True
162 if self.executable:
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')))
180 if options:
181 executable = self._get_alternate_executable(executable, options)
182
183 if sys.platform.startswith('java'): # pragma: no cover
184 executable = self._fix_jython_executable(executable)
185
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.
194
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)
197 if enquote:
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
211 # UTF-8.
212 try:
213 shebang.decode('utf-8')
214 except UnicodeDecodeError: # pragma: no cover
215 raise ValueError(
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':
221 try:
222 shebang.decode(encoding)
223 except UnicodeDecodeError: # pragma: no cover
224 raise ValueError(
225 'The shebang (%r) is not decodable '
226 'from the script encoding (%r)' % (shebang, encoding))
227 return shebang
228
229 def _get_script_text(self, entry):
230 return self.script_template % dict(module=entry.prefix,
231 import_name=entry.suffix.split('.')[0],
232 func=entry.suffix)
233
234 manifest = _DEFAULT_MANIFEST
235
236 def get_manifest(self, exename):
237 base = os.path.basename(exename)
238 return self.manifest % base
239
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):
244 shebang += linesep
245 if not use_launcher:
246 script_bytes = shebang + script_bytes
247 else: # pragma: no cover
248 if ext == 'py':
249 launcher = self._get_launcher('t')
250 else:
251 launcher = self._get_launcher('w')
252 stream = BytesIO()
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)
259 else:
260 zf.writestr('__main__.py', script_bytes)
261 zip_data = stream.getvalue()
262 script_bytes = launcher + shebang + zip_data
263 for name in names:
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'):
268 outname = n
269 outname = '%s.exe' % outname
270 try:
271 self._fileop.write_binary_file(outname, script_bytes)
272 except Exception:
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 '
282 '.deleteme logic')
283 try:
284 os.remove(dfname)
285 except Exception:
286 pass # still in use - ignore error
287 else:
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)
292 continue
293 self._fileop.write_binary_file(outname, script_bytes)
294 if self.set_mode:
295 self._fileop.set_executable_mode([outname])
296 filenames.append(outname)
297
298 variant_separator = '-'
299
300 def get_script_filenames(self, name):
301 result = set()
302 if '' in self.variants:
303 result.add(name)
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]))
309 return result
310
311 def _make_script(self, entry, filenames, options=None):
312 post_interp = b''
313 if options:
314 args = options.get('interpreter_args', [])
315 if 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):
322 ext = 'pyw'
323 else:
324 ext = 'py'
325 self._write_script(scriptnames, shebang, script, filenames, ext)
326
327 def _copy_script(self, script, filenames):
328 adjust = False
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)
333 return
334
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
337 # script.
338 try:
339 f = open(script, 'rb')
340 except IOError: # pragma: no cover
341 if not self.dry_run:
342 raise
343 f = None
344 else:
345 first_line = f.readline()
346 if not first_line: # pragma: no cover
347 logger.warning('%s is an empty file (skipping)', script)
348 return
349
350 match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n'))
351 if match:
352 adjust = True
353 post_interp = match.group(1) or b''
354
355 if not adjust:
356 if f:
357 f.close()
358 self._fileop.copy_file(script, outname)
359 if self.set_mode:
360 self._fileop.set_executable_mode([outname])
361 filenames.append(outname)
362 else:
363 logger.info('copying and adjusting %s -> %s', script,
364 self.target_dir)
365 if not self._fileop.dry_run:
366 encoding, lines = detect_encoding(f.readline)
367 f.seek(0)
368 shebang = self._get_shebang(encoding, post_interp)
369 if b'pythonw' in first_line: # pragma: no cover
370 ext = 'pyw'
371 else:
372 ext = 'py'
373 n = os.path.basename(outname)
374 self._write_script([n], shebang, f.read(), filenames, ext)
375 if f:
376 f.close()
377
378 @property
379 def dry_run(self):
380 return self._fileop.dry_run
381
382 @dry_run.setter
383 def dry_run(self, value):
384 self._fileop.dry_run = value
385
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/
389
390 def _get_launcher(self, kind):
391 if struct.calcsize('P') == 8: # 64-bit
392 bits = '64'
393 else:
394 bits = '32'
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)
401 if not resource:
402 msg = ('Unable to find resource %s in package %s' % (name,
403 distlib_package))
404 raise ValueError(msg)
405 return resource.bytes
406
407 # Public API follows
408
409 def make(self, specification, options=None):
410 """
411 Make a script.
412
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.
419 """
420 filenames = []
421 entry = get_export_entry(specification)
422 if entry is None:
423 self._copy_script(specification, filenames)
424 else:
425 self._make_script(entry, filenames, options=options)
426 return filenames
427
428 def make_multiple(self, specifications, options=None):
429 """
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,
433 """
434 filenames = []
435 for specification in specifications:
436 filenames.extend(self.make(specification, options))
437 return filenames