omdev 0.0.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omdev/__about__.py +35 -0
- omdev/__init__.py +0 -0
- omdev/amalg/__init__.py +0 -0
- omdev/amalg/__main__.py +4 -0
- omdev/amalg/amalg.py +513 -0
- omdev/classdot.py +61 -0
- omdev/cmake.py +164 -0
- omdev/exts/__init__.py +0 -0
- omdev/exts/_distutils/__init__.py +10 -0
- omdev/exts/_distutils/build_ext.py +367 -0
- omdev/exts/_distutils/compilers/__init__.py +3 -0
- omdev/exts/_distutils/compilers/ccompiler.py +1032 -0
- omdev/exts/_distutils/compilers/options.py +80 -0
- omdev/exts/_distutils/compilers/unixccompiler.py +385 -0
- omdev/exts/_distutils/dir_util.py +76 -0
- omdev/exts/_distutils/errors.py +62 -0
- omdev/exts/_distutils/extension.py +107 -0
- omdev/exts/_distutils/file_util.py +216 -0
- omdev/exts/_distutils/modified.py +47 -0
- omdev/exts/_distutils/spawn.py +103 -0
- omdev/exts/_distutils/sysconfig.py +349 -0
- omdev/exts/_distutils/util.py +201 -0
- omdev/exts/_distutils/version.py +308 -0
- omdev/exts/build.py +43 -0
- omdev/exts/cmake.py +195 -0
- omdev/exts/importhook.py +88 -0
- omdev/exts/scan.py +74 -0
- omdev/interp/__init__.py +1 -0
- omdev/interp/__main__.py +4 -0
- omdev/interp/cli.py +63 -0
- omdev/interp/inspect.py +105 -0
- omdev/interp/providers.py +67 -0
- omdev/interp/pyenv.py +353 -0
- omdev/interp/resolvers.py +76 -0
- omdev/interp/standalone.py +187 -0
- omdev/interp/system.py +125 -0
- omdev/interp/types.py +92 -0
- omdev/mypy/__init__.py +0 -0
- omdev/mypy/debug.py +86 -0
- omdev/pyproject/__init__.py +1 -0
- omdev/pyproject/__main__.py +4 -0
- omdev/pyproject/cli.py +319 -0
- omdev/pyproject/configs.py +97 -0
- omdev/pyproject/ext.py +107 -0
- omdev/pyproject/pkg.py +196 -0
- omdev/scripts/__init__.py +0 -0
- omdev/scripts/execrss.py +19 -0
- omdev/scripts/findimports.py +62 -0
- omdev/scripts/findmagic.py +70 -0
- omdev/scripts/interp.py +2118 -0
- omdev/scripts/pyproject.py +3584 -0
- omdev/scripts/traceimport.py +502 -0
- omdev/tokens.py +42 -0
- omdev/toml/__init__.py +1 -0
- omdev/toml/parser.py +823 -0
- omdev/toml/writer.py +104 -0
- omdev/tools/__init__.py +0 -0
- omdev/tools/dockertools.py +81 -0
- omdev/tools/sqlrepl.py +193 -0
- omdev/versioning/__init__.py +1 -0
- omdev/versioning/specifiers.py +531 -0
- omdev/versioning/versions.py +416 -0
- omdev-0.0.0.dev7.dist-info/LICENSE +21 -0
- omdev-0.0.0.dev7.dist-info/METADATA +24 -0
- omdev-0.0.0.dev7.dist-info/RECORD +67 -0
- omdev-0.0.0.dev7.dist-info/WHEEL +5 -0
- omdev-0.0.0.dev7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Provides the Extension class, used to describe C/C++ extension modules in setup scripts."""
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Extension:
|
|
6
|
+
"""
|
|
7
|
+
Just a collection of attributes that describes an extension module and everything needed to build it (hopefully in a
|
|
8
|
+
portable way, but there are hooks that let you be as unportable as you need).
|
|
9
|
+
|
|
10
|
+
Instance attributes:
|
|
11
|
+
name : string
|
|
12
|
+
the full name of the extension, including any packages -- ie. *not* a filename or pathname, but Python dotted
|
|
13
|
+
name
|
|
14
|
+
sources : [string]
|
|
15
|
+
list of source filenames, relative to the distribution root (where the setup script lives), in Unix form
|
|
16
|
+
(slash-separated) for portability. Source files may be C, C++, SWIG (.i), platform-specific resource files, or
|
|
17
|
+
whatever else is recognized by the "build_ext" command as source for a Python extension.
|
|
18
|
+
include_dirs : [string]
|
|
19
|
+
list of directories to search for C/C++ header files (in Unix form for portability)
|
|
20
|
+
define_macros : [(name : string, value : string|None)]
|
|
21
|
+
list of macros to define; each macro is defined using a 2-tuple, where 'value' is either the string to define it
|
|
22
|
+
to or None to define it without a particular value (equivalent of "#define FOO" in source or -DFOO on Unix C
|
|
23
|
+
compiler command line)
|
|
24
|
+
undef_macros : [string]
|
|
25
|
+
list of macros to undefine explicitly
|
|
26
|
+
library_dirs : [string]
|
|
27
|
+
list of directories to search for C/C++ libraries at link time
|
|
28
|
+
libraries : [string]
|
|
29
|
+
list of library names (not filenames or paths) to link against
|
|
30
|
+
runtime_library_dirs : [string]
|
|
31
|
+
list of directories to search for C/C++ libraries at run time (for shared extensions, this is when the extension
|
|
32
|
+
is loaded)
|
|
33
|
+
extra_objects : [string]
|
|
34
|
+
list of extra files to link with (eg. object files not implied by 'sources', static library that must be
|
|
35
|
+
explicitly specified, binary resource files, etc.)
|
|
36
|
+
extra_compile_args : [string]
|
|
37
|
+
any extra platform- and compiler-specific information to use when compiling the source files in 'sources'. For
|
|
38
|
+
platforms and compilers where "command line" makes sense, this is typically a list of command-line arguments,
|
|
39
|
+
but for other platforms it could be anything.
|
|
40
|
+
extra_link_args : [string]
|
|
41
|
+
any extra platform- and compiler-specific information to use when linking object files together to create the
|
|
42
|
+
extension (or to create a new static Python interpreter). Similar interpretation as for 'extra_compile_args'.
|
|
43
|
+
export_symbols : [string]
|
|
44
|
+
list of symbols to be exported from a shared extension. Not used on all platforms, and not generally necessary
|
|
45
|
+
for Python extensions, which typically export exactly one symbol: "init" + extension_name.
|
|
46
|
+
swig_opts : [string]
|
|
47
|
+
any extra options to pass to SWIG if a source file has the .i extension.
|
|
48
|
+
depends : [string]
|
|
49
|
+
list of files that the extension depends on
|
|
50
|
+
language : string
|
|
51
|
+
extension language (i.e. "c", "c++", "objc"). Will be detected from the source extensions if not provided.
|
|
52
|
+
optional : boolean
|
|
53
|
+
specifies that a build failure in the extension should not abort the build process, but simply not install the
|
|
54
|
+
failing extension.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# When adding arguments to this constructor, be sure to update setup_keywords in core.py.
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
name: str,
|
|
61
|
+
sources: list[str],
|
|
62
|
+
include_dirs: list[str] | None = None,
|
|
63
|
+
define_macros: list[tuple[str, str]] | None = None,
|
|
64
|
+
undef_macros: list[str] | None = None,
|
|
65
|
+
library_dirs: list[str] | None = None,
|
|
66
|
+
libraries: list[str] | None = None,
|
|
67
|
+
runtime_library_dirs: list[str] | None = None,
|
|
68
|
+
extra_objects: list[str] | None = None,
|
|
69
|
+
extra_compile_args: list[str] | None = None,
|
|
70
|
+
extra_link_args: list[str] | None = None,
|
|
71
|
+
export_symbols: list[str] | None = None,
|
|
72
|
+
swig_opts: list[str] | None = None,
|
|
73
|
+
depends: list[str] | None = None,
|
|
74
|
+
language: str | None = None,
|
|
75
|
+
optional: str | None = None,
|
|
76
|
+
**kw, # To catch unknown keywords
|
|
77
|
+
):
|
|
78
|
+
if not isinstance(name, str):
|
|
79
|
+
raise TypeError("'name' must be a string")
|
|
80
|
+
if not (isinstance(sources, list) and all(isinstance(v, str) for v in sources)):
|
|
81
|
+
raise TypeError("'sources' must be a list of strings")
|
|
82
|
+
|
|
83
|
+
self.name = name
|
|
84
|
+
self.sources = sources
|
|
85
|
+
self.include_dirs = include_dirs or []
|
|
86
|
+
self.define_macros = define_macros or []
|
|
87
|
+
self.undef_macros = undef_macros or []
|
|
88
|
+
self.library_dirs = library_dirs or []
|
|
89
|
+
self.libraries = libraries or []
|
|
90
|
+
self.runtime_library_dirs = runtime_library_dirs or []
|
|
91
|
+
self.extra_objects = extra_objects or []
|
|
92
|
+
self.extra_compile_args = extra_compile_args or []
|
|
93
|
+
self.extra_link_args = extra_link_args or []
|
|
94
|
+
self.export_symbols = export_symbols or []
|
|
95
|
+
self.swig_opts = swig_opts or []
|
|
96
|
+
self.depends = depends or []
|
|
97
|
+
self.language = language
|
|
98
|
+
self.optional = optional
|
|
99
|
+
|
|
100
|
+
# If there are unknown keyword options, warn about them
|
|
101
|
+
if len(kw) > 0:
|
|
102
|
+
options = ', '.join(sorted([repr(option) for option in kw]))
|
|
103
|
+
msg = f'Unknown Extension options: {options}'
|
|
104
|
+
warnings.warn(msg)
|
|
105
|
+
|
|
106
|
+
def __repr__(self):
|
|
107
|
+
return f'<{self.__class__.__module__}.{self.__class__.__qualname__}({self.name!r}) at {id(self):#x}>'
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Utility functions for operating on single files."""
|
|
2
|
+
import contextlib
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from .errors import DistutilsFileError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# for generating verbose output in 'copy_file()'
|
|
13
|
+
_copy_action = {None: 'copying', 'hard': 'hard linking', 'sym': 'symbolically linking'}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _copy_file_contents(src, dst, buffer_size=16 * 1024): # noqa: C901
|
|
17
|
+
"""
|
|
18
|
+
Copy the file 'src' to 'dst'; both must be filenames. Any error opening either file, reading from 'src', or writing
|
|
19
|
+
to 'dst', raises DistutilsFileError. Data is read/written in chunks of 'buffer_size' bytes (default 16k). No
|
|
20
|
+
attempt is made to handle anything apart from regular files.
|
|
21
|
+
"""
|
|
22
|
+
# Stolen from shutil module in the standard library, but with custom error-handling added.
|
|
23
|
+
fsrc = None
|
|
24
|
+
fdst = None
|
|
25
|
+
try:
|
|
26
|
+
try:
|
|
27
|
+
fsrc = open(src, 'rb') # noqa
|
|
28
|
+
except OSError as e:
|
|
29
|
+
raise DistutilsFileError(f"could not open '{src}': {e.strerror}") from None
|
|
30
|
+
|
|
31
|
+
if os.path.exists(dst):
|
|
32
|
+
try:
|
|
33
|
+
os.unlink(dst)
|
|
34
|
+
except OSError as e:
|
|
35
|
+
raise DistutilsFileError(f"could not delete '{dst}': {e.strerror}") from None
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
fdst = open(dst, 'wb') # noqa
|
|
39
|
+
except OSError as e:
|
|
40
|
+
raise DistutilsFileError(f"could not create '{dst}': {e.strerror}") from None
|
|
41
|
+
|
|
42
|
+
while True:
|
|
43
|
+
try:
|
|
44
|
+
buf = fsrc.read(buffer_size)
|
|
45
|
+
except OSError as e:
|
|
46
|
+
raise DistutilsFileError(f"could not read from '{src}': {e.strerror}") from None
|
|
47
|
+
|
|
48
|
+
if not buf:
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
fdst.write(buf)
|
|
53
|
+
except OSError as e:
|
|
54
|
+
raise DistutilsFileError(f"could not write to '{dst}': {e.strerror}") from None
|
|
55
|
+
|
|
56
|
+
finally:
|
|
57
|
+
if fdst:
|
|
58
|
+
fdst.close()
|
|
59
|
+
if fsrc:
|
|
60
|
+
fsrc.close()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def copy_file( # noqa: C901
|
|
64
|
+
src,
|
|
65
|
+
dst,
|
|
66
|
+
preserve_mode=1,
|
|
67
|
+
preserve_times=1,
|
|
68
|
+
update=0,
|
|
69
|
+
link=None,
|
|
70
|
+
verbose=1,
|
|
71
|
+
dry_run=False,
|
|
72
|
+
):
|
|
73
|
+
"""
|
|
74
|
+
Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is copied there with the same name; otherwise, it
|
|
75
|
+
must be a filename. (If the file exists, it will be ruthlessly clobbered.) If 'preserve_mode' is true (the
|
|
76
|
+
default), the file's mode (type and permission bits, or whatever is analogous on the current platform) is copied.
|
|
77
|
+
If 'preserve_times' is true (the default), the last-modified and last-access times are copied as well. If 'update'
|
|
78
|
+
is true, 'src' will only be copied if 'dst' does not exist, or if 'dst' does exist but is older than 'src'.
|
|
79
|
+
|
|
80
|
+
'link' allows you to make hard links (os.link) or symbolic links (os.symlink) instead of copying: set it to "hard"
|
|
81
|
+
or "sym"; if it is None (the default), files are copied. Don't set 'link' on systems that don't support it:
|
|
82
|
+
'copy_file()' doesn't check if hard or symbolic linking is available. If hardlink fails, falls back to
|
|
83
|
+
_copy_file_contents().
|
|
84
|
+
|
|
85
|
+
Under Mac OS, uses the native file copy function in macostools; on other systems, uses '_copy_file_contents()' to
|
|
86
|
+
copy file contents.
|
|
87
|
+
|
|
88
|
+
Return a tuple (dest_name, copied): 'dest_name' is the actual name of the output file, and 'copied' is true if the
|
|
89
|
+
file was copied (or would have been copied, if 'dry_run' true).
|
|
90
|
+
"""
|
|
91
|
+
# if the destination file already exists, we clobber it if copying, but blow up if linking. Hmmm. And I don't know
|
|
92
|
+
# what macostools.copyfile() does. Should definitely be consistent, and should probably blow up if destination
|
|
93
|
+
# exists and we would be changing it (ie. it's not already a hard/soft link to src OR (not update) and (src newer
|
|
94
|
+
# than dst).
|
|
95
|
+
from stat import S_IMODE
|
|
96
|
+
from stat import ST_ATIME
|
|
97
|
+
from stat import ST_MODE
|
|
98
|
+
from stat import ST_MTIME
|
|
99
|
+
|
|
100
|
+
from .modified import newer
|
|
101
|
+
|
|
102
|
+
if not os.path.isfile(src):
|
|
103
|
+
raise DistutilsFileError(f"can't copy '{src}': doesn't exist or not a regular file")
|
|
104
|
+
|
|
105
|
+
if os.path.isdir(dst):
|
|
106
|
+
dir = dst # noqa
|
|
107
|
+
dst = os.path.join(dst, os.path.basename(src))
|
|
108
|
+
else:
|
|
109
|
+
dir = os.path.dirname(dst) # noqa
|
|
110
|
+
|
|
111
|
+
if update and not newer(src, dst):
|
|
112
|
+
if verbose >= 1:
|
|
113
|
+
log.debug('not copying %s (output up-to-date)', src)
|
|
114
|
+
return (dst, 0)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
action = _copy_action[link]
|
|
118
|
+
except KeyError:
|
|
119
|
+
raise ValueError(f"invalid value '{link}' for 'link' argument") from None
|
|
120
|
+
|
|
121
|
+
if verbose >= 1:
|
|
122
|
+
if os.path.basename(dst) == os.path.basename(src):
|
|
123
|
+
log.info('%s %s -> %s', action, src, dir)
|
|
124
|
+
else:
|
|
125
|
+
log.info('%s %s -> %s', action, src, dst)
|
|
126
|
+
|
|
127
|
+
if dry_run:
|
|
128
|
+
return (dst, 1)
|
|
129
|
+
|
|
130
|
+
# If linking (hard or symbolic), use the appropriate system call (Unix only, of course, but that's the caller's
|
|
131
|
+
# responsibility)
|
|
132
|
+
elif link == 'hard':
|
|
133
|
+
if not (os.path.exists(dst) and os.path.samefile(src, dst)):
|
|
134
|
+
try:
|
|
135
|
+
os.link(src, dst)
|
|
136
|
+
except OSError:
|
|
137
|
+
# If hard linking fails, fall back on copying file (some special filesystems don't support hard linking
|
|
138
|
+
# even under Unix, see issue #8876).
|
|
139
|
+
pass
|
|
140
|
+
else:
|
|
141
|
+
return (dst, 1)
|
|
142
|
+
elif link == 'sym':
|
|
143
|
+
if not (os.path.exists(dst) and os.path.samefile(src, dst)):
|
|
144
|
+
os.symlink(src, dst)
|
|
145
|
+
return (dst, 1)
|
|
146
|
+
|
|
147
|
+
# Otherwise (non-Mac, not linking), copy the file contents and (optionally) copy the times and mode.
|
|
148
|
+
_copy_file_contents(src, dst)
|
|
149
|
+
if preserve_mode or preserve_times:
|
|
150
|
+
st = os.stat(src)
|
|
151
|
+
|
|
152
|
+
# According to David Ascher <da@ski.org>, utime() should be done before chmod() (at least under NT).
|
|
153
|
+
if preserve_times:
|
|
154
|
+
os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))
|
|
155
|
+
if preserve_mode:
|
|
156
|
+
os.chmod(dst, S_IMODE(st[ST_MODE]))
|
|
157
|
+
|
|
158
|
+
return (dst, 1)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# I suspect this is Unix-specific -- need porting help!
|
|
162
|
+
def move_file(src, dst, verbose=1, dry_run=False): # noqa: C901
|
|
163
|
+
"""
|
|
164
|
+
Move a file 'src' to 'dst'. If 'dst' is a directory, the file will be moved into it with the same name; otherwise,
|
|
165
|
+
'src' is just renamed to 'dst'. Return the new full name of the file.
|
|
166
|
+
|
|
167
|
+
Handles cross-device moves on Unix using 'copy_file()'. What about other systems???
|
|
168
|
+
"""
|
|
169
|
+
import errno
|
|
170
|
+
from os.path import basename
|
|
171
|
+
from os.path import dirname
|
|
172
|
+
from os.path import exists
|
|
173
|
+
from os.path import isdir
|
|
174
|
+
from os.path import isfile
|
|
175
|
+
|
|
176
|
+
if verbose >= 1:
|
|
177
|
+
log.info('moving %s -> %s', src, dst)
|
|
178
|
+
|
|
179
|
+
if dry_run:
|
|
180
|
+
return dst
|
|
181
|
+
|
|
182
|
+
if not isfile(src):
|
|
183
|
+
raise DistutilsFileError(f"can't move '{src}': not a regular file")
|
|
184
|
+
|
|
185
|
+
if isdir(dst):
|
|
186
|
+
dst = os.path.join(dst, basename(src))
|
|
187
|
+
elif exists(dst):
|
|
188
|
+
raise DistutilsFileError(f"can't move '{src}': destination '{dst}' already exists")
|
|
189
|
+
|
|
190
|
+
if not isdir(dirname(dst)):
|
|
191
|
+
raise DistutilsFileError(f"can't move '{src}': destination '{dst}' not a valid path")
|
|
192
|
+
|
|
193
|
+
copy_it = False
|
|
194
|
+
try:
|
|
195
|
+
os.rename(src, dst)
|
|
196
|
+
except OSError as e:
|
|
197
|
+
(num, msg) = e.args
|
|
198
|
+
if num == errno.EXDEV:
|
|
199
|
+
copy_it = True
|
|
200
|
+
else:
|
|
201
|
+
raise DistutilsFileError(f"couldn't move '{src}' to '{dst}': {msg}") from None
|
|
202
|
+
|
|
203
|
+
if copy_it:
|
|
204
|
+
copy_file(src, dst, verbose=verbose)
|
|
205
|
+
try:
|
|
206
|
+
os.unlink(src)
|
|
207
|
+
except OSError as e:
|
|
208
|
+
(num, msg) = e.args
|
|
209
|
+
with contextlib.suppress(OSError):
|
|
210
|
+
os.unlink(dst)
|
|
211
|
+
raise DistutilsFileError(
|
|
212
|
+
f"couldn't move '{src}' to '{dst}' by copy/delete: "
|
|
213
|
+
f"delete '{src}' failed: {msg}",
|
|
214
|
+
) from None
|
|
215
|
+
|
|
216
|
+
return dst
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Timestamp comparison of files and groups of files."""
|
|
2
|
+
import os.path
|
|
3
|
+
|
|
4
|
+
from .errors import DistutilsFileError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _newer(source, target):
|
|
8
|
+
return not os.path.exists(target) or (os.path.getmtime(source) > os.path.getmtime(target))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def newer(source, target):
|
|
12
|
+
"""
|
|
13
|
+
Is source modified more recently than target.
|
|
14
|
+
|
|
15
|
+
Returns True if 'source' is modified more recently than 'target' or if 'target' does not exist.
|
|
16
|
+
|
|
17
|
+
Raises DistutilsFileError if 'source' does not exist.
|
|
18
|
+
"""
|
|
19
|
+
if not os.path.exists(source):
|
|
20
|
+
raise DistutilsFileError(f"file '{os.path.abspath(source)}' does not exist")
|
|
21
|
+
|
|
22
|
+
return _newer(source, target)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def newer_group(sources, target, missing='error'):
|
|
26
|
+
"""
|
|
27
|
+
Is target out-of-date with respect to any file in sources.
|
|
28
|
+
|
|
29
|
+
Return True if 'target' is out-of-date with respect to any file listed in 'sources'. In other words, if 'target'
|
|
30
|
+
exists and is newer than every file in 'sources', return False; otherwise return True. ``missing`` controls how to
|
|
31
|
+
handle a missing source file:
|
|
32
|
+
|
|
33
|
+
- error (default): allow the ``stat()`` call to fail.
|
|
34
|
+
- ignore: silently disregard any missing source files.
|
|
35
|
+
- newer: treat missing source files as "target out of date". This mode is handy in "dry-run" mode: it will pretend
|
|
36
|
+
to carry out commands that wouldn't work because inputs are missing, but that doesn't matter because dry-run won't
|
|
37
|
+
run the commands.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def missing_as_newer(source):
|
|
41
|
+
return missing == 'newer' and not os.path.exists(source)
|
|
42
|
+
|
|
43
|
+
ignored = os.path.exists if missing == 'ignore' else None
|
|
44
|
+
return any(
|
|
45
|
+
missing_as_newer(source) or _newer(source, target)
|
|
46
|
+
for source in filter(ignored, sources)
|
|
47
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provides the 'spawn()' function, a front-end to various platform-
|
|
3
|
+
specific functions for launching another program in a sub-process.
|
|
4
|
+
Also provides the 'find_executable()' to search the path for a given
|
|
5
|
+
executable name.
|
|
6
|
+
"""
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from .errors import DistutilsExecError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def spawn(cmd, search_path=1, verbose=0, dry_run=False, env=None, debug=False): # noqa: C901
|
|
19
|
+
"""
|
|
20
|
+
Run another program, specified as a command list 'cmd', in a new process.
|
|
21
|
+
|
|
22
|
+
'cmd' is just the argument list for the new process, ie. cmd[0] is the program to run and cmd[1:] are the rest of
|
|
23
|
+
its arguments. There is no way to run a program with a name different from that of its executable.
|
|
24
|
+
|
|
25
|
+
If 'search_path' is true (the default), the system's executable search path will be used to find the program;
|
|
26
|
+
otherwise, cmd[0] must be the exact path to the executable. If 'dry_run' is true, the command will not actually be
|
|
27
|
+
run.
|
|
28
|
+
|
|
29
|
+
Raise DistutilsExecError if running the program fails in any way; just return on success.
|
|
30
|
+
"""
|
|
31
|
+
# cmd is documented as a list, but just in case some code passes a tuple in, protect our %-formatting code against
|
|
32
|
+
# horrible death
|
|
33
|
+
cmd = list(cmd)
|
|
34
|
+
|
|
35
|
+
log.info(subprocess.list2cmdline(cmd))
|
|
36
|
+
if dry_run:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
if search_path:
|
|
40
|
+
executable = find_executable(cmd[0])
|
|
41
|
+
if executable is not None:
|
|
42
|
+
cmd[0] = executable
|
|
43
|
+
|
|
44
|
+
env = env if env is not None else dict(os.environ)
|
|
45
|
+
|
|
46
|
+
if sys.platform == 'darwin':
|
|
47
|
+
from .util import MACOSX_VERSION_VAR
|
|
48
|
+
from .util import get_macosx_target_ver
|
|
49
|
+
|
|
50
|
+
macosx_target_ver = get_macosx_target_ver()
|
|
51
|
+
if macosx_target_ver:
|
|
52
|
+
env[MACOSX_VERSION_VAR] = macosx_target_ver
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
proc = subprocess.Popen(cmd, env=env)
|
|
56
|
+
proc.wait()
|
|
57
|
+
exitcode = proc.returncode
|
|
58
|
+
except OSError as exc:
|
|
59
|
+
if not debug:
|
|
60
|
+
cmd = cmd[0]
|
|
61
|
+
raise DistutilsExecError(f'command {cmd!r} failed: {exc.args[-1]}') from exc
|
|
62
|
+
|
|
63
|
+
if exitcode:
|
|
64
|
+
if not debug:
|
|
65
|
+
cmd = cmd[0]
|
|
66
|
+
raise DistutilsExecError(f'command {cmd!r} failed with exit code {exitcode}')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def find_executable(executable, path=None):
|
|
70
|
+
"""
|
|
71
|
+
Tries to find 'executable' in the directories listed in 'path'.
|
|
72
|
+
|
|
73
|
+
A string listing directories separated by 'os.pathsep'; defaults to os.environ['PATH']. Returns the complete
|
|
74
|
+
filename or None if not found.
|
|
75
|
+
"""
|
|
76
|
+
_, ext = os.path.splitext(executable)
|
|
77
|
+
if (sys.platform == 'win32') and (ext != '.exe'):
|
|
78
|
+
executable = executable + '.exe'
|
|
79
|
+
|
|
80
|
+
if os.path.isfile(executable):
|
|
81
|
+
return executable
|
|
82
|
+
|
|
83
|
+
if path is None:
|
|
84
|
+
path = os.environ.get('PATH', None)
|
|
85
|
+
if path is None:
|
|
86
|
+
try:
|
|
87
|
+
path = os.confstr('CS_PATH')
|
|
88
|
+
except (AttributeError, ValueError):
|
|
89
|
+
# os.confstr() or CS_PATH is not available
|
|
90
|
+
path = os.defpath
|
|
91
|
+
# bpo-35755: Don't use os.defpath if the PATH environment variable is set to an empty string
|
|
92
|
+
|
|
93
|
+
# PATH='' doesn't match, whereas PATH=':' looks in the current directory
|
|
94
|
+
if not path:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
paths = path.split(os.pathsep)
|
|
98
|
+
for p in paths:
|
|
99
|
+
f = os.path.join(p, executable)
|
|
100
|
+
if os.path.isfile(f):
|
|
101
|
+
# the file exists, we have a shot at spawn working
|
|
102
|
+
return f
|
|
103
|
+
return None
|