diff --git a/.config/cranko/config.toml b/.config/cranko/config.toml new file mode 100644 index 000000000..b1e133d54 --- /dev/null +++ b/.config/cranko/config.toml @@ -0,0 +1,5 @@ +[repo] +upstream_urls = [ + 'git@github.com:tectonic-typesetting/tectonic.git', + 'https://github.com/tectonic-typesetting/tectonic.git', +] diff --git a/.gitignore b/.gitignore index 5ad584b56..cb81b5d43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,7 @@ /cross +/docs/book/* /target /tests/plain.fmt /tests/00*00-latex-*.fmt /tests/00*00-plain-*.fmt /tests/xenia/paper.pdf - -# ignore mdbook compiled output (HTML) -docs/book/* diff --git a/CHANGELOG.md b/CHANGELOG.md index a684d0e37..d012f2e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# tectonic 0.1.16 (2020-10-02) + +- Add a "plain" backend for reporting status, used when the program is not + attached to a TTY. It will print out reports with colorization. (#636, + @ralismark) +- Start adding infrastructure to automate the creation of bindings from the + C/C++ code to the Rust code, using `cbindgen`. (#643, @ralismark) +- Update the code-coverage infrastructure to gather coverage information + from invocations of the CLI executable inside the test suite (@pkgw) +- Fully automated deployment should really actually totally work this time. + # tectonic 0.1.15 (2020-09-10) - Building on the work done in 0.1.13, we now capture and report diagnostics diff --git a/Cargo.lock b/Cargo.lock index 320de519e..5f47366f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,24 @@ dependencies = [ "iovec", ] +[[package]] +name = "cbindgen" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e783d38a7700989e0209d0b0ed224c34ade92d3603da0cf15dc502ebada685a6" +dependencies = [ + "clap", + "heck", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.0.59" @@ -770,9 +788,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.76" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" +checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" [[package]] name = "libz-sys" @@ -1484,18 +1502,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" +checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" dependencies = [ "proc-macro2", "quote", @@ -1582,9 +1600,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5" +checksum = "a33f6461027d7f08a13715659b2948e1602c31a3756aeae9378bfe7518c72e82" dependencies = [ "clap", "lazy_static", @@ -1593,9 +1611,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f" +checksum = "c92e775028122a4b3dd55d58f14fc5120289c69bee99df1d117ae30f84b225c9" dependencies = [ "heck", "proc-macro-error", @@ -1629,10 +1647,12 @@ dependencies = [ [[package]] name = "tectonic" -version = "0.1.15" +version = "0.1.16" dependencies = [ "app_dirs2", + "atty", "byte-unit", + "cbindgen", "cc", "cfg-if", "error-chain", @@ -2195,9 +2215,9 @@ checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" [[package]] name = "zip" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30de6e58104bb7b9a94f34b52a2bdabb8a40b678a64201cd0069e3d7119b5ff" +checksum = "543adf038106b64cfca4711c82c917d785e3540e04f7996554488f988ec43124" dependencies = [ "byteorder", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml index a9f03ff0c..9f6b90693 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "tectonic" -version = "0.1.15" +version = "0.1.16" authors = ["Peter Williams "] build = "build.rs" description = """ @@ -40,9 +40,11 @@ regex = "^1.3" sha2 = "^0.9" tectonic_cfg_support = { path = "cfg_support", version ="^0.1.0"} vcpkg = "0.2.10" +cbindgen = "0.14.4" [dependencies] app_dirs = { version = "2", package = "app_dirs2" } +atty = "0.2" byte-unit = "^4.0" cfg-if = "0.1" structopt = "0.3" diff --git a/build.rs b/build.rs index 771c737a2..706c8ac68 100644 --- a/build.rs +++ b/build.rs @@ -1,17 +1,16 @@ // build.rs -- build helper script for Tectonic. -// Copyright 2016-2019 the Tectonic Project +// Copyright 2016-2020 the Tectonic Project // Licensed under the MIT License. /// The Tectonic build script. Not only do we have internal C/C++ code, we /// also depend on several external C/C++ libraries, so there's a lot to do /// here. It would be great to streamline things. -/// -/// TODO: this surely needs to become much smarter and more flexible. +use std::{ + env, + path::{Path, PathBuf}, +}; use tectonic_cfg_support::*; -use std::env; -use std::path::{Path, PathBuf}; - #[cfg(not(target_os = "macos"))] const PKGCONFIG_LIBS: &str = "fontconfig harfbuzz >= 1.4 harfbuzz-icu icu-uc freetype2 graphite2 libpng zlib"; @@ -184,6 +183,18 @@ fn main() { let target = env::var("TARGET").unwrap(); let rustflags = env::var("RUSTFLAGS").unwrap_or_default(); + // Generate bindings for the C/C++ code to interface with backend Rust code. + // As a heuristic we trigger rebuilds on changes to src/engines/mod.rs since + // most of `core-bindgen.h` comes from this file. + let mut cbindgen_header_path: PathBuf = env::var("OUT_DIR").unwrap().into(); + cbindgen_header_path.push("core-bindgen.h"); + + cbindgen::generate(env::var("CARGO_MANIFEST_DIR").unwrap()) + .unwrap() + .write_to_file(&cbindgen_header_path); + + println!("cargo:rerun-if-changed=src/engines/mod.rs"); + // Re-export $TARGET during the build so that our executable tests know // what environment variable CARGO_TARGET_@TARGET@_RUNNER to check when // they want to spawn off executables. @@ -365,6 +376,7 @@ fn main() { .define("HAVE_ZLIB", "1") .define("HAVE_ZLIB_COMPRESS2", "1") .define("ZLIB_CONST", "1") + .include(env::var("OUT_DIR").unwrap()) .include("."); let cppflags = [ @@ -413,6 +425,7 @@ fn main() { .file("tectonic/xetex-XeTeXFontMgr.cpp") .file("tectonic/xetex-XeTeXLayoutInterface.cpp") .file("tectonic/xetex-XeTeXOTMath.cpp") + .include(env::var("OUT_DIR").unwrap()) .include("."); dep_state.foreach_include_path(|p| { diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 000000000..167a6037c --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,18 @@ +include_guard = "tectonic_core_bindgen_h" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = true + +language = "C" + +# typedef Name struct {} Name +style = "both" + +[export] +exclude = [ "DIGEST_LEN" ] + +# Table of name conversions to apply to item names +[export.rename] +TectonicBridgeApi = "tt_bridge_api_t" + +[fn] +sort_by = "None" # Make cbindgen not mess with our declaration order diff --git a/dist/azure-coverage.yml b/dist/azure-coverage.yml index 0f216fbfe..2248afd69 100644 --- a/dist/azure-coverage.yml +++ b/dist/azure-coverage.yml @@ -28,31 +28,57 @@ steps: cargo install --force cargo-kcov displayName: Set up code coverage +# As of Rust 1.44, test executables land in target/debug/deps/ instead of +# target/debug/, which messes up current cargo-kcov (0.5.2) because it tries to +# search for those executables. Work around with `cp`. One of the `tectonic-*` +# binaries is the debug executable, which is hard-linked to +# `target/debug/tectonic`. kcov will erroneously try to run this as a test if we +# copy it, so we have to make not to do that, which we accomplish with a search +# based on the hardlink count. Hacky and fragile but this should get us going. +# Hopefully kcov will get fixed where this will become unneccessary anyway. - bash: | - # As of Rust 1.44, test executables land in target/debug/deps/ instead of - # target/debug/, which messes up current cargo-kcov (0.5.2) because it tries - # to search for those executables. Work around with `cp`. One of the - # `tectonic-*` binaries is the debug executable, which is hard-linked to - # `target/debug/tectonic`. kcov will erroneously try to run this as a test - # if we copy it, so we have to make not to do that, which we accomplish with - # a search based on the hardlink count. Hacky and fragile but this should - # get us going. Hopefully kcov will get fixed where this will become - # unneccessary anyway. - rm target/debug/deps/tectonic-???????????????? + set -xeuo pipefail cargo test --no-run find target/debug/deps/tectonic-???????????????? -links 2 -print -delete cp -vp target/debug/deps/*-???????????????? target/debug/ displayName: cargo test --no-run - bash: | + set -xeuo pipefail git add . cranko release-workflow commit git show HEAD displayName: Make release commit -- bash: RUNNING_COVERAGE=1 cargo kcov --no-clean-rebuild +- bash: | + set -xeuo pipefail + p="$(pwd)" + cargo kcov --no-clean-rebuild -- --include-pattern="$p/src,$p/tectonic,$p/xdv" displayName: cargo kcov +# Our "executable" test executes the actual Tectonic binary. In order to collect +# coverage information about what happens in those executions, we have special +# support in the test harness to wrap the invocations in `kcov` calls. +- bash: | + set -xeuo pipefail + p="$(pwd)" + export TECTONIC_EXETEST_KCOV_RUNNER="kcov --include-pattern=$p/src,$p/tectonic,$p/xdv" + cargo test --test executable + displayName: Special-case executable tests + +# Now, merge all of the coverage reports. `cargo kcov` does this automatically, +# but it uses an explicit list of coverage runs, so there's no way to get it to +# include our special executable tests. We just glob everything, which means we +# have to delete the preexisting merged report. - bash: | + set -xeou pipefail + cd target/cov + rm -rf amber.png bcov.css data glass.png index.html index.js* \ + kcov-merged merged-kcov-output + kcov --merge . * + displayName: Merge coverage reports + +- bash: | + set -xeuo pipefail bash <(curl -s https://codecov.io/bash) displayName: Report coverage results diff --git a/dist/azure-deployment.yml b/dist/azure-deployment.yml index 506f3ac26..da32c5e43 100644 --- a/dist/azure-deployment.yml +++ b/dist/azure-deployment.yml @@ -36,7 +36,7 @@ jobs: set -xeuo pipefail cranko github delete-release continuous git tag -f continuous HEAD - git push -f --tags origin continuous + git push -f origin refs/tags/continuous cranko github create-custom-release \ --name "Continuous Deployment" \ --prerelease \ @@ -88,6 +88,7 @@ jobs: CARGO_REGISTRY_TOKEN: $(CARGO_REGISTRY_TOKEN) - job: github_releases + dependsOn: branch_and_tag # otherwise, GitHub will create the tag itself pool: vmImage: ubuntu-20.04 variables: @@ -160,7 +161,7 @@ jobs: GITHUB_TOKEN: $(GITHUB_TOKEN) - job: update_arch_linux - dependsOn: cargo_publish # <= note! + dependsOn: cargo_publish # need to download the crate file and calc its digest pool: vmImage: ubuntu-20.04 variables: diff --git a/src/bin/tectonic.rs b/src/bin/tectonic.rs index dc229533e..fc301b143 100644 --- a/src/bin/tectonic.rs +++ b/src/bin/tectonic.rs @@ -13,10 +13,10 @@ use std::time; use tectonic::config::PersistentConfig; use tectonic::driver::{OutputFormat, PassSetting, ProcessingSessionBuilder}; use tectonic::errors::{ErrorKind, Result}; +use tectonic::status::plain::PlainStatusBackend; use tectonic::status::termcolor::TermcolorStatusBackend; use tectonic::status::{ChatterLevel, StatusBackend}; - -use tectonic::{errmsg, tt_error, tt_error_styled, tt_note}; +use tectonic::{errmsg, tt_error, tt_note}; #[derive(Debug, StructOpt)] #[structopt(name = "Tectonic", about = "Process a (La)TeX document")] @@ -37,11 +37,14 @@ struct CliOptions { /// How much chatter to print when running #[structopt(long = "chatter", short, name = "level", default_value = "default", possible_values(&["default", "minimal"]))] chatter_level: String, + /// Enable/disable colorful log output. + #[structopt(long = "color", name = "when", default_value = "auto", possible_values(&["always", "auto", "never"]))] + cli_color: String, /// Use only resource files cached locally #[structopt(short = "C", long)] only_cached: bool, /// The kind of output to generate - #[structopt(long, name = "format", default_value = "pdf", possible_values(&["pdf", "html", "xdv", "aux", "format"]))] + #[structopt(long, name = "format", default_value = "pdf", possible_values(&["pdf", "html", "xdv", "aux", "fmt"]))] outfmt: String, /// Write Makefile-format rules expressing the dependencies of this run to #[structopt(long, name = "dest_path")] @@ -71,11 +74,7 @@ struct CliOptions { #[structopt(name = "outdir", short, long, parse(from_os_str))] outdir: Option, } -fn inner( - args: CliOptions, - config: PersistentConfig, - status: &mut TermcolorStatusBackend, -) -> Result<()> { +fn inner(args: CliOptions, config: PersistentConfig, status: &mut dyn StatusBackend) -> Result<()> { let mut sess_builder = ProcessingSessionBuilder::default(); let format_path = args.format; sess_builder @@ -187,10 +186,8 @@ fn inner( "something bad happened inside {}; its output follows:\n", engine ); - tt_error_styled!(status, "==============================================================================="); - status.dump_to_stderr(&output); - tt_error_styled!(status, "==============================================================================="); - tt_error_styled!(status, ""); + + status.dump_error_logs(&output); } } } @@ -235,8 +232,19 @@ fn main() { // something I'd be relatively OK with since it'd only affect the progam // UI, not the processing results). - let mut status = - TermcolorStatusBackend::new(ChatterLevel::from_str(&args.chatter_level).unwrap()); + let chatter_level = ChatterLevel::from_str(&args.chatter_level).unwrap(); + let use_cli_color = match &*args.cli_color { + "always" => true, + "auto" => atty::is(atty::Stream::Stdout), + "never" => false, + _ => unreachable!(), + }; + + let mut status = if use_cli_color { + Box::new(TermcolorStatusBackend::new(chatter_level)) as Box + } else { + Box::new(PlainStatusBackend::new(chatter_level)) as Box + }; // For now ... @@ -249,8 +257,8 @@ fn main() { // function ... all so that we can print out the word "error:" in red. // This code parallels various bits of the `error_chain` crate. - if let Err(ref e) = inner(args, config, &mut status) { - status.bare_error(e); + if let Err(ref e) = inner(args, config, &mut *status) { + tt_error!(status, ""; e); process::exit(1) } } diff --git a/src/driver.rs b/src/driver.rs index 4427d6554..0f53bf3ec 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -633,7 +633,7 @@ impl ProcessingSession { /// Assess whether we need to rerun an engine. This is the case if there /// was a file that the engine read and then rewrote, and the rewritten /// version is different than the version that it read in. - fn is_rerun_needed(&self, status: &mut S) -> Option { + fn is_rerun_needed(&self, status: &mut dyn StatusBackend) -> Option { // TODO: we should probably wire up diagnostics since I expect this // stuff could get finicky and we're going to want to be able to // figure out why rerun detection is breaking. @@ -664,7 +664,7 @@ impl ProcessingSession { } #[allow(dead_code)] - fn _dump_access_info(&self, status: &mut S) { + fn _dump_access_info(&self, status: &mut dyn StatusBackend) { for (name, info) in &self.events.0 { if info.access_pattern != AccessPattern::Read { let r = match info.read_digest { @@ -697,7 +697,7 @@ impl ProcessingSession { /// - run BibTeX, if it seems to be required /// - repeat the last two steps as often as needed /// - write the output files to disk, including a Makefile if it was requested. - pub fn run(&mut self, status: &mut S) -> Result<()> { + pub fn run(&mut self, status: &mut dyn StatusBackend) -> Result<()> { // Do we need to generate the format file? let generate_format = if self.output_format == OutputFormat::Format { @@ -817,10 +817,10 @@ impl ProcessingSession { Ok(()) } - fn write_files( + fn write_files( &mut self, mut mf_dest_maybe: Option<&mut File>, - status: &mut S, + status: &mut dyn StatusBackend, only_logs: bool, ) -> Result { let root = match self.output_path { @@ -909,11 +909,7 @@ impl ProcessingSession { /// The "default" pass really runs a bunch of sub-passes. It is a "Do What /// I Mean" operation. - fn default_pass( - &mut self, - bibtex_first: bool, - status: &mut S, - ) -> Result { + fn default_pass(&mut self, bibtex_first: bool, status: &mut dyn StatusBackend) -> Result { // If `bibtex_first` is true, we start by running bibtex, and run // proceed with the standard rerun logic. Otherwise, we run TeX, // auto-detect whether we need to run bibtex, possibly run it, and @@ -1011,7 +1007,7 @@ impl ProcessingSession { } /// Use the TeX engine to generate a format file. - fn make_format_pass(&mut self, status: &mut S) -> Result { + fn make_format_pass(&mut self, status: &mut dyn StatusBackend) -> Result { if self.io.bundle.is_none() { return Err( ErrorKind::Msg("cannot create formats without using a bundle".to_owned()).into(), @@ -1089,10 +1085,10 @@ impl ProcessingSession { } /// Run one pass of the TeX engine. - fn tex_pass( + fn tex_pass( &mut self, rerun_explanation: Option<&str>, - status: &mut S, + status: &mut dyn StatusBackend, ) -> Result> { let result = { let mut stack = self.io.as_stack(); @@ -1131,7 +1127,7 @@ impl ProcessingSession { Ok(warnings) } - fn bibtex_pass(&mut self, status: &mut S) -> Result { + fn bibtex_pass(&mut self, status: &mut dyn StatusBackend) -> Result { let result = { let mut stack = self.io.as_stack(); let mut engine = BibtexEngine::new(); @@ -1167,7 +1163,7 @@ impl ProcessingSession { Ok(0) } - fn xdvipdfmx_pass(&mut self, status: &mut S) -> Result { + fn xdvipdfmx_pass(&mut self, status: &mut dyn StatusBackend) -> Result { { let mut stack = self.io.as_stack(); let mut engine = XdvipdfmxEngine::new().with_date(self.build_date); @@ -1185,7 +1181,7 @@ impl ProcessingSession { Ok(0) } - fn spx2html_pass(&mut self, status: &mut S) -> Result { + fn spx2html_pass(&mut self, status: &mut dyn StatusBackend) -> Result { { let mut stack = self.io.as_stack(); let mut engine = Spx2HtmlEngine::new(); diff --git a/src/engines/bibtex.rs b/src/engines/bibtex.rs index a7895627e..32cc64b8f 100644 --- a/src/engines/bibtex.rs +++ b/src/engines/bibtex.rs @@ -29,8 +29,8 @@ impl BibtexEngine { let caux = CString::new(aux)?; - let /*mut*/ state = ExecutionState::new(io, events, status); - let bridge = TectonicBridgeApi::new(&state); + let mut state = ExecutionState::new(io, events, status); + let bridge = TectonicBridgeApi::new(&mut state); unsafe { match super::bibtex_simple_main(&bridge, caux.as_ptr()) { diff --git a/src/engines/mod.rs b/src/engines/mod.rs index e9796189e..82e766711 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -12,6 +12,9 @@ //! substantial private API that defines the interface between Tectonic's Rust //! code and the C/C++ code that the backends are (currently) implemented in. +// Mostly for the bridge functions +#![allow(clippy::not_unsafe_ptr_arg_deref)] + use flate2::read::GzDecoder; use flate2::{Compression, GzBuilder}; use lazy_static::lazy_static; @@ -122,8 +125,8 @@ lazy_static! { /// The methods on ExecutionState pretty much all work to implement for the /// "bridge" API (C/C++ => Rust) defined below. -struct ExecutionState<'a, I: 'a + IoProvider> { - io: &'a mut I, +pub struct ExecutionState<'a> { + io: &'a mut dyn IoProvider, events: &'a mut dyn IoEventBackend, status: &'a mut dyn StatusBackend, #[allow(clippy::vec_box)] @@ -132,12 +135,12 @@ struct ExecutionState<'a, I: 'a + IoProvider> { output_handles: Vec>, } -impl<'a, I: 'a + IoProvider> ExecutionState<'a, I> { +impl<'a> ExecutionState<'a> { pub fn new( - io: &'a mut I, + io: &'a mut dyn IoProvider, events: &'a mut dyn IoEventBackend, status: &'a mut dyn StatusBackend, - ) -> ExecutionState<'a, I> { + ) -> ExecutionState<'a> { ExecutionState { io, events, @@ -281,13 +284,13 @@ impl<'a, I: 'a + IoProvider> ExecutionState<'a, I> { error_occurred } - fn output_open(&mut self, name: &OsStr, is_gz: bool) -> *const OutputHandle { + fn output_open(&mut self, name: &OsStr, is_gz: bool) -> *mut OutputHandle { let mut oh = match self.io.output_open_name(name) { OpenResult::Ok(oh) => oh, - OpenResult::NotAvailable => return ptr::null(), + OpenResult::NotAvailable => return ptr::null_mut(), OpenResult::Err(e) => { tt_warning!(self.status, "open of output {} failed", name.to_string_lossy(); e); - return ptr::null(); + return ptr::null_mut(); } }; @@ -301,22 +304,22 @@ impl<'a, I: 'a + IoProvider> ExecutionState<'a, I> { self.events.output_opened(oh.name()); self.output_handles.push(Box::new(oh)); - &*self.output_handles[self.output_handles.len() - 1] + &mut **self.output_handles.last_mut().unwrap() } - fn output_open_stdout(&mut self) -> *const OutputHandle { + fn output_open_stdout(&mut self) -> *mut OutputHandle { let oh = match self.io.output_open_stdout() { OpenResult::Ok(oh) => oh, - OpenResult::NotAvailable => return ptr::null(), + OpenResult::NotAvailable => return ptr::null_mut(), OpenResult::Err(e) => { tt_warning!(self.status, "open of stdout failed"; e); - return ptr::null(); + return ptr::null_mut(); } }; self.events.stdout_opened(); self.output_handles.push(Box::new(oh)); - &*self.output_handles[self.output_handles.len() - 1] + &mut **self.output_handles.last_mut().unwrap() } fn output_write(&mut self, handle: *mut OutputHandle, buf: &[u8]) -> bool { @@ -367,41 +370,41 @@ impl<'a, I: 'a + IoProvider> ExecutionState<'a, I> { rv } - fn input_open(&mut self, name: &OsStr, format: FileFormat, is_gz: bool) -> *const InputHandle { + fn input_open(&mut self, name: &OsStr, format: FileFormat, is_gz: bool) -> *mut InputHandle { let ih = match self.input_open_name_format_gz(name, format, is_gz) { OpenResult::Ok(ih) => ih, OpenResult::NotAvailable => { self.events.input_not_available(name); - return ptr::null(); + return ptr::null_mut(); } OpenResult::Err(e) => { tt_warning!(self.status, "open of input {} failed", name.to_string_lossy(); e); - return ptr::null(); + return ptr::null_mut(); } }; // the file name may have had an extension added, so we use ih.name() here: self.events.input_opened(ih.name(), ih.origin()); self.input_handles.push(Box::new(ih)); - &*self.input_handles[self.input_handles.len() - 1] + &mut **self.input_handles.last_mut().unwrap() } - fn input_open_primary(&mut self) -> *const InputHandle { + fn input_open_primary(&mut self) -> *mut InputHandle { let ih = match self.io.input_open_primary(self.status) { OpenResult::Ok(ih) => ih, OpenResult::NotAvailable => { tt_error!(self.status, "primary input not available (?!)"); - return ptr::null(); + return ptr::null_mut(); } OpenResult::Err(e) => { tt_error!(self.status, "open of primary input failed"; e); - return ptr::null(); + return ptr::null_mut(); } }; self.events.primary_input_opened(ih.origin()); self.input_handles.push(Box::new(ih)); - &*self.input_handles[self.input_handles.len() - 1] + &mut **self.input_handles.last_mut().unwrap() } fn input_get_size(&mut self, handle: *mut InputHandle) -> usize { @@ -459,70 +462,63 @@ impl<'a, I: 'a + IoProvider> ExecutionState<'a, I> { } } -// Now, here' the actual C API. There are two parts to this: the functions in -// the backing C/C++ code that *we* call, and the API bridge -- a struct of -// function pointers that we pass to the C/C++ entry points so that they can -// call back into our code. Keep synchronized with **tectonic/core-bridge.h**. +// The bridge only contains the ExecutionState now. It used to hold pointers to the below bridge +// api functions (which would allow the C code to call back into our code), but those are now +// exported using cbindgen. #[repr(C)] -struct TectonicBridgeApi { - context: *const libc::c_void, - diag_warn_begin: *const libc::c_void, - diag_error_begin: *const libc::c_void, - diag_finish: *const libc::c_void, - diag_append: *const libc::c_void, - issue_warning: *const libc::c_void, - issue_error: *const libc::c_void, - get_file_md5: *const libc::c_void, - get_data_md5: *const libc::c_void, - output_open: *const libc::c_void, - output_open_stdout: *const libc::c_void, - output_putc: *const libc::c_void, - output_write: *const libc::c_void, - output_flush: *const libc::c_void, - output_close: *const libc::c_void, - input_open: *const libc::c_void, - input_open_primary: *const libc::c_void, - input_get_size: *const libc::c_void, - input_seek: *const libc::c_void, - input_read: *const libc::c_void, - input_getc: *const libc::c_void, - input_ungetc: *const libc::c_void, - input_close: *const libc::c_void, +pub struct TectonicBridgeApi<'a> { + context: *mut ExecutionState<'a>, } +// This silences the warning that ExecutionState is not FFI-safe. The C side only passes the +// pointer around and doesn't actually look into the struct, so we can ignore this warning. + +#[allow(improper_ctypes)] extern "C" { + fn tt_get_error_message() -> *const libc::c_char; + fn tt_xetex_set_int_variable(var_name: *const libc::c_char, value: libc::c_int) -> libc::c_int; - //fn tt_xetex_set_string_variable(var_name: *const libc::c_char, value: *const libc::c_char) -> libc::c_int; + + #[allow(dead_code)] // currently unused + fn tt_xetex_set_string_variable( + var_name: *const libc::c_char, + value: *const libc::c_char, + ) -> libc::c_int; + fn tex_simple_main( - api: *const TectonicBridgeApi, + api: &TectonicBridgeApi, dump_name: *const libc::c_char, input_file_name: *const libc::c_char, build_date: libc::time_t, ) -> libc::c_int; + fn dvipdfmx_simple_main( - api: *const TectonicBridgeApi, + api: &TectonicBridgeApi, dviname: *const libc::c_char, pdfname: *const libc::c_char, enable_compression: bool, deterministic_tags: bool, build_date: libc::time_t, ) -> libc::c_int; + fn bibtex_simple_main( - api: *const TectonicBridgeApi, + api: &TectonicBridgeApi, aux_file_name: *const libc::c_char, ) -> libc::c_int; + } // Entry points for the C/C++ API functions. -struct Diagnostic { +pub struct Diagnostic { message: String, kind: MessageKind, } -extern "C" fn diag_warn_begin() -> *mut Diagnostic { +#[no_mangle] +pub extern "C" fn diag_warn_begin() -> *mut Diagnostic { let warning = Box::new(Diagnostic { message: String::new(), kind: MessageKind::Warning, @@ -530,7 +526,8 @@ extern "C" fn diag_warn_begin() -> *mut Diagnostic { Box::into_raw(warning) } -extern "C" fn diag_error_begin() -> *mut Diagnostic { +#[no_mangle] +pub extern "C" fn diag_error_begin() -> *mut Diagnostic { let warning = Box::new(Diagnostic { message: String::new(), kind: MessageKind::Error, @@ -538,50 +535,41 @@ extern "C" fn diag_error_begin() -> *mut Diagnostic { Box::into_raw(warning) } -extern "C" fn diag_finish<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - diag: *mut Diagnostic, -) { +#[no_mangle] +pub extern "C" fn diag_finish(es: &mut ExecutionState, diag: *mut Diagnostic) { let rdiag = unsafe { Box::from_raw(diag as *mut Diagnostic) }; - let es = unsafe { &mut *es }; es.status .report(rdiag.kind, format_args!("{}", rdiag.message), None); } -extern "C" fn diag_append(diag: *mut Diagnostic, text: *const libc::c_char) { - let rdiag = unsafe { &mut *diag }; +#[no_mangle] +pub extern "C" fn diag_append(diag: &mut Diagnostic, text: *const libc::c_char) { let rtext = unsafe { CStr::from_ptr(text) }; - rdiag.message.push_str(&rtext.to_string_lossy()); + diag.message.push_str(&rtext.to_string_lossy()); } -extern "C" fn issue_warning<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - text: *const libc::c_char, -) { - let es = unsafe { &mut *es }; +#[no_mangle] +pub extern "C" fn issue_warning(es: &mut ExecutionState, text: *const libc::c_char) { let rtext = unsafe { CStr::from_ptr(text) }; tt_warning!(es.status, "{}", rtext.to_string_lossy()); } -extern "C" fn issue_error<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - text: *const libc::c_char, -) { - let es = unsafe { &mut *es }; +#[no_mangle] +pub extern "C" fn issue_error(es: &mut ExecutionState, text: *const libc::c_char) { let rtext = unsafe { CStr::from_ptr(text) }; tt_error!(es.status, "{}", rtext.to_string_lossy()); } -extern "C" fn get_file_md5<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, +#[no_mangle] +pub extern "C" fn get_file_md5( + es: &mut ExecutionState, path: *const libc::c_char, digest: *mut u8, ) -> libc::c_int { - let es = unsafe { &mut *es }; let rpath = osstr_from_cstr(unsafe { CStr::from_ptr(path) }); let rdest = unsafe { slice::from_raw_parts_mut(digest, 16) }; @@ -592,13 +580,8 @@ extern "C" fn get_file_md5<'a, I: 'a + IoProvider>( } } -extern "C" fn get_data_md5<'a, I: 'a + IoProvider>( - _es: *mut ExecutionState<'a, I>, - data: *const u8, - len: libc::size_t, - digest: *mut u8, -) -> libc::c_int { - //let es = unsafe { &mut *es }; +#[no_mangle] +pub extern "C" fn get_data_md5(data: *const u8, len: libc::size_t, digest: *mut u8) -> libc::c_int { let rdata = unsafe { slice::from_raw_parts(data, len) }; let rdest = unsafe { slice::from_raw_parts_mut(digest, 16) }; @@ -610,139 +593,116 @@ extern "C" fn get_data_md5<'a, I: 'a + IoProvider>( 0 } -extern "C" fn output_open<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, +#[no_mangle] +pub extern "C" fn output_open( + es: &mut ExecutionState, name: *const libc::c_char, is_gz: libc::c_int, -) -> *const libc::c_void { - let es = unsafe { &mut *es }; +) -> *mut OutputHandle { let rname = osstr_from_cstr(&unsafe { CStr::from_ptr(name) }); let ris_gz = is_gz != 0; - es.output_open(&rname, ris_gz) as *const _ + es.output_open(&rname, ris_gz) } -extern "C" fn output_open_stdout<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, -) -> *const libc::c_void { - let es = unsafe { &mut *es }; - - es.output_open_stdout() as *const _ +#[no_mangle] +pub extern "C" fn output_open_stdout(es: &mut ExecutionState) -> *mut OutputHandle { + es.output_open_stdout() } -extern "C" fn output_putc<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, +#[no_mangle] +pub extern "C" fn output_putc( + es: &mut ExecutionState, + handle: *mut OutputHandle, c: libc::c_int, ) -> libc::c_int { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut OutputHandle; let rc = c as u8; - if es.output_write(rhandle, &[rc]) { + if es.output_write(handle, &[rc]) { libc::EOF } else { c } } -extern "C" fn output_write<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, +#[no_mangle] +pub extern "C" fn output_write( + es: &mut ExecutionState, + handle: *mut OutputHandle, data: *const u8, len: libc::size_t, ) -> libc::size_t { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut OutputHandle; let rdata = unsafe { slice::from_raw_parts(data, len) }; // NOTE: we use f.write_all() so partial writes are not gonna be a thing. - if es.output_write(rhandle, rdata) { + if es.output_write(handle, rdata) { 0 } else { len } } -extern "C" fn output_flush<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, -) -> libc::c_int { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut OutputHandle; - - if es.output_flush(rhandle) { +#[no_mangle] +pub extern "C" fn output_flush(es: &mut ExecutionState, handle: *mut OutputHandle) -> libc::c_int { + if es.output_flush(handle) { 1 } else { 0 } } -extern "C" fn output_close<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, -) -> libc::c_int { - let es = unsafe { &mut *es }; - +#[no_mangle] +pub extern "C" fn output_close(es: &mut ExecutionState, handle: *mut OutputHandle) -> libc::c_int { if handle.is_null() { return 0; // This is/was the behavior of close_file() in C. } - let rhandle = handle as *mut OutputHandle; - - if es.output_close(rhandle) { + if es.output_close(handle) { 1 } else { 0 } } -extern "C" fn input_open<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, +#[no_mangle] +pub extern "C" fn input_open( + es: &mut ExecutionState, name: *const libc::c_char, format: libc::c_int, is_gz: libc::c_int, -) -> *const libc::c_void { - let es = unsafe { &mut *es }; +) -> *mut InputHandle { let rname = osstr_from_cstr(unsafe { CStr::from_ptr(name) }); let rformat = c_format_to_rust(format); let ris_gz = is_gz != 0; match rformat { - Some(fmt) => es.input_open(&rname, fmt, ris_gz) as *const _, - None => ptr::null(), + Some(fmt) => es.input_open(&rname, fmt, ris_gz), + None => ptr::null_mut(), } } -extern "C" fn input_open_primary<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, -) -> *const libc::c_void { - let es = unsafe { &mut *es }; - - es.input_open_primary() as *const _ +#[no_mangle] +pub extern "C" fn input_open_primary(es: &mut ExecutionState) -> *mut InputHandle { + es.input_open_primary() } -extern "C" fn input_get_size<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, +#[no_mangle] +pub extern "C" fn input_get_size( + es: &mut ExecutionState, + handle: *mut InputHandle, ) -> libc::size_t { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut InputHandle; - - es.input_get_size(rhandle) + es.input_get_size(handle) } -extern "C" fn input_seek<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, +#[no_mangle] +pub extern "C" fn input_seek( + es: &mut ExecutionState, + handle: *mut InputHandle, offset: libc::ssize_t, whence: libc::c_int, internal_error: *mut libc::c_int, ) -> libc::size_t { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut InputHandle; - let rwhence = match whence { libc::SEEK_SET => SeekFrom::Start(offset as u64), libc::SEEK_CUR => SeekFrom::Current(offset as i64), @@ -760,7 +720,7 @@ extern "C" fn input_seek<'a, I: 'a + IoProvider>( } }; - match es.input_seek(rhandle, rwhence) { + match es.input_seek(handle, rwhence) { Ok(pos) => pos as libc::size_t, Err(e) => { // TODO: Handle the error better. Report the error properly to the caller? @@ -770,17 +730,12 @@ extern "C" fn input_seek<'a, I: 'a + IoProvider>( } } -extern "C" fn input_getc<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, -) -> libc::c_int { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut InputHandle; - +#[no_mangle] +pub extern "C" fn input_getc(es: &mut ExecutionState, handle: *mut InputHandle) -> libc::c_int { // If we couldn't fill the whole (1-byte) buffer, that's boring old EOF. // No need to complain. Fun match statement here. - match es.input_getc(rhandle) { + match es.input_getc(handle) { Ok(b) => libc::c_int::from(b), Err(Error(ErrorKind::Io(ref ioe), _)) if ioe.kind() == io::ErrorKind::UnexpectedEof => { libc::EOF @@ -792,15 +747,13 @@ extern "C" fn input_getc<'a, I: 'a + IoProvider>( } } -extern "C" fn input_ungetc<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, +#[no_mangle] +pub extern "C" fn input_ungetc( + es: &mut ExecutionState, + handle: *mut InputHandle, ch: libc::c_int, ) -> libc::c_int { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut InputHandle; - - match es.input_ungetc(rhandle, ch as u8) { + match es.input_ungetc(handle, ch as u8) { Ok(_) => 0, Err(e) => { tt_warning!(es.status, "ungetc() failed"; e); @@ -809,17 +762,16 @@ extern "C" fn input_ungetc<'a, I: 'a + IoProvider>( } } -extern "C" fn input_read<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, +#[no_mangle] +pub extern "C" fn input_read( + es: &mut ExecutionState, + handle: *mut InputHandle, data: *mut u8, len: libc::size_t, ) -> libc::ssize_t { - let es = unsafe { &mut *es }; - let rhandle = handle as *mut InputHandle; let rdata = unsafe { slice::from_raw_parts_mut(data, len) }; - match es.input_read(rhandle, rdata) { + match es.input_read(handle, rdata) { Ok(_) => len as isize, Err(e) => { tt_warning!(es.status, "{}-byte read failed", len; e); @@ -828,53 +780,23 @@ extern "C" fn input_read<'a, I: 'a + IoProvider>( } } -extern "C" fn input_close<'a, I: 'a + IoProvider>( - es: *mut ExecutionState<'a, I>, - handle: *mut libc::c_void, -) -> libc::c_int { - let es = unsafe { &mut *es }; - +#[no_mangle] +pub extern "C" fn input_close(es: &mut ExecutionState, handle: *mut InputHandle) -> libc::c_int { if handle.is_null() { return 0; // This is/was the behavior of close_file() in C. } - let rhandle = handle as *mut InputHandle; - - if es.input_close(rhandle) { + if es.input_close(handle) { 1 } else { 0 } } -// All of these entry points are used to populate the bridge API struct: - -impl TectonicBridgeApi { - fn new<'a, I: 'a + IoProvider>(exec_state: &ExecutionState<'a, I>) -> TectonicBridgeApi { +impl TectonicBridgeApi<'_> { + fn new<'a>(exec_state: &'a mut ExecutionState<'a>) -> TectonicBridgeApi<'a> { TectonicBridgeApi { - context: (exec_state as *const ExecutionState<'a, I>) as *const libc::c_void, - diag_warn_begin: diag_warn_begin as *const libc::c_void, - diag_error_begin: diag_error_begin as *const libc::c_void, - diag_finish: diag_finish::<'a, I> as *const libc::c_void, - diag_append: diag_append as *const libc::c_void, - issue_warning: issue_warning::<'a, I> as *const libc::c_void, - issue_error: issue_error::<'a, I> as *const libc::c_void, - get_file_md5: get_file_md5::<'a, I> as *const libc::c_void, - get_data_md5: get_data_md5::<'a, I> as *const libc::c_void, - output_open: output_open::<'a, I> as *const libc::c_void, - output_open_stdout: output_open_stdout::<'a, I> as *const libc::c_void, - output_putc: output_putc::<'a, I> as *const libc::c_void, - output_write: output_write::<'a, I> as *const libc::c_void, - output_flush: output_flush::<'a, I> as *const libc::c_void, - output_close: output_close::<'a, I> as *const libc::c_void, - input_open: input_open::<'a, I> as *const libc::c_void, - input_open_primary: input_open_primary::<'a, I> as *const libc::c_void, - input_get_size: input_get_size::<'a, I> as *const libc::c_void, - input_seek: input_seek::<'a, I> as *const libc::c_void, - input_read: input_read::<'a, I> as *const libc::c_void, - input_getc: input_getc::<'a, I> as *const libc::c_void, - input_ungetc: input_ungetc::<'a, I> as *const libc::c_void, - input_close: input_close::<'a, I> as *const libc::c_void, + context: exec_state, } } } @@ -882,6 +804,8 @@ impl TectonicBridgeApi { // Finally, some support -- several of the C API functions pass arguments that // are "file format" enumerations. This code bridges the two. See the // `tt_input_format_type` enum in . +// +// TODO use cbindgen to export this so we don't need to synchronise definitions #[derive(Clone, Copy, Debug)] enum FileFormat { diff --git a/src/engines/tex.rs b/src/engines/tex.rs index a3646a9da..f55d896e9 100644 --- a/src/engines/tex.rs +++ b/src/engines/tex.rs @@ -115,8 +115,8 @@ impl TexEngine { let cformat = CString::new(format_file_name)?; let cinput = CString::new(input_file_name)?; - let /*mut*/ state = ExecutionState::new(io, events, status); - let bridge = TectonicBridgeApi::new(&state); + let mut state = ExecutionState::new(io, events, status); + let bridge = TectonicBridgeApi::new(&mut state); // initialize globals let v = if self.halt_on_error { 1 } else { 0 }; diff --git a/src/engines/xdvipdfmx.rs b/src/engines/xdvipdfmx.rs index c141d4aa0..45ab2a686 100644 --- a/src/engines/xdvipdfmx.rs +++ b/src/engines/xdvipdfmx.rs @@ -57,8 +57,8 @@ impl XdvipdfmxEngine { let cdvi = CString::new(dvi)?; let cpdf = CString::new(pdf)?; - let /*mut*/ state = ExecutionState::new(io, events, status); - let bridge = TectonicBridgeApi::new(&state); + let mut state = ExecutionState::new(io, events, status); + let bridge = TectonicBridgeApi::new(&mut state); unsafe { match super::dvipdfmx_simple_main( diff --git a/src/lib.rs b/src/lib.rs index a0651ac06..5954d43e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,11 @@ pub use crate::engines::tex::{TexEngine, TexResult}; pub use crate::engines::xdvipdfmx::XdvipdfmxEngine; pub use crate::errors::{Error, ErrorKind, Result}; -const FORMAT_SERIAL: u32 = 28; // keep synchronized with tectonic/xetex-constants.h!! +// Increase this whenever the engine internals change such that the contents +// of the "format" files must be regenerated -- this includes changes to the +// string pool. + +pub const FORMAT_SERIAL: u32 = 28; /// Compile LaTeX text to a PDF. /// diff --git a/src/status/mod.rs b/src/status/mod.rs index 0a7454a42..663dc9f02 100644 --- a/src/status/mod.rs +++ b/src/status/mod.rs @@ -4,6 +4,7 @@ //! A framework for showing status messages to the user. +pub mod plain; pub mod termcolor; use std::cmp; @@ -77,6 +78,10 @@ pub trait StatusBackend { None, ) } + + /// This is used to print TeX engine logs after it encountered errors. This prints the log, + /// surrounded by lines of equal signs. + fn dump_error_logs(&mut self, output: &[u8]); } /// Report a formatted informational message to the user. @@ -133,4 +138,5 @@ impl NoopStatusBackend { impl StatusBackend for NoopStatusBackend { fn report(&mut self, _kind: MessageKind, _args: Arguments, _err: Option<&Error>) {} + fn dump_error_logs(&mut self, _output: &[u8]) {} } diff --git a/src/status/plain.rs b/src/status/plain.rs new file mode 100644 index 000000000..af5090b88 --- /dev/null +++ b/src/status/plain.rs @@ -0,0 +1,67 @@ +use std::fmt::Arguments; + +use super::{ChatterLevel, MessageKind, StatusBackend}; +use crate::errors::Error; +use std::io::{self, Write}; + +pub struct PlainStatusBackend { + chatter: ChatterLevel, +} + +impl PlainStatusBackend { + pub fn new(chatter: ChatterLevel) -> Self { + PlainStatusBackend { chatter } + } +} + +impl StatusBackend for PlainStatusBackend { + fn report(&mut self, kind: MessageKind, args: Arguments, err: Option<&Error>) { + if kind == MessageKind::Note && self.chatter <= ChatterLevel::Minimal { + return; + } + + let prefix = match kind { + MessageKind::Note => "note:", + MessageKind::Warning => "warning:", + MessageKind::Error => "error:", + }; + if kind == MessageKind::Note { + println!("{} {}", prefix, args); + } else { + eprintln!("{} {}", prefix, args); + } + if let Some(e) = err { + for item in e.iter() { + eprintln!("caused by: {}", item); + } + if let Some(backtrace) = e.backtrace() { + eprintln!("debugging: backtrace follows:"); + eprintln!("{:?}", backtrace); + } + } + } + + fn note_highlighted(&mut self, before: &str, highlighted: &str, after: &str) { + if self.chatter > ChatterLevel::Minimal { + self.report( + MessageKind::Note, + format_args!("{}{}{}", before, highlighted, after), + None, + ); + } + } + + fn dump_error_logs(&mut self, output: &[u8]) { + eprintln!( + "===============================================================================" + ); + + io::stderr() + .write_all(output) + .expect("write to stderr failed"); + + eprintln!( + "===============================================================================" + ); + } +} diff --git a/src/status/termcolor.rs b/src/status/termcolor.rs index 36a54e2ce..7099a1c1b 100644 --- a/src/status/termcolor.rs +++ b/src/status/termcolor.rs @@ -137,12 +137,6 @@ impl TermcolorStatusBackend { }); } } - - pub fn dump_to_stderr(&mut self, output: &[u8]) { - self.stderr - .write_all(output) - .expect("write to stderr failed"); - } } /// Show formatted text to the user, styled as an error message. @@ -185,4 +179,20 @@ impl StatusBackend for TermcolorStatusBackend { writeln!(self.stdout, "{}", after).expect("write to stdout failed"); } } + + fn dump_error_logs(&mut self, output: &[u8]) { + tt_error_styled!( + self, + "===============================================================================" + ); + + self.stderr + .write_all(output) + .expect("write to stderr failed"); + + tt_error_styled!( + self, + "===============================================================================" + ); + } } diff --git a/tectonic/core-bridge.c b/tectonic/core-bridge.c index 71c510865..992506754 100644 --- a/tectonic/core-bridge.c +++ b/tectonic/core-bridge.c @@ -20,7 +20,7 @@ /* The global variable that represents the Rust API. Some fine day we'll get * rid of all of the globals ... */ -static tt_bridge_api_t *tectonic_global_bridge = NULL; +static const tt_bridge_api_t *tectonic_global_bridge = NULL; /* Highest-level abort/error handling. */ @@ -52,7 +52,7 @@ tt_get_error_message(void) * setjmp aborts and error message extraction. */ int -tex_simple_main(tt_bridge_api_t *api, char *dump_name, char *input_file_name, time_t build_date) +tex_simple_main(const tt_bridge_api_t *api, const char *dump_name, const char *input_file_name, time_t build_date) { int rv; @@ -70,7 +70,7 @@ tex_simple_main(tt_bridge_api_t *api, char *dump_name, char *input_file_name, ti int -dvipdfmx_simple_main(tt_bridge_api_t *api, char *dviname, char *pdfname, bool compress, bool deterministic_tags, time_t build_date) +dvipdfmx_simple_main(const tt_bridge_api_t *api, const char *dviname, const char *pdfname, bool compress, bool deterministic_tags, time_t build_date) { int rv; @@ -89,7 +89,7 @@ dvipdfmx_simple_main(tt_bridge_api_t *api, char *dviname, char *pdfname, bool co int -bibtex_simple_main(tt_bridge_api_t *api, char *aux_file_name) +bibtex_simple_main(const tt_bridge_api_t *api, const char *aux_file_name) { int rv; @@ -113,32 +113,32 @@ bibtex_simple_main(tt_bridge_api_t *api, char *aux_file_name) diagnostic_t ttstub_diag_warn_begin(void) { - return TGB->diag_warn_begin(); + return diag_warn_begin(); } diagnostic_t ttstub_diag_error_begin(void) { - return TGB->diag_error_begin(); + return diag_error_begin(); } void ttstub_diag_finish(diagnostic_t diag) { - TGB->diag_finish(TGB->context, diag); + diag_finish(TGB->context, diag); } void ttstub_diag_append(diagnostic_t diag, char const *text) { - TGB->diag_append(diag, text); + diag_append(diag, text); } PRINTF_FUNC(2,0) void ttstub_diag_vprintf(diagnostic_t diag, const char *format, va_list ap) { vsnprintf(error_buf, BUF_SIZE, format, ap); /* Not ideal to (ab)use error_buf here */ - TGB->diag_append(diag, error_buf); + ttstub_diag_append(diag, error_buf); } PRINTF_FUNC(2,3) void @@ -159,7 +159,7 @@ ttstub_issue_warning(const char *format, ...) va_start(ap, format); vsnprintf(error_buf, BUF_SIZE, format, ap); /* Not ideal to (ab)use error_buf here */ va_end(ap); - TGB->issue_warning(TGB->context, error_buf); + issue_warning(TGB->context, error_buf); } PRINTF_FUNC(1,2) void @@ -170,7 +170,7 @@ ttstub_issue_error(const char *format, ...) va_start(ap, format); vsnprintf(error_buf, BUF_SIZE, format, ap); /* Not ideal to (ab)use error_buf here */ va_end(ap); - TGB->issue_error(TGB->context, error_buf); + issue_error(TGB->context, error_buf); } PRINTF_FUNC(2,3) int @@ -197,74 +197,76 @@ ttstub_fprintf(rust_output_handle_t handle, const char *format, ...) int ttstub_get_file_md5(char const *path, char *digest) { - return TGB->get_file_md5(TGB->context, path, digest); + // TODO change uses of ttstub_get_file_md5 to pass uint8_t* + return get_file_md5(TGB->context, path, (uint8_t*) digest); } int ttstub_get_data_md5(char const *data, size_t len, char *digest) { - return TGB->get_data_md5(TGB->context, data, len, digest); + // TODO change uses of ttstub_get_data_md5 to pass uint8_t* + return get_data_md5((uint8_t const*) data, len, (uint8_t*) digest); } rust_output_handle_t ttstub_output_open(char const *path, int is_gz) { - return TGB->output_open(TGB->context, path, is_gz); + return output_open(TGB->context, path, is_gz); } rust_output_handle_t ttstub_output_open_stdout(void) { - return TGB->output_open_stdout(TGB->context); + return output_open_stdout(TGB->context); } int ttstub_output_putc(rust_output_handle_t handle, int c) { - return TGB->output_putc(TGB->context, handle, c); + return output_putc(TGB->context, handle, c); } size_t ttstub_output_write(rust_output_handle_t handle, const char *data, size_t len) { - return TGB->output_write(TGB->context, handle, data, len); + return output_write(TGB->context, handle, (const uint8_t*) data, len); } int ttstub_output_flush(rust_output_handle_t handle) { - return TGB->output_flush(TGB->context, handle); + return output_flush(TGB->context, handle); } int ttstub_output_close(rust_output_handle_t handle) { - return TGB->output_close(TGB->context, handle); + return output_close(TGB->context, handle); } rust_input_handle_t ttstub_input_open(char const *path, tt_input_format_type format, int is_gz) { - return TGB->input_open(TGB->context, path, format, is_gz); + return input_open(TGB->context, path, format, is_gz); } rust_input_handle_t ttstub_input_open_primary(void) { - return TGB->input_open_primary(TGB->context); + return input_open_primary(TGB->context); } size_t ttstub_input_get_size(rust_input_handle_t handle) { - return TGB->input_get_size(TGB->context, handle); + return input_get_size(TGB->context, handle); } size_t ttstub_input_seek(rust_input_handle_t handle, ssize_t offset, int whence) { int internal_error = 0; - size_t rv = TGB->input_seek(TGB->context, handle, offset, whence, &internal_error); + size_t rv = input_seek(TGB->context, handle, offset, whence, &internal_error); if (internal_error) { // Nonzero indicates a serious internal error. longjmp(jump_buffer, 1); @@ -275,25 +277,25 @@ ttstub_input_seek(rust_input_handle_t handle, ssize_t offset, int whence) ssize_t ttstub_input_read(rust_input_handle_t handle, char *data, size_t len) { - return TGB->input_read(TGB->context, handle, data, len); + return input_read(TGB->context, handle, (uint8_t*) data, len); } int ttstub_input_getc(rust_input_handle_t handle) { - return TGB->input_getc(TGB->context, handle); + return input_getc(TGB->context, handle); } int ttstub_input_ungetc(rust_input_handle_t handle, int ch) { - return TGB->input_ungetc(TGB->context, handle, ch); + return input_ungetc(TGB->context, handle, ch); } int ttstub_input_close(rust_input_handle_t handle) { - if (TGB->input_close(TGB->context, handle)) { + if (input_close(TGB->context, handle)) { // Nonzero return value indicates a serious internal error. longjmp(jump_buffer, 1); } diff --git a/tectonic/core-bridge.h b/tectonic/core-bridge.h index 293816138..0f50f7d31 100644 --- a/tectonic/core-bridge.h +++ b/tectonic/core-bridge.h @@ -7,6 +7,7 @@ #define TECTONIC_CORE_BRIDGE_H #include "core-foundation.h" +#include "core-bindgen.h" /* Both XeTeX and bibtex use this enum: */ @@ -48,54 +49,12 @@ typedef enum TTIF_TECTONIC_PRIMARY = 59, /* quasi-hack to get the primary input */ } tt_input_format_type; -typedef void *rust_output_handle_t; -typedef void *rust_input_handle_t; -typedef void *diagnostic_t; - -/* Bridge API. Keep synchronized with src/engines/mod.rs. */ - -typedef struct tt_bridge_api_t { - void *context; - - diagnostic_t (*diag_warn_begin)(void); - diagnostic_t (*diag_error_begin)(void); - void (*diag_finish)(void *context, diagnostic_t diag); - void (*diag_append)(diagnostic_t diag, char const *text); - - void (*issue_warning)(void *context, char const *text); - void (*issue_error)(void *context, char const *text); - - int (*get_file_md5)(void *context, char const *path, char *digest); - int (*get_data_md5)(void *context, char const *data, size_t len, char *digest); - - rust_output_handle_t (*output_open)(void *context, char const *path, int is_gz); - rust_output_handle_t (*output_open_stdout)(void *context); - int (*output_putc)(void *context, rust_output_handle_t handle, int c); - size_t (*output_write)(void *context, rust_output_handle_t handle, const char *data, size_t len); - int (*output_flush)(void *context, rust_output_handle_t handle); - int (*output_close)(void *context, rust_output_handle_t handle); - - rust_input_handle_t (*input_open)(void *context, char const *path, tt_input_format_type format, int is_gz); - rust_input_handle_t (*input_open_primary)(void *context); - size_t (*input_get_size)(void *context, rust_input_handle_t handle); - size_t (*input_seek)(void *context, rust_input_handle_t handle, ssize_t offset, int whence, int* internal_error); - ssize_t (*input_read)(void *context, rust_input_handle_t handle, char *data, size_t len); - int (*input_getc)(void *context, rust_input_handle_t handle); - int (*input_ungetc)(void *context, rust_input_handle_t handle, int ch); - int (*input_close)(void *context, rust_input_handle_t handle); -} tt_bridge_api_t; - +typedef OutputHandle *rust_output_handle_t; +typedef InputHandle *rust_input_handle_t; +typedef Diagnostic *diagnostic_t; BEGIN_EXTERN_C -/* These functions are not meant to be used in the C/C++ code. They define the - * API that we expose to the Rust side of things. */ - -const char *tt_get_error_message(void); -int tex_simple_main(tt_bridge_api_t *api, char *dump_name, char *input_file_name, time_t build_date); -int dvipdfmx_simple_main(tt_bridge_api_t *api, char *dviname, char *pdfname, bool compress, bool deterministic_tags, time_t build_date); -int bibtex_simple_main(tt_bridge_api_t *api, char *aux_file_name); - /* The internal, C/C++ interface: */ NORETURN PRINTF_FUNC(1,2) int _tt_abort(const char *format, ...); diff --git a/tectonic/dpx-cidtype0.c b/tectonic/dpx-cidtype0.c index 61326acbe..28adfd09e 100644 --- a/tectonic/dpx-cidtype0.c +++ b/tectonic/dpx-cidtype0.c @@ -1321,7 +1321,7 @@ t1_load_UnicodeCMap (const char *font_name, { int cmap_id = -1; cff_font *cffont; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; if (!font_name) return -1; diff --git a/tectonic/dpx-epdf.c b/tectonic/dpx-epdf.c index beb581789..ec102779e 100644 --- a/tectonic/dpx-epdf.c +++ b/tectonic/dpx-epdf.c @@ -578,7 +578,7 @@ static struct operator int -pdf_copy_clip (FILE *image_file, int pageNo, double x_user, double y_user) +pdf_copy_clip (rust_input_handle_t image_file, int pageNo, double x_user, double y_user) { pdf_obj *page_tree, *contents; int depth = 0, top = -1; diff --git a/tectonic/dpx-epdf.h b/tectonic/dpx-epdf.h index 32b443c2d..3e128af63 100644 --- a/tectonic/dpx-epdf.h +++ b/tectonic/dpx-epdf.h @@ -37,7 +37,7 @@ #define pdfbox_trim 4 #define pdfbox_art 5 -int pdf_copy_clip (FILE *image_file, int page_index, +int pdf_copy_clip (rust_input_handle_t image_file, int page_index, double x_user, double y_user); int pdf_include_page (pdf_ximage * ximage, rust_input_handle_t handle, diff --git a/tectonic/dpx-otl_conf.c b/tectonic/dpx-otl_conf.c index b3d8b0c57..8ad4d04bd 100644 --- a/tectonic/dpx-otl_conf.c +++ b/tectonic/dpx-otl_conf.c @@ -457,7 +457,7 @@ otl_read_conf (const char *conf_name) { pdf_obj *rule; pdf_obj *gclass; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; char *filename, *wbuf, *p, *endptr; const char *pp; int size, len; diff --git a/tectonic/dpx-pdfdoc.c b/tectonic/dpx-pdfdoc.c index 5a88bd742..0ce6832f1 100644 --- a/tectonic/dpx-pdfdoc.c +++ b/tectonic/dpx-pdfdoc.c @@ -76,7 +76,7 @@ read_thumbnail (const char *thumb_filename) { pdf_obj *image_ref; int xobj_id; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; load_options options = {1, 0, NULL}; handle = ttstub_input_open(thumb_filename, TTIF_PICT, 0); diff --git a/tectonic/dpx-spc_dvips.c b/tectonic/dpx-spc_dvips.c index ee97d37fc..5f5af717f 100644 --- a/tectonic/dpx-spc_dvips.c +++ b/tectonic/dpx-spc_dvips.c @@ -60,7 +60,7 @@ static int spc_handler_ps_header (struct spc_env *spe, struct spc_arg *args) { char *pro; - rust_input_handle_t *ps_header; + rust_input_handle_t ps_header; skip_white(&args->curptr, args->endptr); if (args->curptr + 1 >= args->endptr || args->curptr[0] != '=') { diff --git a/tectonic/dpx-spc_pdfm.c b/tectonic/dpx-spc_pdfm.c index 3ebff2b9d..a6df8e6ff 100644 --- a/tectonic/dpx-spc_pdfm.c +++ b/tectonic/dpx-spc_pdfm.c @@ -1349,7 +1349,7 @@ spc_handler_pdfm_stream_with_type (struct spc_env *spe, struct spc_arg *args, in ssize_t nb_read; char *ident, *instring, *fullname; pdf_obj *tmp; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; skip_white(&args->curptr, args->endptr); diff --git a/tectonic/dpx-subfont.c b/tectonic/dpx-subfont.c index c167f75cc..913189b56 100644 --- a/tectonic/dpx-subfont.c +++ b/tectonic/dpx-subfont.c @@ -115,7 +115,7 @@ static char line_buf[LINE_BUF_SIZE]; * for line-continuation. */ static char * -readline (char *buf, int buf_len, rust_input_handle_t *handle) +readline (char *buf, int buf_len, rust_input_handle_t handle) { char *r, *q, *p = buf; int n = 0, c = 0; @@ -239,7 +239,7 @@ read_sfd_record (struct sfd_rec_ *rec, const char *lbuf) /* Scan for subfont IDs */ static int -scan_sfd_file (struct sfd_file_ *sfd, rust_input_handle_t *handle) +scan_sfd_file (struct sfd_file_ *sfd, rust_input_handle_t handle) { char *id; char *q, *p; @@ -308,7 +308,7 @@ find_sfd_file (const char *sfd_name) if (id < 0) { struct sfd_file_ *sfd = NULL; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; if (num_sfd_files >= max_sfd_files) { max_sfd_files += 8; @@ -362,7 +362,7 @@ sfd_load_record (const char *sfd_name, const char *subfont_id) { int rec_id = -1; struct sfd_file_ *sfd; - rust_input_handle_t *handle; + rust_input_handle_t handle; int sfd_id, i, error = 0; char *p, *q; diff --git a/tectonic/dpx-tfm.c b/tectonic/dpx-tfm.c index 29cec97eb..cbf7e9b07 100644 --- a/tectonic/dpx-tfm.c +++ b/tectonic/dpx-tfm.c @@ -927,7 +927,7 @@ tfm_get_design_size (int font_id) bool tfm_exists (const char *tfm_name) { - rust_input_handle_t *handle; + rust_input_handle_t handle; handle = ttstub_input_open(tfm_name, TTIF_OFM, 0); if (handle) { diff --git a/tectonic/dpx-truetype.c b/tectonic/dpx-truetype.c index b807d4b6c..deb091145 100644 --- a/tectonic/dpx-truetype.c +++ b/tectonic/dpx-truetype.c @@ -59,7 +59,7 @@ pdf_font_open_truetype (pdf_font *font) pdf_obj *fontdict, *descriptor; sfnt *sfont; int embedding = 1; /* Must be embedded. */ - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; int length, error = 0; assert( font ); @@ -875,7 +875,7 @@ pdf_font_load_truetype (pdf_font *font) int index = pdf_font_get_index(font); char **enc_vec; pdf_obj *fontfile; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; sfnt *sfont; int i, error = 0; diff --git a/tectonic/dpx-tt_cmap.c b/tectonic/dpx-tt_cmap.c index 8112cfd12..8dfce4cb9 100644 --- a/tectonic/dpx-tt_cmap.c +++ b/tectonic/dpx-tt_cmap.c @@ -1361,7 +1361,7 @@ otf_load_Unicode_CMap (const char *map_name, int ttc_index, /* 0 for non-TTC fon sfnt *sfont; ULONG offset = 0; char *base_name = NULL, *cmap_name = NULL; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; otl_gsub *gsub_vert = NULL, *gsub_list = NULL; tt_cmap *ttcmap; CIDSysInfo csi = {NULL, NULL, 0}; diff --git a/tectonic/dpx-type1c.c b/tectonic/dpx-type1c.c index 774d8c667..dacb38d1d 100644 --- a/tectonic/dpx-type1c.c +++ b/tectonic/dpx-type1c.c @@ -64,7 +64,7 @@ pdf_font_open_type1c (pdf_font *font) { char *fontname; char *ident; - rust_input_handle_t *handle = NULL; + rust_input_handle_t handle = NULL; sfnt *sfont; cff_font *cffont; pdf_obj *descriptor, *tmp; @@ -230,7 +230,7 @@ pdf_font_load_type1c (pdf_font *font) pdf_obj *pdfcharset; /* Actually string object */ char *usedchars; char *fontname, *uniqueTag, *ident, *fullname; - rust_input_handle_t *handle; + rust_input_handle_t handle; int encoding_id; pdf_obj *fontfile, *stream_dict; char **enc_vec; diff --git a/tectonic/xetex-constants.h b/tectonic/xetex-constants.h index f6f0f9412..ad858d20b 100644 --- a/tectonic/xetex-constants.h +++ b/tectonic/xetex-constants.h @@ -1030,10 +1030,4 @@ #define MOV_D_FIXED 6 #define MOV_Z_SEEN 12 -/* Increase this whenever the engine internals change such that the contents - * of the "format" files must be regenerated -- this includes changes to the - * string pool. KEEP SYNCHRONIZED WITH src/lib.rs!!! */ - -#define FORMAT_SERIAL 28 - #endif /* not TECTONIC_CONSTANTS_H */ diff --git a/tectonic/xetex-engine-interface.c b/tectonic/xetex-engine-interface.c index 903749c3b..2aa23264f 100644 --- a/tectonic/xetex-engine-interface.c +++ b/tectonic/xetex-engine-interface.c @@ -12,11 +12,8 @@ /* These functions aren't used within the C/C++ library, but are called * by the Rust code to configure the XeTeX engine before launching it. */ -int tt_xetex_set_int_variable (char *var_name, int value); -int tt_xetex_set_string_variable (char *var_name, char *value); - int -tt_xetex_set_int_variable (char *var_name, int value) +tt_xetex_set_int_variable (const char *var_name, int value) { if (streq_ptr(var_name, "halt_on_error_p")) halt_on_error_p = value; @@ -34,7 +31,7 @@ tt_xetex_set_int_variable (char *var_name, int value) int -tt_xetex_set_string_variable (char *var_name, char *value) +tt_xetex_set_string_variable (const char *var_name, const char *value) { /* Currently unused; see Git history for how we used to set output_comment */ return 1; diff --git a/tectonic/xetex-ini.c b/tectonic/xetex-ini.c index 274b64fc5..ca3484396 100644 --- a/tectonic/xetex-ini.c +++ b/tectonic/xetex-ini.c @@ -3942,7 +3942,7 @@ tt_cleanup(void) { } tt_history_t -tt_run_engine(char *dump_name, char *input_file_name, time_t build_date) +tt_run_engine(const char *dump_name, const char *input_file_name, time_t build_date) { int32_t font_k; diff --git a/tectonic/xetex-xetexd.h b/tectonic/xetex-xetexd.h index 10abab7c6..af14c2ec7 100644 --- a/tectonic/xetex-xetexd.h +++ b/tectonic/xetex-xetexd.h @@ -1136,7 +1136,7 @@ cur_length(void) { /* Tectonic related functions */ void tt_cleanup(void); -tt_history_t tt_run_engine(char *dump_name, char *input_file_name, time_t build_date); +tt_history_t tt_run_engine(const char *dump_name, const char *input_file_name, time_t build_date); /* formerly xetex.h: */ diff --git a/tests/executable.rs b/tests/executable.rs index ffd33540f..3dc1358fa 100644 --- a/tests/executable.rs +++ b/tests/executable.rs @@ -23,6 +23,7 @@ lazy_static! { root.push("tests"); root }; + static ref TARGET_RUNNER_WORDS: Vec = { // compile-time environment variable from build.rs: let target = env!("TARGET").to_owned(); @@ -36,6 +37,17 @@ lazy_static! { vec![] } }; + + // Special coverage-collection mode. This implementation is quite tuned for + // the Tectonic CI/CD system, so if you're trying to use it manually, expect + // some rough edges. + static ref KCOV_WORDS: Vec = { + if let Ok(runtext) = env::var("TECTONIC_EXETEST_KCOV_RUNNER") { + runtext.split_whitespace().map(|x| x.to_owned()).collect() + } else { + vec![] + } + }; } fn get_plain_format_arg() -> String { @@ -61,10 +73,32 @@ fn prep_tectonic(cwd: &Path, args: &[&str]) -> Command { println!("using tectonic binary at {:?}", tectonic); println!("using cwd {:?}", cwd); + // We may need to wrap the Tectonic invocation. If we're cross-compiling, we + // might need to use something like QEMU to actually be able to run the + // executable. If we're collecting code coverage information with kcov, we + // need to wrap the invocation with that program. let mut command = if TARGET_RUNNER_WORDS.len() > 0 { let mut cmd = Command::new(&TARGET_RUNNER_WORDS[0]); cmd.args(&TARGET_RUNNER_WORDS[1..]).arg(tectonic); cmd + } else if KCOV_WORDS.len() > 0 { + let mut cmd = Command::new(&KCOV_WORDS[0]); + cmd.args(&KCOV_WORDS[1..]); + + // Give kcov a directory into which to put its output. We use + // mktemp-like functionality to automatically create such directories + // uniquely so that we don't have to manually bookkeep. This does mean + // that successive runs will build up new data directories indefinitely. + let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + root.push("target"); + root.push("cov"); + root.push("exetest."); + let tempdir = tempfile::Builder::new().prefix(&root).tempdir().unwrap(); + let tempdir = tempdir.into_path(); + cmd.arg(tempdir); + + cmd.arg(tectonic); + cmd } else { Command::new(tectonic) }; @@ -102,8 +136,14 @@ fn setup_and_copy_files(files: &[&str]) -> TempDir { .prefix("tectonic_executable_test") .tempdir() .unwrap(); - let executable_test_dir = - PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("tests/executable"); + + // `cargo kcov` (0.5.2) does not set this variable: + let executable_test_dir = if let Some(v) = env::var_os("CARGO_MANIFEST_DIR") { + PathBuf::from(v) + } else { + PathBuf::new() + } + .join("tests/executable"); for file in files { // Create parent directories, if the file is not at the root of `tests/executable/` @@ -165,60 +205,36 @@ fn check_file(tempdir: &TempDir, rest: &str) { #[test] fn bad_chatter_1() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let output = run_tectonic(&PathBuf::from("."), &["-", "--chatter=reticent"]); error_or_panic(output); } #[test] fn bad_input_path_1() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let output = run_tectonic(&PathBuf::from("."), &["/"]); error_or_panic(output); } #[test] fn bad_input_path_2() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let output = run_tectonic(&PathBuf::from("."), &["somedir/.."]); error_or_panic(output); } #[test] fn bad_outfmt_1() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let output = run_tectonic(&PathBuf::from("."), &["-", "--outfmt=dd"]); error_or_panic(output); } #[test] fn help_flag() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let output = run_tectonic(&PathBuf::from("."), &["-h"]); success_or_panic(output); } #[test] // GitHub #31 fn relative_include() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let fmt_arg = get_plain_format_arg(); let tempdir = setup_and_copy_files(&[ "subdirectory/relative_include.tex", @@ -235,10 +251,6 @@ fn relative_include() { #[test] fn stdin_content() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - // No input files here, but output files are created. let fmt_arg = get_plain_format_arg(); let tempdir = setup_and_copy_files(&[]); @@ -253,10 +265,6 @@ fn stdin_content() { // Regression #36 #[test] fn test_space() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let fmt_arg = get_plain_format_arg(); let tempdir = setup_and_copy_files(&["test space.tex"]); @@ -266,10 +274,6 @@ fn test_space() { #[test] fn test_outdir() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - let fmt_arg = get_plain_format_arg(); let tempdir = setup_and_copy_files(&["subdirectory/content/1.tex"]); @@ -290,10 +294,6 @@ fn test_outdir() { // panic unwinding broken: https://github.com/rust-embedded/cross/issues/343 #[cfg(not(all(target_arch = "arm", target_env = "musl")))] fn test_bad_outdir() { - if env::var("RUNNING_COVERAGE").is_ok() { - panic!() - } - let fmt_arg = get_plain_format_arg(); let tempdir = setup_and_copy_files(&["subdirectory/content/1.tex"]); @@ -313,10 +313,6 @@ fn test_bad_outdir() { // panic unwinding broken: https://github.com/rust-embedded/cross/issues/343 #[cfg(not(all(target_arch = "arm", target_env = "musl")))] fn test_outdir_is_file() { - if env::var("RUNNING_COVERAGE").is_ok() { - panic!() - } - let fmt_arg = get_plain_format_arg(); let tempdir = setup_and_copy_files(&["test space.tex", "subdirectory/content/1.tex"]); @@ -333,10 +329,6 @@ fn test_outdir_is_file() { #[test] fn test_keep_logs_on_error() { - if env::var("RUNNING_COVERAGE").is_ok() { - return; - } - // No input files here, but output files are created. let fmt_arg = get_plain_format_arg(); let tempdir = setup_and_copy_files(&[]); @@ -355,3 +347,29 @@ fn test_keep_logs_on_error() { assert!(log.contains(r"job aborted, no legal \end found")); } + +#[test] +fn test_no_color() { + // No input files here, but output files are created. + let fmt_arg = get_plain_format_arg(); + + let tempdir = setup_and_copy_files(&[]); + let output_nocolor = run_tectonic_with_stdin( + tempdir.path(), + &[&fmt_arg, "-", "--color=never"], + "no end to this file", + ); + + // Output is not a terminal, so these two should be the same + let tempdir = setup_and_copy_files(&[]); + let output_autocolor = run_tectonic_with_stdin( + tempdir.path(), + &[&fmt_arg, "-", "--color=auto"], + "no end to this file", + ); + + assert_eq!(output_nocolor, output_autocolor); + + error_or_panic(output_nocolor); + error_or_panic(output_autocolor); +}