/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use anyhow::Context;
use buck2_core::cells::CellResolver;
use buck2_core::package::PackageLabel;
use buck2_core::pattern::display_precise_pattern;
use buck2_core::pattern::pattern_type::ConfiguredProvidersPatternExtra;
use buck2_core::pattern::pattern_type::PatternType;
use buck2_core::pattern::PackageSpec;
use buck2_core::pattern::ParsedPattern;
use buck2_core::target::name::TargetName;
use dupe::Dupe;
use gazebo::prelude::VecExt;
use indexmap::IndexMap;

use crate::file_ops::FileOps;
use crate::pattern::package_roots::find_package_roots;

#[derive(Debug, thiserror::Error)]
enum ResolvedPatternError {
    #[error("Expecting {0} pattern, got `{1}`")]
    InvalidPattern(&'static str, String),
}

/// Pattern where `foo/...` is expanded to matching packages.
/// Targets are not validated yet, and `:` is not yet expanded.
#[derive(Debug)]
pub struct ResolvedPattern<T: PatternType> {
    pub specs: IndexMap<PackageLabel, PackageSpec<T>>,
}

impl<T> ResolvedPattern<T>
where
    T: PatternType,
{
    pub fn new() -> Self {
        Self {
            specs: IndexMap::new(),
        }
    }

    pub fn add_package(&mut self, package: PackageLabel) {
        self.specs.insert(package, PackageSpec::All);
    }

    pub fn add_target(&mut self, package: PackageLabel, target_name: TargetName, extra: T) {
        if let Some(s) = self.specs.get_mut(&package) {
            match s {
                PackageSpec::Targets(ref mut t) => t.push((target_name, extra)),
                PackageSpec::All => {}
            }
        } else {
            self.specs
                .insert(package, PackageSpec::Targets(vec![(target_name, extra)]));
        }
    }
}

impl ResolvedPattern<ConfiguredProvidersPatternExtra> {
    pub fn convert_pattern<U: PatternType>(self) -> anyhow::Result<ResolvedPattern<U>> {
        let mut specs = IndexMap::with_capacity(self.specs.len());
        for (package, spec) in self.specs {
            let spec = match spec {
                PackageSpec::Targets(targets) => {
                    PackageSpec::Targets(targets.into_try_map(|(target_name, extra)| {
                        let extra = U::from_configured_providers(extra.clone()).context(
                            ResolvedPatternError::InvalidPattern(
                                U::NAME,
                                display_precise_pattern(&package, target_name.as_ref(), &extra)
                                    .to_string(),
                            ),
                        )?;
                        anyhow::Ok((target_name, extra))
                    })?)
                }
                PackageSpec::All => PackageSpec::All,
            };
            specs.insert(package, spec);
        }
        Ok(ResolvedPattern { specs })
    }
}

/// Resolves a list of [ParsedPattern] to a [ResolvedPattern].
pub async fn resolve_target_patterns<P: PatternType>(
    cell_resolver: &CellResolver,
    patterns: &[ParsedPattern<P>],
    file_ops: &dyn FileOps,
) -> anyhow::Result<ResolvedPattern<P>> {
    let mut resolved = ResolvedPattern::new();
    for pattern in patterns {
        match pattern {
            ParsedPattern::Target(package, target_name, extra) => {
                resolved.add_target(package.dupe(), target_name.clone(), extra.clone());
            }
            ParsedPattern::Package(package) => {
                resolved.add_package(package.dupe());
            }
            ParsedPattern::Recursive(cell_path) => {
                let roots = find_package_roots(cell_path.clone(), file_ops, cell_resolver)
                    .await
                    .context("Error resolving recursive target pattern.")?;
                for package in roots {
                    resolved.add_package(package);
                }
            }
        }
    }
    Ok(resolved)
}

#[cfg(test)]
mod tests {
    use std::collections::BTreeMap;
    use std::collections::BTreeSet;
    use std::collections::HashMap;
    use std::marker::PhantomData;
    use std::sync::Arc;

    use buck2_core::cells::alias::NonEmptyCellAlias;
    use buck2_core::cells::cell_root_path::CellRootPathBuf;
    use buck2_core::cells::name::CellName;
    use buck2_core::cells::CellResolver;
    use buck2_core::cells::CellsAggregator;
    use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
    use buck2_core::package::PackageLabel;
    use buck2_core::pattern::pattern_type::PatternType;
    use buck2_core::pattern::pattern_type::ProvidersPatternExtra;
    use buck2_core::pattern::pattern_type::TargetPatternExtra;
    use buck2_core::pattern::PackageSpec;
    use buck2_core::pattern::ParsedPattern;
    use buck2_core::provider::label::NonDefaultProvidersName;
    use buck2_core::provider::label::ProviderName;
    use buck2_core::provider::label::ProvidersName;
    use buck2_core::target::name::TargetName;
    use dupe::Dupe;
    use gazebo::prelude::*;
    use test_case::test_case;

    use crate::file_ops::testing::TestFileOps;
    use crate::file_ops::FileOps;
    use crate::pattern::resolve::resolve_target_patterns;
    use crate::pattern::resolve::ResolvedPattern;

    #[derive(Clone)]
    struct TestPatternResolver {
        resolver: CellResolver,
        file_ops: Arc<dyn FileOps>,
    }

    impl TestPatternResolver {
        fn new(cells: &[(&str, &str)], files: &[&str]) -> anyhow::Result<Self> {
            let resolver = {
                let mut agg = CellsAggregator::new();
                let mut cell_paths = HashMap::new();
                for (name, path) in cells {
                    cell_paths.insert(*name, *path);
                }

                for (_, path) in cells {
                    for (alias, alias_path) in &cell_paths {
                        agg.add_cell_entry(
                            CellRootPathBuf::new(ProjectRelativePathBuf::try_from(
                                (*path).to_owned(),
                            )?),
                            NonEmptyCellAlias::new((*alias).to_owned())?,
                            CellRootPathBuf::new(ProjectRelativePathBuf::try_from(
                                (*alias_path).to_owned(),
                            )?),
                        )?;
                    }
                }
                agg.make_cell_resolver()?
            };

            let resolved_files = files
                .iter()
                .map(|p| {
                    (
                        resolver
                            .get_cell_path(&ProjectRelativePathBuf::unchecked_new((*p).to_owned()))
                            .unwrap(),
                        "".to_owned(),
                    )
                })
                .collect();

            let file_ops = Arc::new(TestFileOps::new_with_files(resolved_files));
            Ok(TestPatternResolver { resolver, file_ops })
        }

        async fn resolve<T>(&self, patterns: &[&str]) -> anyhow::Result<ResolvedPattern<T>>
        where
            T: PatternType,
        {
            let patterns: Vec<_> = patterns.map(|p| {
                ParsedPattern::<T>::parse_precise(p, CellName::testing_new("root"), &self.resolver)
                    .unwrap()
            });

            resolve_target_patterns(&self.resolver, &patterns, &*self.file_ops).await
        }
    }

    trait ResolvedTargetPatternTestExt<T: PatternType> {
        fn assert_eq(&self, expected: &[(PackageLabel, PackageSpec<T>)]);
    }

    impl<T> ResolvedTargetPatternTestExt<T> for ResolvedPattern<T>
    where
        T: PatternType,
    {
        fn assert_eq(&self, expected: &[(PackageLabel, PackageSpec<T>)]) {
            let expected: BTreeMap<_, _> = expected.iter().map(|(p, s)| (p.dupe(), s)).collect();

            let expected_keys: BTreeSet<_> = expected.keys().collect();
            let actual_keys: BTreeSet<_> = self.specs.keys().collect();

            let missing_keys: Vec<_> = expected_keys.difference(&actual_keys).collect();
            assert!(
                missing_keys.is_empty(),
                "Expected entries for keys {:?}. Had {:?}",
                missing_keys,
                self.specs
            );

            let extra_keys: Vec<_> = actual_keys.difference(&expected_keys).collect();
            assert!(
                extra_keys.is_empty(),
                "Got unexpected keys {:?}",
                extra_keys
            );

            for (k, v) in expected {
                assert_eq!(v, self.specs.get(&k).unwrap());
            }
        }
    }

    #[tokio::test]
    async fn test_simple_specs_targets() -> anyhow::Result<()> {
        let tester = TestPatternResolver::new(&[("root", ""), ("child", "child/cell")], &[])?;
        tester
            .resolve::<TargetPatternExtra>(&[])
            .await?
            .assert_eq(&[]);
        tester
            .resolve::<TargetPatternExtra>(&[
                "//some:target",
                "//some:other_target",
                "child//a/package:",
            ])
            .await?
            .assert_eq(&[
                (
                    PackageLabel::testing_parse("root//some"),
                    PackageSpec::Targets(vec![
                        (TargetName::unchecked_new("target"), TargetPatternExtra),
                        (
                            TargetName::unchecked_new("other_target"),
                            TargetPatternExtra,
                        ),
                    ]),
                ),
                (
                    PackageLabel::testing_parse("child//a/package"),
                    PackageSpec::All,
                ),
            ]);
        Ok(())
    }

    #[tokio::test]
    async fn test_simple_specs_providers() -> anyhow::Result<()> {
        let tester = TestPatternResolver::new(&[("root", ""), ("child", "child/cell")], &[])?;
        tester
            .resolve::<ProvidersPatternExtra>(&[])
            .await?
            .assert_eq(&[]);
        tester
            .resolve::<ProvidersPatternExtra>(&[
                "//some:target",
                "//some:other_target[my-label]",
                "child//a/package:",
            ])
            .await?
            .assert_eq(&[
                (
                    PackageLabel::testing_parse("root//some"),
                    PackageSpec::Targets(vec![
                        (
                            TargetName::unchecked_new("target"),
                            ProvidersPatternExtra {
                                providers: ProvidersName::Default,
                            },
                        ),
                        (
                            TargetName::unchecked_new("other_target"),
                            ProvidersPatternExtra {
                                providers: ProvidersName::NonDefault(Box::new(
                                    NonDefaultProvidersName::Named(Box::new([ProviderName::new(
                                        "my-label".to_owned(),
                                    )
                                    .unwrap()])),
                                )),
                            },
                        ),
                    ]),
                ),
                (
                    PackageLabel::testing_parse("child//a/package"),
                    PackageSpec::All,
                ),
            ]);
        Ok(())
    }

