/*
 * 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::future::Future;
use std::pin::Pin;

use async_trait::async_trait;
use buck2_core::cells::name::CellName;
use buck2_core::configuration::compatibility::MaybeCompatible;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::target::label::TargetLabel;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_query::query::syntax::simple::eval::file_set::FileSet;
use buck2_query::query::syntax::simple::eval::set::TargetSet;
use buck2_query::query::syntax::simple::functions::helpers::CapturedExpr;
use buck2_util::late_binding::LateBinding;
use dice::DiceComputations;

#[async_trait]
pub trait BxlCqueryFunctions<'c>: Send + 'c {
    async fn allpaths(
        &self,
        from: &TargetSet<ConfiguredTargetNode>,
        to: &TargetSet<ConfiguredTargetNode>,
    ) -> anyhow::Result<TargetSet<ConfiguredTargetNode>>;
    async fn somepath(
        &self,
        from: &TargetSet<ConfiguredTargetNode>,
        to: &TargetSet<ConfiguredTargetNode>,
    ) -> anyhow::Result<TargetSet<ConfiguredTargetNode>>;
    async fn owner(&self, file_set: &FileSet) -> anyhow::Result<TargetSet<ConfiguredTargetNode>>;
    async fn deps(
        &self,
        targets: &TargetSet<ConfiguredTargetNode>,
        deps: Option<i32>,
        captured_expr: Option<&CapturedExpr>,
    ) -> anyhow::Result<TargetSet<ConfiguredTargetNode>>;
    async fn rdeps(
        &self,
        universe: &TargetSet<ConfiguredTargetNode>,
        targets: &TargetSet<ConfiguredTargetNode>,
        depth: Option<i32>,
    ) -> anyhow::Result<TargetSet<ConfiguredTargetNode>>;
    async fn testsof(
        &self,
        targets: &TargetSet<ConfiguredTargetNode>,
    ) -> anyhow::Result<TargetSet<ConfiguredTargetNode>>;
    async fn testsof_with_default_target_platform(
        &self,
        targets: &TargetSet<ConfiguredTargetNode>,
    ) -> anyhow::Result<Vec<MaybeCompatible<ConfiguredTargetNode>>>;
}

#[async_trait]
pub trait BxlUqueryFunctions<'c>: Send + 'c {
    async fn allpaths(
        &self,
        from: &TargetSet<TargetNode>,
        to: &TargetSet<TargetNode>,
    ) -> anyhow::Result<TargetSet<TargetNode>>;
    async fn somepath(
        &self,
        from: &TargetSet<TargetNode>,
        to: &TargetSet<TargetNode>,
    ) -> anyhow::Result<TargetSet<TargetNode>>;
    async fn deps(
        &self,
        targets: &TargetSet<TargetNode>,
        deps: Option<i32>,
        captured_expr: Option<&CapturedExpr>,
    ) -> anyhow::Result<TargetSet<TargetNode>>;
    async fn rdeps(
        &self,
        universe: &TargetSet<TargetNode>,
        targets: &TargetSet<TargetNode>,
        depth: Option<i32>,
    ) -> anyhow::Result<TargetSet<TargetNode>>;
    async fn testsof(
        &self,
        targets: &TargetSet<TargetNode>,
    ) -> anyhow::Result<TargetSet<TargetNode>>;
    async fn owner(&self, file_set: &FileSet) -> anyhow::Result<TargetSet<TargetNode>>;
}

pub static NEW_BXL_CQUERY_FUNCTIONS: LateBinding<
    for<'c> fn(
        &'c DiceComputations,
        // Target platform
        Option<TargetLabel>,
        ProjectRoot,
        CellName,
    ) -> Pin<
        Box<dyn Future<Output = anyhow::Result<Box<dyn BxlCqueryFunctions<'c> + 'c>>> + 'c>,
    >,
> = LateBinding::new("NEW_BXL_CQUERY_FUNCTIONS");

pub static NEW_BXL_UQUERY_FUNCTIONS: LateBinding<
    for<'c> fn(
        &'c DiceComputations,
        ProjectRoot,
        CellName,
    ) -> Pin<
        Box<dyn Future<Output = anyhow::Result<Box<dyn BxlUqueryFunctions<'c> + 'c>>> + 'c>,
    >,
> = LateBinding::new("NEW_BXL_UQUERY_FUNCTIONS");


/*
 * 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 analysis;
pub mod bxl;
pub mod oneshot;


/*
 * 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 async_trait::async_trait;
use buck2_core::fs::project_rel_path::ProjectRelativePath;
use buck2_core::target::label::TargetLabel;
use buck2_node::configured_universe::CqueryUniverse;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_query::query::syntax::simple::eval::values::QueryEvaluationResult;
use buck2_util::late_binding::LateBinding;
use dice::DiceComputations;
use dupe::Dupe;

use crate::actions::query::ActionQueryNode;

/// [Context](https://fburl.com/adiagq2f).
#[derive(Copy, Clone, Dupe)]
pub enum CqueryOwnerBehavior {
    Deprecated,
    Correct,
}

#[async_trait]
pub trait QueryFrontend: Send + Sync + 'static {
    async fn eval_uquery(
        &self,
        ctx: &DiceComputations,
        working_dir: &ProjectRelativePath,
        query: &str,
        query_args: &[String],
        global_target_platform: Option<TargetLabel>,
    ) -> anyhow::Result<QueryEvaluationResult<TargetNode>>;

    async fn eval_cquery(
        &self,
        ctx: &DiceComputations,
        working_dir: &ProjectRelativePath,
        owner_behavior: CqueryOwnerBehavior,
        query: &str,
        query_args: &[String],
        global_target_platform: Option<TargetLabel>,
        target_universe: Option<&[String]>,
    ) -> anyhow::Result<QueryEvaluationResult<ConfiguredTargetNode>>;

    async fn eval_aquery(
        &self,
        ctx: &DiceComputations,
        working_dir: &ProjectRelativePath,
        query: &str,
        query_args: &[String],
        global_target_platform: Option<TargetLabel>,
    ) -> anyhow::Result<QueryEvaluationResult<ActionQueryNode>>;

    async fn universe_from_literals(
        &self,
        ctx: &DiceComputations,
        cwd: &ProjectRelativePath,
        literals: &[String],
        global_target_platform: Option<TargetLabel>,
    ) -> anyhow::Result<CqueryUniverse>;
}

pub static QUERY_FRONTEND: LateBinding<&'static dyn QueryFrontend> =
    LateBinding::new("QUERY_FRONTEND");


/*
 * 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.
 */

#![cfg_attr(feature = "gazebo_lint", feature(plugin))]
#![cfg_attr(feature = "gazebo_lint", allow(deprecated))] // :(
#![cfg_attr(feature = "gazebo_lint", plugin(gazebo_lint))]

#[allow(unused_extern_crates)] // proc_macro is very special
extern crate proc_macro;

mod provider;

/// Generates the starlark implementation for an internal provider.
///
/// Implements StarlarkValue, ComplexValue, ProviderLike for the provider type and
/// adds a ProviderCallable that's used in starlark as the constructor.
///
/// Return types can be specified directly by adding `#[provider(field_type=SomeIdentifier)]`
/// to a field. The type `SomeIdentifier` must be one that implements
/// `starlark::values::type_repr::StarlarkTypeRepr`
///
/// In starlark, a provider instance will have starlark-accessible attributes for
/// each of its fields.
///
/// In rust, a utility is added for getting the provider from a provider collection like
/// Foo::from_providers(collection).
///
/// You can customize the freeze implementation (for example if you want to do validation
/// at that point) by passing it to the attribute like:
/// `#[internal_provider(creator, freeze=my_freeze)]`
#[proc_macro_attribute]
pub fn internal_provider(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input);
    let attr = syn::parse_macro_input!(attr);
    match provider::define_provider(attr, input) {
        Ok(tokens) => tokens,
        Err(e) => e.to_compile_error().into(),
    }
}


