9192: internal: Build test-macros in a build script r=jonas-schievink a=jonas-schievink

This build the test-proc-macros in `proc_macro_test` in a build script, and copies the artifact to `OUT_DIR`. This should make it available throughout all of rust-analyzer at no cost other than depending on `proc_macro_test`, fixing https://github.com/rust-analyzer/rust-analyzer/issues/9067.

This hopefully will let us later write inline tests that utilize proc macros, which makes my life fixing proc macro bugs easier.

Opening this as a sort of RFC, because I'm not totally sure this approach is the best.

Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
This commit is contained in:
bors[bot] 2021-06-11 10:44:07 +00:00 committed by GitHub
commit 050232a37e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 140 additions and 79 deletions

9
Cargo.lock generated
View File

@ -1158,6 +1158,15 @@ dependencies = [
[[package]]
name = "proc_macro_test"
version = "0.0.0"
dependencies = [
"cargo_metadata",
"proc_macro_test_impl",
"toolchain",
]
[[package]]
name = "proc_macro_test_impl"
version = "0.0.0"
[[package]]
name = "profile"

View File

@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = ["xtask/", "lib/*", "crates/*"]
exclude = ["crates/proc_macro_test/imp"]
[profile.dev]
# Disabling debug info speeds up builds a bunch,

View File

@ -188,7 +188,9 @@ impl Expander {
/// Copy the dylib to temp directory to prevent locking in Windows
#[cfg(windows)]
fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
use std::{ffi::OsString, time::SystemTime};
use std::collections::hash_map::RandomState;
use std::ffi::OsString;
use std::hash::{BuildHasher, Hasher};
let mut to = std::env::temp_dir();
@ -199,10 +201,11 @@ fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
)
})?;
// generate a time deps unique number
let t = SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("Time went backwards");
// Generate a unique number by abusing `HashMap`'s hasher.
// Maybe this will also "inspire" a libs team member to finally put `rand` in libstd.
let t = RandomState::new().build_hasher().finish();
let mut unique_name = OsString::from(t.as_millis().to_string());
let mut unique_name = OsString::from(t.to_string());
unique_name.push(file_name);
to.push(unique_name);

View File

@ -7,35 +7,8 @@ use proc_macro_api::ListMacrosTask;
use std::str::FromStr;
pub mod fixtures {
use cargo_metadata::Message;
use std::path::PathBuf;
use std::process::Command;
// Use current project metadata to get the proc-macro dylib path
pub fn proc_macro_test_dylib_path() -> std::path::PathBuf {
let name = "proc_macro_test";
let version = "0.0.0";
let command = Command::new(toolchain::cargo())
.args(&["check", "--tests", "--message-format", "json"])
.output()
.unwrap()
.stdout;
for message in Message::parse_stream(command.as_slice()) {
match message.unwrap() {
Message::CompilerArtifact(artifact) => {
if artifact.target.kind.contains(&"proc-macro".to_string()) {
let repr = format!("{} {}", name, version);
if artifact.package_id.repr.starts_with(&repr) {
return PathBuf::from(&artifact.filenames[0]);
}
}
}
_ => (), // Unknown message
}
}
panic!("No proc-macro dylib for {} found!", name);
proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
}
}

View File

@ -8,4 +8,8 @@ publish = false
[lib]
doctest = false
proc-macro = true
[build-dependencies]
proc_macro_test_impl = { path = "imp", version = "0.0.0" }
toolchain = { path = "../toolchain", version = "0.0.0" }
cargo_metadata = "0.13"

View File

@ -0,0 +1,48 @@
//! This will build the proc macro in `imp`, and copy the resulting dylib artifact into the
//! `OUT_DIR`.
//!
//! `proc_macro_test` itself contains only a path to that artifact.
use std::{
env, fs,
path::{Path, PathBuf},
process::Command,
};
use cargo_metadata::Message;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let name = "proc_macro_test_impl";
let version = "0.0.0";
let output = Command::new(toolchain::cargo())
.current_dir("imp")
.args(&["build", "-p", "proc_macro_test_impl", "--message-format", "json"])
.output()
.unwrap();
assert!(output.status.success());
let mut artifact_path = None;
for message in Message::parse_stream(output.stdout.as_slice()) {
match message.unwrap() {
Message::CompilerArtifact(artifact) => {
if artifact.target.kind.contains(&"proc-macro".to_string()) {
let repr = format!("{} {}", name, version);
if artifact.package_id.repr.starts_with(&repr) {
artifact_path = Some(PathBuf::from(&artifact.filenames[0]));
}
}
}
_ => (), // Unknown message
}
}
let src_path = artifact_path.expect("no dylib for proc_macro_test_impl found");
let dest_path = out_dir.join(src_path.file_name().unwrap());
fs::copy(src_path, &dest_path).unwrap();
let info_path = out_dir.join("proc_macro_test_location.txt");
fs::write(info_path, dest_path.to_str().unwrap()).unwrap();
}

