@@ -9,15 +9,16 @@ use std::{
99
1010use boulder:: {
1111 Env , Macros , architecture,
12- draft:: { self , Drafter } ,
12+ draft:: { self , Drafter , upstream :: fetched_upstream_cache_path } ,
1313 macros, recipe,
1414} ;
1515use clap:: Parser ;
16- use fs_err as fs;
16+ use fs_err:: { self as fs} ;
1717use futures_util:: StreamExt ;
1818use itertools:: Itertools ;
19- use moss:: { request, runtime} ;
19+ use moss:: { request, runtime, util } ;
2020use sha2:: { Digest , Sha256 } ;
21+ use tempfile:: NamedTempFile ;
2122use thiserror:: Error ;
2223use tokio:: io:: AsyncWriteExt ;
2324use tui:: {
@@ -107,14 +108,14 @@ fn parse_upstream(s: &str) -> Result<Upstream, String> {
107108pub fn handle ( command : Command , env : Env ) -> Result < ( ) , Error > {
108109 match command. subcommand {
109110 Subcommand :: Bump { recipe, release } => bump ( recipe, release) ,
110- Subcommand :: New { output, upstreams } => new ( output , upstreams , env ) ,
111+ Subcommand :: New { output, upstreams } => new ( env , output , upstreams ) ,
111112 Subcommand :: Update {
112113 recipe,
113114 overwrite,
114115 version,
115116 upstreams,
116117 no_bump,
117- } => update ( recipe, overwrite, version, upstreams, no_bump) ,
118+ } => update ( env , recipe, overwrite, version, upstreams, no_bump) ,
118119 Subcommand :: Macros { _macro } => macros ( _macro, env) ,
119120 }
120121}
@@ -145,11 +146,11 @@ fn bump(recipe: PathBuf, release: Option<u64>) -> Result<(), Error> {
145146 Ok ( ( ) )
146147}
147148
148- fn new ( output : PathBuf , upstreams : Vec < Url > , env : Env ) -> Result < ( ) , Error > {
149+ fn new ( env : Env , output : PathBuf , upstreams : Vec < Url > ) -> Result < ( ) , Error > {
149150 const RECIPE_FILE : & str = "stone.yaml" ;
150151 const MONITORING_FILE : & str = "monitoring.yaml" ;
151152
152- let drafter = Drafter :: new ( upstreams , env . data_dir ) ;
153+ let drafter = Drafter :: new ( env , upstreams ) ;
153154 let draft = drafter. run ( ) ?;
154155
155156 if !output. is_dir ( ) {
@@ -165,6 +166,7 @@ fn new(output: PathBuf, upstreams: Vec<Url>, env: Env) -> Result<(), Error> {
165166}
166167
167168fn update (
169+ env : Env ,
168170 recipe : Option < PathBuf > ,
169171 overwrite : bool ,
170172 version : String ,
@@ -245,7 +247,7 @@ fn update(
245247 updater. update_value ( version, |root| root / "version" ) ;
246248 }
247249 Update :: PlainUpstream ( i, key, new_uri) => {
248- let hash = runtime:: block_on ( fetch_hash ( new_uri. clone ( ) , & mpb) ) ?;
250+ let hash = runtime:: block_on ( fetch_and_cache_upstream ( & env , new_uri. clone ( ) , & mpb) ) ?;
249251
250252 let path = |root| root / "upstreams" / i / key. as_str ( ) . unwrap_or_default ( ) ;
251253
@@ -281,7 +283,13 @@ fn update(
281283 Ok ( ( ) )
282284}
283285
284- async fn fetch_hash ( uri : Url , mpb : & MultiProgress ) -> Result < String , Error > {
286+ /// Fetches the upstream at `uri` and caches it so it doesn't need to be refetched
287+ /// when this recipe is finally built.
288+ ///
289+ /// Returns the sha256 hash of the fetched upstream
290+ async fn fetch_and_cache_upstream ( env : & Env , uri : Url , mpb : & MultiProgress ) -> Result < String , Error > {
291+ use fs_err:: tokio:: { self as fs, File } ;
292+
285293 let pb = mpb. add (
286294 ProgressBar :: new ( u64:: MAX )
287295 . with_message ( format ! ( "{} {}" , "Fetching" . blue( ) , uri. as_str( ) . bold( ) ) )
@@ -293,11 +301,13 @@ async fn fetch_hash(uri: Url, mpb: &MultiProgress) -> Result<String, Error> {
293301 ) ;
294302 pb. enable_steady_tick ( Duration :: from_millis ( 150 ) ) ;
295303
296- let mut stream = request:: stream ( uri) . await ?;
304+ let mut stream = request:: stream ( uri. clone ( ) ) . await ?;
297305
306+ let ( temp_file, temp_file_path) = NamedTempFile :: with_prefix ( "boulder-" )
307+ . map_err ( Error :: CreateTempFile ) ?
308+ . into_parts ( ) ;
298309 let mut hasher = Sha256 :: new ( ) ;
299- // Discard bytes
300- let mut out = tokio:: io:: sink ( ) ;
310+ let mut out = File :: from_std ( fs_err:: File :: from_parts ( temp_file, & temp_file_path) ) ;
301311
302312 while let Some ( chunk) = stream. next ( ) . await {
303313 let bytes = & chunk?;
@@ -312,6 +322,22 @@ async fn fetch_hash(uri: Url, mpb: &MultiProgress) -> Result<String, Error> {
312322
313323 let hash = hex:: encode ( hasher. finalize ( ) ) ;
314324
325+ // Move fetched asset to cache dir so we don't need to refetch it
326+ // when the user finally builds this new recipe
327+ {
328+ let cache_path = fetched_upstream_cache_path ( env, & uri, & hash) ;
329+
330+ if let Some ( parent) = cache_path. parent ( ) {
331+ fs:: create_dir_all ( parent) . await . map_err ( Error :: CreateDir ) ?;
332+ }
333+
334+ util:: async_hardlink_or_copy ( & temp_file_path, & cache_path)
335+ . await
336+ . map_err ( Error :: MoveTempFile ) ?;
337+
338+ drop ( temp_file_path) ;
339+ }
340+
315341 pb. finish ( ) ;
316342 mpb. remove ( & pb) ;
317343
@@ -419,6 +445,10 @@ pub enum Error {
419445 CreateDir ( #[ source] io:: Error ) ,
420446 #[ error( "deserializing recipe" ) ]
421447 Deser ( #[ from] serde_yaml:: Error ) ,
448+ #[ error( "create temp file" ) ]
449+ CreateTempFile ( #[ source] io:: Error ) ,
450+ #[ error( "move temp file" ) ]
451+ MoveTempFile ( #[ source] io:: Error ) ,
422452 #[ error( "fetch upstream" ) ]
423453 Fetch ( #[ from] request:: Error ) ,
424454 #[ error( "fetch upstream" ) ]
0 commit comments