spin 0.11__tar.gz → 0.12__tar.gz
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.
- {spin-0.11/spin.egg-info → spin-0.12}/PKG-INFO +28 -13
- {spin-0.11 → spin-0.12}/README.md +26 -11
- {spin-0.11 → spin-0.12}/pyproject.toml +13 -5
- {spin-0.11 → spin-0.12}/spin/__init__.py +1 -1
- {spin-0.11 → spin-0.12}/spin/__main__.py +47 -10
- spin-0.12/spin/cmds/build.py +15 -0
- {spin-0.11 → spin-0.12}/spin/cmds/meson.py +99 -48
- spin-0.12/spin/cmds/meta.py +60 -0
- {spin-0.11 → spin-0.12}/spin/cmds/pip.py +7 -1
- {spin-0.11 → spin-0.12}/spin/cmds/util.py +5 -2
- {spin-0.11 → spin-0.12}/spin/color_format.py +1 -1
- {spin-0.11 → spin-0.12}/spin/tests/test_build_cmds.py +28 -1
- spin-0.12/spin/tests/test_meson.py +109 -0
- {spin-0.11 → spin-0.12}/spin/tests/testutil.py +4 -0
- {spin-0.11 → spin-0.12/spin.egg-info}/PKG-INFO +28 -13
- {spin-0.11 → spin-0.12}/spin.egg-info/SOURCES.txt +1 -0
- {spin-0.11 → spin-0.12}/spin.egg-info/requires.txt +1 -1
- spin-0.11/spin/cmds/build.py +0 -9
- spin-0.11/spin/tests/test_meson.py +0 -116
- {spin-0.11 → spin-0.12}/LICENSE +0 -0
- {spin-0.11 → spin-0.12}/setup.cfg +0 -0
- {spin-0.11 → spin-0.12}/spin/cmds/__init__.py +0 -0
- {spin-0.11 → spin-0.12}/spin/containers.py +0 -0
- {spin-0.11 → spin-0.12}/spin/sectioned_help.py +0 -0
- {spin-0.11 → spin-0.12}/spin/tests/conftest.py +0 -0
- {spin-0.11 → spin-0.12}/spin/tests/test_cli.py +0 -0
- {spin-0.11 → spin-0.12}/spin/tests/test_editable.py +0 -0
- {spin-0.11 → spin-0.12}/spin/tests/test_test.py +0 -0
- {spin-0.11 → spin-0.12}/spin.egg-info/dependency_links.txt +0 -0
- {spin-0.11 → spin-0.12}/spin.egg-info/entry_points.txt +0 -0
- {spin-0.11 → spin-0.12}/spin.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: spin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12
|
|
4
4
|
Summary: Developer tool for scientific Python libraries
|
|
5
5
|
Maintainer-email: Scientific Python <spin@discuss.scientific-python.org>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -55,7 +55,7 @@ Requires-Dist: tomli; python_version < "3.11"
|
|
|
55
55
|
Requires-Dist: colorama; platform_system == "Windows"
|
|
56
56
|
Requires-Dist: importlib_metadata>=7
|
|
57
57
|
Provides-Extra: lint
|
|
58
|
-
Requires-Dist: pre-commit==3.
|
|
58
|
+
Requires-Dist: pre-commit==3.8.0; extra == "lint"
|
|
59
59
|
Provides-Extra: dev
|
|
60
60
|
Requires-Dist: changelist==0.5; extra == "dev"
|
|
61
61
|
|
|
@@ -147,15 +147,15 @@ python -m spin
|
|
|
147
147
|
Available as `spin.cmds.meson.*`.
|
|
148
148
|
|
|
149
149
|
```
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
build 🔧 Build package with Meson/ninja
|
|
151
|
+
ipython 💻 Launch IPython shell with PYTHONPATH set
|
|
152
|
+
python 🐍 Launch Python shell with PYTHONPATH set
|
|
153
|
+
shell 💻 Launch shell with PYTHONPATH set
|
|
154
|
+
test 🔧 Run pytest
|
|
155
|
+
run 🏁 Run a shell command with PYTHONPATH set
|
|
156
|
+
docs 📖 Build Sphinx documentation
|
|
157
|
+
gdb 👾 Execute a Python snippet with GDB
|
|
158
|
+
lldb 👾 Execute a Python snippet with LLDB
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
### [Build](https://pypa-build.readthedocs.io/en/stable/) (PEP 517 builder)
|
|
@@ -163,7 +163,7 @@ Available as `spin.cmds.meson.*`.
|
|
|
163
163
|
Available as `spin.cmds.build.*`:
|
|
164
164
|
|
|
165
165
|
```
|
|
166
|
-
|
|
166
|
+
sdist 📦 Build a source distribution in `dist/`
|
|
167
167
|
```
|
|
168
168
|
|
|
169
169
|
### [pip](https://pip.pypa.io) (Package Installer for Python)
|
|
@@ -174,7 +174,15 @@ development workflow.
|
|
|
174
174
|
Available as `spin.cmds.pip.*`:
|
|
175
175
|
|
|
176
176
|
```
|
|
177
|
-
|
|
177
|
+
install 💽 Build and install package using pip.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Meta (commands that operate on commands)
|
|
181
|
+
|
|
182
|
+
Available as `spin.cmds.meta.*`:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
introspect 🔍 Print a command's location and source code
|
|
178
186
|
```
|
|
179
187
|
|
|
180
188
|
## 🧪 Custom commands
|
|
@@ -310,6 +318,13 @@ fc-cache -f -v
|
|
|
310
318
|
nox -s test
|
|
311
319
|
```
|
|
312
320
|
|
|
321
|
+
Other examples:
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
nox -s test -- -v
|
|
325
|
+
nox -s test -- -v spin/tests/test_meson.py
|
|
326
|
+
```
|
|
327
|
+
|
|
313
328
|
## History
|
|
314
329
|
|
|
315
330
|
The `dev.py` tool was [proposed for SciPy](https://github.com/scipy/scipy/issues/15489) by Ralf Gommers and [implemented](https://github.com/scipy/scipy/pull/15959) by Sayantika Banik, Eduardo Naufel Schettino, and Ralf Gommers (also see [Sayantika's blog post](https://labs.quansight.org/blog/the-evolution-of-the-scipy-developer-cli)).
|
|
@@ -86,15 +86,15 @@ python -m spin
|
|
|
86
86
|
Available as `spin.cmds.meson.*`.
|
|
87
87
|
|
|
88
88
|
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
build 🔧 Build package with Meson/ninja
|
|
90
|
+
ipython 💻 Launch IPython shell with PYTHONPATH set
|
|
91
|
+
python 🐍 Launch Python shell with PYTHONPATH set
|
|
92
|
+
shell 💻 Launch shell with PYTHONPATH set
|
|
93
|
+
test 🔧 Run pytest
|
|
94
|
+
run 🏁 Run a shell command with PYTHONPATH set
|
|
95
|
+
docs 📖 Build Sphinx documentation
|
|
96
|
+
gdb 👾 Execute a Python snippet with GDB
|
|
97
|
+
lldb 👾 Execute a Python snippet with LLDB
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
### [Build](https://pypa-build.readthedocs.io/en/stable/) (PEP 517 builder)
|
|
@@ -102,7 +102,7 @@ Available as `spin.cmds.meson.*`.
|
|
|
102
102
|
Available as `spin.cmds.build.*`:
|
|
103
103
|
|
|
104
104
|
```
|
|
105
|
-
|
|
105
|
+
sdist 📦 Build a source distribution in `dist/`
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
### [pip](https://pip.pypa.io) (Package Installer for Python)
|
|
@@ -113,7 +113,15 @@ development workflow.
|
|
|
113
113
|
Available as `spin.cmds.pip.*`:
|
|
114
114
|
|
|
115
115
|
```
|
|
116
|
-
|
|
116
|
+
install 💽 Build and install package using pip.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Meta (commands that operate on commands)
|
|
120
|
+
|
|
121
|
+
Available as `spin.cmds.meta.*`:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
introspect 🔍 Print a command's location and source code
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
## 🧪 Custom commands
|
|
@@ -249,6 +257,13 @@ fc-cache -f -v
|
|
|
249
257
|
nox -s test
|
|
250
258
|
```
|
|
251
259
|
|
|
260
|
+
Other examples:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
nox -s test -- -v
|
|
264
|
+
nox -s test -- -v spin/tests/test_meson.py
|
|
265
|
+
```
|
|
266
|
+
|
|
252
267
|
## History
|
|
253
268
|
|
|
254
269
|
The `dev.py` tool was [proposed for SciPy](https://github.com/scipy/scipy/issues/15489) by Ralf Gommers and [implemented](https://github.com/scipy/scipy/pull/15959) by Sayantika Banik, Eduardo Naufel Schettino, and Ralf Gommers (also see [Sayantika's blog post](https://labs.quansight.org/blog/the-evolution-of-the-scipy-developer-cli)).
|
|
@@ -34,12 +34,15 @@ dynamic = ['version']
|
|
|
34
34
|
spin = "spin.__main__:main"
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
|
-
lint = ["pre-commit == 3.
|
|
37
|
+
lint = ["pre-commit == 3.8.0"]
|
|
38
38
|
dev = ["changelist == 0.5"]
|
|
39
39
|
|
|
40
40
|
[project.urls]
|
|
41
41
|
homepage = "https://github.com/scientific-python/spin"
|
|
42
42
|
|
|
43
|
+
[tool.changelist]
|
|
44
|
+
ignored_user_logins = ["dependabot[bot]", "pre-commit-ci[bot]", "web-flow"]
|
|
45
|
+
|
|
43
46
|
[tool.setuptools.dynamic.version]
|
|
44
47
|
attr = 'spin.__version__'
|
|
45
48
|
|
|
@@ -47,13 +50,13 @@ attr = 'spin.__version__'
|
|
|
47
50
|
include = ["spin*"]
|
|
48
51
|
|
|
49
52
|
[tool.pytest.ini_options]
|
|
53
|
+
minversion = "8.0"
|
|
54
|
+
addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
|
|
55
|
+
xfail_strict = true
|
|
50
56
|
filterwarnings = [
|
|
51
57
|
"ignore:cannot collect 'test' because it is not a function:",
|
|
52
58
|
]
|
|
53
|
-
|
|
54
|
-
[tool.ruff]
|
|
55
|
-
line-length = 88
|
|
56
|
-
target-version = "py38"
|
|
59
|
+
log_cli_level = "info"
|
|
57
60
|
|
|
58
61
|
[tool.ruff.lint]
|
|
59
62
|
select = [
|
|
@@ -63,6 +66,11 @@ select = [
|
|
|
63
66
|
"W",
|
|
64
67
|
"B",
|
|
65
68
|
"I",
|
|
69
|
+
"C4",
|
|
70
|
+
"ISC",
|
|
71
|
+
"PGH",
|
|
66
72
|
"UP",
|
|
73
|
+
"FURB",
|
|
74
|
+
"PYI",
|
|
67
75
|
]
|
|
68
76
|
ignore = ["B006", "C901", "E501", "E722"]
|
|
@@ -2,9 +2,11 @@ import collections
|
|
|
2
2
|
import importlib
|
|
3
3
|
import importlib.util
|
|
4
4
|
import os
|
|
5
|
+
import pathlib
|
|
5
6
|
import sys
|
|
6
7
|
import textwrap
|
|
7
8
|
import traceback
|
|
9
|
+
from typing import Union
|
|
8
10
|
|
|
9
11
|
import click
|
|
10
12
|
|
|
@@ -19,8 +21,26 @@ if sys.version_info >= (3, 11):
|
|
|
19
21
|
else:
|
|
20
22
|
import tomli as tomllib
|
|
21
23
|
|
|
24
|
+
|
|
22
25
|
click.Context.formatter_class = ColorHelpFormatter
|
|
23
26
|
|
|
27
|
+
config_filenames = (
|
|
28
|
+
".spin.toml",
|
|
29
|
+
"spin.toml",
|
|
30
|
+
"pyproject.toml",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _detect_config_dir(path: pathlib.Path) -> Union[pathlib.Path, None]:
|
|
35
|
+
path = path.resolve()
|
|
36
|
+
files = os.listdir(path)
|
|
37
|
+
if any(f in files for f in config_filenames):
|
|
38
|
+
return path
|
|
39
|
+
elif path.parent != path:
|
|
40
|
+
return _detect_config_dir(path.parent)
|
|
41
|
+
else:
|
|
42
|
+
return None
|
|
43
|
+
|
|
24
44
|
|
|
25
45
|
def main():
|
|
26
46
|
# Alias `spin help` to `spin --help`
|
|
@@ -38,15 +58,25 @@ def main():
|
|
|
38
58
|
|
|
39
59
|
toml_config = collections.ChainMap()
|
|
40
60
|
toml_config.maps.extend(
|
|
41
|
-
DotDict(cfg)
|
|
42
|
-
for filename in (
|
|
43
|
-
".spin.toml",
|
|
44
|
-
"spin.toml",
|
|
45
|
-
"pyproject.toml",
|
|
46
|
-
)
|
|
47
|
-
if (cfg := load_toml(filename))
|
|
61
|
+
DotDict(cfg) for filename in config_filenames if (cfg := load_toml(filename))
|
|
48
62
|
)
|
|
49
63
|
|
|
64
|
+
if not toml_config:
|
|
65
|
+
click.secho(
|
|
66
|
+
f"Could not load configuration from one of: {', '.join(config_filenames)}",
|
|
67
|
+
file=sys.stderr,
|
|
68
|
+
fg="red",
|
|
69
|
+
)
|
|
70
|
+
config_dir = _detect_config_dir(pathlib.Path("."))
|
|
71
|
+
if config_dir:
|
|
72
|
+
print()
|
|
73
|
+
print(
|
|
74
|
+
"Are you running `spin` from the correct directory? Perhaps you'd like to\n"
|
|
75
|
+
)
|
|
76
|
+
click.secho(f" $ cd {os.path.relpath(config_dir, '.')}\n")
|
|
77
|
+
print("and try again.")
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
50
80
|
# Basic configuration validation
|
|
51
81
|
version_query = len(sys.argv) == 2 and (sys.argv[1] == "--version")
|
|
52
82
|
|
|
@@ -55,15 +85,18 @@ def main():
|
|
|
55
85
|
if "tool.spin" in toml_config:
|
|
56
86
|
spin_config = toml_config["tool.spin"]
|
|
57
87
|
if "tool.spin.commands" not in toml_config:
|
|
58
|
-
|
|
59
|
-
"Error: configuration is missing section [tool.spin.commands]\n"
|
|
88
|
+
click.secho(
|
|
89
|
+
"Error: configuration is missing section [tool.spin.commands]\n"
|
|
90
|
+
"See https://github.com/scientific-python/spin/blob/main/README.md\n",
|
|
60
91
|
file=sys.stderr,
|
|
92
|
+
fg="red",
|
|
61
93
|
)
|
|
62
94
|
else:
|
|
63
|
-
|
|
95
|
+
click.secho(
|
|
64
96
|
"Error: need valid configuration in [.spin.toml], [spin.toml], or [pyproject.toml]\n"
|
|
65
97
|
"See https://github.com/scientific-python/spin/blob/main/README.md\n",
|
|
66
98
|
file=sys.stderr,
|
|
99
|
+
fg="red",
|
|
67
100
|
)
|
|
68
101
|
|
|
69
102
|
proj_name = (
|
|
@@ -132,10 +165,14 @@ def main():
|
|
|
132
165
|
|
|
133
166
|
try:
|
|
134
167
|
cmd_func = getattr(mod, func)
|
|
168
|
+
cmd_func.module = mod # metadata for use by `introspect` command
|
|
135
169
|
except AttributeError:
|
|
136
170
|
print(f"!! Could not load command `{func}` from file `{path}`.\n")
|
|
137
171
|
continue
|
|
138
172
|
|
|
173
|
+
# Save command definition for use in `introspect`
|
|
174
|
+
cmd_func.spec = cmd
|
|
175
|
+
|
|
139
176
|
default_kwargs = cmd_default_kwargs.get(cmd)
|
|
140
177
|
import functools
|
|
141
178
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from .util import run
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.argument("pyproject-build-args", metavar="", nargs=-1)
|
|
8
|
+
def sdist(pyproject_build_args):
|
|
9
|
+
"""📦 Build a source distribution in `dist/`
|
|
10
|
+
|
|
11
|
+
Extra arguments are passed to `pyproject-build`, e.g.
|
|
12
|
+
|
|
13
|
+
spin sdist -- -x -n
|
|
14
|
+
"""
|
|
15
|
+
run(["pyproject-build", ".", "--sdist"] + list(pyproject_build_args))
|
|
@@ -13,9 +13,6 @@ import click
|
|
|
13
13
|
from .util import get_commands, get_config
|
|
14
14
|
from .util import run as _run
|
|
15
15
|
|
|
16
|
-
install_dir = "build-install"
|
|
17
|
-
build_dir = "build"
|
|
18
|
-
|
|
19
16
|
|
|
20
17
|
class GcovReportFormat(str, Enum):
|
|
21
18
|
html = "html"
|
|
@@ -68,16 +65,15 @@ def _is_editable_install_of_same_source(distname):
|
|
|
68
65
|
return editable_path and os.path.samefile(_editable_install_path(distname), ".")
|
|
69
66
|
|
|
70
67
|
|
|
71
|
-
def _set_pythonpath(quiet=False):
|
|
68
|
+
def _set_pythonpath(build_dir, quiet=False):
|
|
72
69
|
"""Set first entry of PYTHONPATH to site packages directory.
|
|
73
70
|
|
|
71
|
+
For editable installs, leave the PYTHONPATH alone.
|
|
72
|
+
|
|
74
73
|
Returns
|
|
75
74
|
-------
|
|
76
75
|
site_packages
|
|
77
76
|
"""
|
|
78
|
-
site_packages = _get_site_packages()
|
|
79
|
-
env = os.environ
|
|
80
|
-
|
|
81
77
|
cfg = get_config()
|
|
82
78
|
distname = cfg.get("project.name", None)
|
|
83
79
|
if distname:
|
|
@@ -101,6 +97,9 @@ def _set_pythonpath(quiet=False):
|
|
|
101
97
|
fg="bright_red",
|
|
102
98
|
)
|
|
103
99
|
|
|
100
|
+
site_packages = _get_site_packages(build_dir)
|
|
101
|
+
env = os.environ
|
|
102
|
+
|
|
104
103
|
if "PYTHONPATH" in env:
|
|
105
104
|
env["PYTHONPATH"] = f"{site_packages}{os.pathsep}{env['PYTHONPATH']}"
|
|
106
105
|
else:
|
|
@@ -114,7 +113,12 @@ def _set_pythonpath(quiet=False):
|
|
|
114
113
|
return site_packages
|
|
115
114
|
|
|
116
115
|
|
|
117
|
-
def
|
|
116
|
+
def _get_install_dir(build_dir):
|
|
117
|
+
return f"{build_dir}-install"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _get_site_packages(build_dir):
|
|
121
|
+
install_dir = _get_install_dir(build_dir)
|
|
118
122
|
try:
|
|
119
123
|
cfg = get_config()
|
|
120
124
|
distname = cfg.get("project.name", None)
|
|
@@ -168,9 +172,9 @@ def _meson_version():
|
|
|
168
172
|
pass
|
|
169
173
|
|
|
170
174
|
|
|
171
|
-
def _meson_version_configured():
|
|
175
|
+
def _meson_version_configured(build_dir):
|
|
172
176
|
try:
|
|
173
|
-
meson_info_fn = os.path.join(
|
|
177
|
+
meson_info_fn = os.path.join(build_dir, "meson-info", "meson-info.json")
|
|
174
178
|
with open(meson_info_fn) as f:
|
|
175
179
|
meson_info = json.load(f)
|
|
176
180
|
return meson_info["meson_version"]["full"]
|
|
@@ -194,7 +198,7 @@ def _meson_coverage_configured() -> bool:
|
|
|
194
198
|
return False
|
|
195
199
|
|
|
196
200
|
|
|
197
|
-
def _check_coverage_tool_installation(coverage_type: GcovReportFormat):
|
|
201
|
+
def _check_coverage_tool_installation(coverage_type: GcovReportFormat, build_dir):
|
|
198
202
|
requirements = { # https://github.com/mesonbuild/meson/blob/6e381714c7cb15877e2bcaa304b93c212252ada3/docs/markdown/Unit-tests.md?plain=1#L49-L62
|
|
199
203
|
GcovReportFormat.html: ["Gcovr/GenHTML", "lcov"],
|
|
200
204
|
GcovReportFormat.xml: ["Gcovr (version 3.3 or higher)"],
|
|
@@ -226,6 +230,16 @@ def _check_coverage_tool_installation(coverage_type: GcovReportFormat):
|
|
|
226
230
|
)
|
|
227
231
|
|
|
228
232
|
|
|
233
|
+
build_dir_option = click.option(
|
|
234
|
+
"-C",
|
|
235
|
+
"--build-dir",
|
|
236
|
+
default="build",
|
|
237
|
+
show_envvar=True,
|
|
238
|
+
envvar="SPIN_BUILD_DIR",
|
|
239
|
+
help="Meson build directory; package is installed into './{build-dir}-install'.",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
229
243
|
@click.command()
|
|
230
244
|
@click.option("-j", "--jobs", help="Number of parallel tasks to launch", type=int)
|
|
231
245
|
@click.option("--clean", is_flag=True, help="Clean build directory before build")
|
|
@@ -238,12 +252,21 @@ def _check_coverage_tool_installation(coverage_type: GcovReportFormat):
|
|
|
238
252
|
help="Enable C code coverage using `gcov`. Use `spin test --gcov` to generate reports.",
|
|
239
253
|
)
|
|
240
254
|
@click.argument("meson_args", nargs=-1)
|
|
255
|
+
@build_dir_option
|
|
241
256
|
def build(
|
|
242
|
-
*,
|
|
257
|
+
*,
|
|
258
|
+
meson_args,
|
|
259
|
+
jobs=None,
|
|
260
|
+
clean=False,
|
|
261
|
+
verbose=False,
|
|
262
|
+
gcov=False,
|
|
263
|
+
quiet=False,
|
|
264
|
+
build_dir=None,
|
|
243
265
|
):
|
|
244
266
|
"""🔧 Build package with Meson/ninja
|
|
245
267
|
|
|
246
|
-
The package is installed to `build-install
|
|
268
|
+
The package is installed to `build-install` (unless a different
|
|
269
|
+
build directory is specified with `-C`).
|
|
247
270
|
|
|
248
271
|
MESON_ARGS are passed through e.g.:
|
|
249
272
|
|
|
@@ -257,7 +280,23 @@ def build(
|
|
|
257
280
|
or set CFLAGS appropriately:
|
|
258
281
|
|
|
259
282
|
CFLAGS="-O0 -g" spin build
|
|
283
|
+
|
|
284
|
+
Build into a different build/build-install directory via the
|
|
285
|
+
`-C/--build-dir` flag:
|
|
286
|
+
|
|
287
|
+
spin build -C build-for-feature-x
|
|
288
|
+
|
|
289
|
+
This feature is useful in combination with a shell alias, e.g.:
|
|
290
|
+
|
|
291
|
+
$ alias spin-clang="SPIN_BUILD_DIR=build-clang CC=clang spin"
|
|
292
|
+
|
|
293
|
+
Which can then be used to build (`spin-clang build`), to test (`spin-clang test ...`), etc.
|
|
294
|
+
|
|
260
295
|
"""
|
|
296
|
+
abs_build_dir = os.path.abspath(build_dir)
|
|
297
|
+
install_dir = _get_install_dir(build_dir)
|
|
298
|
+
abs_install_dir = os.path.abspath(install_dir)
|
|
299
|
+
|
|
261
300
|
cfg = get_config()
|
|
262
301
|
distname = cfg.get("project.name", None)
|
|
263
302
|
if distname and _is_editable_install_of_same_source(distname):
|
|
@@ -283,7 +322,7 @@ def build(
|
|
|
283
322
|
if os.path.isdir(install_dir):
|
|
284
323
|
shutil.rmtree(install_dir)
|
|
285
324
|
|
|
286
|
-
if not (os.path.exists(build_dir) and _meson_version_configured()):
|
|
325
|
+
if not (os.path.exists(build_dir) and _meson_version_configured(build_dir)):
|
|
287
326
|
p = _run(setup_cmd, sys_exit=False, output=not quiet)
|
|
288
327
|
if p.returncode != 0:
|
|
289
328
|
raise RuntimeError(
|
|
@@ -293,7 +332,7 @@ def build(
|
|
|
293
332
|
# Build dir has been configured; check if it was configured by
|
|
294
333
|
# current version of Meson
|
|
295
334
|
|
|
296
|
-
if (_meson_version() != _meson_version_configured()) or (
|
|
335
|
+
if (_meson_version() != _meson_version_configured(build_dir)) or (
|
|
297
336
|
gcov and not _meson_coverage_configured()
|
|
298
337
|
):
|
|
299
338
|
_run(setup_cmd + ["--reconfigure"], output=not quiet)
|
|
@@ -301,6 +340,9 @@ def build(
|
|
|
301
340
|
# Any other conditions that warrant a reconfigure?
|
|
302
341
|
|
|
303
342
|
compile_flags = ["-v"] if verbose else []
|
|
343
|
+
if jobs:
|
|
344
|
+
compile_flags += ["-j", str(jobs)]
|
|
345
|
+
|
|
304
346
|
p = _run(
|
|
305
347
|
_meson_cli() + ["compile"] + compile_flags + ["-C", build_dir],
|
|
306
348
|
sys_exit=True,
|
|
@@ -314,7 +356,9 @@ def build(
|
|
|
314
356
|
"-C",
|
|
315
357
|
build_dir,
|
|
316
358
|
"--destdir",
|
|
317
|
-
|
|
359
|
+
install_dir
|
|
360
|
+
if os.path.isabs(install_dir)
|
|
361
|
+
else os.path.relpath(abs_install_dir, abs_build_dir),
|
|
318
362
|
],
|
|
319
363
|
output=(not quiet) and verbose,
|
|
320
364
|
)
|
|
@@ -334,7 +378,7 @@ def _get_configured_command(command_name):
|
|
|
334
378
|
metavar="N_JOBS",
|
|
335
379
|
default="1",
|
|
336
380
|
help=(
|
|
337
|
-
"Number of parallel jobs for testing.
|
|
381
|
+
"Number of parallel jobs for testing. Can be set to `auto` to use all cores."
|
|
338
382
|
),
|
|
339
383
|
)
|
|
340
384
|
@click.option(
|
|
@@ -372,6 +416,7 @@ Which tests to run. Can be a module, function, class, or method:
|
|
|
372
416
|
default="html",
|
|
373
417
|
help=f"Format of the gcov report. Can be one of {', '.join(e.value for e in GcovReportFormat)}.",
|
|
374
418
|
)
|
|
419
|
+
@build_dir_option
|
|
375
420
|
@click.pass_context
|
|
376
421
|
def test(
|
|
377
422
|
ctx,
|
|
@@ -383,6 +428,7 @@ def test(
|
|
|
383
428
|
coverage=False,
|
|
384
429
|
gcov=None,
|
|
385
430
|
gcov_format=None,
|
|
431
|
+
build_dir=None,
|
|
386
432
|
):
|
|
387
433
|
"""🔧 Run tests
|
|
388
434
|
|
|
@@ -420,10 +466,6 @@ def test(
|
|
|
420
466
|
|
|
421
467
|
spin test -j auto
|
|
422
468
|
|
|
423
|
-
To run a test inside of LLDB:
|
|
424
|
-
|
|
425
|
-
spin lldb -- python -mspin test ...
|
|
426
|
-
|
|
427
469
|
For more, see `pytest --help`.
|
|
428
470
|
""" # noqa: E501
|
|
429
471
|
cfg = get_config()
|
|
@@ -469,13 +511,11 @@ def test(
|
|
|
469
511
|
"Invoking `build` prior to running tests:", bold=True, fg="bright_green"
|
|
470
512
|
)
|
|
471
513
|
if gcov is not None:
|
|
472
|
-
ctx.invoke(build_cmd, gcov=bool(gcov))
|
|
514
|
+
ctx.invoke(build_cmd, build_dir=build_dir, gcov=bool(gcov))
|
|
473
515
|
else:
|
|
474
|
-
ctx.invoke(build_cmd)
|
|
516
|
+
ctx.invoke(build_cmd, build_dir=build_dir)
|
|
475
517
|
|
|
476
|
-
site_path = _set_pythonpath()
|
|
477
|
-
if site_path:
|
|
478
|
-
print(f'$ export PYTHONPATH="{site_path}"')
|
|
518
|
+
site_path = _set_pythonpath(build_dir)
|
|
479
519
|
|
|
480
520
|
# Sanity check that library built properly
|
|
481
521
|
#
|
|
@@ -520,6 +560,7 @@ def test(
|
|
|
520
560
|
else:
|
|
521
561
|
cmd = ["pytest"]
|
|
522
562
|
|
|
563
|
+
install_dir = _get_install_dir(build_dir)
|
|
523
564
|
if not os.path.exists(install_dir):
|
|
524
565
|
os.mkdir(install_dir)
|
|
525
566
|
|
|
@@ -534,7 +575,7 @@ def test(
|
|
|
534
575
|
bold=True,
|
|
535
576
|
fg="bright_yellow",
|
|
536
577
|
)
|
|
537
|
-
_check_coverage_tool_installation(gcov_format)
|
|
578
|
+
_check_coverage_tool_installation(gcov_format, build_dir)
|
|
538
579
|
|
|
539
580
|
# Generate report
|
|
540
581
|
click.secho(
|
|
@@ -568,8 +609,9 @@ def test(
|
|
|
568
609
|
@click.command()
|
|
569
610
|
@click.option("--code", "-c", help="Python program passed in as a string")
|
|
570
611
|
@click.argument("gdb_args", nargs=-1)
|
|
612
|
+
@build_dir_option
|
|
571
613
|
@click.pass_context
|
|
572
|
-
def gdb(ctx, *, code, gdb_args):
|
|
614
|
+
def gdb(ctx, *, code, gdb_args, build_dir):
|
|
573
615
|
"""👾 Execute code through GDB
|
|
574
616
|
|
|
575
617
|
spin gdb -c 'import numpy as np; print(np.__version__)'
|
|
@@ -595,9 +637,9 @@ def gdb(ctx, *, code, gdb_args):
|
|
|
595
637
|
click.secho(
|
|
596
638
|
"Invoking `build` prior to invoking gdb:", bold=True, fg="bright_green"
|
|
597
639
|
)
|
|
598
|
-
ctx.invoke(build_cmd)
|
|
640
|
+
ctx.invoke(build_cmd, build_dir=build_dir)
|
|
599
641
|
|
|
600
|
-
_set_pythonpath()
|
|
642
|
+
_set_pythonpath(build_dir)
|
|
601
643
|
gdb_args = list(gdb_args)
|
|
602
644
|
|
|
603
645
|
if gdb_args and gdb_args[0].endswith(".py"):
|
|
@@ -620,8 +662,9 @@ def gdb(ctx, *, code, gdb_args):
|
|
|
620
662
|
|
|
621
663
|
@click.command()
|
|
622
664
|
@click.argument("ipython_args", nargs=-1)
|
|
665
|
+
@build_dir_option
|
|
623
666
|
@click.pass_context
|
|
624
|
-
def ipython(ctx, *, ipython_args):
|
|
667
|
+
def ipython(ctx, *, ipython_args, build_dir):
|
|
625
668
|
"""💻 Launch IPython shell with PYTHONPATH set
|
|
626
669
|
|
|
627
670
|
IPYTHON_ARGS are passed through directly to IPython, e.g.:
|
|
@@ -633,9 +676,9 @@ def ipython(ctx, *, ipython_args):
|
|
|
633
676
|
click.secho(
|
|
634
677
|
"Invoking `build` prior to invoking ipython:", bold=True, fg="bright_green"
|
|
635
678
|
)
|
|
636
|
-
ctx.invoke(build_cmd)
|
|
679
|
+
ctx.invoke(build_cmd, build_dir=build_dir)
|
|
637
680
|
|
|
638
|
-
p = _set_pythonpath()
|
|
681
|
+
p = _set_pythonpath(build_dir)
|
|
639
682
|
if p:
|
|
640
683
|
print(f'💻 Launching IPython with PYTHONPATH="{p}"')
|
|
641
684
|
_run(["ipython", "--ignore-cwd"] + list(ipython_args), replace=True)
|
|
@@ -643,8 +686,9 @@ def ipython(ctx, *, ipython_args):
|
|
|
643
686
|
|
|
644
687
|
@click.command()
|
|
645
688
|
@click.argument("shell_args", nargs=-1)
|
|
689
|
+
@build_dir_option
|
|
646
690
|
@click.pass_context
|
|
647
|
-
def shell(ctx, shell_args=[]):
|
|
691
|
+
def shell(ctx, shell_args=[], build_dir=None):
|
|
648
692
|
"""💻 Launch shell with PYTHONPATH set
|
|
649
693
|
|
|
650
694
|
SHELL_ARGS are passed through directly to the shell, e.g.:
|
|
@@ -659,9 +703,9 @@ def shell(ctx, shell_args=[]):
|
|
|
659
703
|
click.secho(
|
|
660
704
|
"Invoking `build` prior to invoking shell:", bold=True, fg="bright_green"
|
|
661
705
|
)
|
|
662
|
-
ctx.invoke(build_cmd)
|
|
706
|
+
ctx.invoke(build_cmd, build_dir=build_dir)
|
|
663
707
|
|
|
664
|
-
p = _set_pythonpath()
|
|
708
|
+
p = _set_pythonpath(build_dir)
|
|
665
709
|
if p:
|
|
666
710
|
print(f'💻 Launching shell with PYTHONPATH="{p}"')
|
|
667
711
|
|
|
@@ -674,8 +718,9 @@ def shell(ctx, shell_args=[]):
|
|
|
674
718
|
|
|
675
719
|
@click.command()
|
|
676
720
|
@click.argument("python_args", nargs=-1)
|
|
721
|
+
@build_dir_option
|
|
677
722
|
@click.pass_context
|
|
678
|
-
def python(ctx, *, python_args):
|
|
723
|
+
def python(ctx, *, python_args, build_dir):
|
|
679
724
|
"""🐍 Launch Python shell with PYTHONPATH set
|
|
680
725
|
|
|
681
726
|
PYTHON_ARGS are passed through directly to Python, e.g.:
|
|
@@ -687,9 +732,9 @@ def python(ctx, *, python_args):
|
|
|
687
732
|
click.secho(
|
|
688
733
|
"Invoking `build` prior to invoking Python:", bold=True, fg="bright_green"
|
|
689
734
|
)
|
|
690
|
-
ctx.invoke(build_cmd)
|
|
735
|
+
ctx.invoke(build_cmd, build_dir=build_dir)
|
|
691
736
|
|
|
692
|
-
p = _set_pythonpath()
|
|
737
|
+
p = _set_pythonpath(build_dir)
|
|
693
738
|
if p:
|
|
694
739
|
print(f'🐍 Launching Python with PYTHONPATH="{p}"')
|
|
695
740
|
|
|
@@ -714,9 +759,10 @@ def python(ctx, *, python_args):
|
|
|
714
759
|
|
|
715
760
|
|
|
716
761
|
@click.command(context_settings={"ignore_unknown_options": True})
|
|
762
|
+
@build_dir_option
|
|
717
763
|
@click.argument("args", nargs=-1)
|
|
718
764
|
@click.pass_context
|
|
719
|
-
def run(ctx, *, args):
|
|
765
|
+
def run(ctx, *, args, build_dir=None):
|
|
720
766
|
"""🏁 Run a shell command with PYTHONPATH set
|
|
721
767
|
|
|
722
768
|
\b
|
|
@@ -740,7 +786,7 @@ def run(ctx, *, args):
|
|
|
740
786
|
# Redirect spin generated output
|
|
741
787
|
with contextlib.redirect_stdout(sys.stderr):
|
|
742
788
|
# Also ask build to be quiet
|
|
743
|
-
ctx.invoke(build_cmd, quiet=True)
|
|
789
|
+
ctx.invoke(build_cmd, build_dir=build_dir, quiet=True)
|
|
744
790
|
|
|
745
791
|
is_posix = sys.platform in ("linux", "darwin")
|
|
746
792
|
shell = len(args) == 1
|
|
@@ -751,7 +797,7 @@ def run(ctx, *, args):
|
|
|
751
797
|
# On Windows, we're going to try to use bash
|
|
752
798
|
cmd_args = ["bash", "-c", cmd_args]
|
|
753
799
|
|
|
754
|
-
_set_pythonpath(quiet=True)
|
|
800
|
+
_set_pythonpath(build_dir, quiet=True)
|
|
755
801
|
p = _run(cmd_args, echo=False, shell=shell, sys_exit=False)
|
|
756
802
|
|
|
757
803
|
# Is the user trying to run a Python script, without calling the Python interpreter?
|
|
@@ -789,6 +835,7 @@ def run(ctx, *, args):
|
|
|
789
835
|
help="Sphinx gallery: enable/disable plots",
|
|
790
836
|
)
|
|
791
837
|
@click.option("--jobs", "-j", default="auto", help="Number of parallel build jobs")
|
|
838
|
+
@build_dir_option
|
|
792
839
|
@click.pass_context
|
|
793
840
|
def docs(
|
|
794
841
|
ctx,
|
|
@@ -799,6 +846,7 @@ def docs(
|
|
|
799
846
|
jobs,
|
|
800
847
|
sphinx_gallery_plot,
|
|
801
848
|
clean_dirs=None,
|
|
849
|
+
build_dir=None,
|
|
802
850
|
):
|
|
803
851
|
"""📖 Build Sphinx documentation
|
|
804
852
|
|
|
@@ -853,12 +901,14 @@ def docs(
|
|
|
853
901
|
click.secho(
|
|
854
902
|
"Invoking `build` prior to building docs:", bold=True, fg="bright_green"
|
|
855
903
|
)
|
|
856
|
-
ctx.invoke(build_cmd)
|
|
904
|
+
ctx.invoke(build_cmd, build_dir=build_dir)
|
|
857
905
|
|
|
858
906
|
try:
|
|
859
|
-
site_path = _get_site_packages()
|
|
907
|
+
site_path = _get_site_packages(build_dir)
|
|
860
908
|
except FileNotFoundError:
|
|
861
|
-
|
|
909
|
+
cfg = get_config()
|
|
910
|
+
distname = cfg.get("project.name", None)
|
|
911
|
+
print(f"{distname} build not found; run `spin build` or `spin install` first.")
|
|
862
912
|
sys.exit(1)
|
|
863
913
|
|
|
864
914
|
opts = os.environ.get("SPHINXOPTS", "-W")
|
|
@@ -889,8 +939,9 @@ def docs(
|
|
|
889
939
|
@click.command()
|
|
890
940
|
@click.option("--code", "-c", help="Python program passed in as a string")
|
|
891
941
|
@click.argument("lldb_args", nargs=-1)
|
|
942
|
+
@build_dir_option
|
|
892
943
|
@click.pass_context
|
|
893
|
-
def lldb(ctx, *, code, lldb_args):
|
|
944
|
+
def lldb(ctx, *, code, lldb_args, build_dir=None):
|
|
894
945
|
"""👾 Execute code through LLDB
|
|
895
946
|
|
|
896
947
|
spin lldb -c 'import numpy as np; print(np.__version__)'
|
|
@@ -918,9 +969,9 @@ def lldb(ctx, *, code, lldb_args):
|
|
|
918
969
|
click.secho(
|
|
919
970
|
"Invoking `build` prior to invoking lldb:", bold=True, fg="bright_green"
|
|
920
971
|
)
|
|
921
|
-
ctx.invoke(build_cmd)
|
|
972
|
+
ctx.invoke(build_cmd, build_dir=build_dir)
|
|
922
973
|
|
|
923
|
-
_set_pythonpath()
|
|
974
|
+
_set_pythonpath(build_dir)
|
|
924
975
|
lldb_args = list(lldb_args)
|
|
925
976
|
|
|
926
977
|
if code:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from .util import get_commands, get_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _highlight(src):
|
|
9
|
+
from pygments import highlight
|
|
10
|
+
from pygments.formatters import TerminalFormatter
|
|
11
|
+
from pygments.lexers import PythonLexer
|
|
12
|
+
|
|
13
|
+
return highlight(src, PythonLexer(), TerminalFormatter())
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command()
|
|
17
|
+
@click.argument("cmd", nargs=1)
|
|
18
|
+
def introspect(*, cmd):
|
|
19
|
+
"""🔍 Print a command's location and source code."""
|
|
20
|
+
cmds_by_section = get_commands()
|
|
21
|
+
overrides = get_config().get("tool.spin.kwargs")
|
|
22
|
+
|
|
23
|
+
cmds = {}
|
|
24
|
+
for section in cmds_by_section:
|
|
25
|
+
cmds.update({cmd.name: cmd for cmd in cmds_by_section[section]})
|
|
26
|
+
|
|
27
|
+
if cmd not in cmds:
|
|
28
|
+
raise SystemExit(f"Command `{cmd}` not found. Exiting.")
|
|
29
|
+
|
|
30
|
+
cmd_func = cmds[cmd]
|
|
31
|
+
try:
|
|
32
|
+
code = inspect.getsource(cmd_func.callback)
|
|
33
|
+
except TypeError:
|
|
34
|
+
# Perhaps a partial, try again
|
|
35
|
+
code = inspect.getsource(cmd_func.callback.func)
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
code = _highlight(code)
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
print(code)
|
|
43
|
+
|
|
44
|
+
click.secho(
|
|
45
|
+
f"The `{cmd}` command is defined as `{cmd_func.spec}`.",
|
|
46
|
+
bold=True,
|
|
47
|
+
fg="magenta",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
click.secho(
|
|
51
|
+
f"The code is in `{cmd_func.module.__file__}`.", bold=True, fg="magenta"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if cmd_func.spec in overrides:
|
|
55
|
+
click.secho(
|
|
56
|
+
"\nThe function has the following keyword overrides defined:\n",
|
|
57
|
+
bold=True,
|
|
58
|
+
fg="magenta",
|
|
59
|
+
)
|
|
60
|
+
print(" ", overrides[cmd_func.spec], "\n")
|
|
@@ -9,7 +9,13 @@ from .util import run as _run
|
|
|
9
9
|
default=True,
|
|
10
10
|
help="Install in editable mode.",
|
|
11
11
|
)
|
|
12
|
-
@click.option(
|
|
12
|
+
@click.option(
|
|
13
|
+
"-v/-q",
|
|
14
|
+
"--verbose/--quiet",
|
|
15
|
+
is_flag=True,
|
|
16
|
+
default=True,
|
|
17
|
+
help="Print detailed build output.",
|
|
18
|
+
)
|
|
13
19
|
@click.option(
|
|
14
20
|
"--verbose-import/--no-verbose-import",
|
|
15
21
|
is_flag=True,
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
from __future__ import (
|
|
2
|
+
annotations, # noqa: F401 # TODO: remove once only >3.8 is supported
|
|
3
|
+
)
|
|
4
|
+
|
|
1
5
|
import os
|
|
2
6
|
import shlex
|
|
3
7
|
import subprocess
|
|
4
8
|
import sys
|
|
5
|
-
from typing import Optional
|
|
6
9
|
|
|
7
10
|
import click
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
def run(
|
|
11
14
|
cmd: list[str],
|
|
12
|
-
cwd:
|
|
15
|
+
cwd: str | None = None, # in 3.10 and up: str | None
|
|
13
16
|
replace: bool = False,
|
|
14
17
|
sys_exit: bool = True,
|
|
15
18
|
output: bool = True,
|
|
@@ -36,7 +36,7 @@ class ColorHelpFormatter(click.formatting.HelpFormatter):
|
|
|
36
36
|
},
|
|
37
37
|
default={"bold": True, "fg": "cyan"},
|
|
38
38
|
)
|
|
39
|
-
val_fmt = RegexpFormatter({r"\[default: .*?\]": {"dim": True}})
|
|
39
|
+
val_fmt = RegexpFormatter({r"\[(env var: .*?; )?default: .*?\]": {"dim": True}})
|
|
40
40
|
items = [(key_fmt(key), val_fmt(val)) for (key, val) in items]
|
|
41
41
|
super().write_dl(items)
|
|
42
42
|
|
|
@@ -5,7 +5,14 @@ import tempfile
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
-
from testutil import
|
|
8
|
+
from testutil import (
|
|
9
|
+
skip_on_windows,
|
|
10
|
+
skip_py_lt_311,
|
|
11
|
+
skip_unless_linux,
|
|
12
|
+
skip_unless_macos,
|
|
13
|
+
spin,
|
|
14
|
+
stdout,
|
|
15
|
+
)
|
|
9
16
|
|
|
10
17
|
from spin.cmds.util import run
|
|
11
18
|
|
|
@@ -147,3 +154,23 @@ def test_lldb():
|
|
|
147
154
|
"--batch",
|
|
148
155
|
)
|
|
149
156
|
assert "hi" in stdout(p)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@skip_py_lt_311 # python command does not run on older pythons
|
|
160
|
+
def test_parallel_builds():
|
|
161
|
+
spin("build")
|
|
162
|
+
spin("build", "-C", "parallel/build")
|
|
163
|
+
p = spin("python", "--", "-c", "import example_pkg; print(example_pkg.__file__)")
|
|
164
|
+
example_pkg_path = stdout(p).split("\n")[-1]
|
|
165
|
+
p = spin(
|
|
166
|
+
"python",
|
|
167
|
+
"-C",
|
|
168
|
+
"parallel/build",
|
|
169
|
+
"--",
|
|
170
|
+
"-c",
|
|
171
|
+
"import example_pkg; print(example_pkg.__file__)",
|
|
172
|
+
)
|
|
173
|
+
example_pkg_parallel_path = stdout(p).split("\n")[-1]
|
|
174
|
+
assert "build-install" in example_pkg_path
|
|
175
|
+
assert "parallel/build-install" in example_pkg_parallel_path
|
|
176
|
+
assert "parallel/build-install" not in example_pkg_path
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import tempfile
|
|
4
|
+
from os.path import join as pjoin
|
|
5
|
+
from os.path import normpath
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from spin.cmds import meson
|
|
10
|
+
from spin.containers import DotDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def make_paths(root, paths):
|
|
14
|
+
for p in paths:
|
|
15
|
+
os.makedirs(pjoin(root, p.lstrip("/")))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_path_discovery():
|
|
19
|
+
version = sys.version_info
|
|
20
|
+
X, Y = version.major, version.minor
|
|
21
|
+
|
|
22
|
+
# With multiple site-packages, choose the one that matches the
|
|
23
|
+
# current Python version
|
|
24
|
+
with tempfile.TemporaryDirectory() as d:
|
|
25
|
+
build_dir = pjoin(d, "./build")
|
|
26
|
+
install_dir = pjoin(d, "./build-install")
|
|
27
|
+
|
|
28
|
+
make_paths(
|
|
29
|
+
install_dir,
|
|
30
|
+
[
|
|
31
|
+
f"/usr/lib64/python{X}.{Y}/site-packages",
|
|
32
|
+
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
|
|
33
|
+
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
|
|
34
|
+
],
|
|
35
|
+
)
|
|
36
|
+
assert normpath(
|
|
37
|
+
f"/usr/lib64/python{X}.{Y}/site-packages"
|
|
38
|
+
) in meson._get_site_packages(build_dir)
|
|
39
|
+
|
|
40
|
+
# Debian uses dist-packages
|
|
41
|
+
with tempfile.TemporaryDirectory() as d:
|
|
42
|
+
build_dir = pjoin(d, "./build")
|
|
43
|
+
install_dir = pjoin(d, "./build-install")
|
|
44
|
+
|
|
45
|
+
make_paths(
|
|
46
|
+
install_dir,
|
|
47
|
+
[
|
|
48
|
+
f"/usr/lib64/python{X}.{Y}/dist-packages",
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
assert normpath(
|
|
52
|
+
f"/usr/lib64/python{X}.{Y}/dist-packages"
|
|
53
|
+
) in meson._get_site_packages(build_dir)
|
|
54
|
+
|
|
55
|
+
# If there is no version information in site-packages,
|
|
56
|
+
# use whatever site-packages can be found
|
|
57
|
+
with tempfile.TemporaryDirectory() as d:
|
|
58
|
+
build_dir = pjoin(d, "./build")
|
|
59
|
+
install_dir = pjoin(d, "./build-install")
|
|
60
|
+
|
|
61
|
+
make_paths(install_dir, ["/Python3/site-packages"])
|
|
62
|
+
assert normpath("/Python3/site-packages") in meson._get_site_packages(build_dir)
|
|
63
|
+
|
|
64
|
+
# Raise if no site-package directory present
|
|
65
|
+
with tempfile.TemporaryDirectory() as d:
|
|
66
|
+
install_dir = pjoin(d, "-install")
|
|
67
|
+
|
|
68
|
+
with pytest.raises(FileNotFoundError):
|
|
69
|
+
meson._get_site_packages(build_dir)
|
|
70
|
+
|
|
71
|
+
# If there are multiple site-package paths, but without version information,
|
|
72
|
+
# refuse the temptation to guess
|
|
73
|
+
with tempfile.TemporaryDirectory() as d:
|
|
74
|
+
build_dir = pjoin(d, "./build")
|
|
75
|
+
install_dir = pjoin(d, "./build-install")
|
|
76
|
+
make_paths(
|
|
77
|
+
install_dir, ["/Python3/x/site-packages", "/Python3/y/site-packages"]
|
|
78
|
+
)
|
|
79
|
+
with pytest.raises(FileNotFoundError):
|
|
80
|
+
meson._get_site_packages(build_dir)
|
|
81
|
+
|
|
82
|
+
# Multiple site-package paths found, but none that matches our Python
|
|
83
|
+
with tempfile.TemporaryDirectory() as d:
|
|
84
|
+
build_dir = pjoin(d, "./build")
|
|
85
|
+
install_dir = pjoin(d, "./build-install")
|
|
86
|
+
|
|
87
|
+
make_paths(
|
|
88
|
+
install_dir,
|
|
89
|
+
[
|
|
90
|
+
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
|
|
91
|
+
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
|
|
92
|
+
],
|
|
93
|
+
)
|
|
94
|
+
with pytest.raises(FileNotFoundError):
|
|
95
|
+
meson._get_site_packages(build_dir)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_meson_cli_discovery(monkeypatch):
|
|
99
|
+
config0 = DotDict({"tool": {"spin": {"meson": {"cli": "~/envs/py311/bin/meson"}}}})
|
|
100
|
+
config1 = DotDict(
|
|
101
|
+
{"tool": {"spin": {"meson": {"cli": "~/envs/py311/bin/meson.py"}}}}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
with monkeypatch.context() as m:
|
|
105
|
+
m.setattr(meson, "get_config", lambda: config0)
|
|
106
|
+
assert meson._meson_cli()[0] == os.path.expanduser("~/envs/py311/bin/meson")
|
|
107
|
+
|
|
108
|
+
m.setattr(meson, "get_config", lambda: config1)
|
|
109
|
+
assert meson._meson_cli()[0] == sys.executable
|
|
@@ -17,6 +17,10 @@ skip_unless_macos = pytest.mark.skipif(
|
|
|
17
17
|
not sys.platform.startswith("darwin"), reason="Skipped; platform not macOS"
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
skip_py_lt_311 = pytest.mark.skipif(
|
|
21
|
+
sys.version_info[:2] < (3, 11), reason="Skipped; Python < 3.11"
|
|
22
|
+
)
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
def spin(*args, **user_kwargs):
|
|
22
26
|
args = (str(el) for el in args)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: spin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12
|
|
4
4
|
Summary: Developer tool for scientific Python libraries
|
|
5
5
|
Maintainer-email: Scientific Python <spin@discuss.scientific-python.org>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -55,7 +55,7 @@ Requires-Dist: tomli; python_version < "3.11"
|
|
|
55
55
|
Requires-Dist: colorama; platform_system == "Windows"
|
|
56
56
|
Requires-Dist: importlib_metadata>=7
|
|
57
57
|
Provides-Extra: lint
|
|
58
|
-
Requires-Dist: pre-commit==3.
|
|
58
|
+
Requires-Dist: pre-commit==3.8.0; extra == "lint"
|
|
59
59
|
Provides-Extra: dev
|
|
60
60
|
Requires-Dist: changelist==0.5; extra == "dev"
|
|
61
61
|
|
|
@@ -147,15 +147,15 @@ python -m spin
|
|
|
147
147
|
Available as `spin.cmds.meson.*`.
|
|
148
148
|
|
|
149
149
|
```
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
build 🔧 Build package with Meson/ninja
|
|
151
|
+
ipython 💻 Launch IPython shell with PYTHONPATH set
|
|
152
|
+
python 🐍 Launch Python shell with PYTHONPATH set
|
|
153
|
+
shell 💻 Launch shell with PYTHONPATH set
|
|
154
|
+
test 🔧 Run pytest
|
|
155
|
+
run 🏁 Run a shell command with PYTHONPATH set
|
|
156
|
+
docs 📖 Build Sphinx documentation
|
|
157
|
+
gdb 👾 Execute a Python snippet with GDB
|
|
158
|
+
lldb 👾 Execute a Python snippet with LLDB
|
|
159
159
|
```
|
|
160
160
|
|
|
161
161
|
### [Build](https://pypa-build.readthedocs.io/en/stable/) (PEP 517 builder)
|
|
@@ -163,7 +163,7 @@ Available as `spin.cmds.meson.*`.
|
|
|
163
163
|
Available as `spin.cmds.build.*`:
|
|
164
164
|
|
|
165
165
|
```
|
|
166
|
-
|
|
166
|
+
sdist 📦 Build a source distribution in `dist/`
|
|
167
167
|
```
|
|
168
168
|
|
|
169
169
|
### [pip](https://pip.pypa.io) (Package Installer for Python)
|
|
@@ -174,7 +174,15 @@ development workflow.
|
|
|
174
174
|
Available as `spin.cmds.pip.*`:
|
|
175
175
|
|
|
176
176
|
```
|
|
177
|
-
|
|
177
|
+
install 💽 Build and install package using pip.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Meta (commands that operate on commands)
|
|
181
|
+
|
|
182
|
+
Available as `spin.cmds.meta.*`:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
introspect 🔍 Print a command's location and source code
|
|
178
186
|
```
|
|
179
187
|
|
|
180
188
|
## 🧪 Custom commands
|
|
@@ -310,6 +318,13 @@ fc-cache -f -v
|
|
|
310
318
|
nox -s test
|
|
311
319
|
```
|
|
312
320
|
|
|
321
|
+
Other examples:
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
nox -s test -- -v
|
|
325
|
+
nox -s test -- -v spin/tests/test_meson.py
|
|
326
|
+
```
|
|
327
|
+
|
|
313
328
|
## History
|
|
314
329
|
|
|
315
330
|
The `dev.py` tool was [proposed for SciPy](https://github.com/scipy/scipy/issues/15489) by Ralf Gommers and [implemented](https://github.com/scipy/scipy/pull/15959) by Sayantika Banik, Eduardo Naufel Schettino, and Ralf Gommers (also see [Sayantika's blog post](https://labs.quansight.org/blog/the-evolution-of-the-scipy-developer-cli)).
|
spin-0.11/spin/cmds/build.py
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
import tempfile
|
|
4
|
-
from os.path import join as pjoin
|
|
5
|
-
from os.path import normpath
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
|
-
from spin.cmds import meson
|
|
10
|
-
from spin.containers import DotDict
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def make_paths(root, paths):
|
|
14
|
-
for p in paths:
|
|
15
|
-
os.makedirs(pjoin(root, p.lstrip("/")))
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_path_discovery(monkeypatch):
|
|
19
|
-
version = sys.version_info
|
|
20
|
-
X, Y = version.major, version.minor
|
|
21
|
-
|
|
22
|
-
# With multiple site-packages, choose the one that
|
|
23
|
-
# matches the current Python version
|
|
24
|
-
with tempfile.TemporaryDirectory() as d:
|
|
25
|
-
with monkeypatch.context() as m:
|
|
26
|
-
install_dir = pjoin(d, "build-install")
|
|
27
|
-
m.setattr(meson, "install_dir", install_dir)
|
|
28
|
-
|
|
29
|
-
make_paths(
|
|
30
|
-
install_dir,
|
|
31
|
-
[
|
|
32
|
-
f"/usr/lib64/python{X}.{Y}/site-packages",
|
|
33
|
-
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
|
|
34
|
-
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
|
|
35
|
-
],
|
|
36
|
-
)
|
|
37
|
-
assert (
|
|
38
|
-
normpath(f"/usr/lib64/python{X}.{Y}/site-packages")
|
|
39
|
-
in meson._get_site_packages()
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# Debian uses dist-packages
|
|
43
|
-
with tempfile.TemporaryDirectory() as d:
|
|
44
|
-
with monkeypatch.context() as m:
|
|
45
|
-
install_dir = pjoin(d, "build-install")
|
|
46
|
-
m.setattr(meson, "install_dir", install_dir)
|
|
47
|
-
|
|
48
|
-
make_paths(
|
|
49
|
-
install_dir,
|
|
50
|
-
[
|
|
51
|
-
f"/usr/lib64/python{X}.{Y}/dist-packages",
|
|
52
|
-
],
|
|
53
|
-
)
|
|
54
|
-
assert (
|
|
55
|
-
normpath(f"/usr/lib64/python{X}.{Y}/dist-packages")
|
|
56
|
-
in meson._get_site_packages()
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# If there is no version information in site-packages,
|
|
60
|
-
# use whatever site-packages can be found
|
|
61
|
-
with tempfile.TemporaryDirectory() as d:
|
|
62
|
-
with monkeypatch.context() as m:
|
|
63
|
-
install_dir = pjoin(d, "build-install")
|
|
64
|
-
m.setattr(meson, "install_dir", install_dir)
|
|
65
|
-
|
|
66
|
-
make_paths(install_dir, ["/Python3/site-packages"])
|
|
67
|
-
assert normpath("/Python3/site-packages") in meson._get_site_packages()
|
|
68
|
-
|
|
69
|
-
# Raise if no site-package directory present
|
|
70
|
-
with tempfile.TemporaryDirectory() as d:
|
|
71
|
-
with monkeypatch.context() as m:
|
|
72
|
-
install_dir = pjoin(d, "build-install")
|
|
73
|
-
m.setattr(meson, "install_dir", install_dir)
|
|
74
|
-
|
|
75
|
-
with pytest.raises(FileNotFoundError):
|
|
76
|
-
meson._get_site_packages()
|
|
77
|
-
|
|
78
|
-
# If there are multiple site-package paths, but without version information,
|
|
79
|
-
# refuse the temptation to guess
|
|
80
|
-
with tempfile.TemporaryDirectory() as d:
|
|
81
|
-
install_dir = pjoin(d, "build-install")
|
|
82
|
-
make_paths(
|
|
83
|
-
install_dir, ["/Python3/x/site-packages", "/Python3/y/site-packages"]
|
|
84
|
-
)
|
|
85
|
-
with pytest.raises(FileNotFoundError):
|
|
86
|
-
meson._get_site_packages()
|
|
87
|
-
|
|
88
|
-
# Multiple site-package paths found, but none that matches our Python
|
|
89
|
-
with tempfile.TemporaryDirectory() as d:
|
|
90
|
-
with monkeypatch.context() as m:
|
|
91
|
-
install_dir = pjoin(d, "build-install")
|
|
92
|
-
m.setattr(meson, "install_dir", install_dir)
|
|
93
|
-
|
|
94
|
-
make_paths(
|
|
95
|
-
install_dir,
|
|
96
|
-
[
|
|
97
|
-
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
|
|
98
|
-
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
|
|
99
|
-
],
|
|
100
|
-
)
|
|
101
|
-
with pytest.raises(FileNotFoundError):
|
|
102
|
-
meson._get_site_packages()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def test_meson_cli_discovery(monkeypatch):
|
|
106
|
-
config0 = DotDict({"tool": {"spin": {"meson": {"cli": "~/envs/py311/bin/meson"}}}})
|
|
107
|
-
config1 = DotDict(
|
|
108
|
-
{"tool": {"spin": {"meson": {"cli": "~/envs/py311/bin/meson.py"}}}}
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
with monkeypatch.context() as m:
|
|
112
|
-
m.setattr(meson, "get_config", lambda: config0)
|
|
113
|
-
assert meson._meson_cli()[0] == os.path.expanduser("~/envs/py311/bin/meson")
|
|
114
|
-
|
|
115
|
-
m.setattr(meson, "get_config", lambda: config1)
|
|
116
|
-
assert meson._meson_cli()[0] == sys.executable
|
{spin-0.11 → spin-0.12}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|