From 3261a4cd4b3547054fece4d2fe221cea366d8a16 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sun, 2 Aug 2026 20:54:14 +0800 Subject: [PATCH] feat: finish basic packer - finish basic packer - add basic jinja2 template --- packer/src/sarasacw_omrf_packer/archiver.py | 93 +++++++++++++++++-- packer/src/sarasacw_omrf_packer/cmake.py | 13 --- packer/src/sarasacw_omrf_packer/pkgconfig.py | 13 --- .../src/sarasacw_omrf_packer/renders/cmake.py | 24 +++++ .../sarasacw_omrf_packer/renders/common.py | 22 +++++ .../sarasacw_omrf_packer/renders/pkgconfig.py | 20 ++++ .../templates/{cmake.in => XXX.pc.jinja} | 0 .../templates/XXXConfig.cmake.jinja | 23 +++++ .../templates/XXXConfigVersion.cmake.jinja | 14 +++ .../templates/pkgconfig.pc.in | 0 packer/src/sarasacw_omrf_packer/utils.py | 13 +++ 11 files changed, 203 insertions(+), 32 deletions(-) delete mode 100644 packer/src/sarasacw_omrf_packer/cmake.py delete mode 100644 packer/src/sarasacw_omrf_packer/pkgconfig.py create mode 100644 packer/src/sarasacw_omrf_packer/renders/cmake.py create mode 100644 packer/src/sarasacw_omrf_packer/renders/common.py create mode 100644 packer/src/sarasacw_omrf_packer/renders/pkgconfig.py rename packer/src/sarasacw_omrf_packer/templates/{cmake.in => XXX.pc.jinja} (100%) create mode 100644 packer/src/sarasacw_omrf_packer/templates/XXXConfig.cmake.jinja create mode 100644 packer/src/sarasacw_omrf_packer/templates/XXXConfigVersion.cmake.jinja delete mode 100644 packer/src/sarasacw_omrf_packer/templates/pkgconfig.pc.in create mode 100644 packer/src/sarasacw_omrf_packer/utils.py diff --git a/packer/src/sarasacw_omrf_packer/archiver.py b/packer/src/sarasacw_omrf_packer/archiver.py index 42df4df..7d14c01 100644 --- a/packer/src/sarasacw_omrf_packer/archiver.py +++ b/packer/src/sarasacw_omrf_packer/archiver.py @@ -1,10 +1,91 @@ import zipfile +import shutil from pathlib import Path +from types import TracebackType +from typing import Optional -def pack(dist_dir: str, output_zip: str) -> None: - dist = Path(dist_dir) - with zipfile.ZipFile(output_zip, "w", zipfile.ZIP_DEFLATED) as zf: - for path in dist.rglob("*"): - if path.is_file(): - zf.write(path, path.relative_to(dist)) +class Archiver: + """Dual sink that mirrors the same distribution tree into a directory and a zip archive. + + On construction the archiver (optionally) prepares a directory on disk and + (optionally) opens a :class:`zipfile.ZipFile` for writing. Every subsequent + :meth:`push_file`/:meth:`push_text` call then writes the same logical entry + -- identified by its ``arcname``, a path relative to the distribution root + -- into both destinations, skipping whichever sink was not requested. + + The archiver is usable as a context manager so that the underlying archive + is closed deterministically:: + + with Archiver(dist_dir, dist_zip) as arc: + arc.push_file(...) + """ + + __dist_dir: Optional[Path] + __dist_zip: Optional[zipfile.ZipFile] + + def __init__(self, dist_dir: Optional[Path], dist_zip: Optional[Path]) -> None: + """Configure the sinks and acquire their backing resources. + + :param dist_dir: Directory to mirror the tree into. Created (with + parents) when given; ``None`` disables the directory sink. + :param dist_zip: Path of the zip archive to create, truncated on open. + ``None`` disables the zip sink. + """ + # make sure the distribution directory is existing + self.__dist_dir = dist_dir + if self.__dist_dir is not None: + self.__dist_dir.mkdir(parents=True, exist_ok=True) + # create zip instance + if dist_zip is not None: + self.__dist_zip = zipfile.ZipFile(dist_zip, "w", zipfile.ZIP_DEFLATED) + else: + self.__dist_zip = None + + def push_file(self, filepath: Path, arcname: Path) -> None: + """Mirror an existing file into both sinks. + + :param filepath: Source file on disk to copy / embed. + :param arcname: Destination path relative to the distribution root. + Missing parent directories are created for the directory sink. + """ + if self.__dist_dir is not None: + target_filepath = self.__dist_dir / arcname + target_filepath.parent.mkdir(parents=True, exist_ok=True) + shutil.copy(filepath, target_filepath) + + if self.__dist_zip is not None: + self.__dist_zip.write(filepath, arcname) + + def push_text(self, text: str, arcname: Path) -> None: + """Mirror in-memory text into both sinks. + + :param text: UTF-8 text content to write. + :param arcname: Destination path relative to the distribution root. + Missing parent directories are created for the directory sink. + """ + if self.__dist_dir is not None: + target_filepath = self.__dist_dir / arcname + target_filepath.parent.mkdir(parents=True, exist_ok=True) + with open(target_filepath, "w", encoding="utf-8") as f: + f.write(text) + + if self.__dist_zip is not None: + self.__dist_zip.writestr(str(arcname), text) + + def __enter__(self) -> "Archiver": + """Enter the context and return this archiver as the bound target.""" + return self + + def __exit__( + self, + exc_type: Optional[type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + """Release the zip sink by closing its backing archive, if any. + + Exceptions raised inside the ``with`` body are never suppressed. + """ + if self.__dist_zip is not None: + self.__dist_zip.close() diff --git a/packer/src/sarasacw_omrf_packer/cmake.py b/packer/src/sarasacw_omrf_packer/cmake.py deleted file mode 100644 index 0c8ce86..0000000 --- a/packer/src/sarasacw_omrf_packer/cmake.py +++ /dev/null @@ -1,13 +0,0 @@ -from pathlib import Path - -from jinja2 import Environment, FileSystemLoader, select_autoescape - - -def render(output_dir: str) -> None: - templates_dir = Path(__file__).parent / "templates" - env = Environment( - loader=FileSystemLoader(templates_dir), - autoescape=select_autoescape(), - ) - out = Path(output_dir) - out.mkdir(parents=True, exist_ok=True) diff --git a/packer/src/sarasacw_omrf_packer/pkgconfig.py b/packer/src/sarasacw_omrf_packer/pkgconfig.py deleted file mode 100644 index 0c8ce86..0000000 --- a/packer/src/sarasacw_omrf_packer/pkgconfig.py +++ /dev/null @@ -1,13 +0,0 @@ -from pathlib import Path - -from jinja2 import Environment, FileSystemLoader, select_autoescape - - -def render(output_dir: str) -> None: - templates_dir = Path(__file__).parent / "templates" - env = Environment( - loader=FileSystemLoader(templates_dir), - autoescape=select_autoescape(), - ) - out = Path(output_dir) - out.mkdir(parents=True, exist_ok=True) diff --git a/packer/src/sarasacw_omrf_packer/renders/cmake.py b/packer/src/sarasacw_omrf_packer/renders/cmake.py new file mode 100644 index 0000000..e359313 --- /dev/null +++ b/packer/src/sarasacw_omrf_packer/renders/cmake.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass +from pathlib import Path +from . import common + + +@dataclass(frozen=True) +class CMakeProperties: + name: str + """The identifier of project""" + + +class CMakeRender: + __render: common.BaseRender + __properties: CMakeProperties + + def __init__(self, properties: CMakeProperties) -> None: + self.__render = common.BaseRender() + self.__properties = properties + + def render_config(self) -> None: + pass + + def render_config_version(self) -> None: + pass diff --git a/packer/src/sarasacw_omrf_packer/renders/common.py b/packer/src/sarasacw_omrf_packer/renders/common.py new file mode 100644 index 0000000..5198185 --- /dev/null +++ b/packer/src/sarasacw_omrf_packer/renders/common.py @@ -0,0 +1,22 @@ +from pathlib import Path +from typing import Any +import jinja2 +from .. import utils + + +class BaseRender: + __loader: jinja2.BaseLoader + __environment: jinja2.Environment + + def __init__(self) -> None: + self.__loader = jinja2.FileSystemLoader(utils.get_templates_dir()) + self.__environment = jinja2.Environment(loader=self.__loader) + + def render( + self, template_filename: str, dest_filepath: Path, payload: dict[str, Any] + ) -> None: + # fetch template + template = self.__environment.get_template(template_filename) + # render template and save + with open(dest_filepath, "w", encoding="utf-8") as f: + f.write(template.render(**payload)) diff --git a/packer/src/sarasacw_omrf_packer/renders/pkgconfig.py b/packer/src/sarasacw_omrf_packer/renders/pkgconfig.py new file mode 100644 index 0000000..5d68421 --- /dev/null +++ b/packer/src/sarasacw_omrf_packer/renders/pkgconfig.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from pathlib import Path +from . import common + +@dataclass(frozen=True) +class PkgConfigProperties: + pass + + +class PkgConfigRender: + + __render: common.BaseRender + __properties: PkgConfigProperties + + def __init__(self, properties: PkgConfigProperties) -> None: + self.__render = common.BaseRender() + self.__properties = properties + + def render(self) -> None: + pass diff --git a/packer/src/sarasacw_omrf_packer/templates/cmake.in b/packer/src/sarasacw_omrf_packer/templates/XXX.pc.jinja similarity index 100% rename from packer/src/sarasacw_omrf_packer/templates/cmake.in rename to packer/src/sarasacw_omrf_packer/templates/XXX.pc.jinja diff --git a/packer/src/sarasacw_omrf_packer/templates/XXXConfig.cmake.jinja b/packer/src/sarasacw_omrf_packer/templates/XXXConfig.cmake.jinja new file mode 100644 index 0000000..360ee8e --- /dev/null +++ b/packer/src/sarasacw_omrf_packer/templates/XXXConfig.cmake.jinja @@ -0,0 +1,23 @@ +{# Get the path to directory where current XXXConfig.cmake file is (i.e. /path/to/installation/lib/cmake/XXX) -#} +get_filename_component(PACKAGE_CMAKE_CONFIG_PATH "${CMAKE_CURRENT_LIST_FILE}" PATH) + +{# Compute installation root directory (back to parent 3 times, i.e. /path/to/installation) -#} +get_filename_component(PACKAGE_PREFIX_DIR "${PACKAGE_CMAKE_CONFIG_PATH}/../../../" ABSOLUTE) + +{# Setup library and header file paths -#} +set(MyRust_LIBRARY "${PACKAGE_PREFIX_DIR}/lib/ {{- artifact_filename -}} }") +set(MyRust_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include") + +{# Create modern CMake target (IMPORTED target) -#} +add_library(MyRust::MyRust SHARED IMPORTED) +set_target_properties(MyRust::MyRust PROPERTIES + IMPORTED_LOCATION "${MyRust_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MyRust_INCLUDE_DIR}" +) + +{# Handle the Windows-specific case that .dll is seperated with .lib -#} +if(WIN32) + set_target_properties(MyRust::MyRust PROPERTIES + IMPORTED_IMPLIB "${PACKAGE_PREFIX_DIR}/lib/myrust.lib" + ) +endif() diff --git a/packer/src/sarasacw_omrf_packer/templates/XXXConfigVersion.cmake.jinja b/packer/src/sarasacw_omrf_packer/templates/XXXConfigVersion.cmake.jinja new file mode 100644 index 0000000..eed0fa0 --- /dev/null +++ b/packer/src/sarasacw_omrf_packer/templates/XXXConfigVersion.cmake.jinja @@ -0,0 +1,14 @@ +{# Setup package version -#} +set(PACKAGE_VERSION "1.2.3") # TODO: replace with jinja argument + +{# Check whether user request EXACT match -#} +if(PACKAGE_FIND_VERSION_EXACT) + if(NOT "${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_UNSUPPORTED TRUE) + endif() +else() + {# Check whether user requested version is <= current version (compatible with new version) -#} + if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_UNSUPPORTED TRUE) + endif() +endif() \ No newline at end of file diff --git a/packer/src/sarasacw_omrf_packer/templates/pkgconfig.pc.in b/packer/src/sarasacw_omrf_packer/templates/pkgconfig.pc.in deleted file mode 100644 index e69de29..0000000 diff --git a/packer/src/sarasacw_omrf_packer/utils.py b/packer/src/sarasacw_omrf_packer/utils.py new file mode 100644 index 0000000..418bf3a --- /dev/null +++ b/packer/src/sarasacw_omrf_packer/utils.py @@ -0,0 +1,13 @@ +from pathlib import Path + + +def get_root_dir() -> Path: + return Path(__file__).resolve().parent + + +def get_templates_dir() -> Path: + return get_root_dir() / 'templates' + + + +