Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor wrapper and main compile functions #96

Merged
merged 4 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion solcx/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class SolcError(Exception):

def __init__(
self,
command: str,
command: list,
return_code: int,
stdin_data: Optional[str],
stdout_data: str,
Expand Down
142 changes: 105 additions & 37 deletions solcx/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import absolute_import

import functools
import json
import re
Expand Down Expand Up @@ -31,7 +29,13 @@ def get_solc_version() -> Version:
return Version(strip_zeroes_from_month_and_day(version_string))


def _parse_compiler_output(stdoutdata):
def _get_combined_json_outputs() -> str:
help_str = solc_wrapper(help=True)[0].split("\n")
combined_json_args = next(i for i in help_str if i.startswith(" --combined-json"))
return combined_json_args.split(" ")[-1]


def _parse_compiler_output(stdoutdata) -> dict:
output = json.loads(stdoutdata)

contracts = output.get("contracts", {})
Expand All @@ -47,29 +51,47 @@ def _parse_compiler_output(stdoutdata):
return contracts


ALL_OUTPUT_VALUES = (
"abi",
"asm",
"ast",
"bin",
"bin-runtime",
"clone-bin",
"devdoc",
"opcodes",
"userdoc",
)


def compile_source(source, allow_empty=False, output_values=ALL_OUTPUT_VALUES, **kwargs):
if "stdin" in kwargs:
raise ValueError("The `stdin` keyword is not allowed in the `compile_source` function")
if "combined_json" in kwargs:
raise ValueError(
"The `combined_json` keyword is not allowed in the `compile_source` function"
)

combined_json = ",".join(output_values)
compiler_kwargs = dict(stdin=source, combined_json=combined_json, **kwargs)
def compile_source(
source: str,
output_values: list = None,
import_remappings: list = None,
base_path: str = None,
allow_paths: list = None,
output_dir: str = None,
overwrite: bool = False,
evm_version: str = None,
revert_strings: bool = False,
metadata_hash: str = None,
metadata_literal: bool = False,
optimize: bool = False,
optimize_runs: int = None,
no_optimize_yul: bool = False,
yul_optimizations: int = None,
allow_empty: bool = False,
) -> dict:

if output_values is None:
combined_json = _get_combined_json_outputs()
else:
combined_json = ",".join(output_values)

compiler_kwargs = dict(
stdin=source,
combined_json=combined_json,
import_remappings=import_remappings,
base_path=base_path,
allow_paths=allow_paths,
output_dir=output_dir,
overwrite=overwrite,
evm_version=evm_version,
revert_strings=revert_strings,
metadata_hash=metadata_hash,
metadata_literal=metadata_literal,
optimize=optimize,
optimize_runs=optimize_runs,
no_optimize_yul=no_optimize_yul,
yul_optimizations=yul_optimizations,
)

stdoutdata, stderrdata, command, proc = solc_wrapper(**compiler_kwargs)

Expand All @@ -86,14 +108,46 @@ def compile_source(source, allow_empty=False, output_values=ALL_OUTPUT_VALUES, *
return contracts


def compile_files(source_files, allow_empty=False, output_values=ALL_OUTPUT_VALUES, **kwargs):
if "combined_json" in kwargs:
raise ValueError(
"The `combined_json` keyword is not allowed in the `compile_files` function"
)

combined_json = ",".join(output_values)
compiler_kwargs = dict(source_files=source_files, combined_json=combined_json, **kwargs)
def compile_files(
source_files: str,
output_values: list = None,
import_remappings: list = None,
base_path: str = None,
allow_paths: list = None,
output_dir: str = None,
overwrite: bool = False,
evm_version: str = None,
revert_strings: bool = False,
metadata_hash: str = None,
metadata_literal: bool = False,
optimize: bool = False,
optimize_runs: int = None,
no_optimize_yul: bool = False,
yul_optimizations: int = None,
allow_empty: bool = False,
) -> dict:
if output_values is None:
combined_json = _get_combined_json_outputs()
else:
combined_json = ",".join(output_values)

compiler_kwargs = dict(
source_files=source_files,
combined_json=combined_json,
import_remappings=import_remappings,
base_path=base_path,
allow_paths=allow_paths,
output_dir=output_dir,
overwrite=overwrite,
evm_version=evm_version,
revert_strings=revert_strings,
metadata_hash=metadata_hash,
metadata_literal=metadata_literal,
optimize=optimize,
optimize_runs=optimize_runs,
no_optimize_yul=no_optimize_yul,
yul_optimizations=yul_optimizations,
)

stdoutdata, stderrdata, command, proc = solc_wrapper(**compiler_kwargs)

Expand All @@ -110,7 +164,14 @@ def compile_files(source_files, allow_empty=False, output_values=ALL_OUTPUT_VALU
return contracts


def compile_standard(input_data, allow_empty=False, **kwargs):
def compile_standard(
input_data,
base_path: str = None,
allow_paths: list = None,
output_dir: str = None,
overwrite: bool = False,
allow_empty=False,
):
if not input_data.get("sources") and not allow_empty:
raise ContractsNotFound(
command=None,
Expand All @@ -120,10 +181,17 @@ def compile_standard(input_data, allow_empty=False, **kwargs):
stderr_data=None,
)

stdoutdata, stderrdata, command, proc = solc_wrapper(
stdin=json.dumps(input_data), standard_json=True, **kwargs
compiler_kwargs = dict(
stdin=json.dumps(input_data),
standard_json=True,
base_path=base_path,
allow_paths=allow_paths,
output_dir=output_dir,
overwrite=overwrite,
)

stdoutdata, stderrdata, command, proc = solc_wrapper(**compiler_kwargs)

compiler_output = json.loads(stdoutdata)
if "errors" in compiler_output:
has_errors = any(error["severity"] == "error" for error in compiler_output["errors"])
Expand Down
175 changes: 37 additions & 138 deletions solcx/wrapper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import absolute_import

import subprocess
from typing import Any

from semantic_version import Version

Expand All @@ -9,155 +8,44 @@


def solc_wrapper(
solc_binary=None,
stdin=None,
help=None,
version=None,
combined_json=None,
optimize=None,
optimize_runs=None,
libraries=None,
output_dir=None,
gas=None,
assemble=None,
link=None,
source_files=None,
import_remappings=None,
ast=None,
ast_json=None,
asm=None,
asm_json=None,
opcodes=None,
bin=None,
bin_runtime=None,
clone_bin=None,
abi=None,
hashes=None,
userdoc=None,
devdoc=None,
formal=None,
allow_paths=None,
base_path=None,
standard_json=None,
success_return_code=None,
evm_version=None,
solc_binary: str = None,
stdin: str = None,
source_files: list = None,
import_remappings: list = None,
success_return_code: int = None,
**kwargs: Any,
):
if solc_binary is None:
solc_binary = get_executable()

command = [solc_binary]

solc_version = Version(solc_binary.rsplit("-v")[-1].split("\\")[0])
solc_minor = solc_version.minor

if help:
command.append("--help")
if success_return_code is None:
success_return_code = 1
if "help" in kwargs:
success_return_code = 1
elif success_return_code is None:
success_return_code = 0

if version:
command.append("--version")

if optimize:
command.append("--optimize")

if optimize_runs is not None:
command.extend(("--optimize-runs", str(optimize_runs)))

if link:
command.append("--link")

if libraries is not None:
command.extend(("--libraries", libraries))

if output_dir is not None:
command.extend(("--output-dir", output_dir))

if combined_json:
if solc_minor >= 5:
combined_json = combined_json.replace(",clone-bin", "")
command.extend(("--combined-json", combined_json))

if gas:
command.append("--gas")

if allow_paths:
command.extend(("--allow-paths", allow_paths))

if standard_json:
command.append("--standard-json")

if assemble:
command.append("--assemble")

if import_remappings is not None:
command.extend(import_remappings)

if source_files is not None:
command.extend(source_files)

# Output configuration
if ast_json:
command.append("--ast-json")

if asm:
command.append("--asm")

if asm_json:
command.append("--asm-json")

if opcodes:
command.append("--opcodes")

if bin:
command.append("--bin")

if bin_runtime:
command.append("--bin-runtime")

if abi:
command.append("--abi")

if hashes:
command.append("--hashes")

if userdoc:
command.append("--userdoc")

if devdoc:
command.append("--devdoc")

if evm_version:
command.extend(("--evm-version", evm_version))

# unsupported by <0.6.9
if base_path:
if solc_version <= Version("0.6.8"):
raise AttributeError(
"solc {} does not support the --base-path flag".format(solc_version)
)
command.extend(("--base-path", base_path))

# unsupported by >=0.6.0
if ast:
if solc_minor >= 6:
raise AttributeError(f"solc 0.{solc_minor}.x does not support the --ast flag")
command.append("--ast")

# unsupported by >=0.5.0
if clone_bin:
if solc_minor >= 5:
raise AttributeError(f"solc 0.{solc_minor}.x does not support the --clone-bin flag")
command.append("--clone-bin")

if formal:
if solc_minor >= 5:
raise AttributeError(f"solc 0.{solc_minor}.x does not support the --formal flag")
command.append("--formal")
if import_remappings is not None:
command.extend(import_remappings)

if not standard_json and not source_files:
for key, value in kwargs.items():
if value is None or value is False:
continue

key = f"--{key.replace('_', '-')}"
if value is True:
command.append(key)
elif isinstance(value, (int, str)):
command.extend([key, str(value)])
elif isinstance(value, (list, tuple)):
command.extend([key, ",".join(str(i) for i in value)])
else:
raise TypeError(f"Invalid type for {key}: {type(value)}")

if "standard_json" not in kwargs and not source_files:
# indicates that solc should read from stdin
command.append("-")

Expand All @@ -175,6 +63,17 @@ def solc_wrapper(
stdoutdata, stderrdata = proc.communicate(stdin)

if proc.returncode != success_return_code:
solc_version = Version(solc_binary.rsplit("-v")[-1].split("\\")[0])
if stderrdata.startswith("unrecognised option"):
# unrecognised option '<FLAG>'
flag = stderrdata.split("'")[1]
raise AttributeError(f"solc {solc_version} - unsupported flag: {flag}")
if stderrdata.startswith("Invalid option"):
# Invalid option to <FLAG>: <OPTION>
flag, option = stderrdata.split(": ")
flag = flag.split(" ")[-1]
raise ValueError(f"solc {solc_version} - invalid option for {flag} flag: {option}")

raise SolcError(
command=command,
return_code=proc.returncode,
Expand Down
Loading