    #[test_case(PhantomData::< TargetPatternExtra >; "parsing TargetPattern")]
    #[test_case(PhantomData::< ProvidersPatternExtra >; "parsing ProvidersPattern")]
    fn test_recursive_specs<T: PatternType>(_: PhantomData<T>) {
        let rt = tokio::runtime::Runtime::new().unwrap();
        rt.block_on(async {
            let tester = TestPatternResolver::new(
                &[("root", ""), ("child", "child/cell")],
                &[
                    ("BUCK"),
                    ("other/BUCK"),
                    ("other/a/bit/deeper/BUCK"),
                    ("other/a/bit/deeper/and/deeper/BUCK"),
                    ("some/thing/dir/a/BUCK"),
                    ("some/thing/dir/a/b/BUCK"),
                    ("some/thing/extra/BUCK"),
                    ("child/cell/BUCK"),
                    ("child/cell/foo/BUCK"),
                ],
            )
            .unwrap();
            tester.resolve::<T>(&[]).await.unwrap().assert_eq(&[]);
            tester
                .resolve::<T>(&[
                    "//other/...",
                    "//other:target_that_doesnt_matter",
                    "//some/...",
                    "//some/thing/extra/...",
                    // root of cell.
                    "child//...",
                ])
                .await
                .unwrap()
                .assert_eq(&[
                    (PackageLabel::testing_parse("root//other"), PackageSpec::All),
                    (
                        PackageLabel::testing_parse("root//other/a/bit/deeper"),
                        PackageSpec::All,
                    ),
                    (
                        PackageLabel::testing_parse("root//other/a/bit/deeper/and/deeper"),
                        PackageSpec::All,
                    ),
                    (
                        PackageLabel::testing_parse("root//some/thing/dir/a"),
                        PackageSpec::All,
                    ),
                    (
                        PackageLabel::testing_parse("root//some/thing/dir/a/b"),
                        PackageSpec::All,
                    ),
                    (
                        PackageLabel::testing_parse("root//some/thing/extra"),
                        PackageSpec::All,
                    ),
                    (PackageLabel::testing_parse("child//"), PackageSpec::All),
                    (PackageLabel::testing_parse("child//foo"), PackageSpec::All),
                ]);
        })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use async_trait::async_trait;
use buck2_common::dice::cycles::CycleAdapterDescriptor;
use buck2_common::result::SharedResult;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use buck2_node::nodes::unconfigured::RuleKind;
use buck2_node::target_calculation::ConfiguredTargetCalculationImpl;
use buck2_node::target_calculation::CONFIGURED_TARGET_CALCULATION;
use buck2_util::cycle_detector::CycleDescriptor;
use derive_more::Display;
use dice::DiceComputations;
use dupe::Dupe;
use gazebo::prelude::*;
use thiserror::Error;

use crate::configuration::calculation::ConfigurationCalculation;
use crate::nodes::calculation::get_execution_platform_toolchain_dep;
use crate::nodes::calculation::ConfiguredTargetNodeKey;

struct ConfiguredTargetCalculationInstance;

pub(crate) fn init_configured_target_calculation() {
    CONFIGURED_TARGET_CALCULATION.init(&ConfiguredTargetCalculationInstance);
}

#[async_trait]
impl ConfiguredTargetCalculationImpl for ConfiguredTargetCalculationInstance {
    async fn get_configured_target(
        &self,
        ctx: &DiceComputations,
        target: &TargetLabel,
        global_target_platform: Option<&TargetLabel>,
    ) -> anyhow::Result<ConfiguredTargetLabel> {
        let node = ctx.get_target_node(target).await?;

        let get_platform_configuration = async || -> SharedResult<ConfigurationData> {
            Ok(match global_target_platform {
                Some(global_target_platform) => {
                    ctx.get_platform_configuration(global_target_platform)
                        .await?
                }
                None => match node.get_default_target_platform() {
                    Some(target) => ctx.get_platform_configuration(target).await?,
                    None => ctx.get_default_platform(target).await?,
                },
            })
        };

        match node.rule_kind() {
            RuleKind::Configuration => Ok(target.configure(ConfigurationData::unbound())),
            RuleKind::Normal => Ok(target.configure(get_platform_configuration().await?)),
            RuleKind::Toolchain => {
                let cfg = get_platform_configuration().await?;
                let exec_cfg =
                    get_execution_platform_toolchain_dep(ctx, &target.configure(cfg.dupe()), &node)
                        .await?
                        .cfg();
                Ok(target.configure_with_exec(cfg, exec_cfg.cfg().dupe()))
            }
        }
    }
}

#[derive(Error, Debug, Clone, Dupe)]
pub struct ConfiguredGraphCycleError {
    cycle: Arc<Vec<ConfiguredGraphCycleKeys>>,
}

impl std::fmt::Display for ConfiguredGraphCycleError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(
            f,
            "Configured target cycle detected (`->` means \"depends on\"):"
        )?;
        for p in self.cycle.iter() {
            writeln!(f, "  {} ->", p)?;
        }
        // point back at the first item in the cycle.
        writeln!(f, "  {}", self.cycle.first().unwrap())?;
        Ok(())
    }
}

// TODO(cjhopman): There's other keys that could be involved in a cycle in the configured graph and they should probably also be tracked
// here. Would be good to check on things like transitions, toolchains, configuration nodes. Still, this will currently catch most
// configured graph cycles.
#[derive(Debug, Display, Clone, Eq, PartialEq, Hash)]
pub enum ConfiguredGraphCycleKeys {
    #[display(fmt = "{}", _0)]
    ConfiguredTargetNode(ConfiguredTargetNodeKey),
}

#[derive(Debug)]
pub struct ConfiguredGraphCycleDescriptor;

impl CycleDescriptor for ConfiguredGraphCycleDescriptor {
    type Key = ConfiguredGraphCycleKeys;

    type Error = ConfiguredGraphCycleError;

    fn cycle_error(cycle: Vec<&Self::Key>) -> Self::Error {
        ConfiguredGraphCycleError {
            cycle: Arc::new(cycle.cloned()),
        }
    }
}

impl CycleAdapterDescriptor for ConfiguredGraphCycleDescriptor {
    fn to_key(key: &dyn std::any::Any) -> Option<Self::Key> {
        if let Some(v) = key.downcast_ref::<ConfiguredTargetNodeKey>() {
            return Some(ConfiguredGraphCycleKeys::ConfiguredTargetNode(v.dupe()));
        }
        None
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

#![feature(async_closure)]

pub mod calculation;
pub mod configuration;
pub mod nodes;

pub fn init_late_bindings() {
    calculation::init_configured_target_calculation();
    nodes::calculation::init_configured_target_node_calculation();
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use allocative::Allocative;use starlark_map::small_map::SmallMap;
use anyhow::Context;
use async_trait::async_trait;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::legacy_configs::dice::HasLegacyConfigs;
use buck2_common::legacy_configs::parse_config_section_and_key;
use buck2_common::result::SharedResult;
use buck2_common::result::ToSharedResultExt;
use buck2_common::result::ToUnsharedResultExt;
use buck2_core::cells::name::CellName;
use buck2_util::collections::unordered_map::UnorderedMap;
use buck2_core::configuration::compatibility::MaybeCompatible;
use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_node::configuration::execution::ExecutionPlatform;
use buck2_node::configuration::execution::ExecutionPlatformError;
use buck2_node::configuration::execution::ExecutionPlatformIncompatibleReason;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use buck2_node::configuration::execution_platforms::ExecutionPlatformFallback;
use buck2_node::configuration::execution_platforms::ExecutionPlatforms;
use buck2_node::configuration::execution_platforms::ExecutionPlatformsData;
use buck2_node::configuration::resolved::ConfigurationNode;
use buck2_node::configuration::resolved::ConfigurationSettingKey;
use buck2_node::configuration::resolved::ResolvedConfiguration;
use buck2_node::configuration::target_platform_detector::TargetPlatformDetector;
use buck2_node::configuration::toolchain_constraints::ToolchainConstraints;
use buck2_node::nodes::configured_frontend::ConfiguredTargetNodeCalculation;
use derive_more::Display;
use dice::DiceComputations;
use dice::Key;
use dupe::Dupe;
use gazebo::prelude::*;
use indexmap::IndexSet;
use more_futures::cancellation::CancellationContext;

use thiserror::Error;

use buck2_build_api::analysis::calculation::RuleAnalysisCalculation;
use buck2_build_api::interpreter::rule_defs::provider::builtin::configuration_info::FrozenConfigurationInfo;
use buck2_build_api::interpreter::rule_defs::provider::builtin::execution_platform_registration_info::ExecutionPlatformRegistrationInfo;
use buck2_build_api::interpreter::rule_defs::provider::builtin::platform_info::PlatformInfo;

#[derive(Debug, Error)]
pub enum ConfigurationError {
    #[error("Expected a ConfigurationInfo provider from `{0}`.")]
    MissingConfigurationInfoProvider(TargetLabel),
    #[error("Expected `{0}` to be a `platform()` target, but it had no `PlatformInfo` provider.")]
    MissingPlatformInfo(TargetLabel),
    #[error(
        "Expected `{0}` to provide a `ExecutionPlatformRegistrationInfo` as it's configured as the `build.execution_platforms` value."
    )]
    MissingExecutionPlatformRegistrationInfo(TargetLabel),
    #[error(
        "Platform target `{0}` evaluation returned `ProviderInfo` label `{1}` which resolved to an unequal configuration"
    )]
    PlatformEvalUnequalConfiguration(TargetLabel, TargetLabel),
}

async fn get_target_platform_detector(
    ctx: &DiceComputations,
) -> SharedResult<Arc<TargetPlatformDetector>> {
    // This requires a bit of computation so cache it on the graph.
    // TODO(cjhopman): Should we construct this (and similar buckconfig-derived objects) as part of the buck config itself?
    #[derive(Clone, Display, Debug, Dupe, Eq, Hash, PartialEq, Allocative)]
    #[display(fmt = "TargetPlatformDetectorKey")]
    struct TargetPlatformDetectorKey;

    #[async_trait]
    impl Key for TargetPlatformDetectorKey {
        type Value = SharedResult<Arc<TargetPlatformDetector>>;
        async fn compute(
            &self,
            ctx: &DiceComputations,
            _cancellation: &CancellationContext,
        ) -> Self::Value {
            // We get this off the root cell's config. It's not clear that that's the appropriate way to do it, but its the easiest to get working at FB.
            // TODO(cjhopman): Consider revisiting that approach.
            let resolver = ctx.get_cell_resolver().await?;
            let root_cell = resolver.root_cell();

            Ok(Arc::new(
                match ctx
                    .get_legacy_config_property(
                        root_cell,
                        "parser",
                        "target_platform_detector_spec",
                    )
                    .await?
                {
                    None => TargetPlatformDetector::empty(),
                    Some(spec) => TargetPlatformDetector::parse_spec(&spec, root_cell, &resolver)?,
                },
            ))
        }

        fn equality(x: &Self::Value, y: &Self::Value) -> bool {
            match (x, y) {
                (Ok(x), Ok(y)) => x == y,
                _ => false,
            }
        }
    }

    ctx.compute(&TargetPlatformDetectorKey).await?
}

