返回 CodeWhale
build_script_relocation.rs
根目录 / crates / build-support / tests / build_script_relocation.rs
1 use std::path::{Path, PathBuf};
2 use std::process::{Command, Output};
3 use tempfile::TempDir;
4
5 struct Fixture(TempDir);
6
7 impl Fixture {
8 fn new() -> Self {
9 let fixture = Self(tempfile::tempdir().expect("create build-script fixture"));
10 let support = fixture.0.path().join("support.rs");
11 std::fs::write(&support, include_str!("../src/lib.rs")).unwrap();
12 checked(
13 Command::new(rustc())
14 .args([
15 "--edition=2024",
16 "--crate-name=codewhale_build_support",
17 "--crate-type=rlib",
18 ])
19 .arg(support)
20 .arg("-o")
21 .arg(fixture.library()),
22 );
23 fixture
24 }
25
26 fn library(&self) -> PathBuf {
27 self.0.path().join("libcodewhale_build_support.rlib")
28 }
29
30 fn compile(&self, name: &str, source: &str, manifest: &Path) -> PathBuf {
31 let source_path = self.0.path().join(format!("{name}.rs"));
32 std::fs::write(&source_path, source).unwrap();
33 let executable = self
34 .0
35 .path()
36 .join(format!("{name}{}", std::env::consts::EXE_SUFFIX));
37 checked(
38 Command::new(rustc())
39 .args(["--edition=2024", "--extern"])
40 .arg(format!(
41 "codewhale_build_support={}",
42 self.library().display()
43 ))
44 .arg(source_path)
45 .arg("-o")
46 .arg(&executable)
47 .env("CARGO_MANIFEST_DIR", manifest)
48 .env("CARGO_PKG_VERSION", "1.2.3"),
49 );
50 executable
51 }
52 }
53
54 fn rustc() -> std::ffi::OsString {
55 std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into())
56 }
57
58 fn checked(command: &mut Command) -> Output {
59 let output = command.output().expect("execute build fixture");
60 assert!(
61 output.status.success(),
62 "{}",
63 String::from_utf8_lossy(&output.stderr)
64 );
65 output
66 }
67
68 fn run_script(executable: &Path, manifest: &Path, cwd: &Path) -> Command {
69 let mut command = Command::new(executable);
70 command
71 .current_dir(cwd)
72 .env("CARGO_MANIFEST_DIR", manifest)
73 .env("CARGO_CFG_TARGET_OS", "linux")
74 .env_remove("CODEWHALE_BUILD_SHA")
75 .env_remove("DEEPSEEK_BUILD_SHA")
76 .env_remove("GITHUB_SHA");
77 command
78 }
79
80 #[test]
81 fn cached_build_scripts_classify_the_current_manifest_and_require_it() {
82 let fixture = Fixture::new();
83 for (name, source) in [
84 ("cli", include_str!("../../cli/build.rs")),
85 ("tui", include_str!("../../tui/build.rs")),
86 ] {
87 let original = fixture.0.path().join(format!("{name} original"));
88 let relocated = fixture.0.path().join(format!("{name} relocated"));
89 std::fs::create_dir(&original).unwrap();
90 let executable = fixture.compile(name, source, &original);
91 let initial = checked(&mut run_script(&executable, &original, fixture.0.path()));
92 assert!(
93 String::from_utf8_lossy(&initial.stdout)
94 .contains("cargo:rustc-env=CODEWHALE_BUILD_VERSION=1.2.3 (dev)\n")
95 );
96
97 // Reuse the same executable after the path baked in at compilation is gone.
98 std::fs::rename(&original, &relocated).unwrap();
99 std::fs::write(relocated.join("Cargo.toml.orig"), "packaged source").unwrap();
100 let output = checked(&mut run_script(&executable, &relocated, fixture.0.path()));
101 let directives = String::from_utf8_lossy(&output.stdout);
102 assert!(directives.contains("cargo:rustc-env=CODEWHALE_BUILD_VERSION=1.2.3\n"));
103 assert!(directives.contains("cargo:rerun-if-env-changed=CARGO_MANIFEST_DIR\n"));
104
105 // Do not guess the current directory or fall back to the removed checkout.
106 let missing = run_script(&executable, &relocated, fixture.0.path())
107 .env_remove("CARGO_MANIFEST_DIR")
108 .output()
109 .unwrap();
110 assert!(!missing.status.success());
111 }
112 }
113
114 #[cfg(unix)]
115 #[test]
116 fn macos_helper_compilation_follows_the_relocated_manifest() {
117 use std::os::unix::fs::PermissionsExt;
118
119 let fixture = Fixture::new();
120 let original = fixture.0.path().join("original manifest");
121 let relocated = fixture.0.path().join("relocated manifest");
122 let source = Path::new("plugins/computer-use/src/backends/darwin-accessibility.m");
123 std::fs::create_dir_all(original.join(source).parent().unwrap()).unwrap();
124 std::fs::write(original.join(source), "fixture source").unwrap();
125 let executable = fixture.compile("tui", include_str!("../../tui/build.rs"), &original);
126 std::fs::rename(&original, &relocated).unwrap();
127 let out = fixture.0.path().join("out");
128 let bin = fixture.0.path().join("bin");
129 std::fs::create_dir(&out).unwrap();
130 std::fs::create_dir(&bin).unwrap();
131 // Exercise the real build-script command arguments without requiring a macOS
132 // SDK or accessing a signing identity. A missing source fails as clang did.
133 for (name, script) in [
134 (
135 "xcrun",
136 "#!/bin/sh\nset -eu\nsource=\noutput=\nwhile [ \"$#\" -gt 0 ]; do\ncase \"$1\" in *.m) source=$1 ;; -o) shift; output=$1 ;; esac\nshift\ndone\ntest -f \"$source\"\nprintf %s \"$source\" > \"$output\"\n",
137 ),
138 (
139 "codesign",
140 "#!/bin/sh\nset -eu\nfor last in \"$@\"; do :; done\ntest -s \"$last\"\n",
141 ),
142 ] {
143 let path = bin.join(name);
144 std::fs::write(&path, script).unwrap();
145 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755)).unwrap();
146 }
147 let mut paths = vec![bin];
148 paths.extend(std::env::split_paths(
149 &std::env::var_os("PATH").unwrap_or_default(),
150 ));
151 let output = checked(
152 run_script(&executable, &relocated, fixture.0.path())
153 .env("CARGO_CFG_TARGET_OS", "macos")
154 .env("CARGO_CFG_TARGET_ARCH", "aarch64")
155 .env("OUT_DIR", &out)
156 .env("PATH", std::env::join_paths(paths).unwrap())
157 .env_remove("CODEWHALE_CU_SIGN_IDENTITY"),
158 );
159 assert_eq!(
160 std::fs::read_to_string(out.join("computer-use-accessibility")).unwrap(),
161 relocated.join(source).to_string_lossy()
162 );
163 let directives = String::from_utf8_lossy(&output.stdout);
164 assert!(directives.contains(&format!(
165 "cargo:rerun-if-changed={}\n",
166 relocated.join(source).display()
167 )));
168 assert!(!directives.contains(original.to_string_lossy().as_ref()));
169 }
170
170 lines RUST