Skip to content

rustbuild: Migrate tidy checks to Rust #32590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 13, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 9 additions & 45 deletions mk/tests.mk
Original file line number Diff line number Diff line change
@@ -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))
$(TARGET_RPATH_VAR0_T_$(CFG_BUILD)_H_$(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)) $(S)src/tools/tidy/src/main.rs \
--out-dir $(@D) --crate-name tidy

######################################################################
# Sets of tests
7 changes: 7 additions & 0 deletions src/bootstrap/build/check.rs
Original file line number Diff line number Diff line change
@@ -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")));
}
6 changes: 6 additions & 0 deletions src/bootstrap/build/mod.rs
Original file line number Diff line number Diff line change
@@ -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),
8 changes: 7 additions & 1 deletion src/bootstrap/build/step.rs
Original file line number Diff line number Diff line change
@@ -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,12 @@ 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 } => {
vec![self.libstd(self.compiler(stage))]
}
Source::ToolErrorIndex { stage } |
2 changes: 2 additions & 0 deletions src/bootstrap/mk/Makefile.in
Original file line number Diff line number Diff line change
@@ -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
20 changes: 0 additions & 20 deletions src/etc/check-binaries.py

This file was deleted.

136 changes: 0 additions & 136 deletions src/etc/errorck.py

This file was deleted.

251 changes: 0 additions & 251 deletions src/etc/featureck.py

This file was deleted.

56 changes: 0 additions & 56 deletions src/etc/licenseck.py

This file was deleted.

230 changes: 0 additions & 230 deletions src/etc/tidy.py

This file was deleted.

Empty file.
4 changes: 4 additions & 0 deletions src/rustc/test_shim/lib.rs
Original file line number Diff line number Diff line change
@@ -9,3 +9,7 @@
// except according to those terms.

// See comments in Cargo.toml for why this exists

#![feature(test)]

extern crate test;
Empty file modified src/test/auxiliary/specialization_cross_crate_defaults.rs
100755 → 100644
Empty file.
2 changes: 1 addition & 1 deletion src/test/compile-fail/regions-wf-trait-object.rs
Original file line number Diff line number Diff line change
@@ -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).
Empty file modified src/test/compile-fail/specialization/specialization-polarity.rs
100755 → 100644
Empty file.
9 changes: 9 additions & 0 deletions src/test/run-make/compiler-lookup-paths/native.c
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion src/test/run-pass/issue-11577.rs
Original file line number Diff line number Diff line change
@@ -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.
//
2 changes: 1 addition & 1 deletion src/test/run-pass/issue-9382.rs
Original file line number Diff line number Diff line change
@@ -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.
//
4 changes: 4 additions & 0 deletions src/tools/tidy/Cargo.lock
6 changes: 6 additions & 0 deletions src/tools/tidy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "tidy"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]

[dependencies]
45 changes: 45 additions & 0 deletions src/tools/tidy/src/bins.rs
Original file line number Diff line number Diff line change
@@ -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;
}
})
}

95 changes: 95 additions & 0 deletions src/tools/tidy/src/cargo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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 `[dependencies]` and `extern crate` are in sync.
//!
//! This tidy check ensures that all crates listed in the `[dependencies]`
//! section of a `Cargo.toml` are present in the corresponding `lib.rs` as
//! `extern crate` declarations. This should help us keep the DAG correctly
//! structured through various refactorings to prune out unnecessary edges.
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;

pub fn check(path: &Path, bad: &mut bool) {
for entry in t!(path.read_dir()).map(|e| t!(e)) {
// Look for `Cargo.toml` with a sibling `src/lib.rs` or `lib.rs`
if entry.file_name().to_str() == Some("Cargo.toml") {
if path.join("src/lib.rs").is_file() {
verify(&entry.path(), &path.join("src/lib.rs"), bad)
}
if path.join("lib.rs").is_file() {
verify(&entry.path(), &path.join("lib.rs"), bad)
}
} else if t!(entry.file_type()).is_dir() {
check(&entry.path(), bad);
}
}
}

// Verify that the dependencies in Cargo.toml at `tomlfile` are sync'd with the
// `extern crate` annotations in the lib.rs at `libfile`.
fn verify(tomlfile: &Path, libfile: &Path, bad: &mut bool) {
let mut toml = String::new();
let mut librs = String::new();
t!(t!(File::open(tomlfile)).read_to_string(&mut toml));
t!(t!(File::open(libfile)).read_to_string(&mut librs));

if toml.contains("name = \"bootstrap\"") {
return
}

// "Poor man's TOML parser", just assume we use one syntax for now
//
// We just look for:
//
// [dependencies]
// name = ...
// name2 = ...
// name3 = ...
//
// If we encounter a line starting with `[` then we assume it's the end of
// the dependency section and bail out.
let deps = match toml.find("[dependencies]") {
Some(i) => &toml[i+1..],
None => return,
};
let mut lines = deps.lines().peekable();
while let Some(line) = lines.next() {
if line.starts_with("[") {
break
}

let mut parts = line.splitn(2, '=');
let krate = parts.next().unwrap().trim();
if parts.next().is_none() {
continue
}

// Don't worry about depending on core/std but not saying `extern crate
// core/std`, that's intentional.
if krate == "core" || krate == "std" {
continue
}

// This is intentional, this dependency just makes the crate available
// for others later on.
if krate == "alloc_jemalloc" && toml.contains("name = \"std\"") {
continue
}

if !librs.contains(&format!("extern crate {}", krate)) {
println!("{} doesn't have `extern crate {}`, but Cargo.toml \
depends on it", libfile.display(), krate);
*bad = true;
}
}
}
93 changes: 93 additions & 0 deletions src/tools/tidy/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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;
for (&code, entries) in map.iter() {
if code > max {
max = code;
}
if entries.len() == 1 {
continue
}

println!("duplicate error code: {}", code);
for &(ref file, line_num, ref line) in entries.iter() {
println!("{}:{}: {}", file.display(), line_num, line);
}
*bad = true;
}

if !*bad {
println!("* {} error codes", map.len());
println!("* highest error code: E{:04}", max);
}
}
159 changes: 159 additions & 0 deletions src/tools/tidy/src/features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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(),
});
}
});

if *bad {
return
}

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
}
78 changes: 78 additions & 0 deletions src/tools/tidy/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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;
mod cargo;

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);
cargo::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);
}
}
}
127 changes: 127 additions & 0 deletions src/tools/tidy/src/style.rs
Original file line number Diff line number Diff line change
@@ -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,
}
})
})

}