diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a1df007c3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +libwild/src/test_data/*.a -text \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b1479273..13635ac9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,6 +93,27 @@ jobs: - run: cargo build --profile ci --workspace --no-default-features - run: WILD_TEST_CROSS=$WILD_TEST_CROSS cargo test --profile ci --workspace + windows-build: + name: Windows build + runs-on: windows-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + id: rust-toolchain + - uses: actions/cache@v5 + with: + path: | + C:\Users\runneradmin\.cargo\registry\index\ + C:\Users\runneradmin\.cargo\registry\cache\ + C:\Users\runneradmin\.cargo\git\db\ + target\ + key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} + - run: cargo build --profile ci --workspace --no-default-features + - run: cargo test --profile ci -p libwild --lib --no-default-features + minimal-versions: runs-on: ubuntu-24.04 timeout-minutes: 10 diff --git a/Cargo.lock b/Cargo.lock index 6d1316045..ecf3bfe9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,7 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1241,13 +1241,14 @@ dependencies = [ [[package]] name = "perfetto-recorder" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61bf1e6015b1f6e9eacdfb095717e87021323f30652edc61da38d662842eabff" +checksum = "fe40ee4444b9e307b0f8d2f281b099fe8dfb9e65f0cc2cfb87800bc7ffb71dbf" dependencies = [ "nix", "prost", "rand", + "windows-sys 0.61.2", ] [[package]] @@ -1532,7 +1533,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1788,7 +1789,7 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fbf77fa47..b35c9d058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ object = { version = "0.38.0", default-features = false, features = [ os_info = "3.0.0" paste = "1.0.15" perf-event = "0.4.8" -perfetto-recorder = "0.2.0" +perfetto-recorder = "0.3.0" postcard = { version = "1.1.1", features = ["use-std"] } rayon = "1.5.1" rstest = "0.26.1" diff --git a/libwild/src/archive.rs b/libwild/src/archive.rs index 3dfc21ba1..2866217bc 100644 --- a/libwild/src/archive.rs +++ b/libwild/src/archive.rs @@ -5,10 +5,8 @@ use crate::bail; use crate::error::Context as _; use crate::error::Result; -use std::ffi::OsStr; use std::ops::Range; -use std::os::unix::ffi::OsStrExt as _; -use std::path::Path; +use std::path::PathBuf; use zerocopy::FromBytes; use zerocopy::Immutable; use zerocopy::KnownLayout; @@ -20,6 +18,20 @@ pub(crate) enum ArchiveEntry<'data> { Thin(ThinEntry<'data>), } +fn path_from_bytes(bytes: &[u8]) -> PathBuf { + #[cfg(unix)] + { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt as _; + std::path::Path::new(OsStr::from_bytes(bytes)).to_path_buf() + } + #[cfg(not(unix))] + { + let path = std::str::from_utf8(bytes).expect("Invalid UTF-8 in archive path name"); + PathBuf::from(path) + } +} + #[derive(Clone, Copy)] pub(crate) struct ExtendedFilenames<'data> { data: &'data [u8], @@ -324,8 +336,8 @@ impl<'data> Identifier<'data> { } } - pub(crate) fn as_path(&self) -> &'data std::path::Path { - Path::new(OsStr::from_bytes(self.as_slice())) + pub(crate) fn as_path(&self) -> PathBuf { + path_from_bytes(self.as_slice()) } } diff --git a/libwild/src/args.rs b/libwild/src/args.rs index ff35b9368..6b7049651 100644 --- a/libwild/src/args.rs +++ b/libwild/src/args.rs @@ -720,7 +720,16 @@ fn arguments_from_string(input: &str) -> Result> { } } else { if ch == '\\' { - ch = chars.next().context("Invalid escape")?; + let next = chars.next().context("Invalid escape")?; + // Keep backslashes unless escaping \, ", or ' so Windows paths like C:\foo survive. + if matches!(next, '\\' | '"' | '\'') { + ch = next; + } else { + let heap = heap.get_or_insert(String::new()); + heap.push('\\'); + heap.push(next); + continue; + } } heap.get_or_insert(String::new()).push(ch); } diff --git a/libwild/src/dwarf_address_info.rs b/libwild/src/dwarf_address_info.rs index 6246b694f..0cec4aaa3 100644 --- a/libwild/src/dwarf_address_info.rs +++ b/libwild/src/dwarf_address_info.rs @@ -11,12 +11,25 @@ use object::SymbolIndex; use object::read::elf::Crel; use object::read::elf::RelocationSections; use std::borrow::Cow; -use std::ffi::OsStr; use std::fmt::Display; -use std::os::unix::ffi::OsStrExt; -use std::path::Path; use std::path::PathBuf; +fn pathbuf_from_bytes(bytes: &[u8]) -> PathBuf { + #[cfg(unix)] + { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + use std::path::Path; + + return Path::new(OsStr::from_bytes(bytes)).to_path_buf(); + } + + #[cfg(not(unix))] + { + PathBuf::from(String::from_utf8_lossy(bytes).into_owned()) + } +} + pub(crate) struct SourceInfo(Option); #[derive(Debug)] @@ -64,7 +77,7 @@ pub(crate) fn get_source_info( let comp_dir = unit .comp_dir .as_ref() - .map(|dir| Path::new(OsStr::from_bytes(dir)).to_owned()) + .map(|dir| pathbuf_from_bytes(dir)) .unwrap_or_default(); let mut rows = program.rows(); @@ -82,7 +95,7 @@ pub(crate) fn get_source_info( if let Some(file) = row.file(header) { path = comp_dir.clone(); - path.push(OsStr::from_bytes( + path.push(pathbuf_from_bytes( &dwarf.attr_string(&unit, file.path_name())?, )); } diff --git a/libwild/src/file_writer.rs b/libwild/src/file_writer.rs index b84638a03..d4cc5d081 100644 --- a/libwild/src/file_writer.rs +++ b/libwild/src/file_writer.rs @@ -263,7 +263,10 @@ impl SizedOutput { // assuming it eventually calls exec, this at least means that it inherits the file // descriptor for less time. i.e. this doesn't really fix anything, but makes problems less // bad. - std::os::unix::fs::OpenOptionsExt::custom_flags(&mut open_options, libc::O_CLOEXEC); + #[cfg(unix)] + { + std::os::unix::fs::OpenOptionsExt::custom_flags(&mut open_options, libc::O_CLOEXEC); + } match output_config.file_write_mode { FileWriteMode::UnlinkAndReplace => { @@ -316,7 +319,10 @@ impl SizedOutput { // Making the file executable is best-effort only. For example if we're writing to a pipe or // something, it isn't going to work and that's OK. - let _ = crate::fs::make_executable(&self.file); + #[cfg(unix)] + { + let _ = crate::fs::make_executable(&self.file); + } Ok(()) } diff --git a/libwild/src/fs.rs b/libwild/src/fs.rs index 2c96862ec..be48b607b 100644 --- a/libwild/src/fs.rs +++ b/libwild/src/fs.rs @@ -1,6 +1,9 @@ +#[cfg(unix)] use crate::error::Result; +#[cfg(unix)] use std::fs::File; +#[cfg(unix)] pub(crate) fn make_executable(file: &File) -> Result { use std::os::unix::prelude::PermissionsExt; diff --git a/libwild/src/input_data.rs b/libwild/src/input_data.rs index 170aaddaa..b225298ed 100644 --- a/libwild/src/input_data.rs +++ b/libwild/src/input_data.rs @@ -486,7 +486,7 @@ fn process_thin_archive<'data>( ArchiveEntry::Filenames(t) => extended_filenames = Some(t), ArchiveEntry::Thin(entry) => { let path = entry.identifier(extended_filenames).as_path(); - let entry_path = parent_path.join(path); + let entry_path = parent_path.join(&path); let file_data = FileData::new(&entry_path, args.prepopulate_maps).with_context(|| { diff --git a/libwild/src/lib.rs b/libwild/src/lib.rs index 221e3a523..3f563a132 100644 --- a/libwild/src/lib.rs +++ b/libwild/src/lib.rs @@ -47,9 +47,9 @@ pub(crate) mod save_dir; pub(crate) mod sframe; pub(crate) mod sharding; pub(crate) mod string_merging; -#[cfg(feature = "fork")] +#[cfg(all(feature = "fork", unix))] pub(crate) mod subprocess; -#[cfg(not(feature = "fork"))] +#[cfg(not(all(feature = "fork", unix)))] #[path = "subprocess_unsupported.rs"] pub(crate) mod subprocess; pub(crate) mod symbol; diff --git a/libwild/src/linker_script.rs b/libwild/src/linker_script.rs index bc9e81a4e..4d03530fb 100644 --- a/libwild/src/linker_script.rs +++ b/libwild/src/linker_script.rs @@ -463,6 +463,20 @@ mod tests { use super::*; use crate::args::InputSpec; use itertools::assert_equal; + use std::path::PathBuf; + + fn abs_test_path(path: &str) -> PathBuf { + #[cfg(windows)] + { + let path = path.trim_start_matches('/'); + let path = path.replace('/', "\\"); + PathBuf::from(format!(r"C:\{path}")) + } + #[cfg(not(windows))] + { + PathBuf::from(path) + } + } fn parse_script(text: &str) -> Result> { LinkerScript::parse(text.as_bytes(), Path::new("test-linker-script.txt")) @@ -529,58 +543,59 @@ mod tests { #[test] fn test_sysroot_application() { - let sysroot = Path::new("/usr/aarch64-linux-gnu"); + let sysroot = abs_test_path("/usr/aarch64-linux-gnu"); + let linker_script_outside_sysroot = abs_test_path("/lib/libc.so"); // Linker script is located in the sysroot assert_equal( maybe_apply_sysroot( &sysroot.join("lib/libc.so"), Path::new("/lib/libc.so.6"), - sysroot, + &sysroot, ), Some(Box::from(sysroot.join("lib/libc.so.6"))), ); // Linker script is not located in the sysroot assert_equal( maybe_apply_sysroot( - Path::new("/lib/libc.so"), + &linker_script_outside_sysroot, Path::new("/lib/libc.so.6"), - sysroot, + &sysroot, ), None, ); // Sysroot enforced by `=/` assert_equal( maybe_apply_sysroot( - Path::new("/lib/libc.so"), + &linker_script_outside_sysroot, Path::new("=/lib/libc.so.6"), - sysroot, + &sysroot, ), Some(Box::from(sysroot.join("lib/libc.so.6"))), ); // Sysroot enforced by `=` assert_equal( maybe_apply_sysroot( - Path::new("/lib/libc.so"), + &linker_script_outside_sysroot, Path::new("=lib/libc.so.6"), - sysroot, + &sysroot, ), Some(Box::from(sysroot.join("lib/libc.so.6"))), ); // Sysroot enforced by `$SYSROOT` assert_equal( maybe_apply_sysroot( - Path::new("/lib/libc.so"), + &linker_script_outside_sysroot, Path::new("$SYSROOT/lib/libc.so.6"), - sysroot, + &sysroot, ), Some(Box::from(sysroot.join("lib/libc.so.6"))), ); // Sysroot enforced by `$SYSROOT` assert_equal( maybe_apply_sysroot( - Path::new("/lib/libc.so"), + &linker_script_outside_sysroot, Path::new("$SYSROOTlib/libc.so.6"), - sysroot, + &sysroot, ), Some(Box::from(sysroot.join("lib/libc.so.6"))), ); diff --git a/libwild/src/save_dir.rs b/libwild/src/save_dir.rs index 2d59e0801..4057190a8 100644 --- a/libwild/src/save_dir.rs +++ b/libwild/src/save_dir.rs @@ -161,7 +161,10 @@ impl SaveDirState { } drop(out); - crate::fs::make_executable(&file)?; + #[cfg(unix)] + { + crate::fs::make_executable(&file)?; + } Ok(()) } @@ -252,31 +255,44 @@ impl SaveDirState { std::fs::create_dir(&dest_path) .with_context(|| format!("Failed to create directory `{}`", dest_path.display()))?; } else if meta.is_symlink() { - let directory = source_path.parent().context("Invalid path")?; - let mut target = std::fs::read_link(source_path) - .with_context(|| format!("Failed to read symlink `{}`", source_path.display()))?; + #[cfg(unix)] + { + let directory = source_path.parent().context("Invalid path")?; + let mut target = std::fs::read_link(source_path).with_context(|| { + format!("Failed to read symlink `{}`", source_path.display()) + })?; + + if target.is_absolute() { + self.copy_file(&target, parsed_args)?; + target = make_relative_path(&target, directory).with_context(|| { + format!( + "Failed to make path `{}` relative to `{}` while copying symlink", + target.display(), + directory.display() + ) + })?; + } else { + let absolute_target = directory.join(&target); + self.copy_file(&absolute_target, parsed_args)?; + } - if target.is_absolute() { - self.copy_file(&target, parsed_args)?; - target = make_relative_path(&target, directory).with_context(|| { + std::os::unix::fs::symlink(&target, &dest_path).with_context(|| { format!( - "Failed to make path `{}` relative to `{}` while copying symlink", - target.display(), - directory.display() + "Failed to symlink {} to {}", + dest_path.display(), + target.display() + ) + })?; + } + #[cfg(not(unix))] + { + std::fs::copy(source_path, &dest_path).with_context(|| { + format!( + "Failed to copy symlink target for `{}`", + source_path.display() ) })?; - } else { - let absolute_target = directory.join(&target); - self.copy_file(&absolute_target, parsed_args)?; } - - std::os::unix::fs::symlink(&target, &dest_path).with_context(|| { - format!( - "Failed to symlink {} to {}", - dest_path.display(), - target.display() - ) - })?; } else { if let Ok(data) = FileData::new(source_path, false) { match FileKind::identify_bytes(&data) { @@ -460,6 +476,19 @@ fn to_output_relative_path(path: &Path) -> PathBuf { mod tests { use super::*; + fn abs_test_path(path: &str) -> PathBuf { + #[cfg(windows)] + { + let path = path.trim_start_matches('/'); + let path = path.replace('/', "\\"); + PathBuf::from(format!(r"C:\{path}")) + } + #[cfg(not(windows))] + { + PathBuf::from(path) + } + } + fn test_make_relative_path(target: &Path, directory: &Path) { let relative = make_relative_path(target, directory).unwrap(); @@ -498,8 +527,8 @@ mod tests { ]; for (a, b) in cases { - let a = PathBuf::from(a); - let b = PathBuf::from(b); + let a = abs_test_path(a); + let b = abs_test_path(b); test_make_relative_path(&a, &b); test_make_relative_path(&b, &a); } diff --git a/wild/src/main.rs b/wild/src/main.rs index 4095d4610..a3c5e84dc 100644 --- a/wild/src/main.rs +++ b/wild/src/main.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "mimalloc")] +#[cfg(all(feature = "mimalloc", not(feature = "dhat")))] #[global_allocator] static MIMALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; diff --git a/wild/tests/integration_tests.rs b/wild/tests/integration_tests.rs index 20536a31e..e62dc73d6 100644 --- a/wild/tests/integration_tests.rs +++ b/wild/tests/integration_tests.rs @@ -1,3 +1,4 @@ +#![cfg(not(windows))] //! Tests that build and run various test programs then link them and run them. Each test is linked //! with both the system linker (ld) and with wild. //!