about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2016-03-29 13:14:52 -0700
committerAlex Crichton <alex@alexcrichton.com>2016-04-12 08:17:42 -0700
commit9dd3c54a2c787c79037f5c772b12c7d2d2f87b18 (patch)
tree4c2f32edacc12823aa53e0a75660f925f78893a2
parentbed32d83fcd1337e962a58fd04fae6b8503e3283 (diff)
downloadrust-9dd3c54a2c787c79037f5c772b12c7d2d2f87b18.tar.gz
rust-9dd3c54a2c787c79037f5c772b12c7d2d2f87b18.zip
rustbuild: Migrate tidy checks to Rust
This commit rewrites all of the tidy checks we have, namely:

* featureck
* errorck
* tidy
* binaries

into Rust under a new `tidy` tool inside of the `src/tools` directory. This at
the same time deletes all the corresponding Python tidy checks so we can be sure
to only have one source of truth for all the tidy checks.

cc #31590
-rw-r--r--mk/tests.mk54
-rw-r--r--src/bootstrap/build/check.rs7
-rw-r--r--src/bootstrap/build/mod.rs6
-rw-r--r--src/bootstrap/build/step.rs9
-rw-r--r--src/bootstrap/mk/Makefile.in2
-rwxr-xr-xsrc/etc/check-binaries.py20
-rw-r--r--src/etc/errorck.py136
-rw-r--r--src/etc/featureck.py251
-rw-r--r--src/etc/licenseck.py56
-rw-r--r--src/etc/tidy.py230
-rw-r--r--src/librustc_incremental/persist/serialize.rs0
-rw-r--r--[-rwxr-xr-x]src/test/auxiliary/specialization_cross_crate_defaults.rs0
-rw-r--r--src/test/compile-fail/regions-wf-trait-object.rs2
-rw-r--r--[-rwxr-xr-x]src/test/compile-fail/specialization/specialization-polarity.rs0
-rw-r--r--src/test/run-make/compiler-lookup-paths/native.c9
-rw-r--r--src/test/run-pass/issue-11577.rs2
-rw-r--r--src/test/run-pass/issue-9382.rs2
-rw-r--r--src/tools/tidy/Cargo.lock4
-rw-r--r--src/tools/tidy/Cargo.toml6
-rw-r--r--src/tools/tidy/src/bins.rs45
-rw-r--r--src/tools/tidy/src/errors.rs90
-rw-r--r--src/tools/tidy/src/features.rs155
-rw-r--r--src/tools/tidy/src/main.rs76
-rw-r--r--src/tools/tidy/src/style.rs127
24 files changed, 547 insertions, 742 deletions
diff --git a/mk/tests.mk b/mk/tests.mk
index 20736165b73..87cfed2426e 100644
--- a/mk/tests.mk
+++ b/mk/tests.mk
@@ -240,52 +240,16 @@ cleantestlibs:
 # Tidy
 ######################################################################
 
-ifdef CFG_NOTIDY
 .PHONY: tidy
-tidy:
-else
-
-# Run the tidy script in multiple parts to avoid huge 'echo' commands
-.PHONY: tidy
-tidy: tidy-basic tidy-binaries tidy-errors tidy-features
-
-endif
-
-.PHONY: tidy-basic
-tidy-basic:
-		@$(call E, check: formatting)
-		$(Q) $(CFG_PYTHON) $(S)src/etc/tidy.py $(S)src/
-
-.PHONY: tidy-binaries
-tidy-binaries:
-		@$(call E, check: binaries)
-		$(Q)find $(S)src -type f \
-		    \( -perm -u+x -or -perm -g+x -or -perm -o+x \) \
-		    -not -name '*.rs' -and -not -name '*.py' \
-		    -and -not -name '*.sh' -and -not -name '*.pp' \
-		| grep '^$(S)src/jemalloc' -v \
-		| grep '^$(S)src/libuv' -v \
-		| grep '^$(S)src/llvm' -v \
-		| grep '^$(S)src/rt/hoedown' -v \
-		| grep '^$(S)src/gyp' -v \
-		| grep '^$(S)src/etc' -v \
-		| grep '^$(S)src/doc' -v \
-		| grep '^$(S)src/compiler-rt' -v \
-		| grep '^$(S)src/libbacktrace' -v \
-		| grep '^$(S)src/rust-installer' -v \
-		| grep '^$(S)src/liblibc' -v \
-		| xargs $(CFG_PYTHON) $(S)src/etc/check-binaries.py
-
-.PHONY: tidy-errors
-tidy-errors:
-		@$(call E, check: extended errors)
-		$(Q) $(CFG_PYTHON) $(S)src/etc/errorck.py $(S)src/
-
-.PHONY: tidy-features
-tidy-features:
-		@$(call E, check: feature sanity)
-		$(Q) $(CFG_PYTHON) $(S)src/etc/featureck.py $(S)src/
-
+tidy: $(HBIN0_H_$(CFG_BUILD))/tidy$(X_$(CFG_BUILD))
+	$< $(S)src
+
+$(HBIN0_H_$(CFG_BUILD))/tidy$(X_$(CFG_BUILD)): \
+		$(TSREQ0_T_$(CFG_BUILD)_H_$(CFG_BUILD)) \
+		$(TLIB0_T_$(CFG_BUILD)_H_$(CFG_BUILD))/stamp.std \
+		$(call rwildcard,$(S)src/tools/tidy/src,*.rs)
+	$(STAGE0_T_$(CFG_BUILD)_H_$(CFG_BUILD)) src/tools/tidy/src/main.rs \
+		--out-dir $(@D) --crate-name tidy
 
 ######################################################################
 # Sets of tests
diff --git a/src/bootstrap/build/check.rs b/src/bootstrap/build/check.rs
index a2445ae498a..d4e344fc482 100644
--- a/src/bootstrap/build/check.rs
+++ b/src/bootstrap/build/check.rs
@@ -33,3 +33,10 @@ pub fn cargotest(build: &Build, stage: u32, host: &str) {
               .env("PATH", newpath)
               .arg(&build.cargo));
 }