/*
 * 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::collections::HashMap;

use convert_case::Case;
use convert_case::Casing;
use quote::format_ident;
use quote::quote;
use quote::ToTokens;
use syn::parse::ParseStream;
use syn::Attribute;
use syn::Fields;
use syn::LitStr;

const PROVIDER_IDENT: &str = "provider";

pub(crate) struct InternalProviderArgs {
    creator_func: syn::Ident,
}

impl syn::parse::Parse for InternalProviderArgs {
    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
        let creator_func = syn::Ident::parse(input)?;
        Ok(InternalProviderArgs { creator_func })
    }
}

/// Documentation information for a single field.
struct FieldDoc {
    /// The name of the field
    name: syn::Ident,
    /// The docstring for the field, if present
    docstring: proc_macro2::TokenStream,
    /// The implementation to generate the field type documentation.
    field_type: proc_macro2::TokenStream,
}

struct ProviderCodegen {
    input: syn::ItemStruct,
    args: InternalProviderArgs,
    field_attr_providers: HashMap<syn::Ident, Attribute>,
}

impl ProviderCodegen {
    /// Create an instance from an input and args.
    ///
    /// This modifies the original input and removes any instances of `#[provider()]` macros
    /// on fields of the provided structs, and saves them into `field_attr_providers`.
    fn new(mut input: syn::ItemStruct, args: InternalProviderArgs) -> syn::Result<Self> {
        let mut provider_attrs = HashMap::new();
        if let Fields::Named(fields_named) = &mut input.fields {
            for field in fields_named.named.iter_mut() {
                let (attrs, mut provider_attr): (Vec<syn::Attribute>, Vec<syn::Attribute>) = field
                    .attrs
                    .clone()
                    .into_iter()
                    .partition(|a| !a.path().is_ident(PROVIDER_IDENT));
                field.attrs = attrs;
                if provider_attr.len() > 1 {
                    return Err(syn::Error::new_spanned(
                        field.to_token_stream(),
                        format!("{} attribute can only be specified once", PROVIDER_IDENT),
                    ));
                } else if !provider_attr.is_empty() {
                    provider_attrs.insert(
                        field.ident.as_ref().unwrap().to_owned(),
                        provider_attr.remove(0),
                    );
                }
            }
        };
        Ok(Self {
            input,
            args,
            field_attr_providers: provider_attrs,
        })
    }

    fn name(&self) -> syn::Result<syn::Ident> {
        match self.input.ident.to_string().strip_suffix("Gen") {
            Some(v) => Ok(format_ident!("{}", v)),
            None => Err(syn::Error::new_spanned(
                &self.input.ident,
                "should end with Gen",
            )),
        }
    }

    fn name_str(&self) -> syn::Result<String> {
        Ok(self.name()?.to_string())
    }

    fn name_snake_str(&self) -> syn::Result<String> {
        Ok(self.name_str()?.to_case(Case::Snake))
    }

    fn frozen_name(&self) -> syn::Result<syn::Ident> {
        let name = self.name()?;
        Ok(format_ident!("Frozen{}", name))
    }

    fn callable_name(&self) -> syn::Result<syn::Ident> {
        let name = self.name()?;
        Ok(format_ident!("{}Callable", name))
    }

    fn register_func_name(&self) -> syn::Result<syn::Ident> {
        let name_snake_str = self.name_snake_str()?;
        Ok(format_ident!("register_{}", name_snake_str))
    }

    /// `id` field is object identity, which is ignored for equality or display purposes.
    fn is_id_field(&self, field: &syn::Field) -> bool {
        field.ident.as_ref().unwrap() == "id" && field.ty.to_token_stream().to_string() == "u64"
    }

    fn field_names(&self) -> syn::Result<Vec<&syn::Ident>> {
        match &self.input.fields {
            syn::Fields::Named(fields) => Ok(fields
                .named
                .iter()
                .filter(|f| !self.is_id_field(f))
                .map(|v| v.ident.as_ref().expect("no field name in named fields?"))
                .collect()),
            _ => Err(syn::Error::new_spanned(
                &self.input,
                "providers only support named fields",
            )),
        }
    }

    /// Parse the "doc" attribute and return a tokenstream that is either None if "doc" is not
    /// present, or the result of DocString::parse_docstring if present.
    fn get_docstring_impl(&self, attrs: &Vec<syn::Attribute>) -> proc_macro2::TokenStream {
        let mut doc_lines = vec![];

        for attr in attrs {
            if attr.path().is_ident("doc") {
                if let syn::Meta::NameValue(syn::MetaNameValue {
                    value:
                        syn::Expr::Lit(syn::ExprLit {
                            lit: syn::Lit::Str(s),
                            ..
                        }),
                    ..
                }) = &attr.meta
                {
                    doc_lines.push(s.value());
                }
            }
        }

        if doc_lines.is_empty() {
            quote! { None }
        } else {
            let docstring = Some(doc_lines.join("\n"));
            quote! {
                starlark::docs::DocString::from_docstring(
                    starlark::docs::DocStringKind::Rust,
                    #docstring,
                )
            }
        }
    }

    fn field_doc(&self, field: &syn::Field) -> syn::Result<FieldDoc> {
        if self.is_id_field(field) {
            return Err(syn::Error::new_spanned(
                field,
                "id field should not be documented",
            ));
        }

        syn::custom_keyword!(field_type);

        let name = field.ident.as_ref().unwrap().to_owned();

        let field_type = if let Some(attr) = self.field_attr_providers.get(&name) {
            attr.parse_args_with(
                |input: ParseStream| -> syn::Result<proc_macro2::TokenStream> {
                    if input.parse::<field_type>().is_ok() {
                        input.parse::<syn::Token![=]>()?;
                        let rust_type = input.parse::<LitStr>()?.value();
                        let rust_type: proc_macro2::TokenStream = rust_type.parse()?;
                        Ok(quote! {
                            Some(starlark::docs::DocType {
                                raw_type: <#rust_type>::starlark_type_repr(),
                            })
                        })
                    } else {
                        Ok(quote! { None })
                    }
                },
            )?
        } else {
            quote! { None }
        };

        let docstring = self.get_docstring_impl(&field.attrs);

        Ok(FieldDoc {
            name,
            docstring,
            field_type,
        })
    }

    /// Grab the information for all fields on the struct, and create the
    /// documentation() function for StarlarkValue.
    fn documentation_function(&self) -> syn::Result<proc_macro2::TokenStream> {
        let provider_docstring = self.get_docstring_impl(&self.input.attrs);
        let create_func = &self.args.creator_func;

        let field_docs = match &self.input.fields {
            syn::Fields::Named(fields) => Ok(fields
                .named
                .iter()
                .filter(|f| !self.is_id_field(f))
                .map(|f| self.field_doc(f))
                .collect::<syn::Result<Vec<_>>>()?),
            _ => Err(syn::Error::new_spanned(
                &self.input,
                "providers only support named fields",
            )),
        }?;

        let mut field_names = vec![];
        let mut field_docstrings = vec![];
        let mut field_types = vec![];

        for doc in field_docs {
            let name = doc.name;
            let name = quote! { stringify!(#name) };

            field_names.push(name);
            field_docstrings.push(doc.docstring);
            field_types.push(doc.field_type);
        }

        Ok(quote! {
            fn documentation(&self) -> Option<starlark::docs::DocItem> {
                let docstring = #provider_docstring;
                let field_names = [
                    #(#field_names),*
                ];
                let field_docs = [
                    #(#field_docstrings),*
                ];
                let field_types = [
                    #(#field_types),*
                ];
                use starlark::values::type_repr::StarlarkTypeRepr;
                use buck2_interpreter::types::provider::callable::ProviderCallableLike;
                self.provider_callable_documentation(Some(#create_func), &docstring, &field_names, &field_docs, &field_types)
            }
        })
    }

    fn impl_display(&self) -> syn::Result<proc_macro2::TokenStream> {
        let gen_name = &self.input.ident;
        let name_str = self.name_str()?;
        let field_names = self.field_names()?;
        Ok(quote! {
            impl<V: std::fmt::Display> std::fmt::Display for #gen_name<V> {
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    display_container::fmt_keyed_container(
                        f,
                        &format!("{}(", #name_str),
                        ")",
                        "=",
                        [
                            #((stringify!(#field_names), &self.#field_names)),*
                        ].into_iter()
                    )
                }
            }
        })
    }

    fn impl_starlark_value(&self) -> syn::Result<proc_macro2::TokenStream> {
        let vis = &self.input.vis;
        let gen_name = &self.input.ident;
        let name = self.name()?;
        let name_str = self.name_str()?;
        let register_func_name = self.register_func_name()?;
        let field_names = self.field_names()?;
        Ok(quote! {
            starlark::starlark_complex_value!(#vis #name);

            #[starlark::values::starlark_value(type = #name_str)]
            impl<'v, V: starlark::values::ValueLike<'v> + 'v> starlark::values::StarlarkValue<'v>
                for #gen_name<V>
            where
                Self: starlark::any::ProvidesStaticType<'v>,
            {
                fn matches_type(&self, ty: &str) -> bool {
                    ty == #name_str || ty == "provider"
                }

                fn get_methods() -> Option<&'static starlark::environment::Methods> {
                    static RES: starlark::environment::MethodsStatic =
                        starlark::environment::MethodsStatic::new();

                    RES.methods(|x| {
                        crate::interpreter::rule_defs::provider::provider_methods(x);
                        _register::#register_func_name(x);
                    })
                }

                fn provide(&'v self, demand: &mut starlark::values::Demand<'_, 'v>) {
                    demand.provide_value::<
                        &dyn crate::interpreter::rule_defs::provider::ProviderLike>(self);
                }

                fn equals(&self, other: starlark::values::Value<'v>) -> anyhow::Result<bool> {
                    let this: &#name = starlark::coerce::coerce(self);
                    let other: &#name = match #name::from_value(other) {
                        Some(other) => other,
                        None => return Ok(false),
                    };

                    #(
                        if !this.#field_names.equals(other.#field_names)? {
                            return Ok(false);
                        }
                    )*
                    Ok(true)
                }

                // TODO(cjhopman): UserProvider implements more of the starlark functions. We should probably match them.
            }
        })
    }

    fn impl_serializable_value(&self) -> syn::Result<proc_macro2::TokenStream> {
        let gen_name = &self.input.ident;
        let field_names = self.field_names()?;
        let field_len = field_names.len();
        Ok(quote! {
            impl<'v, V: starlark::values::ValueLike<'v>> serde::Serialize
                for #gen_name<V>
            {
                fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> where S : serde::Serializer {
                    use serde::ser::SerializeMap;

                    let mut s = s.serialize_map(Some(#field_len))?;
                    #(
                        s.serialize_entry(
                            stringify!(#field_names),
                            &self.#field_names
                        )?;
                    )*
                    s.end()
                }
            }
        })
    }

    fn from_providers(&self) -> syn::Result<proc_macro2::TokenStream> {
        let gen_name = &self.input.ident;
        let frozen_name = self.frozen_name()?;
        let callable_name = self.callable_name()?;
        Ok(quote! {
            impl<'v, V: starlark::values::ValueLike<'v>> #gen_name<V> {
                pub fn from_providers(
                    providers: &crate::interpreter::rule_defs::provider::collection::FrozenProviderCollection,
                ) -> Option<starlark::values::FrozenRef<#frozen_name>> {
                    providers.get_provider(#callable_name::provider_id_t())
                }
            }
        })
    }

    fn impl_provider_like(&self) -> syn::Result<proc_macro2::TokenStream> {
        let gen_name = &self.input.ident;
        let field_names = self.field_names()?;
        let callable_name = self.callable_name()?;
        Ok(quote! {
            impl<'v, V: starlark::values::ValueLike<'v> + 'v> crate::interpreter::rule_defs::provider::ProviderLike<'v> for #gen_name<V>
            where
                Self: std::fmt::Debug,
            {
                fn id(&self) -> &std::sync::Arc<buck2_core::provider::id::ProviderId> {
                    #callable_name::provider_id()
                }

                fn get_field(&self, name: &str) -> Option<starlark::values::Value<'v>> {
                    match name {
                        #(stringify!(#field_names) => Some(self.#field_names.to_value()),)*
                        _ => None,
                    }
                }

                fn items(&self) -> Vec<(&str, starlark::values::Value<'v>)> {
                    vec![
                        #((stringify!(#field_names), self.#field_names.to_value())),*
                    ]
                }
            }
        })
    }

    fn callable_struct(&self) -> syn::Result<proc_macro2::TokenStream> {
        let vis = &self.input.vis;
        let callable_name = self.callable_name()?;
        Ok(quote! {
            #[derive(Debug, Clone, dupe::Dupe, starlark::any::ProvidesStaticType, starlark::values::NoSerialize, allocative::Allocative)]
            #vis struct #callable_name {
                id: &'static std::sync::Arc<buck2_core::provider::id::ProviderId>,
            }
        })
    }

    fn callable_impl_starlark_value(&self) -> syn::Result<proc_macro2::TokenStream> {
        let name_str = self.name_str()?;
        let callable_name = self.callable_name()?;
        let documentation_function = self.documentation_function()?;
        let create_func = &self.args.creator_func;
        let callable_name_snake_str = callable_name.to_string().to_case(Case::Snake);

        Ok(quote! {
            starlark::starlark_simple_value!(#callable_name);

            #[starlark::values::starlark_value(type = #callable_name_snake_str)]
            impl<'v> starlark::values::StarlarkValue<'v> for #callable_name
            {
                fn get_methods() -> Option<&'static starlark::environment::Methods> {
                    static RES: starlark::environment::MethodsStatic =
                        starlark::environment::MethodsStatic::new();
                    // TODO(nmj): This should use the docstring from the attribute, rather than
                    //            None
                    RES.methods(|x| x.set_attribute("type", #name_str, None))
                }

                fn invoke(
                    &self,
                    _me: starlark::values::Value<'v>,
                    args: &starlark::eval::Arguments<'v, '_>,
                    eval: &mut starlark::eval::Evaluator<'v, '_>,
                ) -> anyhow::Result<starlark::values::Value<'v>> {
                    static RES: starlark::environment::GlobalsStatic =
                        starlark::environment::GlobalsStatic::new();
                    starlark::values::ValueLike::invoke(
                        RES.function(#create_func), args, eval)
                }

                fn provide(&'v self, demand: &mut starlark::values::Demand<'_, 'v>) {
                    demand.provide_value::<
                        &dyn buck2_interpreter::types::provider::callable::ProviderCallableLike>(self);
                }

                #documentation_function
            }
        })
    }

    fn callable_impl_provider_callable_like(&self) -> syn::Result<proc_macro2::TokenStream> {
        let callable_name = self.callable_name()?;
        Ok(quote! {
            impl buck2_interpreter::types::provider::callable::ProviderCallableLike for #callable_name {
                fn id(&self) -> Option<&std::sync::Arc<buck2_core::provider::id::ProviderId>> {
                    Some(self.id)
                }
            }
        })
    }

    fn callable_impl(&self) -> syn::Result<proc_macro2::TokenStream> {
        let callable_name = self.callable_name()?;
        let vis = &self.input.vis;
        let name_str = self.name_str()?;
        let frozen_name = self.frozen_name()?;

        Ok(quote! {
            impl #callable_name {
                #vis fn provider_id()
                -> &'static std::sync::Arc<buck2_core::provider::id::ProviderId> {
                    Self::provider_id_t().id()
                }

                #vis fn provider_id_t() -> &'static std::sync::Arc<
                    buck2_core::provider::id::ProviderIdWithType<#frozen_name>,
                > {
                    static PROVIDER_ID_T: once_cell::sync::OnceCell<
                        std::sync::Arc<
                            buck2_core::provider::id::ProviderIdWithType<#frozen_name>,
                        >
                    > = once_cell::sync::OnceCell::new();
                    PROVIDER_ID_T.get_or_init(|| {
                        std::sync::Arc::new(
                            buck2_core::provider::id::ProviderIdWithType::new(
                                None,
                                #name_str.to_owned(),
                            ),
                        )
                    })
                }

                #vis fn new() -> Self {
                    Self {
                        id: Self::provider_id(),
                    }
                }
            }
        })
    }

    fn callable_impl_display(&self) -> syn::Result<proc_macro2::TokenStream> {
        let callable_name = self.callable_name()?;
        let name_str = self.name_str()?;
        Ok(quote! {
            impl std::fmt::Display for #callable_name {
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    write!(f, "{}()", #name_str)
                }
            }
        })
    }

    fn callable(&self) -> syn::Result<proc_macro2::TokenStream> {
        let gen = vec![
            self.callable_struct()?,
            self.callable_impl()?,
            self.callable_impl_display()?,
            self.callable_impl_starlark_value()?,
            self.callable_impl_provider_callable_like()?,
        ];

        Ok(quote! {
            #(#gen)*
        })
    }

    fn register(&self) -> syn::Result<proc_macro2::TokenStream> {
        let register_func_name = self.register_func_name()?;
        let field_names = self.field_names()?;
        let name = self.name()?;
        let name_str = self.name_str()?;
        let callable_name = self.callable_name()?;

        Ok(quote! {
            // workaround starlark requiring that GlobalsBuilder is unqualified
            mod _register {
                use super::*;
                use starlark::environment::MethodsBuilder;

                #[starlark::starlark_module]
                pub(crate) fn #register_func_name(builder: &mut MethodsBuilder) {
                    #(
                        #[starlark(attribute)]
                        fn #field_names<'v>(this: & #name) -> anyhow::Result<starlark::values::Value<'v>> {
                            Ok(this.#field_names)
                        }
                    )*
                }
            }

            fn register_provider(builder: &mut starlark::environment::GlobalsBuilder) {
                builder.set(#name_str, #callable_name::new());
            }
        })
    }

    fn inventory(&self) -> syn::Result<proc_macro2::TokenStream> {
        Ok(quote! {
            inventory::submit! {
                crate::interpreter::rule_defs::provider::registration::ProviderRegistration {
                    register_globals: |globals| {
                        register_provider(globals)
                    }
                }
            }
        })
    }
}

pub(crate) fn define_provider(
    args: InternalProviderArgs,
    input: syn::ItemStruct,
) -> syn::Result<proc_macro::TokenStream> {
    let codegen = ProviderCodegen::new(input, args)?;

    if let Some(where_clause) = codegen.input.generics.where_clause {
        return Err(syn::Error::new_spanned(
            where_clause,
            "should have no where clauses",
        ));
    }
    if let Some(const_param) = codegen.input.generics.const_params().next() {
        return Err(syn::Error::new_spanned(
            const_param,
            "should have no const params",
        ));
    }
    if let Some(lifetime) = codegen.input.generics.lifetimes().next() {
        return Err(syn::Error::new_spanned(
            lifetime,
            "should have no lifetime params",
        ));
    }
    let mut type_params: Vec<_> = codegen.input.generics.type_params().collect();
    if type_params.len() != 1 {
        return Err(syn::Error::new_spanned(
            codegen.input,
            "should have exactly one type param",
        ));
    }
    let type_param = type_params.pop().unwrap();
    if let Some(bound) = type_param.bounds.iter().next() {
        return Err(syn::Error::new_spanned(
            bound,
            "type param should have no bounds",
        ));
    }

    // TODO(cjhopman): Verify `V` type param as expected
    // TODO(cjhopman): Verify all fields are type `V`

    let input = &codegen.input;

    let gen = vec![
        quote! { #input },
        codegen.impl_display()?,
        codegen.impl_starlark_value()?,
        codegen.impl_serializable_value()?,
        codegen.from_providers()?,
        codegen.impl_provider_like()?,
        codegen.callable()?,
        codegen.register()?,
        codegen.inventory()?,
    ];
    let gen = quote! { #(#gen)* };

    Ok(gen.into())
}


/*
 * 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.
 */

