diff --git a/src/ape/managers/project.py b/src/ape/managers/project.py index 5eff5360cc..e6513749b9 100644 --- a/src/ape/managers/project.py +++ b/src/ape/managers/project.py @@ -749,7 +749,32 @@ def compile( self.project.reconfigure(**override) self._cache.cache_api(self.api) - return self.project.load_contracts(use_cache=use_cache) + result = self.project.load_contracts(use_cache=use_cache) + if not result: + contracts_folder = self.project.contracts_folder + message = "Compiling dependency produced no contract types." + if isinstance(self.project, LocalProject): + all_files = [x.name for x in get_all_files_in_directory(contracts_folder)] + has_solidity_sources = any(get_full_extension(Path(x)) == ".sol" for x in all_files) + has_vyper_sources = any( + get_full_extension(Path(x)) in (".vy", ".vyi") for x in all_files + ) + compilers = self.compiler_manager.registered_compilers + warn_sol = has_solidity_sources and ".sol" not in compilers + warn_vyper = has_vyper_sources and ".vy" not in compilers + suffix = "" + if warn_sol: + suffix = "Try installing 'ape-solidity'" + if warn_vyper and warn_sol: + suffix += " or 'ape-vyper'" + elif warn_vyper: + suffix = "Try installing 'ape-vyper'" + if suffix: + message = f"{message} {suffix}." + + logger.warning(message) + + return result def unpack(self, path: Path) -> Iterator["Dependency"]: """ @@ -2331,7 +2356,7 @@ def load_contracts( starting = { n: ContractContainer(ct) for n, ct in (self.manifest.contract_types or {}).items() - if ct.source_id and (self.path / ct.source_id).is_file() + if use_cache and ct.source_id and (self.path / ct.source_id).is_file() } paths = self.sources.paths diff --git a/src/ape_pm/_cli.py b/src/ape_pm/_cli.py index 1c035c2e8b..ec26a6d024 100644 --- a/src/ape_pm/_cli.py +++ b/src/ape_pm/_cli.py @@ -293,15 +293,7 @@ def compile(cli_ctx, name, version, force, config_override): cfg["config_override"] = config_override dependency = pm.dependencies.install(**cfg) - try: - dependency.compile(use_cache=not force) - except Exception as err: - cli_ctx.logger.error(str(err)) - continue - else: - cli_ctx.logger.success( - f"Package '{dependency.name}@{dependency.version}' compiled." - ) + _compile_dependency(cli_ctx, dependency, force) if did_error: sys.exit(1) @@ -321,10 +313,16 @@ def compile(cli_ctx, name, version, force, config_override): if config_override: dependency.api.config_override = config_override - try: - dependency.compile(use_cache=not force) - except Exception as err: - cli_ctx.logger.error(str(err)) - continue - else: + _compile_dependency(cli_ctx, dependency, force) + + +def _compile_dependency(cli_ctx, dependency: Dependency, force: bool): + try: + result = dependency.compile(use_cache=not force) + except Exception as err: + cli_ctx.logger.error(str(err)) + else: + if result: cli_ctx.logger.success(f"Package '{dependency.name}@{dependency.version}' compiled.") + # else: user should have received warning from `dependency.compile()` if there + # was no result. diff --git a/tests/functional/test_dependencies.py b/tests/functional/test_dependencies.py index 80c0d08e70..398edccdac 100644 --- a/tests/functional/test_dependencies.py +++ b/tests/functional/test_dependencies.py @@ -10,6 +10,7 @@ from ape.managers.project import Dependency, LocalProject, PackagesCache, Project, ProjectManager from ape.utils import create_tempdir from ape_pm.dependency import GithubDependency, LocalDependency, NpmDependency +from tests.conftest import skip_if_plugin_installed @pytest.fixture @@ -543,6 +544,35 @@ def test_manifest_path(self, dependency, data_folder): expected = data_folder / "packages" / "manifests" / name / "1_0_0.json" assert actual == expected + def test_compile(self, project): + with create_tempdir() as path: + api = LocalDependency(local=path, name="ooga", version="1.0.0") + dependency = Dependency(api, project) + contract_path = dependency.project.contracts_folder / "CCC.json" + contract_path.write_text( + '[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}]' + ) + result = dependency.compile() + assert len(result) == 1 + assert result["CCC"].name == "CCC" + + @skip_if_plugin_installed("vyper", "solidity") + def test_compile_missing_compilers(self, project, ape_caplog): + with create_tempdir() as path: + api = LocalDependency(local=path, name="ooga2", version="1.1.0") + dependency = Dependency(api, project) + sol_path = dependency.project.contracts_folder / "Sol.sol" + sol_path.write_text("// Sol") + vy_path = dependency.project.contracts_folder / "Vy.vy" + vy_path.write_text("# Vy") + expected = ( + "Compiling dependency produced no contract types. " + "Try installing 'ape-solidity' or 'ape-vyper'." + ) + result = dependency.compile() + assert len(result) == 0 + assert expected in ape_caplog.head + class TestProject: """ diff --git a/tests/integration/cli/projects/with-contracts/ape-config.yaml b/tests/integration/cli/projects/with-contracts/ape-config.yaml index 72c5e8ee4e..b0848b112e 100644 --- a/tests/integration/cli/projects/with-contracts/ape-config.yaml +++ b/tests/integration/cli/projects/with-contracts/ape-config.yaml @@ -11,6 +11,9 @@ dependencies: config_override: contracts_folder: . + - name: depwithunregisteredcontracts + local: ./dep_with_sol_and_vy + test: # `false` because running pytest within pytest. disconnect_providers_after: false diff --git a/tests/integration/cli/projects/with-contracts/dep_with_sol_and_vy/contracts/SolFile.sol b/tests/integration/cli/projects/with-contracts/dep_with_sol_and_vy/contracts/SolFile.sol new file mode 100644 index 0000000000..830b9825af --- /dev/null +++ b/tests/integration/cli/projects/with-contracts/dep_with_sol_and_vy/contracts/SolFile.sol @@ -0,0 +1 @@ +// Solidity file test diff --git a/tests/integration/cli/projects/with-contracts/dep_with_sol_and_vy/contracts/VyFile.vy b/tests/integration/cli/projects/with-contracts/dep_with_sol_and_vy/contracts/VyFile.vy new file mode 100644 index 0000000000..78f465cb99 --- /dev/null +++ b/tests/integration/cli/projects/with-contracts/dep_with_sol_and_vy/contracts/VyFile.vy @@ -0,0 +1 @@ +# Vyper file test diff --git a/tests/integration/cli/test_pm.py b/tests/integration/cli/test_pm.py index 020893d817..de2ec7f89e 100644 --- a/tests/integration/cli/test_pm.py +++ b/tests/integration/cli/test_pm.py @@ -3,7 +3,7 @@ import pytest -from tests.conftest import ApeSubprocessRunner +from tests.conftest import ApeSubprocessRunner, skip_if_plugin_installed from tests.integration.cli.utils import github_xfail, run_once, skip_projects_except EXPECTED_FAIL_MESSAGE = "Unknown package '{}'." @@ -157,6 +157,28 @@ def test_compile_dependency(pm_runner, integ_project): assert result.exit_code == 0, result.output assert f"Package '{name}@local' compiled." in result.output + # Show it can happen more than once. (no --force this time). + result = pm_runner.invoke("compile", name) + assert result.exit_code == 0, result.output + assert f"Package '{name}@local' compiled." in result.output + + +@skip_if_plugin_installed("vyper", "solidity") +@skip_projects_except("with-contracts") +def test_compile_missing_compiler_plugins(pm_runner, integ_project, compilers): + pm_runner.project = integ_project + name = "depwithunregisteredcontracts" + result = pm_runner.invoke("compile", name, "--force") + expected = ( + "Compiling dependency produced no contract types. " + "Try installing 'ape-solidity' or 'ape-vyper'" + ) + assert expected in result.output + + # Also show it happens when installing _all_. + result = pm_runner.invoke("compile", ".", "--force") + assert expected in result.output + @skip_projects_except("only-dependencies") def test_uninstall(pm_runner, integ_project):