@@ -5,9 +5,12 @@ use cargo::util::Sha256;
55use flate2:: write:: GzEncoder ;
66use flate2:: Compression ;
77use std:: collections:: HashMap ;
8+ use std:: fmt:: Write as _;
89use std:: fs:: { self , File } ;
9- use std:: io:: prelude:: * ;
10+ use std:: io:: { BufRead , BufReader , Write } ;
11+ use std:: net:: TcpListener ;
1012use std:: path:: { Path , PathBuf } ;
13+ use std:: thread;
1114use tar:: { Builder , Header } ;
1215use url:: Url ;
1316
@@ -70,6 +73,183 @@ pub fn generate_alt_dl_url(name: &str) -> String {
7073 format ! ( "{}/{{crate}}/{{version}}/{{crate}}-{{version}}.crate" , base)
7174}
7275
76+ /// A builder for initializing registries.
77+ pub struct RegistryBuilder {
78+ /// If `true`, adds source replacement for crates.io to a registry on the filesystem.
79+ replace_crates_io : bool ,
80+ /// If `true`, configures a registry named "alternative".
81+ alternative : bool ,
82+ /// If set, sets the API url for the "alternative" registry.
83+ /// This defaults to a directory on the filesystem.
84+ alt_api_url : Option < String > ,
85+ /// If `true`, configures `.cargo/credentials` with some tokens.
86+ add_tokens : bool ,
87+ }
88+
89+ impl RegistryBuilder {
90+ pub fn new ( ) -> RegistryBuilder {
91+ RegistryBuilder {
92+ replace_crates_io : true ,
93+ alternative : false ,
94+ alt_api_url : None ,
95+ add_tokens : true ,
96+ }
97+ }
98+
99+ /// Sets whether or not to replace crates.io with a registry on the filesystem.
100+ /// Default is `true`.
101+ pub fn replace_crates_io ( & mut self , replace : bool ) -> & mut Self {
102+ self . replace_crates_io = replace;
103+ self
104+ }
105+
106+ /// Sets whether or not to initialize an alternative registry named "alternative".
107+ /// Default is `false`.
108+ pub fn alternative ( & mut self , alt : bool ) -> & mut Self {
109+ self . alternative = alt;
110+ self
111+ }
112+
113+ /// Sets the API url for the "alternative" registry.
114+ /// Defaults to a path on the filesystem ([`alt_api_path`]).
115+ pub fn alternative_api_url ( & mut self , url : & str ) -> & mut Self {
116+ self . alternative = true ;
117+ self . alt_api_url = Some ( url. to_string ( ) ) ;
118+ self
119+ }
120+
121+ /// Sets whether or not to initialize `.cargo/credentials` with some tokens.
122+ /// Defaults to `true`.
123+ pub fn add_tokens ( & mut self , add : bool ) -> & mut Self {
124+ self . add_tokens = add;
125+ self
126+ }
127+
128+ /// Initializes the registries.
129+ pub fn build ( & self ) {
130+ let config_path = paths:: home ( ) . join ( ".cargo/config" ) ;
131+ if config_path. exists ( ) {
132+ panic ! (
133+ "{} already exists, the registry may only be initialized once, \
134+ and must be done before the config file is created",
135+ config_path. display( )
136+ ) ;
137+ }
138+ t ! ( fs:: create_dir_all( config_path. parent( ) . unwrap( ) ) ) ;
139+ let mut config = String :: new ( ) ;
140+ if self . replace_crates_io {
141+ write ! (
142+ & mut config,
143+ "
144+ [source.crates-io]
145+ replace-with = 'dummy-registry'
146+
147+ [source.dummy-registry]
148+ registry = '{}'
149+ " ,
150+ registry_url( )
151+ )
152+ . unwrap ( ) ;
153+ }
154+ if self . alternative {
155+ write ! (
156+ config,
157+ "
158+ [registries.alternative]
159+ index = '{}'
160+ " ,
161+ alt_registry_url( )
162+ )
163+ . unwrap ( ) ;
164+ }
165+ t ! ( fs:: write( & config_path, config) ) ;
166+
167+ if self . add_tokens {
168+ let credentials = paths:: home ( ) . join ( ".cargo/credentials" ) ;
169+ t ! ( fs:: write(
170+ & credentials,
171+ r#"
172+ [registry]
173+ token = "api-token"
174+
175+ [registries.alternative]
176+ token = "api-token"
177+ "#
178+ ) ) ;
179+ }
180+
181+ if self . replace_crates_io {
182+ init_registry (
183+ registry_path ( ) ,
184+ dl_url ( ) . into_string ( ) ,
185+ api_url ( ) ,
186+ api_path ( ) ,
187+ ) ;
188+ }
189+
190+ if self . alternative {
191+ init_registry (
192+ alt_registry_path ( ) ,
193+ alt_dl_url ( ) ,
194+ self . alt_api_url
195+ . as_ref ( )
196+ . map_or_else ( alt_api_url, |url| Url :: parse ( & url) . expect ( "valid url" ) ) ,
197+ alt_api_path ( ) ,
198+ ) ;
199+ }
200+ }
201+
202+ /// Initializes the registries, and sets up an HTTP server for the
203+ /// "alternative" registry.
204+ ///
205+ /// The given callback takes a `Vec` of headers when a request comes in.
206+ /// The first entry should be the HTTP command, such as
207+ /// `PUT /api/v1/crates/new HTTP/1.1`.
208+ ///
209+ /// The callback should return the HTTP code for the response, and the
210+ /// response body.
211+ ///
212+ /// This method returns a `JoinHandle` which you should call
213+ /// `.join().unwrap()` on before exiting the test.
214+ pub fn build_api_server < ' a > (
215+ & mut self ,
216+ handler : & ' static ( dyn ( Fn ( Vec < String > ) -> ( u32 , & ' a dyn AsRef < [ u8 ] > ) ) + Sync ) ,
217+ ) -> thread:: JoinHandle < ( ) > {
218+ let server = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
219+ let addr = server. local_addr ( ) . unwrap ( ) ;
220+ let api_url = format ! ( "http://{}" , addr) ;
221+
222+ self . replace_crates_io ( false )
223+ . alternative_api_url ( & api_url)
224+ . build ( ) ;
225+
226+ let t = thread:: spawn ( move || {
227+ let mut conn = BufReader :: new ( server. accept ( ) . unwrap ( ) . 0 ) ;
228+ let headers: Vec < _ > = ( & mut conn)
229+ . lines ( )
230+ . map ( |s| s. unwrap ( ) )
231+ . take_while ( |s| s. len ( ) > 2 )
232+ . map ( |s| s. trim ( ) . to_string ( ) )
233+ . collect ( ) ;
234+ let ( code, response) = handler ( headers) ;
235+ let response = response. as_ref ( ) ;
236+ let stream = conn. get_mut ( ) ;
237+ write ! (
238+ stream,
239+ "HTTP/1.1 {}\r \n \
240+ Content-Length: {}\r \n \
241+ \r \n ",
242+ code,
243+ response. len( )
244+ )
245+ . unwrap ( ) ;
246+ stream. write_all ( response) . unwrap ( ) ;
247+ } ) ;
248+
249+ t
250+ }
251+ }
252+
73253/// A builder for creating a new package in a registry.
74254///
75255/// This uses "source replacement" using an automatically generated
@@ -162,70 +342,28 @@ pub struct Dependency {
162342 optional : bool ,
163343}
164344
345+ /// Initializes the on-disk registry and sets up the config so that crates.io
346+ /// is replaced with the one on disk.
165347pub fn init ( ) {
166348 let config = paths:: home ( ) . join ( ".cargo/config" ) ;
167- t ! ( fs:: create_dir_all( config. parent( ) . unwrap( ) ) ) ;
168349 if config. exists ( ) {
169350 return ;
170351 }
171- t ! ( fs:: write(
172- & config,
173- format!(
174- r#"
175- [source.crates-io]
176- registry = 'https://wut'
177- replace-with = 'dummy-registry'
178-
179- [source.dummy-registry]
180- registry = '{reg}'
181-
182- [registries.alternative]
183- index = '{alt}'
184- "# ,
185- reg = registry_url( ) ,
186- alt = alt_registry_url( )
187- )
188- ) ) ;
189- let credentials = paths:: home ( ) . join ( ".cargo/credentials" ) ;
190- t ! ( fs:: write(
191- & credentials,
192- r#"
193- [registry]
194- token = "api-token"
195-
196- [registries.alternative]
197- token = "api-token"
198- "#
199- ) ) ;
352+ RegistryBuilder :: new ( ) . build ( ) ;
353+ }
200354
201- // Initialize a new registry.
202- init_registry (
203- registry_path ( ) ,
204- dl_url ( ) . into_string ( ) ,
205- api_url ( ) ,
206- api_path ( ) ,
207- ) ;
208-
209- // Initialize an alternative registry.
210- init_registry (
211- alt_registry_path ( ) ,
212- alt_dl_url ( ) ,
213- alt_api_url ( ) ,
214- alt_api_path ( ) ,
215- ) ;
355+ /// Variant of `init` that initializes the "alternative" registry.
356+ pub fn alt_init ( ) {
357+ RegistryBuilder :: new ( ) . alternative ( true ) . build ( ) ;
216358}
217359
360+ /// Creates a new on-disk registry.
218361pub fn init_registry ( registry_path : PathBuf , dl_url : String , api_url : Url , api_path : PathBuf ) {
219362 // Initialize a new registry.
220363 repo ( & registry_path)
221364 . file (
222365 "config.json" ,
223- & format ! (
224- r#"
225- {{"dl":"{}","api":"{}"}}
226- "# ,
227- dl_url, api_url
228- ) ,
366+ & format ! ( r#"{{"dl":"{}","api":"{}"}}"# , dl_url, api_url) ,
229367 )
230368 . build ( ) ;
231369 fs:: create_dir_all ( api_path. join ( "api/v1/crates" ) ) . unwrap ( ) ;
0 commit comments