From 7939d3fb5bcb79c16b85327820993efdfc513bd0 Mon Sep 17 00:00:00 2001 From: Ahmed Ilyas Date: Wed, 4 Dec 2024 22:20:30 +0100 Subject: [PATCH] Create missing dir for `uv export`/ `uv pip compile` (#9648) ## Summary Closes #9643. I modified the `commit` fn so this applies to `uv compile --output-file` too. But I can move it to the export module if we want to restrict this to `uv export` only. ## Test Plan `cargo test` --- crates/uv/src/commands/mod.rs | 4 ++ crates/uv/tests/it/export.rs | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/crates/uv/src/commands/mod.rs b/crates/uv/src/commands/mod.rs index 2b875ce052dd..a3fb800b7249 100644 --- a/crates/uv/src/commands/mod.rs +++ b/crates/uv/src/commands/mod.rs @@ -231,6 +231,10 @@ impl<'a> OutputWriter<'a> { /// Commit the buffer to the output file. async fn commit(self) -> std::io::Result<()> { if let Some(output_file) = self.output_file { + if let Some(parent_dir) = output_file.parent() { + fs_err::create_dir_all(parent_dir)?; + } + // If the output file is an existing symlink, write to the destination instead. let output_file = fs_err::read_link(output_file) .map(Cow::Owned) diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index a5b6102a7247..73274923d270 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -761,6 +761,78 @@ fn frozen() -> Result<()> { Ok(()) } +#[test] +fn create_missing_dir() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.export() + .arg("--output-file") + .arg("requirements/requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv export --cache-dir [CACHE_DIR] --output-file requirements/requirements.txt + -e . + anyio==3.7.0 \ + --hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \ + --hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0 + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + + ----- stderr ----- + Resolved 4 packages in [TIME] + "###); + // + // Read the file contents. + let contents = apply_filters( + fs_err::read_to_string( + context + .temp_dir + .child("requirements") + .child("requirements.txt"), + ) + .unwrap(), + context.filters(), + ); + insta::assert_snapshot!(contents, @r###" + # This file was autogenerated by uv via the following command: + # uv export --cache-dir [CACHE_DIR] --output-file requirements/requirements.txt + -e . + anyio==3.7.0 \ + --hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \ + --hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0 + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + "###); + Ok(()) +} + #[test] fn non_project() -> Result<()> { let context = TestContext::new("3.12");