Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Use Version object #93

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion solcx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
compile_source,
compile_standard,
get_solc_version,
get_solc_version_string,
link_code,
)

Expand Down
115 changes: 66 additions & 49 deletions solcx/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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
Expand All @@ -29,7 +30,7 @@
tqdm = None


DOWNLOAD_BASE = "https://github.com/ethereum/solidity/releases/download/{}/{}"
DOWNLOAD_BASE = "https://github.com/ethereum/solidity/releases/download/v{}/{}"
ALL_RELEASES = "https://api.github.com/repos/ethereum/solidity/releases?per_page=100"

MINIMAL_SOLC_VERSION = "v0.4.11"
Expand Down Expand Up @@ -64,6 +65,16 @@ def _get_platform():
)


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 ValueError("py-solc-x does not support solc versions <0.4.11")
return version


def get_solc_folder(solcx_binary_path=None):
if os.getenv(SOLCX_BINARY_PATH_VARIABLE):
return Path(os.getenv(SOLCX_BINARY_PATH_VARIABLE))
Expand All @@ -75,9 +86,10 @@ 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_import_version(path: str) -> Version:
stdout_data = subprocess.check_output([path, "--version"]).decode().strip()
stdout_data = stdout_data[stdout_data.index("Version: ") + 9 : stdout_data.index("+")]
return Version.coerce(stdout_data)


def import_installed_solc(solcx_binary_path=None):
Expand All @@ -101,30 +113,32 @@ def import_installed_solc(solcx_binary_path=None):

for path in path_list:
try:
version = _import_version(path)
version = _get_import_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)
get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath(f"solc-v{version}")
)
shutil.copy(path, copy_path)
try:
# confirm that solc still works after being copied
assert version == _import_version(copy_path)
assert version == _get_import_version(copy_path)
except Exception:
os.unlink(copy_path)


def get_executable(version=None, solcx_binary_path=None):
if not version:
version = solc_version
else:
version = _convert_and_validate_version(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)
solc_bin = get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath(f"solc-v{version}")
if sys.platform == "win32":
solc_bin = solc_bin.joinpath("solc.exe")
if not solc_bin.exists():
Expand All @@ -136,7 +150,7 @@ def get_executable(version=None, solcx_binary_path=None):


def set_solc_version(version, silent=False, solcx_binary_path=None):
version = _check_version(version)
version = _convert_and_validate_version(version)
get_executable(version, solcx_binary_path)
global solc_version
solc_version = version
Expand All @@ -145,42 +159,38 @@ def set_solc_version(version, silent=False, solcx_binary_path=None):


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()]
)
version = _select_pragma_version(pragma_string, get_installed_solc_versions())
if not version:
raise SolcNotInstalled(
f"No compatible solc version installed."
f" Use solcx.install_solc_version_pragma('{version}') to install."
)
version = _check_version(version)
version = _convert_and_validate_version(version)
global solc_version
solc_version = version
if not silent:
LOGGER.info(f"Using solc version {solc_version}")
if check_new:
latest = install_solc_pragma(pragma_string, False)
if Version(latest) > Version(version[1:]):
if latest > version:
LOGGER.info(f"Newer compatible solc version exists: {latest}")


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()]
)
version = _select_pragma_version(pragma_string, get_available_solc_versions())
if not version:
raise ValueError("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 = []
def get_available_solc_versions(headers: Optional[Dict] = None) -> List[Version]:
version_list = []
pattern = VERSION_REGEX[_get_platform()]

if not headers and os.getenv("GITHUB_TOKEN"):
auth = b64encode(os.getenv("GITHUB_TOKEN").encode()).decode()
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)
Expand All @@ -200,13 +210,14 @@ def get_available_solc_versions(headers=None):
for release in data.json():
asset = next((i for i in release["assets"] if re.match(pattern, i["name"])), False)
if asset:
versions.append(release["tag_name"])
version = Version.coerce(release["tag_name"].lstrip("v"))
version_list.append(version)
if release["tag_name"] == MINIMAL_SOLC_VERSION:
break
return versions
return sorted(version_list, reverse=True)


def _select_pragma_version(pragma_string, version_list):
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
Expand All @@ -216,20 +227,25 @@ def _select_pragma_version(pragma_string, version_list):
selected = spec.select(version_list)
if selected and (not version or version < selected):
version = selected
if version:
return str(version)

return version


def get_installed_solc_versions(solcx_binary_path=None) -> List[Version]:
install_path = get_solc_folder(solcx_binary_path=solcx_binary_path)
return sorted([Version(i.name[6:]) for i in install_path.glob("solc-v*")], reverse=True)

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*")
)

def install_solc(
version: Union[str, Version],
allow_osx: bool = False,
show_progress: bool = False,
solcx_binary_path: str = None,
) -> None:

def install_solc(version, allow_osx=False, show_progress=False, solcx_binary_path=None):
arch = _get_arch()
platform = _get_platform()
version = _check_version(version)
version = _convert_and_validate_version(version)

