910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Note that `target_triple.rs` module is auto-generated by `gnrt`'s `build.rs`.
use crate::target_triple::{RustTargetTriple, RUST_TRIPLE_PROPERTIES};

use anyhow::{anyhow, Result};
use std::{
    collections::{HashMap, HashSet},
    sync::LazyLock,
};

/// Representation of a `Condition` associated with a conditional/optional
/// dependency from a `Cargo.toml` file - see an example here:
/// https://source.chromium.org/chromium/chromium/src/+/main:third_party/rust/chromium_crates_io/vendor/windows-targets-v0_52/Cargo.toml;l=38;drc=5977f5edc277200b0674e8df80ba7495980d87bb
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Condition(Result<HashSet<RustTargetTriple>, String>);

impl Condition {
    pub fn is_always_false(&self) -> bool {
        self.0.as_ref().is_ok_and(|triple_set| triple_set.is_empty())
    }

    /// Creates a `Condition` that is never met - target platforms supported in
    /// Chromium builds never meet this condition.  For example
    /// `#[cfg(target_arch = "powerpc")]` is effectively equivalent to
    /// `Condition::always_false()`.
    pub fn always_false() -> Self {
        Condition(Ok(HashSet::new()))
    }

    pub fn is_always_true(&self) -> bool {
        self.0.as_ref().is_ok_and(|triple_set| *triple_set == *RustTargetTriple::all())
    }

    /// Creates a `Condition` that is always true - *all* target platforms
    /// supported in Chromium builds meet this condition.  For example
    /// `#[cfg(not(target_arch = "powerpc"))]` is effectively equivalent to
    /// `Condition::always_true()`.
    pub fn always_true() -> Self {
        Condition(Ok(RustTargetTriple::all().clone()))
    }

    fn from_triple(triple: RustTargetTriple) -> Self {
        Self::from_triple_set([triple].into())
    }

    fn from_triple_set(set: HashSet<RustTargetTriple>) -> Self {
        Condition(Ok(set))
    }

    pub fn or(lhs: Condition, rhs: Condition) -> Self {
        // First check if one of the operands (potentially an `Err` variant!)
        // can be ignored (when the other operand `is_always_true`).
        if lhs.is_always_true() {
            return lhs;
        }
        if rhs.is_always_true() {
            return rhs;
        }
        match (lhs, rhs) {
            (err @ Condition(Err(_)), _) | (_, err @ Condition(Err(_))) => err.clone(),
            (Condition(Ok(lhs)), Condition(Ok(rhs))) => Condition::from_triple_set(&lhs | &rhs),
        }
    }

    pub fn and(lhs: Condition, rhs: Condition) -> Self {
        // First check if one of the operands (potentially an `Err` variant!)
        // can be ignored (when the other operand `is_always_false`).
        if lhs.is_always_false() {
            return lhs;
        }
        if rhs.is_always_false() {
            return rhs;
        }
        match (lhs, rhs) {
            (err @ Condition(Err(_)), _) | (_, err @ Condition(Err(_))) => err.clone(),
            (Condition(Ok(lhs)), Condition(Ok(rhs))) => Condition::from_triple_set(&lhs & &rhs),
        }
    }

    fn not(other: Condition) -> Self {
        match other {
            err @ Condition(Err(_)) => err,
            Condition(Ok(triple_set)) => Condition(Ok(negate_triple_set(&triple_set))),
        }
    }

    pub fn to_handlebars_value(&self) -> Result<Option<String>> {
        assert!(
            !self.is_always_false(),
            "'always false' dependencies should be filtered out \
             by `fn collect_dependencies` from `deps.rs`"
        );
        self.0.as_ref().map_err(|msg| anyhow!("{msg}")).map(|triple_set| {
            if *triple_set == *RustTargetTriple::all() {
                None
            } else {
                Some(format_as_gn_expr::format(triple_set))
            }
        })
    }

    pub fn from_target_spec(spec: &target_spec::TargetSpec) -> Self {
        use target_spec::TargetSpec::*;
        match spec {
            PlainString(triple) => triple_to_condition(triple.as_str()),
            Expression(expr) => {
                let cfg_expr = expr.expression_str().parse().unwrap();
                cfg_expr_to_condition(&cfg_expr)
            }
        }
    }
}

