@@ -35,6 +35,134 @@ use crate::{
3535 DropErrorDetailsExt ,
3636} ;
3737
38+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
39+ pub struct Versions {
40+ pub stable : ChannelVersions ,
41+ pub beta : ChannelVersions ,
42+ pub nightly : ChannelVersions ,
43+ }
44+
45+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
46+ pub struct ChannelVersions {
47+ pub rustc : Version ,
48+ pub rustfmt : Version ,
49+ pub clippy : Version ,
50+ pub miri : Option < Version > ,
51+ }
52+
53+ /// Parsing this struct is very lenient — we'd rather return some
54+ /// partial data instead of absolutely nothing.
55+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
56+ pub struct Version {
57+ pub release : String ,
58+ pub commit_hash : String ,
59+ pub commit_date : String ,
60+ }
61+
62+ impl Version {
63+ fn parse_rustc_version_verbose ( rustc_version : & str ) -> Self {
64+ let mut release = "" ;
65+ let mut commit_hash = "" ;
66+ let mut commit_date = "" ;
67+
68+ let fields = rustc_version. lines ( ) . skip ( 1 ) . filter_map ( |line| {
69+ let mut pieces = line. splitn ( 2 , ':' ) ;
70+ let key = pieces. next ( ) ?. trim ( ) ;
71+ let value = pieces. next ( ) ?. trim ( ) ;
72+ Some ( ( key, value) )
73+ } ) ;
74+
75+ for ( k, v) in fields {
76+ match k {
77+ "release" => release = v,
78+ "commit-hash" => commit_hash = v,
79+ "commit-date" => commit_date = v,
80+ _ => { }
81+ }
82+ }
83+
84+ Self {
85+ release : release. into ( ) ,
86+ commit_hash : commit_hash. into ( ) ,
87+ commit_date : commit_date. into ( ) ,
88+ }
89+ }
90+
91+ // Parses versions of the shape `toolname 0.0.0 (0000000 0000-00-00)`
92+ fn parse_tool_version ( tool_version : & str ) -> Self {
93+ let mut parts = tool_version. split_whitespace ( ) . fuse ( ) . skip ( 1 ) ;
94+
95+ let release = parts. next ( ) . unwrap_or ( "" ) . into ( ) ;
96+ let commit_hash = parts. next ( ) . unwrap_or ( "" ) . trim_start_matches ( '(' ) . into ( ) ;
97+ let commit_date = parts. next ( ) . unwrap_or ( "" ) . trim_end_matches ( ')' ) . into ( ) ;
98+
99+ Self {
100+ release,
101+ commit_hash,
102+ commit_date,
103+ }
104+ }
105+ }
106+
107+ #[ derive( Debug , Snafu ) ]
108+ #[ snafu( module) ]
109+ pub enum VersionsError {
110+ #[ snafu( display( "Unable to determine versions for the stable channel" ) ) ]
111+ Stable { source : VersionsChannelError } ,
112+
113+ #[ snafu( display( "Unable to determine versions for the beta channel" ) ) ]
114+ Beta { source : VersionsChannelError } ,
115+
116+ #[ snafu( display( "Unable to determine versions for the nightly channel" ) ) ]
117+ Nightly { source : VersionsChannelError } ,
118+ }
119+
120+ #[ derive( Debug , Snafu ) ]
121+ pub enum VersionsChannelError {
122+ #[ snafu( context( false ) ) ] // transparent
123+ Channel { source : Error } ,
124+
125+ #[ snafu( context( false ) ) ] // transparent
126+ Versions { source : ContainerVersionsError } ,
127+ }
128+
129+ #[ derive( Debug , Snafu ) ]
130+ #[ snafu( module) ]
131+ pub enum ContainerVersionsError {
132+ #[ snafu( display( "Failed to get `rustc` version" ) ) ]
133+ Rustc { source : VersionError } ,
134+
135+ #[ snafu( display( "`rustc` not executable" ) ) ]
136+ RustcMissing ,
137+
138+ #[ snafu( display( "Failed to get `rustfmt` version" ) ) ]
139+ Rustfmt { source : VersionError } ,
140+
141+ #[ snafu( display( "`cargo fmt` not executable" ) ) ]
142+ RustfmtMissing ,
143+
144+ #[ snafu( display( "Failed to get clippy version" ) ) ]
145+ Clippy { source : VersionError } ,
146+
147+ #[ snafu( display( "`cargo clippy` not executable" ) ) ]
148+ ClippyMissing ,
149+
150+ #[ snafu( display( "Failed to get miri version" ) ) ]
151+ Miri { source : VersionError } ,
152+ }
153+
154+ #[ derive( Debug , Snafu ) ]
155+ #[ snafu( module) ]
156+ pub enum VersionError {
157+ #[ snafu( display( "Could not start the process" ) ) ]
158+ #[ snafu( context( false ) ) ]
159+ SpawnProcess { source : SpawnCargoError } ,
160+
161+ #[ snafu( display( "The task panicked" ) ) ]
162+ #[ snafu( context( false ) ) ]
163+ TaskPanic { source : tokio:: task:: JoinError } ,
164+ }
165+
38166#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
39167pub enum AssemblyFlavor {
40168 Att ,
@@ -639,6 +767,28 @@ where
639767 }
640768 }
641769
770+ pub async fn versions ( & self ) -> Result < Versions , VersionsError > {
771+ use versions_error:: * ;
772+
773+ let [ stable, beta, nightly] =
774+ [ Channel :: Stable , Channel :: Beta , Channel :: Nightly ] . map ( |c| async move {
775+ let c = self . select_channel ( c) . await ?;
776+ c. versions ( ) . await . map_err ( VersionsChannelError :: from)
777+ } ) ;
778+
779+ let ( stable, beta, nightly) = join ! ( stable, beta, nightly) ;
780+
781+ let stable = stable. context ( StableSnafu ) ?;
782+ let beta = beta. context ( BetaSnafu ) ?;
783+ let nightly = nightly. context ( NightlySnafu ) ?;
784+
785+ Ok ( Versions {
786+ stable,
787+ beta,
788+ nightly,
789+ } )
790+ }
791+
642792 pub async fn execute (
643793 & self ,
644794 request : ExecuteRequest ,
@@ -904,6 +1054,72 @@ impl Container {
9041054 } )
9051055 }
9061056
1057+ async fn versions ( & self ) -> Result < ChannelVersions , ContainerVersionsError > {
1058+ use container_versions_error:: * ;
1059+
1060+ let token = CancellationToken :: new ( ) ;
1061+
1062+ let rustc = self . rustc_version ( token. clone ( ) ) ;
1063+ let rustfmt = self . tool_version ( token. clone ( ) , "fmt" ) ;
1064+ let clippy = self . tool_version ( token. clone ( ) , "clippy" ) ;
1065+ let miri = self . tool_version ( token, "miri" ) ;
1066+
1067+ let ( rustc, rustfmt, clippy, miri) = join ! ( rustc, rustfmt, clippy, miri) ;
1068+
1069+ let rustc = rustc. context ( RustcSnafu ) ?. context ( RustcMissingSnafu ) ?;
1070+ let rustfmt = rustfmt
1071+ . context ( RustfmtSnafu ) ?
1072+ . context ( RustfmtMissingSnafu ) ?;
1073+ let clippy = clippy. context ( ClippySnafu ) ?. context ( ClippyMissingSnafu ) ?;
1074+ let miri = miri. context ( MiriSnafu ) ?;
1075+
1076+ Ok ( ChannelVersions {
1077+ rustc,
1078+ rustfmt,
1079+ clippy,
1080+ miri,
1081+ } )
1082+ }
1083+
1084+ async fn rustc_version (
1085+ & self ,
1086+ token : CancellationToken ,
1087+ ) -> Result < Option < Version > , VersionError > {
1088+ let rustc_cmd = ExecuteCommandRequest :: simple ( "rustc" , [ "--version" , "--verbose" ] ) ;
1089+ let output = self . version_output ( token, rustc_cmd) . await ?;
1090+
1091+ Ok ( output. map ( |o| Version :: parse_rustc_version_verbose ( & o) ) )
1092+ }
1093+
1094+ async fn tool_version (
1095+ & self ,
1096+ token : CancellationToken ,
1097+ subcommand_name : & str ,
1098+ ) -> Result < Option < Version > , VersionError > {
1099+ let tool_cmd = ExecuteCommandRequest :: simple ( "cargo" , [ subcommand_name, "--version" ] ) ;
1100+ let output = self . version_output ( token, tool_cmd) . await ?;
1101+
1102+ Ok ( output. map ( |o| Version :: parse_tool_version ( & o) ) )
1103+ }
1104+
1105+ async fn version_output (
1106+ & self ,
1107+ token : CancellationToken ,
1108+ cmd : ExecuteCommandRequest ,
1109+ ) -> Result < Option < String > , VersionError > {
1110+ let v = self . spawn_cargo_task ( token. clone ( ) , cmd) . await ?;
1111+ let SpawnCargo {
1112+ task,
1113+ stdin_tx,
1114+ stdout_rx,
1115+ stderr_rx,
1116+ } = v;
1117+ drop ( stdin_tx) ;
1118+ let task = async { task. await ?. map_err ( VersionError :: from) } ;
1119+ let o = WithOutput :: try_absorb ( task, stdout_rx, stderr_rx) . await ?;
1120+ Ok ( if o. success { Some ( o. stdout ) } else { None } )
1121+ }
1122+
9071123 async fn execute (
9081124 & self ,
9091125 request : ExecuteRequest ,
@@ -2416,6 +2632,20 @@ mod tests {
24162632 }
24172633 }
24182634
2635+ #[ tokio:: test]
2636+ #[ snafu:: report]
2637+ async fn versions ( ) -> Result < ( ) > {
2638+ let coordinator = new_coordinator ( ) . await ;
2639+
2640+ let versions = coordinator. versions ( ) . with_timeout ( ) . await . unwrap ( ) ;
2641+
2642+ assert_starts_with ! ( versions. stable. rustc. release, "1." ) ;
2643+
2644+ coordinator. shutdown ( ) . await ?;
2645+
2646+ Ok ( ( ) )
2647+ }
2648+
24192649 const ARBITRARY_EXECUTE_REQUEST : ExecuteRequest = ExecuteRequest {
24202650 channel : Channel :: Stable ,
24212651 mode : Mode :: Debug ,
0 commit comments