about summary refs log tree commit diff
path: root/src/bootstrap
diff options
context:
space:
mode:
authorbjorn3 <bjorn3@users.noreply.github.com>2022-03-03 18:15:01 +0100
committerbjorn3 <bjorn3@users.noreply.github.com>2022-03-05 15:31:22 +0100
commite657da72aa4bd7ed5edda194b770903ea0cf1dd1 (patch)
tree5e905cb8bd2e883878d8e6fc8842b6284cd5a4bd /src/bootstrap
parent0cfc3e101689d050432f600a35e953413b87147f (diff)
downloadrust-e657da72aa4bd7ed5edda194b770903ea0cf1dd1.tar.gz
rust-e657da72aa4bd7ed5edda194b770903ea0cf1dd1.zip
Merge build_helper into util
Diffstat (limited to 'src/bootstrap')
-rw-r--r--src/bootstrap/build_helper.rs145
-rw-r--r--src/bootstrap/builder.rs3
-rw-r--r--src/bootstrap/cc_detect.rs2
-rw-r--r--src/bootstrap/channel.rs2
-rw-r--r--src/bootstrap/clean.rs2
-rw-r--r--src/bootstrap/compile.rs3
-rw-r--r--src/bootstrap/config.rs3
-rw-r--r--src/bootstrap/dist.rs3
-rw-r--r--src/bootstrap/doc.rs3
-rw-r--r--src/bootstrap/flags.rs2
-rw-r--r--src/bootstrap/format.rs2
-rw-r--r--src/bootstrap/install.rs2
-rw-r--r--src/bootstrap/lib.rs6
-rw-r--r--src/bootstrap/metadata.rs2
-rw-r--r--src/bootstrap/native.rs4
-rw-r--r--src/bootstrap/run.rs2
-rw-r--r--src/bootstrap/sanity.rs2
-rw-r--r--src/bootstrap/tarball.rs2
-rw-r--r--src/bootstrap/test.rs7
-rw-r--r--src/bootstrap/tool.rs3
-rw-r--r--src/bootstrap/toolstate.rs2
-rw-r--r--src/bootstrap/util.rs146
22 files changed, 166 insertions, 182 deletions
diff --git a/src/bootstrap/build_helper.rs b/src/bootstrap/build_helper.rs
deleted file mode 100644
index 320099102fc..00000000000
--- a/src/bootstrap/build_helper.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use std::fs;
-use std::path::{Path, PathBuf};
-use std::process::{Command, Stdio};
-use std::time::{SystemTime, UNIX_EPOCH};
-
-/// A helper macro to `unwrap` a result except also print out details like:
-///
-/// * The file/line of the panic
-/// * The expression that failed
-/// * The error itself
-///
-/// This is currently used judiciously throughout the build system rather than
-/// using a `Result` with `try!`, but this may change one day...
-macro_rules! t {
-    ($e:expr) => {
-        match $e {
-            Ok(e) => e,
-            Err(e) => panic!("{} failed with {}", stringify!($e), e),
-        }
-    };
-    // it can show extra info in the second parameter
-    ($e:expr, $extra:expr) => {
-        match $e {
-            Ok(e) => e,
-            Err(e) => panic!("{} failed with {} ({:?})", stringify!($e), e, $extra),
-        }
-    };
-}
-pub(crate) use t;
-
-pub fn run(cmd: &mut Command, print_cmd_on_fail: bool) {
-    if !try_run(cmd, print_cmd_on_fail) {
-        std::process::exit(1);
-    }
-}
-
-pub fn try_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool {
-    let status = match cmd.status() {
-        Ok(status) => status,
-        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
-    };
-    if !status.success() && print_cmd_on_fail {
-        println!(
-            "\n\ncommand did not execute successfully: {:?}\n\
-             expected success, got: {}\n\n",
-            cmd, status
-        );
-    }
-    status.success()
-}
-
-pub fn run_suppressed(cmd: &mut Command) {
-    if !try_run_suppressed(cmd) {
-        std::process::exit(1);
-    }
-}
-
-pub fn try_run_suppressed(cmd: &mut Command) -> bool {
-    let output = match cmd.output() {
-        Ok(status) => status,
-        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
-    };
-    if !output.status.success() {
-        println!(
-            "\n\ncommand did not execute successfully: {:?}\n\
-             expected success, got: {}\n\n\
-             stdout ----\n{}\n\
-             stderr ----\n{}\n\n",
-            cmd,
-            output.status,
-            String::from_utf8_lossy(&output.stdout),
-            String::from_utf8_lossy(&output.stderr)
-        );
-    }
-    output.status.success()
-}
-
-pub fn make(host: &str) -> PathBuf {
-    if host.contains("dragonfly")
-        || host.contains("freebsd")
-        || host.contains("netbsd")
-        || host.contains("openbsd")
-    {
-        PathBuf::from("gmake")
-    } else {
-        PathBuf::from("make")
-    }
-}
-
-#[track_caller]
-pub fn output(cmd: &mut Command) -> String {
-    let output = match cmd.stderr(Stdio::inherit()).output() {
-        Ok(status) => status,
-        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
-    };
-    if !output.status.success() {
-        panic!(
-            "command did not execute successfully: {:?}\n\
-             expected success, got: {}",
-            cmd, output.status
-        );
-    }
-    String::from_utf8(output.stdout).unwrap()
-}
-
-/// Returns the last-modified time for `path`, or zero if it doesn't exist.
-pub fn mtime(path: &Path) -> SystemTime {
-    fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH)
-}
-
-/// Returns `true` if `dst` is up to date given that the file or files in `src`
-/// are used to generate it.
-///
-/// Uses last-modified time checks to verify this.
-pub fn up_to_date(src: &Path, dst: &Path) -> bool {
-    if !dst.exists() {
-        return false;
-    }
-    let threshold = mtime(dst);
-    let meta = match fs::metadata(src) {
-        Ok(meta) => meta,
-        Err(e) => panic!("source {:?} failed to get metadata: {}", src, e),
-    };
-    if meta.is_dir() {
-        dir_up_to_date(src, threshold)
-    } else {
-        meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
-    }
-}
-
-fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
-    t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
-        let meta = t!(e.metadata());
-        if meta.is_dir() {
-            dir_up_to_date(&e.path(), threshold)
-        } else {
-            meta.modified().unwrap_or(UNIX_EPOCH) < threshold
-        }
-    })
-}
-
-fn fail(s: &str) -> ! {
-    println!("\n\n{}\n\n", s);
-    std::process::exit(1);
-}
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 531d9c27b47..fc55c8626d9 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -11,7 +11,6 @@ use std::path::{Component, Path, PathBuf};
 use std::process::Command;
 use std::time::{Duration, Instant};
 