fn negate_triple_set(set: &HashSet<RustTargetTriple>) -> HashSet<RustTargetTriple> {
    RustTargetTriple::all() - set
}

/// Module for converting a set of target triples into a conditional expression
/// that uses GN/Chromium variables and syntax.
///
/// The main entrypoint is the `pub fn format` function below.
///
/// The conditional expression is built as a combination of:
///
/// * OS filters (e.g. `"is_win"`, see `fn get_gn_os_expr`)
/// * CPU filters (e.g. `"current_cpu == \"x64\""`, see `fn get_gn_arch_expr`)
/// * Triple filters (e.g. `"rust_abi_target == \"aarch64-apple-ios-sim\"`, see
///   `gn get_gn_target_triple_expr`).  This is used as a last resort, because
///   this results in long and not very readable expressions.
mod format_as_gn_expr {
    use super::negate_triple_set;
    use crate::target_triple::{RustTargetArch, RustTargetOs, RustTargetTriple};
    use itertools::Itertools;
    use std::collections::HashSet;

    /// Translates `target_triples` into a condition expressed in GN/Chromium
    /// syntax (e.g. `is_win && current_cpu == "x64"`).  Tries to use
    /// heuristics to return a short, readable expression.
    pub fn format(target_triples: &HashSet<RustTargetTriple>) -> String {
        assert!(!target_triples.is_empty());
        assert_ne!(target_triples, RustTargetTriple::all());

        if let Some(expr) = get_single_filter(target_triples, get_gn_os_expr) {
            return expr;
        }
        if let Some(expr) = get_single_filter(target_triples, get_gn_arch_expr) {
            return expr;
        }

        let result1 = get_double_filter(target_triples, get_gn_os_expr, get_gn_arch_expr);
        let result2 = get_double_filter(target_triples, get_gn_arch_expr, get_gn_os_expr);
        if let Some((result1, result2)) = result1.zip(result2) {
            if result1.len() <= result2.len() {
                return result1;
            } else {
                return result2;
            }
        }

        target_triples.iter().map(get_gn_target_triple_expr).sorted().join(" || ")
    }

