Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,10 @@ impl<T> Maybe<T> {
Maybe::None => None,
}
}

pub fn is_some(&self) -> bool {
matches!(self, Maybe::Some(_))
}
}

/// Parse a string into an [`IndexUrl`], mapping the empty string to `None`.
Expand Down
85 changes: 84 additions & 1 deletion crates/uv-workspace/src/pyproject_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::path::Path;
use std::str::FromStr;
use std::{fmt, mem};
use thiserror::Error;
use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value};
use toml_edit::{Array, ArrayOfTables, DocumentMut, Item, RawString, Table, TomlError, Value};
use uv_distribution_types::Index;
use uv_fs::PortablePath;
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers};
use uv_pep508::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl};
Expand Down Expand Up @@ -190,6 +191,88 @@ impl PyProjectTomlMut {
Ok(edit)
}

/// Add an [`Index`] to `tool.uv.index`.
pub fn add_index(&mut self, index: &Index) -> Result<(), Error> {
let existing = self
.doc
.entry("tool")
.or_insert(implicit())
.as_table_mut()
.ok_or(Error::MalformedSources)?
.entry("uv")
.or_insert(implicit())
.as_table_mut()
.ok_or(Error::MalformedSources)?
.entry("index")
.or_insert(Item::ArrayOfTables(ArrayOfTables::new()))
.as_array_of_tables()
.ok_or(Error::MalformedSources)?;

let mut table = Table::new();
if let Some(name) = index.name.as_ref() {
table.insert("name", toml_edit::value(name.to_string()));
} else if let Some(name) = existing
.iter()
.find(|table| {
table
.get("url")
.is_some_and(|url| url.as_str() == Some(index.url.url().as_str()))
})
.and_then(|existing| existing.get("name"))
{
// If there's an existing index with the same URL, and a name, preserve the name.
table.insert("name", name.clone());
}
table.insert("url", toml_edit::value(index.url.to_string()));
if index.default {
table.insert("default", toml_edit::value(true));
}

// Push the item to the table.
let mut updated = ArrayOfTables::new();
updated.push(table);
for table in existing {
// If there's another index with the same name, replace it.
if table
.get("name")
.is_some_and(|name| name.as_str() == index.name.as_deref())
{
continue;
}

// If there's another default index, remove it.
if index.default
&& table
.get("default")
.is_some_and(|default| default.as_bool() == Some(true))
{
continue;
}

// If there's another index with the same URL, replace it.
if table
.get("url")
.is_some_and(|url| url.as_str() == Some(index.url.url().as_str()))
{
continue;
}

updated.push(table.clone());
}
self.doc
.entry("tool")
.or_insert(implicit())
.as_table_mut()
.ok_or(Error::MalformedSources)?
.entry("uv")
.or_insert(implicit())
.as_table_mut()
.ok_or(Error::MalformedSources)?
.insert("index", Item::ArrayOfTables(updated));

Ok(())
}

/// Adds a dependency to `project.optional-dependencies`.
///
/// Returns `true` if the dependency was added, `false` if it was updated.
Expand Down
10 changes: 9 additions & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use uv_configuration::{
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
use uv_distribution_types::{UnresolvedRequirement, VersionId};
use uv_distribution_types::{Index, UnresolvedRequirement, VersionId};
use uv_fs::Simplified;
use uv_git::{GitReference, GIT_STORE};
use uv_normalize::PackageName;
Expand Down Expand Up @@ -59,6 +59,7 @@ pub(crate) async fn add(
editable: Option<bool>,
dependency_type: DependencyType,
raw_sources: bool,
indexes: Vec<Index>,
rev: Option<String>,
tag: Option<String>,
branch: Option<String>,
Expand Down Expand Up @@ -487,6 +488,13 @@ pub(crate) async fn add(
});
}

// Add any indexes that were provided on the command-line.
if !raw_sources {
for index in indexes {
toml.add_index(&index)?;
}
}

let content = toml.to_string();

// Save the modified `pyproject.toml` or script.
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,7 @@ async fn run_project(
args.editable,
args.dependency_type,
args.raw_sources,
args.indexes,
args.rev,
args.tag,
args.branch,
Expand Down
47 changes: 47 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ pub(crate) struct AddSettings {
pub(crate) script: Option<PathBuf>,
pub(crate) python: Option<String>,
pub(crate) refresh: Refresh,
pub(crate) indexes: Vec<Index>,
pub(crate) settings: ResolverInstallerSettings,
}

Expand Down Expand Up @@ -847,6 +848,51 @@ impl AddSettings {
DependencyType::Production
};

// Track the `--index` and `--default-index` arguments from the command-line.
let indexes = installer
.index_args
.default_index
.clone()
.and_then(Maybe::into_option)
.into_iter()
.chain(
installer
.index_args
.index
.clone()
.into_iter()
.flatten()
.filter_map(Maybe::into_option),
)
.collect::<Vec<_>>();

// If the user passed an `--index-url` or `--extra-index-url`, warn.
if installer
.index_args
.index_url
.as_ref()
.is_some_and(Maybe::is_some)
{
if script.is_some() {
warn_user_once!("Indexes specified via `--index-url` will not be persisted to the script; use `--default-index` instead.");
} else {
warn_user_once!("Indexes specified via `--index-url` will not be persisted to the `pyproject.toml` file; use `--default-index` instead.");
}
}

if installer
.index_args
.extra_index_url
.as_ref()
.is_some_and(|extra_index_url| extra_index_url.iter().any(Maybe::is_some))
{
if script.is_some() {
warn_user_once!("Indexes specified via `--extra-index-url` will not be persisted to the script; use `--index` instead.");
} else {
warn_user_once!("Indexes specified via `--extra-index-url` will not be persisted to the `pyproject.toml` file; use `--index` instead.");
}
}

Self {
locked,
frozen,
Expand All @@ -864,6 +910,7 @@ impl AddSettings {
editable: flag(editable, no_editable),
extras: extra.unwrap_or_default(),
refresh: Refresh::from(refresh),
indexes,
settings: ResolverInstallerSettings::combine(
resolver_installer_options(installer, build),
filesystem,
Expand Down
Loading