@@ -23,9 +23,12 @@ use rustwide::cmd::{Command, CommandError, SandboxBuilder, SandboxImage};
2323use rustwide:: logging:: { self , LogStorage } ;
2424use rustwide:: toolchain:: ToolchainError ;
2525use rustwide:: { AlternativeRegistry , Build , Crate , Toolchain , Workspace , WorkspaceBuilder } ;
26- use std:: collections:: { HashMap , HashSet } ;
27- use std:: path:: Path ;
28- use std:: sync:: Arc ;
26+ use std:: {
27+ collections:: { HashMap , HashSet } ,
28+ path:: Path ,
29+ sync:: Arc ,
30+ time:: Instant ,
31+ } ;
2932use tracing:: { debug, info, warn} ;
3033
3134const USER_AGENT : & str = "docs.rs builder (https://github.com/rust-lang/docs.rs)" ;
@@ -244,9 +247,19 @@ impl RustwideBuilder {
244247 . run ( |build| {
245248 ( || -> Result < ( ) > {
246249 let metadata = Metadata :: from_crate_root ( build. host_source_dir ( ) ) ?;
250+ let deadline = Instant :: now ( )
251+ . checked_add ( limits. timeout ( ) )
252+ . context ( "deadline is not representable" ) ?;
247253
248- let res =
249- self . execute_build ( HOST_TARGET , true , build, & limits, & metadata, true ) ?;
254+ let res = self . execute_build (
255+ HOST_TARGET ,
256+ true ,
257+ build,
258+ & limits,
259+ & metadata,
260+ true ,
261+ deadline,
262+ ) ?;
250263 if !res. result . successful {
251264 bail ! ( "failed to build dummy crate for {}" , self . rustc_version) ;
252265 }
@@ -333,15 +346,15 @@ impl RustwideBuilder {
333346 return Ok ( false ) ;
334347 }
335348
336- self . update_toolchain ( ) ?;
337-
338- info ! ( "building package {} {}" , name, version) ;
339-
340349 if is_blacklisted ( & mut conn, name) ? {
341350 info ! ( "skipping build of {}, crate has been blacklisted" , name) ;
342351 return Ok ( false ) ;
343352 }
344353
354+ self . update_toolchain ( ) ?;
355+
356+ info ! ( "building package {} {}" , name, version) ;
357+
345358 let limits = Limits :: for_crate ( & mut conn, name) ?;
346359 #[ cfg( target_os = "linux" ) ]
347360 if !self . config . disable_memory_limit {
@@ -388,18 +401,55 @@ impl RustwideBuilder {
388401 default_target,
389402 other_targets,
390403 } = metadata. targets ( self . config . include_default_targets ) ;
391- let mut targets = vec ! [ default_target] ;
392- targets. extend ( & other_targets) ;
404+
405+ let cargo_args = metadata. cargo_args ( & [ ] , & [ ] ) ;
406+ let has_build_std = cargo_args. iter ( ) . any ( |arg| arg. starts_with ( "-Zbuild-std" ) )
407+ || cargo_args
408+ . windows ( 2 )
409+ . any ( |args| args[ 0 ] == "-Z" && args[ 1 ] . starts_with ( "build-std" ) ) ;
410+
411+ let other_targets: Vec < _ > = other_targets
412+ . into_iter ( )
413+ . filter ( |target| {
414+ // If the explicit target is not a tier one target, we need to install it.
415+ if !docsrs_metadata:: DEFAULT_TARGETS . contains ( target) && !has_build_std {
416+ // This is a no-op if the target is already installed.
417+ if let Err ( e) = self . toolchain . add_target ( & self . workspace , target) {
418+ info ! ( "Skipping target {target} since it failed to install: {e}" ) ;
419+ return false ;
420+ }
421+ }
422+ true
423+ } )
424+ // Limit the number of targets so that no one can try to build all 200000 possible targets
425+ . take ( limits. targets ( ) )
426+ . collect ( ) ;
427+
393428 // Fetch this before we enter the sandbox, so networking isn't blocked.
394- build. fetch_build_std_dependencies ( & targets) ?;
429+ build. fetch_build_std_dependencies ( & {
430+ let mut targets = other_targets. clone ( ) ;
431+ targets. push ( default_target) ;
432+ targets
433+ } ) ?;
395434
396435 ( || -> Result < bool > {
436+ let deadline = Instant :: now ( )
437+ . checked_add ( limits. timeout ( ) )
438+ . context ( "deadline is not representable" ) ?;
439+
397440 let mut has_docs = false ;
398441 let mut successful_targets = Vec :: new ( ) ;
399442
400443 // Perform an initial build
401- let mut res =
402- self . execute_build ( default_target, true , build, & limits, & metadata, false ) ?;
444+ let mut res = self . execute_build (
445+ default_target,
446+ true ,
447+ build,
448+ & limits,
449+ & metadata,
450+ false ,
451+ deadline,
452+ ) ?;
403453
404454 // If the build fails with the lockfile given, try using only the dependencies listed in Cargo.toml.
405455 let cargo_lock = build. host_source_dir ( ) . join ( "Cargo.lock" ) ;
@@ -421,6 +471,7 @@ impl RustwideBuilder {
421471 & limits,
422472 & metadata,
423473 false ,
474+ deadline,
424475 ) ?;
425476 }
426477
@@ -448,8 +499,7 @@ impl RustwideBuilder {
448499 successful_targets. push ( res. target . clone ( ) ) ;
449500
450501 // Then build the documentation for all the targets
451- // Limit the number of targets so that no one can try to build all 200000 possible targets
452- for target in other_targets. into_iter ( ) . take ( limits. targets ( ) ) {
502+ for target in other_targets {
453503 debug ! ( "building package {} {} for {}" , name, version, target) ;
454504 self . build_target (
455505 target,
@@ -458,6 +508,7 @@ impl RustwideBuilder {
458508 local_storage. path ( ) ,
459509 & mut successful_targets,
460510 & metadata,
511+ deadline,
461512 ) ?;
462513 }
463514 let ( _, new_alg) = add_path_into_remote_archive (
@@ -572,6 +623,7 @@ impl RustwideBuilder {
572623 Ok ( successful)
573624 }
574625
626+ #[ allow( clippy:: too_many_arguments) ]
575627 fn build_target (
576628 & self ,
577629 target : & str ,
@@ -580,8 +632,10 @@ impl RustwideBuilder {
580632 local_storage : & Path ,
581633 successful_targets : & mut Vec < String > ,
582634 metadata : & Metadata ,
635+ deadline : Instant ,
583636 ) -> Result < ( ) > {
584- let target_res = self . execute_build ( target, false , build, limits, metadata, false ) ?;
637+ let target_res =
638+ self . execute_build ( target, false , build, limits, metadata, false , deadline) ?;
585639 if target_res. result . successful {
586640 // Cargo is not giving any error and not generating documentation of some crates
587641 // when we use a target compile options. Check documentation exists before
@@ -600,7 +654,7 @@ impl RustwideBuilder {
600654 target : & str ,
601655 build : & Build ,
602656 metadata : & Metadata ,
603- limits : & Limits ,
657+ deadline : Instant ,
604658 ) -> Result < Option < DocCoverage > > {
605659 let rustdoc_flags = vec ! [
606660 "--output-format" . to_string( ) ,
@@ -623,7 +677,7 @@ impl RustwideBuilder {
623677 items_with_examples : 0 ,
624678 } ;
625679
626- self . prepare_command ( build, target, metadata, limits , rustdoc_flags ) ?
680+ self . prepare_command ( build, target, metadata, rustdoc_flags , deadline ) ?
627681 . process_lines ( & mut |line, _| {
628682 if line. starts_with ( '{' ) && line. ends_with ( '}' ) {
629683 let parsed = match serde_json:: from_str :: < HashMap < String , FileCoverage > > ( line) {
@@ -650,6 +704,9 @@ impl RustwideBuilder {
650704 )
651705 }
652706
707+ // TODO(Nemo157): Look at pulling out a sub-builder for each crate-build
708+ // that holds a lot of this state
709+ #[ allow( clippy:: too_many_arguments) ]
653710 fn execute_build (
654711 & self ,
655712 target : & str ,
@@ -658,6 +715,7 @@ impl RustwideBuilder {
658715 limits : & Limits ,
659716 metadata : & Metadata ,
660717 create_essential_files : bool ,
718+ deadline : Instant ,
661719 ) -> Result < FullBuildResult > {
662720 let cargo_metadata = CargoMetadata :: load_from_rustwide (
663721 & self . workspace ,
@@ -682,7 +740,7 @@ impl RustwideBuilder {
682740 // we have to run coverage before the doc-build because currently it
683741 // deletes the doc-target folder.
684742 // https://github.com/rust-lang/cargo/issues/9447
685- let doc_coverage = match self . get_coverage ( target, build, metadata, limits ) {
743+ let doc_coverage = match self . get_coverage ( target, build, metadata, deadline ) {
686744 Ok ( cov) => cov,
687745 Err ( err) => {
688746 info ! ( "error when trying to get coverage: {}" , err) ;
@@ -692,10 +750,11 @@ impl RustwideBuilder {
692750 } ;
693751
694752 let successful = logging:: capture ( & storage, || {
695- self . prepare_command ( build, target, metadata, limits , rustdoc_flags )
753+ self . prepare_command ( build, target, metadata, rustdoc_flags , deadline )
696754 . and_then ( |command| command. run ( ) . map_err ( Error :: from) )
697- . is_ok ( )
698- } ) ;
755+ } )
756+ . map_err ( |e| info ! ( "failed build: {e:?}" ) )
757+ . is_ok ( ) ;
699758
700759 // For proc-macros, cargo will put the output in `target/doc`.
701760 // Move it to the target-specific directory for consistency with other builds.
@@ -732,9 +791,13 @@ impl RustwideBuilder {
732791 build : & ' ws Build ,
733792 target : & str ,
734793 metadata : & Metadata ,
735- limits : & Limits ,
736794 mut rustdoc_flags_extras : Vec < String > ,
795+ deadline : Instant ,
737796 ) -> Result < Command < ' ws , ' pl > > {
797+ let timeout = deadline
798+ . checked_duration_since ( Instant :: now ( ) )
799+ . context ( "exceeded deadline" ) ?;
800+
738801 // Add docs.rs specific arguments
739802 let mut cargo_args = vec ! [
740803 "--offline" . into( ) ,
@@ -783,22 +846,7 @@ impl RustwideBuilder {
783846 rustdoc_flags_extras. extend ( UNCONDITIONAL_ARGS . iter ( ) . map ( |& s| s. to_owned ( ) ) ) ;
784847 let cargo_args = metadata. cargo_args ( & cargo_args, & rustdoc_flags_extras) ;
785848
786- // If the explicit target is not a tier one target, we need to install it.
787- let has_build_std = cargo_args. windows ( 2 ) . any ( |args| {
788- args[ 0 ] . starts_with ( "-Zbuild-std" )
789- || ( args[ 0 ] == "-Z" && args[ 1 ] . starts_with ( "build-std" ) )
790- } ) || cargo_args. last ( ) . unwrap ( ) . starts_with ( "-Zbuild-std" ) ;
791- if !docsrs_metadata:: DEFAULT_TARGETS . contains ( & target) && !has_build_std {
792- // This is a no-op if the target is already installed.
793- self . toolchain
794- . add_target ( & self . workspace , target)
795- . map_err ( FailureError :: compat) ?;
796- }
797-
798- let mut command = build
799- . cargo ( )
800- . timeout ( Some ( limits. timeout ( ) ) )
801- . no_output_timeout ( None ) ;
849+ let mut command = build. cargo ( ) . timeout ( Some ( timeout) ) . no_output_timeout ( None ) ;
802850
803851 for ( key, val) in metadata. environment_variables ( ) {
804852 command = command. env ( key, val) ;
@@ -885,7 +933,10 @@ pub(crate) struct BuildResult {
885933#[ cfg( test) ]
886934mod tests {
887935 use super :: * ;
888- use crate :: test:: { assert_redirect, assert_success, wrapper, TestEnvironment } ;
936+ use crate :: {
937+ db:: Overrides ,
938+ test:: { assert_redirect, assert_success, wrapper, TestEnvironment } ,
939+ } ;
889940 use serde_json:: Value ;
890941
891942 fn remove_cache_files ( env : & TestEnvironment , crate_ : & str , version : & str ) -> Result < ( ) > {
@@ -1218,6 +1269,49 @@ mod tests {
12181269 } ) ;
12191270 }
12201271
1272+ #[ test]
1273+ #[ ignore]
1274+ fn test_timeout_skips_some_targets ( ) {
1275+ wrapper ( |env| {
1276+ let crate_ = "bs58" ;
1277+ let version = "0.5.0" ;
1278+ let mut builder = RustwideBuilder :: init ( env) . unwrap ( ) ;
1279+ let get_targets = || -> i32 {
1280+ env. db ( )
1281+ . conn ( )
1282+ . query_one (
1283+ "SELECT json_array_length(releases.doc_targets) FROM releases;" ,
1284+ & [ ] ,
1285+ )
1286+ . unwrap ( )
1287+ . get ( 0 )
1288+ } ;
1289+
1290+ // Build once to time it and count how many targets are built
1291+ let start = Instant :: now ( ) ;
1292+ assert ! ( builder. build_package( crate_, version, PackageKind :: CratesIo ) ?) ;
1293+ let timeout = start. elapsed ( ) / 2 ;
1294+ let original_targets = get_targets ( ) ;
1295+
1296+ // Build again with half the time and count how many targets are built
1297+ Overrides :: save (
1298+ & mut env. db ( ) . conn ( ) ,
1299+ crate_,
1300+ Overrides {
1301+ memory : None ,
1302+ targets : Some ( original_targets as usize ) ,
1303+ timeout : Some ( timeout) ,
1304+ } ,
1305+ ) ?;
1306+ assert ! ( builder. build_package( crate_, version, PackageKind :: CratesIo ) ?) ;
1307+ let new_targets = get_targets ( ) ;
1308+
1309+ assert ! ( new_targets < original_targets) ;
1310+
1311+ Ok ( ( ) )
1312+ } ) ;
1313+ }
1314+
12211315 #[ test]
12221316 #[ ignore]
12231317 fn test_implicit_features_for_optional_dependencies ( ) {
0 commit comments