11use std:: { path:: PathBuf , str:: FromStr } ;
22
33use crate :: {
4- ClangTool , PyPiDownloadError , PyPiDownloader ,
4+ Cacher , ClangTool , PyPiDownloadError , PyPiDownloader ,
55 downloader:: { native_packages:: try_install_package, static_dist:: StaticDistDownloader } ,
66 tool:: { GetClangPathError , GetClangVersionError } ,
77 utils:: normalize_path,
@@ -92,7 +92,7 @@ impl RequestedVersion {
9292 } ) )
9393 }
9494 RequestedVersion :: SystemDefault => {
95- let path = tool. get_exe_path ( & RequestedVersion :: SystemDefault ) ?;
95+ let path = tool. get_exe_path ( & Self :: SystemDefault ) ?;
9696 let version = tool. capture_version ( & path) ?;
9797 log:: info!(
9898 "Found {tool} version {version} at path: {:?}" ,
@@ -101,9 +101,8 @@ impl RequestedVersion {
101101 Ok ( Some ( ClangVersion { version, path } ) )
102102 }
103103 RequestedVersion :: Requirement ( version_req) => {
104- if let Ok ( path) =
105- tool. get_exe_path ( & RequestedVersion :: Requirement ( version_req. clone ( ) ) )
106- {
104+ // check default available version first (if any)
105+ if let Ok ( path) = tool. get_exe_path ( & Self :: Requirement ( version_req. clone ( ) ) ) {
107106 let version = tool. capture_version ( & path) ?;
108107 if version_req. matches ( & version) {
109108 log:: info!(
@@ -113,11 +112,33 @@ impl RequestedVersion {
113112 return Ok ( Some ( ClangVersion { version, path } ) ) ;
114113 }
115114 }
115+
116+ // check if cache has a suitable version
117+ let bin_ext = if cfg ! ( windows) { ".exe" } else { "" } ;
118+ let min_ver = get_min_ver ( version_req) . ok_or ( GetToolError :: VersionMajorRequired ) ?;
119+ let cached_bin = StaticDistDownloader :: get_cache_dir ( )
120+ . join ( "bin" )
121+ . join ( format ! ( "{tool}-{min_ver}{bin_ext}" ) ) ;
122+ if cached_bin. exists ( ) {
123+ let version = tool. capture_version ( & cached_bin) ?;
124+ if version_req. matches ( & version) {
125+ log:: info!(
126+ "Found {tool} version {version} in cache at path: {:?}" ,
127+ cached_bin. to_string_lossy( )
128+ ) ;
129+ return Ok ( Some ( ClangVersion {
130+ version,
131+ path : cached_bin,
132+ } ) ) ;
133+ }
134+ }
135+
136+ // try to download a suitable version
116137 let bin = match PyPiDownloader :: download_tool ( tool, version_req) . await {
117138 Ok ( bin) => bin,
118139 Err ( e) => {
119140 log:: error!( "Failed to download {tool} {version_req} from PyPi: {e}" ) ;
120- if let Some ( result) = try_install_package ( tool, version_req) ? {
141+ if let Some ( result) = try_install_package ( tool, version_req, & min_ver ) ? {
121142 return Ok ( Some ( result) ) ;
122143 }
123144 log:: info!( "Falling back to downloading {tool} static binaries." ) ;
@@ -132,9 +153,10 @@ impl RequestedVersion {
132153 }
133154 }
134155 } ;
156+
157+ // create a symlink
135158 let bin_dir = bin. parent ( ) . ok_or ( GetToolError :: ExecutablePathNoParent ) ?;
136- let symlink_path =
137- bin_dir. join ( format ! ( "{tool}{}" , if cfg!( windows) { ".exe" } else { "" } ) ) ;
159+ let symlink_path = bin_dir. join ( format ! ( "{tool}{bin_ext}" ) ) ;
138160 tool. symlink_bin ( & bin, & symlink_path, overwrite_symlink)
139161 . map_err ( GetToolError :: SymlinkError ) ?;
140162 let version = tool. capture_version ( & bin) ?;
@@ -152,6 +174,25 @@ impl RequestedVersion {
152174 }
153175}
154176
177+ pub fn get_min_ver ( version_req : & VersionReq ) -> Option < Version > {
178+ let mut result = None ;
179+ for cmp in & version_req. comparators {
180+ if matches ! ( cmp. op, semver:: Op :: Exact | semver:: Op :: Caret ) {
181+ let ver = Version {
182+ major : cmp. major ,
183+ minor : cmp. minor . unwrap_or ( 0 ) ,
184+ patch : cmp. patch . unwrap_or ( 0 ) ,
185+ pre : cmp. pre . clone ( ) ,
186+ build : Default :: default ( ) ,
187+ } ;
188+ if result. as_ref ( ) . is_none_or ( |r| ver < * r) {
189+ result = Some ( ver) ;
190+ }
191+ }
192+ }
193+ result
194+ }
195+
155196/// Represents an error that occurred while parsing a requested version.
156197#[ derive( Debug , thiserror:: Error ) ]
157198pub enum RequestedVersionParsingError {
@@ -272,13 +313,10 @@ mod tests {
272313 /// It is designed to use the system's package manager to install clang-tidy.
273314 /// If successful, clang-tidy will be installed globally, which may be undesirable.
274315 #[ tokio:: test]
275- async fn eval_static_dist ( ) {
276- let tmp_cache_dir = TempDir :: new ( ) . unwrap ( ) ;
277- unsafe {
278- std:: env:: set_var ( "CPP_LINTER_CACHE" , tmp_cache_dir. path ( ) ) ;
279- }
316+ async fn eval_version ( ) {
317+ let clang_version = option_env ! ( "CLANG_VERSION" ) . unwrap_or ( "12.0.1" ) ;
280318 let tool = ClangTool :: ClangTidy ;
281- let version_req = VersionReq :: parse ( "=12.0.1" ) . unwrap ( ) ;
319+ let version_req = VersionReq :: parse ( clang_version ) . unwrap ( ) ;
282320 let clang_path = RequestedVersion :: Requirement ( version_req. clone ( ) )
283321 . eval_tool ( & tool, false )
284322 . await
0 commit comments