lock = get_process_lock(version)
if not lock.acquire(False):
Expand Down Expand Up @@ -259,13 +275,6 @@ def install_solc(version, allow_osx=False, show_progress=False, solcx_binary_pat
lock.release()


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 f"v{version}"


def _check_subprocess_call(command, message=None, verbose=False, **proc_kwargs):
if message:
LOGGER.debug(message)
Expand All @@ -280,8 +289,8 @@ def _chmod_plus_x(executable_path):
executable_path.chmod(executable_path.stat().st_mode | stat.S_IEXEC)


def _check_for_installed_version(version, solcx_binary_path=None):
path = get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath("solc-" + version)
def _check_for_installed_version(version: Version, solcx_binary_path=None):
path = get_solc_folder(solcx_binary_path=solcx_binary_path).joinpath(f"solc-v{version}")
if path.exists():
LOGGER.info(f"solc {version} already installed at: {path}")
return False
Expand Down Expand Up @@ -322,7 +331,9 @@ def _download_solc(url, show_progress):
return content


def _install_solc_linux(version, show_progress, solcx_binary_path=None):
def _install_solc_linux(
version: Version, show_progress: bool, solcx_binary_path: Optional[str]
) -> 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:
Expand All @@ -333,7 +344,9 @@ def _install_solc_linux(version, show_progress, solcx_binary_path=None):
_chmod_plus_x(binary_path)


def _install_solc_windows(version, show_progress, solcx_binary_path=None):
def _install_solc_windows(
version: Version, show_progress: bool, solcx_binary_path: Optional[str]
) -> None:
download = DOWNLOAD_BASE.format(version, "solidity-windows.zip")
install_folder = _check_for_installed_version(version)
if install_folder:
Expand All @@ -342,17 +355,21 @@ def _install_solc_windows(version, show_progress, solcx_binary_path=None):
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
f"solc-v{version}"
)
temp_path.rename(install_folder)


def _install_solc_arm(version, show_progress, solcx_binary_path):
def _install_solc_arm(
version: Version, show_progress: bool, solcx_binary_path: Optional[str]
) -> None:
_compile_solc(version, show_progress, solcx_binary_path)


def _install_solc_osx(version, allow_osx, show_progress, solcx_binary_path):
if version.startswith("v0.4") and not allow_osx:
def _install_solc_osx(
version: Version, allow_osx: bool, show_progress: bool, solcx_binary_path: Optional[str]
) -> None:
if version < Version("0.5.0") 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"
Expand All @@ -363,9 +380,9 @@ def _install_solc_osx(version, allow_osx, show_progress, solcx_binary_path):
_compile_solc(version, show_progress, solcx_binary_path)


def _compile_solc(version, show_progress, solcx_binary_path):
def _compile_solc(version: Version, show_progress: bool, solcx_binary_path: Optional[str]) -> None:
temp_path = _get_temp_folder()
download = DOWNLOAD_BASE.format(version, f"solidity_{version[1:]}.tar.gz")
download = DOWNLOAD_BASE.format(version, f"solidity_{version}.tar.gz")
binary_path = _check_for_installed_version(version)
if not binary_path:
return
Expand Down
25 changes: 8 additions & 17 deletions solcx/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import re

import semantic_version
from semantic_version import Version

from .exceptions import ContractsNotFound, SolcError
from .wrapper import solc_wrapper
Expand All @@ -15,11 +15,9 @@
)


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: "):
def get_solc_version() -> Version:
stdoutdata, stderrdata, command, proc = solc_wrapper(version=True)
if "Version: " not in stdoutdata:
raise SolcError(
command=command,
return_code=proc.returncode,
Expand All @@ -28,16 +26,9 @@ def get_solc_version_string(**kwargs):
stderr_data=stderrdata,
message="Unable to extract version string from command output",
)
return version_string.rstrip()


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")
)
)
version_string = stdoutdata.split("Version: ", maxsplit=1)[1]
version_string = version_string.replace("++", "pp").strip()
return Version(strip_zeroes_from_month_and_day(version_string))


def _parse_compiler_output(stdoutdata):
Expand Down Expand Up @@ -160,7 +151,7 @@ def link_code(unlinked_bytecode, libraries):
(":".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,
stdin=unlinked_bytecode, link=True, libraries=libraries_arg
)

return stdoutdata.replace("Linking completed.", "").strip()
6 changes: 3 additions & 3 deletions tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ def isolation():

def test_not_installed():
solcx.install.get_executable()
with pytest.raises(SolcNotInstalled):
with pytest.raises(ValueError):
solcx.install.get_executable("v0.4.0")
solcx.install.solc_version = None
with pytest.raises(SolcNotInstalled):
solcx.install.get_executable()


def test_unsupported_version():
solcx.install._check_version("0.4.11")
solcx.install._convert_and_validate_version("0.4.11")
with pytest.raises(ValueError):
solcx.install._check_version("0.4.10")
solcx.install._convert_and_validate_version("0.4.10")


def test_unknown_platform():
Expand Down
Loading