Skip to content

Commit

Permalink
Run build before test command (#51)
Browse files Browse the repository at this point in the history
In order to run the `build` command reliably without arguments, the
`--build-dir` flag was removed from all commands.

Closes gh-50
  • Loading branch information
stefanv authored Mar 1, 2023
1 parent c0d4f80 commit 30ae90e
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 88 deletions.
1 change: 1 addition & 0 deletions devpy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def __getitem__(self, key):
@click.pass_context
def group(ctx):
ctx.meta["config"] = DotDict(toml_config)
ctx.meta["commands"] = ctx.command.section_commands
ctx.show_default = True

config_cmds = config["commands"]
Expand Down
16 changes: 6 additions & 10 deletions devpy/cmds/_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@


@click.command("build")
@click.option(
"--build-dir", default="build", help="Build directory; default is `$PWD/build`"
)
@click.option("-j", "--jobs", help="Number of parallel tasks to launch", type=int)
@click.option("--clean", is_flag=True, help="Clean build directory before build")
@click.option(
"-v", "--verbose", is_flag=True, help="Print all build output, even installation"
)
@click.argument("meson_args", nargs=-1)
def build_meson(build_dir, meson_args, jobs=None, clean=False, verbose=False):
def build_meson(meson_args, jobs=None, clean=False, verbose=False):
"""🔧 Build package with Meson/ninja and install
MESON_ARGS are passed through e.g.:
Expand All @@ -29,18 +26,17 @@ def build_meson(build_dir, meson_args, jobs=None, clean=False, verbose=False):
CFLAGS="-O0 -g" ./dev.py build
"""
build_dir = os.path.abspath(build_dir)
inst_dir = install_dir(build_dir)
build_dir = os.path.abspath("build")
build_cmd = ["meson", "setup", build_dir, "--prefix=/usr"] + list(meson_args)
flags = []

if clean:
print(f"Removing `{build_dir}`")
if os.path.isdir(build_dir):
shutil.rmtree(build_dir)
print(f"Removing `{inst_dir}`")
if os.path.isdir(inst_dir):
shutil.rmtree(inst_dir)
print(f"Removing `{install_dir}`")
if os.path.isdir(install_dir):
shutil.rmtree(install_dir)

if os.path.exists(build_dir):
flags += ["--reconfigure"]
Expand All @@ -63,7 +59,7 @@ def build_meson(build_dir, meson_args, jobs=None, clean=False, verbose=False):
"-C",
build_dir,
"--destdir",
install_dir(build_dir),
f"../{install_dir}",
],
output=verbose,
)
Expand Down
18 changes: 6 additions & 12 deletions devpy/cmds/_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,22 @@


@click.command()
@click.option(
"--build-dir", default="build", help="Build directory; default is `$PWD/build`"
)
@click.argument("ipython_args", nargs=-1)
def ipython(build_dir, ipython_args):
def ipython(ipython_args):
"""💻 Launch IPython shell with PYTHONPATH set
IPYTHON_ARGS are passed through directly to IPython, e.g.:
./dev.py ipython -- -i myscript.py
"""
p = set_pythonpath(build_dir)
p = set_pythonpath()
print(f'💻 Launching IPython with PYTHONPATH="{p}"')
run(["ipython", "--ignore-cwd"] + list(ipython_args), replace=True)


@click.command()
@click.option(
"--build-dir", default="build", help="Build directory; default is `$PWD/build`"
)
@click.argument("shell_args", nargs=-1)
def shell(build_dir, shell_args=[]):
def shell(shell_args=[]):
"""💻 Launch shell with PYTHONPATH set
SHELL_ARGS are passed through directly to the shell, e.g.:
Expand All @@ -39,7 +33,7 @@ def shell(build_dir, shell_args=[]):
Ensure that your shell init file (e.g., ~/.zshrc) does not override
the PYTHONPATH.
"""
p = set_pythonpath(build_dir)
p = set_pythonpath()
shell = os.environ.get("SHELL", "sh")
cmd = [shell] + list(shell_args)
print(f'💻 Launching shell with PYTHONPATH="{p}"')
Expand All @@ -53,14 +47,14 @@ def shell(build_dir, shell_args=[]):
"--build-dir", default="build", help="Build directory; default is `$PWD/build`"
)
@click.argument("python_args", nargs=-1)
def python(build_dir, python_args):
def python(python_args):
"""🐍 Launch Python shell with PYTHONPATH set
PYTHON_ARGS are passed through directly to Python, e.g.:
./dev.py python -- -c 'import sys; print(sys.path)'
"""
p = set_pythonpath(build_dir)
p = set_pythonpath()
v = sys.version_info
if (v.major < 3) or (v.major == 3 and v.minor < 11):
print("We're sorry, but this feature only works on Python 3.11 and greater 😢")
Expand Down
21 changes: 14 additions & 7 deletions devpy/cmds/_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
import sys
import click

