summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2021-10-13 10:26:30 +0000
committerDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2021-10-13 10:26:30 +0000
commit59375983ef9a7f69d7a1e4250da5babee9c302c7 (patch)
treef118f9df8306999746140120ede94fe3cda1b645
parent2459137129c64e02a2948f6c5f9e17bf56742c64 (diff)
parentda9fff9856897198448f210487646a6373491c66 (diff)
downloadsubplot-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.lock9
-rw-r--r--Cargo.toml2
-rw-r--r--RELEASE.md11
-rw-r--r--src/bin/cli/mod.rs44
-rw-r--r--src/bin/subplot.rs34
-rw-r--r--src/doc.rs93
-rw-r--r--src/lib.rs1
-rw-r--r--src/resource.rs2
-rw-r--r--subplot-build/Cargo.toml19
-rw-r--r--subplot-build/src/lib.rs83
10 files changed, 220 insertions, 78 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dac263c..6a6f7f9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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 = [
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/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:
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);
}
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<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.
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<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
+}