Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ 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() {
if !parent_dir.as_os_str().is_empty() && !parent_dir.is_dir() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be try_exists? What happens if the parent is a link to another directory? What happens if it's a file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, I've changed it to try_exists

fs_err::tokio::create_dir(parent_dir).await?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need create_dir_all?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just unequivocally call create_dir_all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to create_dir_all

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to skip try_exists as suggested by Charlie, it already has a behavior for existing directories and probably handles race conditions?

Also, I think you can skip the empty check

If the empty path is passed to this function, it always succeeds without creating any directories.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok done

}
}

// 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)
Expand Down
72 changes: 72 additions & 0 deletions crates/uv/tests/it/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down