├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── simple.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cake" 3 | version = "0.1.0" 4 | authors = ["ticki "] 5 | description = "A delicious build tool with a obvious syntax." 6 | repository = "https://github.com/ticki/cake" 7 | documentation = "https://docs.rs/cake" 8 | license = "MIT" 9 | keywords = ["build", "make", "cake", "compile", "parallel"] 10 | exclude = ["target", "Cargo.lock"] 11 | 12 | [dependencies] 13 | rayon = "0.4" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ticki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :cake: Cake 2 | =========== 3 | 4 | Cake is a simple, Rustic build tool, which is configured through the advanced macro system of Rust, making it very flexible and expressive. 5 | 6 | Features & advantages 7 | --------------------- 8 | 9 | 1. A sane and obvious syntax. 10 | 2. Fast parallel builds through work-stealing. 11 | 3. Ahead of time compilation. 12 | 4. Efficient dependency resolution. 13 | 14 | An example 15 | ---------- 16 | 17 | ```rust 18 | #[macro_use] 19 | extern crate cake; 20 | 21 | build! { 22 | start(sodium, libstd) => cmd!("ls"; in "src"), 23 | sodium(libstd, libextra) => println!("yay"), 24 | libstd() => println!("libstd"), 25 | libextra(run) => cmd!("ls"; where LAL = "2"), 26 | run() => println!("check"), 27 | } 28 | ``` 29 | 30 | The syntax 31 | ---------- 32 | 33 | The build is declared through the `build!` macro, which, when invoked, expands to the main function. The `build!` macro takes a block, containing a match like syntax: 34 | 35 | ```rust 36 | recipe(dependencies...) => instructions 37 | ``` 38 | 39 | The first denotes the name of the build recipe. `dependencies`, delimited by `()` and splited by commas, denotes what build recipe this recipe depends on, i.e., requires to build. 40 | 41 | A recipe can be failed by returning `Err(())`, e.g. using `try!()` on a result. 42 | 43 | For the extra helper macros, see the rendered docs. 44 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cake; 3 | 4 | const LS_FLAGS: &'static [&'static str] = &["-a", "/"]; 5 | 6 | build! { 7 | start(sodium, libstd) => cmd!("ls"; in "src"), 8 | sodium(libstd, libextra) => println!("yay"), 9 | libstd() => println!("libstd"), 10 | libextra(run, other) => cmd!("ls"; where LAL = "2"), 11 | other(run) => cmd!("ls", LS_FLAGS), 12 | run() => println!("check"), 13 | } 14 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub extern crate rayon; 2 | 3 | use std::slice; 4 | 5 | /// Execute a command. 6 | /// 7 | /// Each argument after the first, denoting the executable, represents a respective argument passed 8 | /// to the executable. Optionally, you can specify the working directory by prepending `; in 9 | /// "blah" ` to the argument list. Any arbitrary number of environment variables can be declared 10 | /// through `where VAR = VAL`, delimited by `;`. 11 | /// 12 | /// An argument can be of any type implementing `AsSlice` where `T: AsRef`, that is, it 13 | /// can take both strings and arrays of strings. 14 | /// 15 | /// Examples 16 | /// ======== 17 | /// 18 | /// To run a command in the current working directory: 19 | /// 20 | /// ```rust 21 | /// cmd!("ls", "/"); // 'ls /' 22 | /// ``` 23 | /// 24 | /// If we want to run this in a custom working directory, we can do: 25 | /// 26 | /// ```rust 27 | /// cmd!("ls", "."; in "/"); 28 | /// ``` 29 | /// 30 | /// In case we want to set environment variables, we do: 31 | /// 32 | /// ```rust 33 | /// cmd!("ls", "."; where BLAH = "/"; where BLUH = "!"); 34 | /// ``` 35 | #[macro_export] 36 | macro_rules! cmd { 37 | ($cmd:expr $(, $arg:expr)*; in $cd:expr $(; where $var:ident = $val:expr)*) => {{ 38 | use std::process; 39 | 40 | if process::Command::new($cmd) 41 | $( 42 | .args({ 43 | use $crate::ToSlice; 44 | ($arg).to_slice() 45 | }) 46 | )* 47 | $( 48 | .env(stringify!($var), $val) 49 | )* 50 | .current_dir($cd) 51 | .stdout(process::Stdio::inherit()) 52 | .stderr(process::Stdio::inherit()) 53 | .status().is_err() { 54 | return Err(()); 55 | } 56 | }}; 57 | ($cmd:expr $(, $arg:expr)* $(; where $var:ident = $val:expr)*) => {{ 58 | use std::process; 59 | 60 | if process::Command::new($cmd) 61 | $( 62 | .args({ 63 | use $crate::ToSlice; 64 | ($arg).to_slice() 65 | }) 66 | )* 67 | $( 68 | .env(stringify!($var), $val) 69 | )* 70 | .stdout(process::Stdio::inherit()) 71 | .stderr(process::Stdio::inherit()) 72 | .status().is_err() { 73 | return Err(()); 74 | } 75 | }}; 76 | } 77 | 78 | /// Helper trait for converting types into slices. 79 | pub trait ToSlice { 80 | /// Convert this type into a slice. 81 | fn to_slice(&self) -> &[T]; 82 | } 83 | 84 | impl ToSlice for T { 85 | fn to_slice(&self) -> &[T] { 86 | unsafe { slice::from_raw_parts(self as *const T, 1) } 87 | } 88 | } 89 | 90 | impl ToSlice for [T] { 91 | fn to_slice(&self) -> &[T] { 92 | self 93 | } 94 | } 95 | 96 | /// Evaluate N expressions in parallel. 97 | /// 98 | /// This uses work-stealing as back end. 99 | #[macro_export] 100 | macro_rules! par { 101 | () => { 102 | Ok(()) 103 | }; 104 | ($a:expr) => {{ 105 | try!($a); 106 | Ok(()) 107 | }}; 108 | ($a:expr, $($rest:expr),*) => {{ 109 | let (a, b) = $crate::rayon::join(|| $a, || par!($($rest),*)); 110 | try!(a); 111 | try!(b); 112 | Ok(()) 113 | }}; 114 | } 115 | 116 | /// Define a build. 117 | /// 118 | /// This macro takes a block, which resembles the `match` syntax. Left to each of the arms are the 119 | /// recipe's (compilation unit) name, which is followed by the names of the recipes it depends on, 120 | /// delimited by `()`. Right to the arm is the recipe's instructions are placed. This is simply 121 | /// normal Rust code, defining the recipe. 122 | /// 123 | /// Additionally, this expands to the main function, which handles arguments as well. 124 | /// 125 | /// Examples 126 | /// ======== 127 | /// 128 | /// ```rust 129 | /// #[macro_use] 130 | /// extern crate cake; 131 | /// 132 | /// build! { 133 | /// start(sodium, libstd) => cmd!("ls"), 134 | /// sodium(libstd, libextra) => println!("yay"), 135 | /// libstd() => println!("libstd"), 136 | /// libextra() => println!("libextra"), 137 | /// } 138 | /// ``` 139 | #[macro_export] 140 | macro_rules! build { 141 | { $($name:ident($($dep:ident),*) => $cont:expr,)* } => { 142 | use std::io::Write; 143 | use std::sync::Mutex; 144 | use std::{env, io, process}; 145 | 146 | enum Error { 147 | Failed(&'static str), 148 | NotFound, 149 | } 150 | 151 | #[derive(Default)] 152 | struct CakeBuild { 153 | $( 154 | $name: Mutex 155 | ),* 156 | } 157 | 158 | fn unit(name: &str) { 159 | let mut stdout = io::stdout(); 160 | writeln!(stdout, "== Running recipe {} ==", name).unwrap(); 161 | } 162 | 163 | impl CakeBuild { 164 | $( 165 | fn $name(&self) -> Result<(), Error> { 166 | fn inner() -> Result<(), ()> { 167 | unit(stringify!($name)); 168 | $cont; 169 | Ok(()) 170 | } 171 | 172 | try!(par!( 173 | $( 174 | { 175 | let mut lock = self.$dep.lock().unwrap(); 176 | if !*lock { 177 | try!(self.$dep()); 178 | } 179 | 180 | *lock = true; 181 | 182 | Ok(()) 183 | } 184 | ),* 185 | )); 186 | 187 | inner().map_err(|_| Error::Failed(stringify!($name))) 188 | } 189 | )* 190 | 191 | fn run_recipe(&self, cmd: &str) -> Result<(), Error> { 192 | match cmd { 193 | $( 194 | stringify!($name) => { 195 | let res = self.$name(); 196 | *self.$name.lock().unwrap() = true; 197 | res 198 | } 199 | )* 200 | _ => Err(Error::NotFound), 201 | } 202 | } 203 | } 204 | 205 | fn main() { 206 | let build = CakeBuild::default(); 207 | let stderr = io::stderr(); 208 | 209 | let mut run = false; 210 | for i in env::args().skip(1) { 211 | run = true; 212 | 213 | if let Err(x) = build.run_recipe(&i) { 214 | let mut stderr = stderr.lock(); 215 | match x { 216 | Error::NotFound => writeln!(stderr, "recipe, {}, not found.", i), 217 | Error::Failed(name) => writeln!(stderr, "recipe, {}, failed.", name), 218 | }.unwrap(); 219 | stderr.flush().unwrap(); 220 | process::exit(1); 221 | } 222 | } 223 | 224 | let mut stderr = stderr.lock(); 225 | if !run { 226 | writeln!(stderr, "No argument given. Aborting.").unwrap(); 227 | } 228 | } 229 | }; 230 | } 231 | --------------------------------------------------------------------------------