/// Returns the configured [ExecutionPlatforms] or None if `build.execution_platforms` is not configured.
async fn get_execution_platforms(
    ctx: &DiceComputations,
) -> SharedResult<Option<ExecutionPlatforms>> {
    let cells = ctx.get_cell_resolver().await?;

    let execution_platforms_target = ctx
        .get_legacy_config_property(cells.root_cell(), "build", "execution_platforms")
        .await?;

    let execution_platforms_target = match execution_platforms_target {
        Some(v) => TargetLabel::parse(&v, cells.root_cell(), &cells)?,
        None => {
            return Ok(None);
        }
    };

    let analysis_result = ctx
        .get_configuration_analysis_result(&execution_platforms_target)
        .await?;
    let providers = analysis_result.providers();

    let result = ExecutionPlatformRegistrationInfo::from_providers(providers.provider_collection())
        .ok_or_else(|| {
            anyhow::anyhow!(
                ConfigurationError::MissingExecutionPlatformRegistrationInfo(
                    execution_platforms_target
                )
            )
        })?;

    let mut platforms = Vec::new();
    for platform in result.platforms()? {
        platforms.push(platform.to_execution_platform()?);
    }
    Ok(Some(Arc::new(ExecutionPlatformsData::new(
        platforms,
        result.fallback()?,
    ))))
}

/// Check if a particular execution platform is compatible with the constraints or not.
/// Return either Ok/Ok if it is, or a reason if not.
async fn check_execution_platform(
    ctx: &DiceComputations,
    target_node_cell: CellName,
    exec_compatible_with: &[TargetLabel],
    exec_deps: &IndexSet<TargetLabel>,
    exec_platform: &ExecutionPlatform,
    toolchain_allows: &[ToolchainConstraints],
) -> anyhow::Result<Result<(), ExecutionPlatformIncompatibleReason>> {
    // First check if the platform satisfies the toolchain requirements
    for allowed in toolchain_allows {
        if let Err(e) = allowed.allows(exec_platform) {
            return Ok(Err(
                ExecutionPlatformIncompatibleReason::ExecutionDependencyIncompatible(e),
            ));
        }
    }

    let resolved_platform_configuration = ctx
        .get_resolved_configuration(
            exec_platform.cfg(),
            target_node_cell,
            exec_compatible_with.iter(),
        )
        .await?;

    // Then check if the platform satisfies compatible_with
    for constraint in exec_compatible_with {
        if resolved_platform_configuration
            .matches(constraint)
            .is_none()
        {
            return Ok(Err(
                ExecutionPlatformIncompatibleReason::ConstraintNotSatisfied(constraint.dupe()),
            ));
        }
    }

    // Then check that all exec_deps are compatible with the platform
    for dep in exec_deps {
        let dep_node = ctx
            .get_configured_target_node(
                &dep.configure_pair_no_exec(exec_platform.cfg_pair_no_exec().dupe()),
            )
            .await
            .with_context(|| {
                format!(
                    "Error checking compatibility of `{}` with `{}`",
                    dep,
                    exec_platform.cfg()
                )
            })?;
        if let MaybeCompatible::Incompatible(reason) = dep_node {
            return Ok(Err(
                ExecutionPlatformIncompatibleReason::ExecutionDependencyIncompatible(reason.dupe()),
            ));
        }
    }

    Ok(Ok(()))
}

async fn get_execution_platforms_enabled(
    ctx: &DiceComputations,
) -> anyhow::Result<ExecutionPlatforms> {
    ctx.get_execution_platforms()
        .await?
        .context("Execution platforms are not enabled")
}

async fn resolve_toolchain_constraints_from_constraints(
    ctx: &DiceComputations,
    target: &ConfiguredTargetLabel,
    exec_compatible_with: &[TargetLabel],
    exec_deps: &IndexSet<TargetLabel>,
    toolchain_allows: &[ToolchainConstraints],
) -> SharedResult<ToolchainConstraints> {
    let mut incompatible = SmallMap::new();
    for exec_platform in get_execution_platforms_enabled(ctx).await?.candidates() {
        if let Err(e) = check_execution_platform(
            ctx,
            target.pkg().cell_name(),
            exec_compatible_with,
            exec_deps,
            exec_platform,
            toolchain_allows,
        )
        .await?
        {
            incompatible.insert(
                exec_platform.dupe(),
                Arc::new(e.into_incompatible_platform_reason(target.dupe())),
            );
        }
    }
    Ok(ToolchainConstraints::new(incompatible))
}

async fn resolve_execution_platform_from_constraints(
    ctx: &DiceComputations,
    target_node_cell: CellName,
    exec_compatible_with: &[TargetLabel],
    exec_deps: &IndexSet<TargetLabel>,
    toolchain_allows: &[ToolchainConstraints],
) -> SharedResult<ExecutionPlatformResolution> {
    let mut skipped = Vec::new();
    let execution_platforms = get_execution_platforms_enabled(ctx).await?;
    for exec_platform in execution_platforms.candidates() {
        match check_execution_platform(
            ctx,
            target_node_cell,
            exec_compatible_with,
            exec_deps,
            exec_platform,
            toolchain_allows,
        )
        .await?
        {
            Ok(()) => {
                return Ok(ExecutionPlatformResolution::new(
                    Some(exec_platform.dupe()),
                    skipped,
                ));
            }
            Err(reason) => {
                skipped.push((exec_platform.id(), reason));
            }
        }
    }

    match execution_platforms.fallback() {
        ExecutionPlatformFallback::UseUnspecifiedExec => {
            Ok(ExecutionPlatformResolution::new(None, skipped))
        }
        ExecutionPlatformFallback::Error => Err(anyhow::anyhow!(
            ExecutionPlatformError::NoCompatiblePlatform(Arc::new(skipped))
        )
        .into()),
        ExecutionPlatformFallback::Platform(platform) => Ok(ExecutionPlatformResolution::new(
            Some(platform.dupe()),
            skipped,
        )),
    }
}

async fn configuration_matches(
    ctx: &DiceComputations,
    cfg: &ConfigurationData,
    target_node_cell: CellName,
    constraints_and_configs: &ConfigSettingData,
) -> SharedResult<bool> {
    for (key, value) in &constraints_and_configs.constraints {
        match cfg.get_constraint_value(key)? {
            Some(v) if v == value => {}
            _ => return Ok(false),
        }
    }

    // Cell used for buckconfigs is set to cell of target that applies select to match Buck v1 behavior.
    // Eventually, we want this to be the cell of the platform instead.
    for (raw_section_and_key, config_value) in &constraints_and_configs.buckconfigs {
        let config_section_and_key = parse_config_section_and_key(raw_section_and_key, None)?;
        let v = ctx
            .get_legacy_config_property(
                target_node_cell,
                &config_section_and_key.section,
                &config_section_and_key.key,
            )
            .await?;
        match v {
            Some(v) if &*v == config_value => {}
            _ => return Ok(false),
        }
    }

    Ok(true)
}

#[derive(Clone, Dupe, Display, Debug, Eq, Hash, PartialEq, Allocative)]
#[display(fmt = "ExecutionPlatforms")]
pub struct ExecutionPlatformsKey;

#[derive(Clone, Display, Debug, Eq, Hash, PartialEq, Allocative)]
#[display(fmt = "ConfigurationNode({}, {})", cfg_target, target_cfg)]
struct ConfigurationNodeKey {
    target_cfg: ConfigurationData,
    target_cell: CellName,
    cfg_target: TargetLabel,
}

#[derive(Clone, Display, Debug, Eq, Hash, PartialEq, Allocative)]
#[display(
    fmt = "ResolvedConfigurationKey(target_cfg: {}, cell: {}, configuration_deps size {})",
    target_cfg,
    target_cell,
    "configuration_deps.len()"
)]
struct ResolvedConfigurationKey {
    target_cfg: ConfigurationData,
    target_cell: CellName,
    configuration_deps: Vec<TargetLabel>,
}

#[async_trait]
pub trait ConfigurationCalculation {
    async fn get_default_platform(&self, target: &TargetLabel) -> SharedResult<ConfigurationData>;

    async fn get_platform_configuration(
        &self,
        target: &TargetLabel,
    ) -> anyhow::Result<ConfigurationData>;

    async fn get_resolved_configuration<'a, T: IntoIterator<Item = &'a TargetLabel> + Send>(
        &self,
        target_cfg: &ConfigurationData,
        target_node_cell: CellName,
        configuration_deps: T,
    ) -> SharedResult<ResolvedConfiguration>;

    async fn get_configuration_node(
        &self,
        target_cfg: &ConfigurationData,
        target_cell: CellName,
        cfg_target: &TargetLabel,
    ) -> SharedResult<ConfigurationNode>;

    /// Returns a list of the configured execution platforms. This looks up the providers on the target
    /// configured **in the root cell's buckconfig** with key `build.execution_platforms`. If there's no
    /// value configured, it will return `None` which indicates we should fallback to the legacy execution
    /// platform behavior.
    async fn get_execution_platforms(&self) -> SharedResult<Option<ExecutionPlatforms>>;

    /// Gets the compatible execution platforms for a give list of compatible_with constraints and execution deps.
    ///
    /// We do this as a sort of monolithic computation (rather than checking things one-by-one or separating
    /// compatible_with and exec deps) because we expect those values to be common across many nodes (for example,
    /// all c++ targets targeting a specific platform are likely to share compatible_with and exec_deps except
    /// for some rare exceptions). By having a monolithic key like `(Vec<TargetLabel>, Vec<TargetLabel>)` allows all
    /// those nodes to just have a single dice dep. This approach has the downside that it is less incremental, but
    /// we expect these things to change rarely.
    async fn resolve_execution_platform_from_constraints(
        &self,
        target_node_cell: CellName,
        exec_compatible_with: &[TargetLabel],
        exec_deps: &IndexSet<TargetLabel>,
        toolchain_allows: &[ToolchainConstraints],
    ) -> SharedResult<ExecutionPlatformResolution>;

    async fn resolve_toolchain_constraints_from_constraints(
        &self,
        target: &ConfiguredTargetLabel,
        exec_compatible_with: &[TargetLabel],
        exec_deps: &IndexSet<TargetLabel>,
        toolchain_allows: &[ToolchainConstraints],
    ) -> SharedResult<ToolchainConstraints>;
}

async fn compute_platform_configuration_no_label_check(
    ctx: &DiceComputations,
    target: &TargetLabel,
) -> anyhow::Result<ConfigurationData> {
    let result = ctx.get_configuration_analysis_result(target).await?;
    let platform_info = PlatformInfo::from_providers(result.providers().provider_collection())
        .ok_or_else(|| ConfigurationError::MissingPlatformInfo(target.dupe()))?;
    platform_info.to_configuration()
}

