mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 13:08:29 +00:00
- Added support for registry-based dependencies in the dependencies section. - Updated the package section to include a project type field. - Introduced a new CLI command `dcr setup` to load and display registry configurations. - Implemented a new module for handling registry operations, including loading and resolving packages from registries. - Enhanced the build process to handle library packaging, including copying headers and libraries to the appropriate target directories. - Updated the CLI to support building library packages and added tests for library package builds. - Improved error handling and output messages during dependency resolution and package fetching.
346 lines
12 KiB
Rust
346 lines
12 KiB
Rust
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
use std::sync::Once;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|
static BUILD_ONCE: Once = Once::new();
|
|
|
|
fn bin_path() -> PathBuf {
|
|
if let Ok(exe) = std::env::var("CARGO_BIN_EXE_dcr") {
|
|
return PathBuf::from(exe);
|
|
}
|
|
ensure_bin_built();
|
|
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
path.push("target");
|
|
path.push("debug");
|
|
path.push(format!("dcr{}", std::env::consts::EXE_SUFFIX));
|
|
path
|
|
}
|
|
|
|
fn unique_sandbox_dir(prefix: &str) -> PathBuf {
|
|
let pid = std::process::id();
|
|
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
|
|
let now = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_nanos();
|
|
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
path.push("sandbox");
|
|
path.push("cli-tests");
|
|
path.push(format!("dcr_{prefix}_{pid}_{n}_{now}"));
|
|
std::fs::create_dir_all(&path).expect("failed to create temp dir");
|
|
path
|
|
}
|
|
|
|
fn run_dcr(args: &[&str], cwd: &Path) -> std::process::Output {
|
|
run_dcr_env(args, cwd, &[])
|
|
}
|
|
|
|
fn run_dcr_env(args: &[&str], cwd: &Path, envs: &[(&str, &str)]) -> std::process::Output {
|
|
let mut cmd = Command::new(bin_path());
|
|
cmd.args(args).current_dir(cwd);
|
|
for (k, v) in envs {
|
|
cmd.env(k, v);
|
|
}
|
|
cmd.output().expect("failed to run dcr")
|
|
}
|
|
|
|
fn ensure_bin_built() {
|
|
BUILD_ONCE.call_once(|| {
|
|
let status = Command::new("cargo")
|
|
.arg("build")
|
|
.current_dir(env!("CARGO_MANIFEST_DIR"))
|
|
.status()
|
|
.expect("failed to run cargo build");
|
|
assert!(status.success(), "cargo build failed");
|
|
});
|
|
}
|
|
|
|
fn available_compiler() -> Option<&'static str> {
|
|
for candidate in ["gcc", "clang", "cc"] {
|
|
let ok = Command::new(candidate)
|
|
.arg("--version")
|
|
.output()
|
|
.map(|o| o.status.success())
|
|
.unwrap_or(false);
|
|
if ok {
|
|
return Some(candidate);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
#[test]
|
|
fn help_and_version_work() {
|
|
let dir = unique_sandbox_dir("help");
|
|
let out = run_dcr(&["--help"], &dir);
|
|
assert!(out.status.success(), "--help should succeed");
|
|
|
|
let out = run_dcr(&["--version"], &dir);
|
|
assert!(out.status.success(), "--version should succeed");
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("dcr"), "version output should mention dcr");
|
|
}
|
|
|
|
#[test]
|
|
fn new_creates_project_layout() {
|
|
let dir = unique_sandbox_dir("new");
|
|
let out = run_dcr(&["new", "hello"], &dir);
|
|
assert!(out.status.success(), "dcr new should succeed");
|
|
|
|
let project_dir = dir.join("hello");
|
|
assert!(project_dir.is_dir(), "project dir should exist");
|
|
assert!(project_dir.join("dcr.toml").is_file(), "dcr.toml missing");
|
|
assert!(
|
|
project_dir.join("src").join("main.c").is_file(),
|
|
"src/main.c missing"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn init_and_clean_remove_target() {
|
|
let dir = unique_sandbox_dir("init");
|
|
let out = run_dcr(&["init"], &dir);
|
|
assert!(out.status.success(), "dcr init should succeed");
|
|
|
|
let target_debug = dir.join("target").join("debug");
|
|
std::fs::create_dir_all(&target_debug).expect("failed to create target/debug");
|
|
std::fs::write(target_debug.join("dummy.o"), "x").expect("failed to write dummy file");
|
|
|
|
let out = run_dcr(&["clean"], &dir);
|
|
assert!(out.status.success(), "dcr clean should succeed");
|
|
assert!(!dir.join("target").exists(), "target should be removed");
|
|
}
|
|
|
|
#[test]
|
|
fn build_run_clean_flags_normal_project() {
|
|
let Some(compiler) = available_compiler() else {
|
|
eprintln!("no compiler found; skipping build/run test");
|
|
return;
|
|
};
|
|
|
|
let dir = unique_sandbox_dir("normal");
|
|
let out = run_dcr(&["init"], &dir);
|
|
assert!(out.status.success(), "dcr init should succeed");
|
|
|
|
let envs = [("DCR_COMPILER", compiler)];
|
|
let out = run_dcr_env(&["build"], &dir, &envs);
|
|
assert!(out.status.success(), "dcr build should succeed");
|
|
|
|
let out = run_dcr_env(&["build", "--release"], &dir, &envs);
|
|
assert!(out.status.success(), "dcr build --release should succeed");
|
|
|
|
let out = run_dcr_env(&["run"], &dir, &envs);
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("Running"), "dcr run should start");
|
|
|
|
let out = run_dcr_env(&["clean", "--release"], &dir, &envs);
|
|
assert!(out.status.success(), "dcr clean --release should succeed");
|
|
let target_dir = "target/x86_64-unknown-linux-gnu".to_string();
|
|
assert!(
|
|
!dir.join(&target_dir).join("release").exists(),
|
|
"target/x86_64-unknown-linux-gnu/release should be removed"
|
|
);
|
|
assert!(
|
|
dir.join(&target_dir).join("debug").is_dir(),
|
|
"target/x86_64-unknown-linux-gnu/debug should remain"
|
|
);
|
|
|
|
let out = run_dcr_env(&["clean"], &dir, &envs);
|
|
assert!(out.status.success(), "dcr clean should succeed");
|
|
assert!(!dir.join("target").exists(), "target should be removed");
|
|
}
|
|
|
|
#[test]
|
|
fn workspace_build_and_clean_all() {
|
|
let Some(compiler) = available_compiler() else {
|
|
eprintln!("no compiler found; skipping workspace test");
|
|
return;
|
|
};
|
|
|
|
let root = unique_sandbox_dir("workspace");
|
|
let out = run_dcr(&["init"], &root);
|
|
assert!(out.status.success(), "root init should succeed");
|
|
|
|
let members = [
|
|
("userspace", &[][..]),
|
|
("core", &["userspace"][..]),
|
|
("kernel", &["core"][..]),
|
|
];
|
|
for (name, _) in &members {
|
|
let member_dir = root.join("src").join(name);
|
|
std::fs::create_dir_all(&member_dir).expect("failed to create member dir");
|
|
let out = run_dcr(&["init"], &member_dir);
|
|
assert!(out.status.success(), "member init should succeed");
|
|
}
|
|
|
|
let workspace_toml = "[package]\nname = \"ws-root\"\nversion = \"0.1.0\"\n\n[build]\nlanguage = \"c\"\nstandard = \"c11\"\ncompiler = \"clang\"\nkind = \"bin\"\n\n[workspace]\nuserspace = { path = \"src/userspace\", deps = [] }\ncore = { path = \"src/core\", deps = [\"userspace\"] }\nkernel = { path = \"src/kernel\", deps = [\"core\"] }\n\n[dependencies]\n";
|
|
std::fs::write(root.join("dcr.toml"), workspace_toml).expect("failed to write root dcr.toml");
|
|
|
|
let envs = [("DCR_COMPILER", compiler)];
|
|
let out = run_dcr_env(&["build"], &root, &envs);
|
|
assert!(out.status.success(), "workspace build should succeed");
|
|
|
|
let out = run_dcr_env(&["build"], &root, &envs);
|
|
assert!(out.status.success(), "workspace build should succeed");
|
|
|
|
let out = run_dcr_env(&["build", "--release"], &root, &envs);
|
|
assert!(
|
|
out.status.success(),
|
|
"workspace build --release should succeed"
|
|
);
|
|
|
|
let out = run_dcr_env(&["clean", "--release", "--all"], &root, &envs);
|
|
assert!(
|
|
out.status.success(),
|
|
"workspace clean --all --release should succeed"
|
|
);
|
|
|
|
for (name, _) in &members {
|
|
let member = root.join("src").join(name);
|
|
let target_dir = "target/x86_64-unknown-linux-gnu";
|
|
assert!(
|
|
!member.join(target_dir).join("release").exists(),
|
|
"member target/x86_64-unknown-linux-gnu/release should be removed"
|
|
);
|
|
assert!(
|
|
member.join(target_dir).join("debug").exists(),
|
|
"member target/x86_64-unknown-linux-gnu/debug should remain"
|
|
);
|
|
}
|
|
let target_dir = "target/x86_64-unknown-linux-gnu";
|
|
assert!(
|
|
!root.join(target_dir).join("release").exists(),
|
|
"root target/x86_64-unknown-linux-gnu/release should be removed"
|
|
);
|
|
assert!(
|
|
root.join(target_dir).join("debug").exists(),
|
|
"root target/x86_64-unknown-linux-gnu/debug should remain"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dcr_add_dependencies() {
|
|
let dir = unique_sandbox_dir("add_dep");
|
|
let out = run_dcr(&["init"], &dir);
|
|
assert!(out.status.success(), "dcr init should succeed");
|
|
|
|
// Test path: prefix
|
|
let out = run_dcr(&["add", "mylib", "path:./libs/mylib"], &dir);
|
|
assert!(out.status.success(), "dcr add path should succeed");
|
|
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
|
assert!(
|
|
toml.contains("mylib = \"./libs/mylib\""),
|
|
"path dep not found in toml"
|
|
);
|
|
|
|
// Test github: prefix
|
|
let out = run_dcr(&["add", "gh_lib", "github:user/repo"], &dir);
|
|
assert!(out.status.success(), "dcr add github should succeed");
|
|
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
|
assert!(
|
|
toml.contains("gh_lib = { git = \"https://github.com/user/repo\" }"),
|
|
"github dep not found in toml"
|
|
);
|
|
|
|
// Test git: prefix (generic)
|
|
let out = run_dcr(&["add", "custom_git", "git:host.com/user/repo"], &dir);
|
|
assert!(out.status.success(), "dcr add custom git should succeed");
|
|
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
|
assert!(
|
|
toml.contains("custom_git = { git = \"https://host.com/user/repo\" }"),
|
|
"custom git dep not found in toml"
|
|
);
|
|
|
|
// Test git: prefix (github default)
|
|
let out = run_dcr(&["add", "git_short", "git:user/repo"], &dir);
|
|
assert!(out.status.success(), "dcr add git short should succeed");
|
|
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
|
assert!(
|
|
toml.contains("git_short = { git = \"https://github.com/user/repo\" }"),
|
|
"git short dep not found in toml"
|
|
);
|
|
|
|
// Test flags (branch)
|
|
let out = run_dcr(
|
|
&["add", "branch_lib", "github:user/repo", "--branch", "dev"],
|
|
&dir,
|
|
);
|
|
assert!(out.status.success(), "dcr add with branch should succeed");
|
|
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
|
assert!(
|
|
toml.contains("branch_lib = { git = \"https://github.com/user/repo\", branch = \"dev\" }"),
|
|
"branch lib not found in toml"
|
|
);
|
|
|
|
// Test failure on no prefix
|
|
let out = run_dcr(&["add", "fail_lib", "user/repo"], &dir);
|
|
assert!(!out.status.success(), "dcr add without prefix should fail");
|
|
}
|
|
|
|
#[test]
|
|
fn dcr_builds_lib_package() {
|
|
let Some(compiler) = available_compiler() else {
|
|
eprintln!("no compiler found; skipping lib package test");
|
|
return;
|
|
};
|
|
|
|
let dir = unique_sandbox_dir("lib_package");
|
|
let out = run_dcr(&["init"], &dir);
|
|
assert!(out.status.success(), "dcr init should succeed");
|
|
|
|
let toml = std::fs::read_to_string(dir.join("dcr.toml")).unwrap();
|
|
let updated_toml = toml
|
|
.replace("kind = \"bin\"", "kind = \"staticlib\"")
|
|
.replace("type = \"none\"", "type = \"lib\"");
|
|
std::fs::write(dir.join("dcr.toml"), updated_toml).expect("failed to write toml");
|
|
|
|
std::fs::write(dir.join("src").join("my_lib.h"), "void hello();")
|
|
.expect("failed to write header");
|
|
|
|
let envs = [("DCR_COMPILER", compiler)];
|
|
let out = run_dcr_env(&["build"], &dir, &envs);
|
|
if !out.status.success() {
|
|
eprintln!("stdout: {}", String::from_utf8_lossy(&out.stdout));
|
|
eprintln!("stderr: {}", String::from_utf8_lossy(&out.stderr));
|
|
}
|
|
assert!(out.status.success(), "dcr build should succeed");
|
|
|
|
let target_dir = dir.join("target");
|
|
assert!(
|
|
target_dir.join("include").join("my_lib.h").is_file(),
|
|
"include/my_lib.h missing"
|
|
);
|
|
assert!(target_dir.join("lib").exists(), "lib directory missing");
|
|
}
|
|
|
|
#[test]
|
|
fn dcr_test_runs_without_sandbox_dependency() {
|
|
let Some(compiler) = available_compiler() else {
|
|
eprintln!("no compiler found; skipping dcr test integration");
|
|
return;
|
|
};
|
|
|
|
let dir = unique_sandbox_dir("dcr_test_independent");
|
|
let out = run_dcr(&["init"], &dir);
|
|
assert!(out.status.success(), "dcr init should succeed");
|
|
|
|
let envs = [("DCR_CC", compiler)];
|
|
let out_init = run_dcr_env(&["test", "--init"], &dir, &envs);
|
|
assert!(out_init.status.success(), "dcr test --init should succeed");
|
|
|
|
let out = run_dcr_env(&["test"], &dir, &envs);
|
|
assert!(out.status.success(), "dcr test should succeed");
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
assert!(stdout.contains("TOTAL: 1"), "TOTAL summary line missing");
|
|
assert!(
|
|
stdout.contains("PASS: 1"),
|
|
"PASS summary line missing\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
}
|