Skip to content

Commit 0c25ec4

Browse files
authored
Merge pull request #783 from stan-dev/improve-validation
CmdStan path validation only requires makefile
2 parents db3087a + 4fb655a commit 0c25ec4

File tree

3 files changed

+46
-34
lines changed

3 files changed

+46
-34
lines changed

cmdstanpy/compilation.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
cmdstan_path,
2020
cmdstan_version,
2121
cmdstan_version_before,
22+
stanc_path,
2223
)
2324
from cmdstanpy.utils.command import do_command
2425
from cmdstanpy.utils.filesystem import SanitizedOrTmpFilePath
@@ -351,7 +352,7 @@ def src_info(
351352
:meth:`CmdStanModel.src_info`, and should not be called directly.
352353
"""
353354
cmd = (
354-
[os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)]
355+
[stanc_path()]
355356
# handle include-paths, allow-undefined etc
356357
+ compiler_options.compose_stanc(None)
357358
+ ['--info', str(stan_file)]
@@ -518,7 +519,7 @@ def format_stan_file(
518519

519520
try:
520521
cmd = (
521-
[os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)]
522+
[stanc_path()]
522523
# handle include-paths, allow-undefined etc
523524
+ CompilerOptions(stanc_options=stanc_options).compose_stanc(None)
524525
+ [str(stan_file)]

cmdstanpy/utils/cmdstan.py

+20-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Utilities for finding and installing CmdStan
33
"""
4+
45
import os
56
import platform
67
import subprocess
@@ -133,15 +134,29 @@ def validate_cmdstan_path(path: str) -> None:
133134
"""
134135
if not os.path.isdir(path):
135136
raise ValueError(f'No CmdStan directory, path {path} does not exist.')
136-
if not os.path.exists(os.path.join(path, 'bin', 'stanc' + EXTENSION)):
137+
if not os.path.exists(os.path.join(path, 'makefile')):
137138
raise ValueError(
138-
f'CmdStan installataion missing binaries in {path}/bin. '
139-
'Re-install cmdstan by running command "install_cmdstan '
140-
'--overwrite", or Python code "import cmdstanpy; '
141-
'cmdstanpy.install_cmdstan(overwrite=True)"'
139+
f'CmdStan installataion missing makefile, path {path} is invalid.'
140+
' You may wish to re-install cmdstan by running command '
141+
'"install_cmdstan --overwrite", or Python code '
142+
'"import cmdstanpy; cmdstanpy.install_cmdstan(overwrite=True)"'
142143
)
143144

144145

146+
def stanc_path() -> str:
147+
"""
148+
Returns the path to the stanc executable in the CmdStan installation.
149+
"""
150+
cmdstan = cmdstan_path()
151+
stanc_exe = os.path.join(cmdstan, 'bin', 'stanc' + EXTENSION)
152+
if not os.path.exists(stanc_exe):
153+
raise ValueError(
154+
f'stanc executable not found in CmdStan installation: {cmdstan}.\n'
155+
'You may need to re-install or re-build CmdStan.',
156+
)
157+
return stanc_exe
158+
159+
145160
def set_cmdstan_path(path: str) -> None:
146161
"""
147162
Validate, then set CmdStan directory path.
@@ -200,13 +215,6 @@ def cmdstan_version() -> Optional[Tuple[int, ...]]:
200215
get_logger().debug("%s", e)
201216
return None
202217

203-
if not os.path.exists(makefile):
204-
get_logger().info(
205-
'CmdStan installation %s missing makefile, cannot get version.',
206-
cmdstan_path(),
207-
)
208-
return None
209-
210218
with open(makefile, 'r') as fd:
211219
contents = fd.read()
212220

test/test_utils.py

+23-20
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
import os
88
import pathlib
99
import platform
10-
import random
1110
import re
1211
import shutil
1312
import stat
14-
import string
1513
import tempfile
1614
from test import check_present, mark_windows_only, raises_nested
1715
from unittest import mock
@@ -43,6 +41,7 @@
4341
validate_dir,
4442
windows_short_path,
4543
)
44+
from cmdstanpy.utils.cmdstan import stanc_path
4645
from cmdstanpy.utils.filesystem import temp_inits, temp_single_json
4746

4847
HERE = os.path.dirname(os.path.abspath(__file__))
@@ -134,6 +133,16 @@ def test_set_path() -> None:
134133
assert os.path.samefile(install_version, os.environ['CMDSTAN'])
135134

136135

136+
@contextlib.contextmanager
137+
def temporary_cmdstan_path(path: str) -> None:
138+
prev = cmdstan_path()
139+
try:
140+
set_cmdstan_path(path)
141+
yield
142+
finally:
143+
set_cmdstan_path(prev)
144+
145+
137146
def test_validate_path() -> None:
138147
if 'CMDSTAN' in os.environ:
139148
install_version = os.environ.get('CMDSTAN')
@@ -150,20 +159,18 @@ def test_validate_path() -> None:
150159
with pytest.raises(ValueError, match='No CmdStan directory'):
151160
validate_cmdstan_path(path_foo)
152161

153-
folder_name = ''.join(
154-
random.choice(string.ascii_letters) for _ in range(10)
155-
)
156-
while os.path.exists(folder_name):
157-
folder_name = ''.join(
158-
random.choice(string.ascii_letters) for _ in range(10)
159-
)
160-
folder = pathlib.Path(folder_name)
161-
folder.mkdir(parents=True)
162-
(folder / "makefile").touch()
162+
with tempfile.TemporaryDirectory() as tmpdir:
163+
folder = pathlib.Path(tmpdir)
164+
with pytest.raises(ValueError, match='missing makefile'):
165+
validate_cmdstan_path(str(folder.absolute()))
163166

164-
with pytest.raises(ValueError, match='missing binaries'):
165-
validate_cmdstan_path(str(folder.absolute()))
166-
shutil.rmtree(folder)
167+
(folder / "makefile").touch()
168+
with temporary_cmdstan_path(str(folder.absolute())):
169+
with pytest.raises(
170+
ValueError,
171+
match='stanc executable not found in CmdStan installation',
172+
):
173+
stanc_path()
167174

168175

169176
def test_validate_dir() -> None:
@@ -216,7 +223,6 @@ def test_cmdstan_version(caplog: pytest.LogCaptureFixture) -> None:
216223
fake_bin.mkdir(parents=True)
217224
fake_makefile = fake_path / 'makefile'
218225
fake_makefile.touch()
219-
(fake_bin / f'stanc{EXTENSION}').touch()
220226
with mock.patch.dict("os.environ", CMDSTAN=str(fake_path)):
221227
assert str(fake_path) == cmdstan_path()
222228
with open(fake_makefile, 'w') as fd:
@@ -230,10 +236,7 @@ def test_cmdstan_version(caplog: pytest.LogCaptureFixture) -> None:
230236
check_present(caplog, ('cmdstanpy', 'INFO', expect))
231237

232238
fake_makefile.unlink()
233-
expect = (
234-
'CmdStan installation {} missing makefile, '
235-
'cannot get version.'.format(fake_path)
236-
)
239+
expect = 'No CmdStan installation found.'
237240
with caplog.at_level(logging.INFO):
238241
cmdstan_version()
239242
check_present(caplog, ('cmdstanpy', 'INFO', expect))

0 commit comments

Comments
 (0)