/*
 * 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 buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

#[test]
fn test_select_funcs() {
    let mut tester = Tester::new().unwrap();
    tester
        .run_starlark_test(indoc!(
            // This is from //xplat/build_infra/buck_client/test/com/facebook/buck/parser/testdata/select_introspection/defs.bzl
            r#"
def _map_func(value):
    if type(value) == type(""):
        if "TEST" in value:
            return value.replace("TEST", "replaced")
    elif type(value) == type([]):
        return [v for v in value if v != "INVALID"]
    elif type(value) == type({}):
        return {k: v for k, v in value.items() if "win" in v}
    return value

def _test_func(value):
    return "TEST" in value

def _assert_eq(expected, actual):
    if type(expected) == type(select({"DEFAULT": []})):
        result = select_equal_internal(expected, actual)
    else:
        result = expected == actual

    if not result:
        fail("expected %s but got %s" % (expected, actual))

def _test_single_config_str():
    str_select = select({"config/windows:x86_64": "flag_TEST"})

    _assert_eq(
        select({"config/windows:x86_64": "flag_replaced"}),
        select_map(str_select, _map_func),
    )
    _assert_eq(True, select_test(str_select, _test_func))

def _test_single_config_list():
    list_select = select({"config/windows:x86_64": ["flag", "INVALID"]})

    _assert_eq(
        select({"config/windows:x86_64": ["flag"]}),
        select_map(list_select, _map_func),
    )
    _assert_eq(False, select_test(list_select, _test_func))

def _test_single_config_dict():
    dict_select = select({
        "config/windows:x86_64": {"test.h": "windows/test.h", "test_apple.h": "apple/test.h"},
    })

    _assert_eq(
        select({"config/windows:x86_64": {"test.h": "windows/test.h"}}),
        select_map(dict_select, _map_func),
    )
    _assert_eq(False, select_test(dict_select, _test_func))

def _test_multi_config():
    multi_select = select({
        "DEFAULT": ["-DBASE", "TEST"],
        "config//android:base": ["-DANDROID"],
        "config//iphoneos:base": ["INVALID", "-DIPHONE"],
        "config//windows:base": ["TEST"],
    })

    _assert_eq(
        select({
            "DEFAULT": ["-DBASE", "TEST"],
            "config//android:base": ["-DANDROID"],
            "config//iphoneos:base": ["-DIPHONE"],
            "config//windows:base": ["TEST"],
        }),
        select_map(multi_select, _map_func),
    )
    _assert_eq(True, select_test(multi_select, _test_func))

def _test_concatenated_native():
    expr_select = ["INVALID"] + ["TEST"] + select({"config/windows:x86_64": ["-DWINDOWS"]})

    _assert_eq(
        [] + ["TEST"] + select({"config/windows:x86_64": ["-DWINDOWS"]}),
        select_map(expr_select, _map_func),
    )
    _assert_eq(True, select_test(expr_select, _test_func))

def _test_concatenated_nested():
    expr_select = ["TEST"] + select({"config/windows:x86_64": ["-DWINDOWS", "INVALID"]})

    _assert_eq(
        ["TEST"] + select({"config/windows:x86_64": ["-DWINDOWS"]}),
        select_map(expr_select, _map_func),
    )
    _assert_eq(True, select_test(expr_select, _test_func))

def test():
    _test_single_config_str()
    _test_single_config_list()
    _test_single_config_dict()
    _test_multi_config()
    _test_concatenated_native()
    _test_concatenated_nested()
    "#
        ))
        .unwrap();
}


/*
 * 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 buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::dice::cells::SetCellResolver;
use buck2_common::dice::data::testing::SetTestingIoProvider;
use buck2_common::legacy_configs::dice::SetLegacyConfigs;
use buck2_common::legacy_configs::LegacyBuckConfig;
use buck2_common::legacy_configs::LegacyBuckConfigs;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::cell_root_path::CellRootPathBuf;
use buck2_core::cells::name::CellName;
use buck2_core::cells::CellResolver;
use buck2_core::fs::project::ProjectRootTemp;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_core::package::PackageLabel;
use buck2_events::dispatch::EventDispatcher;
use buck2_interpreter::dice::starlark_debug::SetStarlarkDebugger;
use buck2_interpreter::dice::starlark_profiler::SetStarlarkProfilerInstrumentation;
use buck2_interpreter::dice::starlark_profiler::StarlarkProfilerConfiguration;
use buck2_interpreter::dice::starlark_types::SetDisableStarlarkTypes;
use buck2_interpreter::extra::InterpreterHostArchitecture;
use buck2_interpreter::extra::InterpreterHostPlatform;
use buck2_interpreter::load_module::InterpreterCalculation;
use buck2_interpreter_for_build::attrs::attrs_global::register_attrs;
use buck2_interpreter_for_build::interpreter::configuror::BuildInterpreterConfiguror;
use buck2_interpreter_for_build::interpreter::context::SetInterpreterContext;
use buck2_interpreter_for_build::rule::register_rule_function;
use buck2_interpreter_for_build::super_package::defs::register_package_natives;
use buck2_interpreter_for_build::super_package::package_value::register_read_package_value;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use dice::DetectCycles;
use dice::Dice;
use dice::DiceTransaction;
use dice::UserComputationData;
use dupe::Dupe;
use indoc::indoc;

fn empty_configs(resolver: &CellResolver) -> LegacyBuckConfigs {
    let config = resolver
        .cells()
        .map(|(name, _)| (name, LegacyBuckConfig::empty()))
        .collect();

    LegacyBuckConfigs::new(config)
}

pub(crate) fn root_cell() -> CellName {
    CellName::testing_new("root")
}

pub(crate) async fn calculation(fs: &ProjectRootTemp) -> DiceTransaction {
    let mut dice = Dice::builder();
    dice.set(EventDispatcher::null());
    dice.set_testing_io_provider(fs);
    let dice = dice.build(DetectCycles::Enabled);

    let mut per_transaction_data = UserComputationData::new();
    per_transaction_data.data.set(EventDispatcher::null());
    per_transaction_data.set_starlark_debugger_handle(None);
    let mut ctx = dice.updater_with_data(per_transaction_data);

    let resolver = CellResolver::testing_with_name_and_path(
        CellName::testing_new("root"),
        CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("".to_owned())),
    );
    let cell_configs = empty_configs(&resolver);

    ctx.set_cell_resolver(resolver.dupe()).unwrap();
    ctx.set_interpreter_context(
        BuildInterpreterConfiguror::new(
            None,
            InterpreterHostPlatform::Linux,
            InterpreterHostArchitecture::X86_64,
            None,
            false,
            false,
            register_read_package_value,
            register_package_natives,
            |globals| {
                register_rule_defs(globals);
                register_rule_function(globals);
                register_attrs(globals);
            },
            |_| {},
            None,
        )
        .unwrap(),
    )
    .unwrap();
    ctx.set_legacy_configs(cell_configs).unwrap();
    ctx.set_starlark_profiler_instrumentation_override(StarlarkProfilerConfiguration::default())
        .unwrap();
    ctx.set_disable_starlark_types(false).unwrap();
    ctx.commit().await
}

#[tokio::test]
async fn test_eval_import() {
    let fs = ProjectRootTemp::new().unwrap();
    fs.write_file(
        "pkg/two.bzl",
        indoc!(
            r#"
        message = "hello world!"
        "#
        ),
    );

    let ctx = calculation(&fs).await;

    let env = ctx
        .get_loaded_module_from_import_path(&ImportPath::testing_new("root//pkg:two.bzl"))
        .await
        .unwrap();
    assert_eq!(
        "hello world!",
        env.env().get("message").unwrap().unpack_str().unwrap()
    );
}

// TODO: this test require imports extractions
#[tokio::test]
async fn test_eval_import_with_load() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file(
        "imports/one.bzl",
        indoc!(
            r#"
                def concat(*args):
                    s = ""
                    for a in args:
                        s += a
                    return s
            "#
        ),
    );
    fs.write_file(
        "pkg/two.bzl",
        indoc!(
            r#"
                load("//imports:one.bzl", "concat")
                message = concat("hello", " ", "world!")
            "#
        ),
    );

    let ctx = calculation(&fs).await;
    let env = ctx
        .get_loaded_module_from_import_path(&ImportPath::testing_new("root//pkg:two.bzl"))
        .await
        .unwrap();
    assert_eq!(
        "hello world!",
        env.env().get("message").unwrap().unpack_str().unwrap()
    );
}

// TODO: this test require imports extractions
#[tokio::test]
async fn test_eval_build_file() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file(
        "rules.bzl",
        indoc!(
            r#"
                def _impl(ctx):
                    return DefaultInfo()

                export_file = rule(
                    impl = _impl,
                    attrs = {
                        "src": attrs.string(),
                    },
                )

                java_library = rule(
                    impl = _impl,
                    attrs = {
                        "srcs": attrs.list(attrs.string()),
                    },
                )
        "#
        ),
    );

    fs.write_file(
        "imports/one.bzl",
        indoc!(
            r#"
                load("//rules.bzl", "export_file")

                def some_macro(name, **kwargs):
                    export_file(
                        name=name+"-exported",
                        **kwargs
                    )
            "#
        ),
    );
    fs.write_file("pkg/file1.java", "");
    fs.write_file("pkg/file2.java", "");
    fs.write_file(
        "pkg/BUCK",
        indoc!(
            r#"
                load("//imports:one.bzl", "some_macro")
                load("//rules.bzl", "java_library")

                some_macro(
                    name = "invoke_some",
                    src = "some.file",
                )
                java_library(
                    name = "java",
                    srcs = glob(["*.java"]),
                )
            "#
        ),
    );

    let ctx = calculation(&fs).await;

    let package = PackageLabel::testing_parse("root//pkg");
    let eval_result = ctx.get_interpreter_results(package.dupe()).await.unwrap();
    assert_eq!(package, eval_result.package());
    let target_names = eval_result
        .targets()
        .keys()
        .map(|t| t.as_str())
        .collect::<Vec<_>>();

    assert_eq!(vec!["invoke_some-exported", "java"], target_names);
}


/*
 * 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 buck2_common::result::SharedResult;
use buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::rule::register_rule_function;
use indoc::indoc;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;

#[test]
fn cannot_register_target_twice() {
    let content = indoc!(
        r#"
            def _impl(ctx):
                pass
            export_file = rule(impl=_impl, attrs = {})
            def test():
                export_file(name="foo")
                export_file(name="foo")
        "#
    );
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_rule_function);
    let err = tester.run_starlark_test(content).expect_err("should fail");
    assert!(
        err.to_string()
            .contains("Attempted to register target root//some/package:foo twice"),
        "got `{}`",
        err
    );
}

// Dummy module just to make sure that our integration test framework is working...
#[starlark_module]
fn extra_provider_module(builder: &mut GlobalsBuilder) {
    fn add_one(i: i32) -> anyhow::Result<i32> {
        Ok(i + 1)
    }
}

#[test]
fn tester_can_load_extra_modules() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(extra_provider_module);

    tester.run_starlark_test(indoc!(
        r#"
            x = add_one(1)
            def test():
                y = 2
                assert_eq(2, x)
                assert_eq(3, add_one(y))
            "#
    ))?;

    tester.run_starlark_bzl_test(indoc!(
        r#"
            x = add_one(1)
            def test():
                y = 2
                assert_eq(2, x)
                assert_eq(3, add_one(y))
            "#
    ))
}

#[test]
fn tester_can_load_symbols_transitively() -> SharedResult<()> {
    fn new_tester() -> SharedResult<Tester> {
        let mut tester = Tester::new()?;
        tester.add_import(
            &ImportPath::testing_new("root//test:def1.bzl"),
            indoc!(
                r#"
                l = [1,2,3]
                "#
            ),
        )?;
        tester.add_import(
            &ImportPath::testing_new("root//test:def2.bzl"),
            indoc!(
                r#"
                load("//test:def1.bzl", "l")
                l2 = l + [4,5,6]
                "#
            ),
        )?;
        Ok(tester)
    }

    let mut tester = new_tester()?;
    tester.run_starlark_test(indoc!(
        r#"
            load("//test:def2.bzl", "l2")
            def test():
                assert_eq([1,2,3,4,5,6], l2)
            "#
    ))?;

    let mut tester = new_tester()?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
            load("//test:def2.bzl", "l2")
            def test():
                assert_eq([1,2,3,4,5,6], l2)
            "#
    ))
}


/*
 * 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 buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

#[test]
fn test_eval() {
    let mut tester = Tester::new().unwrap();
    tester
        .run_starlark_test(indoc!(
            r#"
            def test():
                assert_eq("some/package", __internal__.package_name())
                assert_eq("@root", __internal__.repository_name())

                assert_eq(package_name(), __internal__.package_name())
                assert_eq(repository_name(), __internal__.repository_name())

                assert_eq(package_name(), get_base_path())

                print("some message")
                print("multiple", "strings")
            "#
        ))
        .unwrap();
}


/*
 * 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 buck2_interpreter_for_build::interpreter::functions::host_info::register_host_info;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

#[test]
fn test_host_info() -> anyhow::Result<()> {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_host_info);
    tester.run_starlark_test(indoc!(
        r#"
            def test():
                assert_eq(True, host_info().os.is_linux)
                assert_eq(False, host_info().os.is_macos)
                assert_eq(False, host_info().os.is_macos)

                assert_eq(True, host_info().arch.is_x86_64)
                assert_eq(False, host_info().arch.is_arm)
                assert_eq(False, host_info().arch.is_mipsel64)

            "#
    ))?;
    Ok(())
}

#[test]
fn test_buck_v2() -> anyhow::Result<()> {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_host_info);
    tester.run_starlark_test(indoc!(
        r#"
            def test():
                assert_eq(True, hasattr(host_info(), "buck2"))
                assert_eq(False, hasattr(host_info(), "buck1"))
        "#
    ))?;
    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 buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::functions::load_symbols::register_load_symbols;
use buck2_interpreter_for_build::interpreter::testing::Tester;

#[test]
fn test_load_symbols() -> anyhow::Result<()> {
    let mut t = Tester::new()?;
    t.additional_globals(register_load_symbols);
    let defines = ImportPath::testing_new("root//pkg:test.bzl");
    t.add_import(
        &defines,
        r#"
y = 2
load_symbols({'x': 1, 'z': 3})
"#,
    )?;
    t.run_starlark_test(
        r#"
load("@root//pkg:test.bzl", "x", "y", "z")
def test():
    assert_eq(x + y + z, 6)"#,
    )?;
    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 host_info;
mod load_symbols;
mod read_config;


/*
 * 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 buck2_interpreter_for_build::interpreter::functions::read_config::register_read_config;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

#[test]
fn test_read_config() -> anyhow::Result<()> {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_read_config);
    tester.run_starlark_test(indoc!(
        r#"
            def test():
                assert_eq("default", read_config("missing_section", "key", "default"))
                assert_eq("default", read_config("section", "missing_key", "default"))
                assert_eq(1, read_config("section", "missing_key", 1))
                assert_eq(None, read_config("section", "missing_key", None))

                assert_eq("value", read_config("section", "key", "default"))
                assert_eq("value", read_config("section", "key"))

                assert_eq("1", read_config("section", "other"))
                assert_eq("hello world!", read_config("section", "multiline"))
                assert_eq("okay", read_config("config", "key"))
            "#
    ))?;
    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 package_function;
mod package_value;


/*
 * 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 buck2_core::fs::project::ProjectRootTemp;
use buck2_core::target::label::TargetLabel;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use buck2_node::visibility::VisibilitySpecification;

use crate::tests::calculation;

const RULES_BZL: &str = r#"
simple = rule(
    impl = lambda ctx: fail(),
    attrs = {},
)
"#;

#[tokio::test]
async fn test_package() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file("rules.bzl", RULES_BZL);
    fs.write_file(
        "juxtaposition/PACKAGE",
        r#"
package(
    visibility = ["//aaa/..."],
    within_view = ["//bbb/..."],
    inherit = True,
)
"#,
    );
    fs.write_file(
        "juxtaposition/BUCK",
        r#"
load("//:rules.bzl", "simple")
simple(name = "a")
"#,
    );

    let ctx = calculation(&fs).await;

    let a = ctx
        .get_target_node(&TargetLabel::testing_parse("root//juxtaposition:a"))
        .await
        .unwrap();

    assert_eq!(
        &VisibilitySpecification::testing_parse(&["root//aaa/..."]),
        a.visibility().unwrap(),
    );
}

#[tokio::test]
async fn test_package_inherit() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file("rules.bzl", RULES_BZL);
    fs.write_file(
        "PACKAGE",
        r#"
package(
    visibility = ["//aaa/..."],
)
"#,
    );
    fs.write_file(
        "juxtaposition/PACKAGE",
        r#"
package(
    visibility = ["//bbb/..."],
    inherit = True,
)
"#,
    );
    fs.write_file(
        "juxtaposition/BUCK",
        r#"
load("//:rules.bzl", "simple")
simple(name = "a")
"#,
    );

    let ctx = calculation(&fs).await;

    let a = ctx
        .get_target_node(&TargetLabel::testing_parse("root//juxtaposition:a"))
        .await
        .unwrap();

    assert_eq!(
        &VisibilitySpecification::testing_parse(&["root//aaa/...", "root//bbb/..."]),
        a.visibility().unwrap(),
    );
}

#[tokio::test]
async fn test_package_not_inherit() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file("rules.bzl", RULES_BZL);
    fs.write_file(
        "PACKAGE",
        r#"
package(
    visibility = ["//aaa/..."],
)
"#,
    );
    fs.write_file(
        "juxtaposition/PACKAGE",
        r#"
package(
    visibility = ["//bbb/..."],
)
"#,
    );
    fs.write_file(
        "juxtaposition/BUCK",
        r#"
load("//:rules.bzl", "simple")
simple(name = "a")
"#,
    );

    let ctx = calculation(&fs).await;

    let a = ctx
        .get_target_node(&TargetLabel::testing_parse("root//juxtaposition:a"))
        .await
        .unwrap();

    assert_eq!(
        &VisibilitySpecification::testing_parse(&["root//bbb/..."]),
        a.visibility().unwrap(),
    );
}


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

//! Tests for `PACKAGE` files.

use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::fs::project::ProjectRootTemp;
use buck2_core::package::PackageLabel;
use buck2_interpreter::starlark_profiler::StarlarkProfilerOrInstrumentation;
use buck2_interpreter_for_build::interpreter::dice_calculation_delegate::HasCalculationDelegate;
use buck2_node::attrs::display::AttrDisplayWithContextExt;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use indoc::indoc;

use crate::tests::calculation;
use crate::tests::root_cell;

const RULES: &str = r#"
rrr = rule(
    impl = lambda ctx: DefaultInfo(),
    attrs = {
        "value": attrs.string(),
    },
)
"#;

#[tokio::test]
async fn test_package_value_same_dir_package_file() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file("rules.bzl", RULES);
    fs.write_file(
        "headphones/PACKAGE",
        "write_package_value('aaa.bbb', 'ccc')",
    );
    fs.write_file(
        "headphones/BUCK",
        indoc!(
            r#"
                load("//:rules.bzl", "rrr")
                rrr(
                    name = "headphones",
                    value = read_package_value("aaa.bbb"),
                )
        "#
        ),
    );

    let ctx = calculation(&fs).await;
    let interpreter = ctx
        .get_interpreter_calculator(root_cell(), BuildFileCell::new(root_cell()))
        .await
        .unwrap();

    let result = interpreter
        .eval_build_file(
            PackageLabel::testing_parse("root//headphones"),
            &mut StarlarkProfilerOrInstrumentation::disabled(),
        )
        .await
        .unwrap();

    let target_nodes: Vec<_> = result.targets().values().collect();
    assert_eq!(1, target_nodes.len());
    let target_node = &target_nodes[0];
    assert_eq!(
        "\"ccc\"",
        target_node
            .attr("value", AttrInspectOptions::DefinedOnly)
            .unwrap()
            .unwrap()
            .as_display_no_ctx()
            .to_string()
    );
}

#[tokio::test]
async fn test_package_value_parent_dir_package_file() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file("rules.bzl", RULES);
    fs.write_file("PACKAGE", "write_package_value('aaa.bbb', 'ccc')");
    fs.write_file(
        "trackpad/BUCK",
        indoc!(
            r#"
                load("//:rules.bzl", "rrr")
                rrr(
                    name = "trackpad",
                    value = read_package_value("aaa.bbb"),
                )
            "#
        ),
    );

    let ctx = calculation(&fs).await;
    let interpreter = ctx
        .get_interpreter_calculator(root_cell(), BuildFileCell::new(root_cell()))
        .await
        .unwrap();

    let result = interpreter
        .eval_build_file(
            PackageLabel::testing_parse("root//trackpad"),
            &mut StarlarkProfilerOrInstrumentation::disabled(),
        )
        .await
        .unwrap();

    let target_nodes: Vec<_> = result.targets().values().collect();
    assert_eq!(1, target_nodes.len());
    let target_node = &target_nodes[0];
    assert_eq!(
        "\"ccc\"",
        target_node
            .attr("value", AttrInspectOptions::DefinedOnly)
            .unwrap()
            .unwrap()
            .as_display_no_ctx()
            .to_string()
    );
}

#[tokio::test]
async fn test_overwrite_package_value_not_allowed_without_overwrite_flag() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file("PACKAGE", "write_package_value('aaa.bbb', 'ccc')");
    fs.write_file("foo/PACKAGE", "write_package_value('aaa.bbb', 'ccc')");
    fs.write_file("foo/BUCK", "");

    let ctx = calculation(&fs).await;
    let interpreter = ctx
        .get_interpreter_calculator(root_cell(), BuildFileCell::new(root_cell()))
        .await
        .unwrap();
    let err = interpreter
        .eval_build_file(
            PackageLabel::testing_parse("root//foo"),
            &mut StarlarkProfilerOrInstrumentation::disabled(),
        )
        .await;
    assert!(
        format!("{:?}", err)
            .contains("key set in parent `PACKAGE` file, and overwrite flag is not set"),
        "err = {:?}",
        err
    );
}

#[tokio::test]
async fn test_overwrite_package_value_with_flag() {
    let fs = ProjectRootTemp::new().unwrap();

    fs.write_file("rules.bzl", RULES);
    fs.write_file("PACKAGE", "write_package_value('aaa.bbb', 'ccc')");
    fs.write_file(
        "foo/PACKAGE",
        "write_package_value('aaa.bbb', 'ddd', overwrite = True)",
    );
    fs.write_file(
        "foo/BUCK",
        indoc!(
            r#"
                load("//:rules.bzl", "rrr")
                rrr(
                    name = "foo",
                    value = read_package_value("aaa.bbb"),
                )
            "#
        ),
    );

    let ctx = calculation(&fs).await;
    let result = ctx
        .get_interpreter_results(PackageLabel::testing_parse("root//foo"))
        .await
        .unwrap();

    let target_nodes: Vec<_> = result.targets().values().collect();
    assert_eq!(1, target_nodes.len());
    let target_node = &target_nodes[0];
    assert_eq!(
        "\"ddd\"",
        target_node
            .attr("value", AttrInspectOptions::DefinedOnly)
            .unwrap()
            .unwrap()
            .as_display_no_ctx()
            .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::env;
use std::io;
use std::os::unix::process::ExitStatusExt;
use std::process::Command;

use anyhow::Context as _;
use buck2_miniperf_proto::MiniperfCounter;
use buck2_miniperf_proto::MiniperfCounters;
use buck2_miniperf_proto::MiniperfOutput;
use perf_event::events::Hardware;
use perf_event::Builder;
use smallvec::SmallVec;

struct Counters {
    user_counter: perf_event::Counter,
    kernel_counter: perf_event::Counter,
}

#[derive(thiserror::Error, Debug)]
#[error("Error at {}: {}", stage, error)]
struct CounterError {
    stage: &'static str,
    error: io::Error,
}

impl Counters {
    fn open() -> Result<Self, CounterError> {
        // NOTE: Kernel is not enabled here: we want to report only userspace cycles.
        let user_counter = Builder::new()
            .kind(Hardware::INSTRUCTIONS)
            .inherit(true)
            .enable_on_exec()
            .build()
            .map_err(|error| CounterError {
                stage: "open user",
                error,
            })?;

        let kernel_counter = Builder::new()
            .kind(Hardware::INSTRUCTIONS)
            .include_kernel()
            .exclude_user()
            .inherit(true)
            .enable_on_exec()
            .build()
            .map_err(|error| CounterError {
                stage: "open user",
                error,
            })?;

        Ok(Self {
            user_counter,
            kernel_counter,
        })
    }

    fn collect(mut self) -> Result<MiniperfCounters, CounterError> {
        let user_value = self
            .user_counter
            .read_count_and_time()
            .map_err(|error| CounterError {
                stage: "collect user",
                error,
            })?;

        let kernel_value =
            self.kernel_counter
                .read_count_and_time()
                .map_err(|error| CounterError {
                    stage: "collect user",
                    error,
                })?;

        Ok(MiniperfCounters {
            user_instructions: MiniperfCounter {
                count: user_value.count,
                time_enabled: user_value.time_enabled,
                time_running: user_value.time_running,
            },
            kernel_instructions: MiniperfCounter {
                count: kernel_value.count,
                time_enabled: kernel_value.time_enabled,
                time_running: kernel_value.time_running,
            },
        })
    }
}

/// First argument is an output path to write output data into. The rest is the command to execute.
pub fn main() -> anyhow::Result<()> {
    let mut args = env::args_os();
    args.next().context("No argv0")?;

    // In an ideal world, we would like this to be a pipe. Unfortunately, we can't do that, because
    // to get a pipe here, we'd have to have it not be CLOEXEC (because we need to exec *this*
    // binary). However, we're spawned by a server that creates many such processes concurrently,
    // so that means CLOEXEC must be set when creating the pipe, then unset between fork and exec.
    // To do this while retaining posix_spawn (which is quite a bit faster than fork + exec), we
    // need to dup the FD (which clears CLOEXEC), but the Rust wrapper around posix_spawn
    // (`Command`) does not expose that.
    let out = args.next().context("No output path")?;

    let counters = Counters::open();

    let status = args.next().context("No process to run").and_then(|bin| {
        Command::new(bin)
            .args(args)
            .status()
            .map_err(anyhow::Error::from)
    });

    let counters = counters.and_then(|c| c.collect());

    let output = MiniperfOutput {
        raw_exit_code: status.map(|s| s.into_raw()).map_err(|e| e.to_string()),
        counters: counters.map_err(|e| e.to_string()),
    };

    // Stack allocate in the happy path.
    let mut buff = SmallVec::<[u8; MiniperfOutput::EXPECTED_SIZE]>::new();

    bincode::serialize_into(&mut buff, &output)
        .with_context(|| format!("Failed to write to `{:?}`", out))?;

    std::fs::write(&out, &buff).with_context(|| format!("Failed to write to `{:?}`", out))?;

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

#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(not(target_os = "linux"), path = "unsupported.rs")]
mod impl_;

fn main() -> anyhow::Result<()> {
    impl_::main()
}


/*
 * 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 fn main() -> anyhow::Result<()> {
    Err(anyhow::anyhow!("Unsupported on this OS"))
}


/*
 * 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)]
#![cfg(target_os = "linux")]
#![feature(exit_status_error)]

use std::process::Command;

use anyhow::Context;
use buck2_miniperf_proto::MiniperfOutput;

#[test]
fn test_miniperf() -> anyhow::Result<()> {
    let temp_dir = tempfile::tempdir()?;
    let path = temp_dir.path().join("out");

    let mut cmd = Command::new(std::env::var_os("MINIPERF").context("$MINIPERF is not set")?);
    cmd.arg(&path);
    cmd.arg(
        std::env::var_os("THREE_BILLION_INSTRUCTIONS")
            .context("$THREE_BILLION_INSTRUCTIONS is not set")?,
    );
    cmd.status()?.exit_ok()?;

    let out = std::fs::read(&path).context("Failed to read")?;
    let out = bincode::deserialize::<MiniperfOutput>(&out).context("Failed to deserialize")?;

    // Check that we're within 5%
    assert!(
        out.counters
            .as_ref()
            .unwrap()
            .user_instructions
            .adjusted_count()
            > 2850000000
    );
    assert!(
        out.counters
            .as_ref()
            .unwrap()
            .user_instructions
            .adjusted_count()
            < 3150000000
    );

    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 dupe::Dupe;

/// NOTE: those structs don't need to maintain wire compatibility, we use them only with matching
/// writer & reader.
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct MiniperfOutput {
    pub raw_exit_code: Result<i32, String>,
    pub counters: Result<MiniperfCounters, String>,
}

#[derive(
    serde::Serialize,
    serde::Deserialize,
    Copy,
    Clone,
    Dupe,
    PartialEq,
    Debug,
    Default
)]
pub struct MiniperfCounters {
    /// Total instructions executed.
    pub user_instructions: MiniperfCounter,
    pub kernel_instructions: MiniperfCounter,
}

impl MiniperfOutput {
    // This is the size we expect this record to take if the command worked out fine.
    pub const EXPECTED_SIZE: usize = 60;
}

/// The fields here come straight out of `perf_event_open`. The count is
#[derive(
    serde::Serialize,
    serde::Deserialize,
    Copy,
    Clone,
    Dupe,
    PartialEq,
    Debug,
    Default
)]
pub struct MiniperfCounter {
    /// Value of the counter.
    pub count: u64,
    /// Time this counter was active, in ns. Equals time_running unless multiplexing happens.
    pub time_enabled: u64,
    /// Time the measured processes were running, in ns.
    pub time_running: u64,
}

impl MiniperfCounter {
    /// Return the count, adjusted for multiplexing.
    pub fn adjusted_count(&self) -> u64 {
        if self.time_enabled == self.time_running {
            return self.count;
        }

        let ratio = self.time_enabled as f64 / self.time_running as f64;
        (self.count as f64 * ratio) as u64
    }
}

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

    #[test]
    fn test_adjusted_count() {
        assert_eq!(
            MiniperfCounter {
                count: 123,
                time_enabled: 100,
                time_running: 100
            }
            .adjusted_count(),
            123
        );

        assert_eq!(
            MiniperfCounter {
                count: 123,
                time_enabled: 100,
                time_running: 50
            }
            .adjusted_count(),
            246
        );
    }

    #[test]
    fn test_expected_size() {
        let max_counter = MiniperfCounter {
            count: u64::MAX,
            time_enabled: u64::MAX,
            time_running: u64::MAX,
        };
        let output = MiniperfOutput {
            raw_exit_code: Ok(i32::MAX),
            counters: Ok(MiniperfCounters {
                user_instructions: max_counter,
                kernel_instructions: max_counter,
            }),
        };

        assert_eq!(
            bincode::serialized_size(&output).unwrap() as usize,
            MiniperfOutput::EXPECTED_SIZE
        );
    }
}


/*
 * 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::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;
use std::hash::Hasher;

use allocative::Allocative;
use cmp_any::PartialEqAny;

/// Untyped version of `starlark::eval::CallStack`.
pub trait StarlarkCallStackImpl: Display + Debug + Send + Sync + 'static {
    fn eq_token(&self) -> PartialEqAny;
    fn hash(&self, hashed: &mut dyn Hasher);
}

impl<S: Display + Debug + Hash + Eq + Send + Any + Sync + Sized + 'static> StarlarkCallStackImpl
    for S
{
    fn eq_token(&self) -> PartialEqAny {
        PartialEqAny::new(self)
    }

    fn hash(&self, mut state: &mut dyn Hasher) {
        self.hash(&mut state);
    }
}

/// `buck2_node` crate does not depend on `starlark`, but need to store Starlark call stack.
#[derive(Debug, Allocative)]
pub struct StarlarkCallStack {
    /// Actually `starlark::eval::CallStack`.
    // We don't care about much call stack size because it is used only when debugging.
    #[allocative(skip)]
    call_stack: Box<dyn StarlarkCallStackImpl>,
}

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

impl PartialEq for StarlarkCallStack {
    fn eq(&self, other: &Self) -> bool {
        self.call_stack.eq_token() == other.call_stack.eq_token()
    }
}

impl Eq for StarlarkCallStack {}

impl Hash for StarlarkCallStack {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.call_stack.hash(state);
    }
}

impl StarlarkCallStack {
    pub fn new(call_stack: impl StarlarkCallStackImpl) -> StarlarkCallStack {
        StarlarkCallStack {
            call_stack: Box::new(call_stack),
        }
    }
}


/*
 * 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::BTreeMap;
use std::collections::BTreeSet;

use buck2_common::pattern::resolve::ResolvedPattern;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::package::PackageLabel;
use buck2_core::pattern::pattern_type::PatternType;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_core::pattern::PackageSpec;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::target::name::TargetName;
use buck2_query::query::syntax::simple::eval::label_indexed::LabelIndexed;
use buck2_query::query::syntax::simple::eval::set::TargetSet;
use dupe::Dupe;
use dupe::IterDupedExt;
use either::Either;
use itertools::Itertools;

use crate::nodes::configured::ConfiguredTargetNode;
use crate::nodes::configured_node_visit_all_deps::configured_node_visit_all_deps;

/// Subset of targets `cquery` command works with.
///
/// Targets are resolved in the universe, and file owners are also resolved in the universe.
pub struct CqueryUniverse {
    targets:
        BTreeMap<PackageLabel, BTreeMap<TargetName, BTreeSet<LabelIndexed<ConfiguredTargetNode>>>>,
}

impl CqueryUniverse {
    pub fn new(
        targets: BTreeMap<
            PackageLabel,
            BTreeMap<TargetName, BTreeSet<LabelIndexed<ConfiguredTargetNode>>>,
        >,
    ) -> CqueryUniverse {
        CqueryUniverse { targets }
    }

    pub async fn build(
        universe: &TargetSet<ConfiguredTargetNode>,
    ) -> anyhow::Result<CqueryUniverse> {
        let mut targets: BTreeMap<
            PackageLabel,
            BTreeMap<TargetName, BTreeSet<LabelIndexed<ConfiguredTargetNode>>>,
        > = BTreeMap::new();

        configured_node_visit_all_deps(universe.iter().duped(), |target| {
            let label = target.label();
            let package_targets: &mut _ = targets
                .entry(label.pkg().dupe())
                .or_insert_with(BTreeMap::new);

            let nodes: &mut _ = match package_targets.get_mut(label.name()) {
                Some(v) => v,
                None => package_targets
                    .entry(label.name().to_owned())
                    .or_insert_with(BTreeSet::new),
            };

            nodes.insert(LabelIndexed(target));

            Ok(())
        })
        .await?;

        Ok(CqueryUniverse::new(targets))
    }

    pub fn get(
        &self,
        resolved_pattern: &ResolvedPattern<TargetPatternExtra>,
    ) -> TargetSet<ConfiguredTargetNode> {
        let mut targets = TargetSet::new();
        for (package, spec) in &resolved_pattern.specs {
            targets.extend(
                self.get_from_package(package.dupe(), spec)
                    .map(|(node, TargetPatternExtra)| node),
            );
        }
        targets
    }

    pub fn get_provider_labels<P: PatternType>(
        &self,
        resolved_pattern: &ResolvedPattern<P>,
    ) -> Vec<ConfiguredProvidersLabel> {
        let mut targets = Vec::new();
        for (package, spec) in &resolved_pattern.specs {
            targets.extend(
                self.get_from_package(package.dupe(), spec)
                    .map(|(node, extra)| {
                        ConfiguredProvidersLabel::new(node.label().dupe(), extra.into_providers())
                    }),
            );
        }
        targets
    }

    fn get_from_package<'a, P: PatternType>(
        &'a self,
        package: PackageLabel,
        spec: &'a PackageSpec<P>,
    ) -> impl Iterator<Item = (&'a ConfiguredTargetNode, P)> + 'a {
        self.targets
            .get(&package)
            .into_iter()
            .flat_map(move |package_universe| match spec {
                PackageSpec::Targets(names) => {
                    Either::Left(names.iter().flat_map(|(name, extra)| {
                        package_universe.get(name).into_iter().flat_map(|nodes| {
                            nodes.iter().filter_map(|node| {
                                if extra.matches_cfg(node.0.label().cfg()) {
                                    Some((&node.0, extra.clone()))
                                } else {
                                    None
                                }
                            })
                        })
                    }))
                }
                PackageSpec::All => Either::Right(
                    package_universe
                        .values()
                        .flatten()
                        .map(|node| (&node.0, P::default())),
                ),
            })
    }

    pub fn owners(&self, path: &CellPath) -> Vec<ConfiguredTargetNode> {
        let mut nodes = Vec::new();

        // We lookup in all ancestors because we still have package boundary violations.
        // But we ignore symlinks: path may be a symlink target
        // for a target living in another directory.
        // This is another reason to not support symlinks in buck.
        for package in path.ancestors() {
            // Here we allocate package for possibly non-existent package,
            // violating `Package` assumptions.
            // This does not leave this function, so we are probably fine.
            // We do it because the map is by `Package`,
            // and `BTreeMap` does not allow lookup by equivalent key.
            let package = PackageLabel::from_cell_path(package);
            let package_data = match self.targets.get(&package) {
                None => continue,
                Some(package_data) => package_data,
            };
            for node in package_data.values().flatten() {
                if node.0.inputs().contains(path) {
                    nodes.push(node.0.dupe());
                }
            }
        }
        nodes
    }
}

#[cfg(test)]
mod tests {
    use buck2_common::pattern::resolve::ResolvedPattern;
    use buck2_core::configuration::bound_label::BoundConfigurationLabel;
    use buck2_core::configuration::data::ConfigurationData;
    use buck2_core::configuration::hash::ConfigurationHash;
    use buck2_core::package::PackageLabel;
    use buck2_core::pattern::pattern_type::ConfigurationPredicate;
    use buck2_core::pattern::pattern_type::ConfiguredProvidersPatternExtra;
    use buck2_core::pattern::PackageSpec;
    use buck2_core::provider::label::ConfiguredProvidersLabel;
    use buck2_core::provider::label::NonDefaultProvidersName;
    use buck2_core::provider::label::ProviderName;
    use buck2_core::provider::label::ProvidersName;
    use buck2_core::target::label::ConfiguredTargetLabel;
    use buck2_core::target::name::TargetName;
    use buck2_query::__derive_refs::indexmap::IndexMap;
    use buck2_query::query::syntax::simple::eval::set::TargetSet;
    use dupe::Dupe;

    use crate::configured_universe::CqueryUniverse;
    use crate::nodes::configured::ConfiguredTargetNode;

    #[tokio::test]
    async fn test_get_from_package_by_configured_provider_pattern() {
        fn providers_name() -> ProvidersName {
            ProvidersName::NonDefault(Box::new(NonDefaultProvidersName::Named(Box::new([
                ProviderName::new("P".to_owned()).unwrap(),
            ]))))
        }

        fn resolved_pattern(
            cfg: ConfigurationPredicate,
        ) -> ResolvedPattern<ConfiguredProvidersPatternExtra> {
            ResolvedPattern {
                specs: IndexMap::from_iter([(
                    PackageLabel::testing_parse("foo//bar"),
                    PackageSpec::Targets(Vec::from_iter([(
                        TargetName::unchecked_new("baz"),
                        ConfiguredProvidersPatternExtra {
                            providers: providers_name(),
                            cfg,
                        },
                    )])),
                )]),
            }
        }

        let target_label =
            ConfiguredTargetLabel::testing_parse("foo//bar:baz", ConfigurationData::testing_new());
        let universe =
            CqueryUniverse::build(&TargetSet::from_iter([ConfiguredTargetNode::testing_new(
                target_label.dupe(),
                "idris_library",
            )]))
            .await
            .unwrap();
        let provider_label = ConfiguredProvidersLabel::new(target_label, providers_name());

        // Any configuration.
        assert_eq!(
            Vec::from_iter([provider_label.clone()]),
            universe.get_provider_labels(&resolved_pattern(ConfigurationPredicate::Any))
        );
        // Configuration label.
        assert_eq!(
            Vec::from_iter([provider_label.clone()]),
            universe.get_provider_labels(&resolved_pattern(ConfigurationPredicate::Bound(
                BoundConfigurationLabel::new(
                    ConfigurationData::testing_new().label().unwrap().to_owned()
                )
                .unwrap(),
                None,
            )))
        );
        // Configuration label with hash.
        assert_eq!(
            Vec::from_iter([provider_label]),
            universe.get_provider_labels(&resolved_pattern(ConfigurationPredicate::Bound(
                BoundConfigurationLabel::new(
                    ConfigurationData::testing_new().label().unwrap().to_owned()
                )
                .unwrap(),
                Some(ConfigurationData::testing_new().output_hash().clone()),
            )))
        );
        // Configuration label with wrong hash.
        assert_eq!(
            Vec::<ConfiguredProvidersLabel>::new(),
            universe.get_provider_labels(&resolved_pattern(ConfigurationPredicate::Bound(
                BoundConfigurationLabel::new(
                    ConfigurationData::testing_new().label().unwrap().to_owned()
                )
                .unwrap(),
                Some(ConfigurationHash::new(17)),
            )))
        );
    }
}


/*
 * 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(box_patterns)]
// Plugins
#![cfg_attr(feature = "gazebo_lint", feature(plugin))]
#![cfg_attr(feature = "gazebo_lint", allow(deprecated))] // :(
#![cfg_attr(feature = "gazebo_lint", plugin(gazebo_lint))]

pub mod attrs;
pub mod call_stack;
pub mod configuration;
pub mod configured_universe;
pub mod load_patterns;
pub mod nodes;
pub mod package;
pub mod provider_id_set;
pub mod query;
pub mod rule;
pub mod rule_type;
pub mod target_calculation;
pub mod visibility;


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

use buck2_common::dice::cells::HasCellResolver;
use buck2_common::dice::file_ops::HasFileOps;
use buck2_common::pattern::package_roots::collect_package_roots;
use buck2_common::pattern::resolve::ResolvedPattern;
use buck2_common::result::SharedResult;
use buck2_common::result::ToSharedResultExt;
use buck2_core::package::PackageLabel;
use buck2_core::pattern::pattern_type::PatternType;
use buck2_core::pattern::ParsedPattern;
use buck2_core::target::name::TargetName;
use buck2_events::dispatch::console_message;
use dice::DiceComputations;
use dupe::Dupe;
use fnv::FnvBuildHasher;
use futures::future::BoxFuture;
use futures::stream::FuturesUnordered;
use futures::FutureExt;
use futures::Stream;
use futures::StreamExt;
use itertools::Itertools;

use crate::nodes::eval_result::EvaluationResult;
use crate::nodes::frontend::TargetGraphCalculation;
use crate::nodes::unconfigured::TargetNode;

#[derive(Debug, thiserror::Error)]
enum BuildErrors {
    #[error("Did not find package with name `{0}`.")]
    MissingPackage(PackageLabel),
}

async fn resolve_patterns_and_load_buildfiles<'c, T: PatternType>(
    ctx: &'c DiceComputations,
    parsed_patterns: Vec<ParsedPattern<T>>,
) -> anyhow::Result<(
    ResolvedPattern<T>,
    impl Stream<Item = (PackageLabel, anyhow::Result<Arc<EvaluationResult>>)> + 'c,
)> {
    let mut spec = ResolvedPattern::<T>::new();
    let mut recursive_packages = Vec::new();

    struct Builder<'c> {
        ctx: &'c DiceComputations,
        already_loading: HashSet<PackageLabel, FnvBuildHasher>,
        load_package_futs:
            FuturesUnordered<BoxFuture<'c, (PackageLabel, anyhow::Result<Arc<EvaluationResult>>)>>,
    }

    let mut builder = Builder {
        ctx,
        load_package_futs: FuturesUnordered::new(),
        already_loading: HashSet::default(),
    };

    impl<'c> Builder<'c> {
        fn load_package(&mut self, package: PackageLabel) {
            if !self.already_loading.insert(package.dupe()) {
                return;
            }

            // it's important that this is not async and the temporary spawn happens when the function is called as we don't immediately start polling these.
            self.load_package_futs.push(
                self.ctx
                    .get_interpreter_results(package.dupe())
                    .map(|res| (package, res))
                    .boxed(),
            )
        }
    }

    for pattern in parsed_patterns {
        match pattern {
            ParsedPattern::Target(package, target_name, extra) => {
                spec.add_target(package.dupe(), target_name, extra);
                builder.load_package(package.dupe());
            }
            ParsedPattern::Package(package) => {
                spec.add_package(package.dupe());
                builder.load_package(package.dupe());
            }
            ParsedPattern::Recursive(package) => {
                recursive_packages.push(package);
            }
        }
    }

    let file_ops = ctx.file_ops();
    let cell_resolver = ctx.get_cell_resolver().await?;

    collect_package_roots(&file_ops, &cell_resolver, recursive_packages, |package| {
        let package = package?;
        spec.add_package(package.dupe());
        builder.load_package(package);
        anyhow::Ok(())
    })
    .await?;

    Ok((spec, builder.load_package_futs))
}

pub struct LoadedPatterns<T: PatternType> {
    results: BTreeMap<PackageLabel, SharedResult<BTreeMap<(TargetName, T), TargetNode>>>,
}

impl<T: PatternType> LoadedPatterns<T> {
    pub fn iter(
        &self,
    ) -> impl Iterator<
        Item = (
            PackageLabel,
            &SharedResult<BTreeMap<(TargetName, T), TargetNode>>,
        ),
    > {
        self.results.iter().map(|(k, v)| (k.dupe(), v))
    }

    // Implementing IntoIterator requires explicitly specifying the iterator type, which seems higher cost than the value of doing it.
    #[allow(clippy::should_implement_trait)]
    pub fn into_iter(
        self,
    ) -> impl Iterator<
        Item = (
            PackageLabel,
            SharedResult<BTreeMap<(TargetName, T), TargetNode>>,
        ),
    > {
        self.results.into_iter()
    }

    pub fn iter_loaded_targets(&self) -> impl Iterator<Item = SharedResult<&TargetNode>> {
        self.results
            .values()
            .map(|result| match result {
                Ok(label_to_node) => Ok(label_to_node.values()),
                Err(e) => Err(e.dupe()),
            })
            .flatten_ok()
    }

    pub fn iter_loaded_targets_by_package(
        &self,
    ) -> impl Iterator<Item = (PackageLabel, anyhow::Result<Vec<TargetNode>>)> + '_ {
        self.results.iter().map(|(package, result)| {
            let targets = result
                .as_ref()
                .map(|label_to_node| label_to_node.values().map(|t| t.dupe()).collect::<Vec<_>>())
                .map_err(|e| anyhow::Error::new(e.dupe()));
            (package.dupe(), targets)
        })
    }
}

/// Option to skip missing targets instead of failing.
/// This is not a good option to use long term, but we need it now to deal with our legacy setup.
#[derive(Clone, Dupe, Copy, Eq, PartialEq, Debug)]
pub enum MissingTargetBehavior {
    /// Skip missing targets (but error on missing packages or evaluation errors).
    /// When skipping, we emit a warning to the console.
    Warn,
    Fail,
}

impl MissingTargetBehavior {
    pub fn from_skip(skip: bool) -> MissingTargetBehavior {
        if skip {
            MissingTargetBehavior::Warn
        } else {
            MissingTargetBehavior::Fail
        }
    }
}

/// Finds all the requested targets in `spec` from a map of loaded targets in `load_result`.
fn apply_spec<T: PatternType>(
    spec: ResolvedPattern<T>,
    load_results: BTreeMap<PackageLabel, SharedResult<Arc<EvaluationResult>>>,
    skip_missing_targets: MissingTargetBehavior,
) -> anyhow::Result<LoadedPatterns<T>> {
    let mut all_targets: BTreeMap<_, SharedResult<BTreeMap<(TargetName, T), _>>> = BTreeMap::new();
    for (pkg, pkg_spec) in spec.specs.into_iter() {
        let result = match load_results.get(&pkg) {
            Some(r) => r,
            None => return Err(anyhow::anyhow!(BuildErrors::MissingPackage(pkg))),
        };
        match result {
            Ok(res) => {
                let (label_to_node, missing) = res.apply_spec(pkg_spec);
                if let Some(missing) = missing {
                    match skip_missing_targets {
                        MissingTargetBehavior::Fail => return Err(missing.into_error()),
                        MissingTargetBehavior::Warn => {
                            console_message(missing.missing_targets_warning())
                        }
                    }
                };
                all_targets.insert(pkg, Ok(label_to_node));
            }
            Err(e) => {
                all_targets.insert(pkg, Err(e.dupe()));
            }
        }
    }

    Ok(LoadedPatterns {
        results: all_targets,
    })
}

pub async fn load_patterns<T: PatternType>(
    ctx: &DiceComputations,
    parsed_patterns: Vec<ParsedPattern<T>>,
    skip_missing_targets: MissingTargetBehavior,
) -> anyhow::Result<LoadedPatterns<T>> {
    let (spec, mut load_package_futs) =
        resolve_patterns_and_load_buildfiles(ctx, parsed_patterns).await?;

    let mut results: BTreeMap<PackageLabel, SharedResult<Arc<EvaluationResult>>> = BTreeMap::new();
    while let Some((pkg, load_res)) = load_package_futs.next().await {
        results.insert(pkg, load_res.shared_error());
    }

    apply_spec(spec, results, skip_missing_targets)
}


/*
 * 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 buck2_core::build_file_path::BuildFilePath;

/// Package-specific data for `TargetNode`.
///
/// (Note this has nothing to do with `PACKAGE` files which are not implemented
/// at the moment of writing.)
#[derive(Debug, Hash, Allocative, Eq, PartialEq)]
pub struct Package {
    /// The build file which defined this target, e.g. `fbcode//foo/bar/TARGETS`
    pub buildfile_path: Arc<BuildFilePath>,
    /// The oncall attribute, if set
    pub oncall: Option<Arc<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::slice;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::provider::id::ProviderId;
use dupe::Dupe;

#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub struct ProviderIdSet(Option<Arc<Vec<Arc<ProviderId>>>>);

impl ProviderIdSet {
    pub const EMPTY: ProviderIdSet = ProviderIdSet(None);

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.providers().is_empty()
    }

    #[inline]
    pub fn providers(&self) -> &[Arc<ProviderId>] {
        match &self.0 {
            None => &[],
            Some(providers) => providers,
        }
    }
}

impl From<Vec<Arc<ProviderId>>> for ProviderIdSet {
    #[inline]
    fn from(mut v: Vec<Arc<ProviderId>>) -> Self {
        if v.is_empty() {
            ProviderIdSet::EMPTY
        } else {
            v.sort_unstable();
            ProviderIdSet(Some(Arc::new(v)))
        }
    }
}

impl<'a> IntoIterator for &'a ProviderIdSet {
    type Item = &'a Arc<ProviderId>;
    type IntoIter = slice::Iter<'a, Arc<ProviderId>>;

    #[inline]
    fn into_iter(self) -> Self::IntoIter {
        self.providers().iter()
    }
}


/*
 * 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 buck2_core::configuration::transition::id::TransitionId;

use crate::attrs::spec::AttributeSpec;
use crate::nodes::unconfigured::RuleKind;
use crate::rule_type::RuleType;

/// Common rule data needed in `TargetNode`.
#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct Rule {
    /// The attribute spec. This holds the attribute name -> index mapping and the default values
    /// (for those attributes without explicit values).
    pub attributes: AttributeSpec,
    /// The 'type', used to find the implementation function from the graph
    pub rule_type: RuleType,
    /// The kind of rule, e.g. configuration or otherwise.
    pub rule_kind: RuleKind,
    /// Transition to apply to the target.
    pub cfg: Option<Arc<TransitionId>>,
}


/*
 * 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 buck2_core::bzl::ImportPath;
use dupe::Dupe;

/// The identifier used to find the implementation function for this rule. Should point at the output of `rule()`
#[derive(Debug, Clone, derive_more::Display, Eq, PartialEq, Hash, Allocative)]
#[display(fmt = "{}:{}", import_path, name)]
pub struct StarlarkRuleType {
    /// The cell, package, and file that contains the output of `rule()`
    pub import_path: ImportPath,
    /// The name of the symbol that is bound to the output of `rule()`, e.g. `cxx_binary`
    pub name: String,
}

#[derive(
    Debug,
    Clone,
    Dupe,
    derive_more::Display,
    Eq,
    PartialEq,
    Hash,
    Allocative
)]
pub enum RuleType {
    Starlark(Arc<StarlarkRuleType>),
    #[display(fmt = "forward")]
    Forward,
}

impl RuleType {
    pub fn name(&self) -> &str {
        match self {
            RuleType::Starlark(rule_type) => rule_type.name.as_str(),
            RuleType::Forward => "forward",
        }
    }
}

#[cfg(test)]
mod tests {
    use buck2_core::bzl::ImportPath;

    use crate::rule_type::StarlarkRuleType;

    #[test]
    fn function_id_has_useful_string() {
        let import_path = ImportPath::testing_new("root//some/subdir:foo.bzl");
        let name = "foo_binary".to_owned();

        assert_eq!(
            "root//some/subdir/foo.bzl:foo_binary",
            &StarlarkRuleType { import_path, name }.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 async_trait::async_trait;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::late_binding::LateBinding;
use dice::DiceComputations;

#[async_trait]
pub trait ConfiguredTargetCalculationImpl: Send + Sync + 'static {
    async fn get_configured_target(
        &self,
        ctx: &DiceComputations,
        target: &TargetLabel,
        global_target_platform: Option<&TargetLabel>,
    ) -> anyhow::Result<ConfiguredTargetLabel>;
}

pub static CONFIGURED_TARGET_CALCULATION: LateBinding<
    &'static dyn ConfiguredTargetCalculationImpl,
> = LateBinding::new("CONFIGURED_TARGET_CALCULATION");

#[async_trait]
pub trait ConfiguredTargetCalculation {
    /// Returns the Configuration for an unconfigured TargetLabel or ProvidersLabel.
    ///
    /// This performs "target platform resolution" on the provided target and returns the configured
    /// result.
    ///
    /// Normally, a TargetLabel and ProvidersLabel would
    /// get its Configuration based on the context it's being requested in (i.e configuration is
    /// passed down from higher nodes). For top-level requested things, though, we will have an
    /// unconfigured (or "lightly"-configured) thing and the Configuration will be determined as
    /// a mix of the global Configuration, the target's `default_target_platform` and
    /// (potentially) self-transitions on that node.
    async fn get_configured_target(
        &self,
        target: &TargetLabel,
        global_target_platform: Option<&TargetLabel>,
    ) -> anyhow::Result<ConfiguredTargetLabel>;

    async fn get_configured_provider_label(
        &self,
        target: &ProvidersLabel,
        global_target_platform: Option<&TargetLabel>,
    ) -> anyhow::Result<ConfiguredProvidersLabel>;

    async fn get_default_configured_target(
        &self,
        target: &TargetLabel,
    ) -> anyhow::Result<ConfiguredTargetLabel>;
}

#[async_trait]
impl ConfiguredTargetCalculation for DiceComputations {
    async fn get_configured_target(
        &self,
        target: &TargetLabel,
        global_target_platform: Option<&TargetLabel>,
    ) -> anyhow::Result<ConfiguredTargetLabel> {
        CONFIGURED_TARGET_CALCULATION
            .get()?
            .get_configured_target(self, target, global_target_platform)
            .await
    }

    async fn get_configured_provider_label(
        &self,
        target: &ProvidersLabel,
        global_target_platform: Option<&TargetLabel>,
    ) -> anyhow::Result<ConfiguredProvidersLabel> {
        let configured_target_label = CONFIGURED_TARGET_CALCULATION
            .get()?
            .get_configured_target(self, target.target(), global_target_platform)
            .await?;
        Ok(ConfiguredProvidersLabel::new(
            configured_target_label,
            target.name().clone(),
        ))
    }

    async fn get_default_configured_target(
        &self,
        target: &TargetLabel,
    ) -> anyhow::Result<ConfiguredTargetLabel> {
        CONFIGURED_TARGET_CALCULATION
            .get()?
            .get_configured_target(self, target, None)
            .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.
 */

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