+
+pub fn tidy(build: &Build, stage: u32, host: &str) {
+    println!("tidy check stage{} ({})", stage, host);
+    let compiler = Compiler::new(stage, host);
+    build.run(build.tool_cmd(&compiler, "tidy")
+                   .arg(build.src.join("src")));
+}
diff --git a/src/bootstrap/build/mod.rs b/src/bootstrap/build/mod.rs
index 248bf6cb4ea..4f0bfb84344 100644
--- a/src/bootstrap/build/mod.rs
+++ b/src/bootstrap/build/mod.rs
@@ -197,6 +197,9 @@ impl Build {
                 ToolCargoTest { stage } => {
                     compile::tool(self, stage, target.target, "cargotest");
                 }
+                ToolTidy { stage } => {
+                    compile::tool(self, stage, target.target, "tidy");
+                }
                 DocBook { stage } => {
                     doc::rustbook(self, stage, target.target, "book", &doc_out);
                 }
@@ -230,6 +233,9 @@ impl Build {
                 CheckCargoTest { stage } => {
                     check::cargotest(self, stage, target.target);
                 }
+                CheckTidy { stage } => {
+                    check::tidy(self, stage, target.target);
+                }
 
                 DistDocs { stage } => dist::docs(self, stage, target.target),
                 DistMingw { _dummy } => dist::mingw(self, target.target),
diff --git a/src/bootstrap/build/step.rs b/src/bootstrap/build/step.rs
index 80fcc32e537..626cf758631 100644
--- a/src/bootstrap/build/step.rs
+++ b/src/bootstrap/build/step.rs
@@ -51,6 +51,7 @@ macro_rules! targets {
             (tool_rustbook, ToolRustbook { stage: u32 }),
             (tool_error_index, ToolErrorIndex { stage: u32 }),
             (tool_cargotest, ToolCargoTest { stage: u32 }),
+            (tool_tidy, ToolTidy { stage: u32 }),
 
             // Steps for long-running native builds. Ideally these wouldn't
             // actually exist and would be part of build scripts, but for now
@@ -79,6 +80,7 @@ macro_rules! targets {
             (check, Check { stage: u32, compiler: Compiler<'a> }),
             (check_linkcheck, CheckLinkcheck { stage: u32 }),
             (check_cargotest, CheckCargoTest { stage: u32 }),
+            (check_tidy, CheckTidy { stage: u32 }),
 
             // Distribution targets, creating tarballs
             (dist, Dist { stage: u32 }),
@@ -316,8 +318,13 @@ impl<'a> Step<'a> {
             Source::CheckCargoTest { stage } => {
                 vec![self.tool_cargotest(stage)]
             }
+            Source::CheckTidy { stage } => {
+                vec![self.tool_tidy(stage)]
+            }
 
-            Source::ToolLinkchecker { stage } => {
+            Source::ToolLinkchecker { stage } |
+            Source::ToolTidy { stage } |
+            Source::ToolCargoTest { stage } => {
                 vec![self.libstd(self.compiler(stage))]
             }
             Source::ToolErrorIndex { stage } |
diff --git a/src/bootstrap/mk/Makefile.in b/src/bootstrap/mk/Makefile.in
index 9199c755f60..1f3ea8f19bb 100644
--- a/src/bootstrap/mk/Makefile.in
+++ b/src/bootstrap/mk/Makefile.in
@@ -42,5 +42,7 @@ check-cargotest:
 	$(Q)$(BOOTSTRAP) --step check-cargotest
 dist:
 	$(Q)$(BOOTSTRAP) --step dist
+tidy:
+	$(Q)$(BOOTSTRAP) --step check-tidy --stage 0
 
 .PHONY: dist
diff --git a/src/etc/check-binaries.py b/src/etc/check-binaries.py
deleted file mode 100755
index 91c01b17807..00000000000
--- a/src/etc/check-binaries.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
-# file at the top-level directory of this distribution and at
-# http://rust-lang.org/COPYRIGHT.
-#
-# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-# option. This file may not be copied, modified, or distributed
-# except according to those terms.
-
-import sys
-
-offenders = sys.argv[1:]
-if len(offenders) > 0:
-    print("Binaries checked into src:")
-    for offender in offenders:
-        print(offender)
-    sys.exit(1)
diff --git a/src/etc/errorck.py b/src/etc/errorck.py
deleted file mode 100644
index 1f5f3784ac6..00000000000
--- a/src/etc/errorck.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
-# file at the top-level directory of this distribution and at
-# http://rust-lang.org/COPYRIGHT.
-#
-# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-# option. This file may not be copied, modified, or distributed
-# except according to those terms.
-
-# Digs error codes out of files named 'diagnostics.rs' across
-# the tree, and ensures thare are no duplicates.
-
-import sys
-import os
-import re
-
-if len(sys.argv) < 2:
-    print("usage: errorck.py <src-dir>")
-    sys.exit(1)
-
-src_dir = sys.argv[1]
-errcode_map = {}
-errcode_checked = []
-errcode_not_found = []
-error_re = re.compile("(E\d\d\d\d)")
-
-def check_unused_error_codes(error_codes, check_error_codes, filenames, dirnames, dirpath):
-    for filename in filenames:
-        if filename == "diagnostics.rs" or not filename.endswith(".rs"):
-            continue
-        path = os.path.join(dirpath, filename)
-
-        with open(path, 'r') as f:
-            for line in f:
-                match = error_re.search(line)
-                if match:
-                    errcode = match.group(1)
-                    if errcode in error_codes:
-                        error_codes.remove(errcode)
-                    if errcode not in check_error_codes:
-                        check_error_codes.append(errcode)
-    for dirname in dirnames:
-        path = os.path.join(dirpath, dirname)
-        for (dirpath, dnames, fnames) in os.walk(path):
-            check_unused_error_codes(error_codes, check_error_codes, fnames, dnames, dirpath)
-
-
-# In the register_long_diagnostics! macro, entries look like this:
-#
-# EXXXX: r##"
-# <Long diagnostic message>
-# "##,
-#
-# These two variables are for detecting the beginning and end of diagnostic
-# messages so that duplicate error codes are not reported when a code occurs
-# inside a diagnostic message
-long_diag_begin = "r##\""
-long_diag_end = "\"##"
-
-errors = False
-all_errors = []
-
-for (dirpath, dirnames, filenames) in os.walk(src_dir):
-    if "src/test" in dirpath or "src/llvm" in dirpath:
-        # Short circuit for fast
-        continue
-
-    errcode_to_check = []
-    for filename in filenames:
-        if filename != "diagnostics.rs":
-            continue
-        path = os.path.join(dirpath, filename)
-
-        with open(path, 'r') as f:
-            inside_long_diag = False
-            errcode_to_check = []
-            for line_num, line in enumerate(f, start=1):
-                if inside_long_diag:
-                    # Skip duplicate error code checking for this line
-                    if long_diag_end in line:
-                        inside_long_diag = False
-                    continue
-
-                match = error_re.search(line)
-                if match:
-                    errcode = match.group(1)
-                    new_record = [(errcode, path, line_num, line)]
-                    existing = errcode_map.get(errcode)
-                    if existing is not None:
-                        # This is a dupe
-                        errcode_map[errcode] = existing + new_record
-                    else:
-                        errcode_map[errcode] = new_record
-                        # we don't check if this is a long error explanation
-                        if (long_diag_begin not in line and not line.strip().startswith("//")
-                            and errcode not in errcode_to_check and errcode not in errcode_checked
-                            and errcode not in errcode_not_found):
-                            errcode_to_check.append(errcode)
-
-                if long_diag_begin in line:
-                    inside_long_diag = True
-        break
-    check_unused_error_codes(errcode_to_check, errcode_checked, filenames, dirnames, dirpath)
-    if len(errcode_to_check) > 0:
-        for errcode in errcode_to_check:
-            if errcode in errcode_checked:
-                continue
-            errcode_not_found.append(errcode)
-
-if len(errcode_not_found) > 0:
-    errcode_not_found.sort()
-    for errcode in errcode_not_found:
-        if errcode in errcode_checked:
-            continue
-        all_errors.append(errcode)
-        print("error: unused error code: {0} ({1}:{2})".format(*errcode_map[errcode][0]))
-        errors = True
-
-
-for errcode, entries in errcode_map.items():
-    all_errors.append(entries[0][0])
-    if len(entries) > 1:
-        entries.sort()
-        print("error: duplicate error code " + errcode)
-        for entry in entries:
-            print("{1}: {2}\n{3}".format(*entry))
-        errors = True
-
-print
-print("* {0} error codes".format(len(errcode_map)))
-print("* highest error code: " + max(all_errors))
-print
-
-if errors:
-    sys.exit(1)
diff --git a/src/etc/featureck.py b/src/etc/featureck.py
deleted file mode 100644
index d6cc25177e4..00000000000
--- a/src/etc/featureck.py
+++ /dev/null
@@ -1,251 +0,0 @@
-# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
-# file at the top-level directory of this distribution and at
-# http://rust-lang.org/COPYRIGHT.
-#
-# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-# option. This file may not be copied, modified, or distributed
-# except according to those terms.
-
-# This script does a tree-wide sanity checks against stability
-# attributes, currently:
-#   * For all feature_name/level pairs the 'since' field is the same
-#   * That no features are both stable and unstable.
-#   * That lib features don't have the same name as lang features
-#     unless they are on the 'joint_features' whitelist
-#   * That features that exist in both lang and lib and are stable
-#     since the same version
-#   * Prints information about features
-
-import sys
-import os
-import re
-import codecs
-
-if len(sys.argv) < 2:
-    print("usage: featureck.py <src-dir>")
-    sys.exit(1)
-
-src_dir = sys.argv[1]
-
-# Features that are allowed to exist in both the language and the library
-joint_features = [ ]
-
-# Grab the list of language features from the compiler
-language_gate_statuses = [ "Active", "Deprecated", "Removed", "Accepted" ]
-feature_gate_source = os.path.join(src_dir, "libsyntax", "feature_gate.rs")
-language_features = []
-language_feature_names = []
-with open(feature_gate_source, 'r') as f:
-    for line in f:
-        original_line = line
-        line = line.strip()
-        is_feature_line = False
-        for status in language_gate_statuses:
-            if status in line and line.startswith("("):
-                is_feature_line = True
-
-        if is_feature_line:
-            # turn `    ("foo", "1.0.0", Some(10), Active)` into
-            # `"foo", "1.0.0", Some(10), Active`
-            line = line.strip(' ,()')
-            parts = line.split(",")
-            if len(parts) != 4:
-                print("error: unexpected number of components in line: " + original_line)
-                sys.exit(1)
-            feature_name = parts[0].strip().replace('"', "")
-            since = parts[1].strip().replace('"', "")
-            issue = parts[2].strip()
-            status = parts[3].strip()
-            assert len(feature_name) > 0
-            assert len(since) > 0
-            assert len(issue) > 0
-            assert len(status) > 0
-
-            language_feature_names += [feature_name]
-            language_features += [(feature_name, since, issue, status)]
-
-assert len(language_features) > 0
-
-errors = False
-
-lib_features = { }
-lib_features_and_level = { }
-for (dirpath, dirnames, filenames) in os.walk(src_dir):
-    # Don't look for feature names in tests
-    if "src/test" in dirpath:
-        continue
-
-    # Takes a long time to traverse LLVM
-    if "src/llvm" in dirpath:
-        continue
-
-    for filename in filenames:
-        if not filename.endswith(".rs"):
-            continue
-
-        path = os.path.join(dirpath, filename)
-        with codecs.open(filename=path, mode='r', encoding="utf-8") as f:
-            line_num = 0
-            for line in f:
-                line_num += 1
-                level = None
-                if "[unstable(" in line:
-                    level = "unstable"
-                elif "[stable(" in line:
-                    level = "stable"
-                else:
-                    continue
-
-                # This is a stability attribute. For the purposes of this
-                # script we expect both the 'feature' and 'since' attributes on
-                # the same line, e.g.
-                # `#[unstable(feature = "foo", since = "1.0.0")]`
-
-                p = re.compile('(unstable|stable).*feature *= *"(\w*)"')
-                m = p.search(line)
-                if not m is None:
-                    feature_name = m.group(2)
-                    since = None
-                    if re.compile("\[ *stable").search(line) is not None:
-                        pp = re.compile('since *= *"([\w\.]*)"')
-                        mm = pp.search(line)
-                        if not mm is None:
-                            since = mm.group(1)
-                        else:
-                            print("error: misformed stability attribute")
-                            print("line %d of %:" % (line_num, path))
-                            print(line)
-                            errors = True
-
-                    lib_features[feature_name] = feature_name
-                    if lib_features_and_level.get((feature_name, level)) is None:
-                        # Add it to the observed features
-                        lib_features_and_level[(feature_name, level)] = \
-                            (since, path, line_num, line)
-                    else:
-                        # Verify that for this combination of feature_name and level the 'since'
-                        # attribute matches.
-                        (expected_since, source_path, source_line_num, source_line) = \
-                            lib_features_and_level.get((feature_name, level))
-                        if since != expected_since:
-                            print("error: mismatch in %s feature '%s'" % (level, feature_name))
-                            print("line %d of %s:" % (source_line_num, source_path))
-                            print(source_line)
-                            print("line %d of %s:" % (line_num, path))
-                            print(line)
-                            errors = True
-
-                    # Verify that this lib feature doesn't duplicate a lang feature
-                    if feature_name in language_feature_names:
-                        print("error: lib feature '%s' duplicates a lang feature" % (feature_name))
-                        print("line %d of %s:" % (line_num, path))
-                        print(line)
-                        errors = True
-
-                else:
-                    print("error: misformed stability attribute")
-                    print("line %d of %s:" % (line_num, path))
-                    print(line)
-                    errors = True
-
-# Merge data about both lists
-# name, lang, lib, status, stable since
-
-language_feature_stats = {}
-
-for f in language_features:
-    name = f[0]
-    lang = True
-    lib = False
-    status = "unstable"
-    stable_since = None
-
-    if f[3] == "Accepted":
-        status = "stable"
-    if status == "stable":
-        stable_since = f[1]
-
-    language_feature_stats[name] = (name, lang, lib, status, stable_since)
-
-lib_feature_stats = {}
-
-for f in lib_features:
-    name = f
-    lang = False
-    lib = True
-    status = "unstable"
-    stable_since = None
-
-    is_stable = lib_features_and_level.get((name, "stable")) is not None
-    is_unstable = lib_features_and_level.get((name, "unstable")) is not None
-
-    if is_stable and is_unstable:
-        print("error: feature '%s' is both stable and unstable" % (name))
-        errors = True
-
-    if is_stable:
-        status = "stable"
-        stable_since = lib_features_and_level[(name, "stable")][0]
-    elif is_unstable:
-        status = "unstable"
-
-    lib_feature_stats[name] = (name, lang, lib, status, stable_since)
-
-# Check for overlap in two sets
-merged_stats = { }
-
-for name in lib_feature_stats:
-    if language_feature_stats.get(name) is not None:
-        if not name in joint_features:
-            print("error: feature '%s' is both a lang and lib feature but not whitelisted" % (name))
-            errors = True
-        lang_status = language_feature_stats[name][3]
-        lib_status = lib_feature_stats[name][3]
-        lang_stable_since = language_feature_stats[name][4]
-        lib_stable_since = lib_feature_stats[name][4]
-
-        if lang_status != lib_status and lib_status != "rustc_deprecated":
-            print("error: feature '%s' has lang status %s " +
-                  "but lib status %s" % (name, lang_status, lib_status))
-            errors = True
-
-        if lang_stable_since != lib_stable_since:
-            print("error: feature '%s' has lang stable since %s " +
-                  "but lib stable since %s" % (name, lang_stable_since, lib_stable_since))
-            errors = True
-
-        merged_stats[name] = (name, True, True, lang_status, lang_stable_since)
-
-        del language_feature_stats[name]
-        del lib_feature_stats[name]
-
-if errors:
-    sys.exit(1)
-
-# Finally, display the stats
-stats = {}
-stats.update(language_feature_stats)
-stats.update(lib_feature_stats)
-stats.update(merged_stats)
-lines = []
-for s in stats:
-    s = stats[s]
-    type_ = "lang"
-    if s[1] and s[2]:
-        type_ = "lang/lib"
-    elif s[2]:
-        type_ = "lib"
-    line = "{: <32}".format(s[0]) + \
-           "{: <8}".format(type_) + \
-           "{: <12}".format(s[3]) + \
-           "{: <8}".format(str(s[4]))
-    lines += [line]
-
-lines.sort()
-
-print
-for line in lines:
-    print("* " + line)
-print
diff --git a/src/etc/licenseck.py b/src/etc/licenseck.py
deleted file mode 100644
index aa2a00beae5..00000000000
--- a/src/etc/licenseck.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
-# file at the top-level directory of this distribution and at
-# http://rust-lang.org/COPYRIGHT.
-#
-# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-# option. This file may not be copied, modified, or distributed
-# except according to those terms.
-
-import re
-import os
-
-license_re = re.compile(
-u"""(#|//) Copyright .* The Rust Project Developers. See the COPYRIGHT
-\\1 file at the top-level directory of this distribution and at
-\\1 http://rust-lang.org/COPYRIGHT.
-\\1
-\\1 Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-\\1 http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-\\1 <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-\\1 option. This file may not be copied, modified, or distributed
-\\1 except according to those terms.""")
-
-exceptions = [
-    "libstd/sync/mpsc/mpsc_queue.rs", # BSD
-    "libstd/sync/mpsc/spsc_queue.rs", # BSD
-    "test/bench/shootout-binarytrees.rs", # BSD
-    "test/bench/shootout-chameneos-redux.rs", # BSD
-    "test/bench/shootout-fannkuch-redux.rs", # BSD
-    "test/bench/shootout-fasta.rs", # BSD
-    "test/bench/shootout-fasta-redux.rs", # BSD
-    "test/bench/shootout-k-nucleotide.rs", # BSD
-    "test/bench/shootout-mandelbrot.rs", # BSD
-    "test/bench/shootout-meteor.rs", # BSD
-    "test/bench/shootout-nbody.rs", # BSD
-    "test/bench/shootout-regex-dna.rs", # BSD
-    "test/bench/shootout-reverse-complement.rs", # BSD
-    "test/bench/shootout-spectralnorm.rs", # BSD
-    "test/bench/shootout-threadring.rs", # BSD
-]
-
-def check_license(name, contents):
-    name = os.path.normpath(name)
-    # Whitelist check
-    if any(name.endswith(os.path.normpath(e)) for e in exceptions):
-        return True
-
-    # Xfail check
-    firstlineish = contents[:100]
-    if "ignore-license" in firstlineish:
-        return True
-
-    # License check
-    boilerplate = contents[:500]
-    return bool(license_re.search(boilerplate))
diff --git a/src/etc/tidy.py b/src/etc/tidy.py
deleted file mode 100644
index 9264646673b..00000000000
--- a/src/etc/tidy.py
+++ /dev/null
@@ -1,230 +0,0 @@
-# Copyright 2010-2014 The Rust Project Developers. See the COPYRIGHT
-# file at the top-level directory of this distribution and at
-# http://rust-lang.org/COPYRIGHT.
-#
-# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-# option. This file may not be copied, modified, or distributed
-# except according to those terms.
-
-import sys
-import fileinput
-import subprocess
-import re
-import os
-from licenseck import check_license
-import snapshot
-
-err = 0
-cols = 100
-cr_flag = "ignore-tidy-cr"
-tab_flag = "ignore-tidy-tab"
-linelength_flag = "ignore-tidy-linelength"
-
-interesting_files = ['.rs', '.py', '.js', '.sh', '.c', '.h']
-uninteresting_files = ['miniz.c', 'jquery', 'rust_android_dummy']
-stable_whitelist = {
-    'src/bootstrap',
-    'src/build_helper',
-    'src/libcollectionstest',
-    'src/libcore',
-    'src/libstd',
-    'src/rustc/std_shim',
-    'src/rustc/test_shim',
-    'src/test'
-}
-
-
-def report_error_name_no(name, no, s):
-    global err
-    print("%s:%d: %s" % (name, no, s))
-    err = 1
-
-
-def report_err(s):
-    report_error_name_no(fileinput.filename(), fileinput.filelineno(), s)
-
-
-def report_warn(s):
-    print("%s:%d: %s" % (fileinput.filename(),
-                         fileinput.filelineno(),
-                         s))
-
-
-def do_license_check(name, contents):
-    if not check_license(name, contents):
-        report_error_name_no(name, 1, "incorrect license")
-
-
-def update_counts(current_name):
-    global file_counts
-    global count_other_linted_files
-
-    _, ext = os.path.splitext(current_name)
-
-    if ext in interesting_files:
-        file_counts[ext] += 1
-    else:
-        count_other_linted_files += 1
-
-
-def interesting_file(f):
-    if any(x in f for x in uninteresting_files):
-        return False
-
-    return any(os.path.splitext(f)[1] == ext for ext in interesting_files)
-
-
-# Be careful to support Python 2.4, 2.6, and 3.x here!
-config_proc = subprocess.Popen(["git", "config", "core.autocrlf"],
-                               stdout=subprocess.PIPE)
-result = config_proc.communicate()[0]
-
-true = "true".encode('utf8')
-autocrlf = result.strip() == true if result is not None else False
-
-current_name = ""
-current_contents = ""
-check_tab = True
-check_cr = True
-check_linelength = True
-
-if len(sys.argv) < 2:
-    print("usage: tidy.py <src-dir>")
-    sys.exit(1)
-
-src_dir = sys.argv[1]
-
-count_lines = 0
-count_non_blank_lines = 0
-count_other_linted_files = 0
-
-file_counts = {ext: 0 for ext in interesting_files}
-
-all_paths = set()
-needs_unstable_attr = set()
-
-try:
-    for (dirpath, dirnames, filenames) in os.walk(src_dir):
-        # Skip some third-party directories
-        skippable_dirs = {
-            'src/jemalloc',
-            'src/llvm',
-            'src/gyp',
-            'src/libbacktrace',
-            'src/libuv',
-            'src/compiler-rt',
-            'src/rt/hoedown',
-            'src/rustllvm',
-            'src/rt/valgrind',
-            'src/rt/msvc',
-            'src/rust-installer',
-            'src/liblibc',
-        }
-
-        dirpath = os.path.normpath(dirpath)
-        if any(os.path.normpath(d) in dirpath for d in skippable_dirs):
-            continue
-
-        file_names = [os.path.join(dirpath, f) for f in filenames
-                      if interesting_file(f)
-                      and not f.endswith("_gen.rs")
-                      and not ".#" is f]
-
-        if not file_names:
-            continue
-
-        for line in fileinput.input(file_names,
-                                openhook=fileinput.hook_encoded("utf-8")):
-
-            filename = fileinput.filename()
-
-            if "tidy.py" not in filename:
-                if "TODO" in line:
-                    report_err("TODO is deprecated; use FIXME")
-                match = re.match(r'^.*/(\*|/!?)\s*XXX', line)
-                if match:
-                    report_err("XXX is no longer necessary, use FIXME")
-                match = re.match(r'^.*//\s*(NOTE.*)$', line)
-                if match and "TRAVIS" not in os.environ:
-                    m = match.group(1)
-                    if "snap" in m.lower():
-                        report_warn(match.group(1))
-                match = re.match(r'^.*//\s*SNAP\s+(\w+)', line)
-                if match:
-                    hsh = match.group(1)
-                    date, rev = snapshot.curr_snapshot_rev()
-                    if not hsh.startswith(rev):
-                        report_err("snapshot out of date (" + date
-                            + "): " + line)
-                else:
-                    if "SNAP " in line:
-                        report_warn("unmatched SNAP line: " + line)
-                search = re.search(r'^#!\[unstable', line)
-                if search:
-                    needs_unstable_attr.discard(filename)
-
-            if cr_flag in line:
-                check_cr = False
-            if tab_flag in line:
-                check_tab = False
-            if linelength_flag in line:
-                check_linelength = False
-
-            if check_tab and ('\t' in line and
-                              "Makefile" not in filename):
-                report_err("tab character")
-            if check_cr and not autocrlf and '\r' in line:
-                report_err("CR character")
-            if line.endswith(" \n") or line.endswith("\t\n"):
-                report_err("trailing whitespace")
-            line_len = len(line)-2 if autocrlf else len(line)-1
-
-            if check_linelength and line_len > cols:
-                report_err("line longer than %d chars" % cols)
-
-            if fileinput.isfirstline():
-                # This happens at the end of each file except the last.
-                if current_name != "":
-                    update_counts(current_name)
-                    assert len(current_contents) > 0
-                    do_license_check(current_name, current_contents)
-
-                current_name = filename
-                current_contents = ""
-                check_cr = True
-                check_tab = True
-                check_linelength = True
-                if all(f not in filename for f in stable_whitelist) and \
-                   re.search(r'src/.*/lib\.rs', filename):
-                    needs_unstable_attr.add(filename)
-
-            # Put a reasonable limit on the amount of header data we use for
-            # the licenseck
-            if len(current_contents) < 1000:
-                current_contents += line
-
-            count_lines += 1
-            if line.strip():
-                count_non_blank_lines += 1
-
-    if current_name != "":
-        update_counts(current_name)
-        assert len(current_contents) > 0
-        do_license_check(current_name, current_contents)
-    for f in needs_unstable_attr:
-        report_error_name_no(f, 1, "requires unstable attribute")
-
-except UnicodeDecodeError as e:
-    report_err("UTF-8 decoding error " + str(e))
-
-print
-for ext in sorted(file_counts, key=file_counts.get, reverse=True):
-    print("* linted {} {} files".format(file_counts[ext], ext))
-print("* linted {} other files".format(count_other_linted_files))
-print("* total lines of code: {}".format(count_lines))
-print("* total non-blank lines of code: {}".format(count_non_blank_lines))
-print()
-
-sys.exit(err)
diff --git a/src/librustc_incremental/persist/serialize.rs b/src/librustc_incremental/persist/serialize.rs
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/librustc_incremental/persist/serialize.rs
+++ /dev/null
diff --git a/src/test/auxiliary/specialization_cross_crate_defaults.rs b/src/test/auxiliary/specialization_cross_crate_defaults.rs
index b62d80b589f..b62d80b589f 100755..100644
--- a/src/test/auxiliary/specialization_cross_crate_defaults.rs
+++ b/src/test/auxiliary/specialization_cross_crate_defaults.rs
diff --git a/src/test/compile-fail/regions-wf-trait-object.rs b/src/test/compile-fail/regions-wf-trait-object.rs
index 40b715cf3b1..39b8482cfa3 100644
--- a/src/test/compile-fail/regions-wf-trait-object.rs
+++ b/src/test/compile-fail/regions-wf-trait-object.rs
@@ -6,7 +6,7 @@
 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 // option. This file may not be copied, modified, or distributed
-// except according to those terms.t
+// except according to those terms.
 
 // Check that the explicit lifetime bound (`'b`, in this example) must
 // outlive all the superbound from the trait (`'a`, in this example).
diff --git a/src/test/compile-fail/specialization/specialization-polarity.rs b/src/test/compile-fail/specialization/specialization-polarity.rs
index 27a3e31491b..27a3e31491b 100755..100644
--- a/src/test/compile-fail/specialization/specialization-polarity.rs
+++ b/src/test/compile-fail/specialization/specialization-polarity.rs
diff --git a/src/test/run-make/compiler-lookup-paths/native.c b/src/test/run-make/compiler-lookup-paths/native.c
index e69de29bb2d..30669470522 100644
--- a/src/test/run-make/compiler-lookup-paths/native.c
+++ b/src/test/run-make/compiler-lookup-paths/native.c
@@ -0,0 +1,9 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
diff --git a/src/test/run-pass/issue-11577.rs b/src/test/run-pass/issue-11577.rs
index c1997fac74b..a64fbb6afd6 100644
--- a/src/test/run-pass/issue-11577.rs
+++ b/src/test/run-pass/issue-11577.rs
@@ -1,4 +1,4 @@
- // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
 // file at the top-level directory of this distribution and at
 // http://rust-lang.org/COPYRIGHT.
 //
diff --git a/src/test/run-pass/issue-9382.rs b/src/test/run-pass/issue-9382.rs
index 2c84e202b26..fb7ffdcd515 100644
--- a/src/test/run-pass/issue-9382.rs
+++ b/src/test/run-pass/issue-9382.rs
@@ -1,6 +1,6 @@
 // pretty-expanded FIXME #23616
 
- // Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
 // file at the top-level directory of this distribution and at
 // http://rust-lang.org/COPYRIGHT.
 //
diff --git a/src/tools/tidy/Cargo.lock b/src/tools/tidy/Cargo.lock
new file mode 100644
index 00000000000..acaf9e5550f
--- /dev/null
+++ b/src/tools/tidy/Cargo.lock
@@ -0,0 +1,4 @@
+[root]
+name = "tidy"
+version = "0.1.0"
+
diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml
new file mode 100644
index 00000000000..e900bd47fb7
--- /dev/null
+++ b/src/tools/tidy/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "tidy"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+
+[dependencies]
diff --git a/src/tools/tidy/src/bins.rs b/src/tools/tidy/src/bins.rs
new file mode 100644
index 00000000000..84d3ff2b238
--- /dev/null
+++ b/src/tools/tidy/src/bins.rs
@@ -0,0 +1,45 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Tidy check to ensure that there are no binaries checked into the source tree
+//! by accident.
+//!
+//! In the past we've accidentally checked in test binaries and such which add a
+//! huge amount of bloat to the git history, so it's good to just ensure we
+//! don't do that again :)
+
+use std::path::Path;
+
+// All files are executable on Windows, so just check on Unix
+#[cfg(windows)]
+pub fn check(_path: &Path, _bad: &mut bool) {}
+
+#[cfg(unix)]
+pub fn check(path: &Path, bad: &mut bool) {
+    use std::fs;
+    use std::os::unix::prelude::*;
+
+    super::walk(path,
+                &mut |path| super::filter_dirs(path) || path.ends_with("src/etc"),
+                &mut |file| {
+        let filename = file.file_name().unwrap().to_string_lossy();
+        let extensions = [".py", ".sh"];
+        if extensions.iter().any(|e| filename.ends_with(e)) {
+            return
+        }
+
+        let metadata = t!(fs::metadata(&file));
+        if metadata.mode() & 0o111 != 0 {
+            println!("binary checked into source: {}", file.display());
+            *bad = true;
+        }
+    })
+}
+
diff --git a/src/tools/tidy/src/errors.rs b/src/tools/tidy/src/errors.rs
new file mode 100644
index 00000000000..21ec9270429
--- /dev/null
+++ b/src/tools/tidy/src/errors.rs
@@ -0,0 +1,90 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Tidy check to verify the validity of long error diagnostic codes.
+//!
+//! This ensures that error codes are used at most once and also prints out some
+//! statistics about the error codes.
+
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::prelude::*;
+use std::path::Path;
+
+pub fn check(path: &Path, bad: &mut bool) {
+    let mut contents = String::new();
+    let mut map = HashMap::new();
+    super::walk(path,
+                &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
+                &mut |file| {
+        let filename = file.file_name().unwrap().to_string_lossy();
+        if filename != "diagnostics.rs" {
+            return
+        }
+
+        contents.truncate(0);
+        t!(t!(File::open(file)).read_to_string(&mut contents));
+
+        // In the register_long_diagnostics! macro, entries look like this:
+        //
+        // EXXXX: r##"
+        // <Long diagnostic message>
+        // "##,
+        //
+        // and these long messages often have error codes themselves inside
+        // them, but we don't want to report duplicates in these cases. This
+        // variable keeps track of whether we're currently inside one of these
+        // long diagnostic messages.
+        let mut inside_long_diag = false;
+        for (num, line) in contents.lines().enumerate() {
+            if inside_long_diag {
+                inside_long_diag = !line.contains("\"##");
+                continue
+            }
+
+            let mut search = line;
+            while let Some(i) = search.find("E") {
+                search = &search[i + 1..];
+                let code = if search.len() > 4 {
+                    search[..4].parse::<u32>()
+                } else {
+                    continue
+                };
+                let code = match code {
+                    Ok(n) => n,
+                    Err(..) => continue,
+                };
+                map.entry(code).or_insert(Vec::new())
+                   .push((file.to_owned(), num + 1, line.to_owned()));
+                break
+            }
+
+            inside_long_diag = line.contains("r##\"");
+        }
+    });
+
+    let mut max = 0;
+    println!("* {} error codes", map.len());
+    for (code, entries) in map {
+        if code > max {
+            max = code;
+        }
+        if entries.len() == 1 {
+            continue
+        }
+
+        println!("duplicate error code: {}", code);
+        for (file, line_num, line) in entries {
+            println!("{}:{}: {}", file.display(), line_num, line);
+        }
+        *bad = true;
+    }
+    println!("* highest error code: E{:04}", max);
+}
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
new file mode 100644
index 00000000000..c665b6e662f
--- /dev/null
+++ b/src/tools/tidy/src/features.rs
@@ -0,0 +1,155 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Tidy check to ensure that unstable features are all in order
+//!
+//! This check will ensure properties like:
+//!
+//! * All stability attributes look reasonably well formed
+//! * The set of library features is disjoint from the set of language features
+//! * Library features have at most one stability level
+//! * Library features have at most one `since` value
+
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::prelude::*;
+use std::path::Path;
+
+const STATUSES: &'static [&'static str] = &[
+    "Active", "Deprecated", "Removed", "Accepted",
+];
+
+struct Feature {
+    name: String,
+    since: String,
+    status: String,
+}
+
+struct LibFeature {
+    level: String,
+    since: String,
+}
+
+pub fn check(path: &Path, bad: &mut bool) {
+    let features = collect_lang_features(&path.join("libsyntax/feature_gate.rs"));
+    let mut lib_features = HashMap::<String, LibFeature>::new();
+
+    let mut contents = String::new();
+    super::walk(path,
+                &mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
+                &mut |file| {
+        let filename = file.file_name().unwrap().to_string_lossy();
+        if !filename.ends_with(".rs") || filename == "features.rs" {
+            return
+        }
+
+        contents.truncate(0);
+        t!(t!(File::open(file)).read_to_string(&mut contents));
+
+        for (i, line) in contents.lines().enumerate() {
+            let mut err = |msg: &str| {
+                println!("{}:{}: {}", file.display(), i + 1, msg);
+                *bad = true;
+            };
+            let level = if line.contains("[unstable(") {
+                "unstable"
+            } else if line.contains("[stable(") {
+                "stable"
+            } else {
+                continue
+            };
+            let feature_name = match find_attr_val(line, "feature") {
+                Some(name) => name,
+                None => {
+                    err("malformed stability attribute");
+                    continue
+                }
+            };
+            let since = match find_attr_val(line, "since") {
+                Some(name) => name,
+                None if level == "stable" => {
+                    err("malformed stability attribute");
+                    continue
+                }
+                None => "None",
+            };
+
+            if features.iter().any(|f| f.name == feature_name) {
+                err("duplicating a lang feature");
+            }
+            if let Some(ref s) = lib_features.get(feature_name) {
+                if s.level != level {
+                    err("different stability level than before");
+                }
+                if s.since != since {
+                    err("different `since` than before");
+                }
+                continue
+            }
+            lib_features.insert(feature_name.to_owned(), LibFeature {
+                level: level.to_owned(),
+                since: since.to_owned(),
+            });
+        }
+    });
+
+    let mut lines = Vec::new();
+    for feature in features {
+        lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
+                           feature.name, "lang", feature.status, feature.since));
+    }
+    for (name, feature) in lib_features {
+        lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
+                           name, "lib", feature.level, feature.since));
+    }
+
+    lines.sort();
+    for line in lines {
+        println!("* {}", line);
+    }
+}
+
+fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
+    line.find(attr).and_then(|i| {
+        line[i..].find("\"").map(|j| i + j + 1)
+    }).and_then(|i| {
+        line[i..].find("\"").map(|j| (i, i + j))
+    }).map(|(i, j)| {
+        &line[i..j]
+    })
+}
+
+fn collect_lang_features(path: &Path) -> Vec<Feature> {
+    let mut contents = String::new();
+    t!(t!(File::open(path)).read_to_string(&mut contents));
+
+    let mut features = Vec::new();
+    for line in contents.lines().map(|l| l.trim()) {
+        if !STATUSES.iter().any(|s| line.contains(s) && line.starts_with("(")) {
+            continue
+        }
+        let mut parts = line.split(",");
+        let name = parts.next().unwrap().replace("\"", "").replace("(", "");
+        let since = parts.next().unwrap().trim().replace("\"", "");
+        let status = match parts.skip(1).next().unwrap() {
+            s if s.contains("Active") => "unstable",
+            s if s.contains("Removed") => "unstable",
+            s if s.contains("Accepted") => "stable",
+            s => panic!("unknown status: {}", s),
+        };
+
+        features.push(Feature {
+            name: name,
+            since: since,
+            status: status.to_owned(),
+        });
+    }
+    return features
+}
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
new file mode 100644
index 00000000000..d8acf3250df
--- /dev/null
+++ b/src/tools/tidy/src/main.rs
@@ -0,0 +1,76 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Tidy checks for source code in this repository
+//!
+//! This program runs all of the various tidy checks for style, cleanliness,
+//! etc. This is run by default on `make check` and as part of the auto
+//! builders.
+
+use std::fs;
+use std::path::{PathBuf, Path};
+use std::env;
+
+macro_rules! t {
+    ($e:expr) => (match $e {
+        Ok(e) => e,
+        Err(e) => panic!("{} failed with {}", stringify!($e), e),
+    })
+}
+
+mod bins;
+mod style;
+mod errors;
+mod features;
+
+fn main() {
+    let path = env::args_os().skip(1).next().expect("need an argument");
+    let path = PathBuf::from(path);
+
+    let mut bad = false;
+    bins::check(&path, &mut bad);
+    style::check(&path, &mut bad);
+    errors::check(&path, &mut bad);
+    features::check(&path, &mut bad);
+
+    if bad {
+        panic!("some tidy checks failed");
+    }
+}
+
+fn filter_dirs(path: &Path) -> bool {
+    let skip = [
+        "src/jemalloc",
+        "src/llvm",
+        "src/libbacktrace",
+        "src/compiler-rt",
+        "src/rt/hoedown",
+        "src/rustllvm",
+        "src/rust-installer",
+        "src/liblibc",
+    ];
+    skip.iter().any(|p| path.ends_with(p))
+}
+
+
+fn walk(path: &Path, skip: &mut FnMut(&Path) -> bool, f: &mut FnMut(&Path)) {
+    for entry in t!(fs::read_dir(path)) {
+        let entry = t!(entry);
+        let kind = t!(entry.file_type());
+        let path = entry.path();
+        if kind.is_dir() {
+            if !skip(&path) {
+                walk(&path, skip, f);
+            }
+        } else {
+            f(&path);
+        }
+    }
+}
diff --git a/src/tools/tidy/src/style.rs b/src/tools/tidy/src/style.rs
new file mode 100644
index 00000000000..4c5f72c1e79
--- /dev/null
+++ b/src/tools/tidy/src/style.rs
@@ -0,0 +1,127 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Tidy check to enforce various stylistic guidelines on the Rust codebase.
+//!
+//! Example checks are:
+//!
+//! * No lines over 100 characters
+//! * No tabs
+//! * No trailing whitespace
+//! * No CR characters
+//! * No `TODO` or `XXX` directives
+//! * A valid license header is at the top
+//!
+//! A number of these checks can be opted-out of with various directives like
+//! `// ignore-tidy-linelength`.
+
+use std::fs::File;
+use std::io::prelude::*;
+use std::path::Path;
+
+const COLS: usize = 100;
+const LICENSE: &'static str = "\
+Copyright <year> The Rust Project Developers. See the COPYRIGHT
+file at the top-level directory of this distribution and at
+http://rust-lang.org/COPYRIGHT.
+
+Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+<LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+option. This file may not be copied, modified, or distributed
+except according to those terms.";
+
+pub fn check(path: &Path, bad: &mut bool) {
+    let mut contents = String::new();
+    super::walk(path, &mut super::filter_dirs, &mut |file| {
+        let filename = file.file_name().unwrap().to_string_lossy();
+        let extensions = [".rs", ".py", ".js", ".sh", ".c", ".h"];
+        if extensions.iter().all(|e| !filename.ends_with(e)) ||
+           filename.starts_with(".#") {
+            return
+        }
+        if filename == "miniz.c" || filename.contains("jquery") {
+            return
+        }
+
+        contents.truncate(0);
+        t!(t!(File::open(file)).read_to_string(&mut contents));
+        let skip_cr = contents.contains("ignore-tidy-cr");
+        let skip_tab = contents.contains("ignore-tidy-tab");
+        let skip_length = contents.contains("ignore-tidy-linelength");
+        for (i, line) in contents.split("\n").enumerate() {
+            let mut err = |msg: &str| {
+                println!("{}:{}: {}", file.display(), i + 1, msg);
+                *bad = true;
+            };
+            if line.chars().count() > COLS && !skip_length {
+                err(&format!("line longer than {} chars", COLS));
+            }
+            if line.contains("\t") && !skip_tab {
+                err("tab character");
+            }
+            if line.ends_with(" ") || line.ends_with("\t") {
+                err("trailing whitespace");
+            }
+            if line.contains("\r") && !skip_cr {
+                err("CR character");
+            }
+            if filename != "style.rs" && filename != "tidy.py" {
+                if line.contains("TODO") {
+                    err("TODO is deprecated; use FIXME")
+                }
+                if line.contains("//") && line.contains(" XXX") {
+                    err("XXX is deprecated; use FIXME")
+                }
+            }
+        }
+        if !licenseck(file, &contents) {
+            println!("{}: incorrect license", file.display());
+            *bad = true;
+        }
+    })
+}
+
+fn licenseck(file: &Path, contents: &str) -> bool {
+    if contents.contains("ignore-license") {
+        return true
+    }
+    let exceptions = [
+        "libstd/sync/mpsc/mpsc_queue.rs",
+        "libstd/sync/mpsc/spsc_queue.rs",
+    ];
+    if exceptions.iter().any(|f| file.ends_with(f)) {
+        return true
+    }
+
+    // Skip the BOM if it's there
+    let bom = "\u{feff}";
+    let contents = if contents.starts_with(bom) {&contents[3..]} else {contents};
+
+    // See if the license shows up in the first 100 lines
+    let lines = contents.lines().take(100).collect::<Vec<_>>();
+    lines.windows(LICENSE.lines().count()).any(|window| {
+        let offset = if window.iter().all(|w| w.starts_with("//")) {
+            2
+        } else if window.iter().all(|w| w.starts_with("#")) {
+            1
+        } else {
+            return false
+        };
+        window.iter().map(|a| a[offset..].trim())
+              .zip(LICENSE.lines()).all(|(a, b)| {
+            a == b || match b.find("<year>") {
+                Some(i) => a.starts_with(&b[..i]) && a.ends_with(&b[i+6..]),
+                None => false,
+            }
+        })
+    })
+
+}