//! Tests for --artifact-dir flag.

use std::env;
use std::fs;
use std::path::Path;

use crate::prelude::*;
use cargo_test_support::sleep_ms;
use cargo_test_support::str;
use cargo_test_support::{basic_manifest, project};

#[cargo_test]
fn binary_with_debug() {
    let p = project()
        .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
        .build();

    p.cargo("build -Z unstable-options --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .enable_mac_dsym()
        .run();
    check_dir_contents(
        &p.root().join("out"),
        &["foo"],
        &["foo", "foo.dSYM"],
        &["foo.exe", "foo.pdb"],
        &["foo.exe"],
    );
}

#[cargo_test]
fn static_library_with_debug() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                authors = []

                [lib]
                crate-type = ["staticlib"]
            "#,
        )
        .file(
            "src/lib.rs",
            r#"
                #[no_mangle]
                pub extern "C" fn foo() { println!("Hello, World!") }
            "#,
        )
        .build();

    p.cargo("build -Z unstable-options --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .run();
    check_dir_contents(
        &p.root().join("out"),
        &["libfoo.a"],
        &["libfoo.a"],
        &["foo.lib"],
        &["libfoo.a"],
    );
}

#[cargo_test]
fn dynamic_library_with_debug() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                authors = []

                [lib]
                crate-type = ["cdylib"]
            "#,
        )
        .file(
            "src/lib.rs",
            r#"
                #[no_mangle]
                pub extern "C" fn foo() { println!("Hello, World!") }
            "#,
        )
        .build();

    p.cargo("build -Z unstable-options --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .enable_mac_dsym()
        .run();
    check_dir_contents(
        &p.root().join("out"),
        &["libfoo.so"],
        &["libfoo.dylib", "libfoo.dylib.dSYM"],
        &["foo.dll", "foo.dll.exp", "foo.dll.lib", "foo.pdb"],
        &["foo.dll", "libfoo.dll.a"],
    );
}

#[cargo_test]
fn rlib_with_debug() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                authors = []

                [lib]
                crate-type = ["rlib"]
            "#,
        )
        .file(
            "src/lib.rs",
            r#"
                pub fn foo() { println!("Hello, World!") }
            "#,
        )
        .build();

    p.cargo("build -Z unstable-options --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .run();
    check_dir_contents(
        &p.root().join("out"),
        &["libfoo.rlib"],
        &["libfoo.rlib"],
        &["libfoo.rlib"],
        &["libfoo.rlib"],
    );
}

#[cargo_test]
fn include_only_the_binary_from_the_current_package() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                authors = []

                [workspace]

                [dependencies]
                utils = { path = "./utils" }
            "#,
        )
        .file("src/lib.rs", "extern crate utils;")
        .file(
            "src/main.rs",
            r#"
                extern crate foo;
                extern crate utils;
                fn main() {
                    println!("Hello, World!")
                }
            "#,
        )
        .file("utils/Cargo.toml", &basic_manifest("utils", "0.0.1"))
        .file("utils/src/lib.rs", "")
        .build();

    p.cargo("build -Z unstable-options --bin foo --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .enable_mac_dsym()
        .run();
    check_dir_contents(
        &p.root().join("out"),
        &["foo"],
        &["foo", "foo.dSYM"],
        &["foo.exe", "foo.pdb"],
        &["foo.exe"],
    );
}

