mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Modernize prepare_report.py and make it easier to extend
- Add argument parser - Add type annotations - Use pathlib - Split into functions - Use dataclasses to separate report data from presentation - Provide more information if the script is interrupted
This commit is contained in:
parent
887497a406
commit
6628f19fd8
@ -1,46 +1,149 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import glob
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from glob import glob
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
SOLC_BIN = sys.argv[1]
|
|
||||||
REPORT_FILE = open("report.txt", mode="w", encoding='utf8', newline='\n')
|
|
||||||
|
|
||||||
for optimize in [False, True]:
|
@dataclass(frozen=True)
|
||||||
for f in sorted(glob.glob("*.sol")):
|
class ContractReport:
|
||||||
sources = {}
|
contract_name: str
|
||||||
sources[f] = {'content': open(f, mode='r', encoding='utf8').read()}
|
file_name: Path
|
||||||
input_json = {
|
bytecode: Optional[str]
|
||||||
'language': 'Solidity',
|
metadata: Optional[str]
|
||||||
'sources': sources,
|
|
||||||
'settings': {
|
|
||||||
'optimizer': {
|
@dataclass
|
||||||
'enabled': optimize
|
class FileReport:
|
||||||
},
|
file_name: Path
|
||||||
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
contract_reports: Optional[List[ContractReport]]
|
||||||
'modelChecker': { "engine": 'none' }
|
|
||||||
}
|
def format_report(self) -> str:
|
||||||
|
report = ""
|
||||||
|
|
||||||
|
if self.contract_reports is None:
|
||||||
|
return f"{self.file_name}: <ERROR>\n"
|
||||||
|
|
||||||
|
for contract_report in self.contract_reports:
|
||||||
|
bytecode = contract_report.bytecode if contract_report.bytecode is not None else '<NO BYTECODE>'
|
||||||
|
metadata = contract_report.metadata if contract_report.metadata is not None else '<NO METADATA>'
|
||||||
|
|
||||||
|
# NOTE: Ignoring contract_report.file_name because it should always be either the same
|
||||||
|
# as self.file_name (for Standard JSON) or just the '<stdin>' placeholder (for CLI).
|
||||||
|
report += f"{self.file_name}:{contract_report.contract_name} {bytecode}\n"
|
||||||
|
report += f"{self.file_name}:{contract_report.contract_name} {metadata}\n"
|
||||||
|
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def load_source(path: Union[Path, str]) -> str:
|
||||||
|
with open(path, mode='r', encoding='utf8') as source_file:
|
||||||
|
file_content = source_file.read()
|
||||||
|
|
||||||
|
return file_content
|
||||||
|
|
||||||
|
|
||||||
|
def parse_standard_json_output(source_file_name: Path, standard_json_output: str) -> FileReport:
|
||||||
|
decoded_json_output = json.loads(standard_json_output.strip())
|
||||||
|
|
||||||
|
if (
|
||||||
|
'contracts' not in decoded_json_output or
|
||||||
|
len(decoded_json_output['contracts']) == 0 or
|
||||||
|
all(len(file_results) == 0 for file_name, file_results in decoded_json_output['contracts'].items())
|
||||||
|
):
|
||||||
|
return FileReport(file_name=source_file_name, contract_reports=None)
|
||||||
|
|
||||||
|
file_report = FileReport(file_name=source_file_name, contract_reports=[])
|
||||||
|
for file_name, file_results in sorted(decoded_json_output['contracts'].items()):
|
||||||
|
for contract_name, contract_results in sorted(file_results.items()):
|
||||||
|
assert file_report.contract_reports is not None
|
||||||
|
file_report.contract_reports.append(ContractReport(
|
||||||
|
contract_name=contract_name,
|
||||||
|
file_name=Path(file_name),
|
||||||
|
bytecode=contract_results.get('evm', {}).get('bytecode', {}).get('object'),
|
||||||
|
metadata=contract_results.get('metadata'),
|
||||||
|
))
|
||||||
|
|
||||||
|
return file_report
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_compiler_input(compiler_path: Path, source_file_name: Path, optimize: bool) -> Tuple[List[str], str]:
|
||||||
|
json_input: dict = {
|
||||||
|
'language': 'Solidity',
|
||||||
|
'sources': {
|
||||||
|
str(source_file_name): {'content': load_source(source_file_name)}
|
||||||
|
},
|
||||||
|
'settings': {
|
||||||
|
'optimizer': {'enabled': optimize},
|
||||||
|
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
||||||
|
'modelChecker': {'engine': 'none'},
|
||||||
}
|
}
|
||||||
args = [SOLC_BIN, '--standard-json']
|
}
|
||||||
if optimize:
|
|
||||||
args += ['--optimize']
|
|
||||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
(out, err) = proc.communicate(json.dumps(input_json).encode('utf-8'))
|
|
||||||
|
|
||||||
result = json.loads(out.decode('utf-8').strip())
|
command_line = [str(compiler_path), '--standard-json']
|
||||||
if (
|
compiler_input = json.dumps(json_input)
|
||||||
'contracts' not in result or
|
|
||||||
len(result['contracts']) == 0 or
|
|
||||||
all(len(file_results) == 0 for file_name, file_results in result['contracts'].items())
|
|
||||||
):
|
|
||||||
REPORT_FILE.write(f + ": <ERROR>\n")
|
|
||||||
else:
|
|
||||||
for filename in sorted(result['contracts'].keys()):
|
|
||||||
for contractName in sorted(result['contracts'][filename].keys()):
|
|
||||||
bytecode = result['contracts'][filename][contractName].get('evm', {}).get('bytecode', {}).get('object', '<NO BYTECODE>')
|
|
||||||
metadata = result['contracts'][filename][contractName].get('metadata', '<NO METADATA>')
|
|
||||||
|
|
||||||
REPORT_FILE.write(filename + ':' + contractName + ' ' + bytecode + '\n')
|
return (command_line, compiler_input)
|
||||||
REPORT_FILE.write(filename + ':' + contractName + ' ' + metadata + '\n')
|
|
||||||
|
|
||||||
|
def run_compiler(compiler_path: Path, source_file_name: Path, optimize: bool) -> FileReport:
|
||||||
|
(command_line, compiler_input) = prepare_compiler_input(compiler_path, Path(Path(source_file_name).name), optimize)
|
||||||
|
|
||||||
|
process = subprocess.run(
|
||||||
|
command_line,
|
||||||
|
input=compiler_input,
|
||||||
|
encoding='utf8',
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
return parse_standard_json_output(Path(source_file_name), process.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_report(source_file_names: List[str], compiler_path: Path):
|
||||||
|
with open('report.txt', mode='w', encoding='utf8', newline='\n') as report_file:
|
||||||
|
for optimize in [False, True]:
|
||||||
|
for source_file_name in sorted(source_file_names):
|
||||||
|
try:
|
||||||
|
report = run_compiler(Path(compiler_path), Path(source_file_name), optimize)
|
||||||
|
report_file.write(report.format_report())
|
||||||
|
except subprocess.CalledProcessError as exception:
|
||||||
|
print(
|
||||||
|
f"\n\nInterrupted by an exception while processing file "
|
||||||
|
f"'{source_file_name}' with optimize={optimize}\n\n"
|
||||||
|
f"COMPILER STDOUT:\n{exception.stdout}\n"
|
||||||
|
f"COMPILER STDERR:\n{exception.stderr}\n",
|
||||||
|
file=sys.stderr
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
print(
|
||||||
|
f"\n\nInterrupted by an exception while processing file "
|
||||||
|
f"'{source_file_name}' with optimize={optimize}\n",
|
||||||
|
file=sys.stderr
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def commandline_parser() -> ArgumentParser:
|
||||||
|
script_description = (
|
||||||
|
"Generates a report listing bytecode and metadata obtained by compiling all the "
|
||||||
|
"*.sol files found in the current working directory using the provided binary."
|
||||||
|
)
|
||||||
|
|
||||||
|
parser = ArgumentParser(description=script_description)
|
||||||
|
parser.add_argument(dest='compiler_path', help="Solidity compiler executable")
|
||||||
|
return parser;
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
options = commandline_parser().parse_args()
|
||||||
|
generate_report(
|
||||||
|
glob("*.sol"),
|
||||||
|
Path(options.compiler_path),
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user