/// Basically, evaluate `platform()` rule.
async fn compute_platform_configuration(
    ctx: &DiceComputations,
    target: &TargetLabel,
) -> anyhow::Result<ConfigurationData> {
    let configuration_data = compute_platform_configuration_no_label_check(ctx, target).await?;

    let cell_resolver = ctx.get_cell_resolver().await?;
    let parsed_target = TargetLabel::parse(
        configuration_data.label()?,
        cell_resolver.root_cell(),
        &cell_resolver,
    )
    .context("`PlatformInfo` label for `platform()` rule should be a valid target label")?;

    if target != &parsed_target {
        // `target` may be an `alias` target. In this case we evaluate the label
        // from the configuration and check it resolves to the same configuration.

        let cfg_again = compute_platform_configuration_no_label_check(ctx, &parsed_target).await
            .context("Checking whether label of returned `PlatformInfo` resolves to the same configuration")?;
        if cfg_again != configuration_data {
            return Err(ConfigurationError::PlatformEvalUnequalConfiguration(
                target.dupe(),
                parsed_target,
            )
            .into());
        }
    }

    Ok(configuration_data)
}

#[async_trait]
impl ConfigurationCalculation for DiceComputations {
    async fn get_platform_configuration(
        &self,
        target: &TargetLabel,
    ) -> anyhow::Result<ConfigurationData> {
        #[derive(derive_more::Display, Debug, Eq, Hash, PartialEq, Clone, Allocative)]
        struct PlatformConfigurationKey(TargetLabel);

        #[async_trait]
        impl Key for PlatformConfigurationKey {
            type Value = SharedResult<ConfigurationData>;

            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                compute_platform_configuration(ctx, &self.0)
                    .await
                    .shared_error()
            }

            fn equality(x: &Self::Value, y: &Self::Value) -> bool {
                match (x, y) {
                    (Ok(x), Ok(y)) => x == y,
                    _ => false,
                }
            }
        }

        self.compute(&PlatformConfigurationKey(target.dupe()))
            .await?
            .unshared_error()
    }

    async fn get_default_platform(&self, target: &TargetLabel) -> SharedResult<ConfigurationData> {
        let detector = get_target_platform_detector(self).await?;
        if let Some(target) = detector.detect(target) {
            return self.get_platform_configuration(target).await.shared_error();
        }
        // TODO(cjhopman): This needs to implement buck1's approach to determining target platform, it's currently missing the fallback to buckconfig parser.target_platform.
        Ok(ConfigurationData::unspecified())
    }

    async fn get_resolved_configuration<'a, T: IntoIterator<Item = &'a TargetLabel> + Send>(
        &self,
        target_cfg: &ConfigurationData,
        target_cell: CellName,
        configuration_deps: T,
    ) -> SharedResult<ResolvedConfiguration> {
        #[async_trait]
        impl Key for ResolvedConfigurationKey {
            type Value = SharedResult<ResolvedConfiguration>;

            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let config_futures: Vec<_> = self
                    .configuration_deps
                    .map(|d| ctx.get_configuration_node(&self.target_cfg, self.target_cell, d));
                let config_nodes = futures::future::join_all(config_futures).await;

                let mut resolved_settings = UnorderedMap::with_capacity(config_nodes.len());
                for node in config_nodes {
                    let node = node?;
                    resolved_settings.insert(ConfigurationSettingKey(node.label().dupe()), node);
                }
                Ok(ResolvedConfiguration::new(
                    ConfigurationNoExec::new(self.target_cfg.dupe()),
                    resolved_settings,
                ))
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                false
            }
        }

        let configuration_deps: Vec<TargetLabel> =
            configuration_deps.into_iter().map(|t| t.dupe()).collect();
        self.compute(&ResolvedConfigurationKey {
            target_cfg: target_cfg.dupe(),
            target_cell,
            configuration_deps,
        })
        .await?
    }

    async fn get_configuration_node(
        &self,
        target_cfg: &ConfigurationData,
        target_cell: CellName,
        cfg_target: &TargetLabel,
    ) -> SharedResult<ConfigurationNode> {
        #[async_trait]
        impl Key for ConfigurationNodeKey {
            type Value = SharedResult<ConfigurationNode>;

            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let analysis_result = ctx
                    .get_configuration_analysis_result(&self.cfg_target)
                    .await?;
                let providers = analysis_result.providers();

                // capture the result so the temporaries get dropped before analysis_result
                let result = match FrozenConfigurationInfo::from_providers(
                    providers.provider_collection(),
                ) {
                    Some(configuration_info) => configuration_info,
                    None => {
                        return Err::<_, anyhow::Error>(
                            ConfigurationError::MissingConfigurationInfoProvider(
                                self.cfg_target.dupe(),
                            )
                            .into(),
                        )
                        .shared_error();
                    }
                }
                .to_config_setting_data();

                let matches =
                    configuration_matches(ctx, &self.target_cfg, self.target_cell, &result).await?;

                Ok(ConfigurationNode::new(
                    self.target_cfg.dupe(),
                    self.cfg_target.dupe(),
                    result,
                    matches,
                ))
            }

            fn equality(x: &Self::Value, y: &Self::Value) -> bool {
                match (x, y) {
                    (Ok(x), Ok(y)) => x == y,
                    _ => false,
                }
            }
        }

        self.compute(&ConfigurationNodeKey {
            target_cfg: target_cfg.dupe(),
            target_cell,
            cfg_target: cfg_target.dupe(),
        })
        .await?
        .with_context(|| {
            format!(
                "Error getting configuration node of `{}` within the `{}` configuration",
                cfg_target, target_cfg,
            )
        })
        .shared_error()
    }

    async fn get_execution_platforms(&self) -> SharedResult<Option<ExecutionPlatforms>> {
        #[async_trait]
        impl Key for ExecutionPlatformsKey {
            type Value = SharedResult<Option<ExecutionPlatforms>>;
            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                get_execution_platforms(ctx).await
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                // TODO(cjhopman) should these be comparable for caching
                false
            }
        }

        self.compute(&ExecutionPlatformsKey).await?
    }

    async fn resolve_execution_platform_from_constraints(
        &self,
        target_node_cell: CellName,
        exec_compatible_with: &[TargetLabel],
        exec_deps: &IndexSet<TargetLabel>,
        toolchain_allows: &[ToolchainConstraints],
    ) -> SharedResult<ExecutionPlatformResolution> {
        resolve_execution_platform_from_constraints(
            self,
            target_node_cell,
            exec_compatible_with,
            exec_deps,
            toolchain_allows,
        )
        .await
    }

    async fn resolve_toolchain_constraints_from_constraints(
        &self,
        target: &ConfiguredTargetLabel,
        exec_compatible_with: &[TargetLabel],
        exec_deps: &IndexSet<TargetLabel>,
        toolchain_allows: &[ToolchainConstraints],
    ) -> SharedResult<ToolchainConstraints> {
        resolve_toolchain_constraints_from_constraints(
            self,
            target,
            exec_compatible_with,
            exec_deps,
            toolchain_allows,
        )
        .await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub mod calculation;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! Calculations relating to 'TargetNode's that runs on Dice

use std::ops::ControlFlow;
use std::sync::Arc;

use allocative::Allocative;
use anyhow::Context;
use async_trait::async_trait;
use buck2_build_api::transition::TRANSITION_CALCULATION;
use buck2_common::dice::cycles::CycleGuard;
use buck2_common::result::SharedError;
use buck2_common::result::SharedResult;
use buck2_common::result::ToSharedResultExt;
use buck2_common::result::ToUnsharedResultExt;
use buck2_core::cells::name::CellName;
use buck2_core::configuration::compatibility::IncompatiblePlatformReason;
use buck2_core::configuration::compatibility::IncompatiblePlatformReasonCause;
use buck2_core::configuration::compatibility::MaybeCompatible;
use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::configuration::pair::ConfigurationWithExec;
use buck2_core::configuration::transition::applied::TransitionApplied;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_execute::execute::dice_data::HasFallbackExecutorConfig;
use buck2_node::attrs::configuration_context::AttrConfigurationContext;
use buck2_node::attrs::configuration_context::AttrConfigurationContextImpl;
use buck2_node::attrs::configured_attr::ConfiguredAttr;
use buck2_node::attrs::configured_traversal::ConfiguredAttrTraversal;
use buck2_node::attrs::display::AttrDisplayWithContextExt;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::attrs::internal::EXEC_COMPATIBLE_WITH_ATTRIBUTE_FIELD;
use buck2_node::attrs::internal::LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD;
use buck2_node::attrs::internal::TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD;
use buck2_node::configuration::execution::ExecutionPlatform;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use buck2_node::configuration::resolved::ConfigurationSettingKeyRef;
use buck2_node::configuration::resolved::ResolvedConfiguration;
use buck2_node::configuration::toolchain_constraints::ToolchainConstraints;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use buck2_node::nodes::configured_frontend::ConfiguredTargetNodeCalculation;
use buck2_node::nodes::configured_frontend::ConfiguredTargetNodeCalculationImpl;
use buck2_node::nodes::configured_frontend::CONFIGURED_TARGET_NODE_CALCULATION;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_node::visibility::VisibilityError;
use buck2_util::collections::ordered_map::OrderedMap;
use derive_more::Display;
use dice::DiceComputations;
use dice::Key;
use dupe::Dupe;
use indexmap::IndexSet;
use more_futures::cancellation::CancellationContext;
use starlark_map::small_set::SmallSet;
use thiserror::Error;

use crate::calculation::ConfiguredGraphCycleDescriptor;
use crate::configuration::calculation::ConfigurationCalculation;

#[derive(Debug, thiserror::Error)]
enum NodeCalculationError {
    #[error("expected `{0}` attribute to be a list but got `{1}`")]
    TargetCompatibleNotList(String, String),
    #[error(
        "`{0}` had both `{}` and `{}` attributes. It should only have one.",
        TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
        LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD
    )]
    BothTargetCompatibleWith(String),
}

enum CompatibilityConstraints {
    Any(ConfiguredAttr),
    All(ConfiguredAttr),
}

async fn compute_platform_cfgs(
    ctx: &DiceComputations,
    node: &TargetNode,
) -> anyhow::Result<OrderedMap<TargetLabel, ConfigurationData>> {
    let mut platform_map = OrderedMap::new();
    for platform_target in node.platform_deps() {
        let config = ctx.get_platform_configuration(platform_target).await?;
        platform_map.insert(platform_target.dupe(), config);
    }

    Ok(platform_map)
}

async fn legacy_execution_platform(
    ctx: &DiceComputations,
    cfg: &ConfigurationNoExec,
) -> ExecutionPlatform {
    ExecutionPlatform::legacy_execution_platform(
        ctx.get_fallback_executor_config().clone(),
        cfg.dupe(),
    )
}

