22Foundry platform
33"""
44import logging
5- import os
65import subprocess
76from pathlib import Path
8- from typing import TYPE_CHECKING , List , Optional , TypeVar
7+ from typing import TYPE_CHECKING , List , Optional , TypeVar , Union
98
109import 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