def validate_version_pragma(version_str: str, start: ParserPosition) -> None: """ Validates a version pragma directive against the current compiler version. """ from vyper import __version__ version_arr = version_str.split("@version") raw_file_version = version_arr[1].strip() strict_file_version = _convert_version_str(raw_file_version) strict_compiler_version = Version(_convert_version_str(__version__)) try: npm_spec = NpmSpec(strict_file_version) except ValueError: raise VersionException( f'Version specification "{raw_file_version}" is not a valid NPM semantic ' f"version specification", start, ) if not npm_spec.match(strict_compiler_version): raise VersionException( f'Version specification "{raw_file_version}" is not compatible ' f'with compiler version "{__version__}"', start, )
def validate_version_pragma(version_str: str, start: ParserPosition) -> None: """ Validates a version pragma directive against the current compiler version. """ from vyper import __version__ # NOTE: should be `x.y.z.*` installed_version = ".".join(__version__.split(".")[:3]) version_arr = version_str.split("@version") raw_file_version = version_arr[1].strip() strict_file_version = _convert_version_str(raw_file_version) strict_compiler_version = Version(_convert_version_str(installed_version)) if len(strict_file_version) == 0: raise VersionException("Version specification cannot be empty", start) try: npm_spec = NpmSpec(strict_file_version) except ValueError: raise VersionException( f'Version specification "{raw_file_version}" is not a valid NPM semantic ' f"version specification", start, ) if not npm_spec.match(strict_compiler_version): raise VersionException( f'Version specification "{raw_file_version}" is not compatible ' f'with compiler version "{installed_version}"', start, )
def get_vyper_pragma_spec(source: str, path: Optional[str] = None) -> NpmSpec: """ Extracts pragma information from Vyper source code. Args: source: Vyper source code path: Optional path to the source (only used for error reporting) Returns: NpmSpec object """ pragma_match = next( re.finditer(r"(?:\n|^)\s*#\s*@version\s*([^\n]*)", source), None) if pragma_match is None: if path: raise PragmaError(f"No version pragma in '{path}'") raise PragmaError("String does not contain a version pragma") pragma_string = pragma_match.groups()[0] pragma_string = " ".join(pragma_string.split()) try: return NpmSpec(pragma_string) except ValueError: pass try: # special case for Vyper 0.1.0-beta.X version = to_vyper_version(pragma_string) return NpmSpec(str(version)) except Exception: pass path = "" if path is None else f"{path}: " raise PragmaError( f"{path}Cannot parse Vyper version from pragma: {pragma_string}")
def checkout_workspace(self, repo: Repository, repo_info: RepoInfo) -> Workspace: image_tags = repo.get_all_hashes_tags() tag_dict = dict((tag, image_hash) for (image_hash, tag) in image_tags if image_hash) #reverse keys default_image = repo.images[ tag_dict['latest']] if 'latest' in tag_dict else repo.head version_list = [ parse_tag(tag) for tag in sorted(list(tag_dict.keys()), key=len, reverse=True) ] valid_versions = [version for version in version_list if version] spec_expr = f'<={repo_info.major}.{repo_info.minor}' if repo_info.minor else f'<={repo_info.major}' base_ref_spec = NpmSpec(spec_expr) base_ref = base_ref_spec.select(valid_versions) if repo_info.prerelease: assert base_ref, 'Cannot checkout using prerelease until a repo is initialized.' prerelease_base_version = base_ref.next_patch() base_ref = NpmSpec( f'>={str(prerelease_base_version)}-{repo_info.prerelease}' ).select(valid_versions) image_hash = tag_dict[str( base_ref)] if base_ref else default_image.image_hash image = repo.images[image_hash] image.checkout(force=True) return Workspace(repo_uri=repo_info.uri, image_hash=image_hash, version=base_ref)
def _parse_version_string(version_str: str) -> NpmSpec: result = _cache_of_parsed_version_strings.get(version_str) if result is not None: return result try: result = NpmSpec(version_str) except ValueError: try: version = to_vyper_version(version_str) result = NpmSpec(str(version)) except Exception: raise InvalidVyperException( f"Cannot parse Vyper version from pragma: {version_str}") _cache_of_parsed_version_strings[version_str] = result return result
def select_version(versions: List[Version], *, spec: NpmSpec = None) -> Optional[Version]: """Select a version according to given specification. Args: versions: List of versions to parse. spec: A version specification. Returns: The selected version according to ``spec`` or highest version if ``spec`` is ``None``. """ if len(versions) == 0: return None if spec is not None: selected_version = spec.select(versions) else: versions.sort() selected_version = versions[-1] return selected_version
def test_select_version_with_empty_candidates(self): versions = [] version = select_version(versions) self.assertIsNone(version) version = select_version(versions, spec=NpmSpec('0.0.x')) self.assertIsNone(version)
def pragma( *, compiler_version: str, **kwargs: Any, ) -> None: """ Specify pragmas for the compiler. Args: compiler_version: Acceptable versions of the compiler. Will fail if the current PyTeal version is not contained in the range. Follows the npm `semver range scheme <https://github.com/npm/node-semver#ranges>`_ for specifying compatible versions. For example: .. code-block:: python # this will immediately fail if the current PyTeal version does not satisfy the # version constraint pragma(compiler_version="^0.14.0") """ pkg_version = pkg_resources.require("pyteal")[0].version pyteal_version = Version(__convert_pep440_compiler_version(pkg_version)) if pyteal_version not in NpmSpec( __convert_pep440_compiler_version(compiler_version) ): raise TealPragmaError( "PyTeal version {} is not compatible with compiler version {}".format( pkg_version, compiler_version ) )
def get_solc_version(version_str): """Extract solidity version from semantic version format :param version_str: solidity version is the semantic version format :return: raw solidity version of the file """ if not version_str: return None # version not found in file version_str = ' <'.join(version_str.split('<')) required_version = [v for v in NpmSpec('>=0.4.19').clause.clauses] given_target = sorted(v.target for v in NpmSpec(version_str).clause.clauses) for t in given_target: if required_version[0].match(t): return str(t) return None # Incompatible version
def test_version_clean(self): """Calling .full_clean() should convert str to Version/Spec objects.""" obj = models.VersionModel(version='0.1.1', spec='==0.1.1,!=0.1.1-alpha', npm_spec='1.x') obj.full_clean() self.assertEqual(Version('0.1.1'), obj.version) self.assertEqual(SimpleSpec('==0.1.1,!=0.1.1-alpha'), obj.spec) self.assertEqual(NpmSpec('1.x'), obj.npm_spec)
def test_version(self): obj = models.VersionModel( version=Version('0.1.1'), spec=SimpleSpec('==0.1.1,!=0.1.1-alpha'), npm_spec=NpmSpec('1.2 - 2.3'), ) self.assertEqual(Version('0.1.1'), obj.version) self.assertEqual(SimpleSpec('==0.1.1,!=0.1.1-alpha'), obj.spec) self.assertEqual(NpmSpec('1.2 - 2.3'), obj.npm_spec) alt_obj = models.VersionModel(version=obj.version, spec=obj.spec, npm_spec=obj.npm_spec) self.assertEqual(Version('0.1.1'), alt_obj.version) self.assertEqual(SimpleSpec('==0.1.1,!=0.1.1-alpha'), alt_obj.spec) self.assertEqual(obj.spec, alt_obj.spec) self.assertEqual(obj.npm_spec, alt_obj.npm_spec) self.assertEqual(obj.version, alt_obj.version)
def find_best_solc_version( contract_sources: Dict[str, str], install_needed: bool = False, install_latest: bool = False, silent: bool = True, ) -> str: """ Analyzes contract pragmas and finds the best version compatible with all sources. Args: contract_sources: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: set to False to enable verbose reporting Returns: version string """ available_versions, installed_versions = _get_solc_version_list() for path, source in contract_sources.items(): pragma_string = next(PRAGMA_REGEX.finditer(source), None) if pragma_string is None: raise PragmaError(f"No version pragma in '{path}'") pragma_spec = NpmSpec(pragma_string.groups()[0]) installed_versions = [ i for i in installed_versions if i in pragma_spec ] available_versions = [ i for i in available_versions if i in pragma_spec ] if not available_versions: raise IncompatibleSolcVersion( "No installable solc version compatible across all sources") if not installed_versions and not (install_needed or install_latest): raise IncompatibleSolcVersion( "No installed solc version compatible across all sources") if max(available_versions) > max(installed_versions, default=Version("0.0.0")): if install_latest or (install_needed and not installed_versions): install_solc(max(available_versions)) return str(max(available_versions)) if not silent: print( f"New compatible solc version available: {max(available_versions)}" ) return str(max(installed_versions))
def test_serialization(self): o1 = models.VersionModel( version=Version('0.1.1'), spec=SimpleSpec('==0.1.1,!=0.1.1-alpha'), npm_spec=NpmSpec('1.2 - 2.3'), ) o2 = models.VersionModel( version=Version('0.4.3-rc3+build3'), spec=SimpleSpec('<=0.1.1-rc2,!=0.1.1-rc1'), npm_spec=NpmSpec('1.2 - 2.3'), ) data = serializers.serialize('json', [o1, o2]) obj1, obj2 = serializers.deserialize('json', data) self.assertEqual(o1.version, obj1.object.version) self.assertEqual(o1.spec, obj1.object.spec) self.assertEqual(o1.npm_spec, obj1.object.npm_spec) self.assertEqual(o2.version, obj2.object.version) self.assertEqual(o2.spec, obj2.object.spec) self.assertEqual(o2.npm_spec, obj2.object.npm_spec)
def is_valid_compiler_version(compiler_version: str): """Check if the compiler version is valid. Args: compiler_version: The compiler version to check. Returns: True if the compiler version is a valid NPM specification range using either the PEP 440 or semantic version format, otherwise False. """ try: pep440_converted = __convert_pep440_compiler_version(compiler_version) NpmSpec(pep440_converted) return True except ValueError: return False
def visitVersionPragma(self, ctx: SolidityParser.VersionPragmaContext): version = ctx.ver.getText().strip() spec = NpmSpec(version) name = self.handle_field(ctx.name) if name == 'zkay' and Version(cfg.zkay_version) not in spec: raise SyntaxException(f'Contract requires a different zkay version.\n' f'Current version is {cfg.zkay_version} but pragma zkay mandates {version}.', ctx.ver, self.code) elif name != 'zkay' and spec != cfg.zkay_solc_version_compatibility: # For backwards compatibility with older zkay versions assert name == 'solidity' raise SyntaxException(f'Contract requires solidity version {spec}, which is not compatible ' f'with the current zkay version (requires {cfg.zkay_solc_version_compatibility}).', ctx.ver, self.code) return f'{name} {version}'
def get_pragma_spec(source: str, path: Optional[str] = None) -> NpmSpec: """ Extracts pragma information from Solidity source code. Args: source: Solidity source code path: Optional path to the source (only used for error reporting) Returns: NpmSpec object """ pragma_match = next(re.finditer(r"pragma +solidity([^;]*);", source), None) if pragma_match is not None: pragma_string = pragma_match.groups()[0] pragma_string = " ".join(pragma_string.split()) return NpmSpec(pragma_string) if path: raise PragmaError(f"No version pragma in '{path}'") raise PragmaError("String does not contain a version pragma")
def get_pragma_spec(source: str) -> Optional[NpmSpec]: """ Extracts pragma information from Solidity source code. Args: source: Solidity source code Returns: NpmSpec object or None, if no valid pragma is found """ pragma_match = next( re.finditer(r"(?:\n|^)\s*pragma\s*solidity\s*([^;\n]*)", source), None) if pragma_match is None: return None # Try compiling with latest pragma_string = pragma_match.groups()[0] pragma_string = " ".join(pragma_string.split()) try: return NpmSpec(pragma_string) except ValueError: return None
import json import logging import subprocess from abc import abstractmethod from pathlib import Path from typing import Dict, Optional, Set import attr from semantic_version import NpmSpec, Version from bento.error import NodeError from bento.tool import Tool NODE_VERSION_RANGE = NpmSpec("^8.10.0 || ^10.13.0 || >=11.10.1") VersionDict = Dict[str, Version] @attr.s(auto_attribs=True) class NpmDeps(object): """Represents top-level npm package dependencies for a project""" main: Dict[str, NpmSpec] dev: Dict[str, NpmSpec] def __contains__(self, item: str) -> bool: return item in self.main or item in self.dev class JsTool(Tool): @property
def test_select_version_with_spec(self): versions = [Version('0.0.3'), Version('1.1.1'), Version('1.1.2')] version = select_version(versions, spec=NpmSpec('0.0.x')) self.assertEqual(version, Version('0.0.3'))
def find_solc_versions( contract_sources: Dict[str, str], install_needed: bool = False, install_latest: bool = False, silent: bool = True, ) -> Dict: """ Analyzes contract pragmas and determines which solc version(s) to use. Args: contract_sources: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: set to False to enable verbose reporting Returns: dictionary of {'version': ['path', 'path', ..]} """ available_versions, installed_versions = _get_solc_version_list() pragma_specs: Dict = {} to_install = set() new_versions = set() for path, source in contract_sources.items(): pragma_string = next(PRAGMA_REGEX.finditer(source), None) if pragma_string is None: raise PragmaError(f"No version pragma in '{path}'") pragma_specs[path] = NpmSpec(pragma_string.groups()[0]) version = pragma_specs[path].select(installed_versions) if not version and not (install_needed or install_latest): raise IncompatibleSolcVersion( f"No installed solc version matching '{pragma_string[0]}' in '{path}'" ) # if no installed version of solc matches the pragma, find the latest available version latest = pragma_specs[path].select(available_versions) if not version and not latest: raise IncompatibleSolcVersion( f"No installable solc version matching '{pragma_string[0]}' in '{path}'" ) if not version or (install_latest and latest > version): to_install.add(latest) elif latest and latest > version: new_versions.add(str(version)) # install new versions if needed if to_install: install_solc(*to_install) installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] elif new_versions and not silent: print( f"New compatible solc version{'s' if len(new_versions) > 1 else ''}" f" available: {', '.join(new_versions)}") # organize source paths by latest available solc version compiler_versions: Dict = {} for path, spec in pragma_specs.items(): version = spec.select(installed_versions) compiler_versions.setdefault(str(version), []).append(path) return compiler_versions
def describe_by_name(self, name: str, *, version: str = None) -> Optional[Resource]: """Describe an analytic by name. When the optional ``version`` argument is ``None``, the analytic with highest version is returned. When the optional ``version`` argument is not ``None``, the analytic with that version is returned. If no such analytic exist, ``None`` is returned. When ``version`` is a version range, the analytic with highest version within the specified range is returned. Note that ``version`` is expected to follow the `syntax defined by npm <https://semver.npmjs.com/>`_. Args: analytic: Identifier or name of the analytic to describe. version: Optional version or version range used to select the analytic to describe. Examples: >>> sdk.analytics.describe_by_name('photogrammetry', ... version='1.0.x') Resource(_id='60c331fed5ffbd0012f1fb1a') """ if not name: return None search_filter = {'name': {'$eq': name}} if version is not None: try: query_exact_version = Version(version) except ValueError: # may be a version range query_exact_version = False if query_exact_version: search_filter['version'] = {'$eq': version} candidates = cast(List[Resource], self.search(filter=search_filter, limit=1)) return candidates[0] if len(candidates) else None try: version_spec = NpmSpec(version) except ValueError: # malformed version range return None else: version_spec = None fields = {'include': ['_id', 'name', 'version']} candidates = [a for a in self.search_generator(filter=search_filter, fields=fields) if hasattr(a, 'version')] # check on version property should not be necessary but API # documentation doesn't say this property is required... candidates_by_version = dict([(a.version, a) for a in candidates]) candidate_versions = [Version(a.version) for a in candidates] selected_version = select_version(candidate_versions, spec=version_spec) if selected_version: return candidates_by_version[str(selected_version)] return None
def test_get_pragma_spec(): assert sources.get_pragma_spec(MESSY_SOURCE) == NpmSpec(">=0.4.22 <0.7.0")
("Foo", "contract"), ("Bar", "interface"), ("Baz", "abstract contract"), ("Potato", "library"), ("Foo2", "contract"), ("Bar2", "library"), ] def test_load_messy_project(): project = compile_source(MESSY_SOURCE) assert list(project.keys()) == ["Bar2", "Foo", "Foo2", "Potato"] def test_get_pragma_spec(): assert sources.get_pragma_spec(MESSY_SOURCE) == NpmSpec(">=0.4.22 <0.7.0") @pytest.mark.parametrize( "version, spec", [ ("0.1.0b16", NpmSpec("0.1.0-beta.16")), ("0.1.0Beta17", NpmSpec("0.1.0-beta.17")), ("^0.2.0", NpmSpec("^0.2.0")), ("<=0.2.4", NpmSpec("<=0.2.4")), ], ) def test_get_vyper_pragma_spec(version, spec): source = f"""# @version {version}""" assert sources.get_vyper_pragma_spec(source) == spec