#[derive(Debug, Error)]
enum ToolchainDepError {
    #[error("Can't find toolchain_dep execution platform using configuration `{0}`")]
    ToolchainDepMissingPlatform(ConfigurationData),
    #[error("Target `{0}` was used as a toolchain_dep, but is not a toolchain rule")]
    NonToolchainRuleUsedAsToolchainDep(TargetLabel),
    #[error("Target `{0}` was used not as a toolchain_dep, but is a toolchain rule")]
    ToolchainRuleUsedAsNormalDep(TargetLabel),
    #[error("Target `{0}` has a transition_dep, which is not permitted on a toolchain rule")]
    ToolchainTransitionDep(TargetLabel),
}

pub async fn find_execution_platform_by_configuration(
    ctx: &DiceComputations,
    exec_cfg: &ConfigurationData,
    cfg: &ConfigurationData,
) -> SharedResult<ExecutionPlatform> {
    match ctx.get_execution_platforms().await? {
        Some(platforms) if exec_cfg != &ConfigurationData::unbound_exec() => {
            for c in platforms.candidates() {
                if c.cfg() == exec_cfg {
                    return Ok(c.dupe());
                }
            }
            Err(SharedError::new(
                ToolchainDepError::ToolchainDepMissingPlatform(exec_cfg.dupe()),
            ))
        }
        _ => Ok(legacy_execution_platform(ctx, &ConfigurationNoExec::new(cfg.dupe())).await),
    }
}

#[derive(Default)]
pub struct ExecutionPlatformConstraints {
    exec_deps: IndexSet<TargetLabel>,
    toolchain_deps: IndexSet<ConfiguredTargetLabel>,
    exec_compatible_with: Vec<TargetLabel>,
}

impl ConfiguredAttrTraversal for ExecutionPlatformConstraints {
    fn dep(&mut self, _dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
        Ok(())
    }

    fn exec_dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
        // TODO(cjhopman): Check that the dep is in the unbound_exe configuration
        self.exec_deps.insert(dep.target().unconfigured().dupe());
        Ok(())
    }

    fn toolchain_dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
        self.toolchain_deps.insert(dep.target().dupe());
        Ok(())
    }
}

impl ExecutionPlatformConstraints {
    pub fn new_constraints(
        exec_deps: IndexSet<TargetLabel>,
        toolchain_deps: IndexSet<ConfiguredTargetLabel>,
        exec_compatible_with: Vec<TargetLabel>,
    ) -> Self {
        Self {
            exec_deps,
            toolchain_deps,
            exec_compatible_with,
        }
    }

    async fn new(
        ctx: &DiceComputations,
        node: &TargetNode,
        resolved_configuration: &ResolvedConfiguration,
        resolved_transitions: &OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>,
    ) -> SharedResult<Self> {
        let mut me = Self::default();

        let platform_cfgs = compute_platform_cfgs(ctx, node).await?;
        let cfg_ctx = AttrConfigurationContextImpl::new(
            resolved_configuration,
            ConfigurationNoExec::unbound_exec(),
            // We don't really need `resolved_transitions` here:
            // `Traversal` declared above ignores transitioned dependencies.
            // But we pass `resolved_transitions` here to prevent breakages in the future
            // if something here changes.
            resolved_transitions,
            &platform_cfgs,
        );

        for a in node.attrs(AttrInspectOptions::All) {
            let configured_attr = a.configure(&cfg_ctx).with_context(|| {
                format!(
                    "Error configuring attribute `{}` to resolve execution platform",
                    a.name
                )
            })?;
            configured_attr.traverse(node.label().pkg(), &mut me)?;
            if a.name == EXEC_COMPATIBLE_WITH_ATTRIBUTE_FIELD {
                for label in
                    ConfiguredTargetNode::attr_as_target_compatible_with(configured_attr.value)
                {
                    me.exec_compatible_with.push(label.with_context(|| {
                        format!("attribute `{}`", EXEC_COMPATIBLE_WITH_ATTRIBUTE_FIELD)
                    })?);
                }
            }
        }
        Ok(me)
    }

    async fn toolchain_allows(
        &self,
        ctx: &DiceComputations,
    ) -> SharedResult<Vec<ToolchainConstraints>> {
        // We could merge these constraints together, but the time to do that
        // probably outweighs the benefits given there are likely to only be a few
        // execution platforms to test.
        let mut result = Vec::with_capacity(self.toolchain_deps.len());
        for x in &self.toolchain_deps {
            result.push(execution_platforms_for_toolchain(ctx, x.dupe()).await?)
        }
        Ok(result)
    }

    async fn one(
        &self,
        ctx: &DiceComputations,
        node: &TargetNode,
    ) -> SharedResult<ExecutionPlatformResolution> {
        ctx.resolve_execution_platform_from_constraints(
            node.label().pkg().cell_name(),
            &self.exec_compatible_with,
            &self.exec_deps,
            &self.toolchain_allows(ctx).await?,
        )
        .await
    }

    pub async fn one_for_cell(
        &self,
        ctx: &DiceComputations,
        cell: CellName,
    ) -> SharedResult<ExecutionPlatformResolution> {
        ctx.resolve_execution_platform_from_constraints(
            cell,
            &self.exec_compatible_with,
            &self.exec_deps,
            &self.toolchain_allows(ctx).await?,
        )
        .await
    }

    async fn many(
        &self,
        ctx: &DiceComputations,
        target: &ConfiguredTargetLabel,
    ) -> SharedResult<ToolchainConstraints> {
        ctx.resolve_toolchain_constraints_from_constraints(
            target,
            &self.exec_compatible_with,
            &self.exec_deps,
            &self.toolchain_allows(ctx).await?,
        )
        .await
    }
}

async fn execution_platforms_for_toolchain(
    ctx: &DiceComputations,
    target: ConfiguredTargetLabel,
) -> SharedResult<ToolchainConstraints> {
    #[derive(Clone, Display, Debug, Dupe, Eq, Hash, PartialEq, Allocative)]
    struct ExecutionPlatformsForToolchainKey(ConfiguredTargetLabel);

    #[async_trait]
    impl Key for ExecutionPlatformsForToolchainKey {
        type Value = SharedResult<ToolchainConstraints>;
        async fn compute(
            &self,
            ctx: &DiceComputations,
            _cancellation: &CancellationContext,
        ) -> Self::Value {
            let node = ctx.get_target_node(self.0.unconfigured()).await?;
            if node.transition_deps().next().is_some() {
                // We could actually check this when defining the rule, but a bit of a corner
                // case, and much simpler to do so here.
                return Err(SharedError::new(ToolchainDepError::ToolchainTransitionDep(
                    self.0.unconfigured().dupe(),
                )));
            }
            let resolved_configuration = &ctx
                .get_resolved_configuration(
                    self.0.cfg(),
                    self.0.pkg().cell_name(),
                    node.get_configuration_deps(),
                )
                .await?;
            let constraints = ExecutionPlatformConstraints::new(
                ctx,
                &node,
                resolved_configuration,
                &OrderedMap::new(),
            )
            .await?;
            constraints.many(ctx, &self.0).await
        }

        fn equality(x: &Self::Value, y: &Self::Value) -> bool {
            match (x, y) {
                (Ok(x), Ok(y)) => x == y,
                _ => false,
            }
        }
    }

    ctx.compute(&ExecutionPlatformsForToolchainKey(target))
        .await?
}

pub async fn get_execution_platform_toolchain_dep(
    ctx: &DiceComputations,
    target_label: &ConfiguredTargetLabel,
    target_node: &TargetNode,
) -> SharedResult<ExecutionPlatformResolution> {
    assert!(target_node.is_toolchain_rule());
    let target_cfg = target_label.cfg();
    let target_cell = target_node.label().pkg().cell_name();
    let resolved_configuration = ctx
        .get_resolved_configuration(
            target_cfg,
            target_cell,
            target_node.get_configuration_deps(),
        )
        .await?;
    if target_node.transition_deps().next().is_some() {
        Err(SharedError::new(ToolchainDepError::ToolchainTransitionDep(
            target_label.unconfigured().dupe(),
        )))
    } else {
        resolve_execution_platform(
            ctx,
            target_node,
            &resolved_configuration,
            &OrderedMap::new(),
        )
        .await
    }
}

async fn resolve_execution_platform(
    ctx: &DiceComputations,
    node: &TargetNode,
    resolved_configuration: &ResolvedConfiguration,
    resolved_transitions: &OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>,
) -> SharedResult<ExecutionPlatformResolution> {
    // If no execution platforms are configured, we fall back to the legacy execution
    // platform behavior. We currently only support legacy execution platforms. That behavior is that there is a
    // single executor config (the fallback config) and the execution platform is in the same
    // configuration as the target.
    // The non-none case will be handled when we invoke the resolve_execution_platform() on ctx below, the none
    // case can't be handled there because we don't pass the full configuration into it.
    if ctx.get_execution_platforms().await?.is_none() {
        return Ok(ExecutionPlatformResolution::new(
            Some(legacy_execution_platform(ctx, resolved_configuration.cfg()).await),
            Vec::new(),
        ));
    };

    let constraints =
        ExecutionPlatformConstraints::new(ctx, node, resolved_configuration, resolved_transitions)
            .await?;
    constraints.one(ctx, node).await
}

fn unpack_target_compatible_with_attr(
    target_node: &TargetNode,
    resolved_cfg: &ResolvedConfiguration,
    attr_name: &str,
) -> anyhow::Result<Option<ConfiguredAttr>> {
    let attr = target_node.attr_or_none(attr_name, AttrInspectOptions::All);
    let attr = match attr {
        Some(attr) => attr,
        None => return Ok(None),
    };

    struct AttrConfigurationContextToResolveCompatibleWith<'c> {
        resolved_cfg: &'c ResolvedConfiguration,
    }

    impl<'c> AttrConfigurationContext for AttrConfigurationContextToResolveCompatibleWith<'c> {
        fn matches<'a>(&'a self, label: &TargetLabel) -> Option<&'a ConfigSettingData> {
            self.resolved_cfg
                .setting_matches(ConfigurationSettingKeyRef(label))
        }

        fn cfg(&self) -> ConfigurationNoExec {
            self.resolved_cfg.cfg().dupe()
        }

        fn exec_cfg(&self) -> ConfigurationNoExec {
            unreachable!(
                "exec_cfg() is not needed to resolve `{}` or `{}`",
                TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
                LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD
            )
        }

        fn toolchain_cfg(&self) -> ConfigurationWithExec {
            unreachable!()
        }

        fn platform_cfg(&self, _label: &TargetLabel) -> anyhow::Result<ConfigurationData> {
            unreachable!(
                "platform_cfg() is not needed to resolve `{}` or `{}`",
                TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
                LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD
            )
        }

        fn resolved_transitions(&self) -> &OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>> {
            unreachable!(
                "resolved_transitions() is not needed to resolve `{}` or `{}`",
                TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
                LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD
            )
        }
    }

    let attr = attr
        .configure(&AttrConfigurationContextToResolveCompatibleWith { resolved_cfg })
        .with_context(|| format!("Error configuring attribute `{}`", attr_name))?;

    match attr.value.unpack_list() {
        Some(values) => {
            if !values.is_empty() {
                Ok(Some(attr.value))
            } else {
                Ok(None)
            }
        }
        None => Err(NodeCalculationError::TargetCompatibleNotList(
            attr.name.to_owned(),
            attr.value.as_display_no_ctx().to_string(),
        )
        .into()),
    }
}