use allocative::Allocative;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_core::pattern::ParsedPattern;
use buck2_core::target::label::TargetLabel;
use buck2_util::arc_str::ThinArcSlice;
use dupe::Dupe;
use gazebo::prelude::SliceExt;
use thiserror::Error;

use crate::attrs::attr_type::any_matches::AnyMatches;

#[derive(Debug, Error)]
pub enum VisibilityError {
    #[error(
        "`{0}` is not visible to `{1}` (run `buck2 uquery --output-attribute visibility {0}` to check the visibility)"
    )]
    NotVisibleTo(TargetLabel, TargetLabel),
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative, derive_more::Display)]
pub struct VisibilityPattern(pub ParsedPattern<TargetPatternExtra>);

impl VisibilityPattern {
    pub const PUBLIC: &'static str = "PUBLIC";

    pub fn testing_new(pattern: &str) -> VisibilityPattern {
        VisibilityPattern(ParsedPattern::testing_parse(pattern))
    }
}

#[derive(derive_more::Display)]
#[display(fmt = "\"{}\"", _0)]
struct VisibilityPatternQuoted<'a>(&'a VisibilityPattern);

#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub enum VisibilityPatternList {
    Public,
    List(ThinArcSlice<VisibilityPattern>),
}

impl VisibilityPatternList {
    fn is_empty(&self) -> bool {
        match self {
            VisibilityPatternList::Public => false,
            VisibilityPatternList::List(patterns) => patterns.is_empty(),
        }
    }

