Expand on xtask (#2176)
* Fix Windows OSError * Ignore .pyd files * Put things in modules * Rename functions to `run` * Expand on cargo xtask * Try to work around missing installs * Run all things by default, but not llvm-cov * Test msrv * Fix more OSErrors * Various refinements and docs * Various refinements * Various refinements
This commit is contained in:
parent
3eb654c81c
commit
16ad15e04f
|
@ -1,11 +1,5 @@
|
||||||
[alias]
|
[alias]
|
||||||
xtask = "run --package xtask --"
|
xtask = "run --package xtask --"
|
||||||
pyo3_doc = "doc --lib --no-default-features --features=full --no-deps --workspace --open --exclude pyo3-macros --exclude pyo3-macros-backend"
|
|
||||||
pyo3_doc_scrape = "doc --lib --no-default-features --features=full --no-deps --workspace --open --exclude pyo3-macros --exclude pyo3-macros-backend -Z unstable-options -Z rustdoc-scrape-examples=examples"
|
|
||||||
pyo3_doc_internal = "doc --lib --no-default-features --features=full --no-deps --workspace --open --document-private-items -Z unstable-options -Z rustdoc-scrape-examples=examples"
|
|
||||||
|
|
||||||
[build]
|
|
||||||
rustdocflags = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[target.'cfg(feature = "cargo-clippy")']
|
[target.'cfg(feature = "cargo-clippy")']
|
||||||
rustflags = [
|
rustflags = [
|
||||||
|
|
|
@ -5,9 +5,6 @@ Please consider adding the following to your pull request:
|
||||||
- docs to all new functions and / or detail in the guide
|
- docs to all new functions and / or detail in the guide
|
||||||
- tests for all new or changed functions
|
- tests for all new or changed functions
|
||||||
|
|
||||||
Be aware the CI pipeline will check your pull request for the following. This is done using `nox` (you can install with `pip install nox`):
|
PyO3's CI pipeline will check your pull request. To run its tests
|
||||||
- Rust tests (`cargo test` or `nox -s test-rust`)
|
locally, you can run ```cargo xtask ci```. See its documentation
|
||||||
- Examples (`nox -s test-py`)
|
[here](https://github.com/PyO3/pyo3/tree/main/xtask#readme).
|
||||||
- Rust lints (`nox -s clippy`)
|
|
||||||
- Rust formatting (`nox -s fmt-rust`)
|
|
||||||
- Python formatting (`nox -s fmt-py`)
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ jobs:
|
||||||
mkdir target
|
mkdir target
|
||||||
mkdir -p gh-pages-build/internal
|
mkdir -p gh-pages-build/internal
|
||||||
echo "<div class='internal-banner' style='position:fixed; z-index: 99999; color:red;border:3px solid red;margin-left: auto; margin-right: auto; width: 430px;left:0;right: 0;'><div style='display: flex; align-items: center; justify-content: center;'> ⚠️ Internal Docs ⚠️ Not Public API 👉 <a href='https://pyo3.rs/main/doc/pyo3/index.html' style='color:red;text-decoration:underline;'>Official Docs Here</a></div></div>" > target/banner.html
|
echo "<div class='internal-banner' style='position:fixed; z-index: 99999; color:red;border:3px solid red;margin-left: auto; margin-right: auto; width: 430px;left:0;right: 0;'><div style='display: flex; align-items: center; justify-content: center;'> ⚠️ Internal Docs ⚠️ Not Public API 👉 <a href='https://pyo3.rs/main/doc/pyo3/index.html' style='color:red;text-decoration:underline;'>Official Docs Here</a></div></div>" > target/banner.html
|
||||||
cargo +nightly pyo3_doc_internal
|
cargo xtask doc --internal
|
||||||
cp -r target/doc gh-pages-build/internal
|
cp -r target/doc gh-pages-build/internal
|
||||||
env:
|
env:
|
||||||
RUSTDOCFLAGS: "--cfg docsrs --Z unstable-options --document-hidden-items --html-before-content target/banner.html"
|
RUSTDOCFLAGS: "--cfg docsrs --Z unstable-options --document-hidden-items --html-before-content target/banner.html"
|
||||||
|
@ -71,7 +71,7 @@ jobs:
|
||||||
# This adds the docs to gh-pages-build/doc
|
# This adds the docs to gh-pages-build/doc
|
||||||
- name: Build the doc
|
- name: Build the doc
|
||||||
run: |
|
run: |
|
||||||
cargo +nightly pyo3_doc_scrape
|
cargo xtask doc
|
||||||
cp -r target/doc gh-pages-build/doc
|
cp -r target/doc gh-pages-build/doc
|
||||||
echo "<meta http-equiv=refresh content=0;url=pyo3/index.html>" > gh-pages-build/doc/index.html
|
echo "<meta http-equiv=refresh content=0;url=pyo3/index.html>" > gh-pages-build/doc/index.html
|
||||||
|
|
||||||
|
|
|
@ -20,3 +20,4 @@ guide/book/
|
||||||
extensions/stamps/
|
extensions/stamps/
|
||||||
pip-wheel-metadata
|
pip-wheel-metadata
|
||||||
valgrind-python.supp
|
valgrind-python.supp
|
||||||
|
*.pyd
|
||||||
|
|
|
@ -48,7 +48,7 @@ There are some specific areas of focus where help is currently needed for the do
|
||||||
- Not all APIs had docs or examples when they were made. The goal is to have documentation on all PyO3 APIs ([#306](https://github.com/PyO3/pyo3/issues/306)). If you see an API lacking a doc, please write one and open a PR!
|
- Not all APIs had docs or examples when they were made. The goal is to have documentation on all PyO3 APIs ([#306](https://github.com/PyO3/pyo3/issues/306)). If you see an API lacking a doc, please write one and open a PR!
|
||||||
|
|
||||||
You can build the docs (including all features) with
|
You can build the docs (including all features) with
|
||||||
```cargo +nightly pyo3_doc_scrape```
|
```cargo xtask doc --open```
|
||||||
|
|
||||||
#### Doctests
|
#### Doctests
|
||||||
|
|
||||||
|
@ -87,6 +87,10 @@ Tests run with all supported Python versions with the latest stable Rust compile
|
||||||
|
|
||||||
If you are adding a new feature, you should add it to the `full` feature in our *Cargo.toml** so that it is tested in CI.
|
If you are adding a new feature, you should add it to the `full` feature in our *Cargo.toml** so that it is tested in CI.
|
||||||
|
|
||||||
|
You can run these tests yourself with
|
||||||
|
```cargo xtask ci```
|
||||||
|
See [it's documentation](https://github.com/PyO3/pyo3/tree/main/xtask#readme)for more commands you can run.
|
||||||
|
|
||||||
## Python and Rust version support policy
|
## Python and Rust version support policy
|
||||||
|
|
||||||
PyO3 aims to keep sufficient compatibility to make packaging Python extensions built with PyO3 feasible on most common package managers.
|
PyO3 aims to keep sufficient compatibility to make packaging Python extensions built with PyO3 feasible on most common package managers.
|
||||||
|
|
|
@ -54,8 +54,9 @@ elif _pointer_size == 4:
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("unexpected pointer size: " + repr(_pointer_size))
|
raise RuntimeError("unexpected pointer size: " + repr(_pointer_size))
|
||||||
IS_WINDOWS = sys.platform == "win32"
|
IS_WINDOWS = sys.platform == "win32"
|
||||||
|
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
MIN_DATETIME = pdt.datetime(1970, 1, 2, 0, 0)
|
MIN_DATETIME = pdt.datetime(1971, 1, 2, 0, 0)
|
||||||
if IS_32_BIT:
|
if IS_32_BIT:
|
||||||
MAX_DATETIME = pdt.datetime(3001, 1, 19, 4, 59, 59)
|
MAX_DATETIME = pdt.datetime(3001, 1, 19, 4, 59, 59)
|
||||||
else:
|
else:
|
||||||
|
@ -227,7 +228,7 @@ def test_datetime_typeerror():
|
||||||
|
|
||||||
|
|
||||||
@given(dt=st.datetimes(MIN_DATETIME, MAX_DATETIME))
|
@given(dt=st.datetimes(MIN_DATETIME, MAX_DATETIME))
|
||||||
@example(dt=pdt.datetime(1970, 1, 2, 0, 0))
|
@example(dt=pdt.datetime(1971, 1, 2, 0, 0))
|
||||||
def test_datetime_from_timestamp(dt):
|
def test_datetime_from_timestamp(dt):
|
||||||
if PYPY and dt < pdt.datetime(1900, 1, 1):
|
if PYPY and dt < pdt.datetime(1900, 1, 1):
|
||||||
pytest.xfail("pdt.datetime.timestamp will raise on PyPy with dates before 1900")
|
pytest.xfail("pdt.datetime.timestamp will raise on PyPy with dates before 1900")
|
||||||
|
|
|
@ -3,9 +3,13 @@ name = "xtask"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "xtask"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.51"
|
anyhow = "1.0.51"
|
||||||
|
|
||||||
# Clap 3 requires MSRV 1.54
|
# Clap 3 requires MSRV 1.54
|
||||||
rustversion = "1.0"
|
rustversion = "1.0"
|
||||||
structopt = { version = "0.3", default-features = false }
|
structopt = { version = "0.3", default-features = false }
|
||||||
|
clap = { version = "2" }
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
## Commands to test PyO3.
|
||||||
|
|
||||||
|
To run these commands, you should be in PyO3's root directory, and run (for example) `cargo xtask ci`.
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE:
|
||||||
|
xtask.exe <SUBCOMMAND>
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Prints help information
|
||||||
|
-V, --version Prints version information
|
||||||
|
|
||||||
|
SUBCOMMANDS:
|
||||||
|
ci Runs everything
|
||||||
|
clippy Runs `clippy`, denying all warnings
|
||||||
|
coverage Runs `cargo llvm-cov` for the PyO3 codebase
|
||||||
|
default Only runs the fast things (this is used if no command is specified)
|
||||||
|
doc Attempts to render the documentation
|
||||||
|
fmt Checks Rust and Python code formatting with `rustfmt` and `black`
|
||||||
|
help Prints this message or the help of the given subcommand(s)
|
||||||
|
test Runs various variations on `cargo test`
|
||||||
|
test-py Runs the tests in examples/ and pytests/
|
||||||
|
```
|
|
@ -0,0 +1,201 @@
|
||||||
|
use crate::utils::*;
|
||||||
|
use anyhow::{ensure, Result};
|
||||||
|
use std::io;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::time::Instant;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
pub const MSRV: &str = "1.48";
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub enum Subcommand {
|
||||||
|
/// Only runs the fast things (this is used if no command is specified)
|
||||||
|
Default,
|
||||||
|
/// Runs everything
|
||||||
|
Ci,
|
||||||
|
/// Checks Rust and Python code formatting with `rustfmt` and `black`
|
||||||
|
Fmt,
|
||||||
|
/// Runs `clippy`, denying all warnings.
|
||||||
|
Clippy,
|
||||||
|
/// Runs `cargo llvm-cov` for the PyO3 codebase.
|
||||||
|
Coverage(CoverageOpts),
|
||||||
|
/// Attempts to render the documentation.
|
||||||
|
Doc(DocOpts),
|
||||||
|
/// Runs various variations on `cargo test`
|
||||||
|
Test,
|
||||||
|
/// Runs the tests in examples/ and pytests/
|
||||||
|
TestPy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Subcommand {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt, Default)]
|
||||||
|
pub struct CoverageOpts {
|
||||||
|
/// Creates an lcov output instead of printing to the terminal.
|
||||||
|
#[structopt(long)]
|
||||||
|
pub output_lcov: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub struct DocOpts {
|
||||||
|
/// Whether to run the docs using nightly rustdoc
|
||||||
|
#[structopt(long)]
|
||||||
|
pub stable: bool,
|
||||||
|
/// Whether to open the docs after rendering.
|
||||||
|
#[structopt(long)]
|
||||||
|
pub open: bool,
|
||||||
|
/// Whether to show the private and hidden API.
|
||||||
|
#[structopt(long)]
|
||||||
|
pub internal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DocOpts {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
stable: true,
|
||||||
|
open: false,
|
||||||
|
internal: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Subcommand {
|
||||||
|
pub fn execute(self) -> Result<()> {
|
||||||
|
print_metadata()?;
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Subcommand::Default => {
|
||||||
|
crate::fmt::rust::run()?;
|
||||||
|
crate::clippy::run()?;
|
||||||
|
crate::test::run()?;
|
||||||
|
crate::doc::run(DocOpts::default())?;
|
||||||
|
}
|
||||||
|
Subcommand::Ci => {
|
||||||
|
let installed = Installed::new()?;
|
||||||
|
crate::fmt::rust::run()?;
|
||||||
|
if installed.black {
|
||||||
|
crate::fmt::python::run()?;
|
||||||
|
} else {
|
||||||
|
Installed::warn_black()
|
||||||
|
};
|
||||||
|
crate::clippy::run()?;
|
||||||
|
crate::test::run()?;
|
||||||
|
crate::doc::run(DocOpts::default())?;
|
||||||
|
if installed.nox {
|
||||||
|
crate::pytests::run(None)?;
|
||||||
|
} else {
|
||||||
|
Installed::warn_nox()
|
||||||
|
};
|
||||||
|
crate::llvm_cov::run(CoverageOpts::default())?;
|
||||||
|
installed.assert()?
|
||||||
|
}
|
||||||
|
|
||||||
|
Subcommand::Doc(opts) => crate::doc::run(opts)?,
|
||||||
|
Subcommand::Fmt => {
|
||||||
|
crate::fmt::rust::run()?;
|
||||||
|
crate::fmt::python::run()?;
|
||||||
|
}
|
||||||
|
Subcommand::Clippy => crate::clippy::run()?,
|
||||||
|
Subcommand::Coverage(opts) => crate::llvm_cov::run(opts)?,
|
||||||
|
Subcommand::TestPy => crate::pytests::run(None)?,
|
||||||
|
Subcommand::Test => crate::test::run()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dt = start.elapsed().as_secs();
|
||||||
|
let minutes = dt / 60;
|
||||||
|
let seconds = dt % 60;
|
||||||
|
println!("\nxtask finished in {}m {}s.", minutes, seconds);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(command: &mut Command) -> Result<()> {
|
||||||
|
println!("Running: {}", format_command(command));
|
||||||
|
|
||||||
|
let output = command
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?
|
||||||
|
.wait_with_output()?;
|
||||||
|
|
||||||
|
ensure! {
|
||||||
|
output.status.success(),
|
||||||
|
"process did not run successfully ({exit}): {command}/n {out} {err}",
|
||||||
|
exit = match output.status.code() {
|
||||||
|
Some(code) => format!("exit code {}", code),
|
||||||
|
None => "terminated by signal".into(),
|
||||||
|
},
|
||||||
|
command = format_command(command),
|
||||||
|
out = String::from_utf8_lossy(&output.stdout),
|
||||||
|
err = String::from_utf8_lossy(&output.stderr)
|
||||||
|
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Installed {
|
||||||
|
pub nox: bool,
|
||||||
|
pub black: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Installed {
|
||||||
|
pub fn new() -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
nox: Self::nox()?,
|
||||||
|
black: Self::black()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nox() -> anyhow::Result<bool> {
|
||||||
|
let output = std::process::Command::new("nox").arg("--version").output();
|
||||||
|
match output {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
|
||||||
|
Err(other) => Err(other.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warn_nox() {
|
||||||
|
eprintln!("Skipping: formatting Python code, because `nox` was not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn black() -> anyhow::Result<bool> {
|
||||||
|
let output = std::process::Command::new("black")
|
||||||
|
.arg("--version")
|
||||||
|
.output();
|
||||||
|
match output {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
|
||||||
|
Err(other) => Err(other.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warn_black() {
|
||||||
|
eprintln!("Skipping: Python code formatting, because `black` was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert(&self) -> anyhow::Result<()> {
|
||||||
|
if self.nox && self.black {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let mut err =
|
||||||
|
String::from("\n\nxtask was unable to run all tests due to some missing programs:");
|
||||||
|
if !self.black {
|
||||||
|
err.push_str("\n`black` was not installed. (`pip install black`)");
|
||||||
|
}
|
||||||
|
if !self.nox {
|
||||||
|
err.push_str("\n`nox` was not installed. (`pip install nox`)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow::anyhow!(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::cli;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub fn run() -> anyhow::Result<()> {
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("clippy")
|
||||||
|
.arg("--features=full")
|
||||||
|
.arg("--all-targets")
|
||||||
|
.arg("--workspace")
|
||||||
|
.arg("--")
|
||||||
|
.arg("-Dwarnings"),
|
||||||
|
)?;
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("clippy")
|
||||||
|
.arg("--all-targets")
|
||||||
|
.arg("--workspace")
|
||||||
|
.arg("--features=abi3,full")
|
||||||
|
.arg("--")
|
||||||
|
.arg("-Dwarnings"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
use crate::cli;
|
||||||
|
use crate::cli::DocOpts;
|
||||||
|
use std::process::Command;
|
||||||
|
//--cfg docsrs --Z unstable-options --document-hidden-items
|
||||||
|
|
||||||
|
pub fn run(opts: DocOpts) -> anyhow::Result<()> {
|
||||||
|
let mut flags = Vec::new();
|
||||||
|
|
||||||
|
if !opts.stable {
|
||||||
|
flags.push("--cfg docsrs");
|
||||||
|
}
|
||||||
|
if opts.internal {
|
||||||
|
flags.push("--Z unstable-options");
|
||||||
|
flags.push("--document-hidden-items");
|
||||||
|
}
|
||||||
|
flags.push("-Dwarnings");
|
||||||
|
|
||||||
|
std::env::set_var("RUSTDOCFLAGS", flags.join(" "));
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(if opts.stable { None } else { Some("+nightly") })
|
||||||
|
.arg("doc")
|
||||||
|
.arg("--lib")
|
||||||
|
.arg("--no-default-features")
|
||||||
|
.arg("--features=full")
|
||||||
|
.arg("--no-deps")
|
||||||
|
.arg("--workspace")
|
||||||
|
.args(if opts.internal {
|
||||||
|
&["--document-private-items"][..]
|
||||||
|
} else {
|
||||||
|
&["--exclude=pyo3-macros", "--exclude=pyo3-macros-backend"][..]
|
||||||
|
})
|
||||||
|
.args(if opts.stable {
|
||||||
|
&[][..]
|
||||||
|
} else {
|
||||||
|
&[
|
||||||
|
"-Z",
|
||||||
|
"unstable-options",
|
||||||
|
"-Z",
|
||||||
|
"rustdoc-scrape-examples=examples",
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.args(if opts.open { Some("--open") } else { None }),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
pub mod rust {
|
||||||
|
use crate::cli;
|
||||||
|
use std::process::Command;
|
||||||
|
pub fn run() -> anyhow::Result<()> {
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("fmt")
|
||||||
|
.arg("--all")
|
||||||
|
.arg("--")
|
||||||
|
.arg("--check"),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod python {
|
||||||
|
use crate::cli;
|
||||||
|
use std::process::Command;
|
||||||
|
pub fn run() -> anyhow::Result<()> {
|
||||||
|
cli::run(Command::new("black").arg(".").arg("--check"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
use crate::cli;
|
||||||
|
use crate::cli::CoverageOpts;
|
||||||
|
use crate::utils::*;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::{collections::HashMap, process::Command};
|
||||||
|
|
||||||
|
/// Runs `cargo llvm-cov` for the PyO3 codebase.
|
||||||
|
pub fn run(opts: CoverageOpts) -> Result<()> {
|
||||||
|
let env = get_coverage_env()?;
|
||||||
|
|
||||||
|
cli::run(llvm_cov_command(&["clean", "--workspace"]).envs(&env))?;
|
||||||
|
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(&["test", "--manifest-path", "pyo3-build-config/Cargo.toml"])
|
||||||
|
.envs(&env),
|
||||||
|
)?;
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(&["test", "--manifest-path", "pyo3-macros-backend/Cargo.toml"])
|
||||||
|
.envs(&env),
|
||||||
|
)?;
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(&["test", "--manifest-path", "pyo3-macros/Cargo.toml"])
|
||||||
|
.envs(&env),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cli::run(Command::new("cargo").arg("test").envs(&env))?;
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(&["test", "--features", "abi3"])
|
||||||
|
.envs(&env),
|
||||||
|
)?;
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(&["test", "--features", "full"])
|
||||||
|
.envs(&env),
|
||||||
|
)?;
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(&["test", "--features", "abi3 full"])
|
||||||
|
.envs(&env),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
crate::pytests::run(&env)?;
|
||||||
|
|
||||||
|
match opts.output_lcov {
|
||||||
|
Some(path) => {
|
||||||
|
cli::run(llvm_cov_command(&["--no-run", "--lcov", "--output-path", &path]).envs(&env))?
|
||||||
|
}
|
||||||
|
None => cli::run(llvm_cov_command(&["--no-run", "--summary-only"]).envs(&env))?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn llvm_cov_command(args: &[&str]) -> Command {
|
||||||
|
let mut command = Command::new("cargo");
|
||||||
|
command
|
||||||
|
.args(&[
|
||||||
|
"llvm-cov",
|
||||||
|
"--package=pyo3",
|
||||||
|
"--package=pyo3-build-config",
|
||||||
|
"--package=pyo3-macros-backend",
|
||||||
|
"--package=pyo3-macros",
|
||||||
|
"--package=pyo3-ffi",
|
||||||
|
])
|
||||||
|
.args(args);
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_coverage_env() -> Result<HashMap<String, String>> {
|
||||||
|
let mut env = HashMap::new();
|
||||||
|
|
||||||
|
let output = String::from_utf8(llvm_cov_command(&["show-env"]).output()?.stdout)?;
|
||||||
|
|
||||||
|
for line in output.trim().split('\n') {
|
||||||
|
let (key, value) = split_once(line, '=')
|
||||||
|
.context("expected '=' in each line of output from llvm-cov show-env")?;
|
||||||
|
env.insert(key.to_owned(), value.trim_matches('"').to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that examples/ and pytests/ all build to the correct target directory to collect
|
||||||
|
// coverage artifacts.
|
||||||
|
env.insert(
|
||||||
|
"CARGO_TARGET_DIR".to_owned(),
|
||||||
|
env.get("CARGO_LLVM_COV_TARGET_DIR").unwrap().to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Coverage only works on nightly.
|
||||||
|
let rustc_version =
|
||||||
|
String::from_utf8(get_output(Command::new("rustc").arg("--version"))?.stdout)
|
||||||
|
.context("failed to parse rust version as utf8")?;
|
||||||
|
if !rustc_version.contains("nightly") {
|
||||||
|
env.insert("RUSTUP_TOOLCHAIN".to_owned(), "nightly".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(env)
|
||||||
|
}
|
|
@ -1,196 +1,24 @@
|
||||||
use anyhow::{ensure, Context, Result};
|
use clap::ErrorKind::MissingArgumentOrSubcommand;
|
||||||
use std::{collections::HashMap, path::Path, process::Command};
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
pub mod cli;
|
||||||
enum Subcommand {
|
pub mod clippy;
|
||||||
/// Runs `cargo llvm-cov` for the PyO3 codebase.
|
pub mod doc;
|
||||||
Coverage(CoverageOpts),
|
pub mod fmt;
|
||||||
/// Runs tests in examples/ and pytests/
|
pub mod llvm_cov;
|
||||||
TestPy,
|
pub mod pytests;
|
||||||
}
|
pub mod test;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
fn main() -> anyhow::Result<()> {
|
||||||
struct CoverageOpts {
|
// Avoid spewing backtraces all over the command line
|
||||||
/// Creates an lcov output instead of printing to the terminal.
|
// For some reason this is automatically enabled on nightly compilers...
|
||||||
#[structopt(long)]
|
std::env::set_var("RUST_LIB_BACKTRACE", "0");
|
||||||
output_lcov: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Subcommand {
|
match cli::Subcommand::from_args_safe() {
|
||||||
fn execute(self) -> Result<()> {
|
Ok(c) => c.execute()?,
|
||||||
match self {
|
Err(e) if e.kind == MissingArgumentOrSubcommand => cli::Subcommand::default().execute()?,
|
||||||
Subcommand::Coverage(opts) => subcommand_coverage(opts),
|
Err(e) => return Err(e.into()),
|
||||||
Subcommand::TestPy => run_python_tests(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
Subcommand::from_args().execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs `cargo llvm-cov` for the PyO3 codebase.
|
|
||||||
fn subcommand_coverage(opts: CoverageOpts) -> Result<()> {
|
|
||||||
let env = get_coverage_env()?;
|
|
||||||
|
|
||||||
run(llvm_cov_command(&["clean", "--workspace"]).envs(&env))?;
|
|
||||||
|
|
||||||
run(Command::new("cargo")
|
|
||||||
.args(&["test", "--manifest-path", "pyo3-build-config/Cargo.toml"])
|
|
||||||
.envs(&env))?;
|
|
||||||
run(Command::new("cargo")
|
|
||||||
.args(&["test", "--manifest-path", "pyo3-macros-backend/Cargo.toml"])
|
|
||||||
.envs(&env))?;
|
|
||||||
run(Command::new("cargo")
|
|
||||||
.args(&["test", "--manifest-path", "pyo3-macros/Cargo.toml"])
|
|
||||||
.envs(&env))?;
|
|
||||||
|
|
||||||
run(Command::new("cargo").arg("test").envs(&env))?;
|
|
||||||
run(Command::new("cargo")
|
|
||||||
.args(&["test", "--features", "abi3"])
|
|
||||||
.envs(&env))?;
|
|
||||||
run(Command::new("cargo")
|
|
||||||
.args(&["test", "--features", "full"])
|
|
||||||
.envs(&env))?;
|
|
||||||
run(Command::new("cargo")
|
|
||||||
.args(&["test", "--features", "abi3 full"])
|
|
||||||
.envs(&env))?;
|
|
||||||
|
|
||||||
run_python_tests(&env)?;
|
|
||||||
|
|
||||||
match opts.output_lcov {
|
|
||||||
Some(path) => {
|
|
||||||
run(llvm_cov_command(&["--no-run", "--lcov", "--output-path", &path]).envs(&env))?
|
|
||||||
}
|
|
||||||
None => run(llvm_cov_command(&["--no-run", "--summary-only"]).envs(&env))?,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(command: &mut Command) -> Result<()> {
|
|
||||||
println!("running: {}", format_command(command));
|
|
||||||
let status = command.spawn()?.wait()?;
|
|
||||||
ensure! {
|
|
||||||
status.success(),
|
|
||||||
"process did not run successfully ({exit}): {command}",
|
|
||||||
exit = match status.code() {
|
|
||||||
Some(code) => format!("exit code {}", code),
|
|
||||||
None => "terminated by signal".into(),
|
|
||||||
},
|
|
||||||
command = format_command(command),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_output(command: &mut Command) -> Result<std::process::Output> {
|
|
||||||
let output = command.output()?;
|
|
||||||
ensure! {
|
|
||||||
output.status.success(),
|
|
||||||
"process did not run successfully ({exit}): {command}",
|
|
||||||
exit = match output.status.code() {
|
|
||||||
Some(code) => format!("exit code {}", code),
|
|
||||||
None => "terminated by signal".into(),
|
|
||||||
},
|
|
||||||
command = format_command(command),
|
|
||||||
};
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn llvm_cov_command(args: &[&str]) -> Command {
|
|
||||||
let mut command = Command::new("cargo");
|
|
||||||
command
|
|
||||||
.args(&[
|
|
||||||
"llvm-cov",
|
|
||||||
"--package=pyo3",
|
|
||||||
"--package=pyo3-build-config",
|
|
||||||
"--package=pyo3-macros-backend",
|
|
||||||
"--package=pyo3-macros",
|
|
||||||
"--package=pyo3-ffi",
|
|
||||||
])
|
|
||||||
.args(args);
|
|
||||||
command
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_python_tests<'a>(
|
|
||||||
env: impl IntoIterator<Item = (&'a String, &'a String)> + Copy,
|
|
||||||
) -> Result<()> {
|
|
||||||
run(Command::new("nox")
|
|
||||||
.arg("--non-interactive")
|
|
||||||
.arg("-f")
|
|
||||||
.arg(Path::new("pytests").join("noxfile.py"))
|
|
||||||
.envs(env))?;
|
|
||||||
|
|
||||||
for entry in std::fs::read_dir("examples")? {
|
|
||||||
let path = entry?.path();
|
|
||||||
if path.is_dir() && path.join("noxfile.py").exists() {
|
|
||||||
run(Command::new("nox")
|
|
||||||
.arg("--non-interactive")
|
|
||||||
.arg("-f")
|
|
||||||
.arg(path.join("noxfile.py"))
|
|
||||||
.envs(env))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_coverage_env() -> Result<HashMap<String, String>> {
|
|
||||||
let mut env = HashMap::new();
|
|
||||||
|
|
||||||
let output = String::from_utf8(llvm_cov_command(&["show-env"]).output()?.stdout)?;
|
|
||||||
|
|
||||||
for line in output.trim().split('\n') {
|
|
||||||
let (key, value) = split_once(line, '=')
|
|
||||||
.context("expected '=' in each line of output from llvm-cov show-env")?;
|
|
||||||
env.insert(key.to_owned(), value.trim_matches('"').to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that examples/ and pytests/ all build to the correct target directory to collect
|
|
||||||
// coverage artifacts.
|
|
||||||
env.insert(
|
|
||||||
"CARGO_TARGET_DIR".to_owned(),
|
|
||||||
env.get("CARGO_LLVM_COV_TARGET_DIR").unwrap().to_owned(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Coverage only works on nightly.
|
|
||||||
let rustc_version =
|
|
||||||
String::from_utf8(get_output(Command::new("rustc").arg("--version"))?.stdout)
|
|
||||||
.context("failed to parse rust version as utf8")?;
|
|
||||||
if !rustc_version.contains("nightly") {
|
|
||||||
env.insert("RUSTUP_TOOLCHAIN".to_owned(), "nightly".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(env)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replacement for str.split_once() on Rust older than 1.52
|
|
||||||
#[rustversion::before(1.52)]
|
|
||||||
fn split_once(s: &str, pat: char) -> Option<(&str, &str)> {
|
|
||||||
let mut iter = s.splitn(2, pat);
|
|
||||||
Some((iter.next()?, iter.next()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustversion::since(1.52)]
|
|
||||||
fn split_once(s: &str, pat: char) -> Option<(&str, &str)> {
|
|
||||||
s.split_once(pat)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustversion::since(1.57)]
|
|
||||||
fn format_command(command: &Command) -> String {
|
|
||||||
let mut buf = String::new();
|
|
||||||
buf.push('`');
|
|
||||||
buf.push_str(&command.get_program().to_string_lossy());
|
|
||||||
for arg in command.get_args() {
|
|
||||||
buf.push(' ');
|
|
||||||
buf.push_str(&arg.to_string_lossy());
|
|
||||||
}
|
|
||||||
buf.push('`');
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustversion::before(1.57)]
|
|
||||||
fn format_command(command: &Command) -> String {
|
|
||||||
// Debug impl isn't as nice as the above, but will do on < 1.57
|
|
||||||
format!("{:?}", command)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::cli;
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::{path::Path, process::Command};
|
||||||
|
|
||||||
|
pub fn run<'a>(env: impl IntoIterator<Item = (&'a String, &'a String)> + Copy) -> Result<()> {
|
||||||
|
cli::run(
|
||||||
|
Command::new("nox")
|
||||||
|
.arg("--non-interactive")
|
||||||
|
.arg("-f")
|
||||||
|
.arg(Path::new("pytests").join("noxfile.py"))
|
||||||
|
.envs(env),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for entry in std::fs::read_dir("examples")? {
|
||||||
|
let path = entry?.path();
|
||||||
|
if path.is_dir() && path.join("noxfile.py").exists() {
|
||||||
|
cli::run(
|
||||||
|
Command::new("nox")
|
||||||
|
.arg("--non-interactive")
|
||||||
|
.arg("-f")
|
||||||
|
.arg(path.join("noxfile.py"))
|
||||||
|
.envs(env),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
use crate::cli::{self, MSRV};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub fn run() -> anyhow::Result<()> {
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("test")
|
||||||
|
.arg("--lib")
|
||||||
|
.arg("--no-default-features")
|
||||||
|
.arg("--tests")
|
||||||
|
.arg("--quiet"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("test")
|
||||||
|
.arg("--no-default-features")
|
||||||
|
.arg("--features=full")
|
||||||
|
.arg("--quiet"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("test")
|
||||||
|
.arg("--no-default-features")
|
||||||
|
.arg("--features=abi3,full")
|
||||||
|
.arg("--quiet"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// If the MSRV toolchain is not installed, this will install it
|
||||||
|
cli::run(
|
||||||
|
Command::new("rustup")
|
||||||
|
.arg("toolchain")
|
||||||
|
.arg("install")
|
||||||
|
.arg(MSRV),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Test MSRV
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg(format!("+{}", MSRV))
|
||||||
|
.arg("test")
|
||||||
|
.arg("--no-default-features")
|
||||||
|
.arg("--features=full,auto-initialize")
|
||||||
|
.arg("--quiet"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("+nightly")
|
||||||
|
.arg("test")
|
||||||
|
.arg("--no-default-features")
|
||||||
|
.arg("--features=full,nightly")
|
||||||
|
.arg("--quiet"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("test")
|
||||||
|
.arg("--manifest-path=pyo3-ffi/Cargo.toml")
|
||||||
|
.arg("--quiet"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
cli::run(
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("test")
|
||||||
|
.arg("--no-default-features")
|
||||||
|
.arg("--manifest-path=pyo3-build-config/Cargo.toml")
|
||||||
|
.arg("--quiet"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
use anyhow::ensure;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
// Replacement for str.split_once() on Rust older than 1.52
|
||||||
|
#[rustversion::before(1.52)]
|
||||||
|
pub fn split_once(s: &str, pat: char) -> Option<(&str, &str)> {
|
||||||
|
let mut iter = s.splitn(2, pat);
|
||||||
|
Some((iter.next()?, iter.next()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::since(1.52)]
|
||||||
|
pub fn split_once(s: &str, pat: char) -> Option<(&str, &str)> {
|
||||||
|
s.split_once(pat)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::since(1.57)]
|
||||||
|
pub fn format_command(command: &Command) -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
buf.push('`');
|
||||||
|
buf.push_str(&command.get_program().to_string_lossy());
|
||||||
|
for arg in command.get_args() {
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push_str(&arg.to_string_lossy());
|
||||||
|
}
|
||||||
|
buf.push('`');
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustversion::before(1.57)]
|
||||||
|
pub fn format_command(command: &Command) -> String {
|
||||||
|
// Debug impl isn't as nice as the above, but will do on < 1.57
|
||||||
|
format!("{:?}", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_output(command: &mut Command) -> anyhow::Result<std::process::Output> {
|
||||||
|
let output = command.output()?;
|
||||||
|
ensure! {
|
||||||
|
output.status.success(),
|
||||||
|
"process did not run successfully ({exit}): {command}",
|
||||||
|
exit = match output.status.code() {
|
||||||
|
Some(code) => format!("exit code {}", code),
|
||||||
|
None => "terminated by signal".into(),
|
||||||
|
},
|
||||||
|
command = format_command(command),
|
||||||
|
};
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_metadata() -> anyhow::Result<()> {
|
||||||
|
let rustc_output = std::process::Command::new("rustc")
|
||||||
|
.arg("--version")
|
||||||
|
.arg("--verbose")
|
||||||
|
.output()?;
|
||||||
|
let rustc_version = core::str::from_utf8(&rustc_output.stdout).unwrap();
|
||||||
|
println!("Metadata: \n\n{}", rustc_version);
|
||||||
|
|
||||||
|
let py_output = std::process::Command::new("python")
|
||||||
|
.arg("--version")
|
||||||
|
.arg("-V")
|
||||||
|
.output()?;
|
||||||
|
let py_version = core::str::from_utf8(&py_output.stdout).unwrap();
|
||||||
|
println!("{}", py_version);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue