Skip to content

Commit cc0f63f

Browse files
committed
Merge branch 'develop' of https://github.com/stan-dev/cmdstanpy into develop
2 parents 9197f6a + f441fae commit cc0f63f

4 files changed

Lines changed: 156 additions & 92 deletions

File tree

cmdstanpy/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def _cleanup_tmpdir() -> None:
3636

3737

3838
from ._version import __version__ # noqa
39+
from .install_cmdstan import rebuild_cmdstan
3940
from .model import CmdStanModel # noqa
4041
from .stanfit import ( # noqa
4142
CmdStanGQ,
@@ -68,4 +69,5 @@ def _cleanup_tmpdir() -> None:
6869
'from_csv',
6970
'write_stan_json',
7071
'show_versions',
72+
'rebuild_cmdstan',
7173
]

cmdstanpy/install_cmdstan.py

Lines changed: 143 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,17 @@
3131
from typing import Callable, Dict, Optional
3232

3333
from cmdstanpy import _DOT_CMDSTAN, _DOT_CMDSTANPY
34-
from cmdstanpy.utils import get_logger, pushd, validate_dir, wrap_progress_hook
35-
34+
from cmdstanpy.utils import (
35+
cmdstan_path,
36+
get_logger,
37+
pushd,
38+
validate_dir,
39+
wrap_progress_hook,
40+
)
41+
42+
MAKE = os.getenv(
43+
'MAKE', 'make' if platform.system() != 'Windows' else 'mingw32-make'
44+
)
3645
EXTENSION = '.exe' if platform.system() == 'Windows' else ''
3746

3847

@@ -63,6 +72,135 @@ def usage() -> None:
6372
print(msg)
6473

6574