#[cargo_test]
fn artifact_dir_is_a_file() {
    let p = project()
        .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
        .file("out", "")
        .build();

    p.cargo("build -Z unstable-options --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .with_status(101)
        .with_stderr_data(str![[r#"
[COMPILING] foo v0.0.1 ([ROOT]/foo)
[ERROR] failed to create directory `[ROOT]/foo/out`

Caused by:
...

"#]])
        .run();
}

#[cargo_test]
fn replaces_artifacts() {
    let p = project()
        .file("src/main.rs", r#"fn main() { println!("foo") }"#)
        .build();

    p.cargo("build -Z unstable-options --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .run();
    p.process(
        &p.root()
            .join(&format!("out/foo{}", env::consts::EXE_SUFFIX)),
    )
    .with_stdout_data(str![[r#"
foo

"#]])
    .run();

    sleep_ms(1000);
    p.change_file("src/main.rs", r#"fn main() { println!("bar") }"#);

    p.cargo("build -Z unstable-options --artifact-dir out")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .run();
    p.process(
        &p.root()
            .join(&format!("out/foo{}", env::consts::EXE_SUFFIX)),
    )
    .with_stdout_data(str![[r#"
bar

"#]])
    .run();
}

#[cargo_test]
fn avoid_build_scripts() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [workspace]
            members = ["a", "b"]
            "#,
        )
        .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
        .file("a/src/main.rs", "fn main() {}")
        .file("a/build.rs", r#"fn main() { println!("hello-build-a"); }"#)
        .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
        .file("b/src/main.rs", "fn main() {}")
        .file("b/build.rs", r#"fn main() { println!("hello-build-b"); }"#)
        .build();

    p.cargo("build -Z unstable-options --artifact-dir out -vv")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .enable_mac_dsym()
        .with_stdout_data(
            str![[r#"
[a 0.0.1] hello-build-a
[b 0.0.1] hello-build-b

"#]]
            .unordered(),
        )
        .run();
    check_dir_contents(
        &p.root().join("out"),
        &["a", "b"],
        &["a", "a.dSYM", "b", "b.dSYM"],
        &["a.exe", "a.pdb", "b.exe", "b.pdb"],
        &["a.exe", "b.exe"],
    );
}

#[cargo_test]
fn cargo_build_artifact_dir() {
    let p = project()
        .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
        .file(
            ".cargo/config.toml",
            r#"
            [build]
            artifact-dir = "out"
            "#,
        )
        .build();

    p.cargo("build -Z unstable-options")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .enable_mac_dsym()
        .run();
    check_dir_contents(
        &p.root().join("out"),
        &["foo"],
        &["foo", "foo.dSYM"],
        &["foo.exe", "foo.pdb"],
        &["foo.exe"],
    );
}

#[cargo_test]
fn unsupported_short_artifact_dir_flag() {
    let p = project()
        .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
        .build();

    p.cargo("build -Z unstable-options -O")
        .masquerade_as_nightly_cargo(&["artifact-dir"])
        .with_stderr_data(str![[r#"
[ERROR] unexpected argument '-O' found

  tip: a similar argument exists: '--artifact-dir'

Usage: cargo[EXE] build [OPTIONS]

For more information, try '--help'.

"#]])
        .with_status(1)
        .run();
}

#[cargo_test]
fn removed_out_dir_flag() {
    let p = project()
        .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
        .build();

    p.cargo("build -Z unstable-options --out-dir out")
        .masquerade_as_nightly_cargo(&["out-dir"])
        .with_status(1)
        .enable_mac_dsym()
        .with_stderr_data(str![[r#"
[ERROR] unexpected argument '--out-dir' found

  tip: a similar argument exists: '--artifact-dir'

Usage: cargo[EXE] build [OPTIONS]

For more information, try '--help'.

"#]])
        .run();
}

#[cargo_test]
fn artifact_dir_rejected_on_stable() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    p.cargo("build --artifact-dir out")
        .with_status(101)
        .with_stderr_data(str![[r#"[ERROR] the `--artifact-dir` flag is unstable, and only available on the nightly channel of Cargo, but this is the `stable` channel
See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information about Rust release channels.
See https://github.com/rust-lang/cargo/issues/6790 for more information about the `--artifact-dir` flag.

"#]])
        .run();
}

fn check_dir_contents(
    artifact_dir: &Path,
    expected_linux: &[&str],
    expected_mac: &[&str],
    expected_win_msvc: &[&str],
    expected_win_gnu: &[&str],
) {
    let expected = if cfg!(target_os = "windows") {
        if cfg!(target_env = "msvc") {
            expected_win_msvc
        } else {
            expected_win_gnu
        }
    } else if cfg!(target_os = "macos") {
        expected_mac
    } else {
        expected_linux
    };

    let actual = list_dir(artifact_dir);
    let mut expected = expected.iter().map(|s| s.to_string()).collect::<Vec<_>>();
    expected.sort_unstable();
    assert_eq!(actual, expected);
}

fn list_dir(dir: &Path) -> Vec<String> {
    let mut res = Vec::new();
    for entry in fs::read_dir(dir).unwrap() {
        let entry = entry.unwrap();
        res.push(entry.file_name().into_string().unwrap());
    }
    res.sort_unstable();
    res
}
