Skip to content

Commit 08f084f

Browse files
committed
cargo-test-support: Make publish http api write to file system
1 parent ebb7167 commit 08f084f

File tree

5 files changed

+208
-80
lines changed

5 files changed

+208
-80
lines changed

crates/cargo-test-support/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ doctest = false
1111
anyhow = "1.0.34"
1212
cargo-test-macro = { path = "../cargo-test-macro" }
1313
cargo-util = { path = "../cargo-util" }
14+
crates-io = { path = "../crates-io" }
1415
snapbox = { version = "0.3.0", features = ["diff", "path"] }
1516
filetime = "0.2"
1617
flate2 = { version = "1.0", default-features = false, features = ["zlib"] }

crates/cargo-test-support/src/publish.rs

+89-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::compare::{assert_match_exact, find_json_mismatch};
2-
use crate::registry::{self, alt_api_path};
2+
use crate::registry::{self, alt_api_path, FeatureMap};
33
use flate2::read::GzDecoder;
44
use std::collections::{HashMap, HashSet};
5+
use std::fs;
56
use std::fs::File;
67
use std::io::{self, prelude::*, SeekFrom};
78
use std::path::{Path, PathBuf};
@@ -155,3 +156,90 @@ pub fn validate_crate_contents(
155156
}
156157
}
157158
}
159+
160+
pub(crate) fn create_index_line(
161+
name: serde_json::Value,
162+
vers: &str,
163+
deps: Vec<serde_json::Value>,
164+
cksum: &str,
165+
features: crate::registry::FeatureMap,
166+
yanked: bool,
167+
links: Option<String>,
168+
v: Option<u32>,
169+
) -> String {
170+
// This emulates what crates.io may do in the future.
171+
let (features, features2) = split_index_features(features.clone());
172+
let mut json = serde_json::json!({
173+
"name": name,
174+
"vers": vers,
175+
"deps": deps,
176+
"cksum": cksum,
177+
"features": features,
178+
"yanked": yanked,
179+
"links": links,
180+
});
181+
if let Some(f2) = &features2 {
182+
json["features2"] = serde_json::json!(f2);
183+
json["v"] = serde_json::json!(2);
184+
}
185+
if let Some(v) = v {
186+
json["v"] = serde_json::json!(v);
187+
}
188+
189+
json.to_string()
190+
}
191+
192+
pub(crate) fn write_to_index(registry_path: &PathBuf, name: &str, line: String, local: bool) {
193+
let file = cargo_util::registry::make_dep_path(name, false);
194+
195+
// Write file/line in the index.
196+
let dst = if local {
197+
registry_path.join("index").join(&file)
198+
} else {
199+
registry_path.join(&file)
200+
};
201+
let prev = fs::read_to_string(&dst).unwrap_or_default();
202+
t!(fs::create_dir_all(dst.parent().unwrap()));
203+
t!(fs::write(&dst, prev + &line[..] + "\n"));
204+
205+
// Add the new file to the index.
206+
if !local {
207+
let repo = t!(git2::Repository::open(&registry_path));
208+
let mut index = t!(repo.index());
209+
t!(index.add_path(Path::new(&file)));
210+
t!(index.write());
211+
let id = t!(index.write_tree());
212+
213+
// Commit this change.
214+
let tree = t!(repo.find_tree(id));
215+
let sig = t!(repo.signature());
216+
let parent = t!(repo.refname_to_id("refs/heads/master"));
217+
let parent = t!(repo.find_commit(parent));
218+
t!(repo.commit(
219+
Some("HEAD"),
220+
&sig,
221+
&sig,
222+
"Another commit",
223+
&tree,
224+
&[&parent]
225+
));
226+
}
227+
}
228+
229+
fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>) {
230+
let mut features2 = FeatureMap::new();
231+
for (feat, values) in features.iter_mut() {
232+
if values
233+
.iter()
234+
.any(|value| value.starts_with("dep:") || value.contains("?/"))
235+
{
236+
let new_values = values.drain(..).collect();
237+
features2.insert(feat.clone(), new_values);
238+
}
239+
}
240+
if features2.is_empty() {
241+
(features, None)
242+
} else {
243+
(features, Some(features2))
244+
}
245+
}

crates/cargo-test-support/src/registry.rs