from .util import run, get_config, set_pythonpath, get_site_packages
from .util import run, get_config, get_commands, set_pythonpath, get_site_packages


@click.command()
@click.option(
"--build-dir", default="build", help="Build directory; default is `$PWD/build`"
)
@click.argument("pytest_args", nargs=-1)
def test(build_dir, pytest_args):
@click.pass_context
def test(ctx, pytest_args):
"""🔧 Run tests
PYTEST_ARGS are passed through directly to pytest, e.g.:
Expand All @@ -23,6 +21,15 @@ def test(build_dir, pytest_args):
"""
cfg = get_config()

command_groups = get_commands()
commands = [cmd for section in command_groups for cmd in command_groups[section]]
build_cmd = next((cmd for cmd in commands if cmd.name == "build"), None)
if build_cmd:
click.secho(
f"Invoking `build` prior to running tests:", bold=True, fg="bright_green"
)
ctx.invoke(build_cmd)

if not pytest_args:
pytest_args = (cfg.get("tool.devpy.package", None),)
if pytest_args == (None,):
Expand All @@ -31,8 +38,8 @@ def test(build_dir, pytest_args):
)
sys.exit(1)

site_path = get_site_packages(build_dir)
set_pythonpath(build_dir)
site_path = get_site_packages()
set_pythonpath()

