Skip to content

Commit 4e8de9c

Browse files
committed
Allow custom build directory
1 parent 7907800 commit 4e8de9c

1 file changed

Lines changed: 69 additions & 36 deletions

File tree

spin/cmds/meson.py

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313
from .util import get_commands, get_config
1414
from .util import run as _run
1515

16-
install_dir = "build-install"
17-
build_dir = "build"
18-
1916

2017
class GcovReportFormat(str, Enum):
2118
html = "html"
@@ -68,16 +65,15 @@ def _is_editable_install_of_same_source(distname):
6865
return editable_path and os.path.samefile(_editable_install_path(distname), ".")
6966

7067

71-
def _set_pythonpath(quiet=False):
68+
def _set_pythonpath(build_dir, quiet=False):
7269
"""Set first entry of PYTHONPATH to site packages directory.
7370
71+
For editable installs, leave the PYTHONPATH alone.
72+
7473
Returns
7574
-------
7675
site_packages
7776
"""
78-
site_packages = _get_site_packages()
79-
env = os.environ
80-
8177
cfg = get_config()
8278
distname = cfg.get("project.name", None)
8379
if distname:
@@ -101,6 +97,9 @@ def _set_pythonpath(quiet=False):
10197
fg="bright_red",
10298
)
10399

100+
site_packages = _get_site_packages(build_dir)
101+
env = os.environ
102+
104103
if "PYTHONPATH" in env:
105104
env["PYTHONPATH"] = f"{site_packages}{os.pathsep}{env['PYTHONPATH']}"
106105
else:
@@ -114,7 +113,12 @@ def _set_pythonpath(quiet=False):
114113
return site_packages
115114

116115

117-
def _get_site_packages():
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)
118122
try:
119123
cfg = get_config()
120124
distname = cfg.get("project.name", None)
@@ -194,7 +198,7 @@ def _meson_coverage_configured() -> bool:
194198
return False
195199

196200

197-
def _check_coverage_tool_installation(coverage_type: GcovReportFormat):
201+
def _check_coverage_tool_installation(coverage_type: GcovReportFormat, build_dir):
198202
requirements = { # https://github.com/mesonbuild/meson/blob/6e381714c7cb15877e2bcaa304b93c212252ada3/docs/markdown/Unit-tests.md?plain=1#L49-L62
199203
GcovReportFormat.html: ["Gcovr/GenHTML", "lcov"],
200204
GcovReportFormat.xml: ["Gcovr (version 3.3 or higher)"],
@@ -226,6 +230,14 @@ def _check_coverage_tool_installation(coverage_type: GcovReportFormat):
226230
)
227231

228232

