From ce8cc8b530db1aed170625e05344daa9cc396a5b Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Sun, 2 Sep 2018 10:56:11 +0200 Subject: [PATCH] Show versions of tools. Closes #300 --- ui/frontend/ToolsMenu.tsx | 39 ++++++++++++++++++++++---- ui/frontend/actions.ts | 17 ++++++++--- ui/frontend/index.scss | 7 +++++ ui/frontend/reducers/versions.ts | 10 +++++-- ui/frontend/selectors/index.ts | 8 ++++++ ui/src/main.rs | 48 ++++++++++++++++++++++++++++++++ ui/src/sandbox.rs | 40 ++++++++++++++++++++++++++ 7 files changed, 158 insertions(+), 11 deletions(-) diff --git a/ui/frontend/ToolsMenu.tsx b/ui/frontend/ToolsMenu.tsx index e83d17e04..ad09a2d63 100644 --- a/ui/frontend/ToolsMenu.tsx +++ b/ui/frontend/ToolsMenu.tsx @@ -4,6 +4,15 @@ import { connect } from 'react-redux'; import ButtonMenuItem from './ButtonMenuItem'; import MenuGroup from './MenuGroup'; +import { + clippyVersionDetailsText, + clippyVersionText, + miriVersionText, + rustfmtVersionDetailsText, + rustfmtVersionText, +} from './selectors'; +import State from './state'; + import { performClippy, performFormat, @@ -11,6 +20,11 @@ import { } from './actions'; interface ToolsMenuProps { + rustfmtVersion: string; + rustfmtVersionDetails: string; + clippyVersion: string; + clippyVersionDetails: string; + miriVersion: string; clippy: () => any; miri: () => any; format: () => any; @@ -22,26 +36,41 @@ const ToolsMenu: React.SFC = props => ( { props.format(); props.close(); }}> - Format this code with Rustfmt. +
Format this code with Rustfmt.
+
{props.rustfmtVersion} ({props.rustfmtVersionDetails})
{ props.clippy(); props.close(); }}> - Catch common mistakes and improve the code using the Clippy linter. +
Catch common mistakes and improve the code using the Clippy linter.
+
{props.clippyVersion} ({props.clippyVersionDetails})
{ props.miri(); props.close(); }}> - Execute this program in the Miri interpreter to detect certain - cases of undefined behavior (like out-of-bounds memory access). +
+ Execute this program in the Miri interpreter to detect certain + cases of undefined behavior (like out-of-bounds memory access). +
+
{props.miriVersion}
); +const mapStateToProps = (state: State) => { + return { + rustfmtVersion: rustfmtVersionText(state), + rustfmtVersionDetails: rustfmtVersionDetailsText(state), + clippyVersionDetails: clippyVersionDetailsText(state), + clippyVersion: clippyVersionText(state), + miriVersion: miriVersionText(state), + }; +}; + const mapDispatchToProps = ({ clippy: performClippy, miri: performMiri, format: performFormat, }); -export default connect(undefined, mapDispatchToProps)(ToolsMenu); +export default connect(mapStateToProps, mapDispatchToProps)(ToolsMenu); diff --git a/ui/frontend/actions.ts b/ui/frontend/actions.ts index 83f030421..1cf814274 100644 --- a/ui/frontend/actions.ts +++ b/ui/frontend/actions.ts @@ -34,6 +34,9 @@ const routes = { stable: '/meta/version/stable', beta: '/meta/version/beta', nightly: '/meta/version/nightly', + rustfmt: '/meta/version/rustfmt', + clippy: '/meta/version/clippy', + miri: '/meta/version/miri', }, gist: { pathname: '/meta/gist/' }, }, @@ -575,8 +578,8 @@ export function performCratesLoad(): ThunkAction { const requestVersionsLoad = () => createAction(ActionType.RequestVersionsLoad); -const receiveVersionsLoadSuccess = ({ stable, beta, nightly }) => - createAction(ActionType.VersionsLoadSucceeded, { stable, beta, nightly }); +const receiveVersionsLoadSuccess = ({ stable, beta, nightly, rustfmt, clippy, miri }) => + createAction(ActionType.VersionsLoadSucceeded, { stable, beta, nightly, rustfmt, clippy, miri }); export function performVersionsLoad(): ThunkAction { return function(dispatch) { @@ -585,14 +588,20 @@ export function performVersionsLoad(): ThunkAction { const stable = jsonGet(routes.meta.version.stable); const beta = jsonGet(routes.meta.version.beta); const nightly = jsonGet(routes.meta.version.nightly); + const rustfmt = jsonGet(routes.meta.version.rustfmt); + const clippy = jsonGet(routes.meta.version.clippy); + const miri = jsonGet(routes.meta.version.miri); - const all = Promise.all([stable, beta, nightly]); + const all = Promise.all([stable, beta, nightly, rustfmt, clippy, miri]); return all - .then(([stable, beta, nightly]) => dispatch(receiveVersionsLoadSuccess({ + .then(([stable, beta, nightly, rustfmt, clippy, miri]) => dispatch(receiveVersionsLoadSuccess({ stable, beta, nightly, + rustfmt, + clippy, + miri, }))); // TODO: Failure case }; diff --git a/ui/frontend/index.scss b/ui/frontend/index.scss index 7576d7b37..a73e92656 100644 --- a/ui/frontend/index.scss +++ b/ui/frontend/index.scss @@ -751,6 +751,13 @@ $header-transition: 0.2s ease-in-out; } } +.tools-menu { + &__aside { + margin: 0.25em 0 0 0; + color: #888; + } +} + .config-element { display: flex; align-items: center; diff --git a/ui/frontend/reducers/versions.ts b/ui/frontend/reducers/versions.ts index 82ee375c6..6aeddc7b6 100644 --- a/ui/frontend/reducers/versions.ts +++ b/ui/frontend/reducers/versions.ts @@ -5,19 +5,25 @@ const DEFAULT: State = { stable: null, beta: null, nightly: null, + rustfmt: null, + clippy: null, + miri: null, }; export interface State { stable?: Version; beta?: Version; nightly?: Version; + rustfmt?: Version; + clippy?: Version; + miri?: Version; } export default function crates(state = DEFAULT, action: Action) { switch (action.type) { case ActionType.VersionsLoadSucceeded: { - const { stable, beta, nightly } = action; - return { stable, beta, nightly }; + const { stable, beta, nightly, rustfmt, clippy, miri } = action; + return { stable, beta, nightly, rustfmt, clippy, miri }; } default: return state; diff --git a/ui/frontend/selectors/index.ts b/ui/frontend/selectors/index.ts index 1fa39940d..9078e410c 100644 --- a/ui/frontend/selectors/index.ts +++ b/ui/frontend/selectors/index.ts @@ -81,15 +81,23 @@ export const getExecutionLabel = createSelector(primaryActionSelector, primaryAc const getStable = (state: State) => state.versions && state.versions.stable; const getBeta = (state: State) => state.versions && state.versions.beta; const getNightly = (state: State) => state.versions && state.versions.nightly; +const getRustfmt = (state: State) => state.versions && state.versions.rustfmt; +const getClippy = (state: State) => state.versions && state.versions.clippy; +const getMiri = (state: State) => state.versions && state.versions.miri; const versionNumber = v => v ? v.version : ''; export const stableVersionText = createSelector([getStable], versionNumber); export const betaVersionText = createSelector([getBeta], versionNumber); export const nightlyVersionText = createSelector([getNightly], versionNumber); +export const clippyVersionText = createSelector([getClippy], versionNumber); +export const rustfmtVersionText = createSelector([getRustfmt], versionNumber); +export const miriVersionText = createSelector([getMiri], versionNumber); const versionDetails = v => v ? `${v.date} ${v.hash.slice(0, 20)}` : ''; export const betaVersionDetailsText = createSelector([getBeta], versionDetails); export const nightlyVersionDetailsText = createSelector([getNightly], versionDetails); +export const clippyVersionDetailsText = createSelector([getClippy], versionDetails); +export const rustfmtVersionDetailsText = createSelector([getRustfmt], versionDetails); export const isWasmAvailable = (state: State) => ( state.configuration.channel === Channel.Nightly diff --git a/ui/src/main.rs b/ui/src/main.rs index 456467fbe..2be1ec472 100644 --- a/ui/src/main.rs +++ b/ui/src/main.rs @@ -104,6 +104,9 @@ fn main() { mount.mount("/meta/version/stable", meta_version_stable); mount.mount("/meta/version/beta", meta_version_beta); mount.mount("/meta/version/nightly", meta_version_nightly); + mount.mount("/meta/version/rustfmt", meta_version_rustfmt); + mount.mount("/meta/version/clippy", meta_version_clippy); + mount.mount("/meta/version/miri", meta_version_miri); mount.mount("/meta/gist", gist_router); mount.mount("/evaluate.json", evaluate); @@ -236,6 +239,30 @@ fn meta_version_nightly(_req: &mut Request) -> IronResult { }) } +fn meta_version_rustfmt(_req: &mut Request) -> IronResult { + with_sandbox_no_request(|sandbox| { + cached(sandbox) + .version_rustfmt() + .map(MetaVersionResponse::from) + }) +} + +fn meta_version_clippy(_req: &mut Request) -> IronResult { + with_sandbox_no_request(|sandbox| { + cached(sandbox) + .version_clippy() + .map(MetaVersionResponse::from) + }) +} + +fn meta_version_miri(_req: &mut Request) -> IronResult { + with_sandbox_no_request(|sandbox| { + cached(sandbox) + .version_miri() + .map(MetaVersionResponse::from) + }) +} + fn meta_gist_create(req: &mut Request) -> IronResult { let token = req.extensions.get::().unwrap().0.as_ref().clone(); serialize_to_response(deserialize_from_request(req, |r: MetaGistCreateRequest| { @@ -401,6 +428,9 @@ struct SandboxCache { version_stable: SandboxCacheOne, version_beta: SandboxCacheOne, version_nightly: SandboxCacheOne, + version_clippy: SandboxCacheOne, + version_rustfmt: SandboxCacheOne, + version_miri: SandboxCacheOne, } /// Provides a similar API to the Sandbox that caches the successful results. @@ -431,6 +461,24 @@ impl<'a> CachedSandbox<'a> { self.sandbox.version(sandbox::Channel::Nightly) }) } + + fn version_clippy(&self) -> Result { + self.cache.version_clippy.clone_or_populate(|| { + self.sandbox.version_clippy() + }) + } + + fn version_rustfmt(&self) -> Result { + self.cache.version_rustfmt.clone_or_populate(|| { + self.sandbox.version_rustfmt() + }) + } + + fn version_miri(&self) -> Result { + self.cache.version_miri.clone_or_populate(|| { + self.sandbox.version_miri() + }) + } } /// A convenience constructor diff --git a/ui/src/sandbox.rs b/ui/src/sandbox.rs index 270c82d5f..2c679756d 100644 --- a/ui/src/sandbox.rs +++ b/ui/src/sandbox.rs @@ -257,6 +257,46 @@ impl Sandbox { Ok(Version { release, commit_hash, commit_date }) } + + pub fn version_rustfmt(&self) -> Result { + let mut command = basic_secure_docker_command(); + command.args(&["rustfmt", "cargo", "fmt", "--version"]); + self.cargo_tool_version(command) + } + + pub fn version_clippy(&self) -> Result { + let mut command = basic_secure_docker_command(); + command.args(&["clippy", "cargo", "clippy", "--version"]); + self.cargo_tool_version(command) + } + + pub fn version_miri(&self) -> Result { + let mut command = basic_secure_docker_command(); + command.args(&["miri", "cargo", "miri", "--version"]); + + let output = command.output().map_err(Error::UnableToExecuteCompiler)?; + let version_output = vec_to_str(output.stdout)?; + + let release = version_output.trim().into(); + let commit_hash = String::new(); + let commit_date = String::new(); + + Ok(Version { release, commit_hash, commit_date }) + } + + // Parses versions of the shape `toolname 0.0.0 (0000000 0000-00-00)` + fn cargo_tool_version(&self, mut command: Command) -> Result { + let output = command.output().map_err(Error::UnableToExecuteCompiler)?; + let version_output = vec_to_str(output.stdout)?; + let mut parts = version_output.split_whitespace().fuse().skip(1); + + let release = parts.next().unwrap_or("").into(); + let commit_hash = parts.next().unwrap_or("").trim_left_matches('(').into(); + let commit_date = parts.next().unwrap_or("").trim_right_matches(')').into(); + + Ok(Version { release, commit_hash, commit_date }) + } + fn write_source_code(&self, code: &str) -> Result<()> { let data = code.as_bytes();