feat: add metadata resolve in module

This commit is contained in:
2026-08-03 16:05:08 +08:00
parent 52bd89ad6e
commit 6f1c5312e0
3 changed files with 161 additions and 33 deletions
+34 -24
View File
@@ -1,45 +1,55 @@
# sarasacw-omrf-packer # sarasacw-omrf-packer
## Metadata
All packer used properties are stored in target Rust manifest file as metadata style. All packer used properties are stored in target Rust manifest file as metadata style.
There is an example about how to define them in `Cargo.toml`. There is an example about how to define them in `Cargo.toml`.
```toml ```toml
[package.metadata.omrf] [package.metadata.omrf]
# Configures the minimum required sarasacw-omrf-packer version. # Minimum required version of sarasacw-omrf-packer.
# Trying to run with an older version causes an error. # Running with an older version fails with an error.
# This property is optional. If there is no specification, no version constraint is applied. # This property is optional; when omitted, no version constraint is enforced.
min_version = "1.0.0" min_version = "1.0.0"
[package.metadata.omrf.headers] # Header files to distribute. Each entry installs one file (or, with glob
assets = [ # expansion, a set of files) into the `include` directory of the installation.
# `from` is the relative path to directory where this Cargo.toml is. # Each entry carries a `from` source path and a `to` destination path.
# `to` is the relative path to `include` directory which is the subdirectory of installation directory. # This property is required. If you really want to distribute nothing header files,
# Blank `to` means that put it directly in `include` directory. # leave empty list here.
headers = [
# `from` is a path relative to the directory that contains this `Cargo.toml`.
# `to` is a path relative to the `include` directory (a subdirectory of the install directory).
# An empty `to` places the file directly under the `include` directory.
{ from = "cbindgen/my_crate.h", to = "" }, { from = "cbindgen/my_crate.h", to = "" },
{ from = "cbindgen/our_crate.h", to = "SomePrefix" }, { from = "cbindgen/our_crate.h", to = "SomePrefix" },
# `from` support UNIX style pathname pattern expansion. # `from` also supports UNIX-style glob (pathname) expansion.
# It will find the toppest immutable component of given pattern. # The matcher uses the leading literal portion of the pattern (`pattern/with`
# In this exmaple, this toppest immutable component is `pattern/with`. # here) as the root directory and copies every matched file into the `to`
# And use that as the root directory and put found files to `to` directory # directory, preserving the directory hierarchy beneath that root.
# with preserving directory hierarchy. { from = "pattern/with/**/*", to = "" },
{ from = "pattern/with/**/*", to = "" } # The same pattern, but nested under an `AllInOne` prefix.
{ from = "pattern/with/**/*", to = "AllInOne" } { from = "pattern/with/**/*", to = "AllInOne" },
] ]
[package.metadata.omrf.cmake] [package.metadata.omrf.cmake]
# Configures the namespace part of CMake target. # Namespace part of the generated CMake target.
# This property is optional. The default value is extracted from the name of target Rust project. # This property is optional; it defaults to the name of the target Rust project.
namespace_name = "foobar" namespace_name = "foobar"
# Configures the target part of CMake target. # Target-name part of the generated CMake target.
# This property is optional. The default value is extracted from the name of target Rust project. # This property is optional; it defaults to the name of the target Rust project.
target_name = "foobar" target_name = "foobar"
[package.metadata.omrf.pkgconfig] [package.metadata.omrf.pkgconfig]
# Configures the human-readable name of this package. # Human-readable name of the package.
# This property is optional. The default value is extracted from the name of target Rust project. # This property is optional; it defaults to the name of the target Rust project.
name = "Foo Bar" name = "Foo Bar"
# Configures the brief description of this package. # Brief description of the package.
# This property is optional. The default value is extracted from the description of target Rust project. # This property is optional; it defaults to the description of the target Rust project.
name = "a brown fox jumps over a lazy dog." description = "a brown fox jumps over a lazy dog."
``` ```
## Environment Variables
- `OMRF_PACKER_CARGO`: Path to the `cargo` executable used to collect project metadata. When unset, the packer invokes `cargo` as resolved from `PATH`.
+2 -2
View File
@@ -1,7 +1,7 @@
import logging import logging
from . import cli from . import cli
from .archiver import Archiver from .archiver import Archiver
from .metadata import Metadata from .metadata import MetadataExtractor
from .renders.cmake import CMakeProperties, CMakeRender from .renders.cmake import CMakeProperties, CMakeRender
@@ -12,7 +12,7 @@ def main() -> None:
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
# build metadata # build metadata
metadata = Metadata(opts.manifest) metadata = MetadataExtractor(opts.manifest)
# create distribution # create distribution
+125 -7
View File
@@ -1,8 +1,11 @@
import json import json
import os
import subprocess import subprocess
from typing import Any from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any
import tomli import tomli
from semver import Version
# YYC MARK: # YYC MARK:
@@ -13,20 +16,134 @@ import tomli
# So we use the upstream of official `toml` package, i.e. `tomli` as our solution. # So we use the upstream of official `toml` package, i.e. `tomli` as our solution.
# And according to the document if `tomli`, the version starting support TOML 1.1 syntax is 2.4.0. # And according to the document if `tomli`, the version starting support TOML 1.1 syntax is 2.4.0.
_TOKEN_PACKAGES: str = "packages"
_TOKEN_PACKAGE_MANIFEST_PATH: str = "manifest_path" @dataclass(frozen=True)
_TOKEN_TARGET_DIRECTORY: str = "target_directory" class MetadataHeader:
from_path: str
to_path: str
def __post_init__(self) -> None:
if self.from_path == "":
raise ValueError("header asset 'from' must not be empty")
@staticmethod
def from_dict(d: dict[str, Any]) -> "MetadataHeader":
from_path = d.get("from")
if from_path is None:
raise ValueError("header asset is missing required key 'from'")
if not isinstance(from_path, str):
raise TypeError("header asset 'from' must be a string")
to_path = d.get("to", "")
if not isinstance(to_path, str):
raise TypeError("header asset 'to' must be a string")
return MetadataHeader(from_path=from_path, to_path=to_path)
@dataclass(frozen=True)
class MetadataCMake:
namespace_name: str | None
target_name: str | None
@staticmethod
def from_dict(d: dict[str, Any]) -> "MetadataCMake":
namespace_name = d.get("namespace_name")
if namespace_name is not None and not isinstance(namespace_name, str):
raise TypeError("cmake 'namespace_name' must be a string or None")
target_name = d.get("target_name")
if target_name is not None and not isinstance(target_name, str):
raise TypeError("cmake 'target_name' must be a string or None")
return MetadataCMake(namespace_name=namespace_name, target_name=target_name)
@dataclass(frozen=True)
class MetadataPkgConfig:
name: str | None
description: str | None
@staticmethod
def from_dict(d: dict[str, Any]) -> "MetadataPkgConfig":
name = d.get("name")
if name is not None and not isinstance(name, str):
raise TypeError("pkgconfig 'name' must be a string or None")
description = d.get("description")
if description is not None and not isinstance(description, str):
raise TypeError("pkgconfig 'description' must be a string or None")
return MetadataPkgConfig(name=name, description=description)
@dataclass(frozen=True)
class Metadata: class Metadata:
min_version: Version | None
headers: tuple[MetadataHeader, ...]
cmake: MetadataCMake | None
pkgconfig: MetadataPkgConfig | None
@staticmethod
def from_dict(d: dict[str, Any]) -> "Metadata":
raw_min_version = d.get("min_version")
if raw_min_version is not None:
if not isinstance(raw_min_version, str):
raise TypeError("min_version must be a string")
try:
min_version = Version.parse(raw_min_version)
except ValueError as e:
raise ValueError(f"invalid min_version {raw_min_version!r}: {e}") from e
else:
min_version = None
raw_headers = d.get("headers")
if raw_headers is None:
raise ValueError(
"headers is required (use an empty list to distribute no headers)"
)
if not isinstance(raw_headers, list):
raise TypeError("headers must be a list")
headers_list: list[MetadataHeader] = []
for i, item in enumerate(raw_headers):
if not isinstance(item, dict):
raise TypeError(f"headers[{i}] must be a table")
headers_list.append(MetadataHeader.from_dict(item))
headers = tuple(headers_list)
raw_cmake = d.get("cmake")
if raw_cmake is not None:
if not isinstance(raw_cmake, dict):
raise TypeError("cmake must be a table")
cmake = MetadataCMake.from_dict(raw_cmake)
else:
cmake = None
raw_pkgconfig = d.get("pkgconfig")
if raw_pkgconfig is not None:
if not isinstance(raw_pkgconfig, dict):
raise TypeError("pkgconfig must be a table")
pkgconfig = MetadataPkgConfig.from_dict(raw_pkgconfig)
else:
pkgconfig = None
return Metadata(
min_version=min_version,
headers=headers,
cmake=cmake,
pkgconfig=pkgconfig,
)
class MetadataExtractor:
__cargo_toml_path: Path __cargo_toml_path: Path
__cargo_metadata: dict[str, Any] __cargo_metadata: dict[str, Any]
__cargo_toml: dict[str, Any] __cargo_toml: dict[str, Any]
def __init__(self, cargo_toml: Path) -> None: def __init__(self, cargo_toml: Path) -> None:
self.__cargo_toml_path = cargo_toml self.__cargo_toml_path = cargo_toml
self.__cargo_toml = Metadata.__extract_cargo_toml(cargo_toml) self.__cargo_toml = MetadataExtractor.__extract_cargo_toml(cargo_toml)
self.__cargo_metadata = Metadata.__extract_cargo_metadata(cargo_toml) self.__cargo_metadata = MetadataExtractor.__extract_cargo_metadata(cargo_toml)
def extract_omrf_metadata(self) -> Metadata:
omrf = self.__cargo_toml.get("package", {}).get("metadata", {}).get("omrf")
if not isinstance(omrf, dict):
raise ValueError("[package.metadata.omrf] is required")
return Metadata.from_dict(omrf)
@staticmethod @staticmethod
def __extract_cargo_toml(cargo_toml: Path) -> dict[str, Any]: def __extract_cargo_toml(cargo_toml: Path) -> dict[str, Any]:
@@ -35,8 +152,9 @@ class Metadata:
@staticmethod @staticmethod
def __extract_cargo_metadata(cargo_toml: Path) -> dict[str, Any]: def __extract_cargo_metadata(cargo_toml: Path) -> dict[str, Any]:
cargo_bin = os.getenv("OMRF_PACKER_CARGO", "cargo")
cmd = [ cmd = [
"cargo", cargo_bin,
"metadata", "metadata",
"--no-deps", "--no-deps",
"--format-version", "--format-version",