diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 187cd46..1b799e3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,14 +26,11 @@ jobs: python -m pip install --upgrade pip pip install .[lint] - - name: Run Black - run: black --check . + - name: Run ruff format + run: ruff format --check . - - name: Run isort - run: isort --check-only . - - - name: Run flake8 - run: flake8 . + - name: Run ruff check + run: ruff check . - name: Run mdformat run: mdformat . --check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58166e7..330b247 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,22 +4,12 @@ repos: hooks: - id: check-yaml -- repo: https://github.com/PyCQA/isort - rev: 6.0.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.10 hooks: - - id: isort - -- repo: https://github.com/psf/black - rev: 25.1.0 - hooks: - - id: black - name: black - -- repo: https://github.com/pycqa/flake8 - rev: 7.1.2 - hooks: - - id: flake8 - additional_dependencies: [flake8-breakpoint, flake8-print, flake8-pydantic, flake8-type-checking] + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.15.0 diff --git a/ape_solidity/compiler.py b/ape_solidity/compiler.py index ef792b6..5ff03d3 100644 --- a/ape_solidity/compiler.py +++ b/ape_solidity/compiler.py @@ -301,7 +301,10 @@ def get_import_remapping(self, project: Optional[ProjectManager] = None) -> dict return remapping def get_compiler_settings( - self, contract_filepaths: Iterable[Path], project: Optional[ProjectManager] = None, **kwargs + self, + contract_filepaths: Iterable[Path], + project: Optional[ProjectManager] = None, + **kwargs, ) -> dict[Version, dict]: pm = project or self.local_project paths = _validate_can_compile(contract_filepaths) @@ -341,9 +344,15 @@ def _get_settings_from_version_map( settings: dict = {} for solc_version, sources in version_map.items(): version_settings: dict[str, Union[Any, list[Any]]] = { - "optimizer": {"enabled": config.optimize, "runs": config.optimization_runs}, + "optimizer": { + "enabled": config.optimize, + "runs": config.optimization_runs, + }, "outputSelection": { - str(get_relative_path(p, pm.path)): {"*": OUTPUT_SELECTION, "": ["ast"]} + str(get_relative_path(p, pm.path)): { + "*": OUTPUT_SELECTION, + "": ["ast"], + } for p in sorted(sources) }, **kwargs, @@ -415,18 +424,10 @@ def get_standard_input_json_from_settings( if solc_version >= Version("0.6.9"): arguments["base_path"] = pm.path - vers_settings["outputSelection"] = { - k: v for k, v in vers_settings["outputSelection"].items() if (pm.path / k).is_file() - } - if missing_sources := [ x for x in vers_settings["outputSelection"] if not (pm.path / x).is_file() ]: - # See if the missing sources are from dependencies (they likely are) - # and cater the error message accordingly. if dependencies_needed := [x for x in missing_sources if str(x).startswith("@")]: - # Missing dependencies. Should only get here if dependencies are found - # in import-strs but are not installed (not in project or globally). missing_str = ", ".join(dependencies_needed) raise CompilerError( f"Missing required dependencies '{missing_str}'. " @@ -434,11 +435,11 @@ def get_standard_input_json_from_settings( "in an ape-config.yaml or using the `ape pm install` command." ) - # Otherwise, we are missing project-level source files for some reason. - # This would only happen if the user passes in unexpected files outside - # of core. missing_src_str = ", ".join(missing_sources) - raise CompilerError(f"Sources '{missing_src_str}' not found in '{pm.name}'.") + raise CompilerError( + f"Sources '{missing_src_str}' not found in '{pm.name}' " + f"(project path: {pm.path})." + ) sources = { x: {"content": (pm.path / x).read_text(encoding="utf8")} @@ -461,8 +462,8 @@ def compile( ) -> Iterator[ContractType]: pm = project or self.local_project settings = settings or {} - paths = [p for p in contract_filepaths] # Handles generator. - source_ids = [f"{get_relative_path(p.absolute(), pm.path)}" for p in paths] + paths = [(p if Path(p).is_absolute() else pm.path / p) for p in contract_filepaths] + source_ids = [f"{get_relative_path(Path(p).absolute(), pm.path)}" for p in paths] _validate_can_compile(paths) # Compile in an isolated env so the .cache folder does not interfere with anything. @@ -500,7 +501,10 @@ def _compile( logger.info(log_str) cleaned_version = Version(solc_version.base_version) solc_binary = get_executable(version=cleaned_version) - arguments: dict = {"solc_binary": solc_binary, "solc_version": cleaned_version} + arguments: dict = { + "solc_binary": solc_binary, + "solc_version": cleaned_version, + } if solc_version >= Version("0.6.9"): arguments["base_path"] = pm.path diff --git a/pyproject.toml b/pyproject.toml index 8991fa8..8beb45f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,16 +9,6 @@ plugins = ["pydantic.mypy"] [tool.setuptools_scm] write_to = "ape_solidity/version.py" -# NOTE: you have to use single-quoted strings in TOML for regular expressions. -# It's the equivalent of r-strings in Python. Multiline strings are treated as -# verbose regular expressions by Black. Use [ ] to denote a significant space -# character. - -[tool.black] -line-length = 100 -target-version = ['py39', 'py310', 'py311', 'py312', 'py313'] -include = '\.pyi?$' - [tool.pytest.ini_options] addopts = """ -p no:ape_test @@ -35,12 +25,21 @@ fuzzing: Run Hypothesis fuzz test suite install: Tests that will install a solc version (slow) """ -[tool.isort] -line_length = 100 -force_grid_wrap = 0 -include_trailing_comma = true -multi_line_output = 3 -use_parentheses = true +[tool.ruff] +target-version = "py310" +line-length = 100 + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.isort] +known-first-party = ["ape_solidity"] + +[tool.ruff.format] +quote-style = "double" +line-ending = "auto" +indent-style = "space" +docstring-code-format = true [tool.mdformat] number = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0bcc87f..0000000 --- a/setup.cfg +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -max-line-length = 100 -ignore = E704,W503,PYD002,TC003,TC006 -exclude = - *.venv* - venv* - docs - build - tests/node_modules -type-checking-pydantic-enabled = True diff --git a/setup.py b/setup.py index abc7899..258e350 100644 --- a/setup.py +++ b/setup.py @@ -12,14 +12,10 @@ "pytest-benchmark", # For performance tests ], "lint": [ - "black>=25.1.0,<26", # Auto-formatter and linter + "ruff>=0.14.10", # Linter and auto-formatter "mypy>=1.15.0,<2", # Static type analyzer "types-requests", # Needed for mypy type shed "types-setuptools", # Needed for mypy type shed - "flake8>=7.1.2,<8", # Style linter - "flake8-pydantic", # For detecting issues with Pydantic models - "flake8-type-checking", # Detect imports to move in/out of type-checking blocks - "isort>=5.13.2,<6", # Import sorting linter "mdformat>=0.7.22", # Auto-formatter for markdown "mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown "mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates diff --git a/tests/test_compiler.py b/tests/test_compiler.py index c0e3c9f..f3617ac 100644 --- a/tests/test_compiler.py +++ b/tests/test_compiler.py @@ -432,7 +432,10 @@ def test_get_version_map_importing_more_constrained_version(project, compiler): actual = compiler.get_version_map((path,), project=project) expected_version = Version("0.8.12+commit.f00d7308") - expected_sources = ("ImportSourceWithEqualSignVersion", "SpecificVersionWithEqualSign") + expected_sources = ( + "ImportSourceWithEqualSignVersion", + "SpecificVersionWithEqualSign", + ) assert expected_version in actual actual_ids = [x.stem for x in actual[expected_version]] @@ -1070,3 +1073,19 @@ def test_compile_code(project, compiler): assert actual.ast is not None assert len(actual.runtime_bytecode.bytecode) > 0 assert len(actual.deployment_bytecode.bytecode) > 0 + + +def test_compile_relative_path_from_other_cwd(project, compiler, tmp_path, monkeypatch): + # Regression: a relative source path must be resolved against the + # project root, not the current working directory. + monkeypatch.chdir(tmp_path) + actual = [c for c in compiler.compile(("contracts/CompilesOnce.sol",), project=project)] + assert len(actual) == 1 + assert actual[0].name == "CompilesOnce" + assert actual[0].source_id == "contracts/CompilesOnce.sol" + + +def test_compile_missing_source_raises_clear_error(project, compiler, monkeypatch): + monkeypatch.chdir(project.path) + with pytest.raises(CompilerError, match=r"not found in .*project path:"): + list(compiler.compile(("contracts/DoesNotExist.sol",), project=project))