-use crate::build_helper::{output, t};
 use crate::cache::{Cache, Interned, INTERNER};
 use crate::check;
 use crate::compile;
@@ -24,7 +23,7 @@ use crate::native;
 use crate::run;
 use crate::test;
 use crate::tool::{self, SourceType};
-use crate::util::{self, add_dylib_path, add_link_lib_path, exe, libdir};
+use crate::util::{self, add_dylib_path, add_link_lib_path, exe, libdir, output, t};
 use crate::EXTRA_CHECK_CFGS;
 use crate::{Build, CLang, DocTests, GitRepo, Mode};
 
diff --git a/src/bootstrap/cc_detect.rs b/src/bootstrap/cc_detect.rs
index 9b19e22e0d0..7ce44687611 100644
--- a/src/bootstrap/cc_detect.rs
+++ b/src/bootstrap/cc_detect.rs
@@ -26,8 +26,8 @@ use std::path::{Path, PathBuf};
 use std::process::Command;
 use std::{env, iter};
 
-use crate::build_helper::output;
 use crate::config::{Target, TargetSelection};
+use crate::util::output;
 use crate::{Build, CLang, GitRepo};
 
 // The `cc` crate doesn't provide a way to obtain a path to the detected archiver,
diff --git a/src/bootstrap/channel.rs b/src/bootstrap/channel.rs
index af5c2e1bcd6..1932a0017ee 100644
--- a/src/bootstrap/channel.rs
+++ b/src/bootstrap/channel.rs
@@ -8,7 +8,7 @@
 use std::path::Path;
 use std::process::Command;
 
