mirror of https://github.com/astral-sh/uv
Include `uv export` command in output (#7374)
## Summary Updates the output of `uv export` to include the command that produced it, similar to how `uv pip compile` does. This addresses #7159 - I had this same itch today, figured it was a good time to dive in! ## Test Plan All the export unit tests were updated to test the new output format.
This commit is contained in:
parent
6907164841
commit
c1888364b5
|
|
@ -1,4 +1,7 @@
|
|||
use std::env;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use itertools::Itertools;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -131,8 +134,9 @@ pub(crate) async fn export(
|
|||
writeln!(
|
||||
writer,
|
||||
"{}",
|
||||
"# This file was autogenerated via `uv export`.".green()
|
||||
"# This file was autogenerated by uv via the following command:".green()
|
||||
)?;
|
||||
writeln!(writer, "{}", format!("# {}", cmd()).green())?;
|
||||
write!(writer, "{export}")?;
|
||||
}
|
||||
}
|
||||
|
|
@ -141,3 +145,54 @@ pub(crate) async fn export(
|
|||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// Format the uv command used to generate the output file.
|
||||
fn cmd() -> String {
|
||||
let args = env::args_os()
|
||||
.skip(1)
|
||||
.map(|arg| arg.to_string_lossy().to_string())
|
||||
.scan(None, move |skip_next, arg| {
|
||||
if matches!(skip_next, Some(true)) {
|
||||
// Reset state; skip this iteration.
|
||||
*skip_next = None;
|
||||
return Some(None);
|
||||
}
|
||||
|
||||
// Always skip the `--upgrade` flag.
|
||||
if arg == "--upgrade" || arg == "-U" {
|
||||
*skip_next = None;
|
||||
return Some(None);
|
||||
}
|
||||
|
||||
// Always skip the `--upgrade-package` and mark the next item to be skipped
|
||||
if arg == "--upgrade-package" || arg == "-P" {
|
||||
*skip_next = Some(true);
|
||||
return Some(None);
|
||||
}
|
||||
|
||||
// Skip only this argument if option and value are together
|
||||
if arg.starts_with("--upgrade-package=") || arg.starts_with("-P") {
|
||||
// Reset state; skip this iteration.
|
||||
*skip_next = None;
|
||||
return Some(None);
|
||||
}
|
||||
|
||||
// Always skip the `--quiet` flag.
|
||||
if arg == "--quiet" || arg == "-q" {
|
||||
*skip_next = None;
|
||||
return Some(None);
|
||||
}
|
||||
|
||||
// Always skip the `--verbose` flag.
|
||||
if arg == "--verbose" || arg == "-v" {
|
||||
*skip_next = None;
|
||||
return Some(None);
|
||||
}
|
||||
|
||||
// Return the argument.
|
||||
Some(Some(arg))
|
||||
})
|
||||
.flatten()
|
||||
.join(" ");
|
||||
format!("uv {args}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1030,6 +1030,18 @@ pub enum WindowsFilters {
|
|||
Universal,
|
||||
}
|
||||
|
||||
/// Helper method to apply filters to a string. Useful when `!uv_snapshot` cannot be used.
|
||||
pub fn apply_filters<T: AsRef<str>>(mut snapshot: String, filters: impl AsRef<[(T, T)]>) -> String {
|
||||
for (matcher, replacement) in filters.as_ref() {
|
||||
// TODO(konstin): Cache regex compilation
|
||||
let re = Regex::new(matcher.as_ref()).expect("Do you need to regex::escape your filter?");
|
||||
if re.is_match(&snapshot) {
|
||||
snapshot = re.replace_all(&snapshot, replacement.as_ref()).to_string();
|
||||
}
|
||||
}
|
||||
snapshot
|
||||
}
|
||||
|
||||
/// Execute the command and format its output status, stdout and stderr into a snapshot string.
|
||||
///
|
||||
/// This function is derived from `insta_cmd`s `spawn_with_info`.
|
||||
|
|
@ -1076,22 +1088,17 @@ pub fn run_and_format_with_status<T: AsRef<str>>(
|
|||
.output()
|
||||
.unwrap_or_else(|err| panic!("Failed to spawn {program}: {err}"));
|
||||
|
||||
let mut snapshot = format!(
|
||||
let mut snapshot = apply_filters(
|
||||
format!(
|
||||
"success: {:?}\nexit_code: {}\n----- stdout -----\n{}\n----- stderr -----\n{}",
|
||||
output.status.success(),
|
||||
output.status.code().unwrap_or(!0),
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
),
|
||||
filters,
|
||||
);
|
||||
|
||||
for (matcher, replacement) in filters.as_ref() {
|
||||
// TODO(konstin): Cache regex compilation
|
||||
let re = Regex::new(matcher.as_ref()).expect("Do you need to regex::escape your filter?");
|
||||
if re.is_match(&snapshot) {
|
||||
snapshot = re.replace_all(&snapshot, replacement.as_ref()).to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// This is a heuristic filter meant to try and make *most* of our tests
|
||||
// pass whether it's on Windows or Unix. In particular, there are some very
|
||||
// common Windows-only dependencies that, when removed from a resolution,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
use anyhow::Result;
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::prelude::*;
|
||||
use common::{uv_snapshot, TestContext};
|
||||
use common::{apply_filters, uv_snapshot, TestContext};
|
||||
use std::process::Stdio;
|
||||
|
||||
mod common;
|
||||
|
|
@ -34,7 +34,8 @@ fn dependency() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
|
|
@ -78,7 +79,8 @@ fn dependency_extra() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
blinker==1.7.0 \
|
||||
--hash=sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182 \
|
||||
|
|
@ -153,7 +155,8 @@ fn project_extra() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
typing-extensions==4.10.0 \
|
||||
--hash=sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb \
|
||||
|
|
@ -167,7 +170,8 @@ fn project_extra() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --extra pytest
|
||||
-e .
|
||||
iniconfig==2.0.0 \
|
||||
--hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
|
||||
|
|
@ -184,7 +188,8 @@ fn project_extra() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --all-extras
|
||||
-e .
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
|
|
@ -234,7 +239,8 @@ fn dependency_marker() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
anyio==4.3.0 ; sys_platform == 'darwin' \
|
||||
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 \
|
||||
|
|
@ -285,7 +291,8 @@ fn dependency_multiple_markers() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
attrs==23.2.0 ; sys_platform == 'win32' or python_full_version >= '3.12' \
|
||||
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
|
||||
|
|
@ -355,7 +362,8 @@ fn dependency_conflicting_markers() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
async-generator==1.10 ; sys_platform == 'win32' \
|
||||
--hash=sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144 \
|
||||
|
|
@ -441,7 +449,8 @@ fn non_root() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --package child
|
||||
-e child
|
||||
iniconfig==2.0.0 \
|
||||
--hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
|
||||
|
|
@ -507,9 +516,13 @@ fn relative_path() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Read the file contents.
|
||||
let contents = fs_err::read_to_string(project.child("requirements.txt")).unwrap();
|
||||
let contents = apply_filters(
|
||||
fs_err::read_to_string(project.child("requirements.txt")).unwrap(),
|
||||
context.filters(),
|
||||
);
|
||||
insta::assert_snapshot!(contents, @r###"
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
../dependency
|
||||
iniconfig==2.0.0 \
|
||||
|
|
@ -563,7 +576,8 @@ fn dev() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR]
|
||||
-e .
|
||||
anyio==4.3.0 \
|
||||
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 \
|
||||
|
|
@ -586,7 +600,8 @@ fn dev() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-dev
|
||||
-e .
|
||||
typing-extensions==4.10.0 \
|
||||
--hash=sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb \
|
||||
|
|
@ -624,7 +639,8 @@ fn no_hashes() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-hashes
|
||||
-e .
|
||||
anyio==3.7.0
|
||||
idna==3.6
|
||||
|
|
@ -662,7 +678,8 @@ fn output_file() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --output-file requirements.txt
|
||||
-e .
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
|
|
@ -678,9 +695,13 @@ fn output_file() -> Result<()> {
|
|||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
let contents = fs_err::read_to_string(context.temp_dir.child("requirements.txt"))?;
|
||||
let contents = apply_filters(
|
||||
fs_err::read_to_string(context.temp_dir.child("requirements.txt")).unwrap(),
|
||||
context.filters(),
|
||||
);
|
||||
insta::assert_snapshot!(contents, @r###"
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --output-file requirements.txt
|
||||
-e .
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
|
|
@ -743,7 +764,8 @@ fn no_emit() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-emit-package anyio
|
||||
-e .
|
||||
-e child
|
||||
idna==3.6 \
|
||||
|
|
@ -765,7 +787,8 @@ fn no_emit() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-emit-project
|
||||
-e child
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
|
|
@ -789,7 +812,8 @@ fn no_emit() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-emit-project --package child
|
||||
iniconfig==2.0.0 \
|
||||
--hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
|
||||
--hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
|
||||
|
|
@ -803,7 +827,8 @@ fn no_emit() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-emit-workspace
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
--hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0
|
||||
|
|
@ -842,7 +867,8 @@ fn no_emit() -> Result<()> {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated via `uv export`.
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-emit-workspace
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
--hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0
|
||||
|
|
|
|||
Loading…
Reference in New Issue