    /// If `target_triples` can be calculated with a single-dimensional filter
    /// (e.g. "all triples with OS=A or OS=B" or "all triples with Arch=x86
    /// or Arch=x64") then returns an expression that represents such a
    /// filter. `get_expr` should typically be `get_gn_os_expr` or
    /// `get_gn_arch_expr`.
    fn get_single_filter(
        target_triples: &HashSet<RustTargetTriple>,
        get_expr: fn(&RustTargetTriple) -> &'static str,
    ) -> Option<String> {
        let get_expr_set = |triples: &HashSet<RustTargetTriple>| -> HashSet<&'static str> {
            triples.iter().map(get_expr).collect()
        };
        fn negate(expr: &str) -> String {
            if expr.contains("==") {
                expr.replace("==", "!=")
            } else {
                format!("!{expr}")
            }
        }
        let expr_in_target = get_expr_set(target_triples);
        let expr_in_rest = get_expr_set(&negate_triple_set(target_triples));
        if !expr_in_target.is_disjoint(&expr_in_rest)
            || expr_in_target.is_empty()
            || expr_in_rest.is_empty()
        {
            return None;
        }
        if expr_in_target.len() <= expr_in_rest.len() {
            Some(expr_in_target.iter().sorted().join(" || "))
        } else {
            Some(expr_in_rest.iter().sorted().map(|&expr| negate(expr)).join(" && "))
        }
    }

    /// If `target_triples` can be calculated with a two-dimensional filter
    /// (e.g. "all triples with (OS=A && Arch=X) or (OS=B && Arch=Y)") then
    /// returns an expression that represents such a filter.  `get_expr1`
    /// and or `get_expr2` should typically be `get_gn_os_expr` or
    /// `get_gn_arch_expr`.
    fn get_double_filter(
        target_triples: &HashSet<RustTargetTriple>,
        get_expr1: fn(&RustTargetTriple) -> &'static str,
        get_expr2: fn(&RustTargetTriple) -> &'static str,
    ) -> Option<String> {
        let mut or_operands = vec![];
        let chunked = target_triples.iter().copied().sorted_by_key(get_expr1).chunk_by(get_expr1);
        for (expr1, chunk) in chunked.into_iter() {
            let target_triples_matching_expr1 = chunk.collect::<HashSet<_>>();
            let all_triples_matching_expr1 = RustTargetTriple::all()
                .iter()
                .copied()
                .filter(|triple| get_expr1(triple) == expr1)
                .collect::<HashSet<_>>();
            assert!(all_triples_matching_expr1.is_superset(&target_triples_matching_expr1));
            if target_triples_matching_expr1 == all_triples_matching_expr1 {
                or_operands.push(expr1.to_string());
            } else {
                let expr2_or_operands = target_triples_matching_expr1
                    .iter()
                    .map(get_expr2)
                    .sorted()
                    .dedup()
                    .collect_vec();
                let all_triples_matching_expr1_and_expr2 = all_triples_matching_expr1
                    .iter()
                    .copied()
                    .filter(|triple| expr2_or_operands.contains(&get_expr2(triple)))
                    .collect::<HashSet<_>>();
                if all_triples_matching_expr1_and_expr2 != target_triples_matching_expr1 {
                    return None;
                }
                let final_expr_for_chunk = match expr2_or_operands.as_slice() {
                    [] => unreachable!(),
                    [single_operand] => format!("{expr1} && {single_operand}"),
                    multiple_operands => {
                        format!("{expr1} && ({})", multiple_operands.iter().join(" || "),)
                    }
                };
                or_operands.push(final_expr_for_chunk);
            }
        }
        let final_expr_for_target_triples = match or_operands.as_slice() {
            [] => unreachable!(),
            [single_operand] => single_operand.clone(),
            multiple_operands => {
                multiple_operands.iter().map(|operand| format!("({operand})")).join(" || ")
            }
        };
        Some(final_expr_for_target_triples)
    }

    /// Translates `RustTargetTriple` into a GN conditional expression that will
    /// match the GN notion of the OS of that triple.
    fn get_gn_os_expr(rust_triple: &RustTargetTriple) -> &'static str {
        // `RustTargetOs` and `into` come from `target_triples.rs` which is
        // auto-generated by `gnrt`'s `build.rs`.
        let rust_os = (*rust_triple).into();
        match rust_os {
            RustTargetOs::Android => "is_android",
            RustTargetOs::Fuchsia => "is_fuchsia",
            RustTargetOs::Linux => "(is_linux || is_chromeos)",
            RustTargetOs::Macos => "is_mac",
            RustTargetOs::Windows => "is_win",
            RustTargetOs::Ios | RustTargetOs::Tvos => "is_ios",
        }
    }

    /// Translates `RustTargetTriple` into a GN conditional expression that will
    /// match the GN notion of the CPU architecture of that triple.
    fn get_gn_arch_expr(rust_triple: &RustTargetTriple) -> &'static str {
        // `RustTargetArch` and `into` come from `target_triples.rs` which is
        // auto-generated by `gnrt`'s `build.rs`.
        let rust_arch = (*rust_triple).into();
        match rust_arch {
            RustTargetArch::Aarch64 => "current_cpu == \"arm64\"",
            RustTargetArch::Arm => "current_cpu == \"arm\"",
            RustTargetArch::Riscv64 => "current_cpu == \"riscv64\"",
            RustTargetArch::X86 => "current_cpu == \"x86\"",
            RustTargetArch::X8664 => "current_cpu == \"x64\"",
            RustTargetArch::Powerpc64 => "(current_cpu == \"ppc64le\")",
            RustTargetArch::S390x => "(current_cpu == \"s390x\")",
            RustTargetArch::Loongarch64 => "(current_cpu == \"loong64\")",
        }
    }

    /// Translates `RustTargetTriple` into a GN conditional expression that will
    /// match the value of `rust_abi_target` as set by
    /// `//build/config/rust.gni`.
    fn get_gn_target_triple_expr(rust_triple: &RustTargetTriple) -> String {
        // `as_triple_name` comes from `target_triples.rs` which is auto-generated by
        // `gnrt`'s `build.rs`.
        let rust_triple_name = rust_triple.as_triple_name();
        format!("rust_abi_target == \"{rust_triple_name}\"")
    }
}

fn cfg_expr_to_condition(cfg_expr: &cargo_platform::CfgExpr) -> Condition {
    match cfg_expr {
        cargo_platform::CfgExpr::Not(expr) => Condition::not(cfg_expr_to_condition(expr)),
        cargo_platform::CfgExpr::All(exprs) => {
            // https://doc.rust-lang.org/reference/conditional-compilation.html#r-cfg.predicate.all
            // says that "It is true if "all of the given predicates are true, or if the
            // list is empty."
            exprs
                .iter()
                .map(cfg_expr_to_condition)
                .fold(Condition::always_true(), |accumulated, condition| {
                    Condition::and(accumulated, condition)
                })
        }
        cargo_platform::CfgExpr::Any(exprs) => {
            // https://doc.rust-lang.org/reference/conditional-compilation.html#r-cfg.predicate.any
            // says that "It is true if at least one of the given predicates is true. If
            // there are no predicates, it is false.".
            exprs
                .iter()
                .map(cfg_expr_to_condition)
                .fold(Condition::always_false(), |accumulated, condition| {
                    Condition::or(accumulated, condition)
                })
        }
        cargo_platform::CfgExpr::Value(cfg) => cfg_to_condition(cfg),
    }
}

fn cfg_to_condition(cfg: &cargo_platform::Cfg) -> Condition {
    match cfg {
        cargo_platform::Cfg::Name(name) => cfg_name_to_condition(name.as_str()),
        cargo_platform::Cfg::KeyPair(key, value) => {
            cfg_key_value_pair_to_condition(key.as_str(), value)
        }
    }
}

/// A mapping from 1) name of Rust condition key (e.g. `target_os`,
/// `target_env`, etc) and 2) value of Rust condition key (e.g. `msvc` or `gnu`
/// for `target_env`) into 3) a set of `RustTargetTriple`s where this condition
/// is true (e.g. `target_env = "msvc"` is true for all 3 of Chromium Windows
/// target triples).
static PROP_NAME_TO_PROP_VALUE_TO_TRIPLE_SET: LazyLock<
    HashMap<&str, HashMap<&str, HashSet<RustTargetTriple>>>,
> = LazyLock::new(|| {
    // `RUST_TRIPLE_PROPERTIES` comes from `target_triples.rs` which is
    // auto-generated by `gnrt`'s `build.rs`.
    let mut result: HashMap<_, HashMap<_, HashSet<_>>> = HashMap::new();
    for (triple, prop_name, prop_value) in RUST_TRIPLE_PROPERTIES {
        let triple = triple.parse().unwrap();
        result.entry(prop_name).or_default().entry(prop_value).or_default().insert(triple);
    }
    result
});

fn cfg_key_value_pair_to_condition(key: &str, value: &str) -> Condition {
    if key == "panic" {
        return panic_cfg_to_condition(value);
    }

    if let Some(value_to_triple_set_map) = PROP_NAME_TO_PROP_VALUE_TO_TRIPLE_SET.get(key) {
        match value_to_triple_set_map.get(value) {
            None => return Condition::always_false(),
            Some(set) => return Condition::from_triple_set(set.clone()),
        }
    }

    // `KNOWN_UNSUPPORTED_KEYS` is based on
    //
    // 1. Reading https://doc.rust-lang.org/reference/conditional-compilation.html
    // 2. Looking at `rustc --print=cfg --target <some target> | grep = | grep -v
    //    target_` (`target_...=...` keys are handled in `build/builds.rs`).
    const KNOWN_UNSUPPORTED_KEYS: [&str; 3] = ["feature", "fmt_debug", "relocation_model"];
    if KNOWN_UNSUPPORTED_KEYS.contains(&key) {
        return Condition(Err(format!(
            "Condition depends on an unsupported, compiler-provided key: `{key}`"
        )));
    }

    // If `key` is not handled above (in particular not in the list of *known*
    // `KNOWN_UNSUPPORTED_KEYS`) then we assume that it is not set *by* `rustc` nor
    // `cargo`, but instead is passed via `--cfg key=value` *to* the compiler.
    //
    // And we also assume that Chromium will never ask GN/ninja to pass `--cfg
    // 'unrecognized_key="something"'` to `rustc` (this is not done today by
    // `//build/rust/*.gni` nor `BUILD.gn.hbs`).
    //
    // And therefore we treat this as `AlwaysFalse`.  See also
    // https://crbug.com/404598090#comment4.
    //
    // (We used to warn about such unrecognized `key`s, but it was too noisy in
    // practice.)
    Condition::always_false()
}

/// `name` should correspond to https://doc.rust-lang.org/reference/conditional-compilation.html#r-cfg.option-name
fn cfg_name_to_condition(name: &str) -> Condition {
    // See https://doc.rust-lang.org/reference/conditional-compilation.html#unix-and-windows
    const FAMILY_NAMES: [&str; 2] = ["unix", "windows"];
    if FAMILY_NAMES.contains(&name) {
        return cfg_key_value_pair_to_condition("target_family", name);
    }

    // We don't support `windows_raw_dylib` in Chromium.  See also
    // https://github.com/rust-lang/rust/issues/58713
    if ["windows_raw_dylib"].contains(&name) {
        return Condition::always_false();
    }

    // See https://doc.rust-lang.org/reference/conditional-compilation.html#debug_assertions
    if name == "debug_assertions" {
        // Returning "always true" is not 100% correct and may bring in unnecessary
        // dependencies. But this conservative behavior shouldn't cause any
        // major issues.
        //
        // TODO(https://crbug.com/402096443): Handle this by tracking not only a set of
        // `RustTargetTriple` but also a parallel set/bitflag of `RustDebugConfig` (with
        // just two bits - on and off).
        return Condition::always_true();
    }

    // See https://doc.rust-lang.org/reference/conditional-compilation.html#test
    if name == "test" {
        // Returning "always true" is not 100% correct and may bring in unnecessary
        // dependencies. But this seems unlikely, given that test-only
        // dependencies should be listed in the `[dev-dependencies]` section of
        // `Cargo.toml` and reported as `guppy::DependencyKind::Development`.
        // At any rate, this conservative behavior shouldn't cause any major
        // issues.
        //
        // TODO(https://crbug.com/402096443): Handle this better.
        return Condition::always_true();
    }

    // `KNOWN_UNSUPPORTED_NAMES` is based on
    //
    // 1. Reading https://doc.rust-lang.org/reference/conditional-compilation.html
    // 2. Looking at `rustc --print=cfg --target <some target> | grep -v =`
    //    (`target_...=...` keys are handled in `build/builds.rs`).
    //
    // TODO(https://crbug.com/402096443): Extract those in `build/build/rs`.
    // (It seems that all of them are properties of the target platform.)
    const KNOWN_UNSUPPORTED_NAMES: [&str; 9] = [
        "overflow_checks",
        "ub_checks",
        "target_has_atomic",
        "target_has_atomic_load_store",
        "target_has_reliable_f128",
        "target_has_reliable_f128_math",
        "target_has_reliable_f16",
        "target_has_reliable_f16_math",
        "target_thread_local",
    ];
    if KNOWN_UNSUPPORTED_NAMES.contains(&name) {
        return Condition(Err(format!(
            "Condition depends on an unsupported, compiler-provided configuration name: `{name}`"
        )));
    }

    // If `name` is not handled above (in particular not in the list of *known*
    // `KNOWN_UNSUPPORTED_NAMES`) then we assume that it is not set *by* `rustc`,
    // but instead is passed via `--cfg name` *to* the compiler.
    //
    // And we also assume that Chromium will never ask GN/ninja to pass `--cfg
    // 'unrecognized_name'` to `rustc` (this is not done today by
    // `//build/rust/*.gni` nor `BUILD.gn.hbs`).
    //
    // And therefore we treat this as `AlwaysFalse`.  See also
    // https://crbug.com/404598090#comment4.
    //
    // We used to warn about such unrecognized `name`s, but it was too noisy in
    // practice.  Example of such a `name` can be found here:
    // https://source.chromium.org/chromium/chromium/src/+/main:third_party/rust/chromium_crates_io/vendor/zip-v2/Cargo.toml;l=258;drc=d41c4d24cd81aef76cda21e4ea84ab2ddb2c71e6
    Condition::always_false()
}

