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

feat: adds compiler_cache_folder config var #1897

Merged
merged 19 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
9 changes: 8 additions & 1 deletion docs/userguides/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,14 @@ compile:
## Settings

Generally, configure compiler plugins using your `ape-config.yaml` file.
For example, when using the `vyper` plugin, you can configure settings under the `vyper` key:
One setting that applies to many compiler plugins is `compiler_cache_folder`, which holds dependency source files the compiler uses when compiling your contracts.
By default, the folder is in your `contracts/.cache` folder but there are times you may want to move this to another location.
mikeshultz marked this conversation as resolved.
Show resolved Hide resolved

```yaml
compiler_cache_folder: .cache
mikeshultz marked this conversation as resolved.
Show resolved Hide resolved
```

As another example, when using the `vyper` plugin, you can configure settings under the `vyper` key:

```yaml
vyper:
Expand Down
13 changes: 11 additions & 2 deletions src/ape/api/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from enum import Enum
from typing import Any, Dict, Optional, TypeVar
from typing import TYPE_CHECKING, Any, Dict, Optional, TypeVar

from pydantic import ConfigDict
from pydantic_settings import BaseSettings

if TYPE_CHECKING:
from ape.managers.config import ConfigManager

ConfigItemType = TypeVar("ConfigItemType")


Expand Down Expand Up @@ -33,8 +36,14 @@ class PluginConfig(BaseSettings):
a config API must register a subclass of this class.
"""

# NOTE: This is probably partially initialized at the time of assignment
_config_manager: Optional["ConfigManager"]

@classmethod
def from_overrides(cls, overrides: Dict) -> "PluginConfig":
def from_overrides(
cls, overrides: Dict, config_manager: Optional["ConfigManager"] = None
) -> "PluginConfig":
cls._config_manager = config_manager
default_values = cls().model_dump()

def update(root: Dict, value_map: Dict):
Expand Down
5 changes: 4 additions & 1 deletion src/ape/managers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ def _plugin_configs(self) -> Dict[str, PluginConfig]:
)

self.contracts_folder = configs["contracts_folder"] = contracts_folder

deployments = user_config.pop("deployments", {})
valid_ecosystems = dict(self.plugin_manager.ecosystems)
valid_network_names = [n[1] for n in [e[1] for e in self.plugin_manager.networks]]
Expand All @@ -216,7 +217,9 @@ def _plugin_configs(self) -> Dict[str, PluginConfig]:
user_override = user_config.pop(plugin_name, {}) or {}
if config_class != ConfigDict:
# NOTE: Will raise if improperly provided keys
config = config_class.from_overrides(user_override) # type: ignore
config = config_class.from_overrides( # type: ignore
mikeshultz marked this conversation as resolved.
Show resolved Hide resolved
user_override, config_manager=self
)
else:
# NOTE: Just use it directly as a dict if `ConfigDict` is passed
config = user_override
Expand Down
19 changes: 13 additions & 6 deletions src/ape/managers/project/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ def contracts_folder(self) -> Path:

return folder

@property
def compiler_cache_folder(self) -> Path:
"""
The path to the project's compiler source cache folder.
"""
# NOTE: as long as config has come up properly, this should not be None
mikeshultz marked this conversation as resolved.
Show resolved Hide resolved
compile_conf = self.config_manager.get_config("compile")
assert compile_conf.cache_folder is not None
return compile_conf.cache_folder

@property
def source_paths(self) -> List[Path]:
"""
Expand All @@ -112,14 +122,12 @@ def source_paths(self) -> List[Path]:

# Dependency sources should be ignored, as they are pulled in
# independently to compiler via import.
in_contracts_cache_dir = contracts_folder / ".cache"

for extension in self.compiler_manager.registered_compilers:
files.extend(
(
x
for x in contracts_folder.rglob(f"*{extension}")
if x.is_file() and in_contracts_cache_dir not in x.parents
if x.is_file() and self.compiler_cache_folder not in x.parents
)
)

Expand Down Expand Up @@ -703,9 +711,8 @@ def load_contracts(
types for each compiled contract.
"""

in_source_cache = self.contracts_folder / ".cache"
if not use_cache and in_source_cache.is_dir():
shutil.rmtree(str(in_source_cache))
if not use_cache and self.compiler_cache_folder.is_dir():
shutil.rmtree(str(self.compiler_cache_folder))

if isinstance(file_paths, Path):
file_path_list = [file_paths]
Expand Down
24 changes: 22 additions & 2 deletions src/ape_compile/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import List
from pathlib import Path
from typing import List, Optional

from pydantic import field_validator
from pydantic import field_validator, model_validator

from ape import plugins
from ape.api import PluginConfig

DEFAULT_CACHE_FOLDER_NAME = ".cache" # default relative to contracts/


class Config(PluginConfig):
include_dependencies: bool = False
Expand All @@ -23,6 +26,23 @@ class Config(PluginConfig):
Source exclusion globs across all file types.
"""

cache_folder: Optional[Path] = None
"""
Path to contract dependency cache directory (e.g. `contracts/.cache`)
"""

@model_validator(mode="after")
def validate_cache_folder(self):
if self._config_manager is None:
return # Not enough information to continue at this time

# Handle setting default
if self.cache_folder is None:
contracts_folder = self._config_manager.contracts_folder
self.cache_folder = contracts_folder / DEFAULT_CACHE_FOLDER_NAME
elif not self.cache_folder.is_absolute():
self.cache_folder = self.cache_folder.expanduser().resolve()

@field_validator("exclude", mode="before")
@classmethod
def validate_exclude(cls, value):
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def temp_config(config):
def func(data: Optional[Dict] = None):
data = data or {}
with tempfile.TemporaryDirectory() as temp_dir_str:
temp_dir = Path(temp_dir_str)
temp_dir = Path(temp_dir_str).resolve()
config._cached_configs = {}
config_file = temp_dir / CONFIG_FILE_NAME
config_file.touch()
Expand Down
9 changes: 9 additions & 0 deletions tests/functional/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,15 @@ def test_contracts_folder_with_hyphen(temp_config):
assert project.contracts_folder.name == "src"


def test_compiler_cache_folder(temp_config):
with temp_config(
{"contracts_folder": "smarts", "compile": {"cache_folder": ".cash"}}
) as project:
assert project.contracts_folder.name == "smarts"
assert project.compiler_cache_folder.name == ".cash"
assert str(project.contracts_folder) not in str(project.compiler_cache_folder)


def test_custom_network():
chain_id = 11191919191991918223773
data = {
Expand Down
Loading