fn check_compatible(
    target_label: &ConfiguredTargetLabel,
    target_node: &TargetNode,
    resolved_cfg: &ResolvedConfiguration,
) -> anyhow::Result<MaybeCompatible<()>> {
    let target_compatible_with = unpack_target_compatible_with_attr(
        target_node,
        resolved_cfg,
        TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
    )?;
    let legacy_compatible_with = unpack_target_compatible_with_attr(
        target_node,
        resolved_cfg,
        LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
    )?;

    let compatibility_constraints = match (target_compatible_with, legacy_compatible_with) {
        (None, None) => return Ok(MaybeCompatible::Compatible(())),
        (Some(..), Some(..)) => {
            return Err(
                NodeCalculationError::BothTargetCompatibleWith(target_label.to_string()).into(),
            );
        }
        (Some(target_compatible_with), None) => {
            CompatibilityConstraints::All(target_compatible_with)
        }
        (None, Some(legacy_compatible_with)) => {
            CompatibilityConstraints::Any(legacy_compatible_with)
        }
    };

    // We are compatible if the list of target expressions is empty,
    // OR if we match ANY expression in the list of attributes.
    let check_compatibility = |attr| -> anyhow::Result<(Vec<_>, Vec<_>)> {
        let mut left = Vec::new();
        let mut right = Vec::new();
        for label in ConfiguredTargetNode::attr_as_target_compatible_with(attr) {
            let label = label?;
            match resolved_cfg.matches(&label) {
                Some(_) => left.push(label),
                None => right.push(label),
            }
        }

        Ok((left, right))
    };

    // We only record the first incompatibility, for either ANY or ALL.
    // TODO(cjhopman): Should we report _all_ the things that are incompatible?
    let incompatible_target = match compatibility_constraints {
        CompatibilityConstraints::Any(attr) => {
            let (compatible, incompatible) = check_compatibility(attr).with_context(|| {
                format!(
                    "attribute `{}`",
                    LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD
                )
            })?;
            let incompatible = incompatible.into_iter().next();
            match (compatible.is_empty(), incompatible.into_iter().next()) {
                (false, _) | (true, None) => {
                    return Ok(MaybeCompatible::Compatible(()));
                }
                (true, Some(v)) => v,
            }
        }
        CompatibilityConstraints::All(attr) => {
            let (_compatible, incompatible) = check_compatibility(attr).with_context(|| {
                format!("attribute `{}`", TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD)
            })?;
            match incompatible.into_iter().next() {
                Some(label) => label,
                None => {
                    return Ok(MaybeCompatible::Compatible(()));
                }
            }
        }
    };
    Ok(MaybeCompatible::Incompatible(Arc::new(
        IncompatiblePlatformReason {
            target: target_label.dupe(),
            cause: IncompatiblePlatformReasonCause::UnsatisfiedConfig(incompatible_target),
        },
    )))
}

/// Compute configured target node ignoring transition for this node.
async fn compute_configured_target_node_no_transition(
    target_label: &ConfiguredTargetLabel,
    target_node: TargetNode,
    ctx: &DiceComputations,
) -> anyhow::Result<MaybeCompatible<ConfiguredTargetNode>> {
    let target_cfg = target_label.cfg();
    let target_cell = target_node.label().pkg().cell_name();
    let resolved_configuration = ctx
        .get_resolved_configuration(
            target_cfg,
            target_cell,
            target_node.get_configuration_deps(),
        )
        .await?;

    // Must check for compatibility before evaluating non-compatibility attributes.
    if let MaybeCompatible::Incompatible(reason) =
        check_compatible(target_label, &target_node, &resolved_configuration)?
    {
        return Ok(MaybeCompatible::Incompatible(reason));
    }

    struct Traversal<'a> {
        deps: &'a mut SmallSet<ConfiguredProvidersLabel>,
        exec_deps: &'a mut SmallSet<ConfiguredProvidersLabel>,
    }

    impl ConfiguredAttrTraversal for Traversal<'_> {
        fn dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
            self.deps.insert(dep.clone());
            Ok(())
        }

        fn exec_dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
            self.exec_deps.insert(dep.clone());
            Ok(())
        }
    }

    let mut resolved_transitions = OrderedMap::new();
    for (_dep, tr) in target_node.transition_deps() {
        let resolved_cfg = TRANSITION_CALCULATION
            .get()?
            .apply_transition(ctx, &target_node, target_cfg, tr)
            .await?;
        resolved_transitions.insert(tr.dupe(), resolved_cfg);
    }

    let execution_platform_resolution = if target_cfg.is_unbound() {
        // The unbound configuration is used when evaluation configuration nodes.
        // That evaluation is
        // (1) part of execution platform resolution and
        // (2) isn't allowed to do execution
        // And so we use an "unspecified" execution platform to avoid cycles and cause any attempts at execution to fail.
        ExecutionPlatformResolution::unspecified()
    } else if let Some(exec_cfg) = target_label.exec_cfg() {
        // The label was produced by a toolchain_dep, so we use the execution platform of our parent
        // We need to convert that to an execution platform, so just find the one with the same configuration.
        ExecutionPlatformResolution::new(
            Some(
                find_execution_platform_by_configuration(
                    ctx,
                    exec_cfg,
                    resolved_configuration.cfg().cfg(),
                )
                .await?,
            ),
            Vec::new(),
        )
    } else {
        resolve_execution_platform(
            ctx,
            &target_node,
            &resolved_configuration,
            &resolved_transitions,
        )
        .await?
    };

    let mut deps = SmallSet::new();
    let mut exec_deps = SmallSet::new();

    let platform_cfgs = compute_platform_cfgs(ctx, &target_node).await?;

    // We need to collect deps and to ensure that all attrs can be successfully
    // configured so that we don't need to support propagate configuration errors on attr access.
    for a in target_node.attrs(AttrInspectOptions::All) {
        let mut traversal = Traversal {
            deps: &mut deps,
            exec_deps: &mut exec_deps,
        };
        let attr_cfg_ctx = AttrConfigurationContextImpl::new(
            &resolved_configuration,
            execution_platform_resolution.cfg(),
            &resolved_transitions,
            &platform_cfgs,
        );

        let configured_attr = a.configure(&attr_cfg_ctx)?;
        configured_attr.traverse(target_node.label().pkg(), &mut traversal)?;
    }

    // Check transitive target compatibility.
    let dep_futures = deps
        .iter()
        .map(|v| ctx.get_configured_target_node(v.target()));

    let exec_dep_futures = exec_deps
        .iter()
        .map(|v| ctx.get_configured_target_node(v.target()));

    let fut = futures::future::join(
        futures::future::join_all(dep_futures),
        futures::future::join_all(exec_dep_futures),
    );
    let (dep_results, exec_dep_results): (Vec<_>, Vec<_>) =
        ConfiguredGraphCycleDescriptor::guard_this(ctx, fut).await??;

    let mut deps = Vec::with_capacity(deps.len());
    let mut exec_deps = Vec::with_capacity(exec_deps.len());

    let unpack_dep = |
        result: anyhow::Result<MaybeCompatible<ConfiguredTargetNode>>| -> ControlFlow<_, ConfiguredTargetNode>
    {
        match result {
            Err(e) => ControlFlow::Break(Err(e)),
            Ok(MaybeCompatible::Incompatible(reason)) => {
                ControlFlow::Break(Ok(MaybeCompatible::Incompatible(Arc::new(IncompatiblePlatformReason {
                    target: target_label.dupe(),
                    cause: IncompatiblePlatformReasonCause::Dependency(reason.dupe()),
                }))))
            }
            Ok(MaybeCompatible::Compatible(dep)) => {
                let visible = match dep.is_visible_to(target_label.unconfigured()) {
                    Ok(visible) => visible,
                    Err(e) => return ControlFlow::Break(Err(e)),
                };
                if !visible {
                    ControlFlow::Break(
                        Err(anyhow::anyhow!(VisibilityError::NotVisibleTo(
                            dep.label().unconfigured().dupe(),
                            target_label.unconfigured().dupe(),
                        ))),
                    )
                } else {
                    ControlFlow::Continue(dep)
                }
            }
        }
    };

    for dep in dep_results {
        match unpack_dep(dep) {
            ControlFlow::Continue(dep) => deps.push(dep),
            ControlFlow::Break(r) => return r,
        };
    }
    for dep in exec_dep_results {
        match unpack_dep(dep) {
            ControlFlow::Continue(dep) => exec_deps.push(dep),
            ControlFlow::Break(r) => return r,
        };
    }

    Ok(MaybeCompatible::Compatible(ConfiguredTargetNode::new(
        target_label.dupe(),
        target_node.dupe(),
        resolved_configuration,
        resolved_transitions,
        execution_platform_resolution,
        deps,
        exec_deps,
        platform_cfgs,
    )))
}

/// Compute configured target node after transition is applied to the target.
///
/// This function creates two node: transitioned node and a forward node.
/// Forward node is returned.
async fn compute_configured_target_node_with_transition(
    key: &ConfiguredTransitionedNodeKey,
    ctx: &DiceComputations,
) -> anyhow::Result<MaybeCompatible<ConfiguredTargetNode>> {
    assert_eq!(
        key.forward.unconfigured(),
        key.transitioned.unconfigured(),
        "Transition can be done only to the nodes with different configuration; \
               this valid case was ruled out earlier"
    );
    assert_ne!(
        key.forward, key.transitioned,
        "Transition can only happen to a node with the same unconfigured target"
    );

    let target_node = ctx.get_target_node(key.transitioned.unconfigured()).await?;
    let transitioned_node =
        compute_configured_target_node_no_transition(&key.transitioned, target_node.dupe(), ctx)
            .await?;
    transitioned_node.try_map(|transitioned_node| {
        ConfiguredTargetNode::new_forward(key.forward.dupe(), transitioned_node)
    })
}