+74-77
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use crate::git::repo;
22
use crate::paths;
3+
use crate::publish::{create_index_line, write_to_index};
34
use cargo_util::paths::append;
4-
use cargo_util::{registry::make_dep_path, Sha256};
5+
use cargo_util::Sha256;
56
use flate2::write::GzEncoder;
67
use flate2::Compression;
78
use std::collections::{BTreeMap, HashMap};
89
use std::fmt;
910
use std::fs::{self, File};
1011
use std::io::{BufRead, BufReader, Read, Write};
1112
use std::net::{SocketAddr, TcpListener, TcpStream};
12-
use std::path::{Path, PathBuf};
13+
use std::path::PathBuf;
1314
use std::thread;
1415
use tar::{Builder, Header};
1516
use url::Url;
@@ -389,7 +390,7 @@ pub struct Package {
389390
v: Option<u32>,
390391
}
391392

392-
type FeatureMap = BTreeMap<String, Vec<String>>;
393+
pub(crate) type FeatureMap = BTreeMap<String, Vec<String>>;
393394

394395
#[derive(Clone)]
395396
pub struct Dependency {
@@ -637,16 +638,72 @@ impl HttpServer {
637638
self.dl(&req)
638639
}
639640
}
641+
// publish
642+
("put", ["api", "v1", "crates", "new"]) => {
643+
if !authorized(true) {
644+
return self.unauthorized(req)
645+
}
646+
if let Some(body) = &req.body {
647+
// Get the metadata of the package
648+
let (len, remaining) = body.split_at(4);
649+
let json_len = u32::from_le_bytes(len.try_into().unwrap());
650+
let (json, remaining) = remaining.split_at(json_len as usize);
651+
let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
652+
// Get the `.crate` file
653+
let (len, remaining) = remaining.split_at(4);
654+
let file_len = u32::from_le_bytes(len.try_into().unwrap());
655+
let (file, _remaining) = remaining.split_at(file_len as usize);
656+
657+
// Write the `.crate`
658+
let dst = self.dl_path.join(&new_crate.name).join(&new_crate.vers).join("download");
659+
t!(fs::create_dir_all(dst.parent().unwrap()));
660+
let mut f = t!(File::create(&dst));
661+
t!(f.write_all(file));
662+
663+
let deps = new_crate.deps.iter().map(|dep| {
664+
serde_json::json!({
665+
"name": dep.name,
666+
"req": dep.version_req,
667+
"features": dep.features,
668+
"default_features": true,
669+
"target": dep.target,
670+
"optional": dep.optional,
671+
"kind": dep.kind,
672+
"registry": dep.registry,
673+
"package": dep.explicit_name_in_toml,
674+
})
675+
}).collect::<Vec<_>>();
676+
677+
let line = create_index_line(
678+
serde_json::json!(new_crate.name),
679+
&new_crate.vers,
680+
deps,
681+
&cksum(file),
682+
new_crate.features,
683+
false,
684+
new_crate.links,
685+
None,
686+
);
687+
688+
write_to_index(&self.registry_path, &new_crate.name, line, false);
689+
690+
self.ok(&req)
691+
} else {
692+
Response {
693+
code: 400,
694+
headers: vec![],
695+
body: b"The request was missing a body".to_vec(),
696+
}
697+
}
698+
}
640699
// The remainder of the operators in the test framework do nothing other than responding 'ok'.
641700
//
642701
// Note: We don't need to support anything real here because the testing framework publishes crates
643702
// by writing directly to the filesystem instead. If the test framework is changed to publish
644703
// via the HTTP API, then this should be made more complete.
645704

646-
// publish
647-
("put", ["api", "v1", "crates", "new"])
648705
// yank
649-
| ("delete", ["api", "v1", "crates", .., "yank"])
706+
("delete", ["api", "v1", "crates", .., "yank"])
650707
// unyank
651708
| ("put", ["api", "v1", "crates", .., "unyank"])
652709
// owners
@@ -999,66 +1056,24 @@ impl Package {
9991056
} else {
10001057
serde_json::json!(self.name)
10011058
};
1002-
// This emulates what crates.io may do in the future.
1003-
let (features, features2) = split_index_features(self.features.clone());
1004-
let mut json = serde_json::json!({
1005-
"name": name,
1006-
"vers": self.vers,
1007-
"deps": deps,
1008-
"cksum": cksum,
1009-
"features": features,
1010-
"yanked": self.yanked,
1011-
"links": self.links,
1012-
});
1013-
if let Some(f2) = &features2 {
1014-
json["features2"] = serde_json::json!(f2);
1015-
json["v"] = serde_json::json!(2);
1016-
}
1017-
if let Some(v) = self.v {
1018-
json["v"] = serde_json::json!(v);
1019-
}
1020-
let line = json.to_string();
1021-
1022-
let file = make_dep_path(&self.name, false);
1059+
let line = create_index_line(
1060+
name,
1061+
&self.vers,
1062+
deps,
1063+
&cksum,
1064+
self.features.clone(),
1065+
self.yanked,
1066+
self.links.clone(),
1067+
self.v,
1068+
);
10231069

