cargo와 패키지 구조 문서의 검증 코드다.
//! 02 cargo와 패키지 구조: cargo가 넣어 주는 환경 변수, cargo new가 만드는 파일, Cargo.lock,
//! dev와 release 프로필의 차이(debug_assertions, overflow-checks), 기능(feature) 켜기를 확인한다.
//! 의존성: 없음 (표준 라이브러리만). 임시 폴더에 의존성 없는 패키지를 만들어 cargo로 빌드한다(인터넷 필요 없음).
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/02_cargo-packages_verify.rs"
use std::path::{Path, PathBuf};
use std::process::Command;
/// 임시 패키지 폴더에서 cargo를 실행하고 (성공 여부, stdout, stderr)를 돌려준다.
fn cargo(dir: &Path, target: &Path, args: &[&str]) -> (bool, String, String) {
let out = Command::new("cargo")
.args(args)
.current_dir(dir)
.env("CARGO_TARGET_DIR", target)
.env("CARGO_TERM_COLOR", "never")
.env_remove("RUSTFLAGS")
.output()
.expect("cargo 실행");
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
const MAIN_RS: &str = r#"use std::hint::black_box;
fn main() {
println!("debug_assertions={}", cfg!(debug_assertions));
println!("fancy={}", cfg!(feature = "fancy"));
let x: u8 = black_box(255);
println!("255+1={}", x + 1);
}
"#;
fn main() {
// 1. cargo가 컴파일할 때 넣어 주는 환경 변수. 이 파일은 harness 패키지(vault-rust 0.1.0)로 빌드된다.
assert_eq!(env!("CARGO_PKG_NAME"), "vault-rust");
assert_eq!(env!("CARGO_PKG_VERSION"), "0.1.0");
// Cargo.toml에 description이 없으면 빈 문자열이다.
assert_eq!(env!("CARGO_PKG_DESCRIPTION"), "");
println!(
"CARGO_PKG_NAME={} CARGO_PKG_VERSION={}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
);
// rs_check는 cargo run(dev 프로필)으로 실행하므로 debug_assertions가 켜져 있다.
const { assert!(cfg!(debug_assertions)) };
println!("이 파일: debug_assertions={}", cfg!(debug_assertions));
// 2. cargo new가 만드는 것.
let root: PathBuf = std::env::temp_dir().join(format!("vr_cargo_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
let target = root.join("target");
let (ok, _, err) = cargo(&root, &target, &["new", "--vcs", "none", "hello"]);
assert!(ok, "{err}");
let pkg = root.join("hello");
let manifest = std::fs::read_to_string(pkg.join("Cargo.toml")).unwrap();
println!("--- cargo new가 만든 Cargo.toml ---\n{manifest}---");
assert!(manifest.contains("name = \"hello\""));
assert!(manifest.contains("version = \"0.1.0\""));
assert!(manifest.contains("edition = \"2024\""));
assert!(manifest.contains("[dependencies]"));
assert!(pkg.join("src/main.rs").exists());
assert!(
!pkg.join("Cargo.lock").exists(),
"빌드 전에는 Cargo.lock이 없다"
);
// 3. 기능(feature)과 release 프로필 설정을 덧붙인다.
let manifest = manifest + "\n[features]\nfancy = []\n";
std::fs::write(pkg.join("Cargo.toml"), &manifest).unwrap();
std::fs::write(pkg.join("src/main.rs"), MAIN_RS).unwrap();
// 4. dev 프로필: debug_assertions 켜짐, 정수 넘침은 panic.
let (ok, out, err) = cargo(&pkg, &target, &["run", "-q"]);
println!("cargo run -> ok={ok} {}", out.replace('\n', " "));
assert!(!ok);
assert!(out.contains("debug_assertions=true"));
assert!(out.contains("fancy=false"));
assert!(err.contains("attempt to add with overflow"), "{err}");
assert!(
pkg.join("Cargo.lock").exists(),
"첫 빌드 뒤 Cargo.lock이 생긴다"
);
// 5. release 프로필: debug_assertions 꺼짐, 넘침은 감아 돌아 0.
let (ok, out, err) = cargo(&pkg, &target, &["run", "-q", "--release"]);
println!("cargo run --release -> ok={ok} {}", out.replace('\n', " "));
assert!(ok, "{err}");
assert!(out.contains("debug_assertions=false"));
assert!(out.contains("255+1=0"));
// 6. 기능 켜기: --features fancy.
let (ok, out, err) = cargo(
&pkg,
&target,
&["run", "-q", "--release", "--features", "fancy"],
);
println!("--features fancy -> ok={ok} {}", out.replace('\n', " "));
assert!(ok, "{err}");
assert!(out.contains("fancy=true"));
// 7. release 프로필에서도 넘침 검사를 켤 수 있다.
let manifest = manifest + "\n[profile.release]\noverflow-checks = true\n";
std::fs::write(pkg.join("Cargo.toml"), &manifest).unwrap();
let (ok, out, err) = cargo(&pkg, &target, &["run", "-q", "--release"]);
println!(
"release + overflow-checks = true -> ok={ok} {}",
out.replace('\n', " ")
);
assert!(!ok);
assert!(out.contains("debug_assertions=false"));
assert!(err.contains("attempt to add with overflow"), "{err}");
// 8. cargo check는 실행 파일을 만들지 않는다.
let (ok, _, err) = cargo(&pkg, &root.join("target-check"), &["check", "-q"]);
assert!(ok, "{err}");
assert!(!root.join("target-check/debug/hello").exists());
println!("cargo check -> 실행 파일 없음");
// 9. Cargo.toml에 없는 기능 이름(오타)은 unexpected_cfgs 경고로 알려 준다.
std::fs::write(
pkg.join("src/main.rs"),
"fn main() {\n println!(\"{}\", cfg!(feature = \"fanci\"));\n}\n",
)
.unwrap();
let (ok, _, err) = cargo(&pkg, &root.join("target-check"), &["check"]);
assert!(ok, "{err}");
assert!(
err.contains("unexpected `cfg` condition value: `fanci`"),
"{err}"
);
println!("기능 이름 오타 -> warning: unexpected `cfg` condition value");
let _ = std::fs::remove_dir_all(&root);
println!("ALL CHECKS PASSED");
}