mirror of https://github.com/astral-sh/uv
Add a `uv auth helper --protocol bazel` command (#16886)
This commit is contained in:
parent
ee6e3be815
commit
0c5391a7c7
|
|
@ -4934,6 +4934,14 @@ pub enum AuthCommand {
|
|||
/// Credentials are only stored in this directory when the plaintext backend is used, as
|
||||
/// opposed to the native backend, which uses the system keyring.
|
||||
Dir(AuthDirArgs),
|
||||
/// Act as a credential helper for external tools.
|
||||
///
|
||||
/// Implements the Bazel credential helper protocol to provide credentials
|
||||
/// to external tools via JSON over stdin/stdout.
|
||||
///
|
||||
/// This command is typically invoked by external tools.
|
||||
#[command(hide = true)]
|
||||
Helper(AuthHelperArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -6215,6 +6223,30 @@ pub struct AuthDirArgs {
|
|||
pub service: Option<Service>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct AuthHelperArgs {
|
||||
#[command(subcommand)]
|
||||
pub command: AuthHelperCommand,
|
||||
|
||||
/// The credential helper protocol to use
|
||||
#[arg(long, value_enum, required = true)]
|
||||
pub protocol: AuthHelperProtocol,
|
||||
}
|
||||
|
||||
/// Credential helper protocols supported by uv
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, clap::ValueEnum)]
|
||||
pub enum AuthHelperProtocol {
|
||||
/// Bazel credential helper protocol as described in [the
|
||||
/// spec](https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md)
|
||||
Bazel,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum AuthHelperCommand {
|
||||
/// Retrieve credentials for a URI
|
||||
Get,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct GenerateShellCompletionArgs {
|
||||
/// The shell to generate the completion script for
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ bitflags::bitflags! {
|
|||
const WORKSPACE_DIR = 1 << 14;
|
||||
const WORKSPACE_LIST = 1 << 15;
|
||||
const SBOM_EXPORT = 1 << 16;
|
||||
const AUTH_HELPER = 1 << 17;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ impl PreviewFeatures {
|
|||
Self::WORKSPACE_DIR => "workspace-dir",
|
||||
Self::WORKSPACE_LIST => "workspace-list",
|
||||
Self::SBOM_EXPORT => "sbom-export",
|
||||
Self::AUTH_HELPER => "auth-helper",
|
||||
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +108,7 @@ impl FromStr for PreviewFeatures {
|
|||
"workspace-dir" => Self::WORKSPACE_DIR,
|
||||
"workspace-list" => Self::WORKSPACE_LIST,
|
||||
"sbom-export" => Self::SBOM_EXPORT,
|
||||
"auth-helper" => Self::AUTH_HELPER,
|
||||
_ => {
|
||||
warn_user_once!("Unknown preview feature: `{part}`");
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
use uv_auth::{AuthBackend, Credentials, PyxTokenStore};
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_preview::{Preview, PreviewFeatures};
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::{commands::ExitStatus, printer::Printer, settings::NetworkSettings};
|
||||
|
||||
/// Request format for the Bazel credential helper protocol.
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct BazelCredentialRequest {
|
||||
uri: DisplaySafeUrl,
|
||||
}
|
||||
|
||||
impl BazelCredentialRequest {
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
serde_json::from_str(s).context("Failed to parse credential request as JSON")
|
||||
}
|
||||
|
||||
fn from_stdin() -> Result<Self> {
|
||||
let mut buffer = String::new();
|
||||
std::io::stdin()
|
||||
.read_to_string(&mut buffer)
|
||||
.context("Failed to read from stdin")?;
|
||||
|
||||
Self::from_str(&buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Response format for the Bazel credential helper protocol.
|
||||
#[derive(Debug, Serialize, Default)]
|
||||
struct BazelCredentialResponse {
|
||||
headers: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
impl TryFrom<Credentials> for BazelCredentialResponse {
|
||||
fn try_from(creds: Credentials) -> Result<Self> {
|
||||
let header_str = creds
|
||||
.to_header_value()
|
||||
.to_str()
|
||||
// TODO: this is infallible in practice
|
||||
.context("Failed to convert header value to string")?
|
||||
.to_owned();
|
||||
|
||||
Ok(Self {
|
||||
headers: HashMap::from([("Authorization".to_owned(), vec![header_str])]),
|
||||
})
|
||||
}
|
||||
|
||||
type Error = anyhow::Error;
|
||||
}
|
||||
|
||||
async fn credentials_for_url(
|
||||
url: &DisplaySafeUrl,
|
||||
preview: Preview,
|
||||
network_settings: &NetworkSettings,
|
||||
) -> Result<Option<Credentials>> {
|
||||
let pyx_store = PyxTokenStore::from_settings()?;
|
||||
|
||||
// Use only the username from the URL, if present - discarding the password
|
||||
let url_credentials = Credentials::from_url(url);
|
||||
let username = url_credentials.as_ref().and_then(|c| c.username());
|
||||
if url_credentials
|
||||
.as_ref()
|
||||
.map(|c| c.password().is_some())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
debug!("URL '{url}' contain a password; ignoring");
|
||||
}
|
||||
|
||||
if pyx_store.is_known_domain(url) {
|
||||
if username.is_some() {
|
||||
bail!(
|
||||
"Cannot specify a username for URLs under {}",
|
||||
url.host()
|
||||
.map(|host| host.to_string())
|
||||
.unwrap_or(url.to_string())
|
||||
);
|
||||
}
|
||||
let client = BaseClientBuilder::new(
|
||||
network_settings.connectivity,
|
||||
network_settings.native_tls,
|
||||
network_settings.allow_insecure_host.clone(),
|
||||
preview,
|
||||
network_settings.timeout,
|
||||
network_settings.retries,
|
||||
)
|
||||
.auth_integration(uv_client::AuthIntegration::NoAuthMiddleware)
|
||||
.build();
|
||||
let token = pyx_store
|
||||
.access_token(client.for_host(pyx_store.api()).raw_client(), 0)
|
||||
.await
|
||||
.context("Authentication failure")?
|
||||
.context("No access token found")?;
|
||||
return Ok(Some(Credentials::bearer(token.into_bytes())));
|
||||
}
|
||||
let backend = AuthBackend::from_settings(preview).await?;
|
||||
let credentials = match &backend {
|
||||
AuthBackend::System(provider) => provider.fetch(url, username).await,
|
||||
AuthBackend::TextStore(store, _lock) => store.get_credentials(url, username).cloned(),
|
||||
};
|
||||
Ok(credentials)
|
||||
}
|
||||
|
||||
/// Implement the Bazel credential helper protocol.
|
||||
///
|
||||
/// Reads a JSON request from stdin containing a URI, looks up credentials
|
||||
/// for that URI using uv's authentication backends, and writes a JSON response
|
||||
/// to stdout containing HTTP headers (if credentials are found).
|
||||
///
|
||||
/// Protocol specification TLDR:
|
||||
/// - Input (stdin): `{"uri": "https://example.com/path"}`
|
||||
/// - Output (stdout): `{"headers": {"Authorization": ["Basic ..."]}}` or `{"headers": {}}`
|
||||
/// - Errors: Written to stderr with non-zero exit code
|
||||
///
|
||||
/// Full spec is [available here](https://github.com/bazelbuild/proposals/blob/main/designs/2022-06-07-bazel-credential-helpers.md)
|
||||
pub(crate) async fn helper(
|
||||
preview: Preview,
|
||||
network_settings: &NetworkSettings,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
if !preview.is_enabled(PreviewFeatures::AUTH_HELPER) {
|
||||
warn_user!(
|
||||
"The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features {}` to disable this warning",
|
||||
PreviewFeatures::AUTH_HELPER
|
||||
);
|
||||
}
|
||||
|
||||
let request = BazelCredentialRequest::from_stdin()?;
|
||||
|
||||
// TODO: make this logic generic over the protocol by providing `request.uri` from a
|
||||
// trait - that should help with adding new protocols
|
||||
let credentials = credentials_for_url(&request.uri, preview, network_settings).await?;
|
||||
|
||||
let response = serde_json::to_string(
|
||||
&credentials
|
||||
.map(BazelCredentialResponse::try_from)
|
||||
.unwrap_or_else(|| Ok(BazelCredentialResponse::default()))?,
|
||||
)
|
||||
.context("Failed to serialize response as JSON")?;
|
||||
writeln!(printer.stdout_important(), "{response}")?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
pub(crate) mod dir;
|
||||
pub(crate) mod helper;
|
||||
pub(crate) mod login;
|
||||
pub(crate) mod logout;
|
||||
pub(crate) mod token;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use owo_colors::OwoColorize;
|
|||
use tracing::debug;
|
||||
|
||||
pub(crate) use auth::dir::dir as auth_dir;
|
||||
pub(crate) use auth::helper::helper as auth_helper;
|
||||
pub(crate) use auth::login::login as auth_login;
|
||||
pub(crate) use auth::logout::logout as auth_logout;
|
||||
pub(crate) use auth::token::token as auth_token;
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ use uv_cache_info::Timestamp;
|
|||
#[cfg(feature = "self-update")]
|
||||
use uv_cli::SelfUpdateArgs;
|
||||
use uv_cli::{
|
||||
AuthCommand, AuthNamespace, BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands,
|
||||
PipCommand, PipNamespace, ProjectCommand, PythonCommand, PythonNamespace, SelfCommand,
|
||||
SelfNamespace, ToolCommand, ToolNamespace, TopLevelArgs, WorkspaceCommand, WorkspaceNamespace,
|
||||
compat::CompatArgs,
|
||||
AuthCommand, AuthHelperCommand, AuthNamespace, BuildBackendCommand, CacheCommand,
|
||||
CacheNamespace, Cli, Commands, PipCommand, PipNamespace, ProjectCommand, PythonCommand,
|
||||
PythonNamespace, SelfCommand, SelfNamespace, ToolCommand, ToolNamespace, TopLevelArgs,
|
||||
WorkspaceCommand, WorkspaceNamespace, compat::CompatArgs,
|
||||
};
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::min_stack_size;
|
||||
|
|
@ -546,6 +546,22 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
commands::auth_dir(args.service.as_ref(), printer)?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Commands::Auth(AuthNamespace {
|
||||
command: AuthCommand::Helper(args),
|
||||
}) => {
|
||||
use uv_cli::AuthHelperProtocol;
|
||||
|
||||
// Validate protocol (currently only Bazel is supported)
|
||||
match args.protocol {
|
||||
AuthHelperProtocol::Bazel => {}
|
||||
}
|
||||
|
||||
match args.command {
|
||||
AuthHelperCommand::Get => {
|
||||
commands::auth_helper(globals.preview, &globals.network_settings, printer).await
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::Help(args) => commands::help(
|
||||
args.command.unwrap_or_default().as_slice(),
|
||||
printer,
|
||||
|
|
|
|||
|
|
@ -1925,3 +1925,205 @@ fn native_auth_host_fallback() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test credential helper with basic auth credentials
|
||||
#[test]
|
||||
fn bazel_helper_basic_auth() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Store credentials
|
||||
uv_snapshot!(context.filters(), context.auth_login()
|
||||
.arg("https://test.example.com")
|
||||
.arg("--username").arg("testuser")
|
||||
.arg("--password").arg("testpass"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Stored credentials for testuser@https://test.example.com/
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.auth_helper()
|
||||
.arg("--protocol=bazel")
|
||||
.arg("get"),
|
||||
input=r#"{"uri":"https://test.example.com/path"}"#,
|
||||
@r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{"headers":{"Authorization":["Basic dGVzdHVzZXI6dGVzdHBhc3M="]}}
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features auth-helper` to disable this warning
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
/// Test credential helper with token credentials
|
||||
#[test]
|
||||
fn bazel_helper_token() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Store token
|
||||
uv_snapshot!(context.filters(), context.auth_login()
|
||||
.arg("https://api.example.com")
|
||||
.arg("--token").arg("mytoken123"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Stored credentials for https://api.example.com/
|
||||
"###);
|
||||
|
||||
// Test credential helper - tokens are stored as Basic auth with __token__ username
|
||||
uv_snapshot!(context.filters(), context.auth_helper()
|
||||
.arg("--protocol=bazel")
|
||||
.arg("get"),
|
||||
input=r#"{"uri":"https://api.example.com/v1/endpoint"}"#,
|
||||
@r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{"headers":{"Authorization":["Basic X190b2tlbl9fOm15dG9rZW4xMjM="]}}
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features auth-helper` to disable this warning
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
/// Test credential helper with no credentials found
|
||||
#[test]
|
||||
fn bazel_helper_no_credentials() {
|
||||
let context = TestContext::new("3.12");
|
||||
uv_snapshot!(context.filters(), context.auth_helper()
|
||||
.arg("--protocol=bazel")
|
||||
.arg("get"),
|
||||
input=r#"{"uri":"https://unknown.example.com/path"}"#,
|
||||
@r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{"headers":{}}
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features auth-helper` to disable this warning
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
/// Test credential helper with invalid JSON input
|
||||
#[test]
|
||||
fn bazel_helper_invalid_json() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.auth_helper()
|
||||
.arg("--protocol=bazel")
|
||||
.arg("get"),
|
||||
input="not json",
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features auth-helper` to disable this warning
|
||||
error: Failed to parse credential request as JSON
|
||||
Caused by: expected ident at line 1 column 2
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test credential helper with invalid URI
|
||||
#[test]
|
||||
fn bazel_helper_invalid_uri() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.auth_helper()
|
||||
.arg("--protocol=bazel")
|
||||
.arg("get"),
|
||||
input=r#"{"uri":"not a url"}"#,
|
||||
@r#"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features auth-helper` to disable this warning
|
||||
error: Failed to parse credential request as JSON
|
||||
Caused by: relative URL without a base: "not a url" at line 1 column 18
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
/// Test credential helper with username in URI
|
||||
#[test]
|
||||
fn bazel_helper_username_in_uri() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Store credentials with specific username
|
||||
uv_snapshot!(context.filters(), context.auth_login()
|
||||
.arg("https://test.example.com")
|
||||
.arg("--username").arg("specificuser")
|
||||
.arg("--password").arg("specificpass"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Stored credentials for specificuser@https://test.example.com/
|
||||
"###);
|
||||
|
||||
// Test with username in URI
|
||||
uv_snapshot!(context.filters(), context.auth_helper()
|
||||
.arg("--protocol=bazel")
|
||||
.arg("get"),
|
||||
input=r#"{"uri":"https://specificuser@test.example.com/path"}"#,
|
||||
@r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{"headers":{"Authorization":["Basic c3BlY2lmaWN1c2VyOnNwZWNpZmljcGFzcw=="]}}
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features auth-helper` to disable this warning
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
/// Test credential helper with unknown username in URI
|
||||
#[test]
|
||||
fn bazel_helper_unknown_username_in_uri() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Store credentials with specific username
|
||||
uv_snapshot!(context.filters(), context.auth_login()
|
||||
.arg("https://test.example.com")
|
||||
.arg("--username").arg("specificuser")
|
||||
.arg("--password").arg("specificpass"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Stored credentials for specificuser@https://test.example.com/
|
||||
"###);
|
||||
|
||||
// Test with username in URI
|
||||
uv_snapshot!(context.filters(), context.auth_helper()
|
||||
.arg("--protocol=bazel")
|
||||
.arg("get"),
|
||||
input=r#"{"uri":"https://differentuser@test.example.com/path"}"#,
|
||||
@r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{"headers":{}}
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv auth helper` command is experimental and may change without warning. Pass `--preview-features auth-helper` to disable this warning
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
|
||||
use std::borrow::BorrowMut;
|
||||
use std::ffi::OsString;
|
||||
use std::io::Write as _;
|
||||
use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitStatus, Output};
|
||||
use std::process::{Command, ExitStatus, Output, Stdio};
|
||||
use std::str::FromStr;
|
||||
use std::{env, io};
|
||||
use uv_python::downloads::ManagedPythonDownloadList;
|
||||
|
|
@ -1457,6 +1458,14 @@ impl TestContext {
|
|||
command
|
||||
}
|
||||
|
||||
/// Create a `uv auth helper --protocol bazel get` command.
|
||||
pub fn auth_helper(&self) -> Command {
|
||||
let mut command = Self::new_command();
|
||||
command.arg("auth").arg("helper");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
}
|
||||
|
||||
/// Create a `uv auth token` command.
|
||||
pub fn auth_token(&self) -> Command {
|
||||
let mut command = Self::new_command();
|
||||
|
|
@ -1656,6 +1665,7 @@ impl TestContext {
|
|||
self.filters(),
|
||||
"diff_lock",
|
||||
Some(WindowsFilters::Platform),
|
||||
None,
|
||||
);
|
||||
assert!(status.success(), "{snapshot}");
|
||||
let new_lock = fs_err::read_to_string(&lock_path).unwrap();
|
||||
|
|
@ -1839,9 +1849,10 @@ pub fn run_and_format<T: AsRef<str>>(
|
|||
filters: impl AsRef<[(T, T)]>,
|
||||
function_name: &str,
|
||||
windows_filters: Option<WindowsFilters>,
|
||||
input: Option<&str>,
|
||||
) -> (String, Output) {
|
||||
let (snapshot, output, _) =
|
||||
run_and_format_with_status(command, filters, function_name, windows_filters);
|
||||
run_and_format_with_status(command, filters, function_name, windows_filters, input);
|
||||
(snapshot, output)
|
||||
}
|
||||
|
||||
|
|
@ -1854,6 +1865,7 @@ pub fn run_and_format_with_status<T: AsRef<str>>(
|
|||
filters: impl AsRef<[(T, T)]>,
|
||||
function_name: &str,
|
||||
windows_filters: Option<WindowsFilters>,
|
||||
input: Option<&str>,
|
||||
) -> (String, Output, ExitStatus) {
|
||||
let program = command
|
||||
.borrow_mut()
|
||||
|
|
@ -1873,10 +1885,30 @@ pub fn run_and_format_with_status<T: AsRef<str>>(
|
|||
);
|
||||
}
|
||||
|
||||
let output = command
|
||||
.borrow_mut()
|
||||
.output()
|
||||
.unwrap_or_else(|err| panic!("Failed to spawn {program}: {err}"));
|
||||
let output = if let Some(input) = input {
|
||||
let mut child = command
|
||||
.borrow_mut()
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("Failed to spawn {program}: {err}"));
|
||||
child
|
||||
.stdin
|
||||
.as_mut()
|
||||
.expect("Failed to open stdin")
|
||||
.write_all(input.as_bytes())
|
||||
.expect("Failed to write to stdin");
|
||||
|
||||
child
|
||||
.wait_with_output()
|
||||
.unwrap_or_else(|err| panic!("Failed to read output from {program}: {err}"))
|
||||
} else {
|
||||
command
|
||||
.borrow_mut()
|
||||
.output()
|
||||
.unwrap_or_else(|err| panic!("Failed to spawn {program}: {err}"))
|
||||
};
|
||||
|
||||
eprintln!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Unfiltered output ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
eprintln!(
|
||||
|
|
@ -2075,19 +2107,25 @@ macro_rules! uv_snapshot {
|
|||
}};
|
||||
($filters:expr, $spawnable:expr, @$snapshot:literal) => {{
|
||||
// Take a reference for backwards compatibility with the vec-expecting insta filters.
|
||||
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, $crate::function_name!(), Some($crate::common::WindowsFilters::Platform));
|
||||
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, $crate::function_name!(), Some($crate::common::WindowsFilters::Platform), None);
|
||||
::insta::assert_snapshot!(snapshot, @$snapshot);
|
||||
output
|
||||
}};
|
||||
($filters:expr, $spawnable:expr, input=$input:expr, @$snapshot:literal) => {{
|
||||
// Take a reference for backwards compatibility with the vec-expecting insta filters.
|
||||
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, $crate::function_name!(), Some($crate::common::WindowsFilters::Platform), Some($input));
|
||||
::insta::assert_snapshot!(snapshot, @$snapshot);
|
||||
output
|
||||
}};
|
||||
($filters:expr, windows_filters=false, $spawnable:expr, @$snapshot:literal) => {{
|
||||
// Take a reference for backwards compatibility with the vec-expecting insta filters.
|
||||
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, $crate::function_name!(), None);
|
||||
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, $crate::function_name!(), None, None);
|
||||
::insta::assert_snapshot!(snapshot, @$snapshot);
|
||||
output
|
||||
}};
|
||||
($filters:expr, universal_windows_filters=true, $spawnable:expr, @$snapshot:literal) => {{
|
||||
// Take a reference for backwards compatibility with the vec-expecting insta filters.
|
||||
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, $crate::function_name!(), Some($crate::common::WindowsFilters::Universal));
|
||||
let (snapshot, output) = $crate::common::run_and_format($spawnable, &$filters, $crate::function_name!(), Some($crate::common::WindowsFilters::Universal), None);
|
||||
::insta::assert_snapshot!(snapshot, @$snapshot);
|
||||
output
|
||||
}};
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ fn lock_ecosystem_package(python_version: &str, name: &str) -> Result<()> {
|
|||
context.filters(),
|
||||
name,
|
||||
Some(common::WindowsFilters::Platform),
|
||||
None,
|
||||
);
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
|
|
|||
|
|
@ -7832,7 +7832,7 @@ fn preview_features() {
|
|||
show_settings: true,
|
||||
preview: Preview {
|
||||
flags: PreviewFeatures(
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR | WORKSPACE_LIST | SBOM_EXPORT,
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR | WORKSPACE_LIST | SBOM_EXPORT | AUTH_HELPER,
|
||||
),
|
||||
},
|
||||
python_preference: Managed,
|
||||
|
|
@ -8060,7 +8060,7 @@ fn preview_features() {
|
|||
show_settings: true,
|
||||
preview: Preview {
|
||||
flags: PreviewFeatures(
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR | WORKSPACE_LIST | SBOM_EXPORT,
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR | WORKSPACE_LIST | SBOM_EXPORT | AUTH_HELPER,
|
||||
),
|
||||
},
|
||||
python_preference: Managed,
|
||||
|
|
|
|||
Loading…
Reference in New Issue