]>
Commit | Line | Data |
---|---|---|
85aaf69f SL |
1 | # Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
2 | # file at the top-level directory of this distribution and at | |
3 | # http://rust-lang.org/COPYRIGHT. | |
4 | # | |
5 | # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | # option. This file may not be copied, modified, or distributed | |
9 | # except according to those terms. | |
10 | ||
11 | # This script does a tree-wide sanity checks against stability | |
12 | # attributes, currently: | |
13 | # * For all feature_name/level pairs the 'since' field is the same | |
14 | # * That no features are both stable and unstable. | |
15 | # * That lib features don't have the same name as lang features | |
16 | # unless they are on the 'joint_features' whitelist | |
17 | # * That features that exist in both lang and lib and are stable | |
18 | # since the same version | |
19 | # * Prints information about features | |
20 | ||
62682a34 SL |
21 | import sys |
22 | import os | |
23 | import re | |
24 | import codecs | |
85aaf69f SL |
25 | |
26 | if len(sys.argv) < 2: | |
62682a34 | 27 | print("usage: featureck.py <src-dir>") |
85aaf69f SL |
28 | sys.exit(1) |
29 | ||
30 | src_dir = sys.argv[1] | |
31 | ||
32 | # Features that are allowed to exist in both the language and the library | |
33 | joint_features = [ ] | |
34 | ||
35 | # Grab the list of language features from the compiler | |
36 | language_gate_statuses = [ "Active", "Deprecated", "Removed", "Accepted" ] | |
37 | feature_gate_source = os.path.join(src_dir, "libsyntax", "feature_gate.rs") | |
38 | language_features = [] | |
39 | language_feature_names = [] | |
40 | with open(feature_gate_source, 'r') as f: | |
41 | for line in f: | |
42 | original_line = line | |
43 | line = line.strip() | |
44 | is_feature_line = False | |
45 | for status in language_gate_statuses: | |
46 | if status in line and line.startswith("("): | |
47 | is_feature_line = True | |
48 | ||
49 | if is_feature_line: | |
50 | line = line.replace("(", "").replace("),", "").replace(")", "") | |
51 | parts = line.split(",") | |
52 | if len(parts) != 3: | |
62682a34 | 53 | print("error: unexpected number of components in line: " + original_line) |
85aaf69f SL |
54 | sys.exit(1) |
55 | feature_name = parts[0].strip().replace('"', "") | |
56 | since = parts[1].strip().replace('"', "") | |
57 | status = parts[2].strip() | |
58 | assert len(feature_name) > 0 | |
59 | assert len(since) > 0 | |
60 | assert len(status) > 0 | |
61 | ||
62 | language_feature_names += [feature_name] | |
63 | language_features += [(feature_name, since, status)] | |
64 | ||
65 | assert len(language_features) > 0 | |
66 | ||
67 | errors = False | |
68 | ||
69 | lib_features = { } | |
70 | lib_features_and_level = { } | |
71 | for (dirpath, dirnames, filenames) in os.walk(src_dir): | |
72 | # Don't look for feature names in tests | |
73 | if "src/test" in dirpath: | |
74 | continue | |
75 | ||
76 | # Takes a long time to traverse LLVM | |
77 | if "src/llvm" in dirpath: | |
78 | continue | |
79 | ||
80 | for filename in filenames: | |
81 | if not filename.endswith(".rs"): | |
82 | continue | |
83 | ||
84 | path = os.path.join(dirpath, filename) | |
62682a34 | 85 | with codecs.open(filename=path, mode='r', encoding="utf-8") as f: |
85aaf69f SL |
86 | line_num = 0 |
87 | for line in f: | |
88 | line_num += 1 | |
89 | level = None | |
90 | if "[unstable(" in line: | |
91 | level = "unstable" | |
92 | elif "[stable(" in line: | |
93 | level = "stable" | |
94 | else: | |
95 | continue | |
96 | ||
97 | # This is a stability attribute. For the purposes of this | |
98 | # script we expect both the 'feature' and 'since' attributes on | |
99 | # the same line, e.g. | |
100 | # `#[unstable(feature = "foo", since = "1.0.0")]` | |
101 | ||
102 | p = re.compile('(unstable|stable).*feature *= *"(\w*)"') | |
103 | m = p.search(line) | |
104 | if not m is None: | |
105 | feature_name = m.group(2) | |
106 | since = None | |
107 | if re.compile("\[ *stable").search(line) is not None: | |
108 | pp = re.compile('since *= *"([\w\.]*)"') | |
109 | mm = pp.search(line) | |
110 | if not mm is None: | |
111 | since = mm.group(1) | |
112 | else: | |
62682a34 SL |
113 | print("error: misformed stability attribute") |
114 | print("line %d of %:" % (line_num, path)) | |
115 | print(line) | |
85aaf69f SL |
116 | errors = True |
117 | ||
118 | lib_features[feature_name] = feature_name | |
119 | if lib_features_and_level.get((feature_name, level)) is None: | |
120 | # Add it to the observed features | |
121 | lib_features_and_level[(feature_name, level)] = \ | |
122 | (since, path, line_num, line) | |
123 | else: | |
124 | # Verify that for this combination of feature_name and level the 'since' | |
125 | # attribute matches. | |
126 | (expected_since, source_path, source_line_num, source_line) = \ | |
127 | lib_features_and_level.get((feature_name, level)) | |
128 | if since != expected_since: | |
62682a34 SL |
129 | print("error: mismatch in %s feature '%s'" % (level, feature_name)) |
130 | print("line %d of %s:" % (source_line_num, source_path)) | |
131 | print(source_line) | |
132 | print("line %d of %s:" % (line_num, path)) | |
133 | print(line) | |
85aaf69f SL |
134 | errors = True |
135 | ||
136 | # Verify that this lib feature doesn't duplicate a lang feature | |
137 | if feature_name in language_feature_names: | |
62682a34 SL |
138 | print("error: lib feature '%s' duplicates a lang feature" % (feature_name)) |
139 | print("line %d of %s:" % (line_num, path)) | |
140 | print(line) | |
85aaf69f SL |
141 | errors = True |
142 | ||
143 | else: | |
62682a34 SL |
144 | print("error: misformed stability attribute") |
145 | print("line %d of %s:" % (line_num, path)) | |
146 | print(line) | |
85aaf69f SL |
147 | errors = True |
148 | ||
149 | # Merge data about both lists | |
150 | # name, lang, lib, status, stable since | |
151 | ||
152 | language_feature_stats = {} | |
153 | ||
154 | for f in language_features: | |
155 | name = f[0] | |
156 | lang = True | |
157 | lib = False | |
158 | status = "unstable" | |
159 | stable_since = None | |
160 | ||
161 | if f[2] == "Accepted": | |
162 | status = "stable" | |
163 | if status == "stable": | |
164 | stable_since = f[1] | |
165 | ||
166 | language_feature_stats[name] = (name, lang, lib, status, stable_since) | |
167 | ||
168 | lib_feature_stats = {} | |
169 | ||
170 | for f in lib_features: | |
171 | name = f | |
172 | lang = False | |
173 | lib = True | |
174 | status = "unstable" | |
175 | stable_since = None | |
176 | ||
177 | is_stable = lib_features_and_level.get((name, "stable")) is not None | |
178 | is_unstable = lib_features_and_level.get((name, "unstable")) is not None | |
179 | ||
180 | if is_stable and is_unstable: | |
62682a34 | 181 | print("error: feature '%s' is both stable and unstable" % (name)) |
85aaf69f SL |
182 | errors = True |
183 | ||
184 | if is_stable: | |
185 | status = "stable" | |
186 | stable_since = lib_features_and_level[(name, "stable")][0] | |
187 | elif is_unstable: | |
188 | status = "unstable" | |
189 | ||
190 | lib_feature_stats[name] = (name, lang, lib, status, stable_since) | |
191 | ||
192 | # Check for overlap in two sets | |
193 | merged_stats = { } | |
194 | ||
195 | for name in lib_feature_stats: | |
196 | if language_feature_stats.get(name) is not None: | |
197 | if not name in joint_features: | |
62682a34 | 198 | print("error: feature '%s' is both a lang and lib feature but not whitelisted" % (name)) |
85aaf69f SL |
199 | errors = True |
200 | lang_status = language_feature_stats[name][3] | |
201 | lib_status = lib_feature_stats[name][3] | |
202 | lang_stable_since = language_feature_stats[name][4] | |
203 | lib_stable_since = lib_feature_stats[name][4] | |
204 | ||
205 | if lang_status != lib_status and lib_status != "deprecated": | |
62682a34 SL |
206 | print("error: feature '%s' has lang status %s " + |
207 | "but lib status %s" % (name, lang_status, lib_status)) | |
85aaf69f SL |
208 | errors = True |
209 | ||
210 | if lang_stable_since != lib_stable_since: | |
62682a34 SL |
211 | print("error: feature '%s' has lang stable since %s " + |
212 | "but lib stable since %s" % (name, lang_stable_since, lib_stable_since)) | |
85aaf69f SL |
213 | errors = True |
214 | ||
215 | merged_stats[name] = (name, True, True, lang_status, lang_stable_since) | |
216 | ||
217 | del language_feature_stats[name] | |
218 | del lib_feature_stats[name] | |
219 | ||
220 | if errors: | |
221 | sys.exit(1) | |
222 | ||
223 | # Finally, display the stats | |
224 | stats = {} | |
225 | stats.update(language_feature_stats) | |
226 | stats.update(lib_feature_stats) | |
227 | stats.update(merged_stats) | |
228 | lines = [] | |
229 | for s in stats: | |
230 | s = stats[s] | |
231 | type_ = "lang" | |
232 | if s[1] and s[2]: | |
233 | type_ = "lang/lib" | |
234 | elif s[2]: | |
235 | type_ = "lib" | |
236 | line = "{: <32}".format(s[0]) + \ | |
237 | "{: <8}".format(type_) + \ | |
238 | "{: <12}".format(s[3]) + \ | |
239 | "{: <8}".format(str(s[4])) | |
240 | lines += [line] | |
241 | ||
242 | lines.sort() | |
243 | ||
244 | ||
245 | for line in lines: | |
62682a34 | 246 | print("* " + line) |
85aaf69f | 247 |