Skip to content

Commit

Permalink
feat: compile
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed May 23, 2024
1 parent 240dcb0 commit 6a862bc
Show file tree
Hide file tree
Showing 30 changed files with 1,139 additions and 1,166 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repos:
rev: 0.7.17
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter]
additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject]


default_language_version:
Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,32 +62,32 @@ solidity:

### Dependency Mapping

To configure import remapping, use your project's `ape-config.yaml` file:

```yaml
solidity:
import_remapping:
- "@openzeppelin=path/to/open_zeppelin/contracts"
```

If you are using the `dependencies:` key in your `ape-config.yaml`, `ape` can automatically
search those dependencies for the path.
By default, `ape-solidity` knows to look at installed dependencies for potential remapping-values and will use those when it notices you are importing them.
For example, if you are using dependencies like:

```yaml
dependencies:
- name: OpenZeppelin
- name: openzeppelin
github: OpenZeppelin/openzeppelin-contracts
version: 4.4.2
solidity:
import_remapping:
- "@openzeppelin=OpenZeppelin/4.4.2"
```

Once you have your dependencies configured, you can import packages using your import keys:
And your source files import from `openzeppelin` this way:

```solidity
import "@openzeppelin/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
```

Ape knows how to resolve the `@openzeppelin` value and find the correct source.

If you want to override this behavior or add new remappings that are not dependencies, you can add them to your `ape-config.yaml` under the `solidity:` key.
For example, let's say you have downloaded `openzeppelin` somewhere and do not have it installed in Ape.
You can map to your local install of `openzeppelin` this way:

```yaml
solidity:
import_remapping:
- "@openzeppelin=path/to/openzeppelin"
```

### Library Linking
Expand Down
3 changes: 2 additions & 1 deletion ape_solidity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ape import plugins

from .compiler import Extension, SolidityCompiler, SolidityConfig
from ._utils import Extension
from .compiler import SolidityCompiler, SolidityConfig


@plugins.register(plugins.Config)
Expand Down
95 changes: 4 additions & 91 deletions ape_solidity/_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import os
import re
from collections.abc import Iterable
from enum import Enum
Expand All @@ -9,15 +8,10 @@
from ape.exceptions import CompilerError
from ape.utils import pragma_str_to_specifier_set
from packaging.specifiers import SpecifierSet
from packaging.version import InvalidVersion
from packaging.version import Version
from packaging.version import Version as _Version
from pydantic import BaseModel, field_validator
from solcx.install import get_executable
from solcx.wrapper import get_solc_version as get_solc_version_from_binary

from ape_solidity.exceptions import IncorrectMappingFormatError

OUTPUT_SELECTION = [
"abi",
"bin-runtime",
Expand All @@ -33,83 +27,7 @@ class Extension(Enum):
SOL = ".sol"


class ImportRemapping(BaseModel):
entry: str
packages_cache: Path

@field_validator("entry", mode="before")
@classmethod
def validate_entry(cls, value):
if len((value or "").split("=")) != 2:
raise IncorrectMappingFormatError()

return value

@property
def _parts(self) -> list[str]:
return self.entry.split("=")

# path normalization needed in case delimiter in remapping key/value
# and system path delimiter are different (Windows as an example)
@property
def key(self) -> str:
return os.path.normpath(self._parts[0])

@property
def name(self) -> str:
suffix_str = os.path.normpath(self._parts[1])
return suffix_str.split(os.path.sep)[0]

@property
def package_id(self) -> Path:
suffix = Path(self._parts[1])
data_folder_cache = self.packages_cache / suffix

try:
_Version(suffix.name)
if not suffix.name.startswith("v"):
suffix = suffix.parent / f"v{suffix.name}"

except InvalidVersion:
# The user did not specify a version_id suffix in their mapping.
# We try to smartly figure one out, else error.
if len(Path(suffix).parents) == 1 and data_folder_cache.is_dir():
version_ids = [d.name for d in data_folder_cache.iterdir()]
if len(version_ids) == 1:
# Use only version ID available.
suffix = suffix / version_ids[0]

elif not version_ids:
raise CompilerError(f"Missing dependency '{suffix}'.")

else:
options_str = ", ".join(version_ids)
raise CompilerError(
"Ambiguous version reference. "
f"Please set import remapping value to {suffix}/{{version_id}} "
f"where 'version_id' is one of '{options_str}'."
)

return suffix


class ImportRemappingBuilder:
def __init__(self, contracts_cache: Path):
# import_map maps import keys like `@openzeppelin/contracts`
# to str paths in the compiler cache folder.
self.import_map: dict[str, str] = {}
self.dependencies_added: set[Path] = set()
self.contracts_cache = contracts_cache

def add_entry(self, remapping: ImportRemapping):
path = remapping.package_id
if self.contracts_cache not in path.parents:
path = self.contracts_cache / path

self.import_map[remapping.key] = str(path)


def get_import_lines(source_paths: set[Path]) -> dict[Path, list[str]]:
def get_import_lines(source_paths: Iterable[Path]) -> dict[Path, list[str]]:
imports_dict: dict[Path, list[str]] = {}
for filepath in source_paths:
import_set = set()
Expand Down Expand Up @@ -184,17 +102,12 @@ def add_commit_hash(version: Union[str, Version]) -> Version:
return get_solc_version_from_binary(solc, with_commit_hash=True)


def verify_contract_filepaths(contract_filepaths: Iterable[Path]) -> set[Path]:
invalid_files = [p.name for p in contract_filepaths if p.suffix != Extension.SOL.value]
if not invalid_files:
return set(contract_filepaths)

sources_str = "', '".join(invalid_files)
raise CompilerError(f"Unable to compile '{sources_str}' using Solidity compiler.")
def get_versions_can_use(pragma_spec: SpecifierSet, options: Iterable[Version]) -> list[Version]:
return sorted(list(pragma_spec.filter(options)), reverse=True)


def select_version(pragma_spec: SpecifierSet, options: Iterable[Version]) -> Optional[Version]:
choices = sorted(list(pragma_spec.filter(options)), reverse=True)
choices = get_versions_can_use(pragma_spec, options)
return choices[0] if choices else None


Expand Down
Loading

0 comments on commit 6a862bc

Please sign in to comment.