def __execute(compiler_version: VersionString, input_config: Dict, allow_paths: Optional[List[str]]): """Executes the solcx command and underlying solc wrapper""" # Lazy import to allow for optional installation of solcx try: from solcx.install import get_executable from solcx.main import compile_standard except ImportError: raise DevelopmentInstallationRequired(importable_name='solcx') # Prepare Solc Command solc_binary_path: Path = get_executable(version=compiler_version) _allow_paths = ',' + ','.join(str(p) for p in allow_paths) # Execute Compilation try: compiler_output = compile_standard(input_data=input_config, allow_paths=_allow_paths, solc_binary=solc_binary_path) except FileNotFoundError: raise CompilationError("The solidity compiler is not at the specified path. " "Check that the file exists and is executable.") except PermissionError: raise CompilationError(f"The solidity compiler binary at {solc_binary_path} is not executable. " "Check the file's permissions.") errors = compiler_output.get('errors') if errors: formatted = '\n'.join([error['formattedMessage'] for error in errors]) SOLC_LOGGER.warn(f"Errors during compilation: \n{formatted}") SOLC_LOGGER.info(f"Successfully compiled {len(compiler_output)} sources with {OPTIMIZER_RUNS} optimization runs") return compiler_output
def _compile_combined_json( output_values: Optional[List], solc_binary: Union[str, Path, None], solc_version: Optional[Version], output_dir: Union[str, Path, None], overwrite: Optional[bool], allow_empty: Optional[bool], **kwargs: Any, ) -> Dict: if output_values is None: combined_json = _get_combined_json_outputs() else: combined_json = ",".join(output_values) if solc_binary is None: solc_binary = get_executable(solc_version) if output_dir: output_dir = Path(output_dir) if output_dir.is_file(): raise FileExistsError( "`output_dir` must be as a directory, not a file") if output_dir.joinpath("combined.json").exists() and not overwrite: target_path = output_dir.joinpath("combined.json") raise FileExistsError( f"Target output file {target_path} already exists - use overwrite=True to overwrite" ) stdoutdata, stderrdata, command, proc = wrapper.solc_wrapper( solc_binary=solc_binary, combined_json=combined_json, output_dir=output_dir, overwrite=overwrite, **kwargs, ) if output_dir: output_path = Path(output_dir).joinpath("combined.json") if stdoutdata: output_path.parent.mkdir(parents=True, exist_ok=True) with output_path.open("w") as fp: fp.write(stdoutdata) else: with output_path.open() as fp: stdoutdata = fp.read() contracts = _parse_compiler_output(stdoutdata) if not contracts and not allow_empty: raise ContractsNotFound( command=command, return_code=proc.returncode, stdout_data=stdoutdata, stderr_data=stderrdata, ) return contracts
def _get_combined_json_outputs(solc_binary: Union[Path, str] = None) -> str: if solc_binary is None: solc_binary = get_executable() help_str = wrapper.solc_wrapper(solc_binary=solc_binary, 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 get_solc_version() -> Version: """ Get the version of the active `solc` binary. Returns ------- Version solc version """ solc_binary = get_executable() return wrapper._get_solc_version(solc_binary)
def get_solc_version(with_commit_hash: bool = False) -> Version: """ Get the version of the active `solc` binary. Arguments --------- with_commit_hash : bool, optional If True, the commit hash is included within the version Returns ------- Version solc version """ solc_binary = get_executable() return wrapper._get_solc_version(solc_binary, with_commit_hash)
def __init__(self, source_dirs: List[SourceDirs] = None, ignore_solidity_check: bool = False) -> None: # Allow for optional installation from solcx.install import get_executable self.log = Logger('solidity-compiler') version = SOLIDITY_COMPILER_VERSION if not ignore_solidity_check else None self.__sol_binary_path = get_executable(version=version) if source_dirs is None or len(source_dirs) == 0: self.source_dirs = [ SourceDirs(root_source_dir=self.__default_contract_dir) ] else: self.source_dirs = source_dirs
def link_code( unlinked_bytecode: str, libraries: Dict, solc_binary: Union[str, Path] = None, solc_version: Version = None, ) -> str: """ Add library addresses into unlinked bytecode. Arguments --------- unlinked_bytecode : str Compiled bytecode containing one or more library placeholders. libraries : Dict Library addresses given as {"library name": "address"} solc_binary : str | Path, optional Path of the `solc` binary to use. If not given, the currently active version is used (as set by `solcx.set_solc_version`) solc_version: Version, optional `solc` version to use. If not given, the currently active version is used. Ignored if `solc_binary` is also given. Returns ------- str Linked bytecode """ if solc_binary is None: solc_binary = get_executable(solc_version) library_list = [f"{name}:{address}" for name, address in libraries.items()] stdoutdata = wrapper.solc_wrapper(solc_binary=solc_binary, stdin=unlinked_bytecode, link=True, libraries=library_list)[0] return stdoutdata.replace("Linking completed.", "").strip()
def compile_standard( input_data: Dict, base_path: str = None, allow_paths: List = None, output_dir: str = None, overwrite: bool = False, solc_binary: Union[str, Path] = None, solc_version: Version = None, allow_empty: bool = False, ) -> Dict: """ Compile Solidity contracts using the JSON-input-output interface. See the Solidity documentation for details on the expected JSON input and output formats. Arguments --------- input_data : Dict Compiler JSON input. base_path : Path | str, optional Use the given path as the root of the source tree instead of the root of the filesystem. allow_paths : List | Path | str, optional A path, or list of paths, to allow for imports. output_dir : str, optional Creates one file per component and contract/file at the specified directory. overwrite : bool, optional Overwrite existing files (used in combination with `output_dir`) solc_binary : str | Path, optional Path of the `solc` binary to use. If not given, the currently active version is used (as set by `solcx.set_solc_version`) solc_version: Version, optional `solc` version to use. If not given, the currently active version is used. Ignored if `solc_binary` is also given. allow_empty : bool, optional If `True`, do not raise when no compiled contracts are returned. Returns ------- Dict Compiler JSON output. """ if not input_data.get("sources") and not allow_empty: raise ContractsNotFound( "Input JSON does not contain any sources", stdin_data=json.dumps(input_data, sort_keys=True, indent=2), ) if solc_binary is None: solc_binary = get_executable(solc_version) stdoutdata, stderrdata, command, proc = wrapper.solc_wrapper( solc_binary=solc_binary, stdin=json.dumps(input_data), standard_json=True, base_path=base_path, allow_paths=allow_paths, output_dir=output_dir, overwrite=overwrite, ) compiler_output = json.loads(stdoutdata) if "errors" in compiler_output: has_errors = any(error["severity"] == "error" for error in compiler_output["errors"]) if has_errors: error_message = "\n".join( tuple(error["formattedMessage"] for error in compiler_output["errors"] if error["severity"] == "error")) raise SolcError( error_message, command=command, return_code=proc.returncode, stdin_data=json.dumps(input_data), stdout_data=stdoutdata, stderr_data=stderrdata, error_dict=compiler_output["errors"], ) return compiler_output
def solc_wrapper( solc_binary: Union[Path, str] = None, stdin: str = None, source_files: Union[List, Path, str] = None, import_remappings: Union[Dict, List, str] = None, success_return_code: int = None, **kwargs: Any, ) -> Tuple[str, str, List, subprocess.Popen]: """ Wrapper function for calling to `solc`. Arguments --------- solc_binary : Path | str, optional Location of the `solc` binary. If not given, the current default binary is used. stdin : str, optional Input to pass to `solc` via stdin source_files : list | Path | str, optional Path, or list of paths, of sources to compile import_remappings : Dict | List | str, optional Path remappings. May be given as a string or list of strings formatted as `"prefix=path"` or a dict of `{"prefix": "path"}` success_return_code : int, optional Expected exit code. Raises `SolcError` if the process returns a different value. Keyword Arguments ----------------- **kwargs : Any Flags to be passed to `solc`. Keywords are converted to flags by prepending `--` and replacing `_` with `-`, for example the keyword `evm_version` becomes `--evm-version`. Values may be given in the following formats: * `False`, `None`: ignored * `True`: flag is used without any arguments * str: given as an argument without modification * int: given as an argument, converted to a string * Path: converted to a string via `Path.as_posix()` * List, Tuple: elements are converted to strings and joined with `,` Returns ------- str Process `stdout` output str Process `stderr` output List Full command executed by the function Popen Subprocess object used to call `solc` """ if solc_binary: solc_binary = Path(solc_binary) else: solc_binary = install.get_executable() solc_version = _get_solc_version(solc_binary) command: List = [str(solc_binary)] if success_return_code is None: success_return_code = 1 if "help" in kwargs else 0 if source_files is not None: if isinstance(source_files, (str, Path)): command.append(_to_string("source_files", source_files)) else: command.extend([_to_string("source_files", i) for i in source_files]) if import_remappings is not None: if isinstance(import_remappings, str): command.append(import_remappings) else: if isinstance(import_remappings, dict): import_remappings = [f"{k}={v}" for k, v in import_remappings.items()] command.extend(import_remappings) 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) else: command.extend([key, _to_string(key, value)]) if "standard_json" not in kwargs and not source_files: # indicates that solc should read from stdin command.append("-") if stdin is not None: stdin = str(stdin) proc = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf8", ) stdoutdata, stderrdata = proc.communicate(stdin) if proc.returncode != success_return_code: if stderrdata.startswith("unrecognised option"): # unrecognised option '<FLAG>' flag = stderrdata.split("'")[1] raise UnknownOption(f"solc {solc_version} does not support the '{flag}' option'") if stderrdata.startswith("Invalid option"): # Invalid option to <FLAG>: <OPTION> flag, option = stderrdata.split(": ") flag = flag.split(" ")[-1] raise UnknownValue( f"solc {solc_version} does not accept '{option}' as an option for the '{flag}' flag" ) raise SolcError( command=command, return_code=proc.returncode, stdin_data=stdin, stdout_data=stdoutdata, stderr_data=stderrdata, ) return stdoutdata, stderrdata, command, proc