diff options
author | Daniel Silverstone <dsilvers+gitlab@digital-scurf.org> | 2021-10-13 10:26:30 +0000 |
---|---|---|
committer | Daniel Silverstone <dsilvers+gitlab@digital-scurf.org> | 2021-10-13 10:26:30 +0000 |
commit | 59375983ef9a7f69d7a1e4250da5babee9c302c7 (patch) | |
tree | f118f9df8306999746140120ede94fe3cda1b645 | |
parent | 2459137129c64e02a2948f6c5f9e17bf56742c64 (diff) | |
parent | da9fff9856897198448f210487646a6373491c66 (diff) | |
download | subplot-59375983ef9a7f69d7a1e4250da5babee9c302c7.tar.gz |
Merge branch 'build.rs-support' into 'main'
feat: add crate subplot-build for using Subplot from build.rs
See merge request subplot/subplot!223
-rw-r--r-- | Cargo.lock | 9 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | RELEASE.md | 11 | ||||
-rw-r--r-- | src/bin/cli/mod.rs | 44 | ||||
-rw-r--r-- | src/bin/subplot.rs | 34 | ||||
-rw-r--r-- | src/doc.rs | 93 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/resource.rs | 2 | ||||
-rw-r--r-- | subplot-build/Cargo.toml | 19 | ||||
-rw-r--r-- | subplot-build/src/lib.rs | 83 |
10 files changed, 220 insertions, 78 deletions
@@ -1067,6 +1067,15 @@ dependencies = [ ] [[package]] +name = "subplot-build" +version = "0.1.0" +dependencies = [ + "subplot", + "tempfile", + "tracing", +] + +[[package]] name = "subplotlib" version = "0.1.0" dependencies = [ @@ -15,7 +15,7 @@ repository = "https://gitlab.com/subplot/subplot" default-run = "subplot" [workspace] -members = [ "subplotlib", "subplotlib-derive" ] +members = [ "subplotlib", "subplotlib-derive", "subplot-build" ] [features] default = ["ast_07"] @@ -9,6 +9,7 @@ file with release notes, and only one set of Debian packaging (only the binaries and template resources are packaged). The crates are, currently: +- `subplot-build` - `subplotlib-derive` - `subplotlib` - `subplot` @@ -22,16 +23,18 @@ git ls-files | grep Cargo.toml Do these for each crate, with leaf crates in the dependency tree first: -1. Run `cargo update` and `cargo outdated` for the crate, and make any +1. Check that the dependency versions on any other Subplot crates are + correct. +2. Run `cargo update` and `cargo outdated` for the crate, and make any needed updates. -2. Review changes in the crate (`git log -p .` in the crate's +3. Review changes in the crate (`git log -p .` in the crate's sub-directory). Update the top level `NEWS.md` with any changes that users of Subplot need to be aware of. -3. Update the crate's `Cargo.toml` with the appropriate version number +4. Update the crate's `Cargo.toml` with the appropriate version number for the new release, if there's been any changes. If any of the other crates depend on this crate, update their dependency information in their `Cargo.toml` as needed. -4. Run `cargo publish --dry-run` and fix any problems. +5. Run `cargo publish --dry-run` and fix any problems. For the top crate `subplot` additionally do the following: diff --git a/src/bin/cli/mod.rs b/src/bin/cli/mod.rs index c53da0f..84bda10 100644 --- a/src/bin/cli/mod.rs +++ b/src/bin/cli/mod.rs @@ -11,50 +11,6 @@ use std::str::FromStr; use subplot::{DataFile, Document, Style, SubplotError}; use tracing::{event, instrument, Level}; -#[instrument(level = "trace")] -pub fn load_document<P>(filename: P, style: Style) -> Result<Document> -where - P: AsRef<Path> + Debug, -{ - let filename = filename.as_ref(); - let base_path = subplot::get_basedir_from(filename); - event!( - Level::TRACE, - ?filename, - ?base_path, - "Loading document based at `{}` called `{}` with {:?}", - base_path.display(), - filename.display(), - style - ); - let doc = Document::from_file(&base_path, filename, style)?; - event!(Level::TRACE, "Loaded doc from file OK"); - - Ok(doc) -} - -#[instrument(level = "trace")] -pub fn load_document_with_pullmark<P>(filename: P, style: Style) -> Result<Document> -where - P: AsRef<Path> + Debug, -{ - let filename = filename.as_ref(); - let base_path = subplot::get_basedir_from(filename); - event!( - Level::TRACE, - ?filename, - ?base_path, - "Loading document based at `{}` called `{}` with {:?} using pullmark-cmark", - base_path.display(), - filename.display(), - style - ); - let doc = Document::from_file_with_pullmark(&base_path, filename, style)?; - event!(Level::TRACE, "Loaded doc from file OK"); - - Ok(doc) -} - pub fn extract_file<'a>(doc: &'a Document, filename: &str) -> Result<&'a DataFile> { for file in doc.files() { if file.filename() == filename { diff --git a/src/bin/subplot.rs b/src/bin/subplot.rs index 3e865fb..8d4a660 100644 --- a/src/bin/subplot.rs +++ b/src/bin/subplot.rs @@ -5,9 +5,7 @@ use anyhow::Result; use chrono::{Local, TimeZone}; use structopt::StructOpt; -use subplot::{ - generate_test_program, resource, template_spec, DataFile, Document, MarkupOpts, Style, -}; +use subplot::{codegen, load_document, resource, DataFile, Document, MarkupOpts, Style}; use tracing::{event, instrument, span, Level, Subscriber}; use std::convert::TryFrom; @@ -162,7 +160,7 @@ impl Extract { fn run(&self) -> Result<()> { let span = span!(Level::TRACE, "extract"); let _enter = span.enter(); - let doc = cli::load_document(&self.filename, Style::default())?; + let doc = load_document(&self.filename, Style::default())?; let files: Vec<&DataFile> = if self.embedded.is_empty() { doc.files() @@ -268,7 +266,7 @@ impl Metadata { fn run(&self) -> Result<()> { let span = span!(Level::TRACE, "metadata"); let _enter = span.enter(); - let mut doc = cli::load_document(&self.filename, Style::default())?; + let mut doc = load_document(&self.filename, Style::default())?; let meta = cli::Metadata::try_from(&mut doc)?; match self.output_format { cli::OutputFormat::Plain => meta.write_out(), @@ -310,7 +308,7 @@ impl Docgen { event!(Level::TRACE, "PDF output chosen"); style.typeset_links_as_notes(); } - let mut doc = cli::load_document(&self.input, style)?; + let mut doc = load_document(&self.input, style)?; event!(Level::TRACE, "Got doc, now linting it"); doc.lint()?; event!(Level::TRACE, "Doc linted ok"); @@ -415,32 +413,14 @@ impl Codegen { fn run(&self) -> Result<()> { let span = span!(Level::TRACE, "codegen"); let _enter = span.enter(); - let mut doc = cli::load_document_with_pullmark(&self.filename, Style::default())?; - doc.lint()?; - let template = doc - .meta() - .template_name() - .ok_or_else(|| anyhow::anyhow!("No template name given"))? - .to_string(); - event!(Level::TRACE, ?template); - if !doc.check_named_files_exist(&template)? || !doc.check_matched_steps_have_impl(&template) - { - event!(Level::ERROR, "Found problems in document, cannot continue"); - eprintln!("Unable to continue"); - std::process::exit(1); - } - - event!(Level::TRACE, "Generating code"); - let spec = template_spec(&doc)?; - generate_test_program(&mut doc, &spec, &self.output, &template)?; - event!(Level::TRACE, "Finished generating code"); + let output = codegen(&self.filename, &self.output)?; if self.run { - let run = match spec.run() { + let run = match output.spec.run() { None => { eprintln!( "Template {} does not specify how to run suites", - spec.template_filename().display() + output.spec.template_filename().display() ); std::process::exit(1); } @@ -1,4 +1,6 @@ use crate::ast; +use crate::generate_test_program; +use crate::get_basedir_from; use crate::visitor; use crate::DataFile; use crate::DataFiles; @@ -10,6 +12,7 @@ use crate::Scenario; use crate::ScenarioStep; use crate::Style; use crate::{bindings::CaptureType, parser::parse_scenario_snippet}; +use crate::{template_spec, TemplateSpec}; use crate::{Result, SubplotError}; use std::collections::HashSet; @@ -21,7 +24,7 @@ use std::str::FromStr; use pandoc_ast::{MutVisitor, Pandoc}; -use tracing::{event, instrument, Level}; +use tracing::{event, instrument, span, Level}; /// The set of known (special) classes which subplot will always recognise /// as being valid. @@ -421,6 +424,94 @@ impl<'a> Document { } } +/// Load a `Document` from a file. +/// +/// This version uses Pandoc to parse the Markdown. +#[instrument(level = "trace")] +pub fn load_document<P>(filename: P, style: Style) -> Result<Document> +where + P: AsRef<Path> + Debug, +{ + let filename = filename.as_ref(); + let base_path = get_basedir_from(filename); + event!( + Level::TRACE, + ?filename, + ?base_path, + "Loading document based at `{}` called `{}` with {:?}", + base_path.display(), + filename.display(), + style + ); + let doc = Document::from_file(&base_path, filename, style)?; + event!(Level::TRACE, "Loaded doc from file OK"); + + Ok(doc) +} + +/// Load a `Document` from a file. +/// +/// This version uses the `cmark-pullmark` crate to parse Markdown. +#[instrument(level = "trace")] +pub fn load_document_with_pullmark<P>(filename: P, style: Style) -> Result<Document> +where + P: AsRef<Path> + Debug, +{ + let filename = filename.as_ref(); + let base_path = get_basedir_from(filename); + event!( + Level::TRACE, + ?filename, + ?base_path, + "Loading document based at `{}` called `{}` with {:?} using pullmark-cmark", + base_path.display(), + filename.display(), + style + ); + crate::resource::add_search_path(filename.parent().unwrap()); + let doc = Document::from_file_with_pullmark(&base_path, filename, style)?; + event!(Level::TRACE, "Loaded doc from file OK"); + Ok(doc) +} + +/// Generate code for one document. +pub fn codegen(filename: &Path, output: &Path) -> Result<CodegenOutput> { + let span = span!(Level::TRACE, "codegen"); + let _enter = span.enter(); + + let mut doc = load_document_with_pullmark(filename, Style::default())?; + doc.lint()?; + let template = doc + .meta() + .template_name() + .ok_or(SubplotError::MissingTemplate)? + .to_string(); + event!(Level::TRACE, ?template); + if !doc.check_named_files_exist(&template)? || !doc.check_matched_steps_have_impl(&template) { + event!(Level::ERROR, "Found problems in document, cannot continue"); + eprintln!("Unable to continue"); + std::process::exit(1); + } + + event!(Level::TRACE, "Generating code"); + let spec = template_spec(&doc)?; + generate_test_program(&mut doc, &spec, output, &template)?; + event!(Level::TRACE, "Finished generating code"); + + Ok(CodegenOutput::new(spec, doc)) +} + +pub struct CodegenOutput { + pub spec: TemplateSpec, + pub doc: Document, +} + +impl CodegenOutput { + fn new(spec: TemplateSpec, doc: Document) -> Self { + Self { spec, doc } + } +} + fn extract_scenario(e: &[visitor::Element]) -> Result<(Option<Scenario>, usize)> { if e.is_empty() { // If we get here, it's a programming error. @@ -40,6 +40,7 @@ pub use metadata::Metadata; mod doc; pub use doc::Document; +pub use doc::{codegen, load_document, load_document_with_pullmark}; mod style; pub use style::Style; diff --git a/src/resource.rs b/src/resource.rs index 43967ef..336c4e2 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -55,7 +55,7 @@ pub fn embedded_files() -> &'static [(&'static str, &'static [u8])] { } /// Put a path at the back of the queue to search in -fn add_search_path<P: AsRef<Path>>(path: P) { +pub fn add_search_path<P: AsRef<Path>>(path: P) { SEARCH_PATHS .lock() .expect("Unable to lock SEARCH_PATHS") diff --git a/subplot-build/Cargo.toml b/subplot-build/Cargo.toml new file mode 100644 index 0000000..40e4bac --- /dev/null +++ b/subplot-build/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "subplot-build" +version = "0.1.0" +authors = [ + "Lars Wirzenius <liw@liw.fi>", + "Daniel Silverstone <dsilvers@digital-scurf.org>", +] +edition = "2018" +license = "GPL-3.0-or-later" +description = '''A library for using Subplot code generation from another project's +`build.rs` module.''' +homepage = "https://subplot.liw.fi/" +repository = "https://gitlab.com/subplot/subplot" + + +[dependencies] +subplot = { version="0.2.2", path = ".." } +tracing = "0.1" +tempfile = "3.1.0" diff --git a/subplot-build/src/lib.rs b/subplot-build/src/lib.rs new file mode 100644 index 0000000..89ea57d --- /dev/null +++ b/subplot-build/src/lib.rs @@ -0,0 +1,83 @@ +//! Subplot test program building from `build.rs` +//! +//! This crate provides the Subplot code generation facility in a way +//! that's meant to be easy to use from another project's `build.rs` +//! module. + +use std::env::var_os; +use std::fmt::Debug; +use std::path::{Path, PathBuf}; +use subplot::{get_basedir_from, Result}; +use tracing::{event, instrument, span, Level}; + +/// Generate code for one document, inside `build.rs`. +/// +/// The output files will be written to the directory specified in the +/// OUT_DIR environment variable. That variable is set by Cargo, when +/// a crate is built. +/// +/// Also emit instructions for Cargo so it knows to re-run `build.rs` +/// whenever the input subplot or any of the bindings or functions +/// files it refers to changes. See +/// <https://doc.rust-lang.org/cargo/reference/build-scripts.html> for +/// details. +/// +/// ``` +/// use subplot_build::codegen; +/// # let dir = tempfile::tempdir().unwrap(); +/// # std::env::set_var("OUT_DIR", dir.path()); +/// +/// codegen("foo.md").ok(); // ignoring error to keep example short +/// # dir.close().unwrap() +/// ``` +#[instrument(level = "trace")] +pub fn codegen<P>(filename: P) -> Result<()> +where + P: AsRef<Path> + Debug, +{ + let span = span!(Level::TRACE, "codegen_buildrs"); + let _enter = span.enter(); + + event!(Level::TRACE, "Generating code in build.rs"); + + // Decide the name of the generated test program. + let out_dir = var_os("OUT_DIR").expect("OUT_DIR is not defined in the environment"); + let out_dir = Path::new(&out_dir); + let filename = filename.as_ref(); + let test_rs = + buildrs_output(out_dir, filename, "rs").expect("could not create output filename"); + + // Generate test program. + let output = subplot::codegen(filename, &test_rs)?; + + // Write instructions for Cargo to check if build scripts needs + // re-running. + let base_path = get_basedir_from(filename); + let meta = output.doc.meta(); + buildrs_deps(&base_path, &meta.bindings_filenames()); + buildrs_deps(&base_path, &meta.functions_filenames()); + buildrs_deps(&base_path, &[filename]); + + event!(Level::TRACE, "Finished generating code"); + Ok(()) +} + +fn buildrs_deps(base_path: &Path, filenames: &[&Path]) { + for filename in filenames { + let filename = base_path.join(filename); + if filename.exists() { + println!("cargo:rerun-if-changed={}", filename.display()); + } + } +} + +fn buildrs_output(dir: &Path, filename: &Path, new_extension: &str) -> Option<PathBuf> { + if let Some(basename) = filename.file_name() { + let basename = Path::new(basename); + if let Some(stem) = basename.file_stem() { + let stem = Path::new(stem); + return Some(dir.join(stem.with_extension(new_extension))); + } + } + None +} |