#![cfg(test)]
#![feature(provide_any)]

mod actions;
mod analysis;
mod artifact_groups;
mod attrs;
mod deferred;
mod interpreter;
mod nodes;

#[test]
fn init_late_bindings_for_test() {
    #[ctor::ctor]
    fn init() {
        buck2_analysis::init_late_bindings();
        buck2_anon_target::init_late_bindings();
        buck2_configured::init_late_bindings();
        buck2_interpreter_for_build::init_late_bindings();
    }
}


/*
 * 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::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;

use assert_matches::assert_matches;
use buck2_artifact::artifact::artifact_type::testing::BuildArtifactTestingExt;
use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_artifact::artifact::build_artifact::BuildArtifact;
use buck2_artifact::artifact::source_artifact::SourceArtifact;
use buck2_artifact::deferred::id::DeferredId;
use buck2_build_api::actions::calculation::command_details;
use buck2_build_api::actions::calculation::ActionCalculation;
use buck2_build_api::actions::impls::run_action_knobs::RunActionKnobs;
use buck2_build_api::actions::Action;
use buck2_build_api::actions::RegisteredAction;
use buck2_build_api::artifact_groups::calculation::ArtifactGroupCalculation;
use buck2_build_api::artifact_groups::ArtifactGroup;
use buck2_build_api::context::SetBuildContextData;
use buck2_build_api::deferred::calculation::DeferredResolve;
use buck2_build_api::deferred::types::AnyValue;
use buck2_build_api::deferred::types::DeferredValueAnyReady;
use buck2_build_api::keep_going::HasKeepGoing;
use buck2_build_api::spawner::BuckSpawner;
use buck2_common::dice::cells::SetCellResolver;
use buck2_common::dice::data::testing::SetTestingIoProvider;
use buck2_common::dice::file_ops::keys::FileOpsValue;
use buck2_common::dice::file_ops::testing::FileOpsKey;
use buck2_common::executor_config::CommandExecutorConfig;
use buck2_common::external_symlink::ExternalSymlink;
use buck2_common::file_ops::testing::TestFileOps;
use buck2_common::file_ops::FileMetadata;
use buck2_common::file_ops::TrackedFileDigest;
use buck2_common::http::counting_client::CountingHttpClient;
use buck2_common::http::ClientForTest;
use buck2_common::http::SetHttpClient;
use buck2_common::result::ToSharedResultExt;
use buck2_core::buck_path::path::BuckPath;
use buck2_core::category::Category;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::cell_root_path::CellRootPathBuf;
use buck2_core::cells::name::CellName;
use buck2_core::cells::paths::CellRelativePathBuf;
use buck2_core::cells::CellResolver;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::directory::DirectoryEntry;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use buck2_core::fs::project::ProjectRootTemp;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_core::package::package_relative_path::PackageRelativePathBuf;
use buck2_core::package::PackageLabel;
use buck2_core::target::label::TargetLabel;
use buck2_core::target::name::TargetNameRef;
use buck2_events::dispatch::with_dispatcher_async;
use buck2_events::dispatch::EventDispatcher;
use buck2_execute::artifact_value::ArtifactValue;
use buck2_execute::digest_config::DigestConfig;
use buck2_execute::digest_config::SetDigestConfig;
use buck2_execute::directory::ActionDirectoryMember;
use buck2_execute::execute::action_digest::ActionDigest;
use buck2_execute::execute::blocking::testing::DummyBlockingExecutor;
use buck2_execute::execute::blocking::SetBlockingExecutor;
use buck2_execute::execute::cache_uploader::NoOpCacheUploader;
use buck2_execute::execute::dice_data::set_fallback_executor_config;
use buck2_execute::execute::dice_data::CommandExecutorResponse;
use buck2_execute::execute::dice_data::HasCommandExecutor;
use buck2_execute::execute::dice_data::SetCommandExecutor;
use buck2_execute::execute::dice_data::SetReClient;
use buck2_execute::execute::kind::CommandExecutionKind;
use buck2_execute::execute::output::CommandStdStreams;
use buck2_execute::execute::prepared::NoOpCommandExecutor;
use buck2_execute::execute::request::CommandExecutionOutput;
use buck2_execute::execute::request::OutputType;
use buck2_execute::execute::result::CommandExecutionReport;
use buck2_execute::execute::result::CommandExecutionStatus;
use buck2_execute::execute::testing_dry_run::DryRunEntry;
use buck2_execute::execute::testing_dry_run::DryRunExecutor;
use buck2_execute::materialize::materializer::SetMaterializer;
use buck2_execute::materialize::nodisk::NoDiskMaterializer;
use buck2_execute::re::manager::ManagedRemoteExecutionClient;
use buck2_file_watcher::mergebase::SetMergebase;
use dice::testing::DiceBuilder;
use dice::DiceTransaction;
use dice::UserComputationData;
use dupe::Dupe;
use indexmap::indexset;
use maplit::btreemap;
use sorted_vector_map::sorted_vector_map;

use crate::actions::testings::SimpleAction;

fn create_test_build_artifact(
    package_cell: &str,
    package_path: &str,
    target_name: &str,
) -> BuildArtifact {
    let configured_target_label = TargetLabel::new(
        PackageLabel::testing_new(package_cell, package_path),
        TargetNameRef::unchecked_new(target_name),
    )
    .configure(ConfigurationData::testing_new());
    let forward_relative_path_buf = ForwardRelativePathBuf::unchecked_new("bar.out".into());
    let deferred_id = DeferredId::testing_new(0);
    BuildArtifact::testing_new(
        configured_target_label,
        forward_relative_path_buf,
        deferred_id,
    )
}

fn create_test_source_artifact(
    package_cell: &str,
    package_path: &str,
    target_name: &str,
) -> SourceArtifact {
    SourceArtifact::new(BuckPath::testing_new(
        PackageLabel::testing_new(package_cell, package_path),
        PackageRelativePathBuf::unchecked_new(target_name.into()),
    ))
}

fn registered_action(
    build_artifact: BuildArtifact,
    action: Box<dyn Action>,
) -> Arc<RegisteredAction> {
    let registered_action = RegisteredAction::new(
        build_artifact.key().dupe(),
        action,
        CommandExecutorConfig::testing_local(),
    );
    Arc::new(registered_action)
}

fn mock_deferred_resolution_calculation(
    dice_builder: DiceBuilder,
    deferred_resolve: DeferredResolve,
    registered_action_arc: Arc<RegisteredAction>,
) -> DiceBuilder {
    let arc_any: Arc<dyn AnyValue + 'static> = Arc::new(registered_action_arc);
    let an_any = DeferredValueAnyReady::AnyValue(arc_any);
    dice_builder.mock_and_return(deferred_resolve, anyhow::Ok(an_any).shared_error())
}

async fn make_default_dice_state(
    dry_run_tracker: Arc<Mutex<Vec<DryRunEntry>>>,
    temp_fs: &ProjectRootTemp,
    mocks: Vec<Box<dyn FnOnce(DiceBuilder) -> DiceBuilder>>,
) -> anyhow::Result<DiceTransaction> {
    let fs = temp_fs.path().dupe();

    let cell_resolver = CellResolver::testing_with_name_and_path(
        CellName::testing_new("cell"),
        CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("cell-path".into())),
    );
    let output_path = ProjectRelativePathBuf::unchecked_new("buck-out/v2".into());

    let mut dice_builder = DiceBuilder::new();
    dice_builder = dice_builder.set_data(|data| {
        data.set_testing_io_provider(temp_fs);
        data.set_digest_config(DigestConfig::testing_default());
    });

    for mock in mocks.into_iter() {
        dice_builder = mock(dice_builder);
    }

    let mut extra = UserComputationData::new();
    extra.set_keep_going(true);
    struct CommandExecutorProvider {
        dry_run_tracker: Arc<Mutex<Vec<DryRunEntry>>>,
    }
    impl HasCommandExecutor for CommandExecutorProvider {
        fn get_command_executor(
            &self,
            artifact_fs: &ArtifactFs,
            _config: &CommandExecutorConfig,
        ) -> anyhow::Result<CommandExecutorResponse> {
            let executor = Arc::new(DryRunExecutor::new(
                self.dry_run_tracker.dupe(),
                artifact_fs.clone(),
            ));
            Ok(CommandExecutorResponse {
                executor,
                cache_checker: Arc::new(NoOpCommandExecutor {}),
                platform: Default::default(),
                cache_uploader: Arc::new(NoOpCacheUploader {}),
            })
        }
    }

    set_fallback_executor_config(&mut extra.data, CommandExecutorConfig::testing_local());
    extra.set_command_executor(Box::new(CommandExecutorProvider { dry_run_tracker }));
    extra.set_blocking_executor(Arc::new(DummyBlockingExecutor { fs }));
    extra.set_materializer(Arc::new(NoDiskMaterializer));
    extra.set_re_client(ManagedRemoteExecutionClient::testing_new_dummy());
    extra.set_http_client(CountingHttpClient::new(Arc::new(ClientForTest {})));
    extra.set_mergebase(Default::default());
    extra.data.set(EventDispatcher::null());
    extra.data.set(RunActionKnobs::default());
    extra.spawner = Arc::new(BuckSpawner);

    let mut computations = dice_builder.build(extra)?;
    computations.set_buck_out_path(Some(output_path))?;
    computations.set_cell_resolver(cell_resolver)?;

    Ok(computations.commit().await)
}

#[tokio::test]
async fn test_get_action_for_artifact() -> anyhow::Result<()> {
    let build_artifact = create_test_build_artifact("cell", "pkg", "foo");
    let deferred_resolve = DeferredResolve(build_artifact.key().deferred_key().dupe());
    let registered_action = registered_action(
        build_artifact.dupe(),
        Box::new(SimpleAction::new(
            indexset![],
            indexset![build_artifact.dupe()],
            vec![],
            Category::try_from("fake_action").unwrap(),
            None,
        )),
    );

    let mut dice_builder = DiceBuilder::new();
    dice_builder = mock_deferred_resolution_calculation(
        dice_builder,
        deferred_resolve,
        registered_action.dupe(),
    );
    let dice_computations = dice_builder
        .build(UserComputationData::new())?
        .commit()
        .await;

    let result = with_dispatcher_async(
        EventDispatcher::null(),
        dice_computations.get_action(build_artifact.key()),
    )
    .await;
    assert_eq!(result?, registered_action);
    Ok(())
}

#[tokio::test]
async fn test_build_action() -> anyhow::Result<()> {
    let temp_fs = ProjectRootTemp::new()?;
    let build_artifact = create_test_build_artifact("cell", "pkg", "foo");
    let deferred_resolve = DeferredResolve(build_artifact.key().deferred_key().dupe());
    let registered_action = registered_action(
        build_artifact.dupe(),
        Box::new(SimpleAction::new(
            indexset![],
            indexset![build_artifact.dupe()],
            vec!["foo".to_owned(), "cmd".to_owned()],
            Category::try_from("fake_action").unwrap(),
            None,
        )),
    );

    let dry_run_tracker = Arc::new(Mutex::new(vec![]));
    let dice_computations = make_default_dice_state(
        dry_run_tracker.dupe(),
        &temp_fs,
        vec![{
            let action = registered_action.dupe();
            Box::new(move |builder| {
                mock_deferred_resolution_calculation(builder, deferred_resolve, action)
            })
        }],
    )
    .await?;

    let result = dice_computations
        .build_action(registered_action.key())
        .await;

    assert!(result.is_ok());

    assert_eq!(
        dry_run_tracker.lock().unwrap()[0],
        DryRunEntry {
            args: vec!["foo".to_owned(), "cmd".to_owned()],
            outputs: vec![CommandExecutionOutput::BuildArtifact {
                path: build_artifact.get_path().dupe(),
                output_type: OutputType::File,
            }],
            env: sorted_vector_map![]
        }
    );

    Ok(())
}

#[tokio::test]
async fn test_build_artifact() -> anyhow::Result<()> {
    let temp_fs = ProjectRootTemp::new()?;
    let build_artifact = create_test_build_artifact("cell", "pkg", "foo");
    let deferred_resolve = DeferredResolve(build_artifact.key().deferred_key().dupe());
    let registered_action = registered_action(
        build_artifact.dupe(),
        Box::new(SimpleAction::new(
            indexset![],
            indexset![build_artifact.dupe()],
            vec!["bar".to_owned(), "cmd".to_owned()],
            Category::try_from("fake_action").unwrap(),
            None,
        )),
    );

    let dry_run_tracker = Arc::new(Mutex::new(vec![]));
    let dice_computations = make_default_dice_state(dry_run_tracker.dupe(), &temp_fs, {
        let registered_action = registered_action.dupe();
        vec![Box::new(move |builder| {
            mock_deferred_resolution_calculation(builder, deferred_resolve, registered_action)
        })]
    })
    .await?;

    let result = with_dispatcher_async(
        EventDispatcher::null(),
        dice_computations.build_artifact(&build_artifact),
    )
    .await;

    assert!(result.is_ok());

    assert_eq!(
        dry_run_tracker.lock().unwrap()[0],
        DryRunEntry {
            args: vec!["bar".to_owned(), "cmd".to_owned()],
            outputs: vec![CommandExecutionOutput::BuildArtifact {
                path: build_artifact.get_path().dupe(),
                output_type: OutputType::File,
            }],
            env: sorted_vector_map![]
        }
    );
    Ok(())
}

#[tokio::test]
async fn test_ensure_artifact_build_artifact() -> anyhow::Result<()> {
    let temp_fs = ProjectRootTemp::new()?;
    let build_artifact = create_test_build_artifact("cell", "pkg", "foo");
    let deferred_resolve = DeferredResolve(build_artifact.key().deferred_key().dupe());
    let registered_action = registered_action(
        build_artifact.dupe(),
        Box::new(SimpleAction::new(
            indexset![],
            indexset![build_artifact.dupe()],
            vec!["ensure".to_owned(), "cmd".to_owned()],
            Category::try_from("fake_action").unwrap(),
            None,
        )),
    );

    let dry_run_tracker = Arc::new(Mutex::new(vec![]));
    let dice_computations = make_default_dice_state(dry_run_tracker.dupe(), &temp_fs, {
        let registered_action = registered_action.dupe();
        vec![Box::new(move |builder| {
            mock_deferred_resolution_calculation(builder, deferred_resolve, registered_action)
        })]
    })
    .await?;

    let result = with_dispatcher_async(
        EventDispatcher::null(),
        dice_computations
            .ensure_artifact_group(&ArtifactGroup::Artifact(build_artifact.dupe().into())),
    )
    .await;

    assert!(result.is_ok());

    assert_eq!(
        dry_run_tracker.lock().unwrap()[0],
        DryRunEntry {
            args: vec!["ensure".to_owned(), "cmd".to_owned()],
            outputs: vec![CommandExecutionOutput::BuildArtifact {
                path: build_artifact.get_path().dupe(),
                output_type: OutputType::File,
            }],
            env: sorted_vector_map![]
        }
    );

    Ok(())
}

#[tokio::test]
async fn test_ensure_artifact_source_artifact() -> anyhow::Result<()> {
    let digest_config = DigestConfig::testing_default();

    let path = CellPath::new(
        CellName::testing_new("cell"),
        CellRelativePathBuf::unchecked_new("pkg/src.cpp".to_owned()),
    );
    let source_artifact = create_test_source_artifact("cell", "pkg", "src.cpp");
    let metadata = FileMetadata {
        digest: TrackedFileDigest::from_content(b"content", digest_config.cas_digest_config()),
        is_executable: true,
    };

    let dice_builder = DiceBuilder::new().set_data(|data| {
        data.set_digest_config(DigestConfig::testing_default());
    });
    let dice_computations = dice_builder
        .mock_and_return(
            FileOpsKey(),
            Ok(FileOpsValue(Arc::new(
                TestFileOps::new_with_files_metadata(btreemap![path => metadata.dupe()]),
            ))),
        )
        .build(UserComputationData::new())?
        .commit()
        .await;

    let source_artifact = Artifact::from(source_artifact);
    let input = ArtifactGroup::Artifact(source_artifact.dupe());
    let result = with_dispatcher_async(
        EventDispatcher::null(),
        dice_computations.ensure_artifact_group(&input),
    )
    .await?
    .iter()
    .cloned()
    .collect::<Vec<_>>();

    assert_eq!(
        &result,
        &[(
            source_artifact,
            ArtifactValue::file(FileMetadata {
                digest: metadata.digest,
                is_executable: metadata.is_executable,
            })
        )],
    );
    Ok(())
}

#[tokio::test]
async fn test_ensure_artifact_external_symlink() -> anyhow::Result<()> {
    let path = CellPath::new(
        CellName::testing_new("cell"),
        CellRelativePathBuf::unchecked_new("proj/to_gvfs/include".to_owned()),
    );
    let source_artifact = create_test_source_artifact("cell", "proj/to_gvfs", "include");
    let symlink = Arc::new(
        ExternalSymlink::new(
            PathBuf::from("/mnt/gvfs"),
            Some(ForwardRelativePathBuf::unchecked_new("include".to_owned())),
        )
        .unwrap(),
    );

    let dice_builder = DiceBuilder::new().set_data(|data| {
        data.set_digest_config(DigestConfig::testing_default());
    });
    let dice_computations = dice_builder
        .mock_and_return(
            FileOpsKey(),
            Ok(FileOpsValue(Arc::new(TestFileOps::new_with_symlinks(
                btreemap![path => symlink.dupe()],
            )))),
        )
        .build(UserComputationData::new())?
        .commit()
        .await;

    let source_artifact = Artifact::from(source_artifact);
    let input = ArtifactGroup::Artifact(source_artifact.dupe());
    let result = with_dispatcher_async(
        EventDispatcher::null(),
        dice_computations.ensure_artifact_group(&input),
    )
    .await?
    .iter()
    .cloned()
    .collect::<Vec<_>>();

    assert_eq!(
        &result,
        &[(
            source_artifact,
            ArtifactValue::new(
                DirectoryEntry::Leaf(ActionDirectoryMember::ExternalSymlink(symlink)),
                None
            )
        )]
    );
    Ok(())
}

#[tokio::test]
async fn test_command_details_omission() {
    use buck2_data::command_execution_details::Command;

    let digest_config = DigestConfig::testing_default();

    let mut report = CommandExecutionReport {
        claim: None,
        status: CommandExecutionStatus::Success {
            execution_kind: CommandExecutionKind::Local {
                digest: ActionDigest::empty(digest_config.cas_digest_config()),
                command: vec![],
                env: sorted_vector_map![],
            },
        },
        timing: Default::default(),
        std_streams: CommandStdStreams::Local {
            stdout: "stdout".to_owned().into_bytes(),
            stderr: "stderr".to_owned().into_bytes(),
        },
        exit_code: Some(1),
    };

    let proto = command_details(&report, false).await;
    assert_matches!(proto.command, Some(Command::LocalCommand(..)));
    assert_eq!(&proto.stdout, "stdout");
    assert_eq!(&proto.stderr, "stderr");

    let proto = command_details(&report, true).await;
    assert_matches!(proto.command, Some(Command::OmittedLocalCommand(..)));
    assert_eq!(&proto.stdout, "");
    assert_eq!(&proto.stderr, "stderr");

    report.status = CommandExecutionStatus::Failure {
        execution_kind: CommandExecutionKind::Local {
            digest: ActionDigest::empty(digest_config.cas_digest_config()),
            command: vec![],
            env: sorted_vector_map![],
        },
    };
    let proto = command_details(&report, true).await;
    assert_matches!(proto.command, Some(Command::LocalCommand(..)));
    assert_eq!(&proto.stdout, "stdout");
    assert_eq!(&proto.stderr, "stderr");
}


/*
 * 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.
 */