async fn compute_configured_target_node(
    key: &ConfiguredTargetNodeKey,
    ctx: &DiceComputations,
) -> anyhow::Result<MaybeCompatible<ConfiguredTargetNode>> {
    let target_node = ctx
        .get_target_node(key.0.unconfigured())
        .await
        .with_context(|| {
            format!(
                "looking up unconfigured target node `{}`",
                key.0.unconfigured()
            )
        })?;

    match key.0.exec_cfg() {
        None if target_node.is_toolchain_rule() => {
            return Err(ToolchainDepError::ToolchainRuleUsedAsNormalDep(
                key.0.unconfigured().dupe(),
            )
            .into());
        }
        Some(_) if !target_node.is_toolchain_rule() => {
            return Err(ToolchainDepError::NonToolchainRuleUsedAsToolchainDep(
                key.0.unconfigured().dupe(),
            )
            .into());
        }
        _ => {}
    }

    if let Some(transition_id) = &target_node.0.rule.cfg {
        #[async_trait]
        impl Key for ConfiguredTransitionedNodeKey {
            type Value = SharedResult<MaybeCompatible<ConfiguredTargetNode>>;

            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> SharedResult<MaybeCompatible<ConfiguredTargetNode>> {
                compute_configured_target_node_with_transition(self, ctx)
                    .await
                    .shared_error()
            }

            fn equality(x: &Self::Value, y: &Self::Value) -> bool {
                if let (Ok(x), Ok(y)) = (x, y) {
                    x == y
                } else {
                    false
                }
            }
        }

        let cfg = TRANSITION_CALCULATION
            .get()?
            .apply_transition(ctx, &target_node, key.0.cfg(), transition_id)
            .await?;
        let configured_target_label = key.0.unconfigured().configure(cfg.single()?.dupe());

        if configured_target_label == key.0 {
            // Transitioned to identical configured target, no need to create a forward node.
            compute_configured_target_node_no_transition(&key.0, target_node.dupe(), ctx).await
        } else {
            Ok(ctx
                .compute(&ConfiguredTransitionedNodeKey {
                    forward: key.0.dupe(),
                    transitioned: configured_target_label,
                })
                .await??)
        }
    } else {
        // We are not caching `ConfiguredTransitionedNodeKey` because this is cheap,
        // and no need to fetch `target_node` again.
        compute_configured_target_node_no_transition(&key.0.dupe(), target_node, ctx).await
    }
}

#[derive(Clone, Dupe, Display, Debug, Eq, Hash, PartialEq, Allocative)]
pub struct ConfiguredTargetNodeKey(pub ConfiguredTargetLabel);

/// Similar to [`ConfiguredTargetNodeKey`], but used when the target
/// is transitioned to different configuration because rule definition requires it.
#[derive(Clone, Dupe, Display, Debug, Eq, Hash, PartialEq, Allocative)]
#[display(fmt = "ConfiguredTransitionedNodeKey({}, {})", forward, transitioned)]
pub struct ConfiguredTransitionedNodeKey {
    /// Forward node label.
    forward: ConfiguredTargetLabel,
    /// Transitional node label.
    transitioned: ConfiguredTargetLabel,
}

struct ConfiguredTargetNodeCalculationInstance;

pub(crate) fn init_configured_target_node_calculation() {
    CONFIGURED_TARGET_NODE_CALCULATION.init(&ConfiguredTargetNodeCalculationInstance);
}

#[async_trait]
impl ConfiguredTargetNodeCalculationImpl for ConfiguredTargetNodeCalculationInstance {
    async fn get_configured_target_node(
        &self,
        ctx: &DiceComputations,
        target: &ConfiguredTargetLabel,
    ) -> anyhow::Result<MaybeCompatible<ConfiguredTargetNode>> {
        #[async_trait]
        impl Key for ConfiguredTargetNodeKey {
            type Value = SharedResult<MaybeCompatible<ConfiguredTargetNode>>;
            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let res = compute_configured_target_node(self, ctx).await;
                Ok(res.with_context(|| format!("Error looking up configured node {}", self.0))?)
            }

            fn equality(x: &Self::Value, y: &Self::Value) -> bool {
                match (x, y) {
                    (Ok(x), Ok(y)) => x == y,
                    _ => false,
                }
            }
        }