233+
build_dir_option = click.option(
234+
"-C",
235+
"--build-dir",
236+
default="build",
237+
help="Meson build directory; package is installed into './{build-dir}-install'",
238+
)
239+
240+
229241
@click.command()
230242
@click.option("-j", "--jobs", help="Number of parallel tasks to launch", type=int)
231243
@click.option("--clean", is_flag=True, help="Clean build directory before build")
@@ -238,8 +250,16 @@ def _check_coverage_tool_installation(coverage_type: GcovReportFormat):
238250
help="Enable C code coverage using `gcov`. Use `spin test --gcov` to generate reports.",
239251
)
240252
@click.argument("meson_args", nargs=-1)
253+
@build_dir_option
241254
def build(
242-
*, meson_args, jobs=None, clean=False, verbose=False, gcov=False, quiet=False
255+
*,
256+
meson_args,
257+
jobs=None,
258+
clean=False,
259+
verbose=False,
260+
gcov=False,
261+
quiet=False,
262+
build_dir=None,
243263
):
244264
"""🔧 Build package with Meson/ninja
245265
@@ -258,6 +278,7 @@ def build(
258278
259279
CFLAGS="-O0 -g" spin build
260280
"""
281+
install_dir = _get_install_dir(build_dir)
261282
cfg = get_config()
262283
distname = cfg.get("project.name", None)
263284
if distname and _is_editable_install_of_same_source(distname):
@@ -301,6 +322,9 @@ def build(
301322
# Any other conditions that warrant a reconfigure?
302323

303324
compile_flags = ["-v"] if verbose else []
325+
if jobs:
326+
compile_flags += ["-j", str(jobs)]
327+
304328
p = _run(
305329
_meson_cli() + ["compile"] + compile_flags + ["-C", build_dir],
306330
sys_exit=True,
@@ -372,6 +396,7 @@ def _get_configured_command(command_name):
372396
default="html",
373397
help=f"Format of the gcov report. Can be one of {', '.join(e.value for e in GcovReportFormat)}.",
374398
)
399+
@build_dir_option
375400
@click.pass_context
376401
def test(
377402
ctx,
@@ -383,6 +408,7 @@ def test(
383408
coverage=False,
384409
gcov=None,
385410
gcov_format=None,
411+
build_dir=None,
386412
):
387413
"""🔧 Run tests
388414
@@ -469,13 +495,11 @@ def test(
469495
"Invoking `build` prior to running tests:", bold=True, fg="bright_green"
470496
)
471497
if gcov is not None:
472-
ctx.invoke(build_cmd, gcov=bool(gcov))
498+
ctx.invoke(build_cmd, build_dir=build_dir, gcov=bool(gcov))
473499
else:
474-
ctx.invoke(build_cmd)
500+
ctx.invoke(build_cmd, build_dir=build_dir)
475501

476-
site_path = _set_pythonpath()
477-
if site_path:
478-
print(f'$ export PYTHONPATH="{site_path}"')
502+
site_path = _set_pythonpath(build_dir)
479503

480504
# Sanity check that library built properly
481505
#
@@ -520,6 +544,7 @@ def test(
520544
else:
521545
cmd = ["pytest"]
522546

547+
install_dir = _get_install_dir(build_dir)
523548
if not os.path.exists(install_dir):
524549
os.mkdir(install_dir)
525550

@@ -534,7 +559,7 @@ def test(
534559
bold=True,
535560
fg="bright_yellow",
536561
)
537-
_check_coverage_tool_installation(gcov_format)
562+
_check_coverage_tool_installation(gcov_format, build_dir)
538563

539564
# Generate report
540565
click.secho(
@@ -568,8 +593,9 @@ def test(
568593
@click.command()
569594
@click.option("--code", "-c", help="Python program passed in as a string")
570595
@click.argument("gdb_args", nargs=-1)
596+
@build_dir_option
571597
@click.pass_context
572-
def gdb(ctx, *, code, gdb_args):
598+
def gdb(ctx, *, code, gdb_args, build_dir):
573599
"""👾 Execute code through GDB
574600
575601
spin gdb -c 'import numpy as np; print(np.__version__)'
@@ -595,9 +621,9 @@ def gdb(ctx, *, code, gdb_args):
595621
click.secho(
596622
"Invoking `build` prior to invoking gdb:", bold=True, fg="bright_green"
597623
)
598-
ctx.invoke(build_cmd)
624+
ctx.invoke(build_cmd, build_dir=build_dir)
599625

600-
_set_pythonpath()
626+
_set_pythonpath(build_dir)
601627
gdb_args = list(gdb_args)
602628

603629
if gdb_args and gdb_args[0].endswith(".py"):
@@ -620,8 +646,9 @@ def gdb(ctx, *, code, gdb_args):
620646

621647
@click.command()
622648
@click.argument("ipython_args", nargs=-1)
649+
@build_dir_option
623650
@click.pass_context
624-
def ipython(ctx, *, ipython_args):
651+
def ipython(ctx, *, ipython_args, build_dir):
625652
"""💻 Launch IPython shell with PYTHONPATH set
626653
627654
IPYTHON_ARGS are passed through directly to IPython, e.g.:
@@ -633,18 +660,19 @@ def ipython(ctx, *, ipython_args):
633660
click.secho(
634661
"Invoking `build` prior to invoking ipython:", bold=True, fg="bright_green"
635662
)
636-
ctx.invoke(build_cmd)
663+
ctx.invoke(build_cmd, build_dir=build_dir)
637664

638-
p = _set_pythonpath()
665+
p = _set_pythonpath(build_dir)
639666
if p:
640667
print(f'💻 Launching IPython with PYTHONPATH="{p}"')
641668
_run(["ipython", "--ignore-cwd"] + list(ipython_args), replace=True)
642669

643670

644671
@click.command()
645672
@click.argument("shell_args", nargs=-1)
673+
@build_dir_option
646674
@click.pass_context
647-
def shell(ctx, shell_args=[]):
675+
def shell(ctx, shell_args=[], build_dir=None):
648676
"""💻 Launch shell with PYTHONPATH set
649677
650678
SHELL_ARGS are passed through directly to the shell, e.g.:
@@ -659,9 +687,9 @@ def shell(ctx, shell_args=[]):
659687
click.secho(
660688
"Invoking `build` prior to invoking shell:", bold=True, fg="bright_green"
661689
)
662-
ctx.invoke(build_cmd)
690+
ctx.invoke(build_cmd, build_dir=build_dir)
663691

664-
p = _set_pythonpath()
692+
p = _set_pythonpath(build_dir)
665693
if p:
666694
print(f'💻 Launching shell with PYTHONPATH="{p}"')
667695

@@ -674,8 +702,9 @@ def shell(ctx, shell_args=[]):
674702

675703
@click.command()
676704
@click.argument("python_args", nargs=-1)
705+
@build_dir_option
677706
@click.pass_context
678-
def python(ctx, *, python_args):
707+
def python(ctx, *, python_args, build_dir):
679708
"""🐍 Launch Python shell with PYTHONPATH set
680709
681710
PYTHON_ARGS are passed through directly to Python, e.g.:
@@ -687,9 +716,9 @@ def python(ctx, *, python_args):
687716
click.secho(
688717
"Invoking `build` prior to invoking Python:", bold=True, fg="bright_green"
689718
)
690-
ctx.invoke(build_cmd)
719+
ctx.invoke(build_cmd, build_dir=build_dir)
691720

692-
p = _set_pythonpath()
721+
p = _set_pythonpath(build_dir)
693722
if p:
694723
print(f'🐍 Launching Python with PYTHONPATH="{p}"')
695724

@@ -714,9 +743,10 @@ def python(ctx, *, python_args):
714743

715744

716745
@click.command(context_settings={"ignore_unknown_options": True})
746+
@build_dir_option
717747
@click.argument("args", nargs=-1)
718748
@click.pass_context
719-
def run(ctx, *, args):
749+
def run(ctx, *, args, build_dir=None):
720750
"""🏁 Run a shell command with PYTHONPATH set
721751
722752
\b
@@ -740,7 +770,7 @@ def run(ctx, *, args):
740770
# Redirect spin generated output
741771
with contextlib.redirect_stdout(sys.stderr):
742772
# Also ask build to be quiet
743-
ctx.invoke(build_cmd, quiet=True)
773+
ctx.invoke(build_cmd, build_dir=build_dir, quiet=True)
744774

745775
is_posix = sys.platform in ("linux", "darwin")
746776
shell = len(args) == 1
@@ -751,7 +781,7 @@ def run(ctx, *, args):
751781
# On Windows, we're going to try to use bash
752782
cmd_args = ["bash", "-c", cmd_args]
753783

754-
_set_pythonpath(quiet=True)
784+
_set_pythonpath(build_dir, quiet=True)
755785
p = _run(cmd_args, echo=False, shell=shell, sys_exit=False)
756786

757787
# Is the user trying to run a Python script, without calling the Python interpreter?
@@ -789,6 +819,7 @@ def run(ctx, *, args):
789819
help="Sphinx gallery: enable/disable plots",
790820
)
791821
@click.option("--jobs", "-j", default="auto", help="Number of parallel build jobs")
822+
@build_dir_option
792823
@click.pass_context
793824
def docs(
794825
ctx,
@@ -799,6 +830,7 @@ def docs(
799830
jobs,
800831
sphinx_gallery_plot,
801832
clean_dirs=None,
833+
build_dir=None,
802834
):
803835
"""📖 Build Sphinx documentation
804836
@@ -853,10 +885,10 @@ def docs(
853885
click.secho(
854886
"Invoking `build` prior to building docs:", bold=True, fg="bright_green"
855887
)
856-
ctx.invoke(build_cmd)
888+
ctx.invoke(build_cmd, build_dir=build_dir)
857889

858890
try:
859-
site_path = _get_site_packages()
891+
site_path = _get_site_packages(build_dir)
860892
except FileNotFoundError:
861893
print("No built numpy found; run `spin build` first.")
862894
sys.exit(1)
@@ -889,8 +921,9 @@ def docs(
889921
@click.command()
890922
@click.option("--code", "-c", help="Python program passed in as a string")
891923
@click.argument("lldb_args", nargs=-1)
924+
@build_dir_option
892925
@click.pass_context
893-
def lldb(ctx, *, code, lldb_args):
926+
def lldb(ctx, *, code, lldb_args, build_dir=None):
894927
"""👾 Execute code through LLDB
895928
896929
spin lldb -c 'import numpy as np; print(np.__version__)'
@@ -918,9 +951,9 @@ def lldb(ctx, *, code, lldb_args):
918951
click.secho(
919952
"Invoking `build` prior to invoking lldb:", bold=True, fg="bright_green"
920953
)
921-
ctx.invoke(build_cmd)
954+
ctx.invoke(build_cmd, build_dir=build_dir)
922955

923-
_set_pythonpath()
956+
_set_pythonpath(build_dir)
924957
lldb_args = list(lldb_args)
925958

926959
if code:

0 commit comments

Comments
 (0)