75+
def clean_all(verbose: bool = False) -> None:
76+
"""
77+
Run `make clean-all` in the current directory (must be a cmdstan library).
78+
79+
:param verbose: when ``True``, print build msgs to stdout.
80+
"""
81+
cmd = [MAKE, 'clean-all']
82+
proc = subprocess.Popen(
83+
cmd,
84+
cwd=None,
85+
stdin=subprocess.DEVNULL,
86+
stdout=subprocess.PIPE,
87+
stderr=subprocess.PIPE,
88+
env=os.environ,
89+
)
90+
while proc.poll() is None:
91+
if proc.stdout:
92+
output = proc.stdout.readline().decode('utf-8').strip()
93+
if verbose and output:
94+
print(output, flush=True)
95+
_, stderr = proc.communicate()
96+
if proc.returncode:
97+
msgs = ['Command "make clean-all" failed']
98+
if stderr:
99+
msgs.append(stderr.decode('utf-8').strip())
100+
raise CmdStanInstallError('\n'.join(msgs))
101+
102+
103+
def build(verbose: bool = False) -> None:
104+
"""
105+
Run `make build` in the current directory (must be a cmdstan library)
106+
107+
:param verbose: when ``True``, print build msgs to stdout.
108+
"""
109+
cmd = [MAKE, 'build']
110+
proc = subprocess.Popen(
111+
cmd,
112+
cwd=None,
113+
stdin=subprocess.DEVNULL,
114+
stdout=subprocess.PIPE,
115+
stderr=subprocess.PIPE,
116+
env=os.environ,
117+
)
118+
while proc.poll() is None:
119+
if proc.stdout:
120+
output = proc.stdout.readline().decode('utf-8').strip()
121+
if verbose and output:
122+
print(output, flush=True)
123+
_, stderr = proc.communicate()
124+
if proc.returncode:
125+
msgs = ['Command "make build" failed']
126+
if stderr:
127+
msgs.append(stderr.decode('utf-8').strip())
128+
raise CmdStanInstallError('\n'.join(msgs))
129+
if not os.path.exists(os.path.join('bin', 'stansummary' + EXTENSION)):
130+
raise CmdStanInstallError(
131+
f'bin/stansummary{EXTENSION} not found'
132+
', please rebuild or report a bug!'
133+
)
134+
if not os.path.exists(os.path.join('bin', 'diagnose' + EXTENSION)):
135+
raise CmdStanInstallError(
136+
f'bin/stansummary{EXTENSION} not found'
137+
', please rebuild or report a bug!'
138+
)
139+
if platform.system() == 'Windows':
140+
# Add tbb to the $PATH on Windows
141+
libtbb = os.path.join(
142+
os.getcwd(), 'stan', 'lib', 'stan_math', 'lib', 'tbb'
143+
)
144+
os.environ['PATH'] = ';'.join(
145+
list(
146+
OrderedDict.fromkeys(
147+
[libtbb] + os.environ.get('PATH', '').split(';')
148+
)
149+
)
150+
)
151+
152+
153+
def compile_example() -> None:
154+
"""
155+
Compile the example model.
156+
The current directory must be a cmdstan library.
157+
"""
158+
cmd = [
159+
MAKE,
160+
Path(
161+
os.path.join('examples', 'bernoulli', 'bernoulli' + EXTENSION)
162+
).as_posix(),
163+
]
164+
proc = subprocess.Popen(
165+
cmd,
166+
cwd=None,
167+
stdin=subprocess.DEVNULL,
168+
stdout=subprocess.PIPE,
169+
stderr=subprocess.PIPE,
170+
env=os.environ,
171+
)
172+
while proc.poll() is None:
173+
if proc.stdout:
174+
proc.stdout.readline().decode('utf-8')
175+
_, stderr = proc.communicate()
176+
if proc.returncode:
177+
msgs = ['Failed to compile example model bernoulli.stan']
178+
if stderr:
179+
msgs.append(stderr.decode('utf-8').strip())
180+
raise CmdStanInstallError('\n'.join(msgs))
181+
182+
183+
def rebuild_cmdstan(verbose: bool = True) -> None:
184+
"""
185+
Rebuilds the existing CmdStan installation.
186+
This assumes CmdStan has already been installed,
187+
though it need not be installed via CmdStanPy for
188+
this function to work.
189+
190+
:param verbose: Boolean value; when ``True``, output from CmdStan build
191+
processes will be streamed to the console. Default is ``False``.
192+
"""
193+
try:
194+
with pushd(cmdstan_path()):
195+
clean_all(verbose)
196+
build(verbose)
197+
compile_example()
198+
except ValueError as e:
199+
raise CmdStanInstallError(
200+
"Failed to rebuild CmdStan. Are you sure it is installed?"
201+
) from e
202+
203+
66204
def install_version(
67205
cmdstan_version: str, overwrite: bool = False, verbose: bool = False
68206
) -> None:
@@ -76,103 +214,17 @@ def install_version(
76214
:param verbose: when ``True``, print build msgs to stdout.
77215
"""
78216
with pushd(cmdstan_version):
79-
make = os.getenv(
80-
'MAKE', 'make' if platform.system() != 'Windows' else 'mingw32-make'
81-
)
82217
print('Building version {}'.format(cmdstan_version))
83218
if overwrite:
84219
print(
85220
'Overwrite requested, remove existing build of version '
86221
'{}'.format(cmdstan_version)
87222
)
88-
cmd = [make, 'clean-all']
89-
proc = subprocess.Popen(
90-
cmd,
91-
cwd=None,
92-
stdin=subprocess.DEVNULL,
93-
stdout=subprocess.PIPE,
94-
stderr=subprocess.PIPE,
95-
env=os.environ,
96-
)
97-
while proc.poll() is None:
98-
if proc.stdout:
99-
output = proc.stdout.readline().decode('utf-8').strip()
100-
if verbose and output:
101-
print(output, flush=True)
102-
_, stderr = proc.communicate()
103-
if proc.returncode:
104-
msgs = ['Command "make clean-all" failed']
105-
if stderr:
106-
msgs.append(stderr.decode('utf-8').strip())
107-
raise CmdStanInstallError('\n'.join(msgs))
223+
clean_all(verbose)
108224
print('Rebuilding version {}'.format(cmdstan_version))
109-
cmd = [make, 'build']
110-
proc = subprocess.Popen(
111-
cmd,
112-
cwd=None,
113-
stdin=subprocess.DEVNULL,
114-
stdout=subprocess.PIPE,
115-
stderr=subprocess.PIPE,
116-
env=os.environ,
117-
)
118-
while proc.poll() is None:
119-
if proc.stdout:
120-
121-
output = proc.stdout.readline().decode('utf-8').strip()
122-
if verbose and output:
123-
print(output, flush=True)
124-
_, stderr = proc.communicate()
125-
if proc.returncode:
126-
msgs = ['Command "make build" failed']
127-
if stderr:
128-
msgs.append(stderr.decode('utf-8').strip())
129-
raise CmdStanInstallError('\n'.join(msgs))
130-
if not os.path.exists(os.path.join('bin', 'stansummary' + EXTENSION)):
131-
raise CmdStanInstallError(
132-
f"bin/stansummary{EXTENSION} not found"
133-
", please rebuild or report a bug!"
134-
)
135-
if not os.path.exists(os.path.join('bin', 'diagnose' + EXTENSION)):
136-
raise CmdStanInstallError(
137-
f"bin/stansummary{EXTENSION} not found"
138-
", please rebuild or report a bug!"
139-
)
225+
build(verbose)
140226
print('Test model compilation')
141-
cmd = [
142-
make,
143-
Path(
144-
os.path.join('examples', 'bernoulli', 'bernoulli' + EXTENSION)
145-
).as_posix(),
146-
]
147-
if platform.system() == "Windows":
148-
# Add tbb to the $PATH on Windows
149-
libtbb = os.path.join(
150-
os.getcwd(), 'stan', 'lib', 'stan_math', 'lib', 'tbb'
151-
)
152-
os.environ['PATH'] = ';'.join(
153-
list(
154-
OrderedDict.fromkeys(
155-
[libtbb] + os.environ.get('PATH', '').split(';')
156-
)
157-
)
158-
)
159-
proc = subprocess.Popen(
160-
cmd,
161-
cwd=None,
162-
stdin=subprocess.DEVNULL,
163-
stdout=subprocess.PIPE,
164-
stderr=subprocess.PIPE,
165-
env=os.environ,
166-
)
167-
while proc.poll() is None:
168-
if proc.stdout:
169-
proc.stdout.readline().decode('utf-8')
170-
_, stderr = proc.communicate()
171-
if proc.returncode:
172-
msgs = ['Failed to compile example model bernoulli.stan']
173-
if stderr:
174-
msgs.append(stderr.decode('utf-8').strip())
175-
raise CmdStanInstallError('\n'.join(msgs))
227+
compile_example()
176228
print('Installed {}'.format(cmdstan_version))
177229

178230

cmdstanpy/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ def compile(
348348
"CmdStan's precompiled header (PCH) files "
349349
"may need to be rebuilt."
350350
"If your model failed to compile please run "
351-
"install_cmdstan(overwrite=True).\nIf the "
351+
"cmdstanpy.rebuild_cmdstan().\nIf the "
352352
"issue persists please open a bug report",
353353
)
354354

test/test_install_cmdstan.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""install_cmdstan test"""
22

3+
import os
34
import unittest
45

56
from cmdstanpy.install_cmdstan import (
7+
CmdStanInstallError,
68
CmdStanRetrieveError,
79
is_version_available,
810
latest_version,
11+
rebuild_cmdstan,
912
retrieve_version,
1013
)
1114

@@ -34,6 +37,13 @@ def test_retrieve_version(self):
3437
with self.assertRaises(ValueError):
3538
retrieve_version('')
3639

40+
@unittest.mock.patch.dict(os.environ, {"CMDSTAN": "~/some/fake/path"})
41+
def test_rebuild_bad_path(self):
42+
with self.assertRaisesRegex(
43+
CmdStanInstallError, "you sure it is installed"
44+
):
45+
rebuild_cmdstan()
46+
3747

3848
if __name__ == '__main__':
3949
unittest.main()

0 commit comments

Comments
 (0)