feat: add metadata resolve in module
This commit is contained in:
+34
-24
@@ -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`.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user