mod calculation;
mod impls;
pub(crate) mod registry;
pub(crate) mod testings;


/*
 * 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 assert_matches::assert_matches;
use buck2_artifact::artifact::artifact_type::testing::ArtifactTestingExt;
use buck2_artifact::artifact::artifact_type::testing::BuildArtifactTestingExt;
use buck2_artifact::artifact::build_artifact::BuildArtifact;
use buck2_artifact::deferred::id::DeferredId;
use buck2_build_api::actions::key::ActionKeyExt;
use buck2_build_api::actions::registry::ActionsRegistry;
use buck2_build_api::actions::ActionErrors;
use buck2_build_api::analysis::registry::AnalysisValueFetcher;
use buck2_build_api::artifact_groups::ArtifactGroup;
use buck2_build_api::deferred::types::BaseKey;
use buck2_build_api::deferred::types::DeferredRegistry;
use buck2_common::executor_config::CommandExecutorConfig;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::category::Category;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::fs::buck_out_path::BuckOutPath;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_execute::execute::request::OutputType;
use buck2_node::configuration::execution::ExecutionPlatform;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use dupe::Dupe;
use indexmap::indexset;

use crate::actions::testings::SimpleUnregisteredAction;

#[test]
fn declaring_artifacts() -> anyhow::Result<()> {
    let base = BaseDeferredKey::TargetLabel(ConfiguredTargetLabel::testing_parse(
        "cell//pkg:foo",
        ConfigurationData::testing_new(),
    ));
    let mut actions = ActionsRegistry::new(base.dupe(), ExecutionPlatformResolution::unspecified());
    let out1 = ForwardRelativePathBuf::unchecked_new("bar.out".into());
    let buckout1 = BuckOutPath::new(base.dupe(), out1.clone());
    let declared1 = actions.declare_artifact(None, out1.clone(), OutputType::File, None)?;
    declared1
        .get_path()
        .with_full_path(|p| assert_eq!(p, buckout1.path()));

    let out2 = ForwardRelativePathBuf::unchecked_new("bar2.out".into());
    let buckout2 = BuckOutPath::new(base, out2.clone());
    let declared2 = actions.declare_artifact(None, out2, OutputType::File, None)?;
    declared2
        .get_path()
        .with_full_path(|p| assert_eq!(p, buckout2.path()));

    if actions
        .declare_artifact(None, out1, OutputType::File, None)
        .is_ok()
    {
        panic!("should error due to duplicate artifact")
    }

    assert_eq!(actions.testing_artifacts().contains(&declared1), true);
    assert_eq!(actions.testing_artifacts().contains(&declared2), true);

    Ok(())
}

#[test]
fn claiming_conflicting_path() -> anyhow::Result<()> {
    let target = ConfiguredTargetLabel::testing_parse(
        "cell//pkg:my_target",
        ConfigurationData::testing_new(),
    );
    let mut actions = ActionsRegistry::new(
        BaseDeferredKey::TargetLabel(target.dupe()),
        ExecutionPlatformResolution::unspecified(),
    );

    let out1 = ForwardRelativePathBuf::unchecked_new("foo/a/1".into());
    actions.claim_output_path(&out1, None)?;

    let out2 = ForwardRelativePathBuf::unchecked_new("foo/a/2".into());
    actions.claim_output_path(&out2, None)?;

    {
        let expected_conflicts = vec!["foo/a/1 declared at <unknown>".to_owned()];
        let prefix_claimed = ForwardRelativePathBuf::unchecked_new("foo/a/1/some/path".into());
        assert_matches!(
            actions.claim_output_path(&prefix_claimed, None),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<ActionErrors>(),
                    Some(ActionErrors::ConflictingOutputPaths(_inserted, existing)) => {
                        assert_eq!(existing, &expected_conflicts);
                    }
                );
            }
        );
    }

    assert_matches!(
        actions.claim_output_path(&out1, None),
        Err(e) => {
            assert_matches!(
                e.downcast_ref::<ActionErrors>(),
                Some(ActionErrors::ConflictingOutputPath(..))
            );
        }
    );

    {
        let overwrite_dir = ForwardRelativePathBuf::unchecked_new("foo".into());
        let expected_conflicts = vec![
            "foo/a/1 declared at <unknown>".to_owned(),
            "foo/a/2 declared at <unknown>".to_owned(),
        ];
        assert_matches!(
            actions.claim_output_path(&overwrite_dir, None),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<ActionErrors>(),
                    Some(ActionErrors::ConflictingOutputPaths(_inserted, existing)) => {
                        assert_eq!(existing, &expected_conflicts);
                    }
                );
            }
        );
    }

    Ok(())
}

#[test]
fn register_actions() -> anyhow::Result<()> {
    let base = BaseDeferredKey::TargetLabel(ConfiguredTargetLabel::testing_parse(
        "cell//pkg:foo",
        ConfigurationData::testing_new(),
    ));
    let mut deferreds = DeferredRegistry::new(BaseKey::Base(base.dupe()));
    let mut actions = ActionsRegistry::new(base.dupe(), ExecutionPlatformResolution::unspecified());
    let out = ForwardRelativePathBuf::unchecked_new("bar.out".into());
    let declared = actions.declare_artifact(None, out, OutputType::File, None)?;

    let inputs = indexset![ArtifactGroup::Artifact(
        BuildArtifact::testing_new(
            base.unpack_target_label().unwrap().dupe(),
            ForwardRelativePathBuf::unchecked_new("input".into()),
            DeferredId::testing_new(1),
        )
        .into()
    )];
    let outputs = indexset![declared.as_output()];

    let unregistered_action =
        SimpleUnregisteredAction::new(vec![], Category::try_from("fake_action").unwrap(), None);
    assert_eq!(
        actions
            .register(&mut deferreds, inputs, outputs, unregistered_action)
            .is_ok(),
        true
    );

    assert_eq!(actions.testing_pending().count(), 1);
    assert_eq!(declared.testing_is_bound(), true);
    assert_eq!(
        actions
            .testing_pending()
            .any(|reserved| reserved.data()
                == declared.testing_action_key().unwrap().deferred_data()),
        true
    );

    Ok(())
}

#[test]
fn finalizing_actions() -> anyhow::Result<()> {
    let base = BaseDeferredKey::TargetLabel(ConfiguredTargetLabel::testing_parse(
        "cell//pkg:foo",
        ConfigurationData::testing_new(),
    ));
    let mut deferreds = DeferredRegistry::new(BaseKey::Base(base.dupe()));
    let mut actions = ActionsRegistry::new(
        base.dupe(),
        ExecutionPlatformResolution::new(
            Some(ExecutionPlatform::legacy_execution_platform(
                CommandExecutorConfig::testing_local(),
                ConfigurationNoExec::testing_new(),
            )),
            Vec::new(),
        ),
    );
    let out = ForwardRelativePathBuf::unchecked_new("bar.out".into());
    let declared = actions.declare_artifact(None, out, OutputType::File, None)?;

    let inputs = indexset![ArtifactGroup::Artifact(
        BuildArtifact::testing_new(
            base.unpack_target_label().unwrap().dupe(),
            ForwardRelativePathBuf::unchecked_new("input".into()),
            DeferredId::testing_new(1),
        )
        .into()
    )];
    let outputs = indexset![declared.as_output()];

    let unregistered_action =
        SimpleUnregisteredAction::new(vec![], Category::try_from("fake_action").unwrap(), None);
    actions.register(&mut deferreds, inputs, outputs, unregistered_action)?;

    let result = actions.ensure_bound(&mut deferreds, &AnalysisValueFetcher::default());
    assert_eq!(result.is_ok(), true, "Expected Ok(_), got `{:?}`", result);

    let registered_deferreds = deferreds.take_result()?;

    assert_eq!(registered_deferreds.len(), 1);

    assert_eq!(
        registered_deferreds
            .get(
                declared
                    .testing_action_key()
                    .unwrap()
                    .deferred_key()
                    .id()
                    .as_usize()
            )
            .is_some(),
        true
    );

    Ok(())
}

#[test]
fn duplicate_category_singleton_actions() {
    let result =
        category_identifier_test(&[("singleton_category", None), ("singleton_category", None)])
            .unwrap_err()
            .downcast::<ActionErrors>()
            .unwrap();

    assert!(matches!(
        result,
        ActionErrors::ActionCategoryDuplicateSingleton(_)
    ));
}

#[test]
fn duplicate_category_identifier() {
    let result = category_identifier_test(&[
        ("cxx_compile", Some("foo.cpp")),
        ("cxx_compile", Some("foo.cpp")),
    ])
    .unwrap_err()
    .downcast::<ActionErrors>()
    .unwrap();

    assert!(matches!(
        result,
        ActionErrors::ActionCategoryIdentifierNotUnique(_, _)
    ),);
}

fn category_identifier_test(
    action_names: &[(&'static str, Option<&'static str>)],
) -> anyhow::Result<()> {
    let base = BaseDeferredKey::TargetLabel(ConfiguredTargetLabel::testing_parse(
        "cell//pkg:foo",
        ConfigurationData::testing_new(),
    ));
    let mut deferreds = DeferredRegistry::new(BaseKey::Base(base.dupe()));
    let mut actions = ActionsRegistry::new(
        base.dupe(),
        ExecutionPlatformResolution::new(
            Some(ExecutionPlatform::legacy_execution_platform(
                CommandExecutorConfig::testing_local(),
                ConfigurationNoExec::testing_new(),
            )),
            Vec::new(),
        ),
    );
    for (category, identifier) in action_names {
        let unregistered_action = SimpleUnregisteredAction::new(
            vec![],
            Category::try_from(category.to_owned()).unwrap(),
            identifier.map(|i| i.to_owned()),
        );

        actions.register(
            &mut deferreds,
            indexset![],
            indexset![],
            unregistered_action,
        )?;
    }

    actions.ensure_bound(&mut deferreds, &AnalysisValueFetcher::default())
}


/*
 * 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::borrow::Cow;

use allocative::Allocative;
use async_trait::async_trait;
use buck2_artifact::artifact::build_artifact::BuildArtifact;
use buck2_build_api::actions::box_slice_set::BoxSliceSet;
use buck2_build_api::actions::execute::action_executor::ActionExecutionMetadata;
use buck2_build_api::actions::execute::action_executor::ActionOutputs;
use buck2_build_api::actions::Action;
use buck2_build_api::actions::ActionExecutable;
use buck2_build_api::actions::ActionExecutionCtx;
use buck2_build_api::actions::PristineActionExecutable;
use buck2_build_api::actions::UnregisteredAction;
use buck2_build_api::artifact_groups::ArtifactGroup;
use buck2_core::category::Category;
use buck2_execute::execute::request::CommandExecutionOutput;
use buck2_execute::execute::request::CommandExecutionPaths;
use buck2_execute::execute::request::CommandExecutionRequest;
use buck2_execute::execute::request::OutputType;
use derivative::Derivative;
use dupe::Dupe;
use indexmap::IndexSet;
use sorted_vector_map::sorted_vector_map;
use starlark::values::OwnedFrozenValue;

/// A simple unregistered action that will eventually be resolved into an action that runs the
/// given cmd as the action execution command. Used for testing
///
/// This action is for testing, and bypasses the need to create starlark values and frozen
/// modules
#[derive(Allocative)]
pub(crate) struct SimpleUnregisteredAction {
    cmd: Vec<String>,
    category: Category,
    identifier: Option<String>,
}

impl SimpleUnregisteredAction {
    pub(crate) fn new(cmd: Vec<String>, category: Category, identifier: Option<String>) -> Self {
        Self {
            cmd,
            category,
            identifier,
        }
    }
}

/// The action created by SimpleUnregisteredAction, or directly.
#[derive(Derivative, Allocative)]
#[derivative(Debug)]
pub(crate) struct SimpleAction {
    inputs: BoxSliceSet<ArtifactGroup>,
    outputs: BoxSliceSet<BuildArtifact>,
    cmd: Vec<String>,
    category: Category,
    identifier: Option<String>,
}

impl SimpleAction {
    pub(crate) fn new(
        inputs: IndexSet<ArtifactGroup>,
        outputs: IndexSet<BuildArtifact>,
        cmd: Vec<String>,
        category: Category,
        identifier: Option<String>,
    ) -> Self {
        Self {
            inputs: BoxSliceSet::from(inputs),
            outputs: BoxSliceSet::from(outputs),
            cmd,
            category,
            identifier,
        }
    }
}

impl UnregisteredAction for SimpleUnregisteredAction {
    fn register(
        self: Box<Self>,
        inputs: IndexSet<ArtifactGroup>,
        outputs: IndexSet<BuildArtifact>,
        _starlark_data: Option<OwnedFrozenValue>,
    ) -> anyhow::Result<Box<dyn Action>> {
        Ok(Box::new(SimpleAction {
            inputs: BoxSliceSet::from(inputs),
            outputs: BoxSliceSet::from(outputs),
            cmd: self.cmd,
            category: self.category,
            identifier: self.identifier,
        }))
    }
}

#[async_trait]
impl Action for SimpleAction {
    fn kind(&self) -> buck2_data::ActionKind {
        buck2_data::ActionKind::NotSet
    }

    fn inputs(&self) -> anyhow::Result<Cow<'_, [ArtifactGroup]>> {
        Ok(Cow::Borrowed(self.inputs.as_slice()))
    }

    fn outputs(&self) -> anyhow::Result<Cow<'_, [BuildArtifact]>> {
        Ok(Cow::Borrowed(self.outputs.as_slice()))
    }

    fn as_executable(&self) -> ActionExecutable<'_> {
        ActionExecutable::Pristine(self)
    }

    fn category(&self) -> &Category {
        &self.category
    }

    fn identifier(&self) -> Option<&str> {
        self.identifier.as_deref()
    }
}

#[async_trait]
impl PristineActionExecutable for SimpleAction {
    async fn execute(
        &self,
        ctx: &mut dyn ActionExecutionCtx,
    ) -> anyhow::Result<(ActionOutputs, ActionExecutionMetadata)> {
        let req = CommandExecutionRequest::new(
            vec![],
            self.cmd.clone(),
            CommandExecutionPaths::new(
                Vec::new(),
                self.outputs
                    .iter()
                    .map(|b| CommandExecutionOutput::BuildArtifact {
                        path: b.get_path().dupe(),
                        output_type: OutputType::File,
                    })
                    .collect(),
                ctx.fs(),
                ctx.digest_config(),
            )?,
            sorted_vector_map![],
        );

        let prepared_action = ctx.prepare_action(&req)?;
        let manager = ctx.command_execution_manager();
        let result = ctx.exec_cmd(manager, &req, &prepared_action).await;
        let (outputs, meta) = ctx.unpack_command_execution_result(&req, result, false)?;

        Ok((outputs, meta))
    }
}


/*
 * 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_artifact::artifact::artifact_type::Artifact;
use buck2_artifact::artifact::artifact_type::OutputArtifact;
use buck2_build_api::actions::impls::json::visit_json_artifacts;
use buck2_build_api::actions::impls::json::SerializeValue;
use buck2_build_api::artifact_groups::ArtifactGroup;
use buck2_build_api::interpreter::rule_defs::artifact::starlark_artifact_like::ValueAsArtifactLike;
use buck2_build_api::interpreter::rule_defs::artifact_tagging::ArtifactTag;
use buck2_build_api::interpreter::rule_defs::cmd_args::CommandLineArtifactVisitor;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use dupe::Dupe;
use indoc::indoc;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;
use starlark::values::Value;

use crate::interpreter::rule_defs::artifact::testing::artifactory;
use crate::interpreter::rule_defs::artifact_tagging::testing::artifact_tag_factory;

#[test]
fn test_tagging() -> anyhow::Result<()> {
    struct AssertVisitor {
        tag: ArtifactTag,
        artifact: Artifact,
    }

    impl CommandLineArtifactVisitor for AssertVisitor {
        fn visit_input(&mut self, input: ArtifactGroup, tag: Option<&ArtifactTag>) {
            assert_eq!(tag, Some(&self.tag));
            assert_eq!(input, ArtifactGroup::Artifact(self.artifact.dupe()));
        }

        fn visit_output(&mut self, _artifact: OutputArtifact, _tag: Option<&ArtifactTag>) {}
    }

    #[starlark_module]
    fn assertions(builder: &mut GlobalsBuilder) {
        fn check_artifact_is_tagged<'v>(
            tagged: Value<'v>,
            tag: Value<'v>,
            artifact: ValueAsArtifactLike<'v>,
        ) -> anyhow::Result<Value<'v>> {
            let tag = ArtifactTag::from_value(tag).context("Invalid tag")?.dupe();

            let artifact = artifact
                .0
                .get_bound_artifact()
                .context("Not a bound artifact")?
                .dupe();

            visit_json_artifacts(tagged, &mut AssertVisitor { tag, artifact })?;
            Ok(Value::new_none())
        }

        fn check_passthrough<'v>(tagged: Value<'v>, value: Value<'v>) -> anyhow::Result<Value<'v>> {
            let json1 = serde_json::to_string(&SerializeValue {
                value: tagged,
                fs: None,
            })?;

            let json2 = serde_json::to_string(&SerializeValue { value, fs: None })?;

            assert_eq!(json1, json2);

            Ok(Value::new_none())
        }
    }

    let mut tester = Tester::new()?;
    tester.additional_globals(artifact_tag_factory);
    tester.additional_globals(artifactory);
    tester.additional_globals(assertions);

    tester.run_starlark_bzl_test(indoc!(
        r#"
        def test():
            t1 = make_tag()
            a1 = source_artifact("foo", "bar")
            v1 = {"foo": "bar"}

            check_artifact_is_tagged(t1.tag_artifacts(a1), t1, a1)
            check_passthrough(t1.tag_artifacts(v1), v1)
        "#
    ))?;

    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.
 */

