diff --git a/src/util/common.rs b/src/util/common.rs index 9fdb4ff..16cd08c 100644 --- a/src/util/common.rs +++ b/src/util/common.rs @@ -4,9 +4,9 @@ use anyhow::{anyhow, bail, ensure, Context, Result}; use once_cell::sync::Lazy; use std::{ env, - fs::{canonicalize, set_permissions, Permissions}, + fs::canonicalize, io::Write, - os::unix::{ffi::OsStrExt, fs::PermissionsExt}, + os::unix::ffi::OsStrExt, path::Path, process::{Command, Output, Stdio}, str::Utf8Error, @@ -29,8 +29,8 @@ const DEFAULT_PROFILE: &str = r#"(version 1) /// Executes `command`, forwards its output to stdout and stderr, and optionally checks whether /// `command` succeeded. /// -/// Called by [`unpack_and_exec`]. Since this file is included in the wrapper build script's -/// src/main.rs file, `exec_forwarding_output` should appear here, alongside [`unpack_and_exec`]. +/// Called by [`exec_sibling`]. Since this file is included in the wrapper build script's +/// src/main.rs file, `exec_forwarding_output` should appear here, alongside [`exec_sibling`]. /// /// # Errors /// @@ -63,15 +63,16 @@ pub fn exec_forwarding_output(mut command: Command, failure_is_error: bool) -> R /// Essentially the body of the wrapper build script's `main` function. Not called by `build-wrap` /// itself. #[allow(dead_code)] -fn unpack_and_exec(bytes: &[u8]) -> Result<()> { - let (mut file, temp_path) = - tempfile::NamedTempFile::new().map(tempfile::NamedTempFile::into_parts)?; +fn exec_sibling(sibling_path_as_str: &str) -> Result<()> { + let current_exe = env::current_exe()?; - file.write_all(bytes)?; + let parent = current_exe + .parent() + .ok_or_else(|| anyhow!("failed to get `current_exe` parent"))?; - drop(file); + let sibling_path = Path::new(sibling_path_as_str); - set_permissions(&temp_path, Permissions::from_mode(0o755))?; + assert!(sibling_path.starts_with(parent)); // smoelius: The `BUILD_WRAP_CMD` used is the one set when set when the wrapper build script is // compiled, not when it is run. So if the wrapped build script prints the following and the @@ -81,7 +82,7 @@ fn unpack_and_exec(bytes: &[u8]) -> Result<()> { // cargo:rerun-if-env-changed=BUILD_WRAP_CMD // ``` // They will cause the wrapped build script to be rerun, however. - let expanded_args = split_and_expand(&temp_path)?; + let expanded_args = split_and_expand(sibling_path)?; let allow_enabled = enabled("BUILD_WRAP_ALLOW"); @@ -93,12 +94,10 @@ fn unpack_and_exec(bytes: &[u8]) -> Result<()> { // `BUILD_WRAP_ALLOW` is enabled. if !output.status.success() { debug_assert!(allow_enabled); - let command = Command::new(&temp_path); + let command = Command::new(sibling_path); let _: Output = exec_forwarding_output(command, true)?; } - drop(temp_path); - Ok(()) } diff --git a/src/wrapper.rs b/src/wrapper.rs index 58e179e..bffdcc6 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -1,14 +1,24 @@ use crate::util::ToUtf8; -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::{ - fs::{create_dir, write}, + fs::{create_dir, rename, write}, path::Path, }; -use tempfile::{tempdir, TempDir}; +use tempfile::{tempdir, NamedTempFile, TempDir}; #[allow(clippy::disallowed_methods)] pub fn package(build_script_path: &Path) -> Result { - let build_script_path_as_str = build_script_path.to_utf8()?; + let parent = build_script_path + .parent() + .ok_or_else(|| anyhow!("failed to get `build_script_path` parent"))?; + + let temp_file = NamedTempFile::new_in(parent)?; + + let (_file, sibling_path) = temp_file.keep()?; + + rename(build_script_path, &sibling_path)?; + + let sibling_path_as_str = sibling_path.to_utf8()?; let tempdir = tempdir()?; @@ -16,7 +26,7 @@ pub fn package(build_script_path: &Path) -> Result { create_dir(tempdir.path().join("src"))?; write( tempdir.path().join("src/main.rs"), - main_rs(build_script_path_as_str), + main_rs(sibling_path_as_str), )?; Ok(tempdir) @@ -39,19 +49,19 @@ tempfile = "3.10" /// A wrapper build script's src/main.rs consists of the following: /// /// - the contents of util/common.rs (included verbatim) -/// - the original build script as a byte slice (`BYTES`) +/// - the path of the renamed original build script (`PATH`) /// - a `main` function /// /// See [`package`]. -fn main_rs(build_script_path_as_str: &str) -> Vec { +fn main_rs(sibling_path_as_str: &str) -> Vec { [ COMMON_RS, format!( r#" -const BYTES: &[u8] = include_bytes!("{build_script_path_as_str}"); +const PATH: &str = "{sibling_path_as_str}"; fn main() -> Result<()> {{ - unpack_and_exec(BYTES) + exec_sibling(PATH) }} "#, ) diff --git a/tests/integration/cargo_config.rs b/tests/integration/cargo_config.rs index 2ff929f..aaf7092 100644 --- a/tests/integration/cargo_config.rs +++ b/tests/integration/cargo_config.rs @@ -1,5 +1,5 @@ use crate::util; -use std::{env::temp_dir, path::Path, process::Command}; +use std::{path::Path, process::Command}; const DIR: &str = "fixtures/cargo_config"; @@ -16,8 +16,19 @@ fn cargo_config() { // smoelius: When `build-wrap` builds the wrapper package, it expects the target directory to be // `target`. So building the wrapper package in `fixtures/cargo_config` would fail because it // contains a `.cargo/config.toml` that sets the target directory to `target-custom`. + + // smoelius: The build script in `fixtures/cargo_config` prints the path of the current + // executable, i.e., the wrapped, original build script. Previously, this was unpacked into + // `std::env::temp_dir()`. However, `build-wrap` now renames the original build script so that + // it is a sibling of the wrapper build script. Hence, when this test is run, the current + // executable should be in `target-custom` alongside the wrapper build script. let command = util::build_with_build_wrap(); - test_build(command, &temp_dir()); + test_build( + command, + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join(DIR) + .join("target-custom/debug"), + ); } fn test_build(mut command: Command, expected_dir: &Path) {