From 39a07964f552f4c3ddfeefab2b9dd94a291810f5 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Mon, 26 Jan 2026 18:35:05 -0800 Subject: [PATCH] chore: satisfy linting concerns from ruff - a step toward resolving #138 --- circuitpython_build_tools/build.py | 187 +++++++++++------- .../scripts/build_bundles.py | 55 +++--- .../scripts/build_mpy_cross.py | 5 +- .../scripts/circuitpython_mpy_cross.py | 2 +- ruff.toml | 2 +- 5 files changed, 148 insertions(+), 103 deletions(-) diff --git a/circuitpython_build_tools/build.py b/circuitpython_build_tools/build.py index 260fa60..53bf648 100644 --- a/circuitpython_build_tools/build.py +++ b/circuitpython_build_tools/build.py @@ -10,17 +10,19 @@ import multiprocessing import os import os.path -import platform import pathlib +import platform import re -import requests -import semver import shutil import stat -import sys import subprocess +import sys import tempfile +from typing import Optional + import platformdirs +import requests +import semver @functools.cache @@ -89,15 +91,19 @@ def version_string(path=None, *, valid_semver=False): tag = subprocess.run( "git describe --tags --exact-match", shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, cwd=path, + check=False, # error handled below ) if tag.returncode == 0: version = tag.stdout.strip().decode("utf-8", "strict") else: describe = subprocess.run( - "git describe --tags --always", shell=True, stdout=subprocess.PIPE, cwd=path + "git describe --tags --always", + shell=True, + stdout=subprocess.PIPE, + cwd=path, + check=True, # Let exception propagate an error from git ) describe = describe.stdout.strip().decode("utf-8", "strict").rsplit("-", maxsplit=2) if len(describe) == 3: @@ -106,7 +112,11 @@ def version_string(path=None, *, valid_semver=False): else: tag = "0.0.0" commit_count = subprocess.run( - "git rev-list --count HEAD", shell=True, stdout=subprocess.PIPE, cwd=path + "git rev-list --count HEAD", + shell=True, + stdout=subprocess.PIPE, + cwd=path, + check=True, # Let exception propagate an error from git ) additional_commits = commit_count.stdout.strip().decode("utf-8", "strict") commitish = describe[0] @@ -135,17 +145,21 @@ def mpy_cross(version, quiet=False): # Try to pull from S3 uname = platform.uname() s3_url = None - if uname[0].title() == "Linux" and uname[4].lower() in ("amd64", "x86_64"): + if uname[0].title() == "Linux" and uname[4].lower() in {"amd64", "x86_64"}: s3_url = f"{S3_MPY_PREFIX}/linux-amd64/mpy-cross-linux-amd64-{circuitpython_tag}.static" elif uname[0].title() == "Linux" and uname[4].lower() == "armv7l": - s3_url = f"{S3_MPY_PREFIX}/linux-raspbian/mpy-cross-linux-raspbian-{circuitpython_tag}.static-raspbian" + s3_url = ( + f"{S3_MPY_PREFIX}/linux-raspbian/mpy-cross-linux-raspbian-{circuitpython_tag}." + "static-raspbian" + ) elif uname[0].title() == "Darwin": s3_url = f"{S3_MPY_PREFIX}/macos/mpy-cross-macos-{circuitpython_tag}-universal" - elif uname[0].title() == "Windows" and uname[4].lower() in ("amd64", "x86_64"): + elif uname[0].title() == "Windows" and uname[4].lower() in {"amd64", "x86_64"}: s3_url = f"{S3_MPY_PREFIX}/windows/mpy-cross-windows-{circuitpython_tag}.static.exe" elif not quiet: print( - f"Pre-built mpy-cross not available for sysname='{uname[0]}' release='{uname[2]}' machine='{uname[4]}'." + "Pre-built mpy-cross not available for", + f"sysname='{uname[0]}' release='{uname[2]}' machine='{uname[4]}'.", ) if s3_url is not None: @@ -201,13 +215,13 @@ def mpy_cross(version, quiet=False): def _munge_to_temp(original_path, temp_file, library_version): - with open(original_path, "r", encoding="utf-8") as original_file: + with open(original_path, encoding="utf-8") as original_file: for line in original_file: - line = line.strip("\n") - if line.startswith("__version__"): - line = line.replace("0.0.0-auto.0", library_version) - line = line.replace("0.0.0+auto.0", library_version) - print(line, file=temp_file) + ln = line.strip("\n") + if ln.startswith("__version__"): + ln = ln.replace("0.0.0-auto.0", library_version) + ln = ln.replace("0.0.0+auto.0", library_version) + print(ln, file=temp_file) temp_file.flush() @@ -230,7 +244,8 @@ def get_package_info(library_path, package_folder_prefix): if blocklisted: print( - f"{lib_path}/settings.toml:1: {blocklisted[0]} blocklisted: not using metadata from pyproject.toml" + f"{lib_path}/settings.toml:1: {blocklisted[0]}", + "blocklisted: not using metadata from pyproject.toml", ) py_modules = packages = () @@ -241,7 +256,7 @@ def get_package_info(library_path, package_folder_prefix): if packages and py_modules: raise ValueError("Cannot specify both tool.setuptools.py-modules and .packages") - elif packages: + if packages: if len(packages) > 1: raise ValueError("Only a single package is supported") package_name = packages[0] @@ -264,28 +279,16 @@ def get_package_info(library_path, package_folder_prefix): else: print(f"{lib_path}: Using legacy autodetection") - package_info["is_package"] = False - for file in glob_search: - if file.parts[parent_idx] != "examples": - if len(file.parts) > parent_idx + 1: - for prefix in package_folder_prefix: - if file.parts[parent_idx].startswith(prefix): - package_info["is_package"] = True - if package_info["is_package"]: - package_files.append(file) - else: - if file.name in IGNORE_PY: - # print("Ignoring:", file.resolve()) - continue - if file.parent == lib_path: - py_files.append(file) - - if package_files: - package_info["module_name"] = package_files[0].relative_to(library_path).parent.name - elif py_files: - package_info["module_name"] = py_files[0].relative_to(library_path).name[:-3] - else: - package_info["module_name"] = None + _detect_legacy_package_structure( + package_info, + package_files, + py_files, + glob_search, + parent_idx, + package_folder_prefix, + lib_path, + library_path, + ) if len(py_files) > 1: raise ValueError( @@ -307,6 +310,40 @@ def get_package_info(library_path, package_folder_prefix): return package_info +def _detect_legacy_package_structure( + package_info: dict, + package_files: list[pathlib.Path], + py_files: list[pathlib.Path], + glob_search: list[pathlib.Path], + parent_idx: int, + package_folder_prefix: str, + lib_path: pathlib.Path, + library_path: str, +) -> None: + package_info["is_package"] = False + for file in glob_search: + if file.parts[parent_idx] != "examples": + if len(file.parts) > parent_idx + 1: + for prefix in package_folder_prefix: + if file.parts[parent_idx].startswith(prefix): + package_info["is_package"] = True + if package_info["is_package"]: + package_files.append(file) + else: + if file.name in IGNORE_PY: + # print("Ignoring:", file.resolve()) + continue + if file.parent == lib_path: + py_files.append(file) + + if package_files: + package_info["module_name"] = package_files[0].relative_to(library_path).parent.name + elif py_files: + package_info["module_name"] = py_files[0].relative_to(library_path).name[:-3] + else: + package_info["module_name"] = None + + def library( library_path, output_directory, package_folder_prefix, mpy_cross=None, example_bundle=False ): @@ -327,33 +364,9 @@ def library( for filename in py_package_files: full_path = os.path.join(library_path, filename) output_file = output_directory / filename.relative_to(library_path) - if filename.suffix == ".py": - with tempfile.NamedTemporaryFile(delete=False, mode="w+") as temp_file: - temp_file_name = temp_file.name - try: - _munge_to_temp(full_path, temp_file, library_version) - temp_file.close() - if mpy_cross and os.stat(temp_file.name).st_size != 0: - output_file = output_file.with_suffix(".mpy") - mpy_success = subprocess.call( - [ - mpy_cross, - "-o", - output_file, - "-s", - str(filename.relative_to(library_path)), - temp_file.name, - ] - ) - if mpy_success != 0: - raise RuntimeError("mpy-cross failed on", full_path) - else: - shutil.copyfile(temp_file_name, output_file) - finally: - os.remove(temp_file_name) - else: - shutil.copyfile(full_path, output_file) - + _run_mpy_cross_on_mod( + filename, full_path, output_file, mpy_cross, library_path, library_version + ) requirements_files = lib_path.glob("requirements.txt*") requirements_files = [f for f in requirements_files if f.stat().st_size > 0] @@ -383,3 +396,39 @@ def library( os.makedirs(os.path.join(*output_file.split(os.path.sep)[:-1]), exist_ok=True) shutil.copyfile(full_path, output_file) + + +def _run_mpy_cross_on_mod( + filename: pathlib.Path, + full_path: str, + output_file: str, + mpy_cross: pathlib.Path | None, + library_path: str, + library_version: str, +) -> None: + if filename.suffix == ".py": + with tempfile.NamedTemporaryFile(delete=False, mode="w+") as temp_file: + temp_file_name = temp_file.name + try: + _munge_to_temp(full_path, temp_file, library_version) + temp_file.close() + if mpy_cross and os.stat(temp_file.name).st_size != 0: + output_file = output_file.with_suffix(".mpy") + mpy_success = subprocess.call( + [ + mpy_cross, + "-o", + output_file, + "-s", + str(filename.relative_to(library_path)), + temp_file.name, + ] + ) + if mpy_success != 0: + raise RuntimeError("mpy-cross failed on", full_path) + else: + shutil.copyfile(temp_file_name, output_file) + finally: + os.remove(temp_file_name) + else: + shutil.copyfile(full_path, output_file) diff --git a/circuitpython_build_tools/scripts/build_bundles.py b/circuitpython_build_tools/scripts/build_bundles.py index 0ece0a2..cb09671 100755 --- a/circuitpython_build_tools/scripts/build_bundles.py +++ b/circuitpython_build_tools/scripts/build_bundles.py @@ -4,6 +4,7 @@ # # SPDX-License-Identifier: MIT +import importlib.metadata as importlib_metadata import json import os import os.path @@ -15,13 +16,7 @@ import click -from circuitpython_build_tools import build -from circuitpython_build_tools import target_versions - -if sys.version_info < (3, 8): - import importlib_metadata -else: - import importlib.metadata as importlib_metadata +from circuitpython_build_tools import build, target_versions BLINKA_LIBRARIES = [ "adafruit-blinka", @@ -56,7 +51,11 @@ def add_file(bundle, src_file, zip_name): def get_module_name(library_path, remote_name): """Figure out the module or package name and return it""" repo = subprocess.run( - f"git remote get-url {remote_name}", shell=True, stdout=subprocess.PIPE, cwd=library_path + f"git remote get-url {remote_name}", + shell=True, + stdout=subprocess.PIPE, + cwd=library_path, + check=True, # Let exception propagate an error from git ) repo = repo.stdout.decode("utf-8", errors="ignore").strip().lower() if repo[-4:] == ".git": @@ -80,22 +79,22 @@ def get_bundle_requirements(directory, package_list): path = directory + "/requirements.txt" if os.path.exists(path): - with open(path, "r") as file: + with open(path) as file: requirements = file.read() file.close() for line in requirements.split("\n"): - line = line.lower().strip() - if line.startswith("#") or line == "": + pkg = line.lower().strip() + if pkg.startswith("#") or not pkg: # skip comments pass else: # Remove any pip version and platform specifiers - original_name = re.split("[<>=~[;]", line)[0].strip() + original_name = re.split("[<>=~[;]", pkg)[0].strip() # Normalize to match the indexes in package_list - line = normalize_dist_name(original_name) - if line in package_list: - dependencies.add(package_list[line]["module_name"]) - elif line not in BLINKA_LIBRARIES: + pkg = normalize_dist_name(original_name) + if pkg in package_list: + dependencies.add(package_list[pkg]["module_name"]) + elif pkg not in BLINKA_LIBRARIES: # add with the exact spelling from requirements.txt pypi_reqs.add(original_name) return sorted(dependencies), sorted(pypi_reqs) @@ -193,10 +192,12 @@ def build_bundle( with open(os.path.join(build_dir, top_folder, "VERSIONS.txt"), "w") as f: f.write(bundle_version + "\r\n") versions = subprocess.run( - f'git submodule --quiet foreach "git remote get-url {remote_name} && git describe --tags"', + f'git submodule --quiet foreach "git remote get-url {remote_name} && git describe ' + '--tags"', shell=True, stdout=subprocess.PIPE, cwd=os.path.commonpath(libs), + check=False, # Error handling done below ) if versions.returncode != 0: print( @@ -270,7 +271,8 @@ def _find_libraries(current_path, depth): @click.option( "--library_depth", default=0, - help="Depth of library folders. This is useful when multiple libraries are bundled together but are initially in separate subfolders.", + help="Depth of library folders. This is useful when multiple libraries are bundled together but" + " are initially in separate subfolders.", ) @click.option( "--package_folder_prefix", @@ -307,11 +309,11 @@ def build_bundles( libs = _find_libraries(os.path.abspath(library_location), library_depth) try: - build_tools_version = importlib_metadata.version("circuitpython-build-tools") + build_tools_version: str = importlib_metadata.version("circuitpython-build-tools") except importlib_metadata.PackageNotFoundError: build_tools_version = "devel" - build_tools_fn = "z-build_tools_version-{}.ignore".format(build_tools_version) + build_tools_fn = f"z-build_tools_version-{build_tools_version}.ignore" build_tools_fn = os.path.join(output_directory, build_tools_fn) with open(build_tools_fn, "w") as f: f.write(build_tools_version) @@ -323,9 +325,7 @@ def build_bundles( # Build raw source .py bundle if "py" not in ignore: - zip_filename = os.path.join( - output_directory, filename_prefix + "-py-{VERSION}.zip".format(VERSION=bundle_version) - ) + zip_filename = os.path.join(output_directory, f"{filename_prefix}-py-{bundle_version}.zip") build_bundle( libs, bundle_version, @@ -341,8 +341,7 @@ def build_bundles( mpy_cross = build.mpy_cross(version) zip_filename = os.path.join( output_directory, - filename_prefix - + "-{TAG}-mpy-{VERSION}.zip".format(TAG=version["name"], VERSION=bundle_version), + f"{filename_prefix}-{version['name']}-mpy-{bundle_version}.zip", ) build_bundle( libs, @@ -358,7 +357,7 @@ def build_bundles( if "example" not in ignore: zip_filename = os.path.join( output_directory, - filename_prefix + "-examples-{VERSION}.zip".format(VERSION=bundle_version), + f"{filename_prefix}-examples-{bundle_version}.zip", ) build_bundle( libs, @@ -372,9 +371,7 @@ def build_bundles( # Build Bundle JSON if "json" not in ignore: - json_filename = os.path.join( - output_directory, filename_prefix + "-{VERSION}.json".format(VERSION=bundle_version) - ) + json_filename = os.path.join(output_directory, f"{filename_prefix}-{bundle_version}.json") build_bundle_json( libs, bundle_version, json_filename, package_folder_prefix, remote_name=remote_name ) diff --git a/circuitpython_build_tools/scripts/build_mpy_cross.py b/circuitpython_build_tools/scripts/build_mpy_cross.py index 5d56420..b23f672 100644 --- a/circuitpython_build_tools/scripts/build_mpy_cross.py +++ b/circuitpython_build_tools/scripts/build_mpy_cross.py @@ -4,11 +4,10 @@ # # SPDX-License-Identifier: MIT -from circuitpython_build_tools import build -from circuitpython_build_tools import target_versions - import click +from circuitpython_build_tools import build, target_versions + @click.command @click.argument("versions") diff --git a/circuitpython_build_tools/scripts/circuitpython_mpy_cross.py b/circuitpython_build_tools/scripts/circuitpython_mpy_cross.py index feb11a5..11d4afa 100644 --- a/circuitpython_build_tools/scripts/circuitpython_mpy_cross.py +++ b/circuitpython_build_tools/scripts/circuitpython_mpy_cross.py @@ -5,8 +5,8 @@ import click -from ..target_versions import VERSIONS from ..build import mpy_cross +from ..target_versions import VERSIONS @click.command(context_settings={"ignore_unknown_options": True}) diff --git a/ruff.toml b/ruff.toml index 9811947..739c4db 100644 --- a/ruff.toml +++ b/ruff.toml @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -target-version = "py38" +target-version = "py310" line-length = 100 [lint]