154 lines
5.4 KiB
Python
Executable File
154 lines
5.4 KiB
Python
Executable File
#!/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', 'feature = "native-tls"'),
|
|
('PreferredRuntime', 'feature = "native-tls"'),
|
|
('PreferredRuntime', 'all(feature = "rustls", not(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"'),
|
|
# I have no idea, but empirically this stops the CI complaining -Diziet
|
|
('Conversation', 'feature = "send-control-msg"'),
|
|
]
|
|
|
|
# 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()
|