1313from .util import get_commands , get_config
1414from .util import run as _run
1515
16- install_dir = "build-install"
17- build_dir = "build"
18-
1916
2017class 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
241254def 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
376401def 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
793824def 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