diff --git a/crates/cargo-gpu/src/dump_usage.rs b/crates/cargo-gpu/src/dump_usage.rs new file mode 100644 index 0000000..d539414 --- /dev/null +++ b/crates/cargo-gpu/src/dump_usage.rs @@ -0,0 +1,51 @@ +//! Convenience function for internal use. Dumps all the CLI usage instructions. Useful for +//! updating the README. + +use crate::{user_output, Cli}; + +/// main dump usage function +pub fn dump_full_usage_for_readme() -> anyhow::Result<()> { + use clap::CommandFactory as _; + let mut command = Cli::command(); + + let mut buffer: Vec = Vec::default(); + command.build(); + + write_help(&mut buffer, &mut command, 0)?; + user_output!("{}", String::from_utf8(buffer)?); + + Ok(()) +} + +/// Recursive function to print the usage instructions for each subcommand. +fn write_help( + buffer: &mut impl std::io::Write, + cmd: &mut clap::Command, + depth: usize, +) -> anyhow::Result<()> { + if cmd.get_name() == "help" { + return Ok(()); + } + + let mut command = cmd.get_name().to_owned(); + let indent_depth = if depth == 0 || depth == 1 { 0 } else { depth }; + let indent = " ".repeat(indent_depth * 4); + writeln!( + buffer, + "\n{}* {}{}", + indent, + command.remove(0).to_uppercase(), + command + )?; + + for line in cmd.render_long_help().to_string().lines() { + writeln!(buffer, "{indent} {line}")?; + } + + for sub in cmd.get_subcommands_mut() { + writeln!(buffer)?; + write_help(buffer, sub, depth + 1)?; + } + + Ok(()) +} diff --git a/crates/cargo-gpu/src/install.rs b/crates/cargo-gpu/src/install.rs index f8578c0..644097b 100644 --- a/crates/cargo-gpu/src/install.rs +++ b/crates/cargo-gpu/src/install.rs @@ -11,12 +11,67 @@ use log::{info, trace}; use spirv_builder::SpirvBuilder; use std::path::{Path, PathBuf}; +/// Represents a functional backend installation, whether it was cached or just installed. +#[derive(Clone, Debug)] +#[expect( + clippy::exhaustive_structs, + reason = "never adding private members to this struct" +)] +pub struct InstalledBackend { + /// path to the `rustc_codegen_spirv` dylib + pub rustc_codegen_spirv_location: PathBuf, + /// toolchain channel name + pub toolchain_channel: String, + /// directory with target-specs json files + pub target_spec_dir: PathBuf, +} + +impl InstalledBackend { + /// Creates a new `SpirvBuilder` configured to use this installed backend. + #[expect( + clippy::missing_panics_doc, + clippy::expect_used, + reason = "unreachable" + )] + #[expect(clippy::impl_trait_in_params, reason = "forwarding spirv-builder API")] + #[inline] + pub fn to_spirv_builder( + &self, + path_to_crate: impl AsRef, + target: impl Into, + ) -> SpirvBuilder { + let mut builder = SpirvBuilder::new(path_to_crate, target); + self.configure_spirv_builder(&mut builder) + .expect("unreachable"); + builder + } + + /// Configures the supplied [`SpirvBuilder`]. `SpirvBuilder.target` must be set and must not change after calling this function. + /// + /// # Errors + /// if `SpirvBuilder.target` is not set + #[inline] + pub fn configure_spirv_builder(&self, builder: &mut SpirvBuilder) -> anyhow::Result<()> { + builder.rustc_codegen_spirv_location = Some(self.rustc_codegen_spirv_location.clone()); + builder.toolchain_overwrite = Some(self.toolchain_channel.clone()); + builder.path_to_target_spec = Some(self.target_spec_dir.join(format!( + "{}.json", + builder.target.as_ref().context("expect target to be set")? + ))); + Ok(()) + } +} + /// Args for an install #[expect( clippy::struct_excessive_bools, reason = "cmdline args have many bools" )] #[derive(clap::Parser, Debug, Clone, serde::Deserialize, serde::Serialize)] +#[expect( + clippy::exhaustive_structs, + reason = "never adding private members to this struct" +)] pub struct Install { /// Directory containing the shader crate to compile. #[clap(long, default_value = "./")] @@ -44,6 +99,8 @@ pub struct Install { pub rebuild_codegen: bool, /// Assume "yes" to "Install Rust toolchain: [y/n]" prompt. + /// + /// Defaults to `false` in cli, `true` in [`Default`] #[clap(long, action)] pub auto_install_rust_toolchain: bool, @@ -77,46 +134,22 @@ pub struct Install { pub force_overwrite_lockfiles_v4_to_v3: bool, } -/// Represents a functional backend installation, whether it was cached or just installed. -#[derive(Clone, Debug)] -pub struct InstalledBackend { - /// path to the `rustc_codegen_spirv` dylib - pub rustc_codegen_spirv_location: PathBuf, - /// toolchain channel name - pub toolchain_channel: String, - /// directory with target-specs json files - pub target_spec_dir: PathBuf, -} - -impl InstalledBackend { - /// Configures the supplied [`SpirvBuilder`]. `SpirvBuilder.target` must be set and must not change after calling this function. - pub fn configure_spirv_builder(&self, builder: &mut SpirvBuilder) -> anyhow::Result<()> { - builder.rustc_codegen_spirv_location = Some(self.rustc_codegen_spirv_location.clone()); - builder.toolchain_overwrite = Some(self.toolchain_channel.clone()); - builder.path_to_target_spec = Some(self.target_spec_dir.join(format!( - "{}.json", - builder.target.as_ref().context("expect target to be set")? - ))); - Ok(()) - } -} - -impl Default for Install { +impl Install { + /// Create a default install for a shader crate of some path #[inline] - fn default() -> Self { + #[must_use] + pub const fn from_shader_crate(shader_crate: PathBuf) -> Self { Self { - shader_crate: PathBuf::from("./"), + shader_crate, spirv_builder_source: None, spirv_builder_version: None, rebuild_codegen: false, - auto_install_rust_toolchain: false, + auto_install_rust_toolchain: true, clear_target: true, force_overwrite_lockfiles_v4_to_v3: false, } } -} -impl Install { /// Create the `rustc_codegen_spirv_dummy` crate that depends on `rustc_codegen_spirv` fn write_source_files(source: &SpirvSource, checkout: &Path) -> anyhow::Result<()> { // skip writing a dummy project if we use a local rust-gpu checkout @@ -238,7 +271,11 @@ package = "rustc_codegen_spirv" Ok(target_specs_dst) } - /// Install the binary pair and return the `(dylib_path, toolchain_channel)`. + /// Install the binary pair and return the [`InstalledBackend`], from which you can create [`SpirvBuilder`] instances. + /// + /// # Errors + /// If the installation somehow fails. + #[inline] #[expect(clippy::too_many_lines, reason = "it's fine")] pub fn run(&self) -> anyhow::Result { // Ensure the cache dir exists diff --git a/crates/cargo-gpu/src/lib.rs b/crates/cargo-gpu/src/lib.rs new file mode 100644 index 0000000..b3adabf --- /dev/null +++ b/crates/cargo-gpu/src/lib.rs @@ -0,0 +1,202 @@ +#![expect(clippy::pub_use, reason = "pub use for build scripts")] + +//! Rust GPU shader crate builder. +//! +//! This program and library allows you to easily compile your rust-gpu shaders, +//! without requiring you to fix your entire project to a specific toolchain. +//! +//! # How it works +//! +//! This program primarily manages installations of `rustc_codegen_spirv`, the +//! codegen backend of rust-gpu to generate SPIR-V shader binaries. The codegen +//! backend builds on internal, ever-changing interfaces of rustc, which requires +//! fixing a version of rust-gpu to a specific version of the rustc compiler. +//! Usually, this would require you to fix your entire project to that specific +//! toolchain, but this project loosens that requirement by managing installations +//! of `rustc_codegen_spirv` and their associated toolchains for you. +//! +//! We continue to use rust-gpu's `spirv_builder` crate to pass the many additional +//! parameters required to configure rustc and our codegen backend, but provide you +//! with a toolchain agnostic version that you may use from stable rustc. And a +//! `cargo gpu` cmdline utility to simplify shader building even more. +//! +//! ## Where the binaries are +//! +//! We store our prebuild `rustc_spirv_builder` binaries in the default cache +//! directory of your OS: +//! * Windows: `C:/users//AppData/Local/rust-gpu` +//! * Mac: `~/Library/Caches/rust-gpu` +//! * Linux: `~/.cache/rust-gpu` +//! +//! ## How we build the backend +//! +//! * retrieve the version of rust-gpu you want to use based on the version of the +//! `spirv-std` dependency in your shader crate. +//! * create a dummy project at `/codegen//` that depends on +//! `rustc_codegen_spirv` +//! * use `cargo metadata` to `cargo update` the dummy project, which downloads the +//! `rustc_codegen_spirv` crate into cargo's cache, and retrieve the path to the +//! download location. +//! * search for the required toolchain in `build.rs` of `rustc_codegen_spirv` +//! * build it with the required toolchain version +//! * copy out the binary and clean the target dir +//! +//! ## Building shader crates +//! +//! `cargo-gpu` takes a path to a shader crate to build, as well as a path to a directory +//! to put the compiled `spv` source files. It also takes a path to an output manifest +//! file where all shader entry points will be mapped to their `spv` source files. This +//! manifest file can be used by build scripts (`build.rs` files) to generate linkage or +//! conduct other post-processing, like converting the `spv` files into `wgsl` files, +//! for example. + +use anyhow::Context as _; + +use crate::dump_usage::dump_full_usage_for_readme; +use build::Build; +use show::Show; + +mod build; +mod config; +mod dump_usage; +mod install; +mod install_toolchain; +mod legacy_target_specs; +mod linkage; +mod lockfile; +mod metadata; +mod show; +mod spirv_source; +mod test; + +pub use install::*; +pub use spirv_builder; + +/// Central function to write to the user. +#[macro_export] +macro_rules! user_output { + ($($args: tt)*) => { + #[allow( + clippy::allow_attributes, + clippy::useless_attribute, + unused_imports, + reason = "`std::io::Write` is only sometimes called??" + )] + use std::io::Write as _; + + #[expect( + clippy::non_ascii_literal, + reason = "CRAB GOOD. CRAB IMPORTANT." + )] + { + print!("🦀 "); + } + print!($($args)*); + std::io::stdout().flush().unwrap(); + } +} + +/// All of the available subcommands for `cargo gpu` +#[derive(clap::Subcommand)] +#[non_exhaustive] +pub enum Command { + /// Install rust-gpu compiler artifacts. + Install(Box), + + /// Compile a shader crate to SPIR-V. + Build(Box), + + /// Show some useful values. + Show(Show), + + /// A hidden command that can be used to recursively print out all the subcommand help messages: + /// `cargo gpu dump-usage` + /// Useful for updating the README. + #[clap(hide(true))] + DumpUsage, +} + +impl Command { + /// Runs the command + /// + /// # Errors + /// Any errors during execution, usually printed to the user + #[inline] + pub fn run(&self, env_args: Vec) -> anyhow::Result<()> { + match &self { + Self::Install(install) => { + let shader_crate_path = &install.shader_crate; + let command = + config::Config::clap_command_with_cargo_config(shader_crate_path, env_args)?; + log::debug!( + "installing with final merged arguments: {:#?}", + command.install + ); + command.install.run()?; + } + Self::Build(build) => { + let shader_crate_path = &build.install.shader_crate; + let mut command = + config::Config::clap_command_with_cargo_config(shader_crate_path, env_args)?; + log::debug!("building with final merged arguments: {command:#?}"); + + if command.build.watch { + // When watching, do one normal run to setup the `manifest.json` file. + command.build.watch = false; + command.run()?; + command.build.watch = true; + command.run()?; + } else { + command.run()?; + } + } + Self::Show(show) => show.run()?, + Self::DumpUsage => dump_full_usage_for_readme()?, + } + + Ok(()) + } +} + +/// the Cli struct representing the main cli +#[derive(clap::Parser)] +#[clap(author, version, about, subcommand_required = true)] +#[non_exhaustive] +pub struct Cli { + /// The command to run. + #[clap(subcommand)] + pub command: Command, +} + +/// The central cache directory of cargo gpu +/// +/// # Errors +/// may fail if we can't find the user home directory +#[inline] +pub fn cache_dir() -> anyhow::Result { + let dir = directories::BaseDirs::new() + .with_context(|| "could not find the user home directory")? + .cache_dir() + .join("rust-gpu"); + + Ok(if cfg!(test) { + let thread_id = std::thread::current().id(); + let id = format!("{thread_id:?}").replace('(', "-").replace(')', ""); + dir.join("tests").join(id) + } else { + dir + }) +} + +/// Returns a string suitable to use as a directory. +/// +/// Created from the spirv-builder source dep and the rustc channel. +fn to_dirname(text: &str) -> String { + text.replace( + [std::path::MAIN_SEPARATOR, '\\', '/', '.', ':', '@', '='], + "_", + ) + .split(['{', '}', ' ', '\n', '"', '\'']) + .collect::>() + .concat() +} diff --git a/crates/cargo-gpu/src/main.rs b/crates/cargo-gpu/src/main.rs index 9aae856..f40e3c6 100644 --- a/crates/cargo-gpu/src/main.rs +++ b/crates/cargo-gpu/src/main.rs @@ -1,94 +1,6 @@ -//! Rust GPU shader crate builder. -//! -//! This program and library allows you to easily compile your rust-gpu shaders, -//! without requiring you to fix your entire project to a specific toolchain. -//! -//! # How it works -//! -//! This program primarily manages installations of `rustc_codegen_spirv`, the -//! codegen backend of rust-gpu to generate SPIR-V shader binaries. The codegen -//! backend builds on internal, ever-changing interfaces of rustc, which requires -//! fixing a version of rust-gpu to a specific version of the rustc compiler. -//! Usually, this would require you to fix your entire project to that specific -//! toolchain, but this project loosens that requirement by managing installations -//! of `rustc_codegen_spirv` and their associated toolchains for you. -//! -//! We continue to use rust-gpu's `spirv_builder` crate to pass the many additional -//! parameters required to configure rustc and our codegen backend, but provide you -//! with a toolchain agnostic version that you may use from stable rustc. And a -//! `cargo gpu` cmdline utility to simplify shader building even more. -//! -//! ## Where the binaries are -//! -//! We store our prebuild `rustc_spirv_builder` binaries in the default cache -//! directory of your OS: -//! * Windows: `C:/users//AppData/Local/rust-gpu` -//! * Mac: `~/Library/Caches/rust-gpu` -//! * Linux: `~/.cache/rust-gpu` -//! -//! ## How we build the backend -//! -//! * retrieve the version of rust-gpu you want to use based on the version of the -//! `spirv-std` dependency in your shader crate. -//! * create a dummy project at `/codegen//` that depends on -//! `rustc_codegen_spirv` -//! * use `cargo metadata` to `cargo update` the dummy project, which downloads the -//! `rustc_codegen_spirv` crate into cargo's cache, and retrieve the path to the -//! download location. -//! * search for the required toolchain in `build.rs` of `rustc_codegen_spirv` -//! * build it with the required toolchain version -//! * copy out the binary and clean the target dir -//! -//! ## Building shader crates -//! -//! `cargo-gpu` takes a path to a shader crate to build, as well as a path to a directory -//! to put the compiled `spv` source files. It also takes a path to an output manifest -//! file where all shader entry points will be mapped to their `spv` source files. This -//! manifest file can be used by build scripts (`build.rs` files) to generate linkage or -//! conduct other post-processing, like converting the `spv` files into `wgsl` files, -//! for example. - -use anyhow::Context as _; - -use build::Build; +//! main executable of cargo gpu +use cargo_gpu::Cli; use clap::Parser as _; -use install::Install; -use show::Show; - -mod build; -mod config; -mod install; -mod install_toolchain; -mod legacy_target_specs; -mod linkage; -mod lockfile; -mod metadata; -mod show; -mod spirv_source; - -/// Central function to write to the user. -#[macro_export] -macro_rules! user_output { - ($($args: tt)*) => { - #[allow( - clippy::allow_attributes, - clippy::useless_attribute, - unused_imports, - reason = "`std::io::Write` is only sometimes called??" - )] - use std::io::Write as _; - - #[expect( - clippy::non_ascii_literal, - reason = "CRAB GOOD. CRAB IMPORTANT." - )] - { - print!("🦀 "); - } - print!($($args)*); - std::io::stdout().flush().unwrap(); - } -} fn main() { #[cfg(debug_assertions)] @@ -124,195 +36,6 @@ fn run() -> anyhow::Result<()> { }) .collect::>(); log::trace!("CLI args: {env_args:#?}"); - let cli = Cli::parse_from(env_args.clone()); - - match cli.command { - Command::Install(install) => { - let shader_crate_path = install.shader_crate; - let command = - config::Config::clap_command_with_cargo_config(&shader_crate_path, env_args)?; - log::debug!( - "installing with final merged arguments: {:#?}", - command.install - ); - command.install.run()?; - } - Command::Build(build) => { - let shader_crate_path = build.install.shader_crate; - let mut command = - config::Config::clap_command_with_cargo_config(&shader_crate_path, env_args)?; - log::debug!("building with final merged arguments: {command:#?}"); - - if command.build.watch { - // When watching, do one normal run to setup the `manifest.json` file. - command.build.watch = false; - command.run()?; - command.build.watch = true; - command.run()?; - } else { - command.run()?; - } - } - Command::Show(show) => show.run()?, - Command::DumpUsage => dump_full_usage_for_readme()?, - } - - Ok(()) -} - -/// All of the available subcommands for `cargo gpu` -#[derive(clap::Subcommand)] -enum Command { - /// Install rust-gpu compiler artifacts. - Install(Box), - - /// Compile a shader crate to SPIR-V. - Build(Box), - - /// Show some useful values. - Show(Show), - - /// A hidden command that can be used to recursively print out all the subcommand help messages: - /// `cargo gpu dump-usage` - /// Useful for updating the README. - #[clap(hide(true))] - DumpUsage, -} - -#[derive(clap::Parser)] -#[clap(author, version, about, subcommand_required = true)] -pub(crate) struct Cli { - /// The command to run. - #[clap(subcommand)] - command: Command, -} - -fn cache_dir() -> anyhow::Result { - let dir = directories::BaseDirs::new() - .with_context(|| "could not find the user home directory")? - .cache_dir() - .join("rust-gpu"); - - Ok(if cfg!(test) { - let thread_id = std::thread::current().id(); - let id = format!("{thread_id:?}").replace('(', "-").replace(')', ""); - dir.join("tests").join(id) - } else { - dir - }) -} - -/// Convenience function for internal use. Dumps all the CLI usage instructions. Useful for -/// updating the README. -fn dump_full_usage_for_readme() -> anyhow::Result<()> { - use clap::CommandFactory as _; - let mut command = Cli::command(); - - let mut buffer: Vec = Vec::default(); - command.build(); - - write_help(&mut buffer, &mut command, 0)?; - user_output!("{}", String::from_utf8(buffer)?); - - Ok(()) -} - -/// Recursive function to print the usage instructions for each subcommand. -fn write_help( - buffer: &mut impl std::io::Write, - cmd: &mut clap::Command, - depth: usize, -) -> anyhow::Result<()> { - if cmd.get_name() == "help" { - return Ok(()); - } - - let mut command = cmd.get_name().to_owned(); - let indent_depth = if depth == 0 || depth == 1 { 0 } else { depth }; - let indent = " ".repeat(indent_depth * 4); - writeln!( - buffer, - "\n{}* {}{}", - indent, - command.remove(0).to_uppercase(), - command - )?; - - for line in cmd.render_long_help().to_string().lines() { - writeln!(buffer, "{indent} {line}")?; - } - - for sub in cmd.get_subcommands_mut() { - writeln!(buffer)?; - write_help(buffer, sub, depth + 1)?; - } - - Ok(()) -} - -/// Returns a string suitable to use as a directory. -/// -/// Created from the spirv-builder source dep and the rustc channel. -fn to_dirname(text: &str) -> String { - text.replace( - [std::path::MAIN_SEPARATOR, '\\', '/', '.', ':', '@', '='], - "_", - ) - .split(['{', '}', ' ', '\n', '"', '\'']) - .collect::>() - .concat() -} - -#[cfg(test)] -mod test { - use crate::cache_dir; - use std::io::Write as _; - - fn copy_dir_all( - src: impl AsRef, - dst: impl AsRef, - ) -> anyhow::Result<()> { - std::fs::create_dir_all(&dst)?; - for maybe_entry in std::fs::read_dir(src)? { - let entry = maybe_entry?; - let ty = entry.file_type()?; - if ty.is_dir() { - copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; - } else { - std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; - } - } - Ok(()) - } - - pub fn shader_crate_template_path() -> std::path::PathBuf { - let project_base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - project_base.join("../shader-crate-template") - } - - pub fn shader_crate_test_path() -> std::path::PathBuf { - let shader_crate_path = crate::cache_dir().unwrap().join("shader_crate"); - copy_dir_all(shader_crate_template_path(), shader_crate_path.clone()).unwrap(); - shader_crate_path - } - - pub fn overwrite_shader_cargo_toml(shader_crate_path: &std::path::Path) -> std::fs::File { - let cargo_toml = shader_crate_path.join("Cargo.toml"); - let mut file = std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .open(cargo_toml) - .unwrap(); - writeln!(file, "[package]").unwrap(); - writeln!(file, "name = \"test\"").unwrap(); - file - } - - pub fn tests_teardown() { - let cache_dir = cache_dir().unwrap(); - if !cache_dir.exists() { - return; - } - std::fs::remove_dir_all(cache_dir).unwrap(); - } + let cli = Cli::parse_from(&env_args); + cli.command.run(env_args) } diff --git a/crates/cargo-gpu/src/show.rs b/crates/cargo-gpu/src/show.rs index 3b0e2f8..265b126 100644 --- a/crates/cargo-gpu/src/show.rs +++ b/crates/cargo-gpu/src/show.rs @@ -33,7 +33,7 @@ pub struct Show { impl Show { /// Entrypoint - pub fn run(self) -> anyhow::Result<()> { + pub fn run(&self) -> anyhow::Result<()> { log::info!("{:?}: ", self.command); #[expect( @@ -41,17 +41,17 @@ impl Show { reason = "The output of this command could potentially be used in a script, \ so we _don't_ want to use `crate::user_output`, as that prefixes a crab." )] - match self.command { + match &self.command { Info::CacheDirectory => { println!("{}\n", cache_dir()?.display()); } Info::SpirvSource(SpirvSourceDep { shader_crate }) => { let rust_gpu_source = - crate::spirv_source::SpirvSource::get_rust_gpu_deps_from_shader(&shader_crate)?; + crate::spirv_source::SpirvSource::get_rust_gpu_deps_from_shader(shader_crate)?; println!("{rust_gpu_source}\n"); } Info::Commitsh => { - println!("{}", std::env!("GIT_HASH")); + println!("{}", env!("GIT_HASH")); } Info::Capabilities => { println!("All available options to the `cargo gpu build --capabilities` argument:"); diff --git a/crates/cargo-gpu/src/test.rs b/crates/cargo-gpu/src/test.rs new file mode 100644 index 0000000..c9ee93a --- /dev/null +++ b/crates/cargo-gpu/src/test.rs @@ -0,0 +1,53 @@ +//! utilities for tests +#![cfg(test)] + +use crate::cache_dir; +use std::io::Write as _; + +fn copy_dir_all( + src: impl AsRef, + dst: impl AsRef, +) -> anyhow::Result<()> { + std::fs::create_dir_all(&dst)?; + for maybe_entry in std::fs::read_dir(src)? { + let entry = maybe_entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} + +pub fn shader_crate_template_path() -> std::path::PathBuf { + let project_base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + project_base.join("../shader-crate-template") +} + +pub fn shader_crate_test_path() -> std::path::PathBuf { + let shader_crate_path = crate::cache_dir().unwrap().join("shader_crate"); + copy_dir_all(shader_crate_template_path(), shader_crate_path.clone()).unwrap(); + shader_crate_path +} + +pub fn overwrite_shader_cargo_toml(shader_crate_path: &std::path::Path) -> std::fs::File { + let cargo_toml = shader_crate_path.join("Cargo.toml"); + let mut file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(cargo_toml) + .unwrap(); + writeln!(file, "[package]").unwrap(); + writeln!(file, "name = \"test\"").unwrap(); + file +} + +pub fn tests_teardown() { + let cache_dir = cache_dir().unwrap(); + if !cache_dir.exists() { + return; + } + std::fs::remove_dir_all(cache_dir).unwrap(); +}