Skip to content

Commit 54e484e

Browse files
authored
Merge pull request #606 from crytic/dev-foundry-partial
platform: foundry: improve targeted compilations, skips
2 parents bd7a221 + 4c2addc commit 54e484e

File tree

1 file changed

+59
-14
lines changed

1 file changed

+59
-14
lines changed

crytic_compile/platform/foundry.py

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
Foundry platform
33
"""
44
import logging
5-
import os
65
import subprocess
76
from pathlib import Path
8-
from typing import TYPE_CHECKING, List, Optional, TypeVar
7+
from typing import TYPE_CHECKING, List, Optional, TypeVar, Union
98

109
import json
1110

@@ -32,6 +31,14 @@ class Foundry(AbstractPlatform):
3231
PROJECT_URL = "https://github.com/foundry-rs/foundry"
3332
TYPE = Type.FOUNDRY
3433

34+
def __init__(self, target: str, **_kwargs: str):
35+
super().__init__(target, **_kwargs)
36+
37+
project_root = Foundry.locate_project_root(target)
38+
# if we are initializing this, it is indeed a foundry project and thus has a root path
39+
assert project_root is not None
40+
self._project_root: Path = project_root
41+
3542
# pylint: disable=too-many-locals,too-many-statements,too-many-branches
3643
def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
3744
"""Compile
@@ -52,38 +59,45 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
5259
LOGGER.info(
5360
"--ignore-compile used, if something goes wrong, consider removing the ignore compile flag"
5461
)
55-
56-
if not ignore_compile:
62+
else:
5763
compilation_command = [
5864
"forge",
5965
"build",
6066
"--build-info",
6167
]
6268

69+
targeted_build = not self._project_root.samefile(self._target)
70+
if targeted_build:
71+
compilation_command += [
72+
str(Path(self._target).resolve().relative_to(self._project_root))
73+
]
74+
6375
compile_all = kwargs.get("foundry_compile_all", False)
6476

65-
if not compile_all:
66-
foundry_config = self.config(self._target)
77+
if not targeted_build and not compile_all:
78+
foundry_config = self.config(self._project_root)
6779
if foundry_config:
6880
compilation_command += [
6981
"--skip",
70-
f"*/{foundry_config.tests_path}/**",
71-
f"*/{foundry_config.scripts_path}/**",
82+
f"./{foundry_config.tests_path}/**",
83+
f"./{foundry_config.scripts_path}/**",
7284
"--force",
7385
]
7486

7587
run(
7688
compilation_command,
77-
cwd=self._target,
89+
cwd=self._project_root,
7890
)
7991

8092
build_directory = Path(
81-
self._target,
93+
self._project_root,
8294
out_directory,
8395
"build-info",
8496
)
8597

86-
hardhat_like_parsing(crytic_compile, self._target, build_directory, self._target)
98+
hardhat_like_parsing(
99+
crytic_compile, str(self._target), build_directory, str(self._project_root)
100+
)
87101

88102
def clean(self, **kwargs: str) -> None:
89103
"""Clean compilation artifacts
@@ -99,7 +113,38 @@ def clean(self, **kwargs: str) -> None:
99113
if ignore_compile:
100114
return
101115

102-
run(["forge", "clean"], cwd=self._target)
116+
run(["forge", "clean"], cwd=self._project_root)
117+
118+
@staticmethod
119+
def locate_project_root(file_or_dir: str) -> Optional[Path]:
120+
"""Determine the project root (if the target is a Foundry project)
121+
122+
Foundry projects are detected through the presence of their
123+
configuration file. See the following for reference:
124+
125+
https://github.com/foundry-rs/foundry/blob/6983a938580a1eb25d9dbd61eb8cad8cd137a86d/crates/config/README.md#foundrytoml
126+
127+
Args:
128+
file_or_dir (str): path to the target
129+
130+
Returns:
131+
Optional[Path]: path to the project root, if found
132+
"""
133+
134+
target = Path(file_or_dir).resolve()
135+
136+
# if the target is a directory, see if it has a foundry config
137+
if target.is_dir() and (target / "foundry.toml").is_file():
138+
return target
139+
140+
# if the target is a file, it might be a specific contract
141+
# within a foundry project. Look in parent directories for a
142+
# config file
143+
for p in target.parents:
144+
if (p / "foundry.toml").is_file():
145+
return p
146+
147+
return None
103148

104149
@staticmethod
105150
def is_supported(target: str, **kwargs: str) -> bool:
@@ -115,10 +160,10 @@ def is_supported(target: str, **kwargs: str) -> bool:
115160
if kwargs.get("foundry_ignore", False):
116161
return False
117162

118-
return os.path.isfile(os.path.join(target, "foundry.toml"))
163+
return Foundry.locate_project_root(target) is not None
119164

120165
@staticmethod
121-
def config(working_dir: str) -> Optional[PlatformConfig]:
166+
def config(working_dir: Union[str, Path]) -> Optional[PlatformConfig]:
122167
"""Return configuration data that should be passed to solc, such as remappings.
123168
124169
Args:

0 commit comments

Comments
 (0)