From 5855c963b8c1e1748f50301ddcc21a923be60240 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Tue, 5 Oct 2021 12:02:56 +0300 Subject: feat: add crate subplot-build for using Subplot from build.rs Make it easy to generate test code from a subplot in another project's `build.rs` script. Move the code to load documents and generate test code from src/bin/subplot.rs and src/bin/cli/mod.rs to src/doc.rs so it can be used without using the subplot executable. Make the add_search_path function public so it can be used outside its module. The subplot executable arranged for the directory where the markdown input file resides to be added to the search path via another way. Sponsored-by: pep.foundation --- Cargo.lock | 9 +++++ Cargo.toml | 2 +- src/bin/cli/mod.rs | 44 ----------------------- src/bin/subplot.rs | 34 ++++-------------- src/doc.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 1 + src/resource.rs | 2 +- subplot-build/Cargo.toml | 19 ++++++++++ subplot-build/src/lib.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 213 insertions(+), 74 deletions(-) create mode 100644 subplot-build/Cargo.toml create mode 100644 subplot-build/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index dac263c..6a6f7f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,6 +1066,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "subplot-build" +version = "0.1.0" +dependencies = [ + "subplot", + "tempfile", + "tracing", +] + [[package]] name = "subplotlib" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5753f63..2823b1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] 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

(filename: P, style: Style) -> Result -where - P: AsRef + 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

(filename: P, style: Style) -> Result -where - P: AsRef + 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); } diff --git a/src/doc.rs b/src/doc.rs index c5bb2c7..ffdd4ac 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -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

(filename: P, style: Style) -> Result +where + P: AsRef + 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

(filename: P, style: Style) -> Result +where + P: AsRef + 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 { + 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, usize)> { if e.is_empty() { // If we get here, it's a programming error. diff --git a/src/lib.rs b/src/lib.rs index e818c5a..59d9fb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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>(path: P) { +pub fn add_search_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 ", + "Daniel Silverstone ", +] +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 +/// 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

(filename: P) -> Result<()> +where + P: AsRef + 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 { + 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 +} -- cgit v1.2.1 From da9fff9856897198448f210487646a6373491c66 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Wed, 13 Oct 2021 09:28:05 +0300 Subject: docs: update release process to check dependencies within workspace Also add the new crate to `RELEASE.md` so it's not overlooked. Sponsored-by: pep.foundation --- RELEASE.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 5798a45..08041b3 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -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: -- cgit v1.2.1