Auto merge of #32590 - alexcrichton:rustbuild-tidy-checks, r=brson

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
This commit is contained in:
bors 2016-04-12 16:24:33 -07:00
commit a43eb4e774
26 changed files with 653 additions and 741 deletions

View File

@ -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

View File

@ -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")));
}

View File

@ -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),

View File

@ -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 } |

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -9,3 +9,7 @@
// except according to those terms.
// See comments in Cargo.toml for why this exists
#![feature(test)]
extern crate test;

View File

View File

@ -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).

View File

View File

@ -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.

4
src/tools/tidy/Cargo.lock generated Normal file
View File

@ -0,0 +1,4 @@
[root]
name = "tidy"
version = "0.1.0"

View File

@ -0,0 +1,6 @@
[package]
name = "tidy"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
[dependencies]

View File

@ -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;
}
})
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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
}

View File

@ -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
src/tools/tidy/src/style.rs Normal file
View File

@ -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,
}
})
})
}