-use crate::build_helper::output;
+use crate::util::output;
 use crate::Build;
 
 pub enum GitInfo {
diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs
index 249ddf9c731..069f3d6acf1 100644
--- a/src/bootstrap/clean.rs
+++ b/src/bootstrap/clean.rs
@@ -9,7 +9,7 @@ use std::fs;
 use std::io::{self, ErrorKind};
 use std::path::Path;
 
-use crate::build_helper::t;
+use crate::util::t;
 use crate::Build;
 
 pub fn clean(build: &Build, all: bool) {
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index b9a80ce2d11..9cfd9f92aa7 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -18,7 +18,6 @@ use std::str;
 
 use serde::Deserialize;
 
-use crate::build_helper::{output, t, up_to_date};
 use crate::builder::Cargo;
 use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
@@ -26,7 +25,7 @@ use crate::config::{LlvmLibunwind, TargetSelection};
 use crate::dist;
 use crate::native;
 use crate::tool::SourceType;
-use crate::util::{exe, is_debug_info, is_dylib, symlink_dir};
+use crate::util::{exe, is_debug_info, is_dylib, output, symlink_dir, t, up_to_date};
 use crate::LLVM_TOOLS;
 use crate::{CLang, Compiler, DependencyType, GitRepo, Mode};
 
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index f7c69e1161e..b17b94f2893 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -12,13 +12,12 @@ use std::fs;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
-use crate::build_helper::t;
 use crate::builder::TaskPath;
 use crate::cache::{Interned, INTERNER};
 use crate::channel::GitInfo;
 pub use crate::flags::Subcommand;
 use crate::flags::{Color, Flags};
-use crate::util::exe;
+use crate::util::{exe, t};
 use serde::Deserialize;
 
 macro_rules! check_ci_llvm {
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index c0489de42de..5f92cc2ca6f 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -14,14 +14,13 @@ use std::fs;
 use std::path::{Path, PathBuf};
 use std::process::Command;
 
-use crate::build_helper::{output, t};
 use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
 use crate::compile;
 use crate::config::TargetSelection;
 use crate::tarball::{GeneratedTarball, OverlayKind, Tarball};
 use crate::tool::{self, Tool};
-use crate::util::{exe, is_dylib, timeit};
+use crate::util::{exe, is_dylib, output, t, timeit};
 use crate::{Compiler, DependencyType, Mode, LLVM_TOOLS};
 
 pub fn pkgname(builder: &Builder<'_>, component: &str) -> String {
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index 710bf2493ec..be55871b56a 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -12,13 +12,12 @@ use std::fs;
 use std::io;
 use std::path::{Path, PathBuf};
 
-use crate::build_helper::{t, up_to_date};
 use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
 use crate::compile;
 use crate::config::{Config, TargetSelection};
 use crate::tool::{self, prepare_tool_cargo, SourceType, Tool};
-use crate::util::symlink_dir;
+use crate::util::{symlink_dir, t, up_to_date};
 use crate::Mode;
 
 macro_rules! submodule_helper {
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index d523171d5b1..e34b40a93ff 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -9,10 +9,10 @@ use std::process;
 
 use getopts::Options;
 
-use crate::build_helper::t;
 use crate::builder::Builder;
 use crate::config::{Config, TargetSelection};
 use crate::setup::Profile;
+use crate::util::t;
 use crate::{Build, DocTests};
 
 pub enum Color {
diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs
index 5774a8ecf09..10b846e6db2 100644
--- a/src/bootstrap/format.rs
+++ b/src/bootstrap/format.rs
@@ -1,6 +1,6 @@
 //! Runs rustfmt on the repository.
 
-use crate::build_helper::{output, t};
+use crate::util::{output, t};
 use crate::Build;
 use ignore::WalkBuilder;
 use std::collections::VecDeque;
diff --git a/src/bootstrap/install.rs b/src/bootstrap/install.rs
index 5aa634ea3a7..27b9196d986 100644
--- a/src/bootstrap/install.rs
+++ b/src/bootstrap/install.rs
@@ -8,7 +8,7 @@ use std::fs;
 use std::path::{Component, PathBuf};
 use std::process::Command;
 
-use crate::build_helper::t;
+use crate::util::t;
 
 use crate::dist::{self, sanitize_sh};
 use crate::tarball::GeneratedTarball;
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 81b54c852cd..ccc8516a89a 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -118,12 +118,12 @@ use std::os::windows::fs::symlink_file;
 
 use filetime::FileTime;
 
-use crate::build_helper::{mtime, output, run, run_suppressed, t, try_run, try_run_suppressed};
 use crate::builder::Kind;
 use crate::config::{LlvmLibunwind, TargetSelection};
-use crate::util::{exe, libdir, CiEnv};
+use crate::util::{
+    exe, libdir, mtime, output, run, run_suppressed, t, try_run, try_run_suppressed, CiEnv,
+};
 
-mod build_helper;
 mod builder;
 mod cache;
 mod cc_detect;
diff --git a/src/bootstrap/metadata.rs b/src/bootstrap/metadata.rs
index 08e2b22fc1b..59dc50be47f 100644
--- a/src/bootstrap/metadata.rs
+++ b/src/bootstrap/metadata.rs
@@ -3,8 +3,8 @@ use std::process::Command;
 
 use serde::Deserialize;
 
-use crate::build_helper::output;
 use crate::cache::INTERNER;
+use crate::util::output;
 use crate::{Build, Crate};
 
 #[derive(Deserialize)]
diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs
index 2d7c1ebed6d..d27ad9644b5 100644
--- a/src/bootstrap/native.rs
+++ b/src/bootstrap/native.rs
@@ -16,11 +16,9 @@ use std::io;
 use std::path::{Path, PathBuf};
 use std::process::Command;
 
-use crate::build_helper::up_to_date;
-use crate::build_helper::{output, t};
 use crate::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::config::TargetSelection;
-use crate::util::{self, exe};
+use crate::util::{self, exe, output, t, up_to_date};
 use crate::{CLang, GitRepo};
 
 pub struct Meta {
diff --git a/src/bootstrap/run.rs b/src/bootstrap/run.rs
index a9a784538b5..25abe7a72fd 100644
--- a/src/bootstrap/run.rs
+++ b/src/bootstrap/run.rs
@@ -1,7 +1,7 @@
-use crate::build_helper::output;
 use crate::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::dist::distdir;
 use crate::tool::Tool;
+use crate::util::output;
 use std::process::Command;
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs
index 36c9ba011c4..8c2899c1ac0 100644
--- a/src/bootstrap/sanity.rs
+++ b/src/bootstrap/sanity.rs
@@ -15,9 +15,9 @@ use std::fs;
 use std::path::PathBuf;
 use std::process::Command;
 
-use crate::build_helper::output;
 use crate::cache::INTERNER;
 use crate::config::Target;
+use crate::util::output;
 use crate::Build;
 
 pub struct Finder {
diff --git a/src/bootstrap/tarball.rs b/src/bootstrap/tarball.rs
index 923522b90be..c743c5188e7 100644
--- a/src/bootstrap/tarball.rs
+++ b/src/bootstrap/tarball.rs
@@ -3,8 +3,8 @@ use std::{
     process::Command,
 };
 
-use crate::build_helper::t;
 use crate::builder::Builder;
+use crate::util::t;
 
 #[derive(Copy, Clone)]
 pub(crate) enum OverlayKind {
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 714a2df34a7..e4fcb287f12 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -11,7 +11,6 @@ use std::iter;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 
-use crate::build_helper::{self, output, t};
 use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::Interned;
 use crate::compile;
@@ -21,7 +20,7 @@ use crate::flags::Subcommand;
 use crate::native;
 use crate::tool::{self, SourceType, Tool};
 use crate::toolstate::ToolState;
-use crate::util::{self, add_link_lib_path, dylib_path, dylib_path_var};
+use crate::util::{self, add_link_lib_path, dylib_path, dylib_path_var, output, t};
 use crate::Crate as CargoCrate;
 use crate::{envify, CLang, DocTests, GitRepo, Mode};
 
@@ -2305,9 +2304,7 @@ impl Step for Distcheck {
                 .current_dir(&dir),
         );
         builder.run(
-            Command::new(build_helper::make(&builder.config.build.triple))
-                .arg("check")
-                .current_dir(&dir),
+            Command::new(util::make(&builder.config.build.triple)).arg("check").current_dir(&dir),
         );
 
         // Now make sure that rust-src has all of libstd's dependencies
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index da2fbe4ef65..2ae4d830721 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -4,13 +4,12 @@ use std::fs;
 use std::path::{Path, PathBuf};
 use std::process::{exit, Command};
 
-use crate::build_helper::t;
 use crate::builder::{Builder, Cargo as CargoCommand, RunConfig, ShouldRun, Step};
 use crate::channel::GitInfo;
 use crate::compile;
 use crate::config::TargetSelection;
 use crate::toolstate::ToolState;
-use crate::util::{add_dylib_path, exe};
+use crate::util::{add_dylib_path, exe, t};
 use crate::Compiler;
 use crate::Mode;
 
diff --git a/src/bootstrap/toolstate.rs b/src/bootstrap/toolstate.rs
index 9880f413f04..c7ea254c5b1 100644
--- a/src/bootstrap/toolstate.rs
+++ b/src/bootstrap/toolstate.rs
@@ -1,5 +1,5 @@
-use crate::build_helper::t;
 use crate::builder::{Builder, RunConfig, ShouldRun, Step};
+use crate::util::t;
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::env;
diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs
index 358bbd19810..8e770d4d57f 100644
--- a/src/bootstrap/util.rs
+++ b/src/bootstrap/util.rs
@@ -7,14 +7,38 @@ use std::env;
 use std::fs;
 use std::io;
 use std::path::{Path, PathBuf};
-use std::process::Command;
+use std::process::{Command, Stdio};
 use std::str;
-use std::time::Instant;
+use std::time::{Instant, SystemTime, UNIX_EPOCH};
 
-use crate::build_helper::t;
 use crate::builder::Builder;
 use crate::config::{Config, TargetSelection};
 
+/// A helper macro to `unwrap` a result except also print out details like:
+///
+/// * The file/line of the panic
+/// * The expression that failed
+/// * The error itself
+///
+/// This is currently used judiciously throughout the build system rather than
+/// using a `Result` with `try!`, but this may change one day...
+macro_rules! t {
+    ($e:expr) => {
+        match $e {
+            Ok(e) => e,
+            Err(e) => panic!("{} failed with {}", stringify!($e), e),
+        }
+    };
+    // it can show extra info in the second parameter
+    ($e:expr, $extra:expr) => {
+        match $e {
+            Ok(e) => e,
+            Err(e) => panic!("{} failed with {} ({:?})", stringify!($e), e, $extra),
+        }
+    };
+}
+pub(crate) use t;
+
 /// Given an executable called `name`, return the filename for the
 /// executable for a particular target.
 pub fn exe(name: &str, target: TargetSelection) -> String {
@@ -300,3 +324,119 @@ pub fn is_valid_test_suite_arg<'a, P: AsRef<Path>>(
         _ => None,
     }
 }
+
+pub fn run(cmd: &mut Command, print_cmd_on_fail: bool) {
+    if !try_run(cmd, print_cmd_on_fail) {
+        std::process::exit(1);
+    }
+}
+
+pub fn try_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool {
+    let status = match cmd.status() {
+        Ok(status) => status,
+        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
+    };
+    if !status.success() && print_cmd_on_fail {
+        println!(
+            "\n\ncommand did not execute successfully: {:?}\n\
+             expected success, got: {}\n\n",
+            cmd, status
+        );
+    }
+    status.success()
+}
+
+pub fn run_suppressed(cmd: &mut Command) {
+    if !try_run_suppressed(cmd) {
+        std::process::exit(1);
+    }
+}
+
+pub fn try_run_suppressed(cmd: &mut Command) -> bool {
+    let output = match cmd.output() {
+        Ok(status) => status,
+        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
+    };
+    if !output.status.success() {
+        println!(
+            "\n\ncommand did not execute successfully: {:?}\n\
+             expected success, got: {}\n\n\
+             stdout ----\n{}\n\
+             stderr ----\n{}\n\n",
+            cmd,
+            output.status,
+            String::from_utf8_lossy(&output.stdout),
+            String::from_utf8_lossy(&output.stderr)
+        );
+    }
+    output.status.success()
+}
+
+pub fn make(host: &str) -> PathBuf {
+    if host.contains("dragonfly")
+        || host.contains("freebsd")
+        || host.contains("netbsd")
+        || host.contains("openbsd")
+    {
+        PathBuf::from("gmake")
+    } else {
+        PathBuf::from("make")
+    }
+}
+
+#[track_caller]
+pub fn output(cmd: &mut Command) -> String {
+    let output = match cmd.stderr(Stdio::inherit()).output() {
+        Ok(status) => status,
+        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)),
+    };
+    if !output.status.success() {
+        panic!(
+            "command did not execute successfully: {:?}\n\
+             expected success, got: {}",
+            cmd, output.status
+        );
+    }
+    String::from_utf8(output.stdout).unwrap()
+}
+
+/// Returns the last-modified time for `path`, or zero if it doesn't exist.
+pub fn mtime(path: &Path) -> SystemTime {
+    fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH)
+}
+
+/// Returns `true` if `dst` is up to date given that the file or files in `src`
+/// are used to generate it.
+///
+/// Uses last-modified time checks to verify this.
+pub fn up_to_date(src: &Path, dst: &Path) -> bool {
+    if !dst.exists() {
+        return false;
+    }
+    let threshold = mtime(dst);
+    let meta = match fs::metadata(src) {
+        Ok(meta) => meta,
+        Err(e) => panic!("source {:?} failed to get metadata: {}", src, e),
+    };
+    if meta.is_dir() {
+        dir_up_to_date(src, threshold)
+    } else {
+        meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
+    }
+}
+
+fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
+    t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
+        let meta = t!(e.metadata());
+        if meta.is_dir() {
+            dir_up_to_date(&e.path(), threshold)
+        } else {
+            meta.modified().unwrap_or(UNIX_EPOCH) < threshold
+        }
+    })
+}
+
+fn fail(s: &str) -> ! {
+    println!("\n\n{}\n\n", s);
+    std::process::exit(1);
+}