Skip to content

Commit

Permalink
Enhance pep517 write-dist-info command to check for rust toolchain
Browse files Browse the repository at this point in the history
  • Loading branch information
Owen-CH-Leung committed Aug 13, 2024
1 parent 57f35c9 commit 1ec7982
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 84 deletions.
17 changes: 0 additions & 17 deletions maturin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,23 +165,6 @@ def get_requires_for_build_sdist(config_settings: Optional[Mapping[str, Any]] =
def prepare_metadata_for_build_wheel(
metadata_directory: str, config_settings: Optional[Mapping[str, Any]] = None
) -> str:
print("Checking for Rust toolchain....")
is_cargo_installed = False
try:
output = subprocess.check_output(["cargo", "--version"]).decode("utf-8", "ignore")
if "cargo" in output:
is_cargo_installed = True
except (FileNotFoundError, SubprocessError):
pass

if not is_cargo_installed:
sys.stderr.write(
"\nCargo, the Rust package manager, is not installed or is not on PATH.\n"
"This package requires Rust and Cargo to compile extensions. Install it through\n"
"the system's package manager or via https://rustup.rs/\n\n"
)
sys.exit(1)

command = [
"maturin",
"pep517",
Expand Down
99 changes: 72 additions & 27 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use crate::module_writer::{
};
use crate::project_layout::ProjectLayout;
use crate::python_interpreter::InterpreterKind;
use crate::source_distribution::{download_and_execute_rustup, source_distribution};
use crate::source_distribution::source_distribution;
use crate::target::{Arch, Os};
#[cfg(feature = "upload")]
use crate::upload::http_agent;
use crate::{
compile, pyproject_toml::Format, BuildArtifact, Metadata23, ModuleWriter, PyProjectToml,
PythonInterpreter, Target,
Expand All @@ -28,8 +30,13 @@ use std::collections::{HashMap, HashSet};
use std::env;
use std::fmt::{Display, Formatter};
use std::io;
#[cfg(feature = "rustls")]
use std::io::copy;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
#[cfg(feature = "rustls")]
use tempfile::NamedTempFile;
use tracing::instrument;

/// The way the rust code is used in the wheel
Expand Down Expand Up @@ -273,22 +280,6 @@ impl BuildContext {
let sdist_path =
source_distribution(self, pyproject, self.excludes(Format::Sdist)?)
.context("Failed to build source distribution")?;
if self.require_rust_toolchain() {
let mut target_path = sdist_path.clone().to_path_buf();
target_path.pop();
target_path.pop();
target_path.push("bin");

fs::create_dir_all(&target_path)
.context("Fail to create directory for installing rust toolchain")?;

let target_path_str = target_path
.to_str()
.context("Fail to construct target path for installing rust toolchain")?;

download_and_execute_rustup(target_path_str, target_path_str)
.context("Failed to download rust toolchain")?;
}
Ok(Some((sdist_path, "source".to_string())))
}
None => Ok(None),
Expand Down Expand Up @@ -1148,18 +1139,72 @@ impl BuildContext {
}
Ok(wheels)
}
/// Check if user requires to install rust toolchain

/// Check if Rust toolchain is installed
pub fn is_toolchain_installed() -> bool {
return Command::new("cargo").arg("--version").output().is_ok();
}

/// Downloads the rustup installer script and executes it to install rustup
///
/// Loop over `build-system.requires` defined in pyproject.toml and see if `rust-toolchain` is provided
pub fn require_rust_toolchain(&self) -> bool {
match &self.pyproject_toml {
Some(pyproject_toml) => pyproject_toml
.build_system
.requires
.iter()
.any(|req| req.name.as_ref() == "rust-toolchain"),
None => false,
/// Inspired by https://github.com/chriskuehl/rustenv
#[cfg(feature = "rustls")]
pub fn download_and_execute_rustup(rustup_home: &str, cargo_home: &str) -> Result<()> {
let mut tf = NamedTempFile::new()?;
let agent = http_agent()?;
let response = agent.get("https://sh.rustup.rs").call()?.into_string()?;

copy(&mut response.as_bytes(), &mut tf)?;

#[cfg(unix)]
{
Command::new("sh")
.arg(tf.path())
.arg("-y")
.env("RUSTUP_HOME", rustup_home)
.env("CARGO_HOME", cargo_home)
.status()?;
}

#[cfg(windows)]
{
let cargo_env_path = cargo_env_path.replace("/", "\\");

Command::new("cmd")
.args(&["/C", "CALL", &cargo_env_path])
.status()?;
}

Ok(())
}

/// Refresh the current shell to include path for rust toolchain
pub fn add_cargo_to_path(cargo_home: &str) -> Result<()> {
let cargo_bin_path = Path::new(cargo_home).join("bin");

#[cfg(unix)]
{
let current_path = env::var("PATH").unwrap_or_default();
let new_path = format!("{}:{}", cargo_bin_path.display(), current_path);
unsafe {env::set_var("PATH", &new_path)};
Command::new(cargo_bin_path.join("rustup"))
.arg("default")
.arg("stable")
.output()
.context("Failed to set default Rust toolchain using rustup")?;
}

/// FIXME: Test the following command
#[cfg(windows)]
{
Command::new("cmd")
.args(&["/C", tf.path(), "-y", "--no-modify-path"])
.env("RUSTUP_HOME", rustup_home)
.env("CARGO_HOME", cargo_home)
.status()?;
}

Ok(())
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub use crate::pyproject_toml::PyProjectToml;
pub use crate::python_interpreter::PythonInterpreter;
pub use crate::target::Target;
#[cfg(feature = "upload")]
pub use crate::upload::{upload, upload_ui, PublishOpt, Registry, UploadError};
pub use crate::upload::{upload_ui, PublishOpt, Registry, UploadError};
pub use auditwheel::PlatformTag;

mod auditwheel;
Expand All @@ -64,4 +64,4 @@ mod python_interpreter;
mod source_distribution;
mod target;
#[cfg(feature = "upload")]
mod upload;
mod upload;
16 changes: 14 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ use cargo_zigbuild::Zig;
#[cfg(feature = "cli-completion")]
use clap::CommandFactory;
use clap::{Parser, Subcommand};
#[cfg(feature = "rustls")]
use dirs::home_dir;
#[cfg(feature = "scaffolding")]
use maturin::{ci::GenerateCI, init_project, new_project, GenerateProjectOptions};
use maturin::{
develop, write_dist_info, BridgeModel, BuildOptions, CargoOptions, DevelopOptions, PathWriter,
PlatformTag, PythonInterpreter, Target,
develop, write_dist_info, BridgeModel, BuildOptions, CargoOptions,
DevelopOptions, PathWriter, PlatformTag, PythonInterpreter, Target,
};
#[cfg(feature = "rustls")]
use maturin::BuildContext;
#[cfg(feature = "schemars")]
use maturin::{generate_json_schema, GenerateJsonSchemaOptions};
#[cfg(feature = "upload")]
Expand Down Expand Up @@ -273,6 +277,14 @@ fn pep517(subcommand: Pep517Command) -> Result<()> {
strip,
} => {
assert_eq!(build_options.interpreter.len(), 1);
#[cfg(feature = "rustls")]
if !BuildContext::is_toolchain_installed() {
let home_dir = home_dir().context("Unabel to get user home directory")?;
let home_dir_str = home_dir.to_str().context("Unable to convert home directory string")?;
BuildContext::download_and_execute_rustup(home_dir_str, home_dir_str)
.context("Unable to install & execute rustup")?;
BuildContext::add_cargo_to_path(home_dir_str).context("Unable to add cargo path")?;
}
let context = build_options.into_build_context(true, strip, false)?;

// Since afaik all other PEP 517 backends also return linux tagged wheels, we do so too
Expand Down
36 changes: 0 additions & 36 deletions src/source_distribution.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::module_writer::{add_data, ModuleWriter};
use crate::pyproject_toml::SdistGenerator;
use crate::upload::http_agent;
use crate::{pyproject_toml::Format, BuildContext, PyProjectToml, SDistWriter};
use anyhow::{bail, Context, Result};
use cargo_metadata::{Metadata, MetadataCommand, PackageId};
Expand All @@ -9,11 +8,9 @@ use ignore::overrides::Override;
use normpath::PathExt as _;
use path_slash::PathExt as _;
use std::collections::HashMap;
use std::io::copy;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str;
use tempfile::NamedTempFile;
use tracing::debug;

/// Path dependency information.
Expand Down Expand Up @@ -738,36 +735,3 @@ where
None
}
}

/// Downloads the rustup installer script and executes it to install rustup
///
/// Inspired by https://github.com/chriskuehl/rustenv
pub fn download_and_execute_rustup(rustup_home: &str, cargo_home: &str) -> Result<()> {
let mut tf = NamedTempFile::new()?;
let agent = http_agent()?;
let response = agent.get("https://sh.rustup.rs").call()?.into_string()?;

copy(&mut response.as_bytes(), &mut tf)?;

#[cfg(unix)]
{
Command::new("sh")
.arg(tf.path())
.arg("-y")
.arg("--no-modify-path")
.env("RUSTUP_HOME", rustup_home)
.env("CARGO_HOME", cargo_home)
.status()?;
}

#[cfg(windows)]
{
Command::new("cmd")
.args(&["/C", tf.path(), "-y", "--no-modify-path"])
.env("RUSTUP_HOME", rustup_home)
.env("CARGO_HOME", cargo_home)
.status()?;
}

Ok(())
}

0 comments on commit 1ec7982

Please sign in to comment.