\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..0856139
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,195 @@
+#!/usr/bin/python3
+
+from pathlib import Path
+from typing import Dict, List
+
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+def setup(sphinx):
+ pass
+
+
+with Path(__file__).parent.joinpath("../setup.py").open() as fp:
+ for line in fp:
+ if "version=" in line:
+ setup_version = line.split('"')[1]
+ break
+
+
+# -- Project information -----------------------------------------------------
+
+
+project = "py-solc-x"
+copyright = "2020"
+author = "Ben Hauser"
+
+# The short X.Y version
+version = setup_version
+# The full version, including alpha/beta/rc tags
+release = setup_version
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions: List = ["sphinx.ext.intersphinx"]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = ".rst"
+
+# The master toctree document.
+master_doc = "toctree"
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = "sphinx"
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "sphinx_rtd_theme"
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+html_css_files = ["css/toggle.css", "css/dark.css"]
+
+html_js_files = ["js/toggle.js"]
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = "Browniedoc"
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements: Dict = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [(master_doc, "solcx.tex", "py-solc-x Documentation", "Ben Hauser", "manual")]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(master_doc, "solcx", "py-solc-x Documentation", [author], 1)]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (
+ master_doc,
+ "py-solc-x",
+ "py-solc-x Documentation",
+ author,
+ "py-solc-x",
+ "Python wrapper and version management tool for the solc Solidity compiler.",
+ "py-solc-x",
+ )
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ["search.html"]
+
+intersphinx_mapping = {
+ "python": ("https://docs.python.org/3.8/", None),
+ "semantic_version": ("https://python-semanticversion.readthedocs.io/en/latest/", None),
+}
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..fbb8e22
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,22 @@
+=========
+py-solc-x
+=========
+
+Python wrapper and version management tool for the ``solc`` `Solidity `_ compiler.
+
+Features
+========
+
+* Full support for Solidity versions ``>=0.4.11``
+* Installs Solidity on Linux, OSX and Windows
+* Compiles Solidity from source on Linux and OSX
+
+Credit
+======
+
+`py-solc-x `_ is forked from `py-solc `_ which was written by `Piper Merriam `_.
+
+Dependencies
+============
+
+Py-solc-x allows the use of multiple versions of solc, and can install or compile them as needed. If you wish to compile from source you must first install the required `solc dependencies `_.
diff --git a/docs/solc-wrapper.rst b/docs/solc-wrapper.rst
new file mode 100644
index 0000000..10bcf05
--- /dev/null
+++ b/docs/solc-wrapper.rst
@@ -0,0 +1,34 @@
+=============================
+The Low-Level Process Wrapper
+=============================
+
+Along with the :ref:`main compiler functions `, you can also directly call ``solc`` using the low-level wrapper.
+
+.. py:function:: solc_wrapper(solc_binary=None, stdin=None, source_files=None, import_remappings=None, success_return_code=None, **kwargs)
+
+ Wrapper function for calling to ``solc``.
+
+ Returns the process ``stdout`` as a string, ``stderr`` as a string, the full command executed as a list of strings, and the completed :py:class:`Popen ` object used to call ``solc``.
+
+ **Arguments**
+
+ ``solc_binary`` : Path | str
+ Location of the ``solc`` binary. If not given, the current default binary is used.
+ ``stdin`` : str
+ Input to pass to ``solc`` via stdin
+ ``source_files`` List | Path | str
+ Solidity source file, or list of source files, to be compiled. Files may be given as strings or :py:class:`Path ` objects.
+ ``import_remappings`` : Dict | List | str
+ 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
+ Expected exit code. Raises ``SolcError`` if the process returns a different value. Defaults to ``0``.
+ ``**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`` or ``None``: The flag is ignored
+ * ``True``: The flag is passed to the compiler without any arguments
+ * ``str``: The value is given as an argument without any modification
+ * ``int``: The value is converted to a string
+ * ``Path``: The value is converted to a string via :py:meth:`Path.as_posix `
+ * ``List`` or ``Tuple``: Elements in the sequence are converted to strings and joined with ``,``
diff --git a/docs/toctree.rst b/docs/toctree.rst
new file mode 100644
index 0000000..ae76270
--- /dev/null
+++ b/docs/toctree.rst
@@ -0,0 +1,11 @@
+=======
+Brownie
+=======
+
+.. toctree::
+ :maxdepth: 2
+
+ Overview
+ using-the-compiler.rst
+ solc-wrapper.rst
+ version-management.rst
diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst
new file mode 100644
index 0000000..4d212e6
--- /dev/null
+++ b/docs/using-the-compiler.rst
@@ -0,0 +1,245 @@
+.. _using-the-compiler:
+
+==================
+Using the Compiler
+==================
+
+py-solc-x provides several functions that you can use to interact with the ``solc`` compiler.
+
+Compiling a Source String
+=========================
+
+.. py:function:: solcx.compile_source(source, **kwargs)
+
+ Compile a Solidity contract.
+
+ Compilation is handled via the ``--combined-json`` flag. Depending on the Solidity version used, some keyword arguments may not be available.
+
+ Returns a dict, where each top-level key is a contract. The filename will be ````.
+
+ .. code-block:: python
+
+ >>> import solcx
+ >>> solcx.compile_source(
+ ... "contract Foo { function bar() public { return; } }",
+ ... output_values=["abi", "bin-runtime"],
+ ... solc_version="0.7.0"
+ ... )
+ {
+ ':Foo': {
+ 'abi': [{'inputs': [], 'name': 'bar', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}],
+ 'bin-runtime': '6080604052348015600f57600080fd5b506004361060285760003560e01c8063febb0f7e14602d575b600080fd5b60336035565b005b56fea26469706673582212203cfdbce82ee8eab351107edac2ebb9dbe5c1aa8bd26609b0eedaa105ed3d4dce64736f6c63430007000033'
+ }
+ }
+
+ **Required Arguments**
+
+ ``source`` str
+ Solidity contract to be compiled.
+
+ **Optional py-solc-x Arguments**
+
+ ``solc_binary`` str | Path
+ Path of the ``solc`` binary to use. May be given as a string or :py:class:`Path ` object. If not given, the currently active version is used (as set by :func:`solcx.set_solc_version `)
+ ``solc_version`` str | Version
+ ``solc`` version to use. May be given as a string or :py:class:`Version ` object. If not given, the currently active version is used. Ignored if ``solc_binary`` is also given.
+ ``allow_empty`` bool
+ If ``True``, do not raise when no compiled contracts are returned. Defaults to ``False``.
+
+ **Optional Compiler Arguments**
+
+ Depending on the Solidity version used, using some of these arguments may raise ``UnknownOption``. See the documentation for your target Solidity version for more information.
+
+ ``output_values`` List
+ Compiler outputs to return. Valid options depend on the version of ``solc``.
+ If not given, all possible outputs for the active version are returned.
+ ``import_remappings`` Dict | List | str
+ Path remappings. May be given as a string or list of strings formatted as
+ ``"prefix=path"``, or a dict of ``{"prefix": "path"}``.
+ ``base_path`` Path | str
+ Use the given path as the root of the source tree instead of the root
+ of the filesystem.
+ ``allow_paths`` List | Path | str
+ A path, or list of paths, to allow for imports.
+ ``output_dir`` str
+ Creates one file per component and contract/file at the specified directory.
+ ``overwrite`` bool
+ Overwrite existing files (used in combination with ``output_dir``)
+ ``evm_version`` str
+ Select the desired EVM version. Valid options depend on the ``solc`` version.
+ ``revert_strings`` List | str
+ Strip revert (and require) reason strings or add additional debugging
+ information.
+ ``metadata_hash`` str
+ Choose hash method for the bytecode metadata or disable it.
+ ``metadata_literal`` bool
+ Store referenced sources as literal data in the metadata output.
+ ``optimize`` bool
+ Enable bytecode optimizer.
+ ``optimize_runs`` int
+ Set for how many contract runs to optimize. Lower values will optimize
+ more for initial deployment cost, higher values will optimize more for
+ high-frequency usage.
+ ``optimize_yul`` bool
+ Enable the yul optimizer.
+ ``no_optimize_yul`` bool
+ Disable the yul optimizer.
+ ``yul_optimizations`` int
+ Force yul optimizer to use the specified sequence of optimization steps
+ instead of the built-in one.
+
+Compiling Files
+===============
+
+.. py:function:: solcx.compile_files(source, **kwargs)
+
+ Compile one or more Solidity source files.
+
+ Compilation is handled via the ``--combined-json`` flag. Depending on the Solidity version used, some keyword arguments may not be available.
+
+ Returns a dict, where each top-level key is a contract.
+
+ .. code-block:: python
+
+ >>> import solcx
+ >>> solcx.compile_files(
+ ... ["Foo.sol"],
+ ... output_values=["abi", "bin-runtime"],
+ ... solc_version="0.7.0"
+ ... )
+ {
+ ':Foo': {
+ 'abi': [{'inputs': [], 'name': 'bar', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}],
+ 'bin-runtime': '6080604052348015600f57600080fd5b506004361060285760003560e01c8063febb0f7e14602d575b600080fd5b60336035565b005b56fea26469706673582212203cfdbce82ee8eab351107edac2ebb9dbe5c1aa8bd26609b0eedaa105ed3d4dce64736f6c63430007000033'
+ }
+ }
+
+ **Required Arguments**
+
+ ``source_files`` List | Path | str
+ Solidity source file, or list of source files, to be compiled. Files may be given as strings or :py:class:`Path ` objects.
+
+ **Optional py-solc-x Arguments**
+
+ ``solc_binary`` str | Path
+ Path of the ``solc`` binary to use. May be given as a string or :py:class:`Path ` object. If not given, the currently active version is used (as set by :func:`solcx.set_solc_version `)
+ ``solc_version`` str | Version
+ ``solc`` version to use. May be given as a string or :py:class:`Version ` object. If not given, the currently active version is used. Ignored if ``solc_binary`` is also given.
+ ``allow_empty`` bool
+ If ``True``, do not raise when no compiled contracts are returned. Defaults to ``False``.
+
+ **Optional Compiler Arguments**
+
+ Depending on the Solidity version used, using some of these arguments may raise ``UnknownOption``. See the documentation for your target Solidity version for more information.
+
+ ``output_values`` List
+ Compiler outputs to return. Valid options depend on the version of ``solc``.
+ If not given, all possible outputs for the active version are returned.
+ ``import_remappings`` Dict | List | str
+ Path remappings. May be given as a string or list of strings formatted as
+ ``"prefix=path"``, or a dict of ``{"prefix": "path"}``.
+ ``base_path`` Path | str
+ Use the given path as the root of the source tree instead of the root
+ of the filesystem.
+ ``allow_paths`` List | Path | str
+ A path, or list of paths, to allow for imports.
+ ``output_dir`` str
+ Creates one file per component and contract/file at the specified directory.
+ ``overwrite`` bool
+ Overwrite existing files (used in combination with ``output_dir``)
+ ``evm_version`` str
+ Select the desired EVM version. Valid options depend on the ``solc`` version.
+ ``revert_strings`` List | str
+ Strip revert (and require) reason strings or add additional debugging
+ information.
+ ``metadata_hash`` str
+ Choose hash method for the bytecode metadata or disable it.
+ ``metadata_literal`` bool
+ Store referenced sources as literal data in the metadata output.
+ ``optimize`` bool
+ Enable bytecode optimizer.
+ ``optimize_runs`` int
+ Set for how many contract runs to optimize. Lower values will optimize
+ more for initial deployment cost, higher values will optimize more for
+ high-frequency usage.
+ ``optimize_yul`` bool
+ Enable the yul optimizer.
+ ``no_optimize_yul`` bool
+ Disable the yul optimizer.
+ ``yul_optimizations`` int
+ Force yul optimizer to use the specified sequence of optimization steps
+ instead of the built-in one.
+
+Compiling with the Standard JSON Format
+=======================================
+
+.. py:function:: solcx.compile_standard(input_data, **kwargs)
+
+ Compile Solidity contracts using the JSON-input-output interface.
+
+ See the Solidity documentation on `the compiler input-output JSON `_ for details on the expected JSON input and output formats.
+
+ **Required Arguments**
+
+ ``input_data`` Dict
+ Compiler JSON input.
+
+ **Optional py-solc-x Arguments**
+
+ ``solc_binary`` str | Path
+ Path of the ``solc`` binary to use. May be given as a string or :py:class:`Path ` object. If not given, the currently active version is used (as set by :func:`solcx.set_solc_version `)
+ ``solc_version`` str | Version
+ ``solc`` version to use. May be given as a string or :py:class:`Version ` object. If not given, the currently active version is used. Ignored if ``solc_binary`` is also given.
+ ``allow_empty`` bool
+ If ``True``, do not raise when no compiled contracts are returned. Defaults to ``False``.
+
+ **Optional Compiler Arguments**
+
+ Depending on the Solidity version used, using some of these arguments may raise ``UnknownOption``. See the documentation for your target Solidity version for more information.
+
+ ``base_path`` Path | str
+ Use the given path as the root of the source tree instead of the root
+ of the filesystem.
+ ``allow_paths`` List | Path | str
+ A path, or list of paths, to allow for imports.
+ ``output_dir`` str
+ Creates one file per component and contract/file at the specified directory.
+ ``overwrite`` bool
+ Overwrite existing files (used in combination with ``output_dir``)
+
+Linking Libraries
+=================
+
+.. py:function:: solcx.link_code(unlinked_bytecode, libraries, solc_binary=None, solc_version=None)
+
+ Add library addresses into unlinked bytecode.
+
+ See the Solidity documentation on `using the commandline compiler `_ for more information on linking libraries.
+
+ Returns the linked bytecode as a string.
+
+ .. code-block:: python
+
+ >>> import solcx
+ >>> unlinked_bytecode = "606060405260768060106000396000f3606060405260e060020a6000350463e7f09e058114601a575b005b60187f0c55699c00000000000000000000000000000000000000000000000000000000606090815273__TestA_________________________________90630c55699c906064906000906004818660325a03f41560025750505056"
+
+ >>> solcx.link_code(
+ ... unlinked_bytecode,
+ ... {'TestA': "0xd3cda913deb6f67967b99d67acdfa1712c293601"}
+ ... )
+ "606060405260768060106000396000f3606060405260e060020a6000350463e7f09e058114601a575b005b60187f0c55699c00000000000000000000000000000000000000000000000000000000606090815273d3cda913deb6f67967b99d67acdfa1712c29360190630c55699c906064906000906004818660325a03f41560025750505056"
+
+
+ **Required Arguments**
+
+ ``unlinked_bytecode`` str
+ Compiled bytecode containing one or more library placeholders.
+ ``libraries`` Dict
+ Library addresses given as ``{"library name": "address"}``
+
+ **Optional py-solc-x Arguments**
+
+ ``solc_binary`` str | Path
+ Path of the ``solc`` binary to use. May be given as a string or :py:class:`Path ` object. If not given, the currently active version is used (as set by :func:`solcx.set_solc_version `)
+ ``solc_version`` str | Version
+ ``solc`` version to use. May be given as a string or :py:class:`Version ` object. If not given, the currently active version is used. Ignored if ``solc_binary`` is also given.
diff --git a/docs/version-management.rst b/docs/version-management.rst
new file mode 100644
index 0000000..2b68ce2
--- /dev/null
+++ b/docs/version-management.rst
@@ -0,0 +1,177 @@
+===========================
+Solidity Version Management
+===========================
+
+Installation Folder
+===================
+
+By default, ``solc`` versions are installed at ``~/.solcx/``. Each installed version is named using the following pattern: ``solc-v[MAJOR].[MINOR].[PATH]``
+
+If you wish to install to a different directory you can specify it with the ``SOLCX_BINARY_PATH`` environment variable. You can also give a custom directory to most installation functions using the optional ``solcx_binary_path`` keyword argument.
+
+.. py:function:: solcx.get_solcx_install_folder(solcx_binary_path=None)
+
+ Return the directory where py-solc-x stores installed ``solc`` binaries.
+
+ .. code-block:: python
+
+ >>> solcx.get_solcx_install_folder()
+ PosixPath('/home/computer/.solcx')
+
+Getting and Setting the Active Version
+======================================
+
+When py-solc-x is imported, it attempts to locate an installed version of ``solc`` using ``which`` on Linux or OSX systems, or ``where.exe`` on Windows. If found, this version is set as the active version. If not found, it uses the latest version that has been installed by py-solc-x.
+
+
+Getting the Active Version
+--------------------------
+
+Use the following methods to check the active ``solc`` version:
+
+.. py:function:: solcx.get_solc_version()
+
+ Return the version of the current active ``solc`` binary, as a :py:class:`Version ` object.
+
+ .. code-block:: python
+
+ >>> solcx.get_solc_version()
+ Version('0.7.0')
+
+.. py:function:: solcx.install.get_executable(version=None, solcx_binary_path=None)
+
+ Return a :py:class:`Path ` object for a ``solc`` binary.
+
+ If no arguments are given, returns the current active version. If a version is specified, returns the installed binary matching the given version.
+
+ Raises ``SolcNotInstalled`` if no binary is found.
+
+ .. code-block:: python
+
+ >>> solcx.install.get_executable()
+ PosixPath('/usr/bin/solc')
+
+.. py:function:: solcx.get_installed_solc_versions(solcx_binary_path=None)
+
+ Return a list of currently installed ``solc`` versions.
+
+ .. code-block:: python
+
+ >>> solcx.get_installed_solc_versions()
+ [Version('0.7.0'), Version('0.6.8'), Version('0.6.3'), Version('0.5.7'), Version('0.4.25')]
+
+Setting the Active Version
+--------------------------
+
+.. py:function:: solcx.set_solc_version(version, silent=False, solcx_binary_path=None)
+
+ Set the currently active ``solc`` version.
+
+ .. code-block:: python
+
+ >>> solcx.set_solc_version('0.5.0')
+
+.. py:function:: solcx.set_solc_version_pragma(pragma_string, silent=False, check_new=False)
+
+ Set the currently active ``solc`` binary based on a pragma statement.
+
+ The newest installed version that matches the pragma is chosen. Raises ``SolcNotInstalled`` if no installed versions match.
+
+ .. code-block:: python
+
+ >>> solcx.set_solc_version_pragma('pragma solidity ^0.5.0;')
+ Version('0.5.17')
+
+
+Importing Already-Installed Versions
+====================================
+
+.. py:function:: solcx.import_installed_solc(solcx_binary_path=None)
+
+ Search for and copy installed ``solc`` versions into the local installation folder.
+
+ This function is especially useful on OSX, to access Solidity versions that you have installed from homebrew and where a precompiled binary is not available.
+
+ .. code-block:: python
+
+ >>> solcx.import_installed_solc()
+ [Version('0.7.0'), Version('0.6.12')]
+
+
+Installing Solidity
+===================
+
+py-solc-x downloads and installs precompiled binaries from `solc-bin.ethereum.org `_. Different binaries are available depending on your operating system.
+
+Getting Installable Versions
+----------------------------
+
+.. py:function:: solcx.get_installable_solc_versions()
+
+ Return a list of all ``solc`` versions that can be installed by py-solc-x.
+
+
+ .. code-block:: python
+
+ >>> solcx.get_installable_solc_versions()
+ [Version('0.7.0'), Version('0.6.12'), Version('0.6.11'), Version('0.6.10'), Version('0.6.9'), Version('0.6.8'), Version('0.6.7'), Version('0.6.6'), Version('0.6.5'), Version('0.6.4'), Version('0.6.3'), Version('0.6.2'), Version('0.6.1'), Version('0.6.0'), Version('0.5.17'), Version('0.5.16'), Version('0.5.15'), Version('0.5.14'), Version('0.5.13'), Version('0.5.12'), Version('0.5.11'), Version('0.5.10'), Version('0.5.9'), Version('0.5.8'), Version('0.5.7'), Version('0.5.6'), Version('0.5.5'), Version('0.5.4'), Version('0.5.3'), Version('0.5.2'), Version('0.5.1'), Version('0.5.0'), Version('0.4.26'), Version('0.4.25'), Version('0.4.24'), Version('0.4.23'), Version('0.4.22'), Version('0.4.21'), Version('0.4.20'), Version('0.4.19'), Version('0.4.18'), Version('0.4.17'), Version('0.4.16'), Version('0.4.15'), Version('0.4.14'), Version('0.4.13'), Version('0.4.12'), Version('0.4.11')]
+
+Installing Precompiled Binaries
+-------------------------------
+
+.. py:function:: solcx.install_solc(version="latest", show_progress=False, solcx_binary_path=None)
+
+ Download and install a precompiled ``solc`` binary.
+
+ ``version`` str | Version
+ Version of ``solc`` to install. Default is the newest available version.
+ ``show_progress`` bool
+ If ``True``, display a progress bar while downloading. Requires installing
+ the `tqdm `_ package.
+ ``solcx_binary_path`` Path | str
+ User-defined path, used to override the default installation directory.
+
+Building from Source
+====================
+
+When a precompiled version of Solidity isn't available for your operating system, you may still install it by building from the source code. Source code is downloaded from `Github `_.
+
+.. note::
+
+ If you wish to compile from source you must first install the required `solc dependencies `_.
+
+
+Getting Compilable Versions
+---------------------------
+
+.. py:function:: solcx.get_compilable_solc_versions(headers=None)
+
+ Return a list of all ``solc`` versions that can be installed by py-solc-x.
+
+ ``headers`` Dict
+ Headers to include in the request to Github.
+
+ .. code-block:: python
+
+ >>> solcx.get_compilable_solc_versions()
+ [Version('0.7.0'), Version('0.6.12'), Version('0.6.11'), Version('0.6.10'), Version('0.6.9'), Version('0.6.8'), Version('0.6.7'), Version('0.6.6'), Version('0.6.5'), Version('0.6.4'), Version('0.6.3'), Version('0.6.2'), Version('0.6.1'), Version('0.6.0'), Version('0.5.17'), Version('0.5.16'), Version('0.5.15'), Version('0.5.14'), Version('0.5.13'), Version('0.5.12'), Version('0.5.11'), Version('0.5.10'), Version('0.5.9'), Version('0.5.8'), Version('0.5.7'), Version('0.5.6'), Version('0.5.5'), Version('0.5.4'), Version('0.5.3'), Version('0.5.2'), Version('0.5.1'), Version('0.5.0'), Version('0.4.26'), Version('0.4.25'), Version('0.4.24'), Version('0.4.23'), Version('0.4.22'), Version('0.4.21'), Version('0.4.20'), Version('0.4.19'), Version('0.4.18'), Version('0.4.17'), Version('0.4.16'), Version('0.4.15'), Version('0.4.14'), Version('0.4.13'), Version('0.4.12'), Version('0.4.11')]
+
+
+Compiling Solidity from Source
+------------------------------
+
+.. py:function:: solcx.compile_solc(version, show_progress=False, solcx_binary_path=None)
+
+ Install a version of ``solc`` by downloading and compiling source code.
+
+ This function is only available when using Linux or OSX.
+
+ **Arguments:**
+
+ ``version`` str | Version
+ Version of ``solc`` to install.
+ ``show_progress`` bool
+ If ``True``, display a progress bar while downloading. Requires installing
+ the `tqdm `_ package.
+ ``solcx_binary_path`` Path | str
+ User-defined path, used to override the default installation directory.
diff --git a/requirements-dev.txt b/requirements-dev.txt
index cc26b40..7e3ce1d 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,11 +1,15 @@
black==19.10b0
bumpversion==0.5.3
-flake8==3.7.9
-isort==4.3.21
-pytest==5.4.1
-pytest-cov==2.8.1
+flake8==3.8.3
+isort==5.4.2
+mypy==0.782
+pytest>=6.0.0,<7.0.0
+pytest-cov==2.10.1
semantic_version>=2.8.1,<3
-tox==3.14.6
-tqdm>=4.41.0,<5.0.0
-twine==1.13.0
+sphinx==3.2.1
+sphinx_rtd_theme==0.5.0
+tox==3.19.0
+tqdm>=4.48.0,<5.0.0
+twine==3.2.0
requests>=2.19.0,<3
+wheel==0.35.1
diff --git a/setup.cfg b/setup.cfg
index ee2afc8..d7514d0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,11 +1,17 @@
[bumpversion]
-current_version = 0.10.1
+current_version = 1.0.0
[bumpversion:file:setup.py]
[flake8]
max-line-length = 100
ignore = E203,W503
+per-file-ignores =
+ */__init__.py: F401
+
+[mypy]
+ignore_missing_imports = True
+follow_imports = silent
[tool:isort]
force_grid_wrap = 0
@@ -17,3 +23,4 @@ use_parentheses = True
[tool:pytest]
addopts = --cov=solcx --cov-branch --cov-report xml
+
diff --git a/setup.py b/setup.py
index 514ad30..1f63f80 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@
setup(
name="py-solc-x",
- version="0.10.1", # don't change this manually, use bumpversion instead
+ version="1.0.0", # don't change this manually, use bumpversion instead
description="Python wrapper around the solc binary with 0.5.x and 0.6.x support",
long_description_markdown_filename="README.md",
author="Ben Hauser (forked from py-solc by Piper Merriam)",
@@ -13,11 +13,8 @@
include_package_data=True,
py_modules=["solcx"],
setup_requires=["setuptools-markdown"],
- python_requires=">=3.4, <4",
- install_requires=[
- "requests>=2.19.0,<3",
- "semantic_version>=2.8.1,<3",
- ],
+ python_requires=">=3.6, <4",
+ install_requires=["requests>=2.19.0,<3", "semantic_version>=2.8.1,<3"],
license="MIT",
zip_safe=False,
keywords="ethereum solidity solc",
@@ -27,8 +24,6 @@
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.4",
- "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
diff --git a/solcx/__init__.py b/solcx/__init__.py
index f55f5f7..0e8284c 100644
--- a/solcx/__init__.py
+++ b/solcx/__init__.py
@@ -1,27 +1,13 @@
-from __future__ import absolute_import
-
-from .install import ( # noqa: F401
- get_available_solc_versions,
+from solcx.install import (
+ compile_solc,
+ get_compilable_solc_versions,
+ get_installable_solc_versions,
get_installed_solc_versions,
- get_solc_folder,
+ get_solcx_install_folder,
import_installed_solc,
install_solc,
install_solc_pragma,
set_solc_version,
set_solc_version_pragma,
)
-from .main import ( # noqa: F401
- compile_files,
- compile_source,
- compile_standard,
- get_solc_version,
- get_solc_version_string,
- link_code,
-)
-
-# check for installed version of solc
-import_installed_solc()
-
-# default to latest version
-if get_installed_solc_versions():
- set_solc_version(get_installed_solc_versions()[-1], silent=True)
+from solcx.main import compile_files, compile_source, compile_standard, get_solc_version, link_code
diff --git a/solcx/exceptions.py b/solcx/exceptions.py
index b15e402..0c8d53b 100644
--- a/solcx/exceptions.py
+++ b/solcx/exceptions.py
@@ -1,43 +1,37 @@
-import textwrap
-
-from .utils.string import force_text
-
-
-def force_text_maybe(value, encoding="iso-8859-1"):
- if value is not None:
- return force_text(value)
-
-
-DEFAULT_MESSAGE = "An error occurred during execution"
+from typing import Dict, List
class SolcError(Exception):
- message = DEFAULT_MESSAGE
+ message = "An error occurred during execution"
- def __init__(self, command, return_code, stdin_data, stdout_data, stderr_data, message=None):
+ def __init__(
+ self,
+ message: str = None,
+ command: List = None,
+ return_code: int = None,
+ stdin_data: str = None,
+ stdout_data: str = None,
+ stderr_data: str = None,
+ error_dict: Dict = None,
+ ) -> None:
if message is not None:
self.message = message
- self.command = command
+ self.command = command or []
self.return_code = return_code
- self.stdin_data = force_text_maybe(stdin_data, "utf8")
- self.stderr_data = force_text_maybe(stderr_data, "utf8")
- self.stdout_data = force_text_maybe(stdout_data, "utf8")
-
- def __str__(self):
- return textwrap.dedent(
- (
- """
- {s.message}
- > command: `{command}`
- > return code: `{s.return_code}`
- > stderr:
- {s.stdout_data}
- > stdout:
- {s.stderr_data}
- """
- ).format(
- s=self, command=" ".join(self.command),
- )
+ self.stdin_data = stdin_data
+ self.stderr_data = stderr_data
+ self.stdout_data = stdout_data
+ self.error_dict = error_dict
+
+ def __str__(self) -> str:
+ return (
+ f"{self.message}"
+ f"\n> command: `{' '.join(str(i) for i in self.command)}`"
+ f"\n> return code: `{self.return_code}`"
+ "\n> stdout:"
+ f"\n{self.stdout_data}"
+ "\n> stderr:"
+ f"\n{self.stderr_data}"
).strip()
@@ -45,9 +39,33 @@ class ContractsNotFound(SolcError):
message = "No contracts found during compilation"
+class SolcInstallationError(Exception):
+ pass
+
+
+class UnknownOption(AttributeError):
+ pass
+
+
+class UnknownValue(ValueError):
+ pass
+
+
+class UnexpectedVersionError(Exception):
+ pass
+
+
+class UnsupportedVersionError(ValueError):
+ pass
+
+
class SolcNotInstalled(Exception):
pass
class DownloadError(Exception):
pass
+
+
+class UnexpectedVersionWarning(Warning):
+ pass
diff --git a/solcx/install.py b/solcx/install.py
index 4a4ee41..539c766 100644
--- a/solcx/install.py
+++ b/solcx/install.py
@@ -4,7 +4,6 @@
import argparse
import logging
import os
-import platform
import re
import shutil
import stat
@@ -12,16 +11,26 @@
import sys
import tarfile
import tempfile
+import warnings
import zipfile
from base64 import b64encode
from io import BytesIO
from pathlib import Path
+from typing import Dict, List, Optional, Union
import requests
from semantic_version import SimpleSpec, Version
-from .exceptions import DownloadError, SolcNotInstalled
-from .utils.lock import get_process_lock
+from solcx import wrapper
+from solcx.exceptions import (
+ DownloadError,
+ SolcInstallationError,
+ SolcNotInstalled,
+ UnexpectedVersionError,
+ UnexpectedVersionWarning,
+ UnsupportedVersionError,
+)
+from solcx.utils.lock import get_process_lock
try:
from tqdm import tqdm
@@ -29,44 +38,62 @@
tqdm = None
-DOWNLOAD_BASE = "https://github.com/ethereum/solidity/releases/download/{}/{}"
-ALL_RELEASES = "https://api.github.com/repos/ethereum/solidity/releases?per_page=100"
+BINARY_DOWNLOAD_BASE = "https://solc-bin.ethereum.org/{}-amd64/{}"
+SOURCE_DOWNLOAD_BASE = "https://github.com/ethereum/solidity/releases/download/v{}/{}"
+GITHUB_RELEASES = "https://api.github.com/repos/ethereum/solidity/releases?per_page=100"
-MINIMAL_SOLC_VERSION = "v0.4.11"
-VERSION_REGEX = {
- "darwin": "solidity_[0-9].[0-9].[0-9]{1,}.tar.gz",
- "linux": "solc-static-linux",
- "win32": "solidity-windows.zip",
-}
+MINIMAL_SOLC_VERSION = Version("0.4.11")
LOGGER = logging.getLogger("solcx")
SOLCX_BINARY_PATH_VARIABLE = "SOLCX_BINARY_PATH"
-solc_version = None
+_default_solc_binary = None
-def _get_arch():
- if platform.machine().startswith("arm") or platform.machine() == "aarch64":
- return "arm"
- if platform.machine().startswith("x86"):
- return "x86"
- else:
- return platform.machine()
-
-
-def _get_platform():
+def _get_os_name() -> str:
if sys.platform.startswith("linux"):
return "linux"
- if sys.platform in ("darwin", "win32"):
- return sys.platform
- raise KeyError(
- "Unknown platform: '{}' - py-solc-x supports" " Linux, OSX and Windows".format(sys.platform)
- )
+ if sys.platform == "darwin":
+ return "macosx"
+ if sys.platform == "win32":
+ return "windows"
+ raise OSError(f"Unsupported OS: '{sys.platform}' - py-solc-x supports Linux, OSX and Windows")
+
+
+def _convert_and_validate_version(version: Union[str, Version]) -> Version:
+ # take a user-supplied version as a string or Version
+ # validate the value, and return a Version object
+ if not isinstance(version, Version):
+ version = Version(version.lstrip("v"))
+ if version not in SimpleSpec(">=0.4.11"):
+ raise UnsupportedVersionError("py-solc-x does not support solc versions <0.4.11")
+ return version
+
+
+def _unlink_solc(solc_path: Path) -> None:
+ solc_path.unlink()
+ if _get_os_name() == "windows":
+ shutil.rmtree(solc_path.parent)
+
+
+def get_solcx_install_folder(solcx_binary_path: Union[Path, str] = None) -> Path:
+ """
+ Return the directory where `py-solc-x` stores installed `solc` binaries.
+
+ By default, this is `~/.solcx`
+ Arguments
+ ---------
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
-def get_solc_folder(solcx_binary_path=None):
+ Returns
+ -------
+ Path
+ Subdirectory where `solc` binaries are are saved.
+ """
if os.getenv(SOLCX_BINARY_PATH_VARIABLE):
- return Path(os.getenv(SOLCX_BINARY_PATH_VARIABLE))
+ return Path(os.environ[SOLCX_BINARY_PATH_VARIABLE])
elif solcx_binary_path is not None:
return Path(solcx_binary_path)
else:
@@ -75,118 +102,262 @@ def get_solc_folder(solcx_binary_path=None):
return path
-def _import_version(path):
- version = subprocess.check_output([path, "--version"]).decode()
- return "v" + version[version.index("Version: ") + 9 : version.index("+")]
+def _get_which_solc() -> Path:
+ # get the path for the currently installed `solc` version, if any
+ if _get_os_name() == "windows":
+ response = subprocess.check_output(["where.exe", "solc"], encoding="utf8").strip()
+ else:
+ response = subprocess.check_output(["which", "solc"], encoding="utf8").strip()
+
+ return Path(response)
+
+def import_installed_solc(solcx_binary_path: Union[Path, str] = None) -> List[Version]:
+ """
+ Search for and copy installed `solc` versions into the local installation folder.
-def import_installed_solc(solcx_binary_path=None):
- platform = _get_platform()
- if platform == "win32":
- return
+ Arguments
+ ---------
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
- # copy active version of solc
- path_list = []
- which = (
- subprocess.run(["which", "solc"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
- .stdout.decode()
- .strip()
- )
- if which:
- path_list.append(which)
+ Returns
+ -------
+ List
+ Imported solc versions
+ """
+ try:
+ path_list = [_get_which_solc()]
+ except (FileNotFoundError, subprocess.CalledProcessError):
+ path_list = []
# on OSX, also copy all versions of solc from cellar
- if platform == "darwin":
- path_list = [str(i) for i in Path("/usr/local/Cellar").glob("solidity*/**/solc")]
+ if _get_os_name() == "macosx":
+ path_list.extend(Path("/usr/local/Cellar").glob("solidity*/**/solc"))
+ imported_versions = []
for path in path_list:
try:
- version = _import_version(path)
+ version = wrapper._get_solc_version(path)
assert version not in get_installed_solc_versions()
except Exception:
continue
- copy_path = str(
- get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath("solc-" + version)
- )
+
+ copy_path = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
+ if _get_os_name() == "windows":
+ copy_path.mkdir()
+ copy_path = copy_path.joinpath("solc.exe")
+
shutil.copy(path, copy_path)
try:
# confirm that solc still works after being copied
- assert version == _import_version(copy_path)
+ assert version == wrapper._get_solc_version(copy_path)
+ imported_versions.append(version)
except Exception:
- os.unlink(copy_path)
-
-
-def get_executable(version=None, solcx_binary_path=None):
+ _unlink_solc(copy_path)
+
+ return imported_versions
+
+
+def get_executable(
+ version: Union[str, Version] = None, solcx_binary_path: Union[Path, str] = None
+) -> Path:
+ """
+ Return the Path to an installed `solc` binary.
+
+ Arguments
+ ---------
+ version : str | Version, optional
+ Installed `solc` version to get the path of. If not given, returns the
+ path of the active version.
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
+
+ Returns
+ -------
+ Path
+ `solc` executable.
+ """
if not version:
- version = solc_version
- if not version:
- raise SolcNotInstalled(
- "Solc is not installed. Call solcx.get_available_solc_versions()"
- " to view for available versions and solcx.install_solc() to install."
- )
- solc_bin = get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath("solc-" + version)
- if sys.platform == "win32":
+ if not _default_solc_binary:
+ raise SolcNotInstalled(
+ "Solc is not installed. Call solcx.get_available_solc_versions()"
+ " to view for available versions and solcx.install_solc() to install."
+ )
+ return _default_solc_binary
+
+ version = _convert_and_validate_version(version)
+ solc_bin = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
+ if _get_os_name() == "windows":
solc_bin = solc_bin.joinpath("solc.exe")
if not solc_bin.exists():
raise SolcNotInstalled(
- "solc {} has not been installed. ".format(version)
- + "Use solcx.install_solc('{}') to install.".format(version)
+ f"solc {version} has not been installed."
+ f" Use solcx.install_solc('{version}') to install."
)
- return str(solc_bin)
+ return solc_bin
+
+
+def set_solc_version(
+ version: Union[str, Version], silent: bool = False, solcx_binary_path: Union[Path, str] = None
+) -> None:
+ """
+ Set the currently active `solc` binary.
+
+ Arguments
+ ---------
+ version : str | Version, optional
+ Installed `solc` version to get the path of. If not given, returns the
+ path of the active version.
+ silent : bool, optional
+ If True, do not generate any logger output.
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
+ """
+ version = _convert_and_validate_version(version)
+ global _default_solc_binary
+ _default_solc_binary = get_executable(version, solcx_binary_path)
+ if not silent:
+ LOGGER.info(f"Using solc version {version}")
-def set_solc_version(version, silent=False, solcx_binary_path=None):
- version = _check_version(version)
- get_executable(version, solcx_binary_path)
- global solc_version
- solc_version = version
- if not silent:
- LOGGER.info("Using solc version {}".format(solc_version))
+def _select_pragma_version(pragma_string: str, version_list: List[Version]) -> Optional[Version]:
+ comparator_set_range = pragma_string.replace(" ", "").split("||")
+ comparator_regex = re.compile(r"(([<>]?=?|\^)\d+\.\d+\.\d+)+")
+ version = None
+ for comparator_set in comparator_set_range:
+ spec = SimpleSpec(*(i[0] for i in comparator_regex.findall(comparator_set)))
+ selected = spec.select(version_list)
+ if selected and (not version or version < selected):
+ version = selected
-def set_solc_version_pragma(pragma_string, silent=False, check_new=False):
- version = _select_pragma_version(
- pragma_string, [Version(i[1:]) for i in get_installed_solc_versions()]
- )
- if not version:
+ return version
+
+
+def set_solc_version_pragma(
+ pragma_string: str, silent: bool = False, check_new: bool = False
+) -> Version:
+ """
+ Set the currently active `solc` binary based on a pragma statement.
+
+ The newest installed version that matches the pragma is chosen. Raises
+ `SolcNotInstalled` if no installed versions match.
+
+ Arguments
+ ---------
+ pragma_string : str
+ Pragma statement, e.g. "pragma solidity ^0.4.22;"
+ silent : bool, optional
+ If True, do not generate any logger output.
+ check_new : bool, optional
+ If True, also check if there is a newer compatible version that has not
+ been installed.
+
+ Returns
+ -------
+ Version
+ The new active `solc` version.
+ """
+ version = _select_pragma_version(pragma_string, get_installed_solc_versions())
+ if version is None:
raise SolcNotInstalled(
- "No compatible solc version installed. "
- + "Use solcx.install_solc_version_pragma('{}') to install.".format(version)
+ f"No compatible solc version installed."
+ f" Use solcx.install_solc_version_pragma('{version}') to install."
)
- version = _check_version(version)
- global solc_version
- solc_version = version
- if not silent:
- LOGGER.info("Using solc version {}".format(solc_version))
+ set_solc_version(version, silent)
if check_new:
latest = install_solc_pragma(pragma_string, False)
- if Version(latest) > Version(version[1:]):
- LOGGER.info("Newer compatible solc version exists: {}".format(latest))
+ if latest > version:
+ LOGGER.info(f"Newer compatible solc version exists: {latest}")
+
+ return version
-def install_solc_pragma(pragma_string, install=True, show_progress=False, solcx_binary_path=None):
- version = _select_pragma_version(
- pragma_string, [Version(i[1:]) for i in get_available_solc_versions()]
- )
+def install_solc_pragma(
+ pragma_string: str,
+ install: bool = True,
+ show_progress: bool = False,
+ solcx_binary_path: Union[Path, str] = None,
+) -> Version:
+ """
+ Find, and optionally install, the latest compatible `solc` version based on
+ a pragma statement.
+
+ Arguments
+ ---------
+ pragma_string : str
+ Pragma statement, e.g. "pragma solidity ^0.4.22;"
+ install : bool, optional
+ If True, installs the version of `solc`.
+ show_progress : bool, optional
+ If True, display a progress bar while downloading. Requires installing
+ the `tqdm` package.
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
+
+ Returns
+ -------
+ Version
+ Installed `solc` version.
+ """
+ version = _select_pragma_version(pragma_string, get_installable_solc_versions())
if not version:
- raise ValueError("Compatible solc version does not exist")
+ raise UnsupportedVersionError("Compatible solc version does not exist")
if install:
install_solc(version, show_progress=show_progress, solcx_binary_path=solcx_binary_path)
+
return version
-def get_available_solc_versions(headers=None):
- versions = []
- pattern = VERSION_REGEX[_get_platform()]
+def get_installable_solc_versions() -> List[Version]:
+ """
+ Return a list of all `solc` versions that can be installed by py-solc-x.
+
+ Returns
+ -------
+ List
+ List of Versions objects of installable `solc` versions.
+ """
+ data = requests.get(BINARY_DOWNLOAD_BASE.format(_get_os_name(), "list.json"))
+ if data.status_code != 200:
+ raise ConnectionError(
+ f"Status {data.status_code} when getting solc versions from solc-bin.ethereum.org"
+ )
+ version_list = sorted((Version(i) for i in data.json()["releases"]), reverse=True)
+ version_list = [i for i in version_list if i >= MINIMAL_SOLC_VERSION]
+ return version_list
+
+
+def get_compilable_solc_versions(headers: Optional[Dict] = None) -> List[Version]:
+ """
+ Return a list of all `solc` versions that can be compiled from source by py-solc-x.
+
+ Arguments
+ ---------
+ headers : Dict, optional
+ Headers to include in the request to Github.
+
+ Returns
+ -------
+ List
+ List of Versions objects of installable `solc` versions.
+ """
+ if _get_os_name() == "windows":
+ raise OSError("Compiling from source is not supported on Windows systems")
+
+ version_list = []
+ pattern = "solidity_[0-9].[0-9].[0-9]{1,}.tar.gz"
- if not headers and os.getenv("GITHUB_TOKEN"):
- auth = b64encode(os.getenv("GITHUB_TOKEN").encode()).decode()
- headers = {"Authorization": "Basic {}".format(auth)}
+ if headers is None and os.getenv("GITHUB_TOKEN") is not None:
+ auth = b64encode(os.environ["GITHUB_TOKEN"].encode()).decode()
+ headers = {"Authorization": f"Basic {auth}"}
- data = requests.get(ALL_RELEASES, headers=headers)
+ data = requests.get(GITHUB_RELEASES, headers=headers)
if data.status_code != 200:
- msg = "Status {} when getting solc versions from Github: '{}'".format(
- data.status_code, data.json()["message"]
+ msg = (
+ f"Status {data.status_code} when getting solc versions from Github:"
+ f" '{data.json()['message']}'"
)
if data.status_code == 403:
msg += (
@@ -197,105 +368,201 @@ def get_available_solc_versions(headers=None):
raise ConnectionError(msg)
for release in data.json():
+ version = Version.coerce(release["tag_name"].lstrip("v"))
asset = next((i for i in release["assets"] if re.match(pattern, i["name"])), False)
if asset:
- versions.append(release["tag_name"])
- if release["tag_name"] == MINIMAL_SOLC_VERSION:
+ version_list.append(version)
+ if version == MINIMAL_SOLC_VERSION:
break
- return versions
+ return sorted(version_list, reverse=True)
+
+
+def get_installed_solc_versions(solcx_binary_path: Union[Path, str] = None) -> List[Version]:
+ """
+ Return a list of currently installed `solc` versions.
+
+ Arguments
+ ---------
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
+
+ Returns
+ -------
+ List
+ List of Version objects of installed `solc` versions.
+ """
+ install_path = get_solcx_install_folder(solcx_binary_path)
+ return sorted([Version(i.name[6:]) for i in install_path.glob("solc-v*")], reverse=True)
+
+
+def install_solc(
+ version: Union[str, Version] = "latest",
+ show_progress: bool = False,
+ solcx_binary_path: Union[Path, str] = None,
+) -> Version:
+ """
+ Download and install a precompiled version of `solc`.
+
+ Arguments
+ ---------
+ version : str | Version, optional
+ Version of `solc` to install. Default is the newest available version.
+ show_progress : bool, optional
+ If True, display a progress bar while downloading. Requires installing
+ the `tqdm` package.
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
+
+ Returns
+ -------
+ Version
+ installed solc version
+ """
+
+ if version == "latest":
+ version = get_installable_solc_versions()[0]
+ else:
+ version = _convert_and_validate_version(version)
+ os_name = _get_os_name()
+ process_lock = get_process_lock(str(version))
-def _select_pragma_version(pragma_string, version_list):
- comparator_set_range = pragma_string.replace(" ", "").split("||")
- comparator_regex = re.compile(r"(([<>]?=?|\^)\d+\.\d+\.\d+)+")
- version = None
+ with process_lock:
+ if _check_for_installed_version(version, solcx_binary_path):
+ path = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
+ LOGGER.info(f"solc {version} already installed at: {path}")
+ return version
- for comparator_set in comparator_set_range:
- spec = SimpleSpec(*(i[0] for i in comparator_regex.findall(comparator_set)))
- selected = spec.select(version_list)
- if selected and (not version or version < selected):
- version = selected
- if version:
- return str(version)
+ data = requests.get(BINARY_DOWNLOAD_BASE.format(_get_os_name(), "list.json"))
+ if data.status_code != 200:
+ raise ConnectionError(
+ f"Status {data.status_code} when getting solc versions from solc-bin.ethereum.org"
+ )
+ try:
+ filename = data.json()["releases"][str(version)]
+ except KeyError:
+ raise SolcInstallationError(f"Solc binary for v{version} is not available for this OS")
+
+ if os_name == "linux":
+ _install_solc_unix(version, filename, show_progress, solcx_binary_path)
+ elif os_name == "macosx":
+ _install_solc_unix(version, filename, show_progress, solcx_binary_path)
+ elif os_name == "windows":
+ _install_solc_windows(version, filename, show_progress, solcx_binary_path)
+ try:
+ _validate_installation(version, solcx_binary_path)
+ except SolcInstallationError as exc:
+ exc.args = (
+ f"{exc.args[0]} If this issue persists, you can try to compile from "
+ f"source code using `solcx.compile_solc('{version}')`.",
+ )
+ raise exc
-def get_installed_solc_versions(solcx_binary_path=None):
- return sorted(
- i.name[5:] for i in get_solc_folder(solcx_binary_path=solcx_binary_path).glob("solc-v*")
- )
+ return version
-def install_solc(version, allow_osx=False, show_progress=False, solcx_binary_path=None):
- arch = _get_arch()
- platform = _get_platform()
- version = _check_version(version)
+def compile_solc(
+ version: Version, show_progress: bool = False, solcx_binary_path: Union[Path, str] = None
+) -> Version:
+ """
+ Install a version of `solc` by downloading and compiling source code.
+
+ Arguments
+ ---------
+ version : str | Version, optional
+ Version of `solc` to install. Default is the newest available version.
+ show_progress : bool, optional
+ If True, display a progress bar while downloading. Requires installing
+ the `tqdm` package.
+ solcx_binary_path : Path | str, optional
+ User-defined path, used to override the default installation directory.
+
+ Returns
+ -------
+ Version
+ installed solc version
+ """
+ if _get_os_name() == "windows":
+ raise OSError("Compiling from source is not supported on Windows systems")
+
+ if version == "latest":
+ version = get_compilable_solc_versions()[0]
+ else:
+ version = _convert_and_validate_version(version)
- lock = get_process_lock(version)
- if not lock.acquire(False):
- lock.wait()
- if not _check_for_installed_version(version):
- return
- return install_solc(version, allow_osx)
+ process_lock = get_process_lock(str(version))
- try:
- if arch == "arm":
- _install_solc_arm(version, show_progress, solcx_binary_path)
- elif platform == "linux":
- _install_solc_linux(version, show_progress, solcx_binary_path)
- elif platform == "darwin":
- _install_solc_osx(version, allow_osx, show_progress, solcx_binary_path)
- elif platform == "win32":
- _install_solc_windows(version, show_progress, solcx_binary_path)
- binary_path = get_executable(version, solcx_binary_path)
- _check_subprocess_call(
- [binary_path, "--version"],
- message="Checking installed executable version @ {}".format(binary_path),
- )
- if not solc_version:
- set_solc_version(version)
- LOGGER.info("solc {} successfully installed at: {}".format(version, binary_path))
- finally:
- lock.release()
+ with process_lock:
+ if _check_for_installed_version(version, solcx_binary_path):
+ path = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
+ LOGGER.info(f"solc {version} already installed at: {path}")
+ return version
+ temp_path = _get_temp_folder()
+ download = SOURCE_DOWNLOAD_BASE.format(version, f"solidity_{version}.tar.gz")
+ install_path = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
-def _check_version(version):
- version = Version(version.lstrip("v"))
- if version not in SimpleSpec(">=0.4.11"):
- raise ValueError("py-solc-x does not support solc versions <0.4.11")
- return "v" + str(version)
+ content = _download_solc(download, show_progress)
+ with tarfile.open(fileobj=BytesIO(content)) as tar:
+ tar.extractall(temp_path)
+ temp_path = temp_path.joinpath(f"solidity_{version}")
+ try:
+ LOGGER.info("Running dependency installation script `install_deps.sh`...")
+ subprocess.check_call(
+ ["sh", temp_path.joinpath("scripts/install_deps.sh")], stderr=subprocess.DEVNULL
+ )
+ except subprocess.CalledProcessError as exc:
+ LOGGER.warning(exc, exc_info=True)
-def _check_subprocess_call(command, message=None, verbose=False, **proc_kwargs):
- if message:
- LOGGER.debug(message)
- LOGGER.info("Executing: {0}".format(" ".join(command)))
+ original_path = os.getcwd()
+ temp_path.joinpath("build").mkdir(exist_ok=True)
+ os.chdir(str(temp_path.joinpath("build").resolve()))
+ try:
+ for cmd in (["cmake", ".."], ["make"]):
+ LOGGER.info(f"Running `{cmd[0]}`...")
+ subprocess.check_call(cmd, stderr=subprocess.DEVNULL)
+ temp_path.joinpath("build/solc/solc").rename(install_path)
+ except subprocess.CalledProcessError as exc:
+ err_msg = (
+ f"{cmd[0]} returned non-zero exit status {exc.returncode}"
+ " while attempting to build solc from the source.\n"
+ "This is likely due to a missing or incorrect version of a build dependency."
+ )
+ if _get_os_name() == "macosx":
+ err_msg = (
+ f"{err_msg}\n\nFor suggested installation options: "
+ "https://github.com/iamdefinitelyahuman/py-solc-x/wiki/Installing-Solidity-on-OSX" # noqa: E501
+ )
+ raise SolcInstallationError(err_msg)
- return subprocess.check_call(
- command, stderr=subprocess.STDOUT if verbose else subprocess.DEVNULL, **proc_kwargs
- )
+ finally:
+ os.chdir(original_path)
+ install_path.chmod(install_path.stat().st_mode | stat.S_IEXEC)
+ _validate_installation(version, solcx_binary_path)
-def _chmod_plus_x(executable_path):
- executable_path.chmod(executable_path.stat().st_mode | stat.S_IEXEC)
+ return version
-def _check_for_installed_version(version, solcx_binary_path=None):
- path = get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath("solc-" + version)
- if path.exists():
- LOGGER.info("solc {} already installed at: {}".format(version, path))
- return False
- return path
+def _check_for_installed_version(
+ version: Version, solcx_binary_path: Union[Path, str] = None
+) -> bool:
+ path = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
+ return path.exists()
-def _get_temp_folder():
- path = Path(tempfile.gettempdir()).joinpath("solcx-tmp-{}".format(os.getpid()))
+def _get_temp_folder() -> Path:
+ path = Path(tempfile.gettempdir()).joinpath(f"solcx-tmp-{os.getpid()}")
if path.exists():
shutil.rmtree(str(path))
path.mkdir()
return path
-def _download_solc(url, show_progress):
+def _download_solc(url: str, show_progress: bool) -> bytes:
+ LOGGER.info(f"Downloading from {url}")
response = requests.get(url, stream=show_progress)
if response.status_code == 404:
raise DownloadError(
@@ -304,9 +571,7 @@ def _download_solc(url, show_progress):
)
if response.status_code != 200:
raise DownloadError(
- "Received status code {} when attempting to download from {}".format(
- response.status_code, url
- )
+ f"Received status code {response.status_code} when attempting to download from {url}"
)
if not show_progress:
return response.content
@@ -323,86 +588,61 @@ def _download_solc(url, show_progress):
return content
-def _install_solc_linux(version, show_progress, solcx_binary_path=None):
- download = DOWNLOAD_BASE.format(version, "solc-static-linux")
- binary_path = _check_for_installed_version(version, solcx_binary_path=solcx_binary_path)
- if binary_path:
- LOGGER.info("Downloading solc {} from {}".format(version, download))
- content = _download_solc(download, show_progress)
- with open(binary_path, "wb") as fp:
- fp.write(content)
- _chmod_plus_x(binary_path)
-
+def _install_solc_unix(
+ version: Version, filename: str, show_progress: bool, solcx_binary_path: Union[Path, str, None]
+) -> None:
+ download = BINARY_DOWNLOAD_BASE.format(_get_os_name(), filename)
+ install_path = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
-def _install_solc_windows(version, show_progress, solcx_binary_path=None):
- download = DOWNLOAD_BASE.format(version, "solidity-windows.zip")
- install_folder = _check_for_installed_version(version)
- if install_folder:
- temp_path = _get_temp_folder()
- content = _download_solc(download, show_progress)
- with zipfile.ZipFile(BytesIO(content)) as zf:
- zf.extractall(str(temp_path))
- install_folder = get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath(
- "solc-" + version
- )
- temp_path.rename(install_folder)
-
-
-def _install_solc_arm(version, show_progress, solcx_binary_path):
- _compile_solc(version, show_progress, solcx_binary_path)
+ content = _download_solc(download, show_progress)
+ with open(install_path, "wb") as fp:
+ fp.write(content)
+ install_path.chmod(install_path.stat().st_mode | stat.S_IEXEC)
-def _install_solc_osx(version, allow_osx, show_progress, solcx_binary_path):
- if version.startswith("v0.4") and not allow_osx:
- raise ValueError(
- "Installing solc {0} on OSX often fails. For suggested installation options:\n"
- "https://github.com/iamdefinitelyahuman/py-solc-x/wiki/Installing-Solidity-on-OSX\n\n"
- "To ignore this warning and attempt to install: "
- "solcx.install_solc('{0}', allow_osx=True)".format(version)
- )
- else:
- _compile_solc(version, show_progress, solcx_binary_path)
+def _install_solc_windows(
+ version: Version, filename: str, show_progress: bool, solcx_binary_path: Union[Path, str, None]
+) -> None:
+ download = BINARY_DOWNLOAD_BASE.format(_get_os_name(), filename)
+ install_path = get_solcx_install_folder(solcx_binary_path).joinpath(f"solc-v{version}")
-def _compile_solc(version, show_progress, solcx_binary_path):
temp_path = _get_temp_folder()
- download = DOWNLOAD_BASE.format(version, "solidity_{}.tar.gz".format(version[1:]))
- binary_path = _check_for_installed_version(version)
- if not binary_path:
- return
-
content = _download_solc(download, show_progress)
- with tarfile.open(fileobj=BytesIO(content)) as tar:
- tar.extractall(temp_path)
- temp_path = temp_path.joinpath("solidity_{}".format(version[1:]))
+ with zipfile.ZipFile(BytesIO(content)) as zf:
+ zf.extractall(str(temp_path))
+
+ temp_path.rename(install_path)
- try:
- _check_subprocess_call(
- ["sh", str(temp_path.joinpath("scripts/install_deps.sh"))],
- message="Running dependency installation script `install_deps.sh`",
- )
- except subprocess.CalledProcessError as e:
- LOGGER.warning(e, exc_info=True)
- original_path = os.getcwd()
- temp_path.joinpath("build").mkdir(exist_ok=True)
- os.chdir(str(temp_path.joinpath("build").resolve()))
+def _validate_installation(version: Version, solcx_binary_path: Union[Path, str, None]) -> None:
+ binary_path = get_executable(version, solcx_binary_path)
try:
- for cmd in (["cmake", ".."], ["make"]):
- _check_subprocess_call(cmd, message="Running {}".format(cmd[0]))
- temp_path.joinpath("build/solc/solc").rename(binary_path)
- except subprocess.CalledProcessError as e:
- raise OSError(
- "{} returned non-zero exit status {} while attempting to build solc from the source.\n"
- "This is likely due to a missing or incorrect version of a build dependency.\n\n"
- "For suggested installation options: "
- "https://github.com/iamdefinitelyahuman/py-solc-x/wiki/Installing-Solidity-on-OSX"
- "".format(cmd[0], e.returncode)
+ installed_version = wrapper._get_solc_version(binary_path)
+ except Exception:
+ _unlink_solc(binary_path)
+ raise SolcInstallationError(
+ "Downloaded binary would not execute, or returned unexpected output."
)
- finally:
- os.chdir(original_path)
+ if installed_version.truncate() != version.truncate():
+ _unlink_solc(binary_path)
+ raise UnexpectedVersionError(
+ f"Attempted to install solc v{version}, but got solc v{installed_version}"
+ )
+ if installed_version != version:
+ warnings.warn(f"Installed solc version is v{installed_version}", UnexpectedVersionWarning)
+ if not _default_solc_binary:
+ set_solc_version(version)
+ LOGGER.info(f"solc {version} successfully installed at: {binary_path}")
+
- _chmod_plus_x(binary_path)
+try:
+ # try to set the result of `which`/`where` as the default
+ _default_solc_binary = _get_which_solc()
+except Exception:
+ # if not available, use the most recent solcx installed version
+ if get_installed_solc_versions():
+ set_solc_version(get_installed_solc_versions()[0], silent=True)
if __name__ == "__main__":
diff --git a/solcx/main.py b/solcx/main.py
index ad21b40..c579672 100644
--- a/solcx/main.py
+++ b/solcx/main.py
@@ -1,57 +1,244 @@
-from __future__ import absolute_import
-
-import functools
import json
-import re
-
-import semantic_version
+from pathlib import Path
+from typing import Any, Dict, List, Optional, Union
-from .exceptions import ContractsNotFound, SolcError
-from .install import get_executable
-from .utils.filesystem import is_executable_available
-from .wrapper import solc_wrapper
+from semantic_version import Version
-VERSION_DEV_DATE_MANGLER_RE = re.compile(r"(\d{4})\.0?(\d{1,2})\.0?(\d{1,2})")
-strip_zeroes_from_month_and_day = functools.partial(
- VERSION_DEV_DATE_MANGLER_RE.sub, r"\g<1>.\g<2>.\g<3>"
-)
+from solcx import wrapper
+from solcx.exceptions import ContractsNotFound, SolcError
+from solcx.install import get_executable
+# from solcx.wrapper import _get_solc_version, solc_wrapper
-def is_solc_available():
- solc_binary = get_executable()
- return is_executable_available(solc_binary)
+def get_solc_version() -> Version:
+ """
+ Get the version of the active `solc` binary.
-def get_solc_version_string(**kwargs):
- kwargs["version"] = True
- stdoutdata, stderrdata, command, proc = solc_wrapper(**kwargs)
- _, _, version_string = stdoutdata.partition("\n")
- if not version_string or not version_string.startswith("Version: "):
- raise SolcError(
- command=command,
- return_code=proc.returncode,
- stdin_data=None,
- stdout_data=stdoutdata,
- stderr_data=stderrdata,
- message="Unable to extract version string from command output",
- )
- return version_string.rstrip()
+ Returns
+ -------
+ Version
+ solc version
+ """
+ solc_binary = get_executable()
+ return wrapper._get_solc_version(solc_binary)
+
+
+def compile_source(
+ source: str,
+ output_values: List = None,
+ import_remappings: Union[Dict, List, str] = None,
+ base_path: Union[Path, str] = None,
+ allow_paths: Union[List, Path, str] = None,
+ output_dir: Union[Path, str] = None,
+ overwrite: bool = False,
+ evm_version: str = None,
+ revert_strings: Union[List, str] = None,
+ metadata_hash: str = None,
+ metadata_literal: bool = False,
+ optimize: bool = False,
+ optimize_runs: int = None,
+ optimize_yul: bool = False,
+ no_optimize_yul: bool = False,
+ yul_optimizations: int = None,
+ solc_binary: Union[str, Path] = None,
+ solc_version: Version = None,
+ allow_empty: bool = False,
+) -> Dict:
+ """
+ Compile a Solidity contract.
+
+ Compilation is handled via the `--combined-json` flag. Depending on the solc
+ version used, some keyword arguments may not be available.
+
+ Arguments
+ ---------
+ source: str
+ Solidity contract to be compiled.
+ output_values : List, optional
+ Compiler outputs to return. Valid options depend on the version of `solc`.
+ If not given, all possible outputs for the active version are returned.
+ 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"}`.
+ 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`)
+ evm_version: str, optional
+ Select the desired EVM version. Valid options depend on the `solc` version.
+ revert_strings : List | str, optional
+ Strip revert (and require) reason strings or add additional debugging
+ information.
+ metadata_hash : str, optional
+ Choose hash method for the bytecode metadata or disable it.
+ metadata_literal : bool, optional
+ Store referenced sources as literal data in the metadata output.
+ optimize : bool, optional
+ Enable bytecode optimizer.
+ optimize_runs : int, optional
+ Set for how many contract runs to optimize. Lower values will optimize
+ more for initial deployment cost, higher values will optimize more for
+ high-frequency usage.
+ optimize_yul: bool, optional
+ Enable the yul optimizer.
+ no_optimize_yul : bool, optional
+ Disable the yul optimizer.
+ yul_optimizations : int, optional
+ Force yul optimizer to use the specified sequence of optimization steps
+ instead of the built-in one.
+ 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 output. The source file name is given as ``.
+ """
+ return _compile_combined_json(
+ solc_binary=solc_binary,
+ solc_version=solc_version,
+ stdin=source,
+ output_values=output_values,
+ 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,
+ allow_empty=allow_empty,
+ )
-def get_solc_version(**kwargs):
- # semantic_version as of 2017-5-5 expects only one + to be used in string
- return semantic_version.Version(
- strip_zeroes_from_month_and_day(
- get_solc_version_string(**kwargs)[len("Version: ") :].replace("++", "pp")
- )
+def compile_files(
+ source_files: Union[List, Path, str],
+ output_values: List = None,
+ import_remappings: Union[Dict, List, str] = None,
+ base_path: Union[Path, str] = None,
+ allow_paths: Union[List, Path, str] = None,
+ output_dir: Union[Path, str] = None,
+ overwrite: bool = False,
+ evm_version: str = None,
+ revert_strings: Union[List, str] = None,
+ metadata_hash: str = None,
+ metadata_literal: bool = False,
+ optimize: bool = False,
+ optimize_runs: int = None,
+ optimize_yul: bool = False,
+ no_optimize_yul: bool = False,
+ yul_optimizations: int = None,
+ solc_binary: Union[str, Path] = None,
+ solc_version: Version = None,
+ allow_empty: bool = False,
+) -> Dict:
+ """
+ Compile one or more Solidity source files.
+
+ Compilation is handled via the `--combined-json` flag. Depending on the solc
+ version used, some keyword arguments may not be available.
+
+ Arguments
+ ---------
+ source_files: List | Path | str
+ Path, or list of paths, of Solidity source files to be compiled.
+ output_values : List, optional
+ Compiler outputs to return. Valid options depend on the version of `solc`.
+ If not given, all possible outputs for the active version are returned.
+ 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"}`.
+ 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`)
+ evm_version: str, optional
+ Select the desired EVM version. Valid options depend on the `solc` version.
+ revert_strings : List | str, optional
+ Strip revert (and require) reason strings or add additional debugging
+ information.
+ metadata_hash : str, optional
+ Choose hash method for the bytecode metadata or disable it.
+ metadata_literal : bool, optional
+ Store referenced sources as literal data in the metadata output.
+ optimize : bool, optional
+ Enable bytecode optimizer.
+ optimize_runs : int, optional
+ Set for how many contract runs to optimize. Lower values will optimize
+ more for initial deployment cost, higher values will optimize more for
+ high-frequency usage.
+ optimize_yul: bool, optional
+ Enable the yul optimizer.
+ no_optimize_yul : bool, optional
+ Disable the yul optimizer.
+ yul_optimizations : int, optional
+ Force yul optimizer to use the specified sequence of optimization steps
+ instead of the built-in one.
+ 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 output
+ """
+ return _compile_combined_json(
+ solc_binary=solc_binary,
+ solc_version=solc_version,
+ source_files=source_files,
+ output_values=output_values,
+ 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,
+ allow_empty=allow_empty,
)
-def solc_supports_standard_json_interface(**kwargs):
- return get_solc_version() in semantic_version.SimpleSpec(">=0.4.11")
+def _get_combined_json_outputs() -> str:
+ help_str = wrapper.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):
+def _parse_compiler_output(stdoutdata: str) -> Dict:
output = json.loads(stdoutdata)
contracts = output.get("contracts", {})
@@ -67,55 +254,51 @@ 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)
-
- stdoutdata, stderrdata, command, proc = solc_wrapper(**compiler_kwargs)
-
- contracts = _parse_compiler_output(stdoutdata)
-
- if not contracts and not allow_empty:
- raise ContractsNotFound(
- command=command,
- return_code=proc.returncode,
- stdin_data=source,
- stdout_data=stdoutdata,
- stderr_data=stderrdata,
- )
- 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"
- )
+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"
+ )
- combined_json = ",".join(output_values)
- compiler_kwargs = dict(source_files=source_files, combined_json=combined_json, **kwargs)
+ stdoutdata, stderrdata, command, proc = wrapper.solc_wrapper(
+ solc_binary=solc_binary,
+ combined_json=combined_json,
+ output_dir=output_dir,
+ overwrite=overwrite,
+ **kwargs,
+ )
- stdoutdata, stderrdata, command, proc = solc_wrapper(**compiler_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)
@@ -123,25 +306,72 @@ def compile_files(source_files, allow_empty=False, output_values=ALL_OUTPUT_VALU
raise ContractsNotFound(
command=command,
return_code=proc.returncode,
- stdin_data=None,
stdout_data=stdoutdata,
stderr_data=stderrdata,
)
return contracts
-def compile_standard(input_data, allow_empty=False, **kwargs):
+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(
- command=None,
- return_code=None,
+ "Input JSON does not contain any sources",
stdin_data=json.dumps(input_data, sort_keys=True, indent=2),
- stdout_data=None,
- stderr_data=None,
)
- stdoutdata, stderrdata, command, proc = solc_wrapper(
- stdin=json.dumps(input_data), standard_json=True, **kwargs
+ 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)
@@ -156,22 +386,51 @@ def compile_standard(input_data, allow_empty=False, **kwargs):
)
)
raise SolcError(
- command,
- proc.returncode,
- json.dumps(input_data),
- stdoutdata,
- stderrdata,
- message=error_message,
+ 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 link_code(unlinked_bytecode, libraries):
- libraries_arg = ",".join(
- (":".join((lib_name, lib_address)) for lib_name, lib_address in libraries.items())
- )
- stdoutdata, stderrdata, _, _ = solc_wrapper(
- stdin=unlinked_bytecode, link=True, libraries=libraries_arg,
- )
+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()
diff --git a/solcx/utils/filesystem.py b/solcx/utils/filesystem.py
deleted file mode 100644
index 0f3bcf7..0000000
--- a/solcx/utils/filesystem.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import os
-
-
-def is_executable_available(program):
- def is_exe(fpath):
- return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
-
- fpath = os.path.dirname(program)
- if fpath:
- if is_exe(program):
- return True
- else:
- for path in os.environ["PATH"].split(os.pathsep):
- path = path.strip('"')
- exe_file = os.path.join(path, program)
- if is_exe(exe_file):
- return True
-
- return False
diff --git a/solcx/utils/lock.py b/solcx/utils/lock.py
index c80b8f9..6e77dd2 100644
--- a/solcx/utils/lock.py
+++ b/solcx/utils/lock.py
@@ -3,7 +3,7 @@
import tempfile
import threading
from pathlib import Path
-from typing import Dict
+from typing import Any, Dict, Union
if sys.platform == "win32":
import msvcrt
@@ -15,11 +15,11 @@
NON_BLOCKING = fcntl.LOCK_EX | fcntl.LOCK_NB
BLOCKING = fcntl.LOCK_EX
-_locks: Dict = {}
+_locks: Dict[str, Union["UnixLock", "WindowsLock"]] = {}
_base_lock = threading.Lock()
-def get_process_lock(lock_id):
+def get_process_lock(lock_id: str) -> Union["UnixLock", "WindowsLock"]:
with _base_lock:
if lock_id not in _locks:
if sys.platform == "win32":
@@ -30,18 +30,24 @@ def get_process_lock(lock_id):
class _ProcessLock:
- def __init__(self, lock_id):
+ """
+ Ensure an action is both thread-safe and process-safe.
+ """
+
+ def __init__(self, lock_id: str) -> None:
self._lock = threading.Lock()
self._lock_path = Path(tempfile.gettempdir()).joinpath(f".solcx-lock-{lock_id}")
self._lock_file = self._lock_path.open("w")
- def wait(self):
+
+class UnixLock(_ProcessLock):
+ def __enter__(self) -> None:
self.acquire(True)
- self.release()
+ def __exit__(self, *args: Any) -> None:
+ self.release()
-class UnixLock(_ProcessLock):
- def acquire(self, blocking):
+ def acquire(self, blocking: bool) -> bool:
if not self._lock.acquire(blocking):
return False
try:
@@ -51,19 +57,27 @@ def acquire(self, blocking):
return False
return True
- def release(self):
+ def release(self) -> None:
fcntl.flock(self._lock_file, fcntl.LOCK_UN)
self._lock.release()
class WindowsLock(_ProcessLock):
- def acquire(self, blocking):
+ def __enter__(self) -> None:
+ self.acquire(True)
+
+ def __exit__(self, *args: Any) -> None:
+ self.release()
+
+ def acquire(self, blocking: bool) -> bool:
if not self._lock.acquire(blocking):
return False
while True:
try:
- fd = os.open(self._lock_path, OPEN_MODE)
- msvcrt.locking(fd, msvcrt.LK_LOCK if blocking else msvcrt.LK_NBLCK, 1)
+ fd = os.open(self._lock_path, OPEN_MODE) # type: ignore
+ msvcrt.locking( # type: ignore
+ fd, msvcrt.LK_LOCK if blocking else msvcrt.LK_NBLCK, 1 # type: ignore
+ )
self._fd = fd
return True
except OSError:
@@ -71,6 +85,6 @@ def acquire(self, blocking):
self._lock.release()
return False
- def release(self):
- msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1)
+ def release(self) -> None:
+ msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) # type: ignore
self._lock.release()
diff --git a/solcx/utils/string.py b/solcx/utils/string.py
deleted file mode 100644
index c584835..0000000
--- a/solcx/utils/string.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import codecs
-import functools
-
-from .types import is_bytes, is_dict, is_list_like, is_string, is_text
-
-
-def force_bytes(value, encoding="iso-8859-1"):
- if is_bytes(value):
- return bytes(value)
- elif is_text(value):
- return codecs.encode(value, encoding)
- else:
- raise TypeError("Unsupported type: {0}".format(type(value)))
-
-
-def force_text(value, encoding="iso-8859-1"):
- if is_text(value):
- return value
- elif is_bytes(value):
- return codecs.decode(value, encoding)
- else:
- raise TypeError("Unsupported type: {0}".format(type(value)))
-
-
-def force_obj_to_bytes(obj):
- if is_string(obj):
- return force_bytes(obj)
- elif is_dict(obj):
- return {k: force_obj_to_bytes(v) for k, v in obj.items()}
- elif is_list_like(obj):
- return type(obj)(force_obj_to_bytes(v) for v in obj)
- else:
- return obj
-
-
-def force_obj_to_text(obj):
- if is_string(obj):
- return force_text(obj)
- elif is_dict(obj):
- return {k: force_obj_to_text(v) for k, v in obj.items()}
- elif is_list_like(obj):
- return type(obj)(force_obj_to_text(v) for v in obj)
- else:
- return obj
-
-
-def coerce_args_to_bytes(fn):
- @functools.wraps(fn)
- def inner(*args, **kwargs):
- bytes_args = force_obj_to_bytes(args)
- bytes_kwargs = force_obj_to_bytes(kwargs)
- return fn(*bytes_args, **bytes_kwargs)
-
- return inner
-
-
-def coerce_args_to_text(fn):
- @functools.wraps(fn)
- def inner(*args, **kwargs):
- text_args = force_obj_to_text(args)
- text_kwargs = force_obj_to_text(kwargs)
- return fn(*text_args, **text_kwargs)
-
- return inner
-
-
-def coerce_return_to_bytes(fn):
- @functools.wraps(fn)
- def inner(*args, **kwargs):
- return force_obj_to_bytes(fn(*args, **kwargs))
-
- return inner
-
-
-def coerce_return_to_text(fn):
- @functools.wraps(fn)
- def inner(*args, **kwargs):
- return force_obj_to_text(fn(*args, **kwargs))
-
- return inner
diff --git a/solcx/utils/types.py b/solcx/utils/types.py
deleted file mode 100644
index c7dfe53..0000000
--- a/solcx/utils/types.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import numbers
-from collections import abc
-
-
-def is_integer(value):
- return isinstance(value, int) and not isinstance(value, bool)
-
-
-def is_bytes(value):
- return isinstance(value, (bytes, bytearray))
-
-
-def is_text(value):
- return isinstance(value, str)
-
-
-def is_string(value):
- return isinstance(value, (bytes, str, bytearray))
-
-
-def is_boolean(value):
- return isinstance(value, bool)
-
-
-def is_dict(obj):
- return isinstance(obj, abc.Mapping)
-
-
-def is_list_like(obj):
- return not is_string(obj) and isinstance(obj, abc.Sequence)
-
-
-def is_null(obj):
- return obj is None
-
-
-def is_number(obj):
- return isinstance(obj, numbers.Number)
diff --git a/solcx/wrapper.py b/solcx/wrapper.py
index 7bac364..d1bbf04 100644
--- a/solcx/wrapper.py
+++ b/solcx/wrapper.py
@@ -1,189 +1,149 @@
-from __future__ import absolute_import
-
+import re
import subprocess
+from pathlib import Path
+from typing import Any, Dict, List, Tuple, Union
from semantic_version import Version
-from .exceptions import SolcError
-from .install import get_executable
-from .utils.string import coerce_return_to_text, force_bytes
-
-
-@coerce_return_to_text
-def solc_wrapper(
- solc_binary=None,
- stdin=None,
- help=None,
- version=None,
- add_std=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,
-):
- 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
- elif success_return_code is None:
- success_return_code = 0
-
- if version:
- command.append("--version")
-
- # removed in 0.4.21 and does nothing since <0.4.11, should be removed in the future
- if add_std:
- command.append("--add-std")
-
- 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")
+from solcx import install
+from solcx.exceptions import SolcError, UnknownOption, UnknownValue
- if opcodes:
- command.append("--opcodes")
- if bin:
- command.append("--bin")
+def _get_solc_version(solc_binary: Union[Path, str]) -> Version:
+ # private wrapper function to get `solc` version
+ stdout_data = subprocess.check_output([solc_binary, "--version"], encoding="utf8")
+ version_str = re.findall(r"(?<=Version: ).*?(?=\+)", stdout_data)[0]
+ version_str = re.sub(r"\.0(?=[1-9])", ".", version_str)
+ return Version.coerce(version_str)
- if bin_runtime:
- command.append("--bin-runtime")
- if abi:
- command.append("--abi")
+def _to_string(key: str, value: Any) -> str:
+ # convert data into a string prior to calling `solc`
+ if isinstance(value, (int, str)):
+ return str(value)
+ elif isinstance(value, Path):
+ return value.as_posix()
+ elif isinstance(value, (list, tuple)):
+ return ",".join(_to_string(key, i) for i in value)
+ else:
+ raise TypeError(f"Invalid type for {key}: {type(value)}")
- 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("solc 0.{}.x does not support the --ast flag".format(solc_minor))
- command.append("--ast")
-
- # unsupported by >=0.5.0
- if clone_bin:
- if solc_minor >= 5:
- raise AttributeError(
- "solc 0.{}.x does not support the --clone-bin flag".format(solc_minor)
- )
- command.append("--clone-bin")
+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 = [solc_binary]
+
+ if success_return_code is None:
+ success_return_code = 1 if "help" in kwargs else 0
- if formal:
- if solc_minor >= 5:
- raise AttributeError(
- "solc 0.{}.x does not support the --formal flag".format(solc_minor)
- )
- command.append("--formal")
+ 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 not standard_json and not 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:
- # solc seems to expects utf-8 from stdin:
- # see Scanner class in Solidity source
- stdin = force_bytes(stdin, "utf8")
+ stdin = str(stdin)
proc = subprocess.Popen(
- command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ 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 = stderrdata.split("'")[1]
+ raise UnknownOption(f"solc {solc_version} does not support the '{flag}' option'")
+ if stderrdata.startswith("Invalid option"):
+ # Invalid option to :