print(f'$ export PYTHONPATH="{site_path}"')
run(
Expand Down
28 changes: 14 additions & 14 deletions devpy/cmds/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import click


install_dir = "build-install"


def run(cmd, cwd=None, replace=False, sys_exit=True, output=True, *args, **kwargs):
if cwd:
click.secho(f"$ cd {cwd}", bold=True, fg="bright_blue")
Expand Down Expand Up @@ -37,9 +40,13 @@ def get_config():
return click.get_current_context().meta["config"]


def get_site_packages(build_dir):
def get_commands():
return click.get_current_context().meta["commands"]


def get_site_packages():
candidate_paths = []
for root, dirs, files in os.walk(install_dir(build_dir)):
for root, dirs, files in os.walk(install_dir):
for subdir in dirs:
if subdir == "site-packages" or subdir == "dist-packages":
candidate_paths.append(os.path.abspath(os.path.join(root, subdir)))
Expand All @@ -52,7 +59,7 @@ def get_site_packages(build_dir):
site_packages = [p for p in candidate_paths if f"python{X}.{Y}" in p]
if len(site_packages) == 0:
raise FileNotFoundError(
"No site-packages found in `{build_dir}` for Python {X}.{Y}"
f"No site-packages found in {install_dir} for Python {X}.{Y}"
)
else:
site_packages = site_packages[0]
Expand All @@ -61,21 +68,21 @@ def get_site_packages(build_dir):
# whatever site-packages path was found
if len(candidate_paths) > 1:
raise FileNotFoundError(
"Multiple `site-packages` found, but cannot use Python version to disambiguate"
f"Multiple `site-packages` found in `{install_dir}`, but cannot use Python version to disambiguate"
)
elif len(candidate_paths) == 1:
site_packages = candidate_paths[0]

if site_packages is None:
raise FileNotFoundError(
f"No `site-packages` or `dist-packages` found under {build_dir}"
f"No `site-packages` or `dist-packages` found under `{install_dir}`"
)

return site_packages


def set_pythonpath(build_dir):
site_packages = get_site_packages(build_dir)
def set_pythonpath():
site_packages = get_site_packages()
env = os.environ

if "PYTHONPATH" in env:
Expand All @@ -84,10 +91,3 @@ def set_pythonpath(build_dir):
env["PYTHONPATH"] = site_packages

return env["PYTHONPATH"]


def install_dir(build_dir):
return os.path.join(
build_dir,
os.path.abspath(f"{build_dir}/../{os.path.basename(build_dir)}-install"),
)
102 changes: 57 additions & 45 deletions devpy/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,76 +13,88 @@ def make_paths(root, paths):
os.makedirs(pjoin(root, p.lstrip("/")))


def test_path_discovery():
def test_path_discovery(monkeypatch):
version = sys.version_info
X, Y = version.major, version.minor

# With multiple site-packages, choose the one that
# matches the current Python version
with tempfile.TemporaryDirectory() as d:
build_dir = pjoin(d, "build")
install_dir = pjoin(d, "build-install")
make_paths(
install_dir,
[
f"/usr/lib64/python{X}.{Y}/site-packages",
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
],
)
assert normpath(
f"/usr/lib64/python{X}.{Y}/site-packages"
) in util.get_site_packages(build_dir)
with monkeypatch.context() as m:
install_dir = pjoin(d, "build-install")
m.setattr(util, "install_dir", install_dir)

make_paths(
install_dir,
[
f"/usr/lib64/python{X}.{Y}/site-packages",
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
],
)
assert (
normpath(f"/usr/lib64/python{X}.{Y}/site-packages")
in util.get_site_packages()
)

# Debian uses dist-packages
with tempfile.TemporaryDirectory() as d:
build_dir = pjoin(d, "build")
install_dir = pjoin(d, "build-install")
make_paths(
install_dir,
[
f"/usr/lib64/python{X}.{Y}/dist-packages",
],
)
assert normpath(
f"/usr/lib64/python{X}.{Y}/dist-packages"
) in util.get_site_packages(build_dir)
with monkeypatch.context() as m:
install_dir = pjoin(d, "build-install")
m.setattr(util, "install_dir", install_dir)

make_paths(
install_dir,
[
f"/usr/lib64/python{X}.{Y}/dist-packages",
],
)
assert (
normpath(f"/usr/lib64/python{X}.{Y}/dist-packages")
in util.get_site_packages()
)

# If there is no version information in site-packages,
# use whatever site-packages can be found
with tempfile.TemporaryDirectory() as d:
build_dir = pjoin(d, "build")
install_dir = pjoin(d, "build-install")
make_paths(install_dir, ["/Python3/site-packages"])
assert normpath("/Python3/site-packages") in util.get_site_packages(build_dir)
with monkeypatch.context() as m:
install_dir = pjoin(d, "build-install")
m.setattr(util, "install_dir", install_dir)

make_paths(install_dir, ["/Python3/site-packages"])
assert normpath("/Python3/site-packages") in util.get_site_packages()

# Raise if no site-package directory present
with tempfile.TemporaryDirectory() as d:
build_dir = pjoin(d, "build")
with pytest.raises(FileNotFoundError):
util.get_site_packages(build_dir)
with monkeypatch.context() as m:
install_dir = pjoin(d, "build-install")
m.setattr(util, "install_dir", install_dir)

with pytest.raises(FileNotFoundError):
util.get_site_packages()

# If there are multiple site-package paths, but without version information,
# refuse the temptation to guess
with tempfile.TemporaryDirectory() as d:
build_dir = pjoin(d, "build")
install_dir = pjoin(d, "build-install")
make_paths(
install_dir, [f"/Python3/x/site-packages", f"/Python3/y/site-packages"]
)
with pytest.raises(FileNotFoundError):
util.get_site_packages(build_dir)
util.get_site_packages()

# Multiple site-package paths found, but none that matches our Python
with tempfile.TemporaryDirectory() as d:
build_dir = pjoin(d, "build")
install_dir = pjoin(d, "build-install")
make_paths(
install_dir,
[
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
],
)
with pytest.raises(FileNotFoundError):
util.get_site_packages(build_dir)
with monkeypatch.context() as m:
install_dir = pjoin(d, "build-install")
m.setattr(util, "install_dir", install_dir)

make_paths(
install_dir,
[
f"/usr/lib64/python{X}.{Y + 1}/site-packages",
f"/usr/lib64/python{X}.{Y + 2}/site-packages",
],
)
with pytest.raises(FileNotFoundError):
util.get_site_packages()

0 comments on commit 30ae90e

Please sign in to comment.