/// `value` should correspond to https://doc.rust-lang.org/reference/conditional-compilation.html#r-cfg.panic.values
fn panic_cfg_to_condition(value: &str) -> Condition {
    // `//build/config/compiler/BUILD.gn` always hardcodes `-Cpanic=abort` into
    // `rustflags`.
    match value {
        "abort" => Condition::always_true(),
        "unwind" => Condition::always_false(),
        _ => Condition(Err(format!(
            "Unrecognized panic configuration: `#[cfg(panic = \"{value}\")]`"
        ))),
    }
}

fn triple_to_condition(triple: &str) -> Condition {
    triple.parse().map(Condition::from_triple).unwrap_or_else(
        // Triples outside of `//build/rust/known-target-triples.txt` won't parse.
        // Such target triples are never used in Chromium builds and therefore we
        // represent tham as "always false".
        |_err| Condition::always_false(),
    )
}

#[cfg(test)]
mod tests {
    use super::Condition;
    use crate::target_triple::RustTargetTriple;

    fn condition_from_test_triple(triple: &str) -> Condition {
        let spec = target_spec::TargetSpec::PlainString(
            target_spec::TargetSpecPlainString::new(triple.to_string()).unwrap(),
        );
        Condition::from_target_spec(&spec)
    }

    fn condition_from_test_expr(expr: &str) -> Condition {
        let spec = target_spec::TargetSpec::Expression(
            target_spec::TargetSpecExpression::new(expr).unwrap(),
        );
        Condition::from_target_spec(&spec)
    }

    fn gn_condition_from_test_expr(expr: &str) -> String {
        let condition = condition_from_test_expr(expr);
        match condition.to_handlebars_value() {
            Ok(Some(s)) => s,
            Ok(None) => panic!("Got 'always true' / `None` when formatting `{expr}`"),
            Err(err) => panic!("Error when formatting `{expr}`: `{err}`"),
        }
    }