2
crates/proc_macro_test/imp/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target/
Cargo.lock

View File

@ -0,0 +1,17 @@
[package]
name = "proc_macro_test_impl"
version = "0.0.0"
license = "MIT OR Apache-2.0"
authors = ["rust-analyzer developers"]
edition = "2018"
publish = false
[lib]
doctest = false
proc-macro = true
[workspace]
[dependencies]
# this crate should not have any dependencies, since it uses its own workspace,
# and its own `Cargo.lock`

View File

@ -0,0 +1,48 @@
//! Exports a few trivial procedural macros for testing.
use proc_macro::TokenStream;
#[proc_macro]
pub fn fn_like_noop(args: TokenStream) -> TokenStream {
args
}
#[proc_macro]
pub fn fn_like_panic(args: TokenStream) -> TokenStream {
panic!("fn_like_panic!({})", args);
}
#[proc_macro]
pub fn fn_like_error(args: TokenStream) -> TokenStream {
format!("compile_error!(\"fn_like_error!({})\");", args).parse().unwrap()
}
#[proc_macro_attribute]
pub fn attr_noop(_args: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn attr_panic(args: TokenStream, item: TokenStream) -> TokenStream {
panic!("#[attr_panic {}] {}", args, item);
}
#[proc_macro_attribute]
pub fn attr_error(args: TokenStream, item: TokenStream) -> TokenStream {
format!("compile_error!(\"#[attr_error({})] {}\");", args, item).parse().unwrap()
}
#[proc_macro_derive(DeriveEmpty)]
pub fn derive_empty(_item: TokenStream) -> TokenStream {
TokenStream::new()
}
#[proc_macro_derive(DerivePanic)]
pub fn derive_panic(item: TokenStream) -> TokenStream {
panic!("#[derive(DerivePanic)] {}", item);
}
#[proc_macro_derive(DeriveError)]
pub fn derive_error(item: TokenStream) -> TokenStream {
format!("compile_error!(\"#[derive(DeriveError)] {}\");", item).parse().unwrap()
}

View File

@ -1,48 +1,4 @@
//! Exports a few trivial procedural macros for testing.
use proc_macro::TokenStream;
#[proc_macro]
pub fn fn_like_noop(args: TokenStream) -> TokenStream {
args
}
#[proc_macro]
pub fn fn_like_panic(args: TokenStream) -> TokenStream {
panic!("fn_like_panic!({})", args);
}
#[proc_macro]
pub fn fn_like_error(args: TokenStream) -> TokenStream {
format!("compile_error!(\"fn_like_error!({})\");", args).parse().unwrap()
}
#[proc_macro_attribute]
pub fn attr_noop(_args: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn attr_panic(args: TokenStream, item: TokenStream) -> TokenStream {
panic!("#[attr_panic {}] {}", args, item);
}
#[proc_macro_attribute]
pub fn attr_error(args: TokenStream, item: TokenStream) -> TokenStream {
format!("compile_error!(\"#[attr_error({})] {}\");", args, item).parse().unwrap()
}
#[proc_macro_derive(DeriveEmpty)]
pub fn derive_empty(_item: TokenStream) -> TokenStream {
TokenStream::new()
}
#[proc_macro_derive(DerivePanic)]
pub fn derive_panic(item: TokenStream) -> TokenStream {
panic!("#[derive(DerivePanic)] {}", item);
}
#[proc_macro_derive(DeriveError)]
pub fn derive_error(item: TokenStream) -> TokenStream {
format!("compile_error!(\"#[derive(DeriveError)] {}\");", item).parse().unwrap()
}
pub static PROC_MACRO_TEST_LOCATION: &str =
include_str!(concat!(env!("OUT_DIR"), "/proc_macro_test_location.txt"));