-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
introduce a build-rs and a
cargo-duchess
utility (#187)
* introduce ClassDecl wrapper struct * introduce build-rs lib, add to integration tests Doesn't really do anything yet, but it does parse and validate the various duchess macros. * create a `cargo duchess` utility * start writing stuff for book * introduce JavapClassInfo for stuff from javap ...and a trait that lets you be generic over JavapClassInfo or ClassInfo. Distinguishing `JavapClassInfo` means we know when the data comes from the "source of truth" vs the user, but it also means we can serialize because there is no span. * Fix classpath concatenation on Windows --------- Co-authored-by: Niko Matsakis <[email protected]> Co-authored-by: Russell Cohen <[email protected]>
- Loading branch information
1 parent
0b2975a
commit eba2cee
Showing
32 changed files
with
1,252 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "cargo-duchess" | ||
edition = "2021" | ||
version.workspace = true | ||
license.workspace = true | ||
repository.workspace = true | ||
homepage.workspace = true | ||
documentation.workspace = true | ||
|
||
[dependencies] | ||
anyhow = "1.0.89" | ||
structopt = "0.3.26" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use std::{path::PathBuf, process::Command}; | ||
|
||
use structopt::StructOpt; | ||
|
||
#[derive(StructOpt, Debug)] | ||
pub struct InitOptions { | ||
/// Directory of the project | ||
#[structopt(short, long, default_value = ".")] | ||
dir: PathBuf, | ||
} | ||
|
||
const DEFAULT_BUILD_RS: &str = " | ||
// This file is automatically generated by `cargo duchess init`. | ||
use duchess_build_rs::DuchessBuildRs; | ||
fn main() -> anyhow::Result<()> { | ||
DuchessBuildRs::new().execute() | ||
} | ||
"; | ||
|
||
const ONE_LINE: &str = "duchess_build_rs::DuchessBuildRs::new().execute().unwrap()"; | ||
|
||
pub fn init(options: InitOptions) -> anyhow::Result<()> { | ||
let InitOptions { dir } = options; | ||
|
||
if !dir.exists() { | ||
anyhow::bail!("directory `{}` not found", dir.display()); | ||
} | ||
|
||
if !dir.is_dir() { | ||
anyhow::bail!("`{}` is not a directory", dir.display()); | ||
} | ||
|
||
let cargo_toml_path = dir.join("Cargo.toml"); | ||
if !cargo_toml_path.exists() { | ||
anyhow::bail!( | ||
"directory `{}` does not contain a `Cargo.toml`", | ||
dir.display() | ||
); | ||
} | ||
|
||
Command::new("cargo") | ||
.arg("add") | ||
.arg("duchess") | ||
.current_dir(&dir) | ||
.spawn()? | ||
.wait()?; | ||
|
||
Command::new("cargo") | ||
.arg("add") | ||
.arg("--build") | ||
.arg("duchess-build-rs") | ||
.current_dir(&dir) | ||
.spawn()? | ||
.wait()?; | ||
|
||
// If `build.rs` does not exist, initialize it with default. | ||
let build_rs_path = dir.join("build.rs"); | ||
if !build_rs_path.exists() { | ||
std::fs::write(&build_rs_path, DEFAULT_BUILD_RS)?; | ||
} else { | ||
// Tell user to add it themselves | ||
// (FIXME: use syn to insert it) | ||
eprintln!("Warning: `build.rs` already exists. Please add the following line to `main`:"); | ||
eprintln!(); | ||
eprintln!("```rust"); | ||
eprintln!("{}", ONE_LINE); | ||
eprintln!("```"); | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use structopt::StructOpt; | ||
|
||
#[derive(StructOpt, Debug)] | ||
#[structopt(name = "cargo-duchess")] | ||
enum Opt { | ||
/// Initialize something | ||
Init { | ||
#[structopt(flatten)] | ||
options: init::InitOptions, | ||
}, | ||
/// Package something | ||
Package { | ||
/// Path to the package | ||
#[structopt(short, long)] | ||
path: String, | ||
}, | ||
} | ||
|
||
mod init; | ||
|
||
fn main() -> anyhow::Result<()> { | ||
let opt = Opt::from_args(); | ||
match opt { | ||
Opt::Init { options } => { | ||
init::init(options)?; | ||
} | ||
Opt::Package { path: _ } => todo!(), | ||
} | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "duchess-build-rs" | ||
edition = "2021" | ||
version.workspace = true | ||
license.workspace = true | ||
repository.workspace = true | ||
homepage.workspace = true | ||
documentation.workspace = true | ||
|
||
[dependencies] | ||
anyhow = "1.0.86" | ||
duchess-reflect = { version = "0.3.0", path = "../duchess-reflect" } | ||
lazy_static = "1.5.0" | ||
proc-macro2 = "1.0.86" | ||
quote = "1.0.36" | ||
regex = "1.10.5" | ||
syn = { version = "2.0.71", features = ["full"] } | ||
tempfile = "3.10.1" | ||
walkdir = "2.5.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use core::fmt; | ||
use std::io::Write; | ||
|
||
pub struct CodeWriter<'w> { | ||
writer: &'w mut dyn Write, | ||
indent: usize, | ||
} | ||
|
||
impl<'w> CodeWriter<'w> { | ||
pub fn new(writer: &'w mut dyn Write) -> Self { | ||
CodeWriter { writer, indent: 0 } | ||
} | ||
|
||
pub fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> anyhow::Result<()> { | ||
let mut string = String::new(); | ||
fmt::write(&mut string, fmt).unwrap(); | ||
|
||
if string.starts_with("}") || string.starts_with(")") || string.starts_with("]") { | ||
self.indent -= 1; | ||
} | ||
|
||
write!( | ||
self.writer, | ||
"{:indent$}{}\n", | ||
"", | ||
string, | ||
indent = self.indent * 4 | ||
)?; | ||
|
||
if string.ends_with("{") || string.ends_with("(") || string.ends_with("[") { | ||
self.indent += 1; | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use std::path::{Path, PathBuf}; | ||
|
||
use walkdir::WalkDir; | ||
|
||
pub(crate) struct File { | ||
pub(crate) path: PathBuf, | ||
pub(crate) contents: String, | ||
} | ||
|
||
pub fn rs_files(path: &Path) -> impl Iterator<Item = anyhow::Result<File>> { | ||
WalkDir::new(path) | ||
.into_iter() | ||
.filter_map(|entry| -> Option<anyhow::Result<File>> { | ||
match entry { | ||
Ok(entry) => { | ||
if entry.path().extension().map_or(false, |e| e == "rs") { | ||
Some(Ok(File { | ||
path: entry.path().to_path_buf(), | ||
contents: match std::fs::read_to_string(entry.path()) { | ||
Ok(s) => s, | ||
Err(err) => return Some(Err(err.into())), | ||
}, | ||
})) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
Err(err) => Some(Err(err.into())), | ||
} | ||
}) | ||
} | ||
|
||
impl File { | ||
/// Return a string that can be used as a slug for error messages. | ||
pub fn slug(&self, offset: usize) -> String { | ||
let line_num = self.contents[..offset].lines().count(); | ||
let column_num = 1 + self.contents[..offset] | ||
.rfind('\n') | ||
.map_or(offset, |i| offset - i - 1); | ||
format!( | ||
"{path}:{line_num}:{column_num}:", | ||
path = self.path.display(), | ||
) | ||
} | ||
|
||
/// Returns a chunk of rust code starting at `offset` | ||
/// and extending until the end of the current token tree | ||
/// or file, whichever comes first. | ||
/// | ||
/// This is used when we are preprocessing and we find | ||
/// some kind of macro invocation. We want to grab all | ||
/// the text that may be part of it and pass it into `syn`. | ||
pub fn rust_slice_from(&self, offset: usize) -> &str { | ||
let mut counter = 0; | ||
let terminator = self.contents[offset..].char_indices().find(|&(_, c)| { | ||
if c == '{' || c == '[' || c == '(' { | ||
counter += 1; | ||
} else if c == '}' || c == ']' || c == ')' { | ||
if counter == 0 { | ||
return true; | ||
} | ||
|
||
counter -= 1; | ||
} | ||
|
||
false | ||
}); | ||
match terminator { | ||
Some((i, _)) => &self.contents[offset..offset + i], | ||
None => &self.contents[offset..], | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
use duchess_reflect::{class_info::ClassRef, reflect::Reflector}; | ||
use proc_macro2::{Span, TokenStream}; | ||
use syn::spanned::Spanned; | ||
|
||
use crate::{files::File, java_compiler::JavaCompiler, shim_writer::ShimWriter}; | ||
|
||
pub fn process_impl(compiler: &JavaCompiler, file: &File, offset: usize) -> anyhow::Result<()> { | ||
let the_impl: JavaInterfaceImpl = syn::parse_str(file.rust_slice_from(offset))?; | ||
the_impl.generate_shim(compiler)?; | ||
Ok(()) | ||
} | ||
|
||
struct JavaInterfaceImpl { | ||
item: syn::ItemImpl, | ||
} | ||
|
||
impl syn::parse::Parse for JavaInterfaceImpl { | ||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { | ||
// we are parsing an input that starts with an impl and then has add'l stuff | ||
let item: syn::ItemImpl = input.parse()?; | ||
|
||
// syn reports an error if there is anything unconsumed, so consume all remaining tokens | ||
// after we parse the impl | ||
let _more_tokens: TokenStream = input.parse()?; | ||
|
||
Ok(Self { item }) | ||
} | ||
} | ||
|
||
impl JavaInterfaceImpl { | ||
fn generate_shim(&self, compiler: &JavaCompiler) -> anyhow::Result<()> { | ||
let reflector = Reflector::new(compiler.configuration()); | ||
let (java_interface_ref, java_interface_span) = self.java_interface()?; | ||
let java_interface_info = | ||
reflector.reflect(&java_interface_ref.name, java_interface_span)?; | ||
|
||
let shim_name = format!("Shim${}", java_interface_info.name.to_dollar_name()); | ||
let java_file = compiler.java_file("duchess", &shim_name); | ||
ShimWriter::new( | ||
&mut java_file.src_writer()?, | ||
&shim_name, | ||
&java_interface_info, | ||
) | ||
.emit_shim_class()?; | ||
|
||
compiler.compile_to_rs_file(&java_file)?; | ||
|
||
eprintln!("compiled to {}", java_file.rs_path.display()); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn java_interface(&self) -> anyhow::Result<(ClassRef, Span)> { | ||
let Some((_, trait_path, _)) = &self.item.trait_ else { | ||
return Err(syn::Error::new_spanned(&self.item, "expected an impl of a trait").into()); | ||
}; | ||
let class_ref = ClassRef::from(&self.item.generics, trait_path)?; | ||
Ok((class_ref, trait_path.span())) | ||
} | ||
} |
Oops, something went wrong.