From 18a51a95d66836d61dea6474fc44b61317d1c84e Mon Sep 17 00:00:00 2001 From: antazoey Date: Thu, 30 Jan 2025 10:24:51 -0600 Subject: [PATCH] feat: allow customizing and configuring which output formats you want #147 Co-authored-by: antazoey --- README.md | 32 ++++++++++++++++ ape_vyper/compiler/_versions/base.py | 48 +++++++++++++++++------- ape_vyper/compiler/_versions/vyper_04.py | 45 ++++++++++++++++------ tests/functional/test_compiler.py | 13 +++++++ 4 files changed, 113 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index becacf5..64fb4d9 100644 --- a/README.md +++ b/README.md @@ -139,3 +139,35 @@ Ape-Vyper supports Vyper 0.3.10's [new pragma formats](https://github.com/vyperl ```python #pragma optimize codesize ``` + +### VVM CLI + +You can install versions of Vyper using the `ape vyper vvm` CLI tools. +List installed versions using: + +```shell +ape vyper vvm list +``` + +Install more versions using the command: + +```shell +ape vyper vvm install 0.3.7 0.3.10 +``` + +### Custom Output Format + +To customize Vyper's output format (like the native `-f` flag), you can configure the output format: +For example, to only get the ABI, do: + +```shell +vyper: + output_format: + - abi +``` + +To do this using the CLI only (adhoc), use the following command: + +```shell +ape compile --config-override '{"vyper": {"output_format": ["abi"]}}' +``` diff --git a/ape_vyper/compiler/_versions/base.py b/ape_vyper/compiler/_versions/base.py index e14d1f6..3eeb743 100644 --- a/ape_vyper/compiler/_versions/base.py +++ b/ape_vyper/compiler/_versions/base.py @@ -124,13 +124,25 @@ def compile( content = Content.model_validate(src_dict[source_id].get("content", "")) for name, output in output_items.items(): # De-compress source map to get PC POS map. - ast = self._parse_ast(result["sources"][source_id]["ast"], content) - evm = output["evm"] - bytecode = evm["deployedBytecode"] - opcodes = bytecode["opcodes"].split(" ") - compressed_src_map = SourceMap(root=bytecode["sourceMap"]) - src_map = list(compressed_src_map.parse())[1:] - pcmap = self._get_pcmap(vyper_version, ast, src_map, opcodes, bytecode) + if "ast" in result["sources"][source_id]: + ast = self._parse_ast(result["sources"][source_id]["ast"], content) + else: + ast = None + + evm = output.get("evm", {}) + runtime_bytecode = evm.get("deployedBytecode", {}) + opcodes = runtime_bytecode.get("opcodes", "").split(" ") + + if "sourceMap" in runtime_bytecode: + compressed_src_map = SourceMap(root=runtime_bytecode["sourceMap"]) + src_map = list(compressed_src_map.parse())[1:] + pcmap = self._get_pcmap( + vyper_version, ast, src_map, opcodes, runtime_bytecode + ) + else: + compressed_src_map = None + src_map = None + pcmap = None # Find content-specified dev messages. dev_messages = {} @@ -144,18 +156,23 @@ def compile( else: final_source_id = source_id + deployment_bytecode = evm.get("bytecode", {}).get("object") contract_type = ContractType.model_validate( { "ast": ast, "contractName": name, "sourceId": final_source_id, - "deploymentBytecode": {"bytecode": evm["bytecode"]["object"]}, - "runtimeBytecode": {"bytecode": bytecode["object"]}, - "abi": output["abi"], + "deploymentBytecode": ( + {"bytecode": deployment_bytecode} if deployment_bytecode else {} + ), + "runtimeBytecode": ( + {"bytecode": runtime_bytecode["object"]} if runtime_bytecode else {} + ), + "abi": output.get("abi"), "sourcemap": compressed_src_map, "pcmap": pcmap, - "userdoc": output["userdoc"], - "devdoc": output["devdoc"], + "userdoc": output.get("userdoc"), + "devdoc": output.get("devdoc"), "dev_messages": dev_messages, } ) @@ -255,7 +272,12 @@ def _get_selection_dictionary( # Interfaces cannot be in the sources dict for those versions # (whereas in Vyper0.4, they must). pm = project or self.local_project - return {s: ["*"] for s in selection if (pm.path / s).is_file() if "interfaces" not in s} + return { + s: self.output_format + for s in selection + if (pm.path / s).is_file() + if "interfaces" not in s + } def _get_pcmap( self, diff --git a/ape_vyper/compiler/_versions/vyper_04.py b/ape_vyper/compiler/_versions/vyper_04.py index 28bf861..91d8b3d 100644 --- a/ape_vyper/compiler/_versions/vyper_04.py +++ b/ape_vyper/compiler/_versions/vyper_04.py @@ -154,13 +154,22 @@ def compile( for source_id, output_items in result.items(): content = Content(root=src_dict[source_id].read_text(encoding="utf-8")) - # De-compress source map to get PC POS map. - ast_dict = json.loads(output_items["ast"])["ast"] - ast = self._parse_ast(ast_dict, content) - bytecode = output_items["bytecode_runtime"] - source_map = json.loads(output_items["source_map"]) - pcmap = PCMap.model_validate(source_map["pc_pos_map"]) + if "ast" in output_items: + # De-compress source map to get PC POS map. + ast_dict = json.loads(output_items["ast"])["ast"] + ast = self._parse_ast(ast_dict, content) + else: + ast = None + + bytecode = output_items.get("bytecode_runtime") + + if "source_map" in output_items: + source_map = json.loads(output_items["source_map"]) + pcmap = PCMap.model_validate(source_map["pc_pos_map"]) + else: + source_map = None + pcmap = None # Find content-specified dev messages. dev_messages = map_dev_messages(content.root) @@ -176,13 +185,25 @@ def compile( "ast": ast, "contractName": f"{Path(final_source_id).stem}", "sourceId": final_source_id, - "deploymentBytecode": {"bytecode": output_items["bytecode"]}, - "runtimeBytecode": {"bytecode": bytecode}, - "abi": json.loads(output_items["abi"]), - "sourcemap": output_items["source_map"], + "deploymentBytecode": ( + {"bytecode": output_items["bytecode"]} + if "bytecode" in output_items + else {} + ), + "runtimeBytecode": {"bytecode": bytecode} if bytecode else {}, + "abi": json.loads(output_items["abi"]) if "abi" in output_items else None, + "sourcemap": ( + output_items["source_map"] if "source_map" in output_items else None + ), "pcmap": pcmap, - "userdoc": json.loads(output_items["userdoc"]), - "devdoc": json.loads(output_items["devdoc"]), + "userdoc": ( + json.loads(output_items["userdoc"]) + if "userdoc" in output_items + else None + ), + "devdoc": ( + json.loads(output_items["devdoc"]) if "devdoc" in output_items else None + ), "dev_messages": dev_messages, } ) diff --git a/tests/functional/test_compiler.py b/tests/functional/test_compiler.py index 04c26b4..2371fef 100644 --- a/tests/functional/test_compiler.py +++ b/tests/functional/test_compiler.py @@ -833,3 +833,16 @@ def test_get_compiler_settings(project, compiler): "tests/contracts/passing_contracts/zero_four.vy": ["*"] } assert vyper4_settings[v4_version_used]["gas%shanghai"]["evmVersion"] == "shanghai" + + +def test_compile_configured_output_format(project, compiler): + paths = [ + project.contracts_folder / "zero_four.vy", + project.contracts_folder / "non_payable_default.vy", + ] + with project.temp_config(vyper={"output_format": ["abi"]}): + result = list(compiler.compile(paths)) + assert len(result) == 2 + for contract_type in result: + assert contract_type.abi is not None + assert contract_type.runtime_bytecode.bytecode is None