        ctx.compute(&ConfiguredTargetNodeKey(target.dupe()))
            .await?
            .unshared_error()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::env;
use std::fs;

/// Returns the version of the rustc compiler.
fn rustc_version() -> String {
    let rustc = env::var("RUSTC").unwrap();
    let version = std::process::Command::new(rustc)
        .arg("--version")
        .output()
        .unwrap();

    assert!(version.status.success());

    let stdout = String::from_utf8(version.stdout).unwrap();
    stdout.trim().to_owned()
}

/// Get the version we expect to be using.
fn expected_version() -> String {
    let path = "../../rust-toolchain";
    for line in fs::read_to_string(path).unwrap().lines() {
        let prefix = "# @rustc_version: ";
        if let Some(version) = line.strip_prefix(prefix) {
            return version.to_owned();
        }
    }
    panic!("could not find rust-toolchain version in `{}`", path);
}

fn check_rustc_version() {
    let expected = expected_version();
    let actual = rustc_version();
    if actual != expected {
        panic!(
            "buck2 only works with version `{}` of rustc, but you are using `{}`. \
            Correct version is installed automatically when `rustup` is used",
            expected, actual
        );
    }
}

fn main() {
    if !cfg!(fbcode_build) {
        check_rustc_version();
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

/// Set of ASCII characters.
pub(crate) struct AsciiCharSet {
    mask: [bool; 0x100],
}

impl AsciiCharSet {
    /// Construct a set.
    pub(crate) const fn new(chars: &str) -> AsciiCharSet {
        let mut mask = [false; 0x100];
        let mut i = 0;
        while i != chars.as_bytes().len() {
            let b = chars.as_bytes()[i];
            assert!(b & 0x80 == 0, "non-ASCII char in set");

            mask[b as usize] = true;
            i += 1;
        }
        AsciiCharSet { mask }
    }

    pub(crate) fn contains(&self, b: u8) -> bool {
        self.mask[b as usize]
    }
}

#[cfg(test)]
mod tests {
    use crate::ascii_char_set::AsciiCharSet;

    #[test]
    fn test_ascii_char_set() {
        let set = AsciiCharSet::new("abcdXYZ");

        assert!(set.contains(b'a'));
        assert!(set.contains(b'd'));
        assert!(set.contains(b'X'));
        assert!(set.contains(b'Z'));
        assert!(!set.contains(b'\n'));
        assert!(!set.contains(b'@'));
        assert!(!set.contains(0x80));
        assert!(!set.contains(0xff));
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use allocative::Allocative;
use futures::Future;
use once_cell::sync::OnceCell;
use tokio::sync::Mutex;

#[derive(Allocative)]
pub struct AsyncOnceCell<T> {
    cell: OnceCell<T>,
    #[allocative(skip)] // Mutex has allocations, but no simple way to measure it.
    initialized: Mutex<()>,
}

impl<T> AsyncOnceCell<T> {
    pub fn new() -> Self {
        Self {
            cell: OnceCell::new(),
            initialized: Mutex::new(()),
        }
    }

    pub fn get(&self) -> Option<&T> {
        self.cell.get()
    }

    pub async fn get_or_init<F: Future<Output = T>>(&self, fut: F) -> &T {
        if let Some(val) = self.cell.get() {
            return val;
        }

        let _guard = self.initialized.lock().await;

        if let Some(val) = self.cell.get() {
            return val;
        }

        let val = fut.await;

        match self.cell.try_insert(val) {
            Ok(val) => val,
            Err(_) => unreachable!(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test() -> anyhow::Result<()> {
        let cell1 = AsyncOnceCell::new();

        let counter = cell1.get_or_init(async { 42u32 }).await;
        assert_eq!(42, *counter);

        let counter = cell1.get_or_init(async { 11111111u32 }).await;
        assert_eq!(42, *counter);

        // There's no guarantee that these two actually race with each other, we can only hope.
        let cell2 = AsyncOnceCell::new();
        let ref1 = cell2.get_or_init(async { tokio::spawn(async { 1u32 }).await.unwrap() });
        let ref2 = cell2.get_or_init(async { tokio::spawn(async { 2u32 }).await.unwrap() });

        let (v1, v2) = tokio::join!(ref1, ref2);

        assert!(*v1 == 1 || *v1 == 2);
        assert_eq!(v1, v2);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::any::Any;
use std::borrow::Cow;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;

use allocative::Allocative;
use buck2_data::action_key_owner::BaseDeferredKeyProto;
use buck2_data::ToProtoMessage;
use cmp_any::PartialEqAny;
use dupe::Dupe;

use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use crate::fs::project_rel_path::ProjectRelativePath;
use crate::fs::project_rel_path::ProjectRelativePathBuf;
use crate::target::label::ConfiguredTargetLabel;
use crate::target::name::EQ_SIGN_SUBST;

pub trait BaseDeferredKeyDyn: Debug + Display + Any + Allocative + Send + Sync + 'static {
    fn eq_token(&self) -> PartialEqAny;
    fn hash(&self) -> u64;
    fn make_hashed_path(
        &self,
        base: &ProjectRelativePath,
        prefix: &ForwardRelativePath,
        action_key: Option<&str>,
        path: &ForwardRelativePath,
    ) -> ProjectRelativePathBuf;
    /// Fake label for anon targets, `None` for BXL.
    fn configured_label(&self) -> Option<ConfiguredTargetLabel>;
    fn to_proto(&self) -> BaseDeferredKeyProto;
    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
}

#[derive(Debug, derive_more::Display, Dupe, Clone, Allocative)]
pub enum BaseDeferredKey {
    TargetLabel(ConfiguredTargetLabel),
    AnonTarget(Arc<dyn BaseDeferredKeyDyn>),
    BxlLabel(Arc<dyn BaseDeferredKeyDyn>),
}

impl PartialEq for BaseDeferredKey {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (BaseDeferredKey::TargetLabel(a), BaseDeferredKey::TargetLabel(b)) => a == b,
            (BaseDeferredKey::TargetLabel(_), _) => false,
            (BaseDeferredKey::AnonTarget(a), BaseDeferredKey::AnonTarget(b)) => {
                a.eq_token() == b.eq_token()
            }
            (BaseDeferredKey::AnonTarget(_), _) => false,
            (BaseDeferredKey::BxlLabel(a), BaseDeferredKey::BxlLabel(b)) => {
                a.eq_token() == b.eq_token()
            }
            (BaseDeferredKey::BxlLabel(_), _) => false,
        }
    }
}

impl Eq for BaseDeferredKey {}

impl Hash for BaseDeferredKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            BaseDeferredKey::TargetLabel(a) => a.hash(state),
            BaseDeferredKey::AnonTarget(d) | BaseDeferredKey::BxlLabel(d) => d.hash().hash(state),
        }
    }
}

impl BaseDeferredKey {
    pub fn unpack_target_label(&self) -> Option<&ConfiguredTargetLabel> {
        match self {
            BaseDeferredKey::TargetLabel(a) => Some(a),
            _ => None,
        }
    }

    pub fn make_hashed_path(
        &self,
        base: &ProjectRelativePath,
        prefix: &ForwardRelativePath,
        action_key: Option<&str>,
        path: &ForwardRelativePath,
    ) -> ProjectRelativePathBuf {
        match self {
            BaseDeferredKey::TargetLabel(target) => {
                let cell_relative_path = target.pkg().cell_relative_path().as_str();
                let escaped_target_name = Self::escape_target_name(target.name().as_str());

                // It is performance critical that we use slices and allocate via `join` instead of
                // repeated calls to `join` on the path object because `join` allocates on each call,
                // which has a significant impact.
                let parts = [
                    base.as_str(),
                    "/",
                    prefix.as_str(),
                    "/",
                    target.pkg().cell_name().as_str(),
                    "/",
                    target.cfg().output_hash().as_str(),
                    if target.exec_cfg().is_some() { "-" } else { "" },
                    target
                        .exec_cfg()
                        .as_ref()
                        .map_or("", |x| x.output_hash().as_str()),
                    "/",
                    cell_relative_path,
                    if cell_relative_path.is_empty() {
                        ""
                    } else {
                        "/"
                    },
                    "__",
                    escaped_target_name.as_ref(),
                    "__",
                    "/",
                    if action_key.is_none() {
                        ""
                    } else {
                        "__action__"
                    },
                    action_key.unwrap_or_default(),
                    if action_key.is_none() { "" } else { "__/" },
                    path.as_str(),
                ];

                ProjectRelativePathBuf::unchecked_new(parts.concat())
            }
            BaseDeferredKey::AnonTarget(d) | BaseDeferredKey::BxlLabel(d) => {
                d.make_hashed_path(base, prefix, action_key, path)
            }
        }
    }

    pub fn make_unhashed_path(&self) -> Option<ForwardRelativePathBuf> {
        match self {
            BaseDeferredKey::TargetLabel(target) => Some(
                ForwardRelativePath::new(target.pkg().cell_name().as_str())
                    .unwrap()
                    .join(target.pkg().cell_relative_path()),
            ),
            _ => None,
        }
    }

    fn escape_target_name(target_name: &str) -> Cow<str> {
        // Equals sign is difficult to escape especially for cmd.exe on Windows
        // which doesn't follow common escaping rules.
        if target_name.contains('=') {
            Cow::Owned(target_name.replace('=', EQ_SIGN_SUBST))
        } else {
            Cow::Borrowed(target_name)
        }
    }

    pub fn to_proto(&self) -> BaseDeferredKeyProto {
        match self {
            BaseDeferredKey::TargetLabel(t) => BaseDeferredKeyProto::TargetLabel(t.as_proto()),
            BaseDeferredKey::AnonTarget(d) | BaseDeferredKey::BxlLabel(d) => d.to_proto(),
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use allocative::Allocative;
use dupe::Dupe;

use crate::cells::build_file_cell::BuildFileCell;
use crate::cells::cell_path::CellPath;
use crate::cells::name::CellName;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::file_name::FileNameBuf;
use crate::package::PackageLabel;

/// Path of a build file (e.g. `BUCK`) only. (`bzl` files are not included).
#[derive(Clone, Hash, Eq, PartialEq, Debug, derive_more::Display, Allocative)]
#[display(fmt = "{}:{}", package, filename)]
pub struct BuildFilePath {
    /// The package of this build file
    package: PackageLabel,
    /// The build file's filename (which can be configured). i.e. `BUCK`
    filename: FileNameBuf,
}

impl BuildFilePath {
    pub fn new(package: PackageLabel, filename: FileNameBuf) -> Self {
        Self { package, filename }
    }

    pub fn testing_new(path: &str) -> Self {
        let (package, filename) = path.split_once(':').unwrap();
        Self::new(
            PackageLabel::testing_parse(package),
            FileNameBuf::try_from(filename.to_owned()).unwrap(),
        )
    }

    pub fn cell(&self) -> CellName {
        self.package.cell_name()
    }

    pub fn package(&self) -> PackageLabel {
        self.package.dupe()
    }

    pub fn path(&self) -> CellPath {
        self.package.as_cell_path().join(&self.filename)
    }

    pub fn build_file_cell(&self) -> BuildFileCell {
        BuildFileCell::new(self.cell())
    }

    pub fn filename(&self) -> &FileName {
        &self.filename
    }
}

#[cfg(test)]
mod tests {
    use crate::build_file_path::BuildFilePath;

    #[test]
    fn test_testing_new() {
        // `testing_new` accepts the same format `Display` produces.
        assert_eq!(
            "foo//bar/baz:BUCK",
            BuildFilePath::testing_new("foo//bar/baz:BUCK").to_string()
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt::Display;
use std::fmt::Formatter;

use allocative::Allocative;

use crate::cells::build_file_cell::BuildFileCell;
use crate::cells::cell_path::CellPath;
use crate::cells::cell_path::CellPathRef;
use crate::cells::name::CellName;
use crate::cells::paths::CellRelativePath;
use crate::fs::paths::file_name::FileName;

#[derive(Debug, thiserror::Error)]
enum ImportPathError {
    #[error("Invalid import path `{0}`")]
    Invalid(CellPath),
    #[error("Import path must have suffix `.bzl`: `{0}`")]
    Suffix(CellPath),
}

/// Path of a `.bzl` file.
#[derive(Clone, Hash, Eq, PartialEq, Debug, Allocative)]
pub struct ImportPath {
    /// The path to the import as a 'CellPath', which contains the cell
    /// information and the cell relative path to the bzl file itself, including the bzl suffix
    path: CellPath,
    /// The cell of the top-level build module that this is being loaded
    /// (perhaps transitively) into.
    build_file_cell: BuildFileCell,
}

impl ImportPath {
    /// We evaluate `bzl` files multiple times: for each cell we evaluate `bzl` file again.
    /// We want to stop doing that.
    /// This function is for call sites where we don't care about the build file cell.
    pub fn new_same_cell(path: CellPath) -> anyhow::Result<Self> {
        let build_file_cell = BuildFileCell::new(path.cell());
        Self::new_with_build_file_cells(path, build_file_cell)
    }

    pub fn new_with_build_file_cells(
        path: CellPath,
        build_file_cell: BuildFileCell,
    ) -> anyhow::Result<Self> {
        if path.parent().is_none() {
            return Err(ImportPathError::Invalid(path).into());
        }

        if path.path().as_str().contains('?') {
            return Err(ImportPathError::Invalid(path).into());
        }

        if path.path().extension() != Some("bzl") {
            return Err(ImportPathError::Suffix(path).into());
        }

        Ok(Self {
            path,
            build_file_cell,
        })
    }

    /// LSP creates imports for non-bzl files.
    pub fn new_hack_for_lsp(
        path: CellPath,
        build_file_cell: BuildFileCell,
    ) -> anyhow::Result<Self> {
        if path.parent().is_none() {
            return Err(ImportPathError::Invalid(path).into());
        }

        if path.path().as_str().contains('?') {
            return Err(ImportPathError::Invalid(path).into());
        }

        Ok(Self {
            path,
            build_file_cell,
        })
    }

    pub fn testing_new(path: &str) -> Self {
        let (cell, rem) = path.split_once("//").unwrap();
        let (cell_relative_path, filename) = rem.rsplit_once(':').unwrap();
        Self::testing_new_cross_cell(cell, cell_relative_path, filename, cell)
    }

    pub fn testing_new_cross_cell(
        cell: &str,
        cell_relative_path: &str,
        filename: &str,
        build_file_cell: &str,
    ) -> Self {
        let cell_path = CellPath::new(
            CellName::testing_new(cell),
            CellRelativePath::unchecked_new(cell_relative_path)
                .join(FileName::unchecked_new(filename)),
        );
        Self::new_with_build_file_cells(
            cell_path,
            BuildFileCell::new(CellName::testing_new(build_file_cell)),
        )
        .unwrap()
    }

    pub fn cell(&self) -> CellName {
        self.path.cell()
    }

    pub fn build_file_cell(&self) -> BuildFileCell {
        self.build_file_cell
    }

    pub fn path(&self) -> &CellPath {
        &self.path
    }

    /// Parent directory of the import path.
    pub fn path_parent(&self) -> CellPathRef {
        self.path
            .parent()
            .expect("constructor verified path has parent")
    }
}

impl Display for ImportPath {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if self.build_file_cell.name() == self.path.cell() {
            write!(f, "{}", self.path)
        } else {
            write!(f, "{}@{}", self.path, self.build_file_cell.name())
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! Categories and identifiers for actions run by buck2.
//!
//! A category is a snake case identifier that identifies a family of actions that are related in some way but differ
//! in their inputs. The canonical example of this is the category `cxx_compile`; conceptually, this represents the
//! category of all actions that invoke a C++ compiler, of which there are potentially many in a single C++ rule
//! implementation.

use std::fmt;

use allocative::Allocative;
use once_cell::sync::Lazy;
use regex::Regex;
use thiserror::Error;

/// A category, representing a family of actions.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Allocative)]
pub struct Category(String);

impl Category {
    /// Returns a string representation of this category.
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

impl TryFrom<String> for Category {
    type Error = CategoryParseError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        static CATEGORY_REGEX: Lazy<Regex> =
            Lazy::new(|| Regex::new("^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*$").unwrap());

        if !CATEGORY_REGEX.is_match(&value) {
            Err(CategoryParseError::NotSnakeCase(value))
        } else {
            Ok(Category(value))
        }
    }
}

impl TryFrom<&str> for Category {
    type Error = CategoryParseError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Category::try_from(value.to_owned())
    }
}

impl fmt::Display for Category {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

#[derive(Debug, Error)]
pub enum CategoryParseError {
    #[error(
        "Invalid category `{0}`. Must be a snake_cased identifier consisting of lowercase alphanumeric characters, e.g. `cxx_compile`. Each section of the snake_cased identifier must begin with a lowercase letter (not a number)."
    )]
    NotSnakeCase(String),
}

#[cfg(test)]
mod tests {
    use super::Category;

    #[test]
    fn valid_categories() {
        Category::try_from("valid_category").unwrap();
        Category::try_from("valid_category_with_numbers10").unwrap();
        Category::try_from("singleword").unwrap();
    }

    #[test]
    fn invalid_categories() {
        Category::try_from("_leading_underscore").unwrap_err();
        Category::try_from("NotSnakeCase").unwrap_err();
        Category::try_from("Not_Snake_Case").unwrap_err();
        Category::try_from("contains_4_number").unwrap_err();
        Category::try_from("trailing_underscore_").unwrap_err();
    }
}