├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── basic.rs ├── cargo_rustc_link_lib.rs ├── cargo_rustc_link_search.rs ├── core.rs ├── instruction.rs ├── lib.rs ├── prefix.rs ├── utils.rs └── value.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | alinuxperson@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "build_script" 3 | version = "0.2.0" 4 | authors = ["ALinuxPerson "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A wrapper for build.rs instructions" 8 | repository = "https://github.com/ALinuxPerson/build_script" 9 | keywords = ["script", "build-script", "build", "build-rs"] 10 | categories = ["development-tools::build-utils"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | once_cell = "1.9.0" 16 | 17 | [dev-dependencies] 18 | gag = "0.1.10" 19 | serial_test = "0.5.1" 20 | 21 | [features] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 ALinuxPerson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Build Script

3 | 4 | 5 | 6 | 7 | 8 | 9 |

A wrapper for build.rs instructions

10 |
11 | 12 | # Why? 13 | I made this because I felt like the way you pass instructions to `build.rs` makes it very easy to make mistakes 14 | (especially when using strings) and it just felt odd that rust doesn't provide an api or an official external crate 15 | (like [`rand`](https://crates.io/crates/rand)). 16 | 17 | # Installation 18 | Add this to your `Cargo.toml`: 19 | ```toml 20 | [build-dependencies] 21 | build_script = "0.2.0" 22 | ``` 23 | 24 | # Examples 25 | ```rust 26 | use build_script::{cargo_rustc_link_lib, cargo_rustc_link_search, BuildScript, Instruction, Value}; 27 | 28 | fn main() { 29 | // basic instructions 30 | build_script::cargo_rerun_if_changed("something.txt"); 31 | build_script::cargo_rerun_if_env_changed("PKG_CONFIG"); 32 | build_script::cargo_rustc_link_lib("somelibrary"); 33 | build_script::cargo_rustc_link_lib_mapping(cargo_rustc_link_lib::Kind::DynamicLibrary, "somelibrary"); 34 | build_script::cargo_rustc_link_search("something-else.txt"); 35 | build_script::cargo_rustc_link_search_mapping(cargo_rustc_link_search::Kind::Crate, "something-else.txt"); 36 | build_script::cargo_rustc_flags("-l ffi"); 37 | build_script::cargo_rustc_cfg("key"); 38 | build_script::cargo_rustc_cfg_mapping("key", "value"); 39 | build_script::cargo_rustc_env("var", "value"); 40 | build_script::cargo_rustc_cdylib_link_arg("flag"); 41 | build_script::cargo_mapping("key", "value"); 42 | 43 | // other, advanced instructions 44 | let mut build_script = BuildScript::default(); 45 | let instruction = { 46 | let value = Value::Singular("something".into()); 47 | Instruction::new("instruction", value) 48 | }; 49 | 50 | // add a custom instruction to the instruction stack 51 | build_script.custom_instruction(instruction); 52 | 53 | // write all instructions to something (for this scenario, and also usually, its stdout) 54 | build_script.build(); 55 | } 56 | ``` 57 | 58 | For more information see the documentation. 59 | 60 | # Terminology 61 | ## Instruction 62 | The instruction is what is passed to cargo. An example would be `cargo:rerun-if-env-changed=ENV`. This example will 63 | also be dissected below. 64 | 65 | The instruction is split into three parts: 66 | ### Prefix 67 | The prefix is the string before the delimiter `:`: `cargo`. 68 | 69 | Usually the prefix is `cargo`, however in this crate other, custom prefixes can be used for future compatibility in case 70 | another prefix is added. 71 | 72 | ### Name 73 | The name is the string in between the delimiters `:` and `=`: `rerun-if-env-changed`. 74 | 75 | If the name is unknown, the instruction will automatically be a mapping (see below). 76 | ### Value 77 | The value is the string after the delimiter `=`: `ENV`. 78 | 79 | This represents the value of the instruction. 80 | ## Mapping 81 | There is a type of instruction which is a mapping: `cargo:KEY=VALUE`. 82 | 83 | This is, verbatim: 84 | 85 | > Metadata, used by `links` scripts. 86 | 87 | The source can be found [here](https://doc.rust-lang.org/cargo/reference/build-scripts.html). 88 | 89 | This is used when an instruction name is unknown. 90 | -------------------------------------------------------------------------------- /src/basic.rs: -------------------------------------------------------------------------------- 1 | //! The most basic usage for [`build_script`](crate). 2 | //! # Notes 3 | //! 99% of the time, all of the public functions in this crate can suffice. 4 | use crate::BuildScript; 5 | use crate::{ 6 | cargo_rustc_link_lib as cargo_rustc_link_lib_, 7 | cargo_rustc_link_search as cargo_rustc_link_search_, 8 | }; 9 | use once_cell::sync::Lazy; 10 | use std::path::PathBuf; 11 | use std::sync::{LockResult, Mutex, MutexGuard}; 12 | 13 | static BUILD_SCRIPT: Lazy> = Lazy::new(|| { 14 | let mut build_script = BuildScript::default(); 15 | build_script.now(); 16 | 17 | Mutex::new(build_script) 18 | }); 19 | 20 | /// Lock the mutex of build script mutex. This panics if the mutex is poisoned. 21 | fn lock_mutex(lock: LockResult>) -> MutexGuard { 22 | lock.expect("mutex is poisoned") 23 | } 24 | 25 | /// Wrapper for locking the build script mutex. Internally this handles locking the build script 26 | /// mutex and then panicking if mutex is poisoned. 27 | fn build_script() -> MutexGuard<'static, BuildScript<'static>> { 28 | lock_mutex(BUILD_SCRIPT.lock()) 29 | } 30 | 31 | /// Wrapper for `cargo:rerun-if-changed=PATH`. This tells Cargo when to rerun the script. 32 | pub fn cargo_rerun_if_changed(path: impl Into) { 33 | build_script().cargo_rerun_if_changed(path.into()); 34 | } 35 | 36 | /// Wrapper for `cargo:rerun-if-env-changed=VAR`. This tells Cargo when to rerun the script. 37 | pub fn cargo_rerun_if_env_changed(var: impl Into) { 38 | build_script().cargo_rerun_if_env_changed(&var.into()); 39 | } 40 | 41 | /// Wrapper for `cargo:rustc-link-lib=[KIND=]NAME`. This adds a library to link. 42 | pub fn cargo_rustc_link_lib(name: impl Into) { 43 | build_script().cargo_rustc_link_lib(None, &name.into()); 44 | } 45 | 46 | /// [`cargo_rustc_link_lib()`](cargo_rustc_link_lib), but with the `kind` parameter needed. 47 | pub fn cargo_rustc_link_lib_mapping(kind: cargo_rustc_link_lib_::Kind, name: impl Into) { 48 | build_script().cargo_rustc_link_lib(kind.into(), &name.into()); 49 | } 50 | 51 | /// Wrapper for `cargo:rustc-link-search=[KIND=]PATH`. This adds to the library search path. 52 | pub fn cargo_rustc_link_search(path: impl Into) { 53 | build_script().cargo_rustc_link_search(None, path.into()); 54 | } 55 | 56 | /// [`cargo_rustc_link_search()`](cargo_rustc_link_search), but with the `kind` parameter needed. 57 | pub fn cargo_rustc_link_search_mapping( 58 | kind: cargo_rustc_link_search_::Kind, 59 | path: impl Into, 60 | ) { 61 | build_script().cargo_rustc_link_search(kind.into(), path.into()); 62 | } 63 | 64 | /// Wrapper for `cargo:rustc-flags=FLAGS`. This passes certain flags to the compiler. 65 | pub fn cargo_rustc_flags(flags: impl Into) { 66 | build_script().cargo_rustc_flags(&flags.into()); 67 | } 68 | 69 | /// Wrapper for `cargo:rustc-cfg=KEY[="VALUE"]`. This enable compile-time `cfg` settings. 70 | pub fn cargo_rustc_cfg(key: impl Into) { 71 | build_script().cargo_rustc_cfg(&key.into(), None); 72 | } 73 | 74 | /// [`cargo_rustc_cfg()`](cargo_rustc_cfg), but with the `value` parameter needed. 75 | pub fn cargo_rustc_cfg_mapping(key: impl Into, value: impl Into) { 76 | build_script().cargo_rustc_cfg(&key.into(), Some(&value.into())); 77 | } 78 | 79 | /// Wrapper for `cargo:rustc-env=VAR=VALUE`. This sets an environment variable. 80 | pub fn cargo_rustc_env(var: impl Into, value: impl Into) { 81 | build_script().cargo_rustc_env(&var.into(), &value.into()); 82 | } 83 | 84 | /// Wrapper for `cargo:rustc-cdylib-link-arg=FLAG`. This passes custom flags to a linker for cdylib 85 | /// crates. 86 | pub fn cargo_rustc_cdylib_link_arg(flag: impl Into) { 87 | build_script().cargo_rustc_cdylib_link_arg(&flag.into()); 88 | } 89 | 90 | /// Wrapper for `cargo:warning=MESSAGE`. This displays a warning on the terminal. 91 | pub fn cargo_warning(message: impl Into) { 92 | build_script().cargo_warning(&message.into()); 93 | } 94 | 95 | /// Wrapper for `cargo:KEY=VALUE`. This is metadata, used by `links` scripts. 96 | pub fn cargo_mapping(key: impl Into, value: impl Into) { 97 | build_script().cargo_mapping(&key.into(), &value.into()); 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use gag::BufferRedirect; 103 | use serial_test::serial; 104 | use std::io::Read; 105 | 106 | fn test(func: impl Fn(), expected: &str) -> bool { 107 | let mut output = String::new(); 108 | let mut buffer = BufferRedirect::stdout().unwrap(); 109 | func(); 110 | buffer.read_to_string(&mut output).unwrap(); 111 | 112 | output.contains(expected) 113 | } 114 | 115 | macro_rules! new_test { 116 | ($name:ident, $func:expr, $expected:literal) => { 117 | #[test] 118 | #[serial] 119 | fn $name() { 120 | assert!(test($func, $expected)) 121 | } 122 | }; 123 | } 124 | 125 | new_test!( 126 | test_cargo_rerun_if_changed, 127 | || super::cargo_rerun_if_changed("path"), 128 | "cargo:rerun-if-changed=path" 129 | ); 130 | new_test!( 131 | test_cargo_rerun_if_env_changed, 132 | || super::cargo_rerun_if_env_changed("var"), 133 | "cargo:rerun-if-env-changed=var" 134 | ); 135 | new_test!( 136 | test_cargo_rustc_link_lib, 137 | || super::cargo_rustc_link_lib("name"), 138 | "cargo:rustc-link-lib=name" 139 | ); 140 | 141 | #[test] 142 | #[serial] 143 | fn test_cargo_rustc_link_lib_mapping() { 144 | use crate::cargo_rustc_link_lib::Kind; 145 | 146 | let kinds = [Kind::Framework, Kind::Static, Kind::DynamicLibrary]; 147 | 148 | for &kind in kinds.iter() { 149 | assert!(test( 150 | || super::cargo_rustc_link_lib_mapping(kind, "name"), 151 | &format!("cargo:rustc-link-lib={}=name", { 152 | let kind: String = kind.into(); 153 | kind 154 | }) 155 | )) 156 | } 157 | } 158 | 159 | new_test!( 160 | test_cargo_rustc_link_search, 161 | || super::cargo_rustc_link_search("path"), 162 | "cargo:rustc-link-search=path" 163 | ); 164 | 165 | #[test] 166 | #[serial] 167 | fn test_cargo_rustc_link_search_mapping() { 168 | use crate::cargo_rustc_link_search::Kind; 169 | 170 | let kinds = [ 171 | Kind::Framework, 172 | Kind::All, 173 | Kind::Crate, 174 | Kind::Dependency, 175 | Kind::Native, 176 | ]; 177 | 178 | for &kind in kinds.iter() { 179 | assert!(test( 180 | || super::cargo_rustc_link_search_mapping(kind, "path"), 181 | &format!("cargo:rustc-link-search={}=path", { 182 | let kind: String = kind.into(); 183 | kind 184 | }) 185 | )) 186 | } 187 | } 188 | 189 | new_test!( 190 | test_cargo_rustc_flags, 191 | || super::cargo_rustc_flags("flags"), 192 | "cargo:rustc-flags=flags" 193 | ); 194 | new_test!( 195 | test_cargo_rustc_cfg, 196 | || super::cargo_rustc_cfg("key"), 197 | "cargo:rustc-cfg=key" 198 | ); 199 | new_test!( 200 | test_cargo_rustc_cfg_mapping, 201 | || super::cargo_rustc_cfg_mapping("key", "value"), 202 | "cargo:rustc-cfg=key=\"value\"" 203 | ); 204 | new_test!( 205 | test_cargo_rustc_env, 206 | || super::cargo_rustc_env("var", "value"), 207 | "cargo:rustc-env=var=value" 208 | ); 209 | new_test!( 210 | test_cargo_rustc_cdylib_link_arg, 211 | || super::cargo_rustc_cdylib_link_arg("flag"), 212 | "cargo:rustc-cdylib-link-arg=flag" 213 | ); 214 | new_test!( 215 | test_cargo_warning, 216 | || super::cargo_warning("message"), 217 | "cargo:warning=message" 218 | ); 219 | new_test!( 220 | test_cargo_mapping, 221 | || super::cargo_mapping("key", "value"), 222 | "cargo:key=value" 223 | ); 224 | } 225 | -------------------------------------------------------------------------------- /src/cargo_rustc_link_lib.rs: -------------------------------------------------------------------------------- 1 | //! A wrapper for [`cargo_rustc_link_lib`](crate::BuildScript::cargo_rustc_link_lib). 2 | /// A kind for [`cargo_rustc_link_lib`](crate::BuildScript::cargo_rustc_link_lib). 3 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 4 | pub enum Kind { 5 | /// Known to the compiler as [`dylib`](Self::DYNAMIC_LIBRARY). 6 | DynamicLibrary, 7 | 8 | /// Known to the compiler as [`static`](Self::STATIC). 9 | Static, 10 | 11 | /// Known to the compiler as [`framework`](Self::FRAMEWORK). 12 | Framework, 13 | } 14 | 15 | impl Kind { 16 | /// Known to this library as [`DynamicLibrary`](Self::DynamicLibrary). 17 | pub const DYNAMIC_LIBRARY: &'static str = "dylib"; 18 | 19 | /// Known to this library as [`Static`](Self::Static). 20 | pub const STATIC: &'static str = "static"; 21 | 22 | /// Known to this library as [`Framework`](Self::Framework). 23 | pub const FRAMEWORK: &'static str = "framework"; 24 | } 25 | 26 | impl From for &'static str { 27 | fn from(kind: Kind) -> Self { 28 | match kind { 29 | Kind::DynamicLibrary => Kind::DYNAMIC_LIBRARY, 30 | Kind::Static => Kind::STATIC, 31 | Kind::Framework => Kind::FRAMEWORK, 32 | } 33 | } 34 | } 35 | 36 | impl From for String { 37 | fn from(kind: Kind) -> Self { 38 | let kind: &str = kind.into(); 39 | kind.into() 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::Kind; 46 | 47 | #[test] 48 | fn test_into_string() { 49 | let kind: String = Kind::DynamicLibrary.into(); 50 | assert_eq!(kind, Kind::DYNAMIC_LIBRARY); 51 | let kind: String = Kind::Static.into(); 52 | assert_eq!(kind, Kind::STATIC); 53 | let kind: String = Kind::Framework.into(); 54 | assert_eq!(kind, Kind::FRAMEWORK) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/cargo_rustc_link_search.rs: -------------------------------------------------------------------------------- 1 | //! A wrapper for [`cargo_rustc_link_search`](crate::BuildScript::cargo_rustc_link_search). 2 | /// A kind for [`cargo_rustc_link_search`](crate::BuildScript::cargo_rustc_link_search). 3 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 4 | pub enum Kind { 5 | /// Known to the compiler as [`dependency`](Self::DEPENDENCY). 6 | Dependency, 7 | 8 | /// Known to the compiler as [`crate`](Self::CRATE). 9 | Crate, 10 | 11 | /// Known to the compiler as [`native`](Self::NATIVE). 12 | Native, 13 | 14 | /// Known to the compiler as [`framework`](Self::FRAMEWORK). 15 | Framework, 16 | 17 | /// Known to the compiler as [`all`](Self::ALL). 18 | All, 19 | } 20 | 21 | impl Kind { 22 | /// Known to this library as [`Dependency`](Self::Dependency). 23 | pub const DEPENDENCY: &'static str = "dependency"; 24 | 25 | /// Known to this library as [`Crate`](Self::Crate). 26 | pub const CRATE: &'static str = "crate"; 27 | 28 | /// Known to this library as [`Native`](Self::Native). 29 | pub const NATIVE: &'static str = "native"; 30 | 31 | /// Known to this library as [`Framework`](Self::Framework). 32 | pub const FRAMEWORK: &'static str = "framework"; 33 | 34 | /// Known to this library as [`All`](Self::All). 35 | pub const ALL: &'static str = "all"; 36 | } 37 | 38 | impl From for &'static str { 39 | fn from(kind: Kind) -> Self { 40 | match kind { 41 | Kind::Dependency => Kind::DEPENDENCY, 42 | Kind::Crate => Kind::CRATE, 43 | Kind::Native => Kind::NATIVE, 44 | Kind::Framework => Kind::FRAMEWORK, 45 | Kind::All => Kind::ALL, 46 | } 47 | } 48 | } 49 | 50 | impl From for String { 51 | fn from(kind: Kind) -> Self { 52 | let kind: &str = kind.into(); 53 | kind.into() 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::Kind; 60 | 61 | #[test] 62 | fn test_into_string() { 63 | let kind: String = Kind::Dependency.into(); 64 | assert_eq!(kind, Kind::DEPENDENCY); 65 | let kind: String = Kind::Crate.into(); 66 | assert_eq!(kind, Kind::CRATE); 67 | let kind: String = Kind::Native.into(); 68 | assert_eq!(kind, Kind::NATIVE); 69 | let kind: String = Kind::Framework.into(); 70 | assert_eq!(kind, Kind::FRAMEWORK); 71 | let kind: String = Kind::All.into(); 72 | assert_eq!(kind, Kind::ALL); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | //! Core functionality for [`build_script`](crate). 2 | //! # Notes 3 | //! 99% of the time, you won't need to use this module. Instead, use the [`basic`](crate::basic) 4 | //! module instead. 5 | use crate::cargo_rustc_link_lib; 6 | use crate::cargo_rustc_link_search; 7 | use crate::utils::VecExt; 8 | use crate::{Instruction, Value}; 9 | use std::path::PathBuf; 10 | use std::{io, str}; 11 | 12 | /// A build script. This is the main struct for creating cargo arguments. 13 | /// # Notes 14 | /// 99% of the time, you won't need this. Instead, use the functions in [`basic`](crate::basic). 15 | pub struct BuildScript<'w> { 16 | /// The instruction stack. If `now` is `true`, this will not be used. 17 | instructions: Vec, 18 | 19 | /// Whether or not you should write instructions to `writer` immediately. 20 | now: bool, 21 | 22 | /// The writer where instructions will be written. 23 | /// # Notes 24 | /// 99% of the time, you can use the defaults, which is [`io::stdout()`](io::stdout). 25 | writer: &'w mut (dyn io::Write + Send), 26 | } 27 | 28 | impl Default for BuildScript<'static> { 29 | /// Get the default build script. Writer is [`io::stdout()`](io::stdout). 30 | /// # Notes 31 | /// 99% of the time, you can use this associated function instead. 32 | fn default() -> Self { 33 | let stdout = Box::new(io::stdout()); 34 | let stdout = Box::leak(stdout); 35 | Self::new(stdout) 36 | } 37 | } 38 | 39 | impl<'w> BuildScript<'w> { 40 | /// Create a new [`BuildScript`](Self). 41 | /// # Notes 42 | /// 99% of the time, you won't need to yse this associated function. The defaults can be used instead 43 | /// ([`BuildScript::default()`](Self::default)). 44 | pub fn new(writer: &'w mut (dyn io::Write + Send)) -> Self { 45 | Self { 46 | writer, 47 | instructions: Vec::new(), 48 | now: false, 49 | } 50 | } 51 | /// Sets `now` to true. 52 | pub fn now(&mut self) -> &mut Self { 53 | self.now = true; 54 | 55 | self 56 | } 57 | 58 | /// Write to `writer`. 59 | fn write(&mut self, string: &str) { 60 | /// Newline. 61 | const NEWLINE: u8 = b'\n'; 62 | 63 | let string = { 64 | let mut bytes = string.as_bytes().to_vec(); 65 | 66 | if let Some(last) = bytes.last() { 67 | if last != &NEWLINE { 68 | bytes.push(NEWLINE) 69 | } 70 | } 71 | 72 | String::from_utf8(bytes) 73 | .expect("string contained invalid utf8 even if it was already a string before") 74 | }; 75 | 76 | write!(self.writer, "{}", string).expect("failed to write to writer") 77 | } 78 | 79 | /// Write the instruction immediately if `now` is true, else push it to the instruction stack. 80 | fn parse_instruction(&mut self, instruction: Instruction) { 81 | if self.now { 82 | self.write(&instruction.to_string()) 83 | } else { 84 | self.instructions.push(instruction) 85 | } 86 | } 87 | 88 | /// Write and remove all the instructions in the stack, starting from the first. 89 | pub fn build(&mut self) { 90 | while let Some(instruction) = self.instructions.take_first() { 91 | self.write(&instruction.to_string()) 92 | } 93 | } 94 | 95 | /// Wrapper for `cargo:rerun-if-changed=PATH`. This tells Cargo when to rerun the script. 96 | pub fn cargo_rerun_if_changed(&mut self, path: PathBuf) -> &mut Self { 97 | let instruction = Instruction::new( 98 | "rerun-if-changed", 99 | Value::Singular(path.display().to_string()), 100 | ); 101 | 102 | self.custom_instruction(instruction) 103 | } 104 | 105 | /// Wrapper for `cargo:rerun-if-env-changed=VAR`. This tells Cargo when to rerun the script. 106 | pub fn cargo_rerun_if_env_changed(&mut self, var: &str) -> &mut Self { 107 | let instruction = Instruction::new("rerun-if-env-changed", Value::Singular(var.into())); 108 | 109 | self.custom_instruction(instruction) 110 | } 111 | 112 | /// Wrapper for `cargo:rustc-link-lib=[KIND=]NAME`. This adds a library to link. 113 | pub fn cargo_rustc_link_lib( 114 | &mut self, 115 | kind: Option, 116 | name: &str, 117 | ) -> &mut Self { 118 | let instruction = Instruction::new( 119 | "rustc-link-lib", 120 | Value::UnquotedOptionalKey(kind.map(Into::into), name.into()), 121 | ); 122 | 123 | self.custom_instruction(instruction) 124 | } 125 | 126 | /// Wrapper for `cargo:rustc-link-search=[KIND=]PATH`. This adds to the library search path. 127 | pub fn cargo_rustc_link_search( 128 | &mut self, 129 | kind: Option, 130 | path: PathBuf, 131 | ) -> &mut Self { 132 | let instruction = Instruction::new( 133 | "rustc-link-search", 134 | Value::UnquotedOptionalKey(kind.map(Into::into), path.display().to_string()), 135 | ); 136 | 137 | self.custom_instruction(instruction) 138 | } 139 | 140 | /// Wrapper for `cargo:rustc-flags=FLAGS`. This passes certain flags to the compiler. 141 | pub fn cargo_rustc_flags(&mut self, flags: &str) -> &mut Self { 142 | let instruction = Instruction::new("rustc-flags", Value::Singular(flags.into())); 143 | 144 | self.custom_instruction(instruction) 145 | } 146 | 147 | /// Wrapper for `cargo:rustc-cfg=KEY[="VALUE"]`. This enable compile-time `cfg` settings. 148 | pub fn cargo_rustc_cfg(&mut self, key: &str, value: Option<&str>) -> &mut Self { 149 | let instruction = Instruction::new( 150 | "rustc-cfg", 151 | Value::OptionalValue(key.into(), value.map(Into::into)), 152 | ); 153 | 154 | self.custom_instruction(instruction) 155 | } 156 | 157 | /// Wrapper for `cargo:rustc-env=VAR=VALUE`. This sets an environment variable. 158 | pub fn cargo_rustc_env(&mut self, var: &str, value: &str) -> &mut Self { 159 | let instruction = Instruction::new( 160 | "rustc-env", 161 | Value::UnquotedMapping(var.into(), value.into()), 162 | ); 163 | 164 | self.custom_instruction(instruction) 165 | } 166 | 167 | /// Wrapper for `cargo:rustc-cdylib-link-arg=FLAG`. This passes custom flags to a linker for 168 | /// cdylib crates. 169 | pub fn cargo_rustc_cdylib_link_arg(&mut self, flag: &str) -> &mut Self { 170 | let instruction = Instruction::new("rustc-cdylib-link-arg", Value::Singular(flag.into())); 171 | 172 | self.custom_instruction(instruction) 173 | } 174 | 175 | /// Wrapper for `cargo:warning=MESSAGE`. This displays a warning on the terminal. 176 | pub fn cargo_warning(&mut self, message: &str) -> &mut Self { 177 | let instruction = Instruction::new("warning", Value::Singular(message.into())); 178 | 179 | self.custom_instruction(instruction) 180 | } 181 | 182 | /// Wrapper for `cargo:KEY=VALUE`. This is metadata, used by `links` scripts. 183 | pub fn cargo_mapping(&mut self, key: &str, value: &str) -> &mut Self { 184 | let instruction = 185 | Instruction::new_mapping(Value::UnquotedMapping(key.into(), value.into())); 186 | 187 | self.custom_instruction(instruction) 188 | } 189 | 190 | /// Pass a custom instruction. Internally, [`BuildScript`](Self) uses this. This may be used 191 | /// when `build_script` isn't updated for new instructions yet in the future. 192 | pub fn custom_instruction(&mut self, instruction: Instruction) -> &mut Self { 193 | self.parse_instruction(instruction); 194 | 195 | self 196 | } 197 | } 198 | 199 | #[cfg(test)] 200 | mod tests { 201 | use super::BuildScript; 202 | use crate::{Instruction, Value}; 203 | 204 | fn parse_bytes_to_lines(bytes: &[u8]) -> Vec { 205 | let bytes = String::from_utf8_lossy(bytes).to_string(); 206 | bytes.lines().map(str::to_owned).collect() 207 | } 208 | 209 | #[test] 210 | fn test_new() { 211 | let mut writer = Vec::new(); 212 | { 213 | let mut build_script = BuildScript::new(&mut writer); 214 | build_script.build(); 215 | assert!(writer.is_empty()); 216 | } 217 | let mut build_script = BuildScript::new(&mut writer); 218 | build_script.cargo_mapping("key", "value"); 219 | assert!(writer.is_empty()); 220 | } 221 | 222 | #[test] 223 | fn test_now() { 224 | let mut writer = Vec::new(); 225 | let mut build_script = BuildScript::new(&mut writer); 226 | build_script.now(); 227 | build_script.cargo_mapping("key", "value"); 228 | let output = &parse_bytes_to_lines(&writer)[0]; 229 | assert_eq!(output, "cargo:key=value") 230 | } 231 | 232 | #[test] 233 | fn test_build() { 234 | let mut writer = Vec::new(); 235 | let mut build_script = BuildScript::new(&mut writer); 236 | build_script.cargo_mapping("key", "value"); 237 | build_script.cargo_mapping("second", "mapping"); 238 | build_script.build(); 239 | let lines = parse_bytes_to_lines(&writer); 240 | let expected = vec![ 241 | "cargo:key=value".to_string(), 242 | "cargo:second=mapping".to_string(), 243 | ]; 244 | assert_eq!(lines, expected) 245 | } 246 | 247 | #[test] 248 | fn test_cargo_rerun_if_changed() { 249 | let mut writer = Vec::new(); 250 | let mut build_script = BuildScript::new(&mut writer); 251 | build_script 252 | .cargo_rerun_if_changed("library.h".into()) 253 | .build(); 254 | let output = &parse_bytes_to_lines(&writer)[0]; 255 | let expected = "cargo:rerun-if-changed=library.h"; 256 | assert_eq!(output, expected) 257 | } 258 | 259 | #[test] 260 | fn test_cargo_rerun_if_env_changed() { 261 | let mut writer = Vec::new(); 262 | let mut build_script = BuildScript::new(&mut writer); 263 | build_script.cargo_rerun_if_env_changed("ENV").build(); 264 | let output = &parse_bytes_to_lines(&writer)[0]; 265 | let expected = "cargo:rerun-if-env-changed=ENV"; 266 | assert_eq!(output, expected) 267 | } 268 | 269 | #[test] 270 | fn test_cargo_rustc_link_lib() { 271 | use crate::cargo_rustc_link_lib::Kind; 272 | 273 | let mut writer = Vec::new(); 274 | let mut build_script = BuildScript::new(&mut writer); 275 | build_script.cargo_rustc_link_lib(None, "first"); 276 | build_script.cargo_rustc_link_lib(Kind::Framework.into(), "second"); 277 | build_script.cargo_rustc_link_lib(Kind::Static.into(), "third"); 278 | build_script.cargo_rustc_link_lib(Kind::DynamicLibrary.into(), "fourth"); 279 | build_script.build(); 280 | let output = parse_bytes_to_lines(&writer); 281 | let expected = vec![ 282 | "cargo:rustc-link-lib=first", 283 | "cargo:rustc-link-lib=framework=second", 284 | "cargo:rustc-link-lib=static=third", 285 | "cargo:rustc-link-lib=dylib=fourth", 286 | ]; 287 | 288 | assert_eq!(output, expected) 289 | } 290 | 291 | #[test] 292 | fn test_cargo_rustc_link_search() { 293 | use crate::cargo_rustc_link_search::Kind; 294 | 295 | let mut writer = Vec::new(); 296 | let mut build_script = BuildScript::new(&mut writer); 297 | build_script.cargo_rustc_link_search(Kind::Framework.into(), "first".into()); 298 | build_script.cargo_rustc_link_search(Kind::All.into(), "second".into()); 299 | build_script.cargo_rustc_link_search(Kind::Native.into(), "third".into()); 300 | build_script.cargo_rustc_link_search(Kind::Crate.into(), "fourth".into()); 301 | build_script.cargo_rustc_link_search(Kind::Dependency.into(), "fifth".into()); 302 | build_script.cargo_rustc_link_search(None, "sixth".into()); 303 | build_script.build(); 304 | let output = parse_bytes_to_lines(&writer); 305 | let expected = vec![ 306 | "cargo:rustc-link-search=framework=first", 307 | "cargo:rustc-link-search=all=second", 308 | "cargo:rustc-link-search=native=third", 309 | "cargo:rustc-link-search=crate=fourth", 310 | "cargo:rustc-link-search=dependency=fifth", 311 | "cargo:rustc-link-search=sixth", 312 | ]; 313 | 314 | assert_eq!(output, expected) 315 | } 316 | 317 | #[test] 318 | fn test_cargo_rustc_flags() { 319 | let mut writer = Vec::new(); 320 | let mut build_script = BuildScript::new(&mut writer); 321 | build_script.cargo_rustc_flags("flags").build(); 322 | let output = &parse_bytes_to_lines(&writer)[0]; 323 | let expected = "cargo:rustc-flags=flags"; 324 | 325 | assert_eq!(output, expected) 326 | } 327 | 328 | #[test] 329 | fn test_cargo_rustc_cfg() { 330 | let mut writer = Vec::new(); 331 | let mut build_script = BuildScript::new(&mut writer); 332 | build_script.cargo_rustc_cfg("key", None); 333 | build_script.cargo_rustc_cfg("key", "value".into()); 334 | build_script.build(); 335 | let output = parse_bytes_to_lines(&writer); 336 | let expected = vec!["cargo:rustc-cfg=key", "cargo:rustc-cfg=key=\"value\""]; 337 | 338 | assert_eq!(output, expected) 339 | } 340 | 341 | #[test] 342 | fn test_cargo_rustc_env() { 343 | let mut writer = Vec::new(); 344 | let mut build_script = BuildScript::new(&mut writer); 345 | build_script.cargo_rustc_env("var", "value").build(); 346 | let output = &parse_bytes_to_lines(&writer)[0]; 347 | let expected = "cargo:rustc-env=var=value"; 348 | 349 | assert_eq!(output, expected) 350 | } 351 | 352 | #[test] 353 | fn test_cargo_rustc_cdylib_link_arg() { 354 | let mut writer = Vec::new(); 355 | let mut build_script = BuildScript::new(&mut writer); 356 | build_script.cargo_rustc_cdylib_link_arg("flag").build(); 357 | let output = &parse_bytes_to_lines(&writer)[0]; 358 | let expected = "cargo:rustc-cdylib-link-arg=flag"; 359 | 360 | assert_eq!(output, expected) 361 | } 362 | 363 | #[test] 364 | fn test_cargo_warning() { 365 | let mut writer = Vec::new(); 366 | let mut build_script = BuildScript::new(&mut writer); 367 | build_script.cargo_warning("message").build(); 368 | let output = &parse_bytes_to_lines(&writer)[0]; 369 | let expected = "cargo:warning=message"; 370 | 371 | assert_eq!(output, expected) 372 | } 373 | 374 | #[test] 375 | fn test_cargo_mapping() { 376 | let mut writer = Vec::new(); 377 | let mut build_script = BuildScript::new(&mut writer); 378 | build_script.cargo_mapping("key", "value").build(); 379 | let output = &parse_bytes_to_lines(&writer)[0]; 380 | let expected = "cargo:key=value"; 381 | 382 | assert_eq!(output, expected) 383 | } 384 | 385 | #[test] 386 | fn test_custom_instruction() { 387 | let mut writer = Vec::new(); 388 | let mut build_script = BuildScript::new(&mut writer); 389 | let instruction = 390 | Instruction::new("some-instruction", Value::Singular("Hello, World!".into())); 391 | build_script.custom_instruction(instruction).build(); 392 | let output = &parse_bytes_to_lines(&writer)[0]; 393 | let expected = "cargo:some-instruction=Hello, World!"; 394 | 395 | assert_eq!(output, expected) 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/instruction.rs: -------------------------------------------------------------------------------- 1 | //! This contains the [`Instruction`](Instruction) struct. 2 | use crate::prefix::Prefix; 3 | use crate::value::Value; 4 | use std::fmt; 5 | use std::fmt::Formatter; 6 | 7 | /// An instruction. Used as a rusty way to parse arguments in build scripts. 8 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 9 | pub struct Instruction { 10 | /// The prefix. Usually [`Cargo`](Prefix::Cargo). 11 | pub prefix: Prefix, 12 | 13 | /// The name of the instruction. Most of the time it's filled in, only when a new mapping 14 | /// [`new_mapping()`](Self::new_mapping) is created it's not. 15 | pub name: Option, 16 | 17 | /// The [`Value`](Value) of the Instruction. 18 | pub value: Value, 19 | } 20 | 21 | impl Instruction { 22 | /// Create a new Instruction. 99% of the time this should suffice instead of 23 | /// [`new_mapping()`](Self::new_mapping). 24 | pub fn new(name: &str, value: Value) -> Self { 25 | Self { 26 | value, 27 | name: Some(name.into()), 28 | prefix: Default::default(), 29 | } 30 | } 31 | 32 | /// Create a new Instruction Mapping. Only 1% of the time is this proven useful, instead use 33 | /// [new()](Self::new). 34 | /// # Panics 35 | /// This panics if `value` is not a [`Mapping`](Value::Mapping) or 36 | /// [`UnquotedMapping`](Value::UnquotedMapping). 37 | pub fn new_mapping(value: Value) -> Self { 38 | if value.is_mapping() || value.is_unquoted_mapping() { 39 | Self { 40 | value, 41 | name: None, 42 | prefix: Default::default(), 43 | } 44 | } else { 45 | panic!("value type must be [Unquoted]Mapping") 46 | } 47 | } 48 | 49 | /// Set the prefix. 50 | pub fn prefix(&mut self, prefix: Prefix) -> &mut Self { 51 | self.prefix = prefix; 52 | 53 | self 54 | } 55 | 56 | /// Set the name. 57 | pub fn name(&mut self, name: &str) -> &mut Self { 58 | self.name = Some(name.into()); 59 | 60 | self 61 | } 62 | } 63 | 64 | impl fmt::Display for Instruction { 65 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 66 | if let Some(name) = &self.name { 67 | write!(f, "{}:{}={}", self.prefix, name, self.value) 68 | } else if let Value::Mapping(key, value) = &self.value { 69 | write!(f, "{}:{}={}", self.prefix, key, value) 70 | } else if let Value::UnquotedMapping(key, value) = &self.value { 71 | write!(f, "{}:{}={}", self.prefix, key, value) 72 | } else { 73 | panic!("value type must be [Unquoted]Mapping") 74 | } 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use crate::{Instruction, Prefix, Value}; 81 | 82 | macro_rules! new_wrong_value_test { 83 | ($name:ident, $value:expr) => { 84 | #[test] 85 | #[should_panic] 86 | fn $name() { 87 | Instruction::new_mapping($value); 88 | } 89 | }; 90 | } 91 | 92 | macro_rules! new_right_value_test { 93 | ($name:ident, $value:expr) => { 94 | #[test] 95 | fn $name() { 96 | Instruction::new_mapping($value); 97 | } 98 | }; 99 | } 100 | 101 | #[test] 102 | fn test_new() { 103 | let value = Value::Singular("singular".into()); 104 | let instruction = Instruction::new("name", value); 105 | assert!(matches!(instruction.prefix, Prefix::Cargo)); 106 | assert_eq!(instruction.name.expect("name is none"), "name"); 107 | assert!(instruction.value.is_singular()); 108 | } 109 | 110 | #[test] 111 | fn test_new_mapping() { 112 | let value = Value::Mapping("key".into(), "value".into()); 113 | let instruction = Instruction::new_mapping(value); 114 | assert!(instruction.name.is_none()); 115 | } 116 | 117 | new_wrong_value_test!( 118 | test_new_mapping_fails_if_value_singular, 119 | Value::Singular("".into()) 120 | ); 121 | new_wrong_value_test!( 122 | test_new_mapping_fails_if_value_optional_key, 123 | Value::OptionalKey(None, "".into()) 124 | ); 125 | new_wrong_value_test!( 126 | test_new_mapping_fails_if_value_unquoted_optional_key, 127 | Value::UnquotedOptionalKey(None, "".into()) 128 | ); 129 | new_wrong_value_test!( 130 | test_new_mapping_fails_if_value_optional_value, 131 | Value::OptionalValue("".into(), None) 132 | ); 133 | new_wrong_value_test!( 134 | test_new_mapping_fails_if_value_unquoted_optional_value, 135 | Value::UnquotedOptionalValue("".into(), None) 136 | ); 137 | new_right_value_test!( 138 | test_new_mapping_succeeds_if_value_mapping, 139 | Value::Mapping("".into(), "".into()) 140 | ); 141 | new_right_value_test!( 142 | test_new_mapping_succeeds_if_value_unquoted_mapping, 143 | Value::UnquotedMapping("".into(), "".into()) 144 | ); 145 | } 146 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A wrapper for `build.rs` instructions. 2 | //! # Examples 3 | //! ```rust 4 | //! use build_script::{cargo_rustc_link_lib, cargo_rustc_link_search, BuildScript, Instruction, Value}; 5 | //! 6 | //! // basic instructions 7 | //! build_script::cargo_rerun_if_changed("something.txt"); 8 | //! build_script::cargo_rerun_if_env_changed("PKG_CONFIG"); 9 | //! build_script::cargo_rustc_link_lib("somelibrary"); 10 | //! build_script::cargo_rustc_link_lib_mapping(cargo_rustc_link_lib::Kind::DynamicLibrary, "somelibrary"); 11 | //! build_script::cargo_rustc_link_search("something-else.txt"); 12 | //! build_script::cargo_rustc_link_search_mapping(cargo_rustc_link_search::Kind::Crate, "something-else.txt"); 13 | //! build_script::cargo_rustc_flags("-l ffi"); 14 | //! build_script::cargo_rustc_cfg("key"); 15 | //! build_script::cargo_rustc_cfg_mapping("key", "value"); 16 | //! build_script::cargo_rustc_env("var", "value"); 17 | //! build_script::cargo_rustc_cdylib_link_arg("flag"); 18 | //! build_script::cargo_mapping("key", "value"); 19 | //! 20 | //! // other, advanced instructions 21 | //! let mut build_script = BuildScript::default(); 22 | //! let instruction = { 23 | //! let value = Value::Singular("something".into()); 24 | //! Instruction::new("instruction", value) 25 | //! }; 26 | //! 27 | //! // add a custom instruction to the instruction stack 28 | //! build_script.custom_instruction(instruction); 29 | //! 30 | //! // write all instructions to something (for this scenario, and also usually, its stdout) 31 | //! build_script.build(); 32 | //! ``` 33 | #![warn(missing_docs)] 34 | #![warn(rustdoc::missing_crate_level_docs)] 35 | pub mod basic; 36 | pub mod cargo_rustc_link_lib; 37 | pub mod cargo_rustc_link_search; 38 | pub mod core; 39 | pub mod instruction; 40 | pub mod prefix; 41 | mod utils; 42 | pub mod value; 43 | 44 | pub use self::core::BuildScript; 45 | pub use basic::*; 46 | pub use instruction::Instruction; 47 | pub use prefix::Prefix; 48 | pub use value::Value; 49 | -------------------------------------------------------------------------------- /src/prefix.rs: -------------------------------------------------------------------------------- 1 | //! This contains the [`Prefix`](Prefix) enum. 2 | use std::fmt; 3 | 4 | /// The prefix. Usually [`Cargo`](Self::Cargo). 5 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 6 | pub enum Prefix { 7 | /// The cargo prefix. 99% of the time this is used. 8 | Cargo, 9 | 10 | /// Other, custom prefixes. 11 | Custom(String), 12 | } 13 | 14 | impl Default for Prefix { 15 | /// The default prefix is [`Cargo`](Self::Cargo). 16 | fn default() -> Self { 17 | Self::Cargo 18 | } 19 | } 20 | 21 | impl fmt::Display for Prefix { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | let prefix = match self { 24 | Self::Cargo => "cargo", 25 | Self::Custom(prefix) => prefix, 26 | }; 27 | 28 | prefix.fmt(f) 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use crate::Prefix; 35 | 36 | #[test] 37 | fn test_default() { 38 | let prefix = Prefix::default(); 39 | assert!(matches!(prefix, Prefix::Cargo)) 40 | } 41 | 42 | #[test] 43 | fn test_display_cargo() { 44 | let prefix = Prefix::Cargo; 45 | let string = format!("{}", prefix); 46 | assert_eq!(string, "cargo") 47 | } 48 | 49 | #[test] 50 | fn test_display_custom() { 51 | let prefix = Prefix::Custom("custom".into()); 52 | let string = format!("{}", prefix); 53 | assert_eq!(string, "custom") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Some other extra utility functions/traits that are only used internally within 2 | //! [`build_script`](crate). 3 | 4 | /// Some useful vector extensions. 5 | pub trait VecExt { 6 | /// Take ownership of an item. If the index is out of range, this returns [`None`](None). 7 | fn take(&mut self, index: usize) -> Option; 8 | 9 | /// Take ownership of the first item in a [`Vec`](Vec). If the vector is empty, this returns 10 | /// [`None`](None). 11 | fn take_first(&mut self) -> Option; 12 | } 13 | 14 | impl VecExt for Vec { 15 | fn take(&mut self, index: usize) -> Option { 16 | if self.get(index).is_none() { 17 | None 18 | } else { 19 | self.drain(index..index + 1).next() 20 | } 21 | } 22 | 23 | fn take_first(&mut self) -> Option { 24 | self.take(0) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::VecExt; 31 | 32 | #[test] 33 | fn test_take() { 34 | let mut vec = vec![1, 2, 3, 4, 5]; 35 | let first = vec.take(0); 36 | assert_eq!(first, Some(1)); 37 | let none = vec.take(999); 38 | assert!(none.is_none()) 39 | } 40 | 41 | #[test] 42 | fn test_take_first() { 43 | let mut vec = vec![1, 2, 3, 4, 5]; 44 | let first = vec.take_first(); 45 | assert_eq!(first, Some(1)); 46 | vec.clear(); 47 | let none = vec.take_first(); 48 | assert!(none.is_none()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | //! This contains the [`Value`](Value) struct. 2 | use std::fmt; 3 | 4 | /// The value of an [`Instruction`](crate::Instruction). 5 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 6 | pub enum Value { 7 | /// A singular value. 8 | Singular(String), 9 | 10 | /// A mapping (2 values). 11 | Mapping(String, String), 12 | 13 | /// A mapping with an optional key. 14 | OptionalKey(Option, String), 15 | 16 | /// A mapping with an optional key. Once printed, the value won't have quotes. 17 | UnquotedOptionalKey(Option, String), 18 | 19 | /// A mapping with an optional value. 20 | OptionalValue(String, Option), 21 | 22 | /// A mapping with an optional value. Once printed, if the value exists, the value won't have 23 | /// quotes. 24 | UnquotedOptionalValue(String, Option), 25 | 26 | /// A mapping with the value not having quotes. 27 | UnquotedMapping(String, String), 28 | } 29 | 30 | impl Value { 31 | /// Returns `true` if the value is a [`Singular`](Self::Singular) value. 32 | pub fn is_singular(&self) -> bool { 33 | matches!(*self, Self::Singular(_)) 34 | } 35 | 36 | /// Returns `true` if the value is a [`Mapping`](Self::Mapping) value. 37 | pub fn is_mapping(&self) -> bool { 38 | matches!(*self, Self::Mapping(_, _)) 39 | } 40 | 41 | /// Returns `true` if the value is an [`OptionalKey`](Self::OptionalKey) value. 42 | pub fn is_optional_key(&self) -> bool { 43 | matches!(*self, Self::OptionalKey(_, _)) 44 | } 45 | 46 | /// Returns `true` if the value is an [`UnquotedOptionalKey`](Self::UnquotedOptionalKey) value. 47 | pub fn is_unquoted_optional_key(&self) -> bool { 48 | matches!(*self, Self::UnquotedOptionalKey(_, _)) 49 | } 50 | 51 | /// Returns `true` if the value is an [`OptionalValue`](Self::OptionalValue) value. 52 | pub fn is_optional_value(&self) -> bool { 53 | matches!(*self, Self::OptionalValue(_, _)) 54 | } 55 | 56 | /// Returns `true` if the value is an [`UnquotedOptionalValue`](Self::UnquotedOptionalValue) value. 57 | pub fn is_unquoted_optional_value(&self) -> bool { 58 | matches!(*self, Self::UnquotedOptionalValue(_, _)) 59 | } 60 | 61 | /// Returns `true` if the value is an [`UnquotedMapping`](Self::UnquotedMapping) value. 62 | pub fn is_unquoted_mapping(&self) -> bool { 63 | matches!(*self, Self::UnquotedMapping(_, _)) 64 | } 65 | } 66 | 67 | impl fmt::Display for Value { 68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 69 | match self { 70 | Self::Singular(value) => write!(f, "{}", value), 71 | Self::Mapping(key, value) => write!(f, "{}=\"{}\"", key, value), 72 | Self::OptionalKey(key, value) => { 73 | if let Some(key) = key { 74 | write!(f, "{}=\"{}\"", key, value) 75 | } else { 76 | write!(f, "{}", value) 77 | } 78 | } 79 | Self::UnquotedOptionalKey(key, value) => { 80 | if let Some(key) = key { 81 | write!(f, "{}={}", key, value) 82 | } else { 83 | write!(f, "{}", value) 84 | } 85 | } 86 | Self::OptionalValue(key, value) => { 87 | if let Some(value) = value { 88 | write!(f, "{}=\"{}\"", key, value) 89 | } else { 90 | write!(f, "{}", key) 91 | } 92 | } 93 | Self::UnquotedOptionalValue(key, value) => { 94 | if let Some(value) = value { 95 | write!(f, "{}={}", key, value) 96 | } else { 97 | write!(f, "{}", key) 98 | } 99 | } 100 | Self::UnquotedMapping(key, value) => write!(f, "{}={}", key, value), 101 | } 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use crate::Value; 108 | 109 | macro_rules! new_display_test { 110 | ($name:ident, $value:expr, $expected:literal) => { 111 | #[test] 112 | fn $name() { 113 | let value = $value; 114 | let output = format!("{}", value); 115 | let expected = $expected; 116 | 117 | assert_eq!(output, expected) 118 | } 119 | }; 120 | } 121 | 122 | #[test] 123 | fn test_is_singular() { 124 | assert!(Value::Singular("".into()).is_singular()) 125 | } 126 | 127 | new_display_test!( 128 | test_singular_display, 129 | Value::Singular("singular".into()), 130 | "singular" 131 | ); 132 | 133 | #[test] 134 | fn test_is_mapping() { 135 | assert!(Value::Mapping("".into(), "".into()).is_mapping()) 136 | } 137 | 138 | new_display_test!( 139 | test_mapping_display, 140 | Value::Mapping("key".into(), "value".into()), 141 | "key=\"value\"" 142 | ); 143 | 144 | #[test] 145 | fn test_is_optional_key() { 146 | assert!(Value::OptionalKey(None, "".into()).is_optional_key()) 147 | } 148 | 149 | new_display_test!( 150 | test_optional_key_display, 151 | Value::OptionalKey(Some("key".into()), "value".into()), 152 | "key=\"value\"" 153 | ); 154 | new_display_test!( 155 | test_optional_key_display_none, 156 | Value::OptionalKey(None, "value".into()), 157 | "value" 158 | ); 159 | 160 | #[test] 161 | fn test_is_unquoted_optional_key() { 162 | assert!(Value::UnquotedOptionalKey(None, "".into()).is_unquoted_optional_key()) 163 | } 164 | 165 | new_display_test!( 166 | test_unquoted_optional_key_display, 167 | Value::UnquotedOptionalKey(Some("key".into()), "value".into()), 168 | "key=value" 169 | ); 170 | new_display_test!( 171 | test_unquoted_optional_key_display_none, 172 | Value::UnquotedOptionalKey(None, "value".into()), 173 | "value" 174 | ); 175 | 176 | #[test] 177 | fn test_is_optional_value() { 178 | assert!(Value::OptionalValue("".into(), None).is_optional_value()) 179 | } 180 | 181 | new_display_test!( 182 | test_optional_value_display, 183 | Value::OptionalValue("key".into(), Some("value".into())), 184 | "key=\"value\"" 185 | ); 186 | new_display_test!( 187 | test_optional_value_display_none, 188 | Value::OptionalValue("key".into(), None), 189 | "key" 190 | ); 191 | 192 | #[test] 193 | fn test_is_unquoted_optional_value() { 194 | assert!(Value::UnquotedOptionalValue("".into(), None).is_unquoted_optional_value()) 195 | } 196 | 197 | new_display_test!( 198 | test_unquoted_optional_value_display, 199 | Value::UnquotedOptionalValue("key".into(), Some("value".into())), 200 | "key=value" 201 | ); 202 | new_display_test!( 203 | test_unquoted_optional_value_display_none, 204 | Value::UnquotedOptionalValue("key".into(), None), 205 | "key" 206 | ); 207 | 208 | #[test] 209 | fn test_is_unquoted_mapping() { 210 | assert!(Value::UnquotedMapping("".into(), "".into()).is_unquoted_mapping()) 211 | } 212 | 213 | new_display_test!( 214 | test_unquoted_mapping_display, 215 | Value::UnquotedMapping("key".into(), "value".into()), 216 | "key=value" 217 | ); 218 | } 219 | --------------------------------------------------------------------------------