mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-29 10:13:54 +00:00
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:
commit
a43eb4e774
52
mk/tests.mk
52
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))
|
||||
$(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
|
||||
|
@ -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")));
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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 } |
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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))
|
230
src/etc/tidy.py
230
src/etc/tidy.py
@ -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)
|
@ -9,3 +9,7 @@
|
||||
// except according to those terms.
|
||||
|
||||
// See comments in Cargo.toml for why this exists
|
||||
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
0
src/test/auxiliary/specialization_cross_crate_defaults.rs
Executable file → Normal file
0
src/test/auxiliary/specialization_cross_crate_defaults.rs
Executable file → Normal 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).
|
||||
|
0
src/test/compile-fail/specialization/specialization-polarity.rs
Executable file → Normal file
0
src/test/compile-fail/specialization/specialization-polarity.rs
Executable file → Normal 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
4
src/tools/tidy/Cargo.lock
generated
Normal file
@ -0,0 +1,4 @@
|
||||
[root]
|
||||
name = "tidy"
|
||||
version = "0.1.0"
|
||||
|
6
src/tools/tidy/Cargo.toml
Normal file
6
src/tools/tidy/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "tidy"
|
||||
version = "0.1.0"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
|
||||
[dependencies]
|
45
src/tools/tidy/src/bins.rs
Normal file
45
src/tools/tidy/src/bins.rs
Normal 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;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
95
src/tools/tidy/src/cargo.rs
Normal file
95
src/tools/tidy/src/cargo.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
93
src/tools/tidy/src/errors.rs
Normal file
93
src/tools/tidy/src/errors.rs
Normal 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);
|
||||
}
|
||||
}
|
159
src/tools/tidy/src/features.rs
Normal file
159
src/tools/tidy/src/features.rs
Normal 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
|
||||
}
|
78
src/tools/tidy/src/main.rs
Normal file
78
src/tools/tidy/src/main.rs
Normal 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
127
src/tools/tidy/src/style.rs
Normal 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,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user