    fn to_json(&self) -> serde_json::Value {
        let list = match self {
            VisibilityPatternList::Public => vec![serde_json::Value::String(
                VisibilityPattern::PUBLIC.to_owned(),
            )],
            VisibilityPatternList::List(patterns) => {
                patterns.map(|p| serde_json::Value::String(p.to_string()))
            }
        };
        serde_json::Value::Array(list)
    }

    fn extend_with(&self, other: &VisibilityPatternList) -> VisibilityPatternList {
        match (self, other) {
            (VisibilityPatternList::Public, _) | (_, VisibilityPatternList::Public) => {
                VisibilityPatternList::Public
            }
            (VisibilityPatternList::List(this), VisibilityPatternList::List(other)) => {
                VisibilityPatternList::List(this.iter().chain(other).cloned().collect())
            }
        }
    }

    fn testing_parse(patterns: &[&str]) -> VisibilityPatternList {
        if patterns.contains(&VisibilityPattern::PUBLIC) {
            VisibilityPatternList::Public
        } else {
            VisibilityPatternList::List(
                patterns
                    .iter()
                    .map(|p| VisibilityPattern::testing_new(p))
                    .collect(),
            )
        }
    }

    pub fn matches_target(&self, target: &TargetLabel) -> bool {
        match self {
            VisibilityPatternList::Public => true,
            VisibilityPatternList::List(patterns) => {
                for pattern in patterns {
                    if pattern.0.matches(target) {
                        return true;
                    }
                }
                false
            }
        }
    }
}

