mirror of https://github.com/astral-sh/uv
Add `--bare` option to `uv init` (#11192)
People are looking for a less opinionated version of `uv init`. The goal here is to create a `pyproject.toml` and nothing else. With the `--lib` or `--package` flags, we'll still configure a build backend but we won't create the source tree. This disables things like the default `description`, author behavior, and VCS. See - https://github.com/astral-sh/uv/issues/8178 - https://github.com/astral-sh/uv/issues/7181 - https://github.com/astral-sh/uv/issues/6750
This commit is contained in:
parent
989b103171
commit
acbbb2b82a
|
|
@ -2518,6 +2518,13 @@ pub struct InitArgs {
|
|||
#[arg(long, conflicts_with = "script")]
|
||||
pub name: Option<PackageName>,
|
||||
|
||||
/// Only create a `pyproject.toml`.
|
||||
///
|
||||
/// Disables creating extra files like `README.md`, the `src/` tree, `.python-version` files,
|
||||
/// etc.
|
||||
#[arg(long, conflicts_with = "script")]
|
||||
pub bare: bool,
|
||||
|
||||
/// Create a virtual project, rather than a package.
|
||||
///
|
||||
/// This option is deprecated and will be removed in a future release.
|
||||
|
|
@ -2574,9 +2581,13 @@ pub struct InitArgs {
|
|||
pub r#script: bool,
|
||||
|
||||
/// Set the project description.
|
||||
#[arg(long, conflicts_with = "script")]
|
||||
#[arg(long, conflicts_with = "script", overrides_with = "no_description")]
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Disable the description for the project.
|
||||
#[arg(long, conflicts_with = "script", overrides_with = "description")]
|
||||
pub no_description: bool,
|
||||
|
||||
/// Initialize a version control system for the project.
|
||||
///
|
||||
/// By default, uv will initialize a Git repository (`git`). Use `--vcs none` to explicitly
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ pub(crate) async fn init(
|
|||
name: Option<PackageName>,
|
||||
package: bool,
|
||||
init_kind: InitKind,
|
||||
bare: bool,
|
||||
description: Option<String>,
|
||||
no_description: bool,
|
||||
vcs: Option<VersionControlSystem>,
|
||||
build_backend: Option<ProjectBuildBackend>,
|
||||
no_readme: bool,
|
||||
|
|
@ -133,7 +135,9 @@ pub(crate) async fn init(
|
|||
&name,
|
||||
package,
|
||||
project_kind,
|
||||
bare,
|
||||
description,
|
||||
no_description,
|
||||
vcs,
|
||||
build_backend,
|
||||
no_readme,
|
||||
|
|
@ -275,7 +279,9 @@ async fn init_project(
|
|||
name: &PackageName,
|
||||
package: bool,
|
||||
project_kind: InitProjectKind,
|
||||
bare: bool,
|
||||
description: Option<String>,
|
||||
no_description: bool,
|
||||
vcs: Option<VersionControlSystem>,
|
||||
build_backend: Option<ProjectBuildBackend>,
|
||||
no_readme: bool,
|
||||
|
|
@ -576,6 +582,8 @@ async fn init_project(
|
|||
path,
|
||||
&requires_python,
|
||||
description.as_deref(),
|
||||
no_description,
|
||||
bare,
|
||||
vcs,
|
||||
build_backend,
|
||||
author_from,
|
||||
|
|
@ -694,12 +702,15 @@ impl InitKind {
|
|||
|
||||
impl InitProjectKind {
|
||||
/// Initialize this project kind at the target path.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
fn init(
|
||||
self,
|
||||
name: &PackageName,
|
||||
path: &Path,
|
||||
requires_python: &RequiresPython,
|
||||
description: Option<&str>,
|
||||
no_description: bool,
|
||||
bare: bool,
|
||||
vcs: Option<VersionControlSystem>,
|
||||
build_backend: Option<ProjectBuildBackend>,
|
||||
author_from: Option<AuthorFrom>,
|
||||
|
|
@ -712,6 +723,8 @@ impl InitProjectKind {
|
|||
path,
|
||||
requires_python,
|
||||
description,
|
||||
no_description,
|
||||
bare,
|
||||
vcs,
|
||||
build_backend,
|
||||
author_from,
|
||||
|
|
@ -723,6 +736,8 @@ impl InitProjectKind {
|
|||
path,
|
||||
requires_python,
|
||||
description,
|
||||
no_description,
|
||||
bare,
|
||||
vcs,
|
||||
build_backend,
|
||||
author_from,
|
||||
|
|
@ -733,11 +748,14 @@ impl InitProjectKind {
|
|||
}
|
||||
|
||||
/// Initialize a Python application at the target path.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
fn init_application(
|
||||
name: &PackageName,
|
||||
path: &Path,
|
||||
requires_python: &RequiresPython,
|
||||
description: Option<&str>,
|
||||
no_description: bool,
|
||||
bare: bool,
|
||||
vcs: Option<VersionControlSystem>,
|
||||
build_backend: Option<ProjectBuildBackend>,
|
||||
author_from: Option<AuthorFrom>,
|
||||
|
|
@ -762,14 +780,17 @@ impl InitProjectKind {
|
|||
requires_python,
|
||||
author.as_ref(),
|
||||
description,
|
||||
no_description,
|
||||
no_readme,
|
||||
);
|
||||
|
||||
// Include additional project configuration for packaged applications
|
||||
if package {
|
||||
// Since it'll be packaged, we can add a `[project.scripts]` entry
|
||||
pyproject.push('\n');
|
||||
pyproject.push_str(&pyproject_project_scripts(name, name.as_str(), "main"));
|
||||
if !bare {
|
||||
pyproject.push('\n');
|
||||
pyproject.push_str(&pyproject_project_scripts(name, name.as_str(), "main"));
|
||||
}
|
||||
|
||||
// Add a build system
|
||||
let build_backend = build_backend.unwrap_or_default();
|
||||
|
|
@ -777,13 +798,15 @@ impl InitProjectKind {
|
|||
pyproject.push_str(&pyproject_build_system(name, build_backend));
|
||||
pyproject_build_backend_prerequisites(name, path, build_backend)?;
|
||||
|
||||
// Generate `src` files
|
||||
generate_package_scripts(name, path, build_backend, false)?;
|
||||
if !bare {
|
||||
// Generate `src` files
|
||||
generate_package_scripts(name, path, build_backend, false)?;
|
||||
}
|
||||
} else {
|
||||
// Create `hello.py` if it doesn't exist
|
||||
// TODO(zanieb): Only create `hello.py` if there are no other Python files?
|
||||
let hello_py = path.join("hello.py");
|
||||
if !hello_py.try_exists()? {
|
||||
if !hello_py.try_exists()? && !bare {
|
||||
fs_err::write(
|
||||
path.join("hello.py"),
|
||||
indoc::formatdoc! {r#"
|
||||
|
|
@ -806,11 +829,14 @@ impl InitProjectKind {
|
|||
}
|
||||
|
||||
/// Initialize a library project at the target path.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
fn init_library(
|
||||
name: &PackageName,
|
||||
path: &Path,
|
||||
requires_python: &RequiresPython,
|
||||
description: Option<&str>,
|
||||
no_description: bool,
|
||||
bare: bool,
|
||||
vcs: Option<VersionControlSystem>,
|
||||
build_backend: Option<ProjectBuildBackend>,
|
||||
author_from: Option<AuthorFrom>,
|
||||
|
|
@ -831,6 +857,7 @@ impl InitProjectKind {
|
|||
requires_python,
|
||||
author.as_ref(),
|
||||
description,
|
||||
no_description,
|
||||
no_readme,
|
||||
);
|
||||
|
||||
|
|
@ -843,7 +870,9 @@ impl InitProjectKind {
|
|||
fs_err::write(path.join("pyproject.toml"), pyproject)?;
|
||||
|
||||
// Generate `src` files
|
||||
generate_package_scripts(name, path, build_backend, true)?;
|
||||
if !bare {
|
||||
generate_package_scripts(name, path, build_backend, true)?;
|
||||
};
|
||||
|
||||
// Initialize the version control system.
|
||||
init_vcs(path, vcs)?;
|
||||
|
|
@ -877,20 +906,24 @@ fn pyproject_project(
|
|||
requires_python: &RequiresPython,
|
||||
author: Option<&Author>,
|
||||
description: Option<&str>,
|
||||
no_description: bool,
|
||||
no_readme: bool,
|
||||
) -> String {
|
||||
indoc::formatdoc! {r#"
|
||||
[project]
|
||||
name = "{name}"
|
||||
version = "0.1.0"
|
||||
description = "{description}"{readme}{authors}
|
||||
version = "0.1.0"{description}{readme}{authors}
|
||||
requires-python = "{requires_python}"
|
||||
dependencies = []
|
||||
"#,
|
||||
readme = if no_readme { "" } else { "\nreadme = \"README.md\"" },
|
||||
description = if no_description {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\ndescription = \"{description}\"", description = description.unwrap_or("Add your description here"))
|
||||
},
|
||||
authors = author.map_or_else(String::new, |author| format!("\nauthors = [\n {}\n]", author.to_toml_string())),
|
||||
requires_python = requires_python.specifiers(),
|
||||
description = description.unwrap_or("Add your description here"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1427,7 +1427,9 @@ async fn run_project(
|
|||
args.name,
|
||||
args.package,
|
||||
args.kind,
|
||||
args.bare,
|
||||
args.description,
|
||||
args.no_description,
|
||||
args.vcs,
|
||||
args.build_backend,
|
||||
args.no_readme,
|
||||
|
|
|
|||
|
|
@ -195,7 +195,9 @@ pub(crate) struct InitSettings {
|
|||
pub(crate) name: Option<PackageName>,
|
||||
pub(crate) package: bool,
|
||||
pub(crate) kind: InitKind,
|
||||
pub(crate) bare: bool,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) no_description: bool,
|
||||
pub(crate) vcs: Option<VersionControlSystem>,
|
||||
pub(crate) build_backend: Option<ProjectBuildBackend>,
|
||||
pub(crate) no_readme: bool,
|
||||
|
|
@ -216,10 +218,12 @@ impl InitSettings {
|
|||
r#virtual,
|
||||
package,
|
||||
no_package,
|
||||
bare,
|
||||
app,
|
||||
lib,
|
||||
script,
|
||||
description,
|
||||
no_description,
|
||||
vcs,
|
||||
build_backend,
|
||||
no_readme,
|
||||
|
|
@ -245,17 +249,21 @@ impl InitSettings {
|
|||
.map(|fs| fs.install_mirrors.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let no_description = no_description || (bare && description.is_none());
|
||||
|
||||
Self {
|
||||
path,
|
||||
name,
|
||||
package,
|
||||
kind,
|
||||
bare,
|
||||
description,
|
||||
vcs,
|
||||
no_description,
|
||||
vcs: vcs.or(bare.then_some(VersionControlSystem::None)),
|
||||
build_backend,
|
||||
no_readme,
|
||||
no_readme: no_readme || bare,
|
||||
author_from,
|
||||
no_pin_python,
|
||||
no_pin_python: no_pin_python || bare,
|
||||
no_workspace,
|
||||
python: python.and_then(Maybe::into_option),
|
||||
install_mirrors,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,53 @@ fn init() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_bare() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().arg("foo").arg("--bare"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
// No extra files should be created
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/README.md")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/hello.py")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.python-version")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.git")
|
||||
.assert(predicate::path::missing());
|
||||
|
||||
let pyproject = context.read("foo/pyproject.toml");
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject, @r###"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Run `uv init --app` to create an application project
|
||||
#[test]
|
||||
fn init_application() -> Result<()> {
|
||||
|
|
@ -399,6 +446,161 @@ fn init_library() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_bare_lib() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().arg("foo").arg("--bare").arg("--lib"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
// No extra files should be created
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/README.md")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/src")
|
||||
.assert(predicate::path::missing());
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.git")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.python-version")
|
||||
.assert(predicate::path::missing());
|
||||
|
||||
let pyproject = context.read("foo/pyproject.toml");
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject, @r###"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_bare_package() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().arg("foo").arg("--bare").arg("--package"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
// No extra files should be created
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/README.md")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/src")
|
||||
.assert(predicate::path::missing());
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.git")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.python-version")
|
||||
.assert(predicate::path::missing());
|
||||
|
||||
let pyproject = context.read("foo/pyproject.toml");
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject, @r###"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"###
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_bare_opt_in() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// With `--bare`, you can still opt-in to extras
|
||||
// TODO(zanieb): Add options for `--pin-python` and `--readme`
|
||||
uv_snapshot!(context.filters(), context.init().arg("foo").arg("--bare")
|
||||
.arg("--description").arg("foo")
|
||||
.arg("--vcs").arg("git"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/README.md")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/src")
|
||||
.assert(predicate::path::missing());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.git")
|
||||
.assert(predicate::path::is_dir());
|
||||
context
|
||||
.temp_dir
|
||||
.child("foo/.python-version")
|
||||
.assert(predicate::path::missing());
|
||||
|
||||
let pyproject = context.read("foo/pyproject.toml");
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject, @r###"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
description = "foo"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// General init --script correctness test
|
||||
#[test]
|
||||
fn init_script() -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -295,3 +295,39 @@ Hello from example-ext!
|
|||
|
||||
Changes to the extension code in `lib.rs` or `main.cpp` will require running `--reinstall` to
|
||||
rebuild them.
|
||||
|
||||
## Creating a minimal project
|
||||
|
||||
If you only want to create a `pyproject.toml`, use the `--bare` option:
|
||||
|
||||
```console
|
||||
$ uv init example --bare
|
||||
```
|
||||
|
||||
uv will skip creating a Python version pin file, a README, and any source directories or files.
|
||||
Additionally, uv will not initialize a version control system (i.e., `git`).
|
||||
|
||||
```console
|
||||
$ tree example-bare
|
||||
example-bare
|
||||
└── pyproject.toml
|
||||
```
|
||||
|
||||
uv will also not add extra metadata to the `pyproject.toml`, such as the `description` or `authors`.
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "example"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
```
|
||||
|
||||
The `--bare` option can be used with other options like `--lib` or `--build-backend` — in these
|
||||
cases uv will still configure a build system but will not create the expected file structure.
|
||||
|
||||
When `--bare` is used, additional features can still be used opt-in:
|
||||
|
||||
```console
|
||||
$ uv init example --description "Hello world" --author-from git --vcs git
|
||||
```
|
||||
|
|
|
|||
|
|
@ -555,6 +555,10 @@ uv init [OPTIONS] [PATH]
|
|||
|
||||
<li><code>none</code>: Do not infer the author information</li>
|
||||
</ul>
|
||||
</dd><dt><code>--bare</code></dt><dd><p>Only create a <code>pyproject.toml</code>.</p>
|
||||
|
||||
<p>Disables creating extra files like <code>README.md</code>, the <code>src/</code> tree, <code>.python-version</code> files, etc.</p>
|
||||
|
||||
</dd><dt><code>--build-backend</code> <i>build-backend</i></dt><dd><p>Initialize a build-backend of choice for the project.</p>
|
||||
|
||||
<p>Implicitly sets <code>--package</code>.</p>
|
||||
|
|
@ -632,6 +636,8 @@ uv init [OPTIONS] [PATH]
|
|||
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
|
||||
|
||||
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p>
|
||||
</dd><dt><code>--no-description</code></dt><dd><p>Disable the description for the project</p>
|
||||
|
||||
</dd><dt><code>--no-package</code></dt><dd><p>Do not set up the project to be built as a Python package.</p>
|
||||
|
||||
<p>Does not include a <code>[build-system]</code> for the project.</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue