use cargo_metadata::MetadataCommand;
use failure::{bail, format_err, Error, ResultExt};
#[cfg(feature = "human-panic")]
use human_panic::setup_panic;
#[cfg(feature = "password-storage")]
use keyring::{Keyring, KeyringError};
use maturin::{
develop, get_pyproject_toml, source_distribution, write_dist_info, BridgeModel, BuildOptions,
CargoToml, Metadata21, PathWriter, PythonInterpreter, Target,
};
#[cfg(feature = "log")]
use pretty_env_logger;
use std::path::PathBuf;
use std::{env, fs};
use structopt::StructOpt;
#[cfg(feature = "upload")]
use {
maturin::{upload, Registry, UploadError},
reqwest::Url,
rpassword,
std::io,
};
#[cfg(feature = "upload")]
fn get_password(_username: &str) -> (String, bool) {
if let Ok(password) = env::var("MATURIN_PASSWORD") {
return (password, false);
};
#[cfg(feature = "keyring")]
{
let service = env!("CARGO_PKG_NAME");
let keyring = Keyring::new(&service, &_username);
if let Ok(password) = keyring.get_password() {
return (password, true);
};
}
let password = rpassword::prompt_password_stdout("Please enter your password: ")
.unwrap_or_else(|_| {
let mut password = String::new();
io::stdin()
.read_line(&mut password)
.expect("Failed to read line");
password.trim().to_string()
});
(password, true)
}
#[cfg(feature = "upload")]
fn get_username() -> String {
println!("Please enter your username:");
let mut line = String::new();
io::stdin().read_line(&mut line).unwrap();
line.trim().to_string()
}
#[cfg(feature = "upload")]
fn complete_registry(opt: &PublishOpt) -> Result<(Registry, bool), Error> {
let username = opt.username.clone().unwrap_or_else(get_username);
let (password, reenter) = match opt.password {
Some(ref password) => (password.clone(), false),
None => get_password(&username),
};
let registry = Registry::new(username, password, Url::parse(&opt.registry)?);
Ok((registry, reenter))
}
#[derive(Debug, StructOpt)]
struct PublishOpt {
#[structopt(
short = "r",
long = "repository-url",
default_value = "https://upload.pypi.org/legacy/"
)]
registry: String,
#[structopt(short, long)]
username: Option<String>,
#[structopt(short, long)]
password: Option<String>,
#[structopt(long)]
debug: bool,
#[structopt(long = "no-strip")]
no_strip: bool,
}
#[derive(Debug, StructOpt)]
#[structopt(name = "maturin")]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::large_enum_variant))]
enum Opt {
#[structopt(name = "build")]
Build {
#[structopt(flatten)]
build: BuildOptions,
#[structopt(long)]
release: bool,
#[structopt(long)]
strip: bool,
#[structopt(long = "no-sdist")]
no_sdist: bool,
},
#[cfg(feature = "upload")]
#[structopt(name = "publish")]
Publish {
#[structopt(flatten)]
build: BuildOptions,
#[structopt(flatten)]
publish: PublishOpt,
#[structopt(long = "no-sdist")]
no_sdist: bool,
},
#[structopt(name = "list-python")]
ListPython,
#[structopt(name = "develop")]
Develop {
#[structopt(short = "b", long = "binding-crate")]
binding_crate: Option<String>,
#[structopt(
short = "m",
long = "manifest-path",
parse(from_os_str),
default_value = "Cargo.toml"
)]
manifest_path: PathBuf,
#[structopt(long)]
release: bool,
#[structopt(long)]
strip: bool,
#[structopt(long = "cargo-extra-args")]
cargo_extra_args: Vec<String>,
#[structopt(long = "rustc-extra-args")]
rustc_extra_args: Vec<String>,
},
#[structopt(name = "sdist")]
SDist {
#[structopt(
short = "m",
long = "manifest-path",
parse(from_os_str),
default_value = "Cargo.toml"
)]
manifest_path: PathBuf,
#[structopt(short, long, parse(from_os_str))]
out: Option<PathBuf>,
},
#[structopt(name = "pep517")]
PEP517(PEP517Command),
}
#[derive(Debug, StructOpt)]
enum PEP517Command {
#[structopt(name = "write-dist-info")]
WriteDistInfo {
#[structopt(flatten)]
build_options: BuildOptions,
#[structopt(long = "metadata-directory", parse(from_os_str))]
metadata_directory: PathBuf,
#[structopt(long)]
strip: bool,
},
#[structopt(name = "build-wheel")]
BuildWheel {
#[structopt(flatten)]
build: BuildOptions,
#[structopt(long)]
strip: bool,
},
#[structopt(name = "write-sdist")]
WriteSDist {
#[structopt(long = "sdist-directory", parse(from_os_str))]
sdist_directory: PathBuf,
#[structopt(
short = "m",
long = "manifest-path",
parse(from_os_str),
default_value = "Cargo.toml",
name = "PATH"
)]
manifest_path: PathBuf,
},
}
fn pep517(subcommand: PEP517Command) -> Result<(), Error> {
match subcommand {
PEP517Command::WriteDistInfo {
mut build_options,
metadata_directory,
strip,
} => {
build_options.interpreter = Some(vec![PathBuf::from("python")]);
let context = build_options.into_build_context(true, strip)?;
let tags = match context.bridge {
BridgeModel::Bindings(_) => vec![context.interpreter[0].get_tag(&context.manylinux)],
BridgeModel::Bin | BridgeModel::Cffi => {
context.target.get_universal_tags(&context.manylinux).1
}
};
let mut writer = PathWriter::from_path(metadata_directory);
write_dist_info(&mut writer, &context.metadata21, &context.scripts, &tags)?;
println!("{}", context.metadata21.get_dist_info_dir().display());
}
PEP517Command::BuildWheel { build, strip } => {
let build_context = build.into_build_context(true, strip)?;
let wheels = build_context.build_wheels()?;
assert_eq!(wheels.len(), 1);
println!("{}", wheels[0].0.file_name().unwrap().to_str().unwrap());
}
PEP517Command::WriteSDist {
sdist_directory,
manifest_path,
} => {
let cargo_toml = CargoToml::from_path(&manifest_path)?;
let manifest_dir = manifest_path.parent().unwrap();
let metadata21 = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir)
.context("Failed to parse Cargo.toml into python metadata")?;
let path = source_distribution(sdist_directory, &metadata21, &manifest_path)
.context("Failed to build source distribution")?;
println!("{}", path.display());
}
};
Ok(())
}
#[cfg(feature = "upload")]
fn upload_ui(build: BuildOptions, publish: &PublishOpt, no_sdist: bool) -> Result<(), Error> {
let build_context = build.into_build_context(!publish.debug, !publish.no_strip)?;
if !build_context.release {
eprintln!("⚠ Warning: You're publishing debug wheels");
}
let mut wheels = build_context.build_wheels()?;
if !no_sdist {
if let Some(source_distribution) = build_context.build_source_distribution()? {
wheels.push(source_distribution);
}
}
let (mut registry, reenter) = complete_registry(&publish)?;
loop {
println!("🚀 Uploading {} packages", wheels.len());
let upload_result = wheels
.iter()
.map(|(wheel_path, supported_versions, _)| {
let result = upload(
®istry,
&wheel_path,
&build_context.metadata21,
&supported_versions,
);
result.map_err(|err| (wheel_path.clone(), err))
})
.collect();
match upload_result {
Ok(()) => break,
Err((_, UploadError::AuthenticationError)) if reenter => {
println!("⛔ Username and/or password are wrong");
#[cfg(feature = "keyring")]
{
let old_username = registry.username.clone();
let keyring = Keyring::new(&env!("CARGO_PKG_NAME"), &old_username);
match keyring.delete_password() {
Ok(()) => {}
Err(KeyringError::NoPasswordFound) | Err(KeyringError::NoBackendFound) => {}
_ => eprintln!("⚠ Failed to remove password from keyring"),
}
}
let username = get_username();
let password = rpassword::prompt_password_stdout("Please enter your password: ")
.unwrap_or_else(|_| {
let mut password = String::new();
io::stdin()
.read_line(&mut password)
.expect("Failed to read line");
password.trim().to_string()
});
registry = Registry::new(username, password, registry.url);
println!("… Retrying");
}
Err((_, UploadError::AuthenticationError)) => {
bail!("Username and/or password are wrong");
}
Err((wheel_path, err)) => {
let filename = wheel_path.file_name().unwrap_or(&wheel_path.as_os_str());
return Err(err).context(format!("💥 Failed to upload {:?}", filename))?;
}
}
}
println!("✨ Packages uploaded succesfully");
#[cfg(feature = "keyring")]
{
let username = registry.username.clone();
let keyring = Keyring::new(&env!("CARGO_PKG_NAME"), &username);
let password = registry.password.clone();
keyring.set_password(&password).unwrap_or_else(|e| {
eprintln!("⚠ Failed to store the password in the keyring: {:?}", e)
});
}
Ok(())
}
fn run() -> Result<(), Error> {
#[cfg(feature = "log")]
pretty_env_logger::init();
let opt = Opt::from_args();
match opt {
Opt::Build {
build,
release,
strip,
no_sdist,
} => {
let build_context = build.into_build_context(release, strip)?;
if !no_sdist {
build_context.build_source_distribution()?;
}
build_context.build_wheels()?;
}
#[cfg(feature = "upload")]
Opt::Publish {
build,
publish,
no_sdist,
} => {
upload_ui(build, &publish, no_sdist)?;
}
Opt::ListPython => {
let target = Target::from_target_triple(None)?;
let found = PythonInterpreter::find_all(&target, &BridgeModel::Cffi)?;
println!("🐍 {} python interpreter found:", found.len());
for interpreter in found {
println!(" - {}", interpreter);
}
}
Opt::Develop {
binding_crate,
manifest_path,
cargo_extra_args,
rustc_extra_args,
release,
strip,
} => {
let venv_dir = match env::var_os("VIRTUAL_ENV") {
Some(dir) => PathBuf::from(dir),
None => {
bail!("You need be inside a virtualenv to use develop (VIRTUAL_ENV isn't set)")
}
};
develop(
binding_crate,
&manifest_path,
cargo_extra_args,
rustc_extra_args,
&venv_dir,
release,
strip,
)?;
}
Opt::SDist { manifest_path, out } => {
let manifest_dir = manifest_path.parent().unwrap();
get_pyproject_toml(&manifest_dir)
.context("A pyproject.toml with a PEP 517 compliant `[build-system]` table is required to build a source distribution")?;
let cargo_toml = CargoToml::from_path(&manifest_path)?;
let metadata21 = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir)
.context("Failed to parse Cargo.toml into python metadata")?;
let cargo_metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()
.map_err(|e| format_err!("Cargo metadata failed: {}", e))?;
let wheel_dir = match out {
Some(ref dir) => dir.clone(),
None => PathBuf::from(&cargo_metadata.target_directory).join("wheels"),
};
fs::create_dir_all(&wheel_dir)
.context("Failed to create the target directory for the source distribution")?;
source_distribution(&wheel_dir, &metadata21, &manifest_path)
.context("Failed to build source distribution")?;
}
Opt::PEP517(subcommand) => pep517(subcommand)?,
}
Ok(())
}
fn main() {
#[cfg(feature = "human-panic")]
{
setup_panic!();
}
if let Err(e) = run() {
eprintln!("💥 maturin failed");
for cause in e.as_fail().iter_chain().collect::<Vec<_>>().iter() {
eprintln!(" Caused by: {}", cause);
}
std::process::exit(1);
}
}