Skip to content

Commit

Permalink
feat: search path resolution for cli (#3694)
Browse files Browse the repository at this point in the history
the current behavior is that the current directory does *not* get into
the search path when `-p` is specified, which is annoying. (one would
expect `vyper some/directory/some/file.vy` to compile no matter what
`-p` is specified as).

this commit also handles the addition of multiple search paths specified
on the CLI, and adds a long `--path` option as an alternative to `-p`.
  • Loading branch information
charles-cooper authored Dec 16, 2023
1 parent 0cbc94d commit b0ea5b6
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 31 deletions.
36 changes: 24 additions & 12 deletions tests/unit/cli/vyper_compile/test_compile_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest

from tests.utils import working_directory
from vyper.cli.vyper_compile import compile_files


Expand All @@ -19,15 +20,15 @@ def test_combined_json_keys(tmp_path, make_file):
"userdoc",
"devdoc",
}
compile_data = compile_files(["bar.vy"], ["combined_json"], root_folder=tmp_path)
compile_data = compile_files(["bar.vy"], ["combined_json"], paths=[tmp_path])

assert set(compile_data.keys()) == {Path("bar.vy"), "version"}
assert set(compile_data[Path("bar.vy")].keys()) == combined_keys


def test_invalid_root_path():
with pytest.raises(FileNotFoundError):
compile_files([], [], root_folder="path/that/does/not/exist")
compile_files([], [], paths=["path/that/does/not/exist"])