10241070
let registry_path = if self.alternative {
10251071
alt_registry_path()
10261072
} else {
10271073
registry_path()
10281074
};
10291075

1030-
// Write file/line in the index.
1031-
let dst = if self.local {
1032-
registry_path.join("index").join(&file)
1033-
} else {
1034-
registry_path.join(&file)
1035-
};
1036-
let prev = fs::read_to_string(&dst).unwrap_or_default();
1037-
t!(fs::create_dir_all(dst.parent().unwrap()));
1038-
t!(fs::write(&dst, prev + &line[..] + "\n"));
1039-
1040-
// Add the new file to the index.
1041-
if !self.local {
1042-
let repo = t!(git2::Repository::open(&registry_path));
1043-
let mut index = t!(repo.index());
1044-
t!(index.add_path(Path::new(&file)));
1045-
t!(index.write());
1046-
let id = t!(index.write_tree());
1047-
1048-
// Commit this change.
1049-
let tree = t!(repo.find_tree(id));
1050-
let sig = t!(repo.signature());
1051-
let parent = t!(repo.refname_to_id("refs/heads/master"));
1052-
let parent = t!(repo.find_commit(parent));
1053-
t!(repo.commit(
1054-
Some("HEAD"),
1055-
&sig,
1056-
&sig,
1057-
"Another commit",
1058-
&tree,
1059-
&[&parent]
1060-
));
1061-
}
1076+
write_to_index(&registry_path, &self.name, line, self.local);
10621077

10631078
cksum
10641079
}
@@ -1279,21 +1294,3 @@ impl Dependency {
12791294
self
12801295
}
12811296
}
1282-
1283-
fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>) {
1284-
let mut features2 = FeatureMap::new();
1285-
for (feat, values) in features.iter_mut() {
1286-
if values
1287-
.iter()
1288-
.any(|value| value.starts_with("dep:") || value.contains("?/"))
1289-
{
1290-
let new_values = values.drain(..).collect();
1291-
features2.insert(feat.clone(), new_values);
1292-
}
1293-
}
1294-
if features2.is_empty() {
1295-
(features, None)
1296-
} else {
1297-
(features, Some(features2))
1298-
}
1299-
}

crates/crates-io/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub struct Crate {
3636
pub max_version: String,
3737
}
3838

39-
#[derive(Serialize)]
39+
#[derive(Serialize, Deserialize)]
4040
pub struct NewCrate {
4141
pub name: String,
4242
pub vers: String,
@@ -57,7 +57,7 @@ pub struct NewCrate {
5757
pub links: Option<String>,
5858
}
5959

60-
#[derive(Serialize)]
60+
#[derive(Serialize, Deserialize)]
6161
pub struct NewCrateDependency {
6262
pub optional: bool,
6363
pub default_features: bool,

tests/testsuite/publish.rs

+42
Original file line numberDiff line numberDiff line change
@@ -2048,3 +2048,45 @@ error: package ID specification `bar` did not match any packages
20482048
)
20492049
.run();
20502050
}
2051+
2052+
#[cargo_test]
2053+
fn http_api_not_noop() {
2054+
let _registry = registry::RegistryBuilder::new().http_api().build();
2055+
2056+
let p = project()
2057+
.file(
2058+
"Cargo.toml",
2059+
r#"
2060+
[project]
2061+
name = "foo"
2062+
version = "0.0.1"
2063+
authors = []
2064+
license = "MIT"
2065+
description = "foo"
2066+
"#,
2067+
)
2068+
.file("src/main.rs", "fn main() {}")
2069+
.build();
2070+
2071+
p.cargo("publish --token api-token").run();
2072+
2073+
let p = project()
2074+
.file(
2075+
"Cargo.toml",
2076+
r#"
2077+
[project]
2078+
name = "bar"
2079+
version = "0.0.1"
2080+
authors = []
2081+
license = "MIT"
2082+
description = "foo"
2083+
2084+
[dependencies]
2085+
foo = "0.0.1"
2086+
"#,
2087+
)
2088+
.file("src/main.rs", "fn main() {}")
2089+
.build();
2090+
2091+
p.cargo("build").run();
2092+
}

0 commit comments

Comments
 (0)