guppy/platform/
platform_eval.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::platform::{Platform, PlatformSpec};
5use std::ops::{BitAnd, BitOr};
6use target_spec::TargetSpec;
7
8/// The status of a dependency or feature, which is possibly platform-dependent.
9///
10/// This is a sub-status of [`EnabledStatus`](crate::graph::EnabledStatus).
11#[derive(Copy, Clone, Debug)]
12pub enum PlatformStatus<'g> {
13    /// This dependency or feature is never enabled on any platforms.
14    Never,
15    /// This dependency or feature is always enabled on all platforms.
16    Always,
17    /// The status is platform-dependent.
18    PlatformDependent {
19        /// An evaluator to run queries against.
20        eval: PlatformEval<'g>,
21    },
22}
23
24assert_covariant!(PlatformStatus);
25
26impl<'g> PlatformStatus<'g> {
27    pub(crate) fn new(specs: &'g PlatformStatusImpl) -> Self {
28        match specs {
29            PlatformStatusImpl::Always => PlatformStatus::Always,
30            PlatformStatusImpl::Specs(specs) => {
31                if specs.is_empty() {
32                    PlatformStatus::Never
33                } else {
34                    PlatformStatus::PlatformDependent {
35                        eval: PlatformEval { specs },
36                    }
37                }
38            }
39        }
40    }
41
42    /// Returns true if this dependency is always enabled on all platforms.
43    pub fn is_always(&self) -> bool {
44        match self {
45            PlatformStatus::Always => true,
46            PlatformStatus::PlatformDependent { .. } | PlatformStatus::Never => false,
47        }
48    }
49
50    /// Returns true if this dependency is never enabled on any platform.
51    pub fn is_never(&self) -> bool {
52        match self {
53            PlatformStatus::Never => true,
54            PlatformStatus::PlatformDependent { .. } | PlatformStatus::Always => false,
55        }
56    }
57
58    /// Returns true if this dependency is possibly enabled on any platform.
59    pub fn is_present(&self) -> bool {
60        !self.is_never()
61    }
62
63    /// Evaluates whether this dependency is enabled on the given platform spec.
64    ///
65    /// Returns `Unknown` if the result was unknown, which may happen if evaluating against an
66    /// individual platform and its target features are unknown.
67    pub fn enabled_on(&self, platform_spec: &PlatformSpec) -> EnabledTernary {
68        match (self, platform_spec) {
69            (PlatformStatus::Always, _) => EnabledTernary::Enabled,
70            (PlatformStatus::Never, _) => EnabledTernary::Disabled,
71            (PlatformStatus::PlatformDependent { .. }, PlatformSpec::Any) => {
72                EnabledTernary::Enabled
73            }
74            (PlatformStatus::PlatformDependent { eval }, PlatformSpec::Platform(platform)) => {
75                eval.eval(platform)
76            }
77            (PlatformStatus::PlatformDependent { .. }, PlatformSpec::Always) => {
78                EnabledTernary::Disabled
79            }
80        }
81    }
82}
83
84/// Whether a dependency or feature is enabled on a specific platform.
85///
86/// This is a ternary or [three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic)
87/// because the result may be unknown in some situations.
88///
89/// Returned by the methods on `EnabledStatus`, `PlatformStatus`, and `PlatformEval`.
90#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
91pub enum EnabledTernary {
92    /// The dependency is disabled on this platform.
93    Disabled,
94    /// The status of this dependency is unknown on this platform.
95    ///
96    /// This may happen if evaluation involves unknown target features. Notably,
97    /// this will not be returned for [`Platform::build_target()`], since the
98    /// target features for the build target platform are determined at compile
99    /// time.
100    Unknown,
101    /// The dependency is enabled on this platform.
102    Enabled,
103}
104
105impl EnabledTernary {
106    fn new(x: Option<bool>) -> Self {
107        match x {
108            Some(false) => EnabledTernary::Disabled,
109            None => EnabledTernary::Unknown,
110            Some(true) => EnabledTernary::Enabled,
111        }
112    }
113
114    /// Returns true if the status is known (either enabled or disabled).
115    pub fn is_known(self) -> bool {
116        match self {
117            EnabledTernary::Disabled | EnabledTernary::Enabled => true,
118            EnabledTernary::Unknown => false,
119        }
120    }
121}
122
123/// AND operation in Kleene K3 logic.
124impl BitAnd for EnabledTernary {
125    type Output = Self;
126
127    fn bitand(self, rhs: Self) -> Self::Output {
128        use EnabledTernary::*;
129
130        match (self, rhs) {
131            (Enabled, Enabled) => Enabled,
132            (Disabled, _) | (_, Disabled) => Disabled,
133            _ => Unknown,
134        }
135    }
136}
137
138/// OR operation in Kleene K3 logic.
139impl BitOr for EnabledTernary {
140    type Output = Self;
141
142    fn bitor(self, rhs: Self) -> Self {
143        use EnabledTernary::*;
144
145        match (self, rhs) {
146            (Disabled, Disabled) => Disabled,
147            (Enabled, _) | (_, Enabled) => Enabled,
148            _ => Unknown,
149        }
150    }
151}
152
153/// An evaluator for platform-specific dependencies.
154///
155/// This represents a collection of platform specifications, of the sort `cfg(unix)`.
156#[derive(Copy, Clone, Debug)]
157pub struct PlatformEval<'g> {
158    specs: &'g [TargetSpec],
159}
160
161assert_covariant!(PlatformEval);
162
163impl<'g> PlatformEval<'g> {
164    /// Runs this evaluator against the given platform.
165    pub fn eval(&self, platform: &Platform) -> EnabledTernary {
166        let mut res = EnabledTernary::Disabled;
167        for spec in self.specs.iter() {
168            let matches = spec.eval(platform);
169            // Short-circuit evaluation if possible.
170            if matches == Some(true) {
171                return EnabledTernary::Enabled;
172            }
173            res = res | EnabledTernary::new(matches);
174        }
175        res
176    }
177
178    /// Returns the [`TargetSpec`] instances backing this evaluator.
179    ///
180    /// The result of [`PlatformEval::eval`] against a platform is a logical OR
181    /// of the results of evaluating the platform against each target spec.
182    pub fn target_specs(&self) -> &'g [TargetSpec] {
183        self.specs
184    }
185}
186
187#[derive(Clone, Debug)]
188pub(crate) enum PlatformStatusImpl {
189    Always,
190    // Empty vector means never.
191    Specs(Vec<TargetSpec>),
192}
193
194impl PlatformStatusImpl {
195    /// Returns true if this is an empty predicate (i.e. will never match).
196    pub(crate) fn is_never(&self) -> bool {
197        match self {
198            PlatformStatusImpl::Always => false,
199            PlatformStatusImpl::Specs(specs) => specs.is_empty(),
200        }
201    }
202
203    pub(crate) fn extend(&mut self, other: &PlatformStatusImpl) {
204        // &mut *self is a reborrow to allow *self to work below.
205        match (&mut *self, other) {
206            (PlatformStatusImpl::Always, _) => {
207                // Always stays the same since it means all specs are included.
208            }
209            (PlatformStatusImpl::Specs(_), PlatformStatusImpl::Always) => {
210                // Mark self as Always.
211                *self = PlatformStatusImpl::Always;
212            }
213            (PlatformStatusImpl::Specs(specs), PlatformStatusImpl::Specs(other)) => {
214                specs.extend_from_slice(other.as_slice());
215            }
216        }
217    }
218
219    pub(crate) fn add_spec(&mut self, spec: Option<&TargetSpec>) {
220        // &mut *self is a reborrow to allow *self to work below.
221        match (&mut *self, spec) {
222            (PlatformStatusImpl::Always, _) => {
223                // Always stays the same since it means all specs are included.
224            }
225            (PlatformStatusImpl::Specs(_), None) => {
226                // Mark self as Always.
227                *self = PlatformStatusImpl::Always;
228            }
229            (PlatformStatusImpl::Specs(specs), Some(spec)) => {
230                specs.push(spec.clone());
231            }
232        }
233    }
234}
235
236impl Default for PlatformStatusImpl {
237    fn default() -> Self {
238        // Empty vector means never.
239        PlatformStatusImpl::Specs(vec![])
240    }
241}