mod json;


/*
 * 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::collections::HashMap;
use std::sync::Arc;

use buck2_build_api::analysis::calculation::RuleAnalysisCalculation;
use buck2_build_api::deferred::types::testing::DeferredAnalysisResultExt;
use buck2_build_api::interpreter::build_defs::register_provider;
use buck2_build_api::interpreter::rule_defs::provider::builtin::default_info::DefaultInfoCallable;
use buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_build_api::keep_going::HasKeepGoing;
use buck2_build_api::spawner::BuckSpawner;
use buck2_common::dice::data::testing::SetTestingIoProvider;
use buck2_common::executor_config::CommandExecutorConfig;
use buck2_common::legacy_configs::LegacyBuckConfig;
use buck2_common::legacy_configs::LegacyBuckConfigs;
use buck2_common::package_listing::listing::testing::PackageListingExt;
use buck2_common::package_listing::listing::PackageListing;
use buck2_configured::configuration::calculation::ExecutionPlatformsKey;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::alias::NonEmptyCellAlias;
use buck2_core::cells::cell_root_path::CellRootPathBuf;
use buck2_core::cells::name::CellName;
use buck2_core::cells::CellAliasResolver;
use buck2_core::cells::CellsAggregator;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::fs::project::ProjectRootTemp;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_core::package::PackageLabel;
use buck2_core::provider::id::testing::ProviderIdExt;
use buck2_core::provider::id::ProviderId;
use buck2_core::target::label::TargetLabel;
use buck2_events::dispatch::EventDispatcher;
use buck2_execute::digest_config::DigestConfig;
use buck2_execute::digest_config::SetDigestConfig;
use buck2_execute::execute::dice_data::set_fallback_executor_config;
use buck2_interpreter::dice::starlark_debug::SetStarlarkDebugger;
use buck2_interpreter::extra::InterpreterHostArchitecture;
use buck2_interpreter::extra::InterpreterHostPlatform;
use buck2_interpreter::file_loader::LoadedModules;
use buck2_interpreter::path::OwnedStarlarkModulePath;
use buck2_interpreter_for_build::attrs::attrs_global::register_attrs;
use buck2_interpreter_for_build::interpreter::calculation::InterpreterResultsKey;
use buck2_interpreter_for_build::interpreter::configuror::BuildInterpreterConfiguror;
use buck2_interpreter_for_build::interpreter::dice_calculation_delegate::testing::EvalImportKey;
use buck2_interpreter_for_build::interpreter::interpreter_setup::setup_interpreter_basic;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::rule::register_rule_function;
use buck2_util::collections::ordered_map::OrderedMap;
use dice::testing::DiceBuilder;
use dice::UserComputationData;
use dupe::Dupe;
use indoc::indoc;
use itertools::Itertools;
use maplit::hashmap;

#[tokio::test]
async fn test_analysis_calculation() -> anyhow::Result<()> {
    let bzlfile = ImportPath::testing_new("cell//pkg:foo.bzl");
    let resolver = {
        let mut cells = CellsAggregator::new();
        cells.add_cell_entry(
            CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("cell".to_owned())),
            NonEmptyCellAlias::new("root".to_owned()).unwrap(),
            CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("".to_owned())),
        )?;
        cells.add_cell_entry(
            CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("cell".to_owned())),
            NonEmptyCellAlias::new("cell".to_owned()).unwrap(),
            CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("cell".to_owned())),
        )?;
        cells.make_cell_resolver()?
    };
    let configs = LegacyBuckConfigs::new(hashmap![
        CellName::testing_new("root") =>
        LegacyBuckConfig::empty(),
        CellName::testing_new("cell") =>
        LegacyBuckConfig::empty(),
    ]);
    let mut interpreter = Tester::with_cells((
        CellAliasResolver::new(CellName::testing_new("cell"), HashMap::new())?,
        resolver.dupe(),
        configs.dupe(),
    ))?;
    interpreter.additional_globals(register_rule_function);
    interpreter.additional_globals(register_provider);
    interpreter.additional_globals(register_builtin_providers);
    interpreter.additional_globals(register_attrs);
    let module = interpreter
        .eval_import(
            &bzlfile,
            indoc!(r#"
                            FooInfo = provider(fields=["str"])

                            def impl(ctx):
                                str = ""
                                if ctx.attrs.dep:
                                    str = ctx.attrs.dep[FooInfo].str
                                return [FooInfo(str=(str + ctx.attrs.str)), DefaultInfo()]
                            foo_binary = rule(impl=impl, attrs={"dep": attrs.option(attrs.dep(providers=[FooInfo]), default = None), "str": attrs.string()})
                        "#),
            LoadedModules::default(),
        )?;

    let buildfile = BuildFilePath::testing_new("cell//pkg:BUCK");
    let eval_res = interpreter.eval_build_file_with_loaded_modules(
        &buildfile,
        indoc!(
            r#"
                    load(":foo.bzl", "FooInfo", "foo_binary")

                    foo_binary(
                        name = "rule1",
                        str = "a",
                        dep = ":rule2",
                    )
                    foo_binary(
                        name = "rule2",
                        str = "b",
                        dep = ":rule3",
                    )
                    foo_binary(
                        name = "rule3",
                        str = "c",
                        dep = None,
                    )
                "#
        ),
        LoadedModules {
            map: OrderedMap::from_iter([(
                OwnedStarlarkModulePath::LoadFile(bzlfile.clone()),
                module.dupe(),
            )]),
        },
        PackageListing::testing_new(&[], "BUCK"),
    )?;

    let fs = ProjectRootTemp::new()?;
    let mut dice = DiceBuilder::new()
        .mock_and_return(
            EvalImportKey(OwnedStarlarkModulePath::LoadFile(bzlfile.clone())),
            Ok(module),
        )
        .mock_and_return(
            InterpreterResultsKey(PackageLabel::testing_parse("cell//pkg")),
            Ok(Arc::new(eval_res)),
        )
        .mock_and_return(ExecutionPlatformsKey, Ok(None))
        .set_data(|data| {
            data.set_testing_io_provider(&fs);
            data.set_digest_config(DigestConfig::testing_default());
        })
        .build({
            let mut data = UserComputationData::new();
            data.set_keep_going(true);
            data.set_starlark_debugger_handle(None);
            set_fallback_executor_config(&mut data.data, CommandExecutorConfig::testing_local());
            data.data.set(EventDispatcher::null());
            data.spawner = Arc::new(BuckSpawner);
            data
        })?;
    setup_interpreter_basic(
        &mut dice,
        resolver,
        BuildInterpreterConfiguror::new(
            None,
            InterpreterHostPlatform::Linux,
            InterpreterHostArchitecture::X86_64,
            None,
            false,
            false,
            |_| {},
            |_| {},
            |_| {},
            |_| {},
            None,
        )?,
        configs,
    )?;
    let dice = dice.commit().await;

    let analysis = dice
        .get_analysis_result(
            &TargetLabel::testing_parse("cell//pkg:rule1")
                .configure(ConfigurationData::testing_new()),
        )
        .await?
        .require_compatible()?;

    assert_eq!(analysis.testing_deferred().get_registered().len(), 0);

    assert_eq!(
        analysis
            .providers()
            .provider_collection()
            .provider_names()
            .iter()
            .sorted()
            .eq(vec!["DefaultInfo", "FooInfo"]),
        true
    );

    assert_eq!(
        analysis
            .providers()
            .provider_collection()
            .get_provider_raw(&ProviderId::testing_new(bzlfile.path().clone(), "FooInfo"))
            .is_some(),
        true
    );
    assert_eq!(
        analysis
            .providers()
            .provider_collection()
            .get_provider_raw(DefaultInfoCallable::provider_id())
            .is_some(),
        true
    );

    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.
 */

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.
 */

