Skip to content

Commit 3a7db17

Browse files
authored
Build backend: Add source tree -> source dist -> wheel tests (#9091)
A first milestone: source tree -> source dist -> wheel -> install works. This PR adds a test for this. There's obviously a lot still missing, including basics such as the Readme inclusion.
1 parent 9a20f8c commit 3a7db17

File tree

21 files changed

+325
-69
lines changed

21 files changed

+325
-69
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-build-backend/src/lib.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -513,16 +513,15 @@ pub fn build_source_dist(
513513
let relative = entry
514514
.path()
515515
.strip_prefix(source_tree)
516-
.expect("walkdir starts with root")
517-
.to_path_buf();
516+
.expect("walkdir starts with root");
518517

519518
// Fast path: Don't descend into a directory that can't be included. This is the most
520519
// important performance optimization, it avoids descending into directories such as
521520
// `.venv`. While walkdir is generally cheap, we still avoid traversing large data
522521
// directories that often exist on the top level of a project. This is especially noticeable
523522
// on network file systems with high latencies per operation (while contiguous reading may
524523
// still be fast).
525-
include_matcher.match_directory(&relative) && !exclude_matcher.is_match(&relative)
524+
include_matcher.match_directory(relative) && !exclude_matcher.is_match(relative)
526525
}) {
527526
let entry = entry.map_err(|err| Error::WalkDir {
528527
root: source_tree.to_path_buf(),
@@ -532,15 +531,14 @@ pub fn build_source_dist(
532531
let relative = entry
533532
.path()
534533
.strip_prefix(source_tree)
535-
.expect("walkdir starts with root")
536-
.to_path_buf();
534+
.expect("walkdir starts with root");
537535

538-
if !include_matcher.match_path(&relative) || exclude_matcher.is_match(&relative) {
536+
if !include_matcher.match_path(relative) || exclude_matcher.is_match(relative) {
539537
trace!("Excluding {}", relative.user_display());
540538
continue;
541539
};
542540

543-
add_source_dist_entry(&mut tar, &entry, &top_level, &source_dist_path, &relative)?;
541+
add_source_dist_entry(&mut tar, &entry, &top_level, &source_dist_path, relative)?;
544542
}
545543

546544
tar.finish()

crates/uv-build-backend/src/tests.rs

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use super::*;
2+
use indoc::indoc;
23
use insta::assert_snapshot;
34
use std::str::FromStr;
45
use tempfile::TempDir;
6+
use uv_fs::copy_dir_all;
57
use uv_normalize::PackageName;
68
use uv_pep440::Version;
79

@@ -28,26 +30,34 @@ fn test_wheel() {
2830
#[test]
2931
fn test_record() {
3032
let record = vec![RecordEntry {
31-
path: "uv_backend/__init__.py".to_string(),
33+
path: "built_by_uv/__init__.py".to_string(),
3234
hash: "89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865".to_string(),
3335
size: 37,
3436
}];
3537

3638
let mut writer = Vec::new();
37-
write_record(&mut writer, "uv_backend-0.1.0", record).unwrap();
39+
write_record(&mut writer, "built_by_uv-0.1.0", record).unwrap();
3840
assert_snapshot!(String::from_utf8(writer).unwrap(), @r"
39-
uv_backend/__init__.py,sha256=89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865,37
40-
uv_backend-0.1.0/RECORD,,
41+
built_by_uv/__init__.py,sha256=89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865,37
42+
built_by_uv-0.1.0/RECORD,,
4143
");
4244
}
4345

4446
/// Check that we write deterministic wheels.
4547
#[test]
4648
fn test_determinism() {
49+
let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
50+
let src = TempDir::new().unwrap();
51+
for dir in ["src", "tests", "data-dir"] {
52+
copy_dir_all(built_by_uv.join(dir), src.path().join(dir)).unwrap();
53+
}
54+
for dir in ["pyproject.toml", "README.md", "uv.lock"] {
55+
fs_err::copy(built_by_uv.join(dir), src.path().join(dir)).unwrap();
56+
}
57+
4758
let temp1 = TempDir::new().unwrap();
48-
let uv_backend = Path::new("../../scripts/packages/uv_backend");
4959
build_wheel(
50-
uv_backend,
60+
src.path(),
5161
temp1.path(),
5262
None,
5363
WheelSettings::default(),
@@ -57,22 +67,26 @@ fn test_determinism() {
5767

5868
// Touch the file to check that we don't serialize the last modified date.
5969
fs_err::write(
60-
uv_backend.join("src/uv_backend/__init__.py"),
61-
"def greet():\n print(\"Hello 👋\")\n",
70+
src.path().join("src/built_by_uv/__init__.py"),
71+
indoc! {r#"
72+
def greet() -> str:
73+
return "Hello 👋"
74+
"#
75+
},
6276
)
6377
.unwrap();
6478

6579
let temp2 = TempDir::new().unwrap();
6680
build_wheel(
67-
uv_backend,
81+
src.path(),
6882
temp2.path(),
6983
None,
7084
WheelSettings::default(),
7185
"1.0.0+test",
7286
)
7387
.unwrap();
7488

75-
let wheel_filename = "uv_backend-0.1.0-py3-none-any.whl";
89+
let wheel_filename = "built_by_uv-0.1.0-py3-none-any.whl";
7690
assert_eq!(
7791
fs_err::read(temp1.path().join(wheel_filename)).unwrap(),
7892
fs_err::read(temp2.path().join(wheel_filename)).unwrap()
@@ -83,8 +97,8 @@ fn test_determinism() {
8397
#[test]
8498
fn test_prepare_metadata() {
8599
let metadata_dir = TempDir::new().unwrap();
86-
let uv_backend = Path::new("../../scripts/packages/uv_backend");
87-
metadata(uv_backend, metadata_dir.path(), "1.0.0+test").unwrap();
100+
let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
101+
metadata(built_by_uv, metadata_dir.path(), "1.0.0+test").unwrap();
88102

89103
let mut files: Vec<_> = WalkDir::new(metadata_dir.path())
90104
.into_iter()
@@ -101,38 +115,36 @@ fn test_prepare_metadata() {
101115
.collect();
102116
files.sort();
103117
assert_snapshot!(files.join("\n"), @r"
104-
uv_backend-0.1.0.dist-info
105-
uv_backend-0.1.0.dist-info/METADATA
106-
uv_backend-0.1.0.dist-info/RECORD
107-
uv_backend-0.1.0.dist-info/WHEEL
118+
built_by_uv-0.1.0.dist-info
119+
built_by_uv-0.1.0.dist-info/METADATA
120+
built_by_uv-0.1.0.dist-info/RECORD
121+
built_by_uv-0.1.0.dist-info/WHEEL
108122
");
109123

110124
let metadata_file = metadata_dir
111125
.path()
112-
.join("uv_backend-0.1.0.dist-info/METADATA");
126+
.join("built_by_uv-0.1.0.dist-info/METADATA");
113127
assert_snapshot!(fs_err::read_to_string(metadata_file).unwrap(), @r###"
114-
Metadata-Version: 2.3
115-
Name: uv-backend
116-
Version: 0.1.0
117-
Summary: Add your description here
118-
Requires-Python: >=3.12
119-
Description-Content-Type: text/markdown
120-
121-
# uv_backend
122-
123-
A simple package to be built with the uv build backend.
124-
"###);
128+
Metadata-Version: 2.3
129+
Name: built-by-uv
130+
Version: 0.1.0
131+
Summary: A package to be built with the uv build backend that uses all features exposed by the build backend
132+
Requires-Dist: anyio>=4,<5
133+
Requires-Python: >=3.12
134+
"###);
125135

126136
let record_file = metadata_dir
127137
.path()
128-
.join("uv_backend-0.1.0.dist-info/RECORD");
138+
.join("built_by_uv-0.1.0.dist-info/RECORD");
129139
assert_snapshot!(fs_err::read_to_string(record_file).unwrap(), @r###"
130-
uv_backend-0.1.0.dist-info/WHEEL,sha256=3da1bfa0e8fd1b6cc246aa0b2b44a35815596c600cb485c39a6f8c106c3d5a8d,83
131-
uv_backend-0.1.0.dist-info/METADATA,sha256=e4a0d390317d7182f65ea978254c71ed283e0a4242150cf1c99a694b113ff68d,224
132-
uv_backend-0.1.0.dist-info/RECORD,,
140+
built_by_uv-0.1.0.dist-info/WHEEL,sha256=3da1bfa0e8fd1b6cc246aa0b2b44a35815596c600cb485c39a6f8c106c3d5a8d,83
141+
built_by_uv-0.1.0.dist-info/METADATA,sha256=ec36b5ae8830bdd248e90aaf581483ffb057f9a2d0f41e19e585531e7d07c9dc,215
142+
built_by_uv-0.1.0.dist-info/RECORD,,
133143
"###);
134144

135-
let wheel_file = metadata_dir.path().join("uv_backend-0.1.0.dist-info/WHEEL");
145+
let wheel_file = metadata_dir
146+
.path()
147+
.join("built_by_uv-0.1.0.dist-info/WHEEL");
136148
assert_snapshot!(fs_err::read_to_string(wheel_file).unwrap(), @r###"
137149
Wheel-Version: 1.0
138150
Generator: uv 1.0.0+test

crates/uv-globfilter/src/glob_dir_filter.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,20 +238,18 @@ mod tests {
238238
let relative = entry
239239
.path()
240240
.strip_prefix(walkdir_root)
241-
.expect("walkdir starts with root")
242-
.to_path_buf();
241+
.expect("walkdir starts with root");
243242

244-
include_matcher.match_directory(&relative)
243+
include_matcher.match_directory(relative)
245244
})
246245
.filter_map(|entry| {
247246
let entry = entry.as_ref().unwrap();
248247
// TODO(konsti): This should be prettier.
249248
let relative = entry
250249
.path()
251250
.strip_prefix(walkdir_root)
252-
.expect("walkdir starts with root")
253-
.to_path_buf();
254-
if include_matcher.match_path(&relative) {
251+
.expect("walkdir starts with root");
252+
if include_matcher.match_path(relative) {
255253
// Translate windows paths back to the unix fixture
256254
Some(relative.to_str().unwrap().replace(MAIN_SEPARATOR, "/"))
257255
} else {

crates/uv/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,15 @@ base64 = { version = "0.22.1" }
105105
byteorder = { version = "1.5.0" }
106106
etcetera = { workspace = true }
107107
filetime = { version = "0.2.25" }
108+
flate2 = { workspace = true }
108109
ignore = { version = "0.4.23" }
109110
indoc = { version = "2.0.5" }
110111
insta = { version = "1.40.0", features = ["filters", "json"] }
111112
predicates = { version = "3.1.2" }
112113
regex = { workspace = true }
113114
reqwest = { workspace = true, features = ["blocking"], default-features = false }
114115
similar = { version = "2.6.0" }
116+
tar = { workspace = true }
115117
tempfile = { workspace = true }
116118
zip = { workspace = true }
117119

crates/uv/tests/it/build_backend.rs

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,141 @@
11
use crate::common::{uv_snapshot, TestContext};
22
use anyhow::Result;
33
use assert_cmd::assert::OutputAssertExt;
4+
use flate2::bufread::GzDecoder;
5+
use fs_err::File;
6+
use indoc::indoc;
47
use std::env;
8+
use std::io::BufReader;
59
use std::path::Path;
610
use tempfile::TempDir;
711
use uv_static::EnvVars;
812

13+
const BUILT_BY_UV_TEST_SCRIPT: &str = indoc! {r#"
14+
from built_by_uv import greet
15+
from built_by_uv.arithmetic.circle import area
16+
17+
print(greet())
18+
print(f"Area of a circle with r=2: {area(2)}")
19+
"#};
20+
921
/// Test that build backend works if we invoke it directly.
1022
///
1123
/// We can't test end-to-end here including the PEP 517 bridge code since we don't have a uv wheel.
1224
#[test]
13-
fn uv_backend_direct() -> Result<()> {
25+
fn built_by_uv_direct_wheel() -> Result<()> {
1426
let context = TestContext::new("3.12");
15-
let uv_backend = Path::new("../../scripts/packages/uv_backend");
27+
let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
1628

1729
let temp_dir = TempDir::new()?;
1830

1931
uv_snapshot!(context
2032
.build_backend()
2133
.arg("build-wheel")
2234
.arg(temp_dir.path())
23-
.current_dir(uv_backend), @r###"
35+
.current_dir(built_by_uv), @r###"
36+
success: true
37+
exit_code: 0
38+
----- stdout -----
39+
built_by_uv-0.1.0-py3-none-any.whl
40+
41+
----- stderr -----
42+
"###);
43+
44+
context
45+
.pip_install()
46+
.arg(temp_dir.path().join("built_by_uv-0.1.0-py3-none-any.whl"))
47+
.assert()
48+
.success();
49+
50+
uv_snapshot!(context
51+
.run()
52+
.arg("python")
53+
.arg("-c")
54+
.arg(BUILT_BY_UV_TEST_SCRIPT)
55+
// Python on windows
56+
.env(EnvVars::PYTHONUTF8, "1"), @r###"
57+
success: true
58+
exit_code: 0
59+
----- stdout -----
60+
Hello 👋
61+
Area of a circle with r=2: 12.56636
62+
63+
----- stderr -----
64+
"###);
65+
66+
Ok(())
67+
}
68+
69+
/// Test that source tree -> source dist -> wheel works.
70+
///
71+
/// We can't test end-to-end here including the PEP 517 bridge code since we don't have a uv wheel,
72+
/// so we call the build backend directly.
73+
#[test]
74+
fn built_by_uv_direct() -> Result<()> {
75+
let context = TestContext::new("3.12");
76+
let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
77+
78+
let sdist_dir = TempDir::new()?;
79+
80+
uv_snapshot!(context
81+
.build_backend()
82+
.arg("build-sdist")
83+
.arg(sdist_dir.path())
84+
.current_dir(built_by_uv), @r###"
2485
success: true
2586
exit_code: 0
2687
----- stdout -----
27-
uv_backend-0.1.0-py3-none-any.whl
88+
built_by_uv-0.1.0.tar.gz
2889
2990
----- stderr -----
3091
"###);
3192

93+
let sdist_tree = TempDir::new()?;
94+
95+
let sdist_reader = BufReader::new(File::open(
96+
sdist_dir.path().join("built_by_uv-0.1.0.tar.gz"),
97+
)?);
98+
tar::Archive::new(GzDecoder::new(sdist_reader)).unpack(sdist_tree.path())?;
99+
100+
drop(sdist_dir);
101+
102+
let wheel_dir = TempDir::new()?;
103+
104+
uv_snapshot!(context
105+
.build_backend()
106+
.arg("build-wheel")
107+
.arg(wheel_dir.path())
108+
.current_dir(sdist_tree.path().join("built-by-uv-0.1.0")), @r###"
109+
success: true
110+
exit_code: 0
111+
----- stdout -----
112+
built_by_uv-0.1.0-py3-none-any.whl
113+
114+
----- stderr -----
115+
"###);
116+
117+
drop(sdist_tree);
118+
32119
context
33120
.pip_install()
34-
.arg(temp_dir.path().join("uv_backend-0.1.0-py3-none-any.whl"))
121+
.arg(wheel_dir.path().join("built_by_uv-0.1.0-py3-none-any.whl"))
35122
.assert()
36123
.success();
37124

125+
drop(wheel_dir);
126+
38127
uv_snapshot!(context
39128
.run()
40129
.arg("python")
41130
.arg("-c")
42-
.arg("import uv_backend\nuv_backend.greet()")
131+
.arg(BUILT_BY_UV_TEST_SCRIPT)
43132
// Python on windows
44133
.env(EnvVars::PYTHONUTF8, "1"), @r###"
45134
success: true
46135
exit_code: 0
47136
----- stdout -----
48137
Hello 👋
138+
Area of a circle with r=2: 12.56636
49139
50140
----- stderr -----
51141
"###);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/dist/
2+
/build-root/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# built_by_uv
2+
3+
A package to be built with the uv build backend that uses all features exposed by the build backend.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
We don't want this file in the source dist or wheel.

0 commit comments

Comments
 (0)