impl Display for VisibilityPatternList {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            VisibilityPatternList::Public => write!(f, "[\"{}\"]", VisibilityPattern::PUBLIC),
            VisibilityPatternList::List(patterns) => display_container::fmt_container(
                f,
                "[",
                "]",
                patterns.iter().map(VisibilityPatternQuoted),
            ),
        }
    }
}

impl AnyMatches for VisibilityPatternList {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        match self {
            VisibilityPatternList::Public => filter(VisibilityPattern::PUBLIC),
            VisibilityPatternList::List(patterns) => {
                for p in patterns {
                    if filter(&p.to_string())? {
                        return Ok(true);
                    }
                }
                Ok(false)
            }
        }
    }
}

/// Represents the visibility spec of a target. Note that targets in the same package will ignore the
/// visibility spec of each other.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub struct VisibilitySpecification(pub VisibilityPatternList);

impl Default for VisibilitySpecification {
    fn default() -> Self {
        Self::DEFAULT
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub struct WithinViewSpecification(pub VisibilityPatternList);

impl Default for WithinViewSpecification {
    fn default() -> Self {
        Self::PUBLIC
    }
}

impl VisibilitySpecification {
    pub const DEFAULT: VisibilitySpecification =
        VisibilitySpecification(VisibilityPatternList::List(ThinArcSlice::empty()));

    pub(crate) fn to_json(&self) -> serde_json::Value {
        self.0.to_json()
    }

    pub fn extend_with(&self, other: &VisibilitySpecification) -> VisibilitySpecification {
        VisibilitySpecification(self.0.extend_with(&other.0))
    }

    pub fn testing_parse(patterns: &[&str]) -> VisibilitySpecification {
        VisibilitySpecification(VisibilityPatternList::testing_parse(patterns))
    }
}

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

impl WithinViewSpecification {
    pub const PUBLIC: WithinViewSpecification =
        WithinViewSpecification(VisibilityPatternList::Public);

    pub fn extend_with(&self, other: &WithinViewSpecification) -> WithinViewSpecification {
        WithinViewSpecification(self.0.extend_with(&other.0))
    }

    pub fn to_json(&self) -> serde_json::Value {
        self.0.to_json()
    }
}

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

impl AnyMatches for VisibilitySpecification {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        self.0.any_matches(filter)
    }
}

impl AnyMatches for WithinViewSpecification {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        self.0.any_matches(filter)
    }
}

