mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-13 02:17:41 +00:00
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:
parent
bed32d83fc
commit
9dd3c54a2c
52
mk/tests.mk
52
mk/tests.mk
@ -240,52 +240,16 @@ cleantestlibs:
|
|||||||
# Tidy
|
# Tidy
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
ifdef CFG_NOTIDY
|
|
||||||
.PHONY: tidy
|
.PHONY: tidy
|
||||||
tidy:
|
tidy: $(HBIN0_H_$(CFG_BUILD))/tidy$(X_$(CFG_BUILD))
|
||||||
else
|
$< $(S)src
|
||||||
|
|
||||||
# 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/
|
|
||||||
|
|
||||||
|
$(HBIN0_H_$(CFG_BUILD))/tidy$(X_$(CFG_BUILD)): \
|
||||||
|
$(TSREQ0_T_$(CFG_BUILD)_H_$(CFG_BUILD)) \
|
||||||
|
$(TLIB0_T_$(CFG_BUILD)_H_$(CFG_BUILD))/stamp.std \
|
||||||
|
$(call rwildcard,$(S)src/tools/tidy/src,*.rs)
|
||||||
|
$(STAGE0_T_$(CFG_BUILD)_H_$(CFG_BUILD)) src/tools/tidy/src/main.rs \
|
||||||
|
--out-dir $(@D) --crate-name tidy
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Sets of tests
|
# Sets of tests
|
||||||
|
@ -33,3 +33,10 @@ pub fn cargotest(build: &Build, stage: u32, host: &str) {
|
|||||||
.env("PATH", newpath)
|
.env("PATH", newpath)
|
||||||
.arg(&build.cargo));
|
.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 } => {
|
ToolCargoTest { stage } => {
|
||||||
compile::tool(self, stage, target.target, "cargotest");
|
compile::tool(self, stage, target.target, "cargotest");
|
||||||
}
|
}
|
||||||
|
ToolTidy { stage } => {
|
||||||
|
compile::tool(self, stage, target.target, "tidy");
|
||||||
|
}
|
||||||
DocBook { stage } => {
|
DocBook { stage } => {
|
||||||
doc::rustbook(self, stage, target.target, "book", &doc_out);
|
doc::rustbook(self, stage, target.target, "book", &doc_out);
|
||||||
}
|
}
|
||||||
@ -230,6 +233,9 @@ impl Build {
|
|||||||
CheckCargoTest { stage } => {
|
CheckCargoTest { stage } => {
|
||||||
check::cargotest(self, stage, target.target);
|
check::cargotest(self, stage, target.target);
|
||||||
}
|
}
|
||||||
|
CheckTidy { stage } => {
|
||||||
|
check::tidy(self, stage, target.target);
|
||||||
|
}
|
||||||
|
|
||||||
DistDocs { stage } => dist::docs(self, stage, target.target),
|
DistDocs { stage } => dist::docs(self, stage, target.target),
|
||||||
DistMingw { _dummy } => dist::mingw(self, target.target),
|
DistMingw { _dummy } => dist::mingw(self, target.target),
|
||||||
|
@ -51,6 +51,7 @@ macro_rules! targets {
|
|||||||
(tool_rustbook, ToolRustbook { stage: u32 }),
|
(tool_rustbook, ToolRustbook { stage: u32 }),
|
||||||
(tool_error_index, ToolErrorIndex { stage: u32 }),
|
(tool_error_index, ToolErrorIndex { stage: u32 }),
|
||||||
(tool_cargotest, ToolCargoTest { stage: u32 }),
|
(tool_cargotest, ToolCargoTest { stage: u32 }),
|
||||||
|
(tool_tidy, ToolTidy { stage: u32 }),
|
||||||
|
|
||||||
// Steps for long-running native builds. Ideally these wouldn't
|
// Steps for long-running native builds. Ideally these wouldn't
|
||||||
// actually exist and would be part of build scripts, but for now
|
// 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, Check { stage: u32, compiler: Compiler<'a> }),
|
||||||
(check_linkcheck, CheckLinkcheck { stage: u32 }),
|
(check_linkcheck, CheckLinkcheck { stage: u32 }),
|
||||||
(check_cargotest, CheckCargoTest { stage: u32 }),
|
(check_cargotest, CheckCargoTest { stage: u32 }),
|
||||||
|
(check_tidy, CheckTidy { stage: u32 }),
|
||||||
|
|
||||||
// Distribution targets, creating tarballs
|
// Distribution targets, creating tarballs
|
||||||
(dist, Dist { stage: u32 }),
|
(dist, Dist { stage: u32 }),
|
||||||
@ -316,8 +318,13 @@ impl<'a> Step<'a> {
|
|||||||
Source::CheckCargoTest { stage } => {
|
Source::CheckCargoTest { stage } => {
|
||||||
vec![self.tool_cargotest(stage)]
|
vec![self.tool_cargotest(stage)]
|
||||||
}
|
}
|
||||||
|
Source::CheckTidy { stage } => {
|
||||||
|
vec![self.tool_tidy(stage)]
|
||||||
|
}
|
||||||
|
|
||||||
Source::ToolLinkchecker { stage } => {
|
Source::ToolLinkchecker { stage } |
|
||||||
|
Source::ToolTidy { stage } |
|
||||||
|
Source::ToolCargoTest { stage } => {
|
||||||
vec![self.libstd(self.compiler(stage))]
|
vec![self.libstd(self.compiler(stage))]
|
||||||
}
|
}
|
||||||
Source::ToolErrorIndex { stage } |
|
Source::ToolErrorIndex { stage } |
|
||||||
|
@ -42,5 +42,7 @@ check-cargotest:
|
|||||||
$(Q)$(BOOTSTRAP) --step check-cargotest
|
$(Q)$(BOOTSTRAP) --step check-cargotest
|
||||||
dist:
|
dist:
|
||||||
$(Q)$(BOOTSTRAP) --step dist
|
$(Q)$(BOOTSTRAP) --step dist
|
||||||
|
tidy:
|
||||||
|
$(Q)$(BOOTSTRAP) --step check-tidy --stage 0
|
||||||
|
|
||||||
.PHONY: dist
|
.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)
|
|
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
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
// option. This file may not be copied, modified, or distributed
|
// 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
|
// Check that the explicit lifetime bound (`'b`, in this example) must
|
||||||
// outlive all the superbound from the trait (`'a`, in this example).
|
// 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.
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
// file at the top-level directory of this distribution and at
|
// file at the top-level directory of this distribution and at
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
//
|
//
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// pretty-expanded FIXME #23616
|
// pretty-expanded FIXME #23616
|
||||||
|
|
||||||
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||||
// file at the top-level directory of this distribution and at
|
// file at the top-level directory of this distribution and at
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
//
|
//
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
90
src/tools/tidy/src/errors.rs
Normal file
90
src/tools/tidy/src/errors.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! Tidy check to verify the validity of long error diagnostic codes.
|
||||||
|
//!
|
||||||
|
//! This ensures that error codes are used at most once and also prints out some
|
||||||
|
//! statistics about the error codes.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub fn check(path: &Path, bad: &mut bool) {
|
||||||
|
let mut contents = String::new();
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
super::walk(path,
|
||||||
|
&mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
|
||||||
|
&mut |file| {
|
||||||
|
let filename = file.file_name().unwrap().to_string_lossy();
|
||||||
|
if filename != "diagnostics.rs" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.truncate(0);
|
||||||
|
t!(t!(File::open(file)).read_to_string(&mut contents));
|
||||||
|
|
||||||
|
// In the register_long_diagnostics! macro, entries look like this:
|
||||||
|
//
|
||||||
|
// EXXXX: r##"
|
||||||
|
// <Long diagnostic message>
|
||||||
|
// "##,
|
||||||
|
//
|
||||||
|
// and these long messages often have error codes themselves inside
|
||||||
|
// them, but we don't want to report duplicates in these cases. This
|
||||||
|
// variable keeps track of whether we're currently inside one of these
|
||||||
|
// long diagnostic messages.
|
||||||
|
let mut inside_long_diag = false;
|
||||||
|
for (num, line) in contents.lines().enumerate() {
|
||||||
|
if inside_long_diag {
|
||||||
|
inside_long_diag = !line.contains("\"##");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut search = line;
|
||||||
|
while let Some(i) = search.find("E") {
|
||||||
|
search = &search[i + 1..];
|
||||||
|
let code = if search.len() > 4 {
|
||||||
|
search[..4].parse::<u32>()
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let code = match code {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(..) => continue,
|
||||||
|
};
|
||||||
|
map.entry(code).or_insert(Vec::new())
|
||||||
|
.push((file.to_owned(), num + 1, line.to_owned()));
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
inside_long_diag = line.contains("r##\"");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut max = 0;
|
||||||
|
println!("* {} error codes", map.len());
|
||||||
|
for (code, entries) in map {
|
||||||
|
if code > max {
|
||||||
|
max = code;
|
||||||
|
}
|
||||||
|
if entries.len() == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("duplicate error code: {}", code);
|
||||||
|
for (file, line_num, line) in entries {
|
||||||
|
println!("{}:{}: {}", file.display(), line_num, line);
|
||||||
|
}
|
||||||
|
*bad = true;
|
||||||
|
}
|
||||||
|
println!("* highest error code: E{:04}", max);
|
||||||
|
}
|
155
src/tools/tidy/src/features.rs
Normal file
155
src/tools/tidy/src/features.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! Tidy check to ensure that unstable features are all in order
|
||||||
|
//!
|
||||||
|
//! This check will ensure properties like:
|
||||||
|
//!
|
||||||
|
//! * All stability attributes look reasonably well formed
|
||||||
|
//! * The set of library features is disjoint from the set of language features
|
||||||
|
//! * Library features have at most one stability level
|
||||||
|
//! * Library features have at most one `since` value
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
const STATUSES: &'static [&'static str] = &[
|
||||||
|
"Active", "Deprecated", "Removed", "Accepted",
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Feature {
|
||||||
|
name: String,
|
||||||
|
since: String,
|
||||||
|
status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LibFeature {
|
||||||
|
level: String,
|
||||||
|
since: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(path: &Path, bad: &mut bool) {
|
||||||
|
let features = collect_lang_features(&path.join("libsyntax/feature_gate.rs"));
|
||||||
|
let mut lib_features = HashMap::<String, LibFeature>::new();
|
||||||
|
|
||||||
|
let mut contents = String::new();
|
||||||
|
super::walk(path,
|
||||||
|
&mut |path| super::filter_dirs(path) || path.ends_with("src/test"),
|
||||||
|
&mut |file| {
|
||||||
|
let filename = file.file_name().unwrap().to_string_lossy();
|
||||||
|
if !filename.ends_with(".rs") || filename == "features.rs" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.truncate(0);
|
||||||
|
t!(t!(File::open(file)).read_to_string(&mut contents));
|
||||||
|
|
||||||
|
for (i, line) in contents.lines().enumerate() {
|
||||||
|
let mut err = |msg: &str| {
|
||||||
|
println!("{}:{}: {}", file.display(), i + 1, msg);
|
||||||
|
*bad = true;
|
||||||
|
};
|
||||||
|
let level = if line.contains("[unstable(") {
|
||||||
|
"unstable"
|
||||||
|
} else if line.contains("[stable(") {
|
||||||
|
"stable"
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let feature_name = match find_attr_val(line, "feature") {
|
||||||
|
Some(name) => name,
|
||||||
|
None => {
|
||||||
|
err("malformed stability attribute");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let since = match find_attr_val(line, "since") {
|
||||||
|
Some(name) => name,
|
||||||
|
None if level == "stable" => {
|
||||||
|
err("malformed stability attribute");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
None => "None",
|
||||||
|
};
|
||||||
|
|
||||||
|
if features.iter().any(|f| f.name == feature_name) {
|
||||||
|
err("duplicating a lang feature");
|
||||||
|
}
|
||||||
|
if let Some(ref s) = lib_features.get(feature_name) {
|
||||||
|
if s.level != level {
|
||||||
|
err("different stability level than before");
|
||||||
|
}
|
||||||
|
if s.since != since {
|
||||||
|
err("different `since` than before");
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lib_features.insert(feature_name.to_owned(), LibFeature {
|
||||||
|
level: level.to_owned(),
|
||||||
|
since: since.to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
for feature in features {
|
||||||
|
lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
|
||||||
|
feature.name, "lang", feature.status, feature.since));
|
||||||
|
}
|
||||||
|
for (name, feature) in lib_features {
|
||||||
|
lines.push(format!("{:<32} {:<8} {:<12} {:<8}",
|
||||||
|
name, "lib", feature.level, feature.since));
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.sort();
|
||||||
|
for line in lines {
|
||||||
|
println!("* {}", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_attr_val<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
|
||||||
|
line.find(attr).and_then(|i| {
|
||||||
|
line[i..].find("\"").map(|j| i + j + 1)
|
||||||
|
}).and_then(|i| {
|
||||||
|
line[i..].find("\"").map(|j| (i, i + j))
|
||||||
|
}).map(|(i, j)| {
|
||||||
|
&line[i..j]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_lang_features(path: &Path) -> Vec<Feature> {
|
||||||
|
let mut contents = String::new();
|
||||||
|
t!(t!(File::open(path)).read_to_string(&mut contents));
|
||||||
|
|
||||||
|
let mut features = Vec::new();
|
||||||
|
for line in contents.lines().map(|l| l.trim()) {
|
||||||
|
if !STATUSES.iter().any(|s| line.contains(s) && line.starts_with("(")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let mut parts = line.split(",");
|
||||||
|
let name = parts.next().unwrap().replace("\"", "").replace("(", "");
|
||||||
|
let since = parts.next().unwrap().trim().replace("\"", "");
|
||||||
|
let status = match parts.skip(1).next().unwrap() {
|
||||||
|
s if s.contains("Active") => "unstable",
|
||||||
|
s if s.contains("Removed") => "unstable",
|
||||||
|
s if s.contains("Accepted") => "stable",
|
||||||
|
s => panic!("unknown status: {}", s),
|
||||||
|
};
|
||||||
|
|
||||||
|
features.push(Feature {
|
||||||
|
name: name,
|
||||||
|
since: since,
|
||||||
|
status: status.to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return features
|
||||||
|
}
|
76
src/tools/tidy/src/main.rs
Normal file
76
src/tools/tidy/src/main.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! Tidy checks for source code in this repository
|
||||||
|
//!
|
||||||
|
//! This program runs all of the various tidy checks for style, cleanliness,
|
||||||
|
//! etc. This is run by default on `make check` and as part of the auto
|
||||||
|
//! builders.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{PathBuf, Path};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
macro_rules! t {
|
||||||
|
($e:expr) => (match $e {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(e) => panic!("{} failed with {}", stringify!($e), e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mod bins;
|
||||||
|
mod style;
|
||||||
|
mod errors;
|
||||||
|
mod features;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let path = env::args_os().skip(1).next().expect("need an argument");
|
||||||
|
let path = PathBuf::from(path);
|
||||||
|
|
||||||
|
let mut bad = false;
|
||||||
|
bins::check(&path, &mut bad);
|
||||||
|
style::check(&path, &mut bad);
|
||||||
|
errors::check(&path, &mut bad);
|
||||||
|
features::check(&path, &mut bad);
|
||||||
|
|
||||||
|
if bad {
|
||||||
|
panic!("some tidy checks failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_dirs(path: &Path) -> bool {
|
||||||
|
let skip = [
|
||||||
|
"src/jemalloc",
|
||||||
|
"src/llvm",
|
||||||
|
"src/libbacktrace",
|
||||||
|
"src/compiler-rt",
|
||||||
|
"src/rt/hoedown",
|
||||||
|
"src/rustllvm",
|
||||||
|
"src/rust-installer",
|
||||||
|
"src/liblibc",
|
||||||
|
];
|
||||||
|
skip.iter().any(|p| path.ends_with(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn walk(path: &Path, skip: &mut FnMut(&Path) -> bool, f: &mut FnMut(&Path)) {
|
||||||
|
for entry in t!(fs::read_dir(path)) {
|
||||||
|
let entry = t!(entry);
|
||||||
|
let kind = t!(entry.file_type());
|
||||||
|
let path = entry.path();
|
||||||
|
if kind.is_dir() {
|
||||||
|
if !skip(&path) {
|
||||||
|
walk(&path, skip, f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f(&path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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