diff --git a/crates/uv-dev/src/build.rs b/crates/uv-dev/src/build.rs index af7d3ca80..5d4b1e5fb 100644 --- a/crates/uv-dev/src/build.rs +++ b/crates/uv-dev/src/build.rs @@ -4,9 +4,9 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use clap::Parser; use fs_err as fs; +use rustc_hash::FxHashMap; use distribution_types::IndexLocations; -use rustc_hash::FxHashMap; use uv_build::{SourceBuild, SourceBuildContext}; use uv_cache::{Cache, CacheArgs}; use uv_client::RegistryClientBuilder; diff --git a/crates/uv-dev/src/compile.rs b/crates/uv-dev/src/compile.rs index 5701c3f1e..c9062edb9 100644 --- a/crates/uv-dev/src/compile.rs +++ b/crates/uv-dev/src/compile.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use clap::Parser; use tracing::info; + use uv_cache::{Cache, CacheArgs}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; diff --git a/crates/uv-dev/src/generate_all.rs b/crates/uv-dev/src/generate_all.rs new file mode 100644 index 000000000..1ea4e2d16 --- /dev/null +++ b/crates/uv-dev/src/generate_all.rs @@ -0,0 +1,30 @@ +//! Run all code and documentation generation steps. + +use anyhow::Result; + +use crate::{generate_json_schema, generate_options_reference}; + +#[derive(clap::Args)] +pub(crate) struct Args { + #[arg(long, default_value_t, value_enum)] + mode: Mode, +} + +#[derive(Copy, Clone, PartialEq, Eq, clap::ValueEnum, Default)] +pub(crate) enum Mode { + /// Update the content in the `configuration.md`. + #[default] + Write, + + /// Don't write to the file, check if the file is up-to-date and error if not. + Check, + + /// Write the generated help to stdout. + DryRun, +} + +pub(crate) fn main(args: &Args) -> Result<()> { + generate_json_schema::main(&generate_json_schema::Args { mode: args.mode })?; + generate_options_reference::main(&generate_options_reference::Args { mode: args.mode })?; + Ok(()) +} diff --git a/crates/uv-dev/src/generate_json_schema.rs b/crates/uv-dev/src/generate_json_schema.rs index 2a7253e17..862abef79 100644 --- a/crates/uv-dev/src/generate_json_schema.rs +++ b/crates/uv-dev/src/generate_json_schema.rs @@ -9,6 +9,7 @@ use serde::Deserialize; use uv_distribution::pyproject::ToolUv as WorkspaceOptions; use uv_settings::Options as SettingsOptions; +use crate::generate_all::Mode; use crate::ROOT_DIR; #[derive(Deserialize, JsonSchema)] @@ -25,26 +26,13 @@ struct CombinedOptions { } #[derive(clap::Args)] -pub(crate) struct GenerateJsonSchemaArgs { - /// Write the generated table to stdout (rather than to `uv.schema.json`). +pub(crate) struct Args { + /// Write the generated output to stdout (rather than to `uv.schema.json`). #[arg(long, default_value_t, value_enum)] - mode: Mode, + pub(crate) mode: Mode, } -#[derive(Copy, Clone, PartialEq, Eq, clap::ValueEnum, Default)] -enum Mode { - /// Update the content in the `configuration.md`. - #[default] - Write, - - /// Don't write to the file, check if the file is up-to-date and error if not. - Check, - - /// Write the generated help to stdout. - DryRun, -} - -pub(crate) fn main(args: &GenerateJsonSchemaArgs) -> Result<()> { +pub(crate) fn main(args: &Args) -> Result<()> { let schema = schema_for!(CombinedOptions); let schema_string = serde_json::to_string_pretty(&schema).unwrap(); let filename = "uv.schema.json"; @@ -98,9 +86,9 @@ mod tests { use anyhow::Result; - use crate::generate_json_schema::Mode; + use crate::generate_all::Mode; - use super::{main, GenerateJsonSchemaArgs}; + use super::{main, Args}; #[test] fn test_generate_json_schema() -> Result<()> { @@ -109,6 +97,6 @@ mod tests { } else { Mode::Check }; - main(&GenerateJsonSchemaArgs { mode }) + main(&Args { mode }) } } diff --git a/crates/uv-dev/src/generate_options_reference.rs b/crates/uv-dev/src/generate_options_reference.rs index a6d245710..8051e0f4c 100644 --- a/crates/uv-dev/src/generate_options_reference.rs +++ b/crates/uv-dev/src/generate_options_reference.rs @@ -2,8 +2,12 @@ //! //! Based on: use std::fmt::Write; +use std::path::PathBuf; +use anstream::println; +use anyhow::{bail, Result}; use itertools::Itertools; +use pretty_assertions::StrComparison; use schemars::JsonSchema; use serde::Deserialize; @@ -12,6 +16,9 @@ use uv_macros::OptionsMetadata; use uv_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit}; use uv_settings::Options as SettingsOptions; +use crate::generate_all::Mode; +use crate::ROOT_DIR; + #[derive(Deserialize, JsonSchema, OptionsMetadata)] #[serde(deny_unknown_fields)] #[allow(dead_code)] @@ -25,7 +32,65 @@ struct CombinedOptions { workspace: WorkspaceOptions, } -pub(crate) fn generate() -> String { +#[derive(clap::Args)] +pub(crate) struct Args { + /// Write the generated output to stdout (rather than to `settings.md`). + #[arg(long, default_value_t, value_enum)] + pub(crate) mode: Mode, +} + +pub(crate) fn main(args: &Args) -> Result<()> { + let reference_string = generate(); + let filename = "settings.md"; + let reference_path = PathBuf::from(ROOT_DIR).join("docs").join(filename); + + match args.mode { + Mode::DryRun => { + println!("{reference_string}"); + } + Mode::Check => match fs_err::read_to_string(reference_path) { + Ok(current) => { + if current == reference_string { + println!("Up-to-date: {filename}"); + } else { + let comparison = StrComparison::new(¤t, &reference_string); + bail!("{filename} changed, please run `cargo dev generate-options-reference`:\n{comparison}"); + } + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + bail!("{filename} not found, please run `cargo dev generate-options-reference`"); + } + Err(err) => { + bail!( + "{filename} changed, please run `cargo dev generate-options-reference`:\n{err}" + ); + } + }, + Mode::Write => { + match fs_err::read_to_string(&reference_path) { + Ok(current) => { + if current == reference_string { + println!("Up-to-date: {filename}"); + } else { + println!("Updating: {filename}"); + fs_err::write(reference_path, reference_string.as_bytes())?; + } + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + println!("Updating: {filename}"); + fs_err::write(reference_path, reference_string.as_bytes())?; + } + Err(err) => { + bail!("{filename} changed, please run `cargo dev generate-options-reference`:\n{err}"); + } + } + } + } + + Ok(()) +} + +fn generate() -> String { let mut output = String::new(); generate_set( @@ -219,3 +284,24 @@ impl Visit for CollectOptionsVisitor { self.fields.push((name.to_owned(), field)); } } + +#[cfg(test)] +mod tests { + use std::env; + + use anyhow::Result; + + use crate::generate_all::Mode; + + use super::{main, Args}; + + #[test] + fn test_generate_options_reference() -> Result<()> { + let mode = if env::var("UV_UPDATE_SCHEMA").as_deref() == Ok("1") { + Mode::Write + } else { + Mode::Check + }; + main(&Args { mode }) + } +} diff --git a/crates/uv-dev/src/main.rs b/crates/uv-dev/src/main.rs index da3802152..b7e698d6c 100644 --- a/crates/uv-dev/src/main.rs +++ b/crates/uv-dev/src/main.rs @@ -19,7 +19,9 @@ use tracing_subscriber::{EnvFilter, Layer}; use crate::build::{build, BuildArgs}; use crate::clear_compile::ClearCompileArgs; use crate::compile::CompileArgs; -use crate::generate_json_schema::GenerateJsonSchemaArgs; +use crate::generate_all::Args as GenerateAllArgs; +use crate::generate_json_schema::Args as GenerateJsonSchemaArgs; +use crate::generate_options_reference::Args as GenerateOptionsReferenceArgs; #[cfg(feature = "render")] use crate::render_benchmarks::RenderBenchmarksArgs; use crate::wheel_metadata::WheelMetadataArgs; @@ -43,6 +45,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; mod build; mod clear_compile; mod compile; +mod generate_all; mod generate_json_schema; mod generate_options_reference; mod render_benchmarks; @@ -60,10 +63,12 @@ enum Cli { Compile(CompileArgs), /// Remove all `.pyc` in the tree. ClearCompile(ClearCompileArgs), + /// Run all code and documentation generation steps. + GenerateAll(GenerateAllArgs), /// Generate JSON schema for the TOML configuration file. GenerateJSONSchema(GenerateJsonSchemaArgs), /// Generate the options reference for the documentation. - GenerateOptionsReference, + GenerateOptionsReference(GenerateOptionsReferenceArgs), #[cfg(feature = "render")] /// Render the benchmarks. RenderBenchmarks(RenderBenchmarksArgs), @@ -80,8 +85,9 @@ async fn run() -> Result<()> { Cli::WheelMetadata(args) => wheel_metadata::wheel_metadata(args).await?, Cli::Compile(args) => compile::compile(args).await?, Cli::ClearCompile(args) => clear_compile::clear_compile(&args)?, + Cli::GenerateAll(args) => generate_all::main(&args)?, Cli::GenerateJSONSchema(args) => generate_json_schema::main(&args)?, - Cli::GenerateOptionsReference => println!("{}", generate_options_reference::generate()), + Cli::GenerateOptionsReference(args) => generate_options_reference::main(&args)?, #[cfg(feature = "render")] Cli::RenderBenchmarks(args) => render_benchmarks::render_benchmarks(&args)?, } diff --git a/docs/settings.md b/docs/settings.md new file mode 100644 index 000000000..446550e07 --- /dev/null +++ b/docs/settings.md @@ -0,0 +1,151 @@ +### Global +##### [`index-url`](#index-url) {: #index-url } + +The URL of the Python package index (by default: ). + +Accepts either a repository compliant with PEP 503 (the simple repository API), or a local +directory laid out in the same format. + +The index provided by this setting is given lower priority than any indexes specified via +[`extra_index_url`](#extra-index-url). + +**Default value**: `https://pypi.org/simple` + +**Type**: `str` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + index-url = "https://pypi.org/simple" + ``` +=== "uv.toml" + + ```toml + + index-url = "https://pypi.org/simple" + ``` + +--- + +##### [`managed`](#managed) {: #managed } + +Whether the project is managed by `uv`. If `false`, `uv` will ignore the project when +`uv run` is invoked. + +**Default value**: `true` + +**Type**: `bool` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + managed = false + ``` +=== "uv.toml" + + ```toml + + managed = false + ``` + +--- + +#### `pip` + +A `[tool.uv.pip]` section. + +##### [`no-header`](#pip_no-header) {: #pip_no-header } + + +Exclude the comment header at the top of output file generated by `uv pip compile`. + +**Default value**: `false` + +**Type**: `bool` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.pip] + no-header = true + ``` +=== "uv.toml" + + ```toml + [pip] + no-header = true + ``` + +--- + +#### `workspace` + +##### [`exclude`](#workspace_exclude) {: #workspace_exclude } + + +Packages to exclude as workspace members. If a package matches both `members` and +`exclude`, it will be excluded. + +Supports both globs and explicit paths. + +For more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html). + +**Default value**: `[]` + +**Type**: `list[str]` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.workspace] + exclude = ["member1", "path/to/member2", "libs/*"] + ``` +=== "uv.toml" + + ```toml + [workspace] + exclude = ["member1", "path/to/member2", "libs/*"] + ``` + +--- + +##### [`members`](#workspace_members) {: #workspace_members } + + +Packages to include as workspace members. + +Supports both globs and explicit paths. + +For more information on the glob syntax, refer to the [`glob` documentation](https://docs.rs/glob/latest/glob/struct.Pattern.html). + +**Default value**: `[]` + +**Type**: `list[str]` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv.workspace] + members = ["member1", "path/to/member2", "libs/*"] + ``` +=== "uv.toml" + + ```toml + [workspace] + members = ["member1", "path/to/member2", "libs/*"] + ``` + +--- +