pub struct VisibilityWithinViewBuilder {
    cap: usize,
    seen_public: bool,
    patterns: Option<Vec<VisibilityPattern>>,
}

impl VisibilityWithinViewBuilder {
    pub fn with_capacity(cap: usize) -> VisibilityWithinViewBuilder {
        VisibilityWithinViewBuilder {
            cap,
            seen_public: false,
            patterns: None,
        }
    }

    pub fn add_public(&mut self) {
        self.seen_public = true;
    }

    pub fn add(&mut self, pattern: VisibilityPattern) {
        if !self.seen_public {
            self.patterns
                .get_or_insert_with(|| Vec::with_capacity(self.cap))
                .push(pattern);
        }
    }

    fn build_list(self) -> VisibilityPatternList {
        if self.seen_public {
            VisibilityPatternList::Public
        } else {
            VisibilityPatternList::List(ThinArcSlice::from_iter(self.patterns.unwrap_or_default()))
        }
    }

    pub fn build_visibility(self) -> VisibilitySpecification {
        VisibilitySpecification(self.build_list())
    }

    pub fn build_within_view(self) -> WithinViewSpecification {
        let list = self.build_list();
        if list.is_empty() {
            WithinViewSpecification::PUBLIC
        } else {
            WithinViewSpecification(list)
        }
    }
}


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

use allocative::Allocative;

use crate::attrs::attr_type::AttrType;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::display::AttrDisplayWithContextExt;

#[derive(Clone, Debug, Eq, PartialEq, Hash, Allocative)]
enum AttributeDefault {
    No,
    Yes(Arc<CoercedAttr>),
    DefaultOnly(Arc<CoercedAttr>),
}

/// Starlark compatible container for results from e.g. `attrs.string()`
#[derive(Clone, Debug, Eq, PartialEq, Hash, Allocative)]
pub struct Attribute {
    /// The default value. If None, the value is not optional and must be provided by the user
    default: AttributeDefault,
    /// Documentation for what the attribute actually means
    doc: String,
    /// The coercer to take this parameter's value from Starlark value -> an
    /// internal representation
    coercer: AttrType,
}

impl Attribute {
    pub fn new(default: Option<Arc<CoercedAttr>>, doc: &str, coercer: AttrType) -> Self {
        Attribute {
            default: match default {
                Some(x) => AttributeDefault::Yes(x),
                None => AttributeDefault::No,
            },
            doc: doc.to_owned(),
            coercer,
        }
    }

    pub fn new_default_only(default: Arc<CoercedAttr>, doc: &str, coercer: AttrType) -> Self {
        Attribute {
            default: AttributeDefault::DefaultOnly(default),
            doc: doc.to_owned(),
            coercer,
        }
    }

    pub fn coercer(&self) -> &AttrType {
        &self.coercer
    }

    pub fn is_default_only(&self) -> bool {
        matches!(self.default, AttributeDefault::DefaultOnly(_))
    }

    pub fn default(&self) -> Option<&Arc<CoercedAttr>> {
        match &self.default {
            AttributeDefault::Yes(x) => Some(x),
            AttributeDefault::DefaultOnly(x) => Some(x),
            AttributeDefault::No => None,
        }
    }

    pub fn doc(&self) -> &str {
        &self.doc
    }
}

impl Display for Attribute {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.coercer.fmt_with_default(
            f,
            self.default()
                .map(|x| x.as_display_no_ctx().to_string())
                .as_deref(),
        )
    }
}

/// Attribute which may be either a custom value supplied by the user, or missing/None to indicate use the default.
#[derive(Eq, PartialEq)]
pub enum CoercedValue {
    Custom(CoercedAttr),
    Default,
}