diff --git a/.github/workflows/test.sh b/.github/workflows/test.sh new file mode 100644 index 0000000..4dd5422 --- /dev/null +++ b/.github/workflows/test.sh @@ -0,0 +1,22 @@ +set -e + +prun() { echo "\$ $@" ; "$@" ; } + +prun cd example_pkg + +prun spin build + +# Test spin run +echo SPIN_PYTHONPATH=\$\(spin run 'echo $PYTHONPATH'\) +SPIN_PYTHONPATH=$(spin run 'echo $PYTHONPATH') +echo spin sees PYTHONPATH=\"${SPIN_PYTHONPATH}\" +if [[ ${SPIN_PYTHONPATH} == "\$PYTHONPATH" ]]; then + echo -n "\!\!\!\!\n\nIf this says \$PYTHONPATH, that's an error\n\n\!\!\!\!\n" +fi +[[ ${SPIN_PYTHONPATH} == *"site-packages" ]] +prun spin run python -c 'import sys; del sys.path[0]; import example_pkg; print(example_pkg.__version__)' + +prun spin test +prun spin sdist +prun spin example +prun spin docs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1199da2..7e7f0a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,34 +32,20 @@ jobs: PYTHONPATH: "." run: | pytest --pyargs spin - - name: Patch Windows runner - if: matrix.os == 'windows-latest' - run: | - Remove-Item -Path C:\Strawberry -Recurse - Remove-Item -Path C:\ProgramData\Chocolatey\bin\gcc.exe - Remove-Item -Path C:\msys64 -Recurse -Force - Remove-Item -Path "C:\Program Files\LLVM" -Recurse - - name: Functional tests (linux) + + - name: Functional tests (Linux) if: matrix.os == 'ubuntu-latest' shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"' env: TERM: xterm-256color - run: | - set -x - cd example_pkg - spin build - spin test - spin sdist - spin example - spin docs - - name: Functional tests (other) - if: matrix.os != 'ubuntu-latest' + run: source .github/workflows/test.sh + + - name: Functional tests (MacOS) + if: matrix.os == 'macos-latest' shell: bash - run: | - set -x - cd example_pkg - spin build - spin test - spin sdist - spin example - spin docs + run: source .github/workflows/test.sh + + - name: Functional tests (Windows) + if: matrix.os == 'windows-latest' + shell: bash + run: source .github/workflows/test.sh diff --git a/README.md b/README.md index af853e0..781c267 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,8 @@ In `pyproject.toml`, instead of specifying the commands as a list, use the follo "spin.cmds.meson.test" ] "Environments" = [ - "spin.cmds.meson.shell", "spin.cmds.meson.ipython", - "spin.cmds.meson.python" + "spin.cmds.meson.run" ] ``` @@ -63,9 +62,8 @@ Build: test ๐Ÿ”ง Run tests Environments: - shell ๐Ÿ’ป Launch shell with PYTHONPATH set ipython ๐Ÿ’ป Launch IPython shell with PYTHONPATH set - python ๐Ÿ Launch Python shell with PYTHONPATH set + run ๐Ÿ Run a shell command with PYTHONPATH set ``` ## Running @@ -90,6 +88,7 @@ python -m spin python ๐Ÿ Launch Python shell with PYTHONPATH set shell ๐Ÿ’ป Launch shell with PYTHONPATH set test ๐Ÿ”ง Run pytest + run ๐Ÿ Run a shell command with PYTHONPATH set docs ๐Ÿ“– Build Sphinx documentation ``` @@ -98,7 +97,7 @@ python -m spin `spin` was started with Meson in mind, but we're working on expanding commands for PEP 517 `build`. ``` - sdist ๐Ÿ“ฆ Build a source distribution in `dist/`. + sdist ๐Ÿ“ฆ Build a source distribution in `dist/` ``` ## ๐Ÿงช Custom commands diff --git a/example_pkg/example_pkg/__init__.py b/example_pkg/example_pkg/__init__.py index fd46c98..46b7736 100644 --- a/example_pkg/example_pkg/__init__.py +++ b/example_pkg/example_pkg/__init__.py @@ -1 +1,3 @@ from ._core import echo + +__version__ = "0.0.0dev0" diff --git a/example_pkg/pyproject.toml b/example_pkg/pyproject.toml index 3a44978..8886419 100644 --- a/example_pkg/pyproject.toml +++ b/example_pkg/pyproject.toml @@ -36,6 +36,7 @@ package = 'example_pkg' "Environments" = [ "spin.cmds.meson.shell", "spin.cmds.meson.ipython", - "spin.cmds.meson.python" + "spin.cmds.meson.python", + "spin.cmds.meson.run" ] "Extensions" = [".spin/cmds.py:example"] diff --git a/spin/cmds/build.py b/spin/cmds/build.py index 57862bc..96c2247 100644 --- a/spin/cmds/build.py +++ b/spin/cmds/build.py @@ -5,5 +5,5 @@ @click.command() def sdist(): - """๐Ÿ“ฆ Build a source distribution in `dist/`.""" + """๐Ÿ“ฆ Build a source distribution in `dist/`""" run(["python", "-m", "build", ".", "--sdist"]) diff --git a/spin/cmds/meson.py b/spin/cmds/meson.py index 4254ec5..bd496c4 100644 --- a/spin/cmds/meson.py +++ b/spin/cmds/meson.py @@ -2,16 +2,17 @@ import sys import shutil import json +import shlex import click -from .util import run, get_config, get_commands +from .util import run as _run, get_config, get_commands install_dir = "build-install" -def _set_pythonpath(): +def _set_pythonpath(quiet=False): site_packages = _get_site_packages() env = os.environ @@ -20,7 +21,10 @@ def _set_pythonpath(): else: env["PYTHONPATH"] = site_packages - click.secho(f'$ export PYTHONPATH="{site_packages}"', bold=True, fg="bright_blue") + if not quiet: + click.secho( + f'$ export PYTHONPATH="{site_packages}"', bold=True, fg="bright_blue" + ) return env["PYTHONPATH"] @@ -64,7 +68,7 @@ def _get_site_packages(): def _meson_version(): try: - p = run(["meson", "--version"], output=False, echo=False) + p = _run(["meson", "--version"], output=False, echo=False) return p.stdout.decode("ascii").strip() except: pass @@ -112,7 +116,7 @@ def build(meson_args, jobs=None, clean=False, verbose=False): shutil.rmtree(install_dir) if not (os.path.exists(build_dir) and _meson_version_configured()): - p = run(setup_cmd, sys_exit=False) + p = _run(setup_cmd, sys_exit=False) if p.returncode != 0: raise RuntimeError( "Meson configuration failed; please try `spin build` again with the `--clean` flag." @@ -122,12 +126,12 @@ def build(meson_args, jobs=None, clean=False, verbose=False): # current version of Meson if _meson_version() != _meson_version_configured(): - run(setup_cmd + ["--reconfigure"]) + _run(setup_cmd + ["--reconfigure"]) # Any other conditions that warrant a reconfigure? - p = run(["meson", "compile", "-C", build_dir], sys_exit=False) - p = run( + p = _run(["meson", "compile", "-C", build_dir], sys_exit=False) + p = _run( [ "meson", "install", @@ -183,9 +187,14 @@ def test(ctx, pytest_args): # Sanity check that library built properly if sys.version_info[:2] >= (3, 11): - run([sys.executable, "-P", "-c", f"import {package}"]) + p = _run([sys.executable, "-P", "-c", f"import {package}"], sys_exit=False) + if p.returncode != 0: + print(f"As a sanity check, we tried to import {package}.") + print("Stopping. Please investigate the build error.") + sys.exit(1) - run( + print(f'$ export PYTHONPATH="{site_path}"') + _run( [sys.executable, "-m", "pytest", f"--rootdir={site_path}"] + list(pytest_args), cwd=site_path, replace=True, @@ -203,7 +212,7 @@ def ipython(ipython_args): """ p = _set_pythonpath() print(f'๐Ÿ’ป Launching IPython with PYTHONPATH="{p}"') - run(["ipython", "--ignore-cwd"] + list(ipython_args), replace=True) + _run(["ipython", "--ignore-cwd"] + list(ipython_args), replace=True) @click.command() @@ -224,7 +233,7 @@ def shell(shell_args=[]): print(f'๐Ÿ’ป Launching shell with PYTHONPATH="{p}"') print(f"โš  Change directory to avoid importing source instead of built package") print(f"โš  Ensure that your ~/.shellrc does not unset PYTHONPATH") - run(cmd, replace=True) + _run(cmd, replace=True) @click.command() @@ -256,7 +265,41 @@ def python(python_args): print(f'๐Ÿ Launching Python with PYTHONPATH="{p}"') - run(["/usr/bin/env", "python", "-P"] + list(python_args), replace=True) + _run(["/usr/bin/env", "python", "-P"] + list(python_args), replace=True) + + +@click.command(context_settings={"ignore_unknown_options": True}) +@click.argument("args", nargs=-1) +def run(args): + """๐Ÿ Run a shell command with PYTHONPATH set + + \b + spin run make + spin run 'echo $PYTHONPATH' + spin run python -c 'import sys; del sys.path[0]; import mypkg' + + If you'd like to expand shell variables, like `$PYTHONPATH` in the example + above, you need to provide a single, quoted command to `run`: + + spin run 'echo $SHELL && echo $PWD' + + On Windows, all shell commands are run via Bash. + Install Git for Windows if you don't have Bash already. + """ + if not len(args) > 0: + raise RuntimeError("No command given") + + is_posix = sys.platform in ("linux", "darwin") + shell = len(args) == 1 + if shell: + args = args[0] + + if shell and not is_posix: + # On Windows, we're going to try to use bash + args = ["bash", "-c", args] + + _set_pythonpath(quiet=True) + _run(args, echo=False, shell=shell) @click.command() @@ -342,4 +385,4 @@ def docs(ctx, sphinx_target, clean, first_build, jobs): click.secho( f"$ export PYTHONPATH={os.environ['PYTHONPATH']}", bold=True, fg="bright_blue" ) - run(["make", "-C", "doc", sphinx_target], replace=True) + _run(["make", "-C", "doc", sphinx_target], replace=True) diff --git a/spin/cmds/util.py b/spin/cmds/util.py index cc86623..849bef8 100644 --- a/spin/cmds/util.py +++ b/spin/cmds/util.py @@ -19,6 +19,7 @@ def run( Change to this directory before execution. replace : bool Whether to replace the current process. + Note that this has no effect on Windows. sys_exit : bool Whether to exit if the shell command returns with error != 0. output : bool @@ -46,7 +47,7 @@ def run( output_kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.STDOUT} kwargs = {**output_kwargs, **kwargs} - if replace: + if replace and (sys.platform in ("linux", "darwin")): os.execvp(cmd[0], cmd) print(f"Failed to launch `{cmd}`") sys.exit(-1)