diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d11bc..0c4b144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Removed debug print statements in `pybricksdev.compile.compile_multi_file()`. +- Fixed `__init__.py` files in packages not being included when compiling multi-file projects ([pybricksdev#131]). + +[pybricksdev#131]: https://github.com/pybricks/pybricksdev/issues/131 + ## [2.3.1] - 2026-01-18 ### Changed diff --git a/pybricksdev/compile.py b/pybricksdev/compile.py index 339f086..78eaf5f 100644 --- a/pybricksdev/compile.py +++ b/pybricksdev/compile.py @@ -180,10 +180,20 @@ async def _compile_module_and_get_imports( """ with TemporaryDirectory() as temp_dir: module_path = os.path.join(*module_name.split(".")) + ".py" + package_path = os.path.join(*module_name.split("."), "__init__.py") # TODO: check for pre-compiled .mpy file first? - mpy = await compile_file(proj_dir, module_path, abi) + if os.path.exists(os.path.join(proj_dir, module_path)): + src_path = module_path + elif os.path.exists(os.path.join(proj_dir, package_path)): + src_path = package_path + else: + raise FileNotFoundError( + f"Module {module_name} not found. Searched for {module_path} and {package_path}." + ) + + mpy = await compile_file(proj_dir, src_path, abi) mpy_path = os.path.join(temp_dir, TMP_MPY_SCRIPT) @@ -251,9 +261,6 @@ async def _compile_multi_file_with_mpy_tool( parts.append(name.encode() + b"\x00") parts.append(mpy) - print(imported_modules) - print(not_found_modules) - return b"".join(parts) diff --git a/tests/test_compile.py b/tests/test_compile.py index 95efa35..8740fc9 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: MIT -# Copyright (c) 2022 The Pybricks Authors +# Copyright (c) 2022-2026 The Pybricks Authors import contextlib @@ -7,6 +7,7 @@ import struct import sys from tempfile import TemporaryDirectory +from typing import Any import pytest @@ -16,18 +17,18 @@ if sys.version_info < (3, 11): from contextlib import AbstractContextManager - class chdir(AbstractContextManager): + class chdir(AbstractContextManager[None]): """Non thread-safe context manager to change the current working directory.""" - def __init__(self, path): + def __init__(self, path: str) -> None: self.path = path - self._old_cwd = [] + self._old_cwd: list[str] = [] - def __enter__(self): + def __enter__(self) -> None: self._old_cwd.append(os.getcwd()) os.chdir(self.path) - def __exit__(self, *excinfo): + def __exit__(self, *excinfo: Any) -> None: os.chdir(self._old_cwd.pop()) setattr(contextlib, "chdir", chdir) @@ -73,6 +74,8 @@ async def test_compile_multi_file(abi: int): "import test1\n", "from test2 import thing2\n", "from nested.test3 import thing3\n", + "from test4 import thing4\n", + "from nested.test5 import thing5\n", ] ) @@ -100,6 +103,24 @@ async def test_compile_multi_file(abi: int): ) as f3: f3.write("thing3 = 'thing3'\n") + # test4 and test5 are to test package modules with non-empty __init__.py + + os.mkdir("test4") + + with open( + os.path.join(temp_dir, "test4", "__init__.py"), "w", encoding="utf-8" + ) as f4: + f4.write("thing4 = 'thing4'\n") + + os.mkdir(os.path.join("nested", "test5")) + + with open( + os.path.join(temp_dir, "nested", "test5", "__init__.py"), + "w", + encoding="utf-8", + ) as f5: + f5.write("thing5 = 'thing5'\n") + multi_mpy = await compile_multi_file("test.py", abi) pos = 0 @@ -130,13 +151,21 @@ def unpack_mpy(data: bytes) -> tuple[bytes, bytes]: names.add(name2.decode()) name3, mpy3 = unpack_mpy(multi_mpy) names.add(name3.decode()) + if uses_module_finder: # ModuleFinder requires __init__.py. name4, mpy4 = unpack_mpy(multi_mpy) names.add(name4.decode()) + name5, mpy5 = unpack_mpy(multi_mpy) names.add(name5.decode()) + name6, mpy6 = unpack_mpy(multi_mpy) + names.add(name6.decode()) + + name7, mpy7 = unpack_mpy(multi_mpy) + names.add(name7.decode()) + assert pos == len(multi_mpy) # It is important that the main module is first. @@ -150,9 +179,9 @@ def unpack_mpy(data: bytes) -> tuple[bytes, bytes]: assert "nested.test3" in names if uses_module_finder: - assert len(names) == 4 + assert len(names) == 6 else: - assert len(names) == 3 + assert len(names) == 5 def check_mpy(mpy: bytes) -> None: magic, abi_ver, flags, int_bits = struct.unpack_from(" None: if uses_module_finder: check_mpy(mpy4) # pyright: ignore[reportPossiblyUnboundVariable] check_mpy(mpy5) + check_mpy(mpy6) + check_mpy(mpy7)