CONTRACT_CODE = """
Expand Down Expand Up @@ -74,7 +75,7 @@ def test_import_same_folder(import_stmt, alias, tmp_path, make_file):
make_file("contracts/foo.vy", CONTRACT_CODE.format(import_stmt=import_stmt, alias=alias))
make_file("contracts/IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
assert compile_files([foo], ["combined_json"], paths=[tmp_path])


SUBFOLDER_IMPORT_STMT = [
Expand All @@ -98,7 +99,7 @@ def test_import_subfolder(import_stmt, alias, tmp_path, make_file):
)
make_file("contracts/other/IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
assert compile_files([foo], ["combined_json"], paths=[tmp_path])


OTHER_FOLDER_IMPORT_STMT = [
Expand All @@ -115,7 +116,7 @@ def test_import_other_folder(import_stmt, alias, tmp_path, make_file):
foo = make_file("contracts/foo.vy", CONTRACT_CODE.format(import_stmt=import_stmt, alias=alias))
make_file("interfaces/IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
assert compile_files([foo], ["combined_json"], paths=[tmp_path])


def test_import_parent_folder(tmp_path, make_file):
Expand All @@ -125,10 +126,21 @@ def test_import_parent_folder(tmp_path, make_file):
)
make_file("IFoo.vyi", INTERFACE_CODE)

assert compile_files([foo], ["combined_json"], root_folder=tmp_path)
assert compile_files([foo], ["combined_json"], paths=[tmp_path])

# perform relative import outside of base folder
compile_files([foo], ["combined_json"], root_folder=tmp_path / "contracts")
compile_files([foo], ["combined_json"], paths=[tmp_path / "contracts"])


def test_import_search_paths(tmp_path, make_file):
with working_directory(tmp_path):
contract_code = CONTRACT_CODE.format(import_stmt="from utils import IFoo", alias="IFoo")
contract_filename = "dir1/baz/foo.vy"
interface_filename = "dir2/utils/IFoo.vyi"
make_file(interface_filename, INTERFACE_CODE)
make_file(contract_filename, contract_code)

assert compile_files([contract_filename], ["combined_json"], paths=["dir2"])


META_IMPORT_STMT = [
Expand Down Expand Up @@ -167,7 +179,7 @@ def be_known() -> ISelf.FooStruct:
make_file("contracts/ISelf.vyi", interface_code)
meta = make_file("contracts/Self.vy", code)

assert compile_files([meta], ["combined_json"], root_folder=tmp_path)
assert compile_files([meta], ["combined_json"], paths=[tmp_path])


# implement IFoo in another contract for fun
Expand All @@ -187,7 +199,7 @@ def bar(_foo: address) -> {alias}.FooStruct:
make_file("contracts/IFoo.vyi", INTERFACE_CODE)
baz = make_file("contracts/Baz.vy", baz_code)

assert compile_files([baz], ["combined_json"], root_folder=tmp_path)
assert compile_files([baz], ["combined_json"], paths=[tmp_path])


def test_local_namespace(make_file, tmp_path):
Expand Down Expand Up @@ -215,15 +227,15 @@ def test_local_namespace(make_file, tmp_path):
for file_name in ("foo.vyi", "bar.vyi"):
make_file(file_name, INTERFACE_CODE)

assert compile_files(paths, ["combined_json"], root_folder=tmp_path)
assert compile_files(paths, ["combined_json"], paths=[tmp_path])


def test_compile_outside_root_path(tmp_path, make_file):
# absolute paths relative to "."
make_file("ifoo.vyi", INTERFACE_CODE)
foo = make_file("foo.vy", CONTRACT_CODE.format(import_stmt="import ifoo as IFoo", alias="IFoo"))

assert compile_files([foo], ["combined_json"], root_folder=".")
assert compile_files([foo], ["combined_json"], paths=None)


def test_import_library(tmp_path, make_file):
Expand All @@ -244,4 +256,4 @@ def foo() -> uint256:
make_file("lib.vy", library_source)
contract_file = make_file("contract.vy", contract_source)

assert compile_files([contract_file], ["combined_json"], root_folder=tmp_path) is not None
assert compile_files([contract_file], ["combined_json"], paths=[tmp_path]) is not None
13 changes: 1 addition & 12 deletions tests/unit/compiler/test_input_bundle.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import contextlib
import json
import os
from pathlib import Path, PurePath

import pytest

from tests.utils import working_directory
from vyper.compiler.input_bundle import ABIInput, FileInput, FilesystemInputBundle, JSONInputBundle


Expand Down Expand Up @@ -83,16 +82,6 @@ def test_load_abi(make_file, input_bundle, tmp_path):
assert file == ABIInput(1, "foo.txt", path, "some string")


@contextlib.contextmanager
def working_directory(directory):
tmp = os.getcwd()
try:
os.chdir(directory)
yield
finally:
os.chdir(tmp)


# check that unique paths give unique source ids
def test_source_id_file_input(make_file, input_bundle, tmp_path):
foopath = make_file("foo.vy", "contents")
Expand Down
12 changes: 12 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import contextlib
import os


@contextlib.contextmanager
def working_directory(directory):
tmp = os.getcwd()
try:
os.chdir(directory)
yield
finally:
os.chdir(tmp)
19 changes: 12 additions & 7 deletions vyper/cli/vyper_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def _parse_args(argv):
)
parser.add_argument("--hex-ir", action="store_true")
parser.add_argument(
"-p", help="Set the root path for contract imports", default=".", dest="root_folder"
"--path", "-p", help="Set the root path for contract imports", action="append", dest="paths"
)
parser.add_argument("-o", help="Set the output path", dest="output_path")
parser.add_argument(
Expand Down Expand Up @@ -190,7 +190,7 @@ def _parse_args(argv):
compiled = compile_files(
args.input_files,
output_formats,
args.root_folder,
args.paths,
args.show_gas_estimates,
settings,
args.storage_layout,
Expand Down Expand Up @@ -228,18 +228,23 @@ def exc_handler(contract_path: ContractPath, exception: Exception) -> None:
def compile_files(
input_files: list[str],
output_formats: OutputFormats,
root_folder: str = ".",
paths: list[str] = None,
show_gas_estimates: bool = False,
settings: Optional[Settings] = None,
storage_layout_paths: list[str] = None,
no_bytecode_metadata: bool = False,
experimental_codegen: bool = False,
) -> dict:
root_path = Path(root_folder).resolve()
if not root_path.exists():
raise FileNotFoundError(f"Invalid root path - '{root_path.as_posix()}' does not exist")
paths = paths or []

input_bundle = FilesystemInputBundle([root_path])
# lowest precedence search path is always `.`
search_paths = [Path(".")]

for p in paths:
path = Path(p).resolve(strict=True)
search_paths.append(path)

input_bundle = FilesystemInputBundle(search_paths)

show_version = False
if "combined_json" in output_formats:
Expand Down

0 comments on commit b0ea5b6

Please sign in to comment.