From 08142db7592e070141df6b67ddb5169c7f9be717 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 30 Nov 2021 08:14:03 +0000 Subject: [PATCH] init: new command similar to `cargo init` --- Changelog.md | 4 ++- src/lib.rs | 2 +- src/main.rs | 23 ++++++++++++- src/new_project.rs | 82 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 83 insertions(+), 28 deletions(-) diff --git a/Changelog.md b/Changelog.md index f52416ce4..302e413a7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* Add a `maturin init` command as a companion to `maturin new` in [#719](https://github.com/PyO3/maturin/pull/719) + ## [0.12.3] - 2021-11-29 * Use platform tag from `sysconfig.platform` on non-portable Linux in [#709](https://github.com/PyO3/maturin/pull/709) @@ -122,7 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.10.3] - 2021-04-13 * The `upload` command is now implemented, it is mostly similar to `twine upload`. [#484](https://github.com/PyO3/maturin/pull/484) - * Interpreter search now uses python 3.6 to 3.12 + * Interpreter search now uses python 3.6 to 3.12 * Add basic support for OpenBSD in [#496](https://github.com/PyO3/maturin/pull/496) * Fix the PowerPC platform by messense in [#503](https://github.com/PyO3/maturin/pull/503) diff --git a/src/lib.rs b/src/lib.rs index 0fe93f2ab..1215fc5cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ pub use crate::metadata::{Metadata21, WheelMetadata}; pub use crate::module_writer::{ write_dist_info, ModuleWriter, PathWriter, SDistWriter, WheelWriter, }; -pub use crate::new_project::new_project; +pub use crate::new_project::{new_project, init_project}; pub use crate::pyproject_toml::PyProjectToml; pub use crate::python_interpreter::PythonInterpreter; pub use crate::target::Target; diff --git a/src/main.rs b/src/main.rs index a087d0058..3c3bf3d25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use human_panic::setup_panic; #[cfg(feature = "password-storage")] use keyring::{Keyring, KeyringError}; use maturin::{ - develop, new_project, source_distribution, write_dist_info, BridgeModel, BuildOptions, + develop, init_project, new_project, source_distribution, write_dist_info, BridgeModel, BuildOptions, CargoToml, Metadata21, PathWriter, PlatformTag, PyProjectToml, PythonInterpreter, Target, }; use std::env; @@ -270,6 +270,21 @@ enum Opt { #[structopt(short, long, parse(from_os_str))] out: Option, }, + /// Create a new cargo project in an existing directory + #[structopt(name = "init")] + InitProject { + /// Project path + path: Option, + /// Set the resulting package name, defaults to the directory name + #[structopt(long)] + name: Option, + /// Use mixed Rust/Python project layout + #[structopt(long)] + mixed: bool, + /// Which kind of bindings to use + #[structopt(short, long, possible_values = &["pyo3", "rust-cpython", "cffi", "bin"])] + bindings: Option, + }, /// Create a new cargo project #[structopt(name = "new")] NewProject { @@ -636,6 +651,12 @@ fn run() -> Result<()> { .context("Failed to build source distribution")?; } Opt::Pep517(subcommand) => pep517(subcommand)?, + Opt::InitProject { + path, + name, + mixed, + bindings, + } => init_project(path, name, mixed, bindings)?, Opt::NewProject { path, name, diff --git a/src/new_project.rs b/src/new_project.rs index 82d0baddc..cf4b3502e 100644 --- a/src/new_project.rs +++ b/src/new_project.rs @@ -11,10 +11,11 @@ struct ProjectGenerator<'a> { crate_name: String, bindings: String, mixed: bool, + overwrite: bool, } impl<'a> ProjectGenerator<'a> { - fn new(project_name: String, mixed: bool, bindings: String) -> Result { + fn new(project_name: String, mixed: bool, bindings: String, overwrite: bool) -> Result { let crate_name = project_name.replace('-', "_"); let mut env = Environment::new(); env.add_template(".gitignore", include_str!("templates/.gitignore.j2"))?; @@ -33,6 +34,7 @@ impl<'a> ProjectGenerator<'a> { crate_name, bindings, mixed, + overwrite, }) } @@ -40,41 +42,26 @@ impl<'a> ProjectGenerator<'a> { let src_path = project_path.join("src"); fs::create_dir_all(&src_path)?; - let gitignore = self.render_template(".gitignore")?; - fs::write(project_path.join(".gitignore"), gitignore)?; - - let cargo_toml = self.render_template("Cargo.toml")?; - fs::write(project_path.join("Cargo.toml"), cargo_toml)?; - - let pyproject_toml = self.render_template("pyproject.toml")?; - fs::write(project_path.join("pyproject.toml"), pyproject_toml)?; + self.write_project_file(project_path, ".gitignore")?; + self.write_project_file(project_path, "Cargo.toml")?; + self.write_project_file(project_path, "pyproject.toml")?; if self.bindings == "bin" { - let main_rs = self.render_template("main.rs")?; - fs::write(src_path.join("main.rs"), main_rs)?; + self.write_project_file(&src_path, "main.rs")?; } else { - let lib_rs = self.render_template("lib.rs")?; - fs::write(src_path.join("lib.rs"), lib_rs)?; + self.write_project_file(&src_path, "lib.rs")?; } let gh_action_path = project_path.join(".github").join("workflows"); fs::create_dir_all(&gh_action_path)?; - let ci_yml = self.render_template("CI.yml")?; - fs::write(gh_action_path.join("CI.yml"), ci_yml)?; + self.write_project_file(&gh_action_path, "CI.yml")?; if self.mixed { let py_path = project_path.join(&self.crate_name); fs::create_dir_all(&py_path)?; - let init_py = self.render_template("__init__.py")?; - fs::write(py_path.join("__init__.py"), init_py)?; + self.write_project_file(&py_path, "__init__.py")?; } - println!( - " ✨ {} {} {}", - style("Done!").bold().green(), - style("New project created").bold(), - style(&project_path.display()).underlined() - ); Ok(()) } @@ -91,6 +78,14 @@ impl<'a> ProjectGenerator<'a> { ))?; Ok(out) } + + fn write_project_file(&self, directory: &Path, file: &str) -> Result<()> { + let path = directory.join(file); + if self.overwrite || !path.exists() { + fs::write(path, self.render_template(file)?)?; + } + Ok(()) + } } /// Generate a new cargo project @@ -104,13 +99,50 @@ pub fn new_project( if project_path.exists() { bail!("destination `{}` already exists", project_path.display()); } + generate_project(project_path, name, mixed,bindings, true)?; + println!( + " ✨ {} {} {}", + style("Done!").bold().green(), + style("New project created").bold(), + style(&project_path.display()).underlined() + ); + Ok(()) +} + +/// Generate a new cargo project in an existing directory +pub fn init_project( + path: Option, + name: Option, + mixed: bool, + bindings: Option, +) -> Result<()> { + let project_path = path.map(Into::into).map_or_else(std::env::current_dir, Ok)?; + if project_path.join("pyproject.toml").exists() || project_path.join("Cargo.toml").exists() { + bail!("`maturin init` cannot be run on existing projects"); + } + generate_project(&project_path, name, mixed,bindings, false)?; + println!( + " ✨ {} {} {}", + style("Done!").bold().green(), + style("Initialized project").bold(), + style(&project_path.display()).underlined() + ); + Ok(()) +} +fn generate_project( + project_path: &Path, + name: Option, + mixed: bool, + bindings: Option, + overwrite: bool, +) -> Result<()> { let name = if let Some(name) = name { name } else { let file_name = project_path .file_name() - .context("Fail to get name from path")?; + .with_context(|| format!("Failed to get name from path '{}'", project_path.display()))?; file_name .to_str() .context("Filename isn't valid Unicode")? @@ -135,6 +167,6 @@ pub fn new_project( bindings_items[selection].to_string() }; - let generator = ProjectGenerator::new(name, mixed, bindings)?; + let generator = ProjectGenerator::new(name, mixed, bindings, overwrite)?; generator.generate(project_path) }