Skip to content

Commit 68aae61

Browse files
committed
Refactor + Implicit crates
Refactors the codebase to be significantly cleaner, and implements passing --extern which allows you to use crates without . This also avoids using a temporary file to compile to rustc and instead pipes the source to rustc. One less file to worry about clogging up your target dir.
1 parent 01aefc3 commit 68aae61

File tree

4 files changed

+102
-61
lines changed

4 files changed

+102
-61
lines changed

Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
[package]
22
name = "constime"
3-
authors = ["David Cruz <[email protected]>"]
4-
repository = "https://github.com/DvvCz/Constime"
53
description = "Zig's comptime for Rust with zero dependencies. Mostly something to play around with until more stuff is const fn."
6-
version = "0.2.2"
4+
version = "0.3.0"
5+
6+
authors = ["David Cruz <[email protected]>"]
7+
repository = "https://github.com/DvvCz/Constime"
78
edition = "2021"
89
license = "MIT"
910

@@ -12,4 +13,4 @@ proc-macro = true
1213
doctest = false
1314

1415
[dev-dependencies]
15-
ureq = "2.6.1"
16+
ureq = "2.12.1"

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
cargo add constime
2323
```
2424

25-
Dependencies in `comptime!` can be stored in either `[dependencies]` or `[build-dependencies]`, and must be explicitly imported using `extern crate`.
25+
Dependencies in `comptime!` can be stored in either `[dependencies]` or `[build-dependencies]`.
2626

27-
You will also need a build.rs file in order to force `[build-dependencies]` to compile.
27+
If you use build dependencies, you will need to explicitly link with `extern crate` and a `build.rs` file to force them to compile.
2828

2929
## Example
3030

@@ -35,7 +35,12 @@ fn main() {
3535
// Let's use a pure-build time dependency
3636
println!("Here's a fact about the number 5: {}", comptime! {
3737
extern crate ureq;
38-
ureq::get("http://numbersapi.com/5/math").call().unwrap().into_string().unwrap()
38+
39+
ureq::get("http://numbersapi.com/5/math")
40+
.call()
41+
.unwrap()
42+
.into_string()
43+
.unwrap()
3944
});
4045

4146
// Standard library works fine too.

src/lib.rs

Lines changed: 88 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,114 @@
22

33
extern crate proc_macro;
44
use proc_macro::TokenStream;
5-
use std::hash::{Hash, Hasher};
5+
6+
use std::hash::{BuildHasher, RandomState};
7+
use std::io::Write;
8+
9+
/// Properly passes an error message to the compiler without crashing macro engines.
10+
macro_rules! build_error {
11+
($($arg:tt)*) => {
12+
format!("compile_error!(r#\"{}\"#)", format!($($arg)*))
13+
.parse::<TokenStream>()
14+
.unwrap()
15+
};
16+
}
617

718
#[proc_macro]
819
#[doc = include_str!("../README.md")]
920
pub fn comptime(code: TokenStream) -> TokenStream {
10-
let (mut externs, mut out_dir, mut args) = (vec![], None, std::env::args());
21+
let mut out_dir = None;
22+
let mut externs = vec![];
23+
24+
let mut args = std::env::args();
1125
while let Some(arg) = args.next() {
12-
if arg == "--out-dir" {
13-
out_dir = args.next();
14-
} else if arg == "--extern" {
15-
externs.push("--extern".to_owned());
26+
// Push deps to rustc so you don't need to explicitly link with 'extern crate'
27+
if arg == "--extern" {
1628
externs.push(args.next().unwrap());
29+
} else if arg == "--out-dir" {
30+
out_dir = args.next().map(std::path::PathBuf::from);
31+
}
32+
}
33+
34+
if out_dir.is_none() {
35+
let out = std::env::current_dir().unwrap().join("target").join("debug").join("deps");
36+
if out.exists() {
37+
out_dir = Some(out);
1738
}
1839
}
1940

2041
let Some(out_dir) = out_dir else {
21-
return "compile_error!(\"Could not find output directory.\")".parse().unwrap()
42+
return build_error!("Could not find output directory.");
2243
};
2344

24-
let code = format!("fn main(){{ println!(\"{{:?}}\", {{ {code} }}) }}");
25-
26-
let mut hash = std::collections::hash_map::DefaultHasher::new();
27-
code.hash(&mut hash);
28-
let hash = hash.finish();
29-
30-
let output_file = format!("{out_dir}{}constime-{hash}.exe", std::path::MAIN_SEPARATOR);
31-
let out_path = std::path::Path::new(&output_file);
32-
if out_path.exists() {
33-
let ext = out_path.with_extension("err");
34-
if ext.exists() {
35-
return format!(
36-
"compile_error!(r#\"{}\"#)",
37-
std::fs::read_to_string(ext).expect("Error when compiling")
38-
)
39-
.parse()
40-
.unwrap();
41-
}
42-
} else {
43-
let input_file = format!("{out_dir}{}constime-{hash}.rs", std::path::MAIN_SEPARATOR);
44-
std::fs::write(&input_file, code).expect("Failed to write temporary file to output");
45+
let wrapped_code = format!(r#"
46+
fn main() {{
47+
println!("{{:?}}", {{ {code} }});
48+
}}
49+
"#);
50+
51+
let hash = RandomState::new().hash_one(&wrapped_code);
52+
53+
let constime_base = out_dir.join("constime");
54+
if !constime_base.exists() {
55+
std::fs::create_dir(&constime_base).unwrap();
56+
}
4557

46-
let rustc = std::process::Command::new("rustc")
58+
let evaluator_base = constime_base
59+
.join(hash.to_string());
60+
61+
if !evaluator_base.exists() { // This hasn't been compiled yet.
62+
let mut rustc = std::process::Command::new("rustc");
63+
rustc
4764
.stderr(std::process::Stdio::piped())
48-
.args([&input_file, "-o", &output_file])
49-
.args(["-L", &out_dir])
50-
.output();
51-
52-
match rustc {
53-
Err(why) => return format!("compile_error!(r#\"{}\"#)", why).parse().unwrap(),
54-
Ok(output) if !output.status.success() => {
55-
return format!(
56-
"compile_error!(r#\"{}\"#)",
57-
std::str::from_utf8(&output.stderr).unwrap()
58-
)
59-
.parse()
60-
.unwrap()
61-
}
62-
_ => (),
65+
.stdin(std::process::Stdio::piped())
66+
.current_dir(constime_base)
67+
.arg("-L")
68+
.arg(out_dir)
69+
.arg("-o")
70+
.arg(&evaluator_base)
71+
.arg("-");
72+
73+
for ext in &externs {
74+
rustc.arg("--extern").arg(ext);
75+
}
76+
77+
let Ok(mut rustc) = rustc.spawn() else {
78+
return build_error!("Failed to spawn rustc");
79+
};
80+
81+
// Avoid deadlock by containing stdin handling in its own scope
82+
if let Some(mut stdin) = rustc.stdin.take() {
83+
if stdin.write_all(wrapped_code.as_bytes()).is_err() {
84+
return build_error!("Failed to write to rustc stdin");
85+
};
86+
} else {
87+
return build_error!("Failed to open stdin for rustc");
88+
}
89+
90+
let Ok(output) = rustc.wait_with_output() else {
91+
return build_error!("Failed to wait for rustc");
92+
};
93+
94+
if !output.status.success() {
95+
return build_error!("{}", String::from_utf8_lossy(&output.stderr));
6396
}
6497
}
6598

66-
let out = std::process::Command::new(&output_file)
99+
let out = std::process::Command::new(&evaluator_base)
67100
.stdout(std::process::Stdio::piped())
68101
.output();
69102

70103
match out {
71-
Err(why) => {
72-
std::fs::write(out_path.with_extension("err"), why.to_string()).unwrap();
73-
format!("compile_error!(r#\"Failed to execute code: {why}\"#)")
74-
.parse()
75-
.unwrap()
104+
Err(why) => return build_error!("Failed to execute code: {why}"),
105+
Ok(out) => {
106+
let out = String::from_utf8_lossy(&out.stdout);
107+
108+
let Ok(out) = out.parse() else {
109+
return build_error!("Failed to parse output into a TokenStream");
110+
};
111+
112+
return out;
76113
}
77-
Ok(out) => std::str::from_utf8(&out.stdout).unwrap().parse().unwrap(),
78114
}
79-
}
115+
}

tests/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ fn test_base() {
1212
}
1313

1414
#[test]
15-
fn test_ureq() {
15+
fn test_dev_ureq() {
1616
let retrieved = comptime! {
17-
extern crate ureq;
1817
ureq::get("https://gist.githubusercontent.com/DvvCz/9972f1627f8418badb1736d6899d5f44/raw/b31a3627458a698dc029750dfc2572a6f8a131cf/test.txt").call().unwrap().into_string().unwrap()
1918
};
2019

0 commit comments

Comments
 (0)