@@ -84,38 +84,43 @@ def get_latest_cmdstan(cmdstan_dir: str) -> Optional[str]:
8484 """
8585 Given a valid directory path, find all installed CmdStan versions
8686 and return highest (i.e., latest) version number.
87- Assumes directory populated via `install_cmdstan`.
87+
88+ Assumes directory consists of CmdStan releases, created by
89+ function `install_cmdstan`, and therefore dirnames have format
90+ "cmdstan-<maj>.<min>.<patch>" or "cmdstan-<maj>.<min>.<patch>-rc<num>",
91+ which is CmdStan release practice as of v 2.24.
8892 """
8993 versions = [
90- '' . join ( name . split ( '-' )[ 1 :]) # name may contain '-rc'
94+ name [ 8 :]
9195 for name in os .listdir (cmdstan_dir )
9296 if os .path .isdir (os .path .join (cmdstan_dir , name ))
9397 and name .startswith ('cmdstan-' )
9498 and name [8 ].isdigit ()
99+ and len (name [8 :].split ('.' )) == 3
95100 ]
96- # munge rc for sort, e.g. 2.25.0-rc1 -> 2.25.0.-99
101+ if len (versions ) == 0 :
102+ return None
103+ # munge rc for sort, e.g. 2.25.0-rc1 -> 2.25.-99
97104 for i in range (len (versions )): # # pylint: disable=C0200
98- tmp = versions [i ]. split ( 'rc' )
99- if len ( tmp ) == 1 :
100- versions [ i ] = '.' . join ([ tmp [ 0 ], '0' ] )
101- else :
102- rc_sortable = str (int (tmp [ 1 ] ) - 100 )
103- versions [i ] = '.' .join ([tmp [0 ], rc_sortable ])
105+ if '-rc' in versions [i ]:
106+ comps = versions [ i ]. split ( '-rc' )
107+ mmp = comps [ 0 ]. split ( '.' )
108+ rc_num = comps [ 1 ]
109+ patch = str (int (rc_num ) - 100 )
110+ versions [i ] = '.' .join ([mmp [0 ], mmp [ 1 ], patch ])
104111
105112 versions .sort (key = lambda s : list (map (int , s .split ('.' ))))
106- if len (versions ) == 0 :
107- return None
108- latest = 'cmdstan-{}' .format (versions [len (versions ) - 1 ])
113+ latest = versions [len (versions ) - 1 ]
109114
110- # unmunge
111- tmp = latest .split ('.' )
112- prefix = '.' . join ( tmp [ 0 : 3 ])
113- if int ( tmp [ 3 ]) == 0 :
114- latest = prefix
115- else :
116- tmp [ 3 ] = 'rc' + str ( int ( tmp [ 3 ]) + 100 )
117- latest = '-' . join ([ prefix , tmp [ 3 ]])
118- return latest
115+ # unmunge as needed
116+ mmp = latest .split ('.' )
117+ if int ( mmp [ 2 ]) < 0 :
118+ print ( "here" )
119+ rc_num = str ( int ( mmp [ 2 ]) + 100 )
120+ mmp [ 2 ] = "0-rc" + rc_num
121+ latest = '.' . join ( mmp )
122+
123+ return 'cmdstan-' + latest
119124
120125
121126def validate_cmdstan_path (path : str ) -> None :
@@ -177,48 +182,75 @@ def cmdstan_path() -> str:
177182 return cmdstan
178183
179184
180- def cmdstan_version_at ( maj : int , min : int ) -> bool :
185+ def cmdstan_version ( ) -> Optional [ Tuple [ int , ...]] :
181186 """
182- Check that CmdStan version is at or above Maj.min version.
183- Parses version string out of CmdStan makefile in CmdStan path dir .
187+ Parses version string out of CmdStan makefile variable CMDSTAN_VERSION,
188+ returns Tuple(Major, minor) .
184189
185- :param maj: Major version number
186- :param min: Minor version number
187-
188- :return: True if version at or above, else False
190+ If CmdStan installation is not found or cannot parse version from makefile
191+ logs warning and returns None. Lenient behavoir required for CI tests,
192+ per comment:
193+ https://github.com/stan-dev/cmdstanpy/pull/321#issuecomment-733817554
189194 """
190- # pylint:disable=bare-except
191195 try :
192- path = cmdstan_path ()
193- makefile = os .path .join (path , 'makefile' )
194- if not os .path .exists (makefile ):
195- raise ValueError (
196- 'CmdStan installation {}: missing makefile' .format (path )
197- )
198- version = None
199- with open (makefile , 'r' ) as fd :
200- contents = fd .read ()
201- start_idx = contents .find ('CMDSTAN_VERSION := ' ) + len (
202- 'CMDSTAN_VERSION := '
203- )
204- end_idx = contents .find ('\n ' , start_idx )
205- version = contents [start_idx :end_idx ]
206- if version is None :
207- raise ValueError (
208- 'Cannot parse version from makefile: {}' .format (makefile )
209- )
210- splits = version .split ('.' )
211- if len (splits ) < 2 :
212- raise ValueError (
213- 'Cannot parse version from makefile: {}' .format (makefile )
214- )
215- cur_maj = int (splits [0 ])
216- cur_min = int (splits [1 ])
196+ makefile = os .path .join (cmdstan_path (), 'makefile' )
197+ except ValueError :
198+ get_logger ().info ('No CmdStan installation found.' )
199+ return None
217200
218- if cur_maj > maj or (cur_maj == maj and cur_min >= min ):
219- return True
220- except : # noqa
221- pass
201+ if not os .path .exists (makefile ):
202+ get_logger ().info (
203+ 'CmdStan installation %s missing makefile, cannot get version.' ,
204+ cmdstan_path (),
205+ )
206+ return None
207+
208+ with open (makefile , 'r' ) as fd :
209+ contents = fd .read ()
210+
211+ start_idx = contents .find ('CMDSTAN_VERSION := ' )
212+ if start_idx < 0 :
213+ get_logger ().info (
214+ 'Cannot parse version from makefile: %s.' ,
215+ makefile ,
216+ )
217+ return None
218+
219+ start_idx += len ('CMDSTAN_VERSION := ' )
220+ end_idx = contents .find ('\n ' , start_idx )
221+
222+ version = contents [start_idx :end_idx ]
223+ splits = version .split ('.' )
224+ if len (splits ) != 3 :
225+ get_logger ().info (
226+ 'Cannot parse version, expected "<major>.<minor>.<patch>", '
227+ 'found: "%s".' ,
228+ version ,
229+ )
230+ return None
231+ return tuple (int (x ) for x in splits [0 :2 ])
232+
233+
234+ def cmdstan_version_before (major : int , minor : int ) -> bool :
235+ """
236+ Check that CmdStan version is less than Major.minor version.
237+
238+ :param major: Major version number
239+ :param minor: Minor version number
240+
241+
242+ :return: True if version at or above major.minor, else False.
243+ """
244+ cur_version = cmdstan_version ()
245+ if cur_version is None :
246+ get_logger ().info (
247+ 'Cannot determine whether version is before %d.%d.' , major , minor
248+ )
249+ return False
250+ if cur_version [0 ] < major or (
251+ cur_version [0 ] == major and cur_version [1 ] < minor
252+ ):
253+ return True
222254 return False
223255
224256
0 commit comments