apple_sdk/search.rs
1// Copyright 2022 Gregory Szorc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use {
10 crate::{
11 command_line_tools_sdks_directory, AppleSdk, DeveloperDirectory, Error, Platform,
12 PlatformDirectory, SdkPath, SdkVersion,
13 },
14 std::{
15 cmp::Ordering,
16 collections::HashSet,
17 fmt::{Display, Formatter},
18 path::PathBuf,
19 },
20};
21
22/// The search location that a [SdkSearchLocation] normalizes to.
23enum SdkSearchResolvedLocation {
24 /// Nothing.
25 None,
26 /// A collection of platform directories.
27 PlatformDirectories(Vec<PlatformDirectory>),
28 /// A directory holding SDKs.
29 SdksDirectory(PathBuf),
30 /// A specific directory with an SDK.
31 SdkDirectory(PathBuf),
32 /// A specified directory with an SDK excluded from SDK filtering.
33 SdkDirectoryUnfiltered(PathBuf),
34}
35
36impl SdkSearchResolvedLocation {
37 fn apply_sdk_filter(&self) -> bool {
38 !matches!(self, Self::SdkDirectoryUnfiltered(_))
39 }
40}
41
42/// Represents a location to search for SDKs.
43#[derive(Clone, Debug)]
44pub enum SdkSearchLocation {
45 /// Use the path specified by the `SDKROOT` environment variable.
46 ///
47 /// If this environment variable is defined and the path is not valid, an error
48 /// occurs.
49 ///
50 /// An SDK yielded from this location skips search filtering. This is because the
51 /// semantic intent of the `SDKROOT` environment variable is to force usage of a
52 /// specific SDK.
53 ///
54 /// If this location yields an SDK, the SDK search will be aborted and subsequent
55 /// locations will not be searched. This effectively honors the intent of `SDKROOT`
56 /// to force usage of a given SDK.
57 ///
58 /// If this behavior is not desirable, construct an [SdkSearch] with a
59 /// [SdkSearchLocation::Sdk] using the value of `SDKROOT`.
60 SdkRootEnv,
61
62 /// Use the Developer Directory specified by the `DEVELOPER_DIR` environment variable.
63 ///
64 /// If this environment variable is defined and the path is not valid, an error
65 /// occurs.
66 ///
67 /// If this location yields an SDK, the SDK search will be aborted and subsequent
68 /// locations will not be searched. This effectively honors the intent of `DEVELOPER_DIR`
69 /// to explicitly define a developer directory to use for SDK searching.
70 ///
71 /// If this behavior is not desirable, construct an [SdkSearch] with a
72 /// [SdkSearchLocation::Developer] using the value of `DEVELOPER_DIR`.
73 DeveloperDirEnv,
74
75 /// Look for SDKs within the system installed `Xcode` application.
76 ///
77 /// This effectively controls whether the Developer Directory resolved by
78 /// [DeveloperDirectory::default_xcode()] will be searched, if available.
79 SystemXcode,
80
81 /// Look for SDKs within the system install `Xcode Command Line Tools` installation.
82 ///
83 /// This effectively uses the directory returned by [command_line_tools_sdks_directory()],
84 /// if available.
85 CommandLineTools,
86
87 /// Check the paths configured by `xcode-select --switch`.
88 ///
89 /// This effectively controls whether the Developer Directory resolved by
90 /// [DeveloperDirectory::from_xcode_select_paths()] will be searched, if available.
91 XcodeSelectPaths,
92
93 /// Invoke `xcode-select` to find a *Developer Directory* to search.
94 ///
95 /// This mechanism is intended as a fallback in case other (pure Rust) mechanisms for locating
96 /// the default *Developer Directory* fail. If you find yourself needing this, it likely
97 /// points to a gap in our feature coverage to locate the default *Developer Directory* without
98 /// running external tools. Consider filing a bug against this crate to track closing the
99 /// feature gap.
100 XcodeSelect,
101
102 /// Look for SDKs within all system installed `Xcode` applications.
103 ///
104 /// This effectively controls whether the paths resolved by
105 /// [DeveloperDirectory::find_system_xcodes()] will be searched, if present.
106 ///
107 /// Many macOS systems only have a single Xcode application at `/Applications/Xcode.app`.
108 /// However, environments like CI workers and developers having beta versions of Xcode installed
109 /// may have multiple versions of Xcode available. This option can enable multiple copies
110 /// of Xcode to be used.
111 SystemXcodes,
112
113 /// Use an explicit *Developer Directory*.
114 ///
115 /// This can be used to point a search at a non-standard location holding a *Developer
116 /// Directory*. A common use case for this is when cross-compiling or using hermetic / chroot /
117 /// container build environments that don't resemble a common macOS system layout and therefore
118 /// prohibit use of mechanisms for locating a *Developer Directory* in default locations.
119 Developer(DeveloperDirectory),
120
121 /// Use an explicit directory holding SDKs.
122 ///
123 /// This is similar to [Self::Developer] with regards to its intended use cases. The difference
124 /// is the path is a directory holding `*.sdk` directories, not a *Developer Directory*.
125 Sdks(PathBuf),
126
127 /// Use an explicit directory holding an SDK.
128 Sdk(PathBuf),
129}
130
131impl Display for SdkSearchLocation {
132 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
133 match self {
134 Self::SdkRootEnv => f.write_str("SDKROOT environment variable"),
135 Self::DeveloperDirEnv => f.write_str("DEVELOPER_DIR environment variable"),
136 Self::SystemXcode => f.write_str("System-installed Xcode application"),
137 Self::CommandLineTools => f.write_str("Xcode Command Line Tools installation"),
138 Self::XcodeSelectPaths => {
139 f.write_str("Internal xcode-select paths (`/var/db/xcode_select_link`)")
140 }
141 Self::XcodeSelect => f.write_str("xcode-select"),
142 Self::SystemXcodes => f.write_str("All system-installed Xcode applications"),
143 Self::Developer(dir) => {
144 f.write_fmt(format_args!("Developer Directory {}", dir.path().display()))
145 }
146 Self::Sdks(path) => f.write_fmt(format_args!("SDKs directory {}", path.display())),
147 Self::Sdk(path) => f.write_fmt(format_args!("SDK directory {}", path.display())),
148 }
149 }
150}
151
152impl SdkSearchLocation {
153 /// Whether this search location is terminal.
154 fn is_terminal(&self) -> bool {
155 matches!(self, Self::SdkRootEnv | Self::DeveloperDirEnv)
156 }
157
158 fn resolve_location(&self) -> Result<SdkSearchResolvedLocation, Error> {
159 match self {
160 Self::SdkRootEnv => {
161 if let Some(path) = std::env::var_os("SDKROOT") {
162 let path = PathBuf::from(path);
163
164 if path.exists() {
165 Ok(SdkSearchResolvedLocation::SdkDirectoryUnfiltered(path))
166 } else {
167 Err(Error::PathNotSdk(path))
168 }
169 } else {
170 Ok(SdkSearchResolvedLocation::None)
171 }
172 }
173 Self::DeveloperDirEnv => {
174 if let Some(dir) = DeveloperDirectory::from_env()? {
175 Ok(SdkSearchResolvedLocation::PlatformDirectories(
176 dir.platforms()?,
177 ))
178 } else {
179 Ok(SdkSearchResolvedLocation::None)
180 }
181 }
182 Self::SystemXcode => {
183 if let Some(dir) = DeveloperDirectory::default_xcode() {
184 Ok(SdkSearchResolvedLocation::PlatformDirectories(
185 dir.platforms()?,
186 ))
187 } else {
188 Ok(SdkSearchResolvedLocation::None)
189 }
190 }
191 Self::CommandLineTools => {
192 if let Some(path) = command_line_tools_sdks_directory() {
193 Ok(SdkSearchResolvedLocation::SdksDirectory(path))
194 } else {
195 Ok(SdkSearchResolvedLocation::None)
196 }
197 }
198 Self::XcodeSelectPaths => {
199 if let Some(dir) = DeveloperDirectory::from_xcode_select_paths()? {
200 Ok(SdkSearchResolvedLocation::PlatformDirectories(
201 dir.platforms()?,
202 ))
203 } else {
204 Ok(SdkSearchResolvedLocation::None)
205 }
206 }
207 Self::XcodeSelect => Ok(SdkSearchResolvedLocation::PlatformDirectories(
208 DeveloperDirectory::from_xcode_select()?.platforms()?,
209 )),
210 Self::SystemXcodes => Ok(SdkSearchResolvedLocation::PlatformDirectories(
211 DeveloperDirectory::find_system_xcodes()?
212 .into_iter()
213 .map(|dir| dir.platforms())
214 .collect::<Result<Vec<_>, Error>>()?
215 .into_iter()
216 .flatten()
217 .collect::<Vec<_>>(),
218 )),
219 Self::Developer(dir) => Ok(SdkSearchResolvedLocation::PlatformDirectories(
220 dir.platforms()?,
221 )),
222 Self::Sdks(path) => Ok(SdkSearchResolvedLocation::SdksDirectory(path.clone())),
223 Self::Sdk(path) => Ok(SdkSearchResolvedLocation::SdkDirectory(path.clone())),
224 }
225 }
226}
227
228/// Sorting strategy to apply to SDK searches.
229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
230pub enum SdkSorting {
231 /// Do not apply any sorting.
232 ///
233 /// This will return SDKs in the order they are discovered from the input
234 /// paths.
235 None,
236
237 /// Order SDKs by their version in descending order.
238 ///
239 /// Newer SDKs will come before older SDKs.
240 VersionDescending,
241
242 /// Order SDKs by their version in ascending order.
243 ///
244 /// Older SDKs will come before newer SDKs.
245 VersionAscending,
246}
247
248impl Display for SdkSorting {
249 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
250 f.write_str(match self {
251 Self::None => "nothing",
252 Self::VersionDescending => "descending version",
253 Self::VersionAscending => "ascending version",
254 })
255 }
256}
257
258impl SdkSorting {
259 pub fn compare_version(&self, a: Option<&SdkVersion>, b: Option<&SdkVersion>) -> Ordering {
260 match self {
261 Self::None => Ordering::Equal,
262 Self::VersionAscending => match (a, b) {
263 (Some(a), Some(b)) => a.cmp(b),
264 (Some(_), None) => Ordering::Greater,
265 (None, Some(_)) => Ordering::Less,
266 (None, None) => Ordering::Equal,
267 },
268 Self::VersionDescending => match (a, b) {
269 (Some(a), Some(b)) => b.cmp(a),
270 (Some(_), None) => Ordering::Less,
271 (None, Some(_)) => Ordering::Greater,
272 (None, None) => Ordering::Equal,
273 },
274 }
275 }
276}
277
278/// Describes an event during SDK discovery.
279///
280/// This events are sent to the progress callback to allow monitoring and debugging
281/// of SDK searching activity.
282pub enum SdkSearchEvent {
283 /// Beginning a search of a given location.
284 SearchingLocation(SdkSearchLocation),
285 PlatformDirectoryInclude(PathBuf),
286 PlatformDirectoryExclude(PathBuf),
287 SdkFilterSkip(SdkPath),
288 SdkFilterMatch(SdkPath),
289 SdkFilterExclude(SdkPath, String),
290 Sorting(usize, SdkSorting),
291}
292
293impl Display for SdkSearchEvent {
294 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
295 match self {
296 Self::SearchingLocation(location) => f.write_fmt(format_args!("searching {location}")),
297 Self::PlatformDirectoryInclude(path) => f.write_fmt(format_args!(
298 "searching Platform directory {}",
299 path.display()
300 )),
301 Self::PlatformDirectoryExclude(path) => f.write_fmt(format_args!(
302 "excluding Platform directory {}",
303 path.display()
304 )),
305 Self::SdkFilterSkip(sdk) => f.write_fmt(format_args!("SDK {sdk} bypasses filter")),
306 Self::SdkFilterMatch(sdk) => {
307 f.write_fmt(format_args!("SDK {sdk} matches search filter"))
308 }
309 Self::SdkFilterExclude(sdk, reason) => {
310 f.write_fmt(format_args!("SDK {sdk} discarded because {reason}"))
311 }
312 Self::Sorting(count, sorting) => {
313 f.write_fmt(format_args!("sorting {count} SDKs by {sorting}"))
314 }
315 }
316 }
317}
318
319/// A callable that receives progress during an SDK search.
320pub type SdkProgressCallback = fn(SdkSearchEvent);
321
322/// Search parameters for locating an Apple SDK.
323///
324/// This type can be used to construct a search for an Apple SDK given user chosen
325/// search parameters.
326///
327/// The search algorithm is essentially:
328///
329/// 1. Iterate through each registered search location.
330/// 2. Discover candidate SDKs and filter.
331/// 3. Globally sort (if enabled).
332///
333/// # Search Locations
334///
335/// Search mechanisms / locations are represented via [SdkSearchLocation] and internally
336/// the searcher maintains a vector of locations. The default search locations are:
337///
338/// 1. Use path specified by `SDKROOT` environment variable, if defined.
339/// 2. Find SDKs within the Developer Directory defined by the `DEVELOPER_DIR` environment
340/// variable.
341/// 3. Find SDKs within the path configured with `xcode-select --switch`.
342/// 4. Find SDKs within the system installed Xcode application.
343/// 5. Find SDKs within the system installed Xcode Command Line Tools.
344///
345/// Simply call [Self::location()] to register a new location. If the default locations
346/// are not desirable, construct an empty instance via [Self::empty()] and register your
347/// explicit list of locations.
348///
349/// An attempt is made to only search a given location at most once. This is done in
350/// order to avoid redundant work. If a location is specified multiple times - even via
351/// different [SdkSearchLocation] variants - subsequent searches of that location will
352/// yield no distinct results. Duplicate SDKs can occur in the returned list.
353///
354/// # Filtering
355///
356/// Filters can be registered to control which SDKs are emitted from the search.
357///
358/// By default, no filtering is performed. This means all SDKs in all search locations
359/// are returned. This can return SDKs belonging to multiple platforms (e.g. macOS and iOS).
360///
361/// The following functions control filtering:
362///
363/// * [Self::platform()]
364/// * [Self::minimum_version()]
365/// * [Self::maximum_version()]
366/// * [Self::deployment_target()]
367///
368/// If you are looking for an SDK to use (e.g. for compilation), you should at least use a
369/// platform filter. Otherwise you may see SDKs for platforms you aren't targeting! It is
370/// also an encouraged practice to specify a minimum or maximum SDK version to use.
371///
372/// If you know you are targeting a specific OS version, applying a targeting filter
373/// via [Self::deployment_target()] is recommended. However, this filter is not always
374/// reliable. See the caveats in its documentation.
375///
376/// # Sorting
377///
378/// By default, the returned list of SDKs is the chained result of SDKs discovered
379/// in all registered search locations. The order of the SDK within each search
380/// location is likely the sorted order of directory names as they appear on the filesystem.
381///
382/// If using an SDK for compilation, sorting by the SDK version is likely desired.
383/// Using the latest/newest SDK that supports a given deployment target is generally
384/// a best practice.
385#[derive(Clone)]
386pub struct SdkSearch {
387 progress_callback: Option<SdkProgressCallback>,
388 locations: Vec<SdkSearchLocation>,
389 platform: Option<Platform>,
390 minimum_version: Option<SdkVersion>,
391 maximum_version: Option<SdkVersion>,
392 deployment_target: Option<(String, SdkVersion)>,
393 sorting: SdkSorting,
394}
395
396impl Default for SdkSearch {
397 fn default() -> Self {
398 Self {
399 progress_callback: None,
400 locations: vec![
401 SdkSearchLocation::SdkRootEnv,
402 SdkSearchLocation::DeveloperDirEnv,
403 SdkSearchLocation::XcodeSelectPaths,
404 SdkSearchLocation::SystemXcode,
405 SdkSearchLocation::CommandLineTools,
406 ],
407 platform: None,
408 minimum_version: None,
409 maximum_version: None,
410 deployment_target: None,
411 sorting: SdkSorting::None,
412 }
413 }
414}
415
416impl SdkSearch {
417 /// Obtain an instance with an empty set of search locations.
418 ///
419 /// The search will not resolve any SDKs unless a search location is registered
420 /// with the instance.
421 pub fn empty() -> Self {
422 let mut s = Self::default();
423 s.locations.clear();
424 s
425 }
426
427 /// Define a function that will be called to provide updates on SDK search status.
428 pub fn progress_callback(mut self, callback: SdkProgressCallback) -> Self {
429 self.progress_callback = Some(callback);
430 self
431 }
432
433 /// Add a location to search.
434 ///
435 /// The location will be appended to the current search location list.
436 pub fn location(mut self, location: SdkSearchLocation) -> Self {
437 self.locations.push(location);
438 self
439 }
440
441 /// Set the SDK platform to search for.
442 ///
443 /// If you do not call this, SDKs for all platforms are returned.
444 ///
445 /// If you are looking for a specific SDK to use, you probably want to call this.
446 /// If you are searching for all available SDKs, you probably don't want to call this.
447 pub fn platform(mut self, platform: Platform) -> Self {
448 self.platform = Some(platform);
449 self
450 }
451
452 /// Minimum SDK version to require.
453 ///
454 /// Effectively imposes a `>=` filter on found SDKs.
455 ///
456 /// If using `SimpleSdk` and the SDK version could not be determined from
457 /// the filesystem path, the version is assumed to be `0.0` and this filter
458 /// will likely exclude the SDK.
459 pub fn minimum_version(mut self, version: impl Into<SdkVersion>) -> Self {
460 self.minimum_version = Some(version.into());
461 self
462 }
463
464 /// Maximum SDK version to return.
465 ///
466 /// Effectively imposes a `<=` filter on found SDKs.
467 pub fn maximum_version(mut self, version: impl Into<SdkVersion>) -> Self {
468 self.maximum_version = Some(version.into());
469 self
470 }
471
472 /// Deployment target that the SDK must support.
473 ///
474 /// When set, only SDKs that support targeting the given target-version pair will
475 /// be returned. Example values are (`macosx`, `10.15`).
476 ///
477 /// Only modern SDKs with `SDKSettings.json` files advertise their targeting settings
478 /// in a way that allows this filter to work.
479 ///
480 /// Attempting to use this filter on `SimpleSdk` will result in a run-time
481 /// error at search time since these SDKs do not parse `SDKSettings` files.
482 pub fn deployment_target(
483 mut self,
484 target: impl ToString,
485 version: impl Into<SdkVersion>,
486 ) -> Self {
487 self.deployment_target = Some((target.to_string(), version.into()));
488 self
489 }
490
491 /// Define the sorting order for returned SDKs.
492 ///
493 /// Default is [SdkSorting::None].
494 pub fn sorting(mut self, sorting: SdkSorting) -> Self {
495 self.sorting = sorting;
496 self
497 }
498
499 /// Perform a search, yielding found SDKs sorted by the search's preferences.
500 ///
501 /// May return an empty vector.
502 pub fn search<SDK: AppleSdk>(&self) -> Result<Vec<SDK>, Error> {
503 let mut sdks = vec![];
504
505 // Track searched locations to avoid redundant work.
506 let mut searched_platform_dirs = HashSet::new();
507 let mut searched_sdks_dirs = HashSet::new();
508
509 for location in &self.locations {
510 if let Some(cb) = &self.progress_callback {
511 cb(SdkSearchEvent::SearchingLocation(location.clone()));
512 }
513
514 // Expand each location to SDKs.
515 let resolved = location.resolve_location()?;
516
517 let candidate_sdks = match &resolved {
518 SdkSearchResolvedLocation::None => {
519 vec![]
520 }
521 SdkSearchResolvedLocation::PlatformDirectories(dirs) => dirs
522 .iter()
523 // Apply platform filter.
524 .filter(|dir| {
525 if let Some(wanted_platform) = &self.platform {
526 if &dir.platform == wanted_platform {
527 if let Some(cb) = &self.progress_callback {
528 cb(SdkSearchEvent::PlatformDirectoryInclude(dir.path.clone()));
529 }
530
531 true
532 } else {
533 if let Some(cb) = &self.progress_callback {
534 cb(SdkSearchEvent::PlatformDirectoryExclude(dir.path.clone()));
535 }
536
537 false
538 }
539 } else {
540 if let Some(cb) = &self.progress_callback {
541 cb(SdkSearchEvent::PlatformDirectoryInclude(dir.path.clone()));
542 }
543
544 true
545 }
546 })
547 // Apply duplicate search filter.
548 .filter(|dir| {
549 if searched_platform_dirs.contains(dir.path()) {
550 false
551 } else {
552 searched_platform_dirs.insert(dir.path().to_path_buf());
553 true
554 }
555 })
556 .map(|dir| dir.find_sdks::<SDK>())
557 .collect::<Result<Vec<_>, Error>>()?
558 .into_iter()
559 .flatten()
560 .collect::<Vec<_>>(),
561 SdkSearchResolvedLocation::SdksDirectory(path) => {
562 if searched_sdks_dirs.contains(path) {
563 vec![]
564 } else {
565 searched_sdks_dirs.insert(path.clone());
566 SDK::find_in_directory(path)?
567 }
568 }
569 SdkSearchResolvedLocation::SdkDirectory(path)
570 | SdkSearchResolvedLocation::SdkDirectoryUnfiltered(path) => {
571 vec![SDK::from_directory(path)?]
572 }
573 };
574
575 let mut added_count = 0;
576
577 for sdk in candidate_sdks {
578 let include = if resolved.apply_sdk_filter() {
579 self.filter_sdk(&sdk)?
580 } else {
581 if let Some(cb) = &self.progress_callback {
582 cb(SdkSearchEvent::SdkFilterSkip(sdk.sdk_path()));
583 }
584
585 true
586 };
587
588 if include {
589 sdks.push(sdk);
590 added_count += 1;
591 }
592 }
593
594 if location.is_terminal() && added_count > 0 {
595 break;
596 }
597 }
598
599 // Sorting should be stable with None variant. But we can avoid the
600 // overhead.
601 if self.sorting != SdkSorting::None {
602 sdks.sort_by(|a, b| self.sorting.compare_version(a.version(), b.version()))
603 }
604
605 Ok(sdks)
606 }
607
608 /// Whether an SDK matches our search filter.
609 ///
610 /// This is exposed as a convenience method to allow custom implementations of
611 /// SDK searching using the filtering logic on this type.
612 pub fn filter_sdk<SDK: AppleSdk>(&self, sdk: &SDK) -> Result<bool, Error> {
613 let sdk_path = sdk.sdk_path();
614
615 if let Some(wanted_platform) = &self.platform {
616 if sdk.platform() != wanted_platform {
617 if let Some(cb) = &self.progress_callback {
618 cb(SdkSearchEvent::SdkFilterExclude(
619 sdk_path,
620 format!(
621 "platform {} != {}",
622 sdk.platform().filesystem_name(),
623 wanted_platform.filesystem_name()
624 ),
625 ));
626 }
627
628 return Ok(false);
629 }
630 }
631
632 if let Some(min_version) = &self.minimum_version {
633 if let Some(sdk_version) = sdk.version() {
634 if sdk_version < min_version {
635 if let Some(cb) = &self.progress_callback {
636 cb(SdkSearchEvent::SdkFilterExclude(
637 sdk_path,
638 format!("SDK version {sdk_version} < minimum version {min_version}"),
639 ));
640 }
641
642 return Ok(false);
643 }
644 } else {
645 // SDKs without a version always fail.
646 if let Some(cb) = &self.progress_callback {
647 cb(SdkSearchEvent::SdkFilterExclude(
648 sdk_path,
649 format!("Unknown SDK version fails to meet minimum version {min_version}"),
650 ));
651 }
652
653 return Ok(false);
654 }
655 }
656
657 if let Some(max_version) = &self.maximum_version {
658 if let Some(sdk_version) = sdk.version() {
659 if sdk_version > max_version {
660 if let Some(cb) = &self.progress_callback {
661 cb(SdkSearchEvent::SdkFilterExclude(
662 sdk_path,
663 format!("SDK version {sdk_version} > maximum version {max_version}"),
664 ));
665 }
666
667 return Ok(false);
668 }
669 } else {
670 // SDKs without a version always fail.
671
672 if let Some(cb) = &self.progress_callback {
673 cb(SdkSearchEvent::SdkFilterExclude(
674 sdk_path,
675 format!("Unknown SDK version fails to meet maximum version {max_version}"),
676 ));
677 }
678
679 return Ok(false);
680 }
681 }
682
683 if let Some((target, version)) = &self.deployment_target {
684 if !sdk.supports_deployment_target(target, version)? {
685 if let Some(cb) = &self.progress_callback {
686 cb(SdkSearchEvent::SdkFilterExclude(
687 sdk_path,
688 format!("does not support deployment target {target}:{version}"),
689 ));
690 }
691
692 return Ok(false);
693 }
694 }
695
696 if let Some(cb) = &self.progress_callback {
697 cb(SdkSearchEvent::SdkFilterMatch(sdk_path));
698 }
699
700 Ok(true)
701 }
702}