    #[test]
    fn test_target_spec_to_condition() {
        // Try a target triple.
        assert_eq!(
            condition_from_test_triple("x86_64-pc-windows-msvc").to_handlebars_value().unwrap(),
            Some("is_win && current_cpu == \"x64\"".to_string()),
        );

        // Try a cfg expression.
        assert_eq!(
            gn_condition_from_test_expr("any(windows, target_os = \"android\")"),
            "is_android || is_win",
        );

        // Redundant cfg expression.
        assert_eq!(gn_condition_from_test_expr("any(windows, windows)"), "is_win",);

        // Testing for brevity (e.g. `!is_win` is shorted than `is_ios || is_linux ||
        // ...`).
        assert_eq!(gn_condition_from_test_expr("windows"), "is_win",);
        assert_eq!(gn_condition_from_test_expr("unix"), "!is_win",);
        assert_eq!(
            gn_condition_from_test_expr("target_arch = \"x86_64\""),
            "current_cpu == \"x64\"",
        );
        assert_eq!(
            gn_condition_from_test_expr("not(target_arch = \"x86_64\")"),
            "current_cpu != \"x64\"",
        );

        // Try sets of target triples that cannot be uniquely identified with GN
        // OS+arch.
        let triple1 = condition_from_test_triple("aarch64-apple-tvos");
        let triple2 = condition_from_test_triple("aarch64-apple-tvos-sim");
        assert_eq!(
            triple1.to_handlebars_value().unwrap(),
            Some("rust_abi_target == \"aarch64-apple-tvos\"".to_string()),
        );
        assert_eq!(
            Condition::or(triple1, triple2).to_handlebars_value().unwrap(),
            Some(
                "\
                rust_abi_target == \"aarch64-apple-tvos\" || \
                rust_abi_target == \"aarch64-apple-tvos-sim\""
                    .to_string()
            ),
        );

        // Try a PlatformSet with multiple filters.
        let filter1 = condition_from_test_expr(
            "all(target_os = \"android\", \
                                                    target_arch = \"arm\")",
        );
        let filter2 = condition_from_test_expr("windows");
        assert_eq!(
            Condition::or(filter1, filter2).to_handlebars_value().unwrap(),
            Some("(is_android && current_cpu == \"arm\") || (is_win)".to_string()),
        );

        // A cfg expression on arch only.
        assert_eq!(
            gn_condition_from_test_expr("target_arch = \"aarch64\""),
            "current_cpu == \"arm64\"",
        );

        // A cfg expression on arch and OS (but not via the target triple string).
        assert_eq!(
            gn_condition_from_test_expr("all(target_arch = \"aarch64\", unix)"),
            "current_cpu == \"arm64\" && (\
                (is_linux || is_chromeos) || \
                is_android || \
                is_fuchsia || \
                is_ios || \
                is_mac)",
        );

        // A cfg expression taken from `windows-targets-v0_52/Cargo.toml`
        // (condition for depending on `windows_x86_64_msvc`).
        assert_eq!(
            gn_condition_from_test_expr(
                "all(any(target_arch = \"x86_64\", target_arch = \"arm64ec\"), \
                     target_env = \"msvc\", \
                     not(windows_raw_dylib))"
            ),
            "is_win && current_cpu == \"x64\"",
        );

        // A cfg expression taken from `windows-targets-v0_52/Cargo.toml` (condition for
        // depending on `windows_i686_gnu`).  When Chromium targets Windows, it
        // always uses `msvc` (rather than `gnu`) environment - this is why we'd
        // like to expect `AlwaysFalse` result here. But to get `AlwaysFalse` we
        // need to know that this condition is only evaluated when the parent/
        // dependent crate is a Windows-only crate - this is covered by unit tests
        // in `deps.rs`.
        assert_eq!(
            condition_from_test_expr(
                "all(target_arch = \"x86\", \
                     target_env = \"gnu\", \
                     not(target_abi = \"llvm\"), \
                     not(windows_raw_dylib))"
            ),
            Condition::from_triple(RustTargetTriple::I686UnknownLinuxGnu),
        );

        // Cfg expressions taken from `getrandom-0.3` => `libc` dependency.
        assert_eq!(
            gn_condition_from_test_expr(
                "any(                         \
                    target_os = \"ios\",      \
                    target_os = \"visionos\", \
                    target_os = \"watchos\",  \
                    target_os = \"tvos\")",
            ),
            "is_ios",
        );
        assert_eq!(
            gn_condition_from_test_expr(
                "any(                        \
                    target_os = \"macos\",   \
                    target_os = \"openbsd\", \
                    target_os = \"vita\",    \
                    target_os = \"emscripten\")",
            ),
            "is_mac",
        );
        assert_eq!(
            condition_from_test_expr(
                // Simplification of one of the real expressions below.
                "all(target_os = \"linux\", target_env = \"\")",
            ),
            Condition::always_false(),
        );
        assert_eq!(
            gn_condition_from_test_expr(
                "all(                                                      \
                    any(target_os = \"linux\", target_os = \"android\"),   \
                    not(any(                                               \
                            all(target_os = \"linux\", target_env = \"\"), \
                            getrandom_backend = \"custom\",                \
                            getrandom_backend = \"linux_raw\",             \
                            getrandom_backend = \"rdrand\",                \
                            getrandom_backend = \"rndr\")))",
            ),
            "(is_linux || is_chromeos) || is_android",
        );
    }

    /// Test that unsupported terms disappear when possible
    /// (i.e. that we don't needlessly propagate an error).
    #[test]
    fn test_err_suppression() {
        let err = Condition(Err("some err msg".to_string()));
        assert_eq!(Condition::always_true(), Condition::or(Condition::always_true(), err.clone()),);
        assert_eq!(Condition::always_true(), Condition::or(err.clone(), Condition::always_true()),);
        assert_eq!(
            Condition::always_false(),
            Condition::and(Condition::always_false(), err.clone()),
        );
        assert_eq!(
            Condition::always_false(),
            Condition::and(err.clone(), Condition::always_false()),
        );
    }
}