1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
#!/usr/bin/env python3
import os
from list_crates import crate_list
from collections import Counter
# This contains annotations the script think are missing, but actually they don't need to be there
additional_provided = {}
# This contains annotations the script detected and think shouldn't be there, but actually they should
additional_required = {}
# PreferredRuntime has a somewhat more complexe rule for existing
additional_provided['tor-rtcompat'] = [
('PreferredRuntime', 'all(feature = "native-tls")'),
('PreferredRuntime', 'all(feature = "rustls", not(feature = "native-tls"))'),
('PreferredRuntime', 'feature = "native-tls"'),
('PreferredRuntime', 'all(feature = "rustls", not(feature = "native-tls"))'),
('NativeTlsProvider', 'all(feature = "native-tls", any(feature = "tokio", feature = "async-std"))'),
('RustlsProvider', 'all(feature = "rustls", any(feature = "tokio", feature = "async-std"))'),
]
# We're not very interested in the testing feature
additional_provided['tor-guardmgr'] = [
('TestConfig', 'any(test, feature = "testing")'),
]
# Sha1 is present both ways
additional_provided['tor-llcrypto'] = [
('Sha1', 'feature = "with-openssl"'),
('Sha1', 'not(feature = "with-openssl")'),
]
additional_required['tor-llcrypto']= [
('aes', 'all()'),
('aes', 'all()'),
]
# This is an * include; expended wildcard must be in additional_required
additional_provided['tor-proto']= [
('*', 'feature = "testing"'),
]
additional_required['tor-proto']= [
('CtrlMsg', 'feature = "testing"'),
('CreateResponse', 'feature = "testing"'),
]
# This is detected two times, but only on cfg_attr(docsrs) is enough
additional_provided['tor-netdoc']= [
('Nickname', 'feature = "dangerous-expose-struct-fields"'),
('NsConsensusRouterStatus', 'feature = "ns_consensus"'),
]
def extract_feature_pub_use(path):
START_CFG = '#[cfg('
END_CFG = ')]'
PUB_USE = 'pub use '
res = []
cfg = None
with open(path, 'r') as file:
for line in file.readlines():
if line.find(PUB_USE) != -1 and cfg:
# last line was a #[cfg(..)] line and this is a pub use
pubuse_pos = line.find(PUB_USE)
# ignore comments
if '//' in line[:pubuse_pos]:
continue
# extract ident
#
# (BUG: this still doesn't handle `pub use {A,B,C} very well.)
start = line.rfind(":")
if start == -1:
start = line.rfind(" ")
ident = line[start + 1:]
if (pos:= ident.find(';')) != -1:
ident = ident[:pos]
res.append((ident, cfg))
cfg = None
continue
# check if we are on a #[cfg(..)] line, if so, remember it
start_cfg = line.find(START_CFG)
end_cfg = line.find(END_CFG)
if start_cfg == -1 or end_cfg == -1:
cfg = None
else:
start_cfg += len(START_CFG)
cfg = line[start_cfg:end_cfg]
return res
def extract_cfg_attr(path):
START_CFG = '#[cfg_attr(docsrs, doc(cfg('
END_CFG = ')))]'
res = []
cfg = None
with open(path, 'r') as file:
for line in file.readlines():
pos = max([line.find(kw + ' ') for kw in ['struct', 'enum', 'mod', 'trait']])
if pos != -1 and cfg:
# last line was a cfg and this is a declaration
subline = line[pos:]
subline = subline[subline.find(' ') + 1:]
end = min(subline.find(pat) for pat in ' (<' if subline.find(pat) !=-1)
ident = subline[:end]
res.append((ident, cfg))
cfg = None
continue
# check if we are on a #[cfg_attr(docsrs, doc(cfg(..)))] line, if so, remember it
start_cfg = line.find(START_CFG)
end_cfg = line.find(END_CFG)
if start_cfg != -1 and end_cfg != -1:
start_cfg += len(START_CFG)
cfg = line[start_cfg:end_cfg]
# don't reset when it's a cfg_attr followed by some other #[something]
elif "#[" not in line:
cfg = None
return res
def for_each_rs(path, fn):
res = []
for dir_, _, files in os.walk(path):
for file in files:
if not file.endswith(".rs"):
continue
res += fn(os.path.join(dir_, file))
return res
def main():
for crate in crate_list():
print(f"processing {crate}")
crate_path = f"crates/{crate}/src"
required = for_each_rs(crate_path, extract_feature_pub_use) + additional_required.get(crate, [])
provided = for_each_rs(crate_path, extract_cfg_attr) + additional_provided.get(crate, [])
req = Counter(required)
prov = Counter(provided)
ok = True
for elem in (req - prov).elements():
ok = False
print(f"feature but no cfg_attr(docsrs): {elem}")
for elem in (prov - req).elements():
ok = False
print(f"cfg_attr(docsrs) but no feature: {elem}")
if not ok:
print("Found error, exiting")
exit(1)
if __name__ == '__main__':
main()
|