use std::collections::HashMap;
use std::sync::Arc;

use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_artifact::artifact::source_artifact::SourceArtifact;
use buck2_build_api::artifact_groups::calculation::ArtifactGroupCalculation;
use buck2_build_api::artifact_groups::deferred::DeferredTransitiveSetData;
use buck2_build_api::artifact_groups::ArtifactGroup;
use buck2_build_api::artifact_groups::TransitiveSetProjectionKey;
use buck2_build_api::context::SetBuildContextData;
use buck2_build_api::deferred::calculation::DeferredResolve;
use buck2_build_api::deferred::types::AnyValue;
use buck2_build_api::deferred::types::DeferredValueAnyReady;
use buck2_build_api::interpreter::rule_defs::transitive_set::TransitiveSet;
use buck2_build_api::interpreter::rule_defs::transitive_set::TransitiveSetOrdering;
use buck2_build_api::keep_going::HasKeepGoing;
use buck2_common::dice::cells::SetCellResolver;
use buck2_common::dice::data::testing::SetTestingIoProvider;
use buck2_common::dice::file_ops::keys::FileOpsKey;
use buck2_common::dice::file_ops::keys::FileOpsValue;
use buck2_common::file_ops::testing::TestFileOps;
use buck2_common::file_ops::FileMetadata;
use buck2_common::file_ops::TrackedFileDigest;
use buck2_common::result::ToSharedResultExt;
use buck2_core::buck_path::path::BuckPath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::cell_root_path::CellRootPathBuf;
use buck2_core::cells::name::CellName;
use buck2_core::cells::paths::CellRelativePathBuf;
use buck2_core::cells::CellResolver;
use buck2_core::fs::project::ProjectRootTemp;
use buck2_core::package::package_relative_path::PackageRelativePathBuf;
use buck2_core::package::PackageLabel;
use buck2_execute::artifact_value::ArtifactValue;
use buck2_execute::digest_config::DigestConfig;
use buck2_execute::digest_config::SetDigestConfig;
use dice::testing::DiceBuilder;
use dice::UserComputationData;
use dupe::Dupe;
use indoc::indoc;
use maplit::btreemap;
use starlark::values::OwnedFrozenValue;

use crate::interpreter::transitive_set::testing::new_transitive_set;

fn mock_deferred_tset(dice_builder: DiceBuilder, value: OwnedFrozenValue) -> DiceBuilder {
    let tset = TransitiveSet::from_value(value.value()).unwrap();
    let resolve = DeferredResolve(tset.key().deferred_key().dupe());

    let data: Arc<dyn AnyValue + 'static> = Arc::new(DeferredTransitiveSetData::testing_new(value));
    let any = DeferredValueAnyReady::AnyValue(data);
    dice_builder.mock_and_return(resolve, anyhow::Ok(any).shared_error())
}

#[tokio::test]
async fn test_ensure_artifact_group() -> anyhow::Result<()> {
    let digest_config = DigestConfig::testing_default();

    let set = new_transitive_set(indoc!(
        r#"
            def project(value):
                return value

            TestSet = transitive_set(args_projections = {
                "project": project
            })

            foo = source_artifact("foo", "foo")
            bar = source_artifact("bar", "bar")

            s1 = make_tset(TestSet, value = foo)
            make_tset(TestSet, value = bar, children = [s1])
            "#
    ))?;

    let heap = set.owner();

    let cell_resolver = CellResolver::testing_with_names_and_paths_with_alias(&[
        (
            CellName::testing_new("root"),
            CellRootPathBuf::testing_new("cell-path"),
            HashMap::new(),
        ),
        (
            CellName::testing_new("parent"),
            CellRootPathBuf::testing_new(""),
            HashMap::new(),
        ),
    ]);

    let foo = CellPath::new(
        CellName::testing_new("root"),
        CellRelativePathBuf::unchecked_new("foo/foo".to_owned()),
    );

    let foo_artifact = Artifact::from(SourceArtifact::new(BuckPath::testing_new(
        PackageLabel::testing_parse("root//foo"),
        PackageRelativePathBuf::unchecked_new("foo".to_owned()),
    )));

    let foo_meta = FileMetadata {
        digest: TrackedFileDigest::from_content(b"foo", digest_config.cas_digest_config()),
        is_executable: true,
    };

    let bar_artifact = Artifact::from(SourceArtifact::new(BuckPath::testing_new(
        PackageLabel::testing_parse("root//bar"),
        PackageRelativePathBuf::unchecked_new("bar".to_owned()),
    )));

    let bar = CellPath::new(
        CellName::testing_new("root"),
        CellRelativePathBuf::unchecked_new("bar/bar".to_owned()),
    );

    let bar_meta = FileMetadata {
        digest: TrackedFileDigest::from_content(b"bar", digest_config.cas_digest_config()),
        is_executable: true,
    };

    let files = TestFileOps::new_with_files_metadata(btreemap![
        foo => foo_meta.dupe(),
        bar => bar_meta.dupe(),
    ]);

    let fs = ProjectRootTemp::new()?;

    let mut dice_builder = DiceBuilder::new()
        .mock_and_return(FileOpsKey(), Ok(FileOpsValue(Arc::new(files))))
        .set_data(|data| {
            data.set_testing_io_provider(&fs);
            data.set_digest_config(DigestConfig::testing_default());
        });

    // Register all the sets as deferreds.
    dice_builder = mock_deferred_tset(dice_builder, set.to_owned_frozen_value());

    // This is kinda clowny, but we can't upcast the TransitiveSetGen back to a Value so we
    // have to access Values from their parents.
    for set in set.as_ref().iter(TransitiveSetOrdering::Preorder) {
        for child in set.children.iter() {
            // Safety: We know the entire set came from the same heap.
            let child = unsafe { OwnedFrozenValue::new(heap.dupe(), *child) };
            dice_builder = mock_deferred_tset(dice_builder, child);
        }
    }

    let mut extra = UserComputationData::new();
    extra.set_keep_going(true);

    let mut dice = dice_builder.build(extra)?;
    dice.set_cell_resolver(cell_resolver)?;
    dice.set_buck_out_path(None)?;
    let dice = dice.commit().await;

    let result = dice
        .ensure_artifact_group(&ArtifactGroup::TransitiveSetProjection(
            TransitiveSetProjectionKey {
                key: set.key.dupe(),
                projection: 0,
            },
        ))
        .await?
        .iter()
        .cloned()
        .collect::<Vec<_>>();

    assert_eq!(
        &result,
        &[
            (
                bar_artifact,
                ArtifactValue::file(FileMetadata {
                    digest: bar_meta.digest,
                    is_executable: bar_meta.is_executable,
                })
            ),
            (
                foo_artifact,
                ArtifactValue::file(FileMetadata {
                    digest: foo_meta.digest,
                    is_executable: foo_meta.is_executable,
                })
            ),
        ]
    );

    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.
 */

mod resolve;
mod tests;