feat: basically finish pkgconfig generation
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
from .archiver import pack
|
||||
from .cargo import get_artifacts
|
||||
from .cli import Cli, parse
|
||||
from .cmake import render as render_cmake
|
||||
from .pkgconfig import render as render_pkgconfig
|
||||
import logging
|
||||
from . import cli
|
||||
from .archiver import Archiver
|
||||
from .metadata import Metadata
|
||||
from .renders.cmake import CMakeProperties, CMakeRender
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cli = parse()
|
||||
get_artifacts(cli.manifest_path)
|
||||
render_cmake(cli.output_dir)
|
||||
render_pkgconfig(cli.output_dir)
|
||||
pack(cli.output_dir, cli.output_dir + ".zip")
|
||||
# parse command line arguments
|
||||
opts = cli.parse()
|
||||
# setup logging
|
||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||
|
||||
# build metadata
|
||||
metadata = Metadata(opts.manifest)
|
||||
|
||||
|
||||
# create distribution
|
||||
with Archiver(opts.dist_dir, opts.dist_zip) as archiver:
|
||||
pass
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
|
||||
def get_metadata(manifest_path: str) -> dict:
|
||||
cmd = [
|
||||
"cargo",
|
||||
"metadata",
|
||||
"--format-version=1",
|
||||
f"--manifest-path={manifest_path}",
|
||||
]
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
stdout, _ = proc.communicate()
|
||||
return json.loads(stdout)
|
||||
|
||||
|
||||
def get_artifacts(manifest_path: str) -> list[str]:
|
||||
metadata = get_metadata(manifest_path)
|
||||
target_directory = metadata["target_directory"]
|
||||
return [target_directory]
|
||||
@@ -0,0 +1,60 @@
|
||||
import json
|
||||
import subprocess
|
||||
from typing import Any
|
||||
from pathlib import Path
|
||||
import tomli
|
||||
|
||||
|
||||
# YYC MARK:
|
||||
# We use `tomli` with at least 2.4.0 version by design.
|
||||
# Considering that Cargo has approve the change allowing TOML 1.1 syntax in `Cargo.toml`,
|
||||
# Python embedded `toml` package, which only support TOML 1.0 syntax until Python 3.15,
|
||||
# is not suit for parsing `Cargo.toml` in future.
|
||||
# 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.
|
||||
|
||||
_TOKEN_PACKAGES: str = "packages"
|
||||
_TOKEN_PACKAGE_MANIFEST_PATH: str = "manifest_path"
|
||||
_TOKEN_TARGET_DIRECTORY: str = "target_directory"
|
||||
|
||||
|
||||
class Metadata:
|
||||
__cargo_toml_path: Path
|
||||
__cargo_metadata: dict[str, Any]
|
||||
__cargo_toml: dict[str, Any]
|
||||
|
||||
def __init__(self, cargo_toml: Path) -> None:
|
||||
self.__cargo_toml_path = cargo_toml
|
||||
self.__cargo_toml = Metadata.__extract_cargo_toml(cargo_toml)
|
||||
self.__cargo_metadata = Metadata.__extract_cargo_metadata(cargo_toml)
|
||||
|
||||
@staticmethod
|
||||
def __extract_cargo_toml(cargo_toml: Path) -> dict[str, Any]:
|
||||
with open(cargo_toml, "rb") as f:
|
||||
return tomli.load(f)
|
||||
|
||||
@staticmethod
|
||||
def __extract_cargo_metadata(cargo_toml: Path) -> dict[str, Any]:
|
||||
cmd = [
|
||||
"cargo",
|
||||
"metadata",
|
||||
"--no-deps",
|
||||
"--format-version",
|
||||
"1",
|
||||
"--manifest-path",
|
||||
str(cargo_toml),
|
||||
]
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
try:
|
||||
stdout, stderr = proc.communicate(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.communicate()
|
||||
raise RuntimeError("fail to fetch cargo metadata: timed out")
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(
|
||||
"fail to fetch cargo metadata: "
|
||||
+ stderr.decode("utf-8", errors="ignore")
|
||||
)
|
||||
else:
|
||||
return json.loads(stdout)
|
||||
@@ -65,9 +65,9 @@ class CMakeProperties:
|
||||
"bad target name component in CMake. consider manually specifying it"
|
||||
)
|
||||
if self.artifact_dll == "":
|
||||
raise ValueError("bad artifact dll file name")
|
||||
raise ValueError("bad artifact dll file name in CMake")
|
||||
if self.artifact_lib == "":
|
||||
raise ValueError("bad artifact lib file name")
|
||||
raise ValueError("bad artifact lib file name in CMake")
|
||||
if self.version.prerelease is not None or self.version.build is not None:
|
||||
raise ValueError(
|
||||
"unsupported semantic version components (prerelease or build) in CMake"
|
||||
|
||||
@@ -1,21 +1,90 @@
|
||||
"""Renderer for pkg-config package files.
|
||||
|
||||
Generates the ``<pkg>.pc`` file consumed by the ``pkg-config`` tool so that the
|
||||
distributed Rust FFI library can be located and linked by downstream build
|
||||
systems. The data fed to the template is carried by
|
||||
:class:`PkgConfigProperties`, and :class:`PkgConfigRender` performs the
|
||||
rendering on top of :class:`renders.common.BaseRender`.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from . import common
|
||||
from semver import Version
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PkgConfigProperties:
|
||||
pass
|
||||
"""Inputs required to render the pkg-config ``.pc`` file.
|
||||
|
||||
A frozen dataclass validated in :meth:`__post_init__`; once constructed, an
|
||||
instance is guaranteed to hold only values that are safe to substitute into
|
||||
the ``.pc`` template.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""Human-readable name of the library or package.
|
||||
|
||||
This is purely descriptive; the ``pkg-config`` tool itself resolves
|
||||
packages by the file name of the ``.pc`` file, not by this field.
|
||||
"""
|
||||
description: str
|
||||
"""Brief description of the package."""
|
||||
|
||||
artifact: str
|
||||
"""Name of the build artifact.
|
||||
|
||||
``pkg-config`` uses this value to determine which dynamic library to link.
|
||||
For an artifact named ``XXX``, it looks for ``libXXX.so`` on Linux and
|
||||
``XXX.lib`` or ``libXXX.dll.a`` on Windows.
|
||||
"""
|
||||
|
||||
version: Version
|
||||
"""Version of the library. Must not carry a prerelease or build component."""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Validate the fields after construction.
|
||||
|
||||
:raises ValueError: if the name or description is blank, or the
|
||||
version carries a prerelease or build component (unsupported in
|
||||
pkg-config).
|
||||
"""
|
||||
if self.name == "":
|
||||
raise ValueError("unexpected blank name of pkg-config")
|
||||
if self.description == "":
|
||||
raise ValueError("unexpected blank description of pkg-config")
|
||||
if self.version.prerelease is not None or self.version.build is not None:
|
||||
raise ValueError(
|
||||
"unsupported semantic version components (prerelease or build) in pkg-config"
|
||||
)
|
||||
|
||||
|
||||
class PkgConfigRender:
|
||||
"""Renderer that turns :class:`PkgConfigProperties` into a ``.pc`` file.
|
||||
|
||||
Wraps a :class:`renders.common.BaseRender` and exposes :meth:`render` to
|
||||
emit the package configuration file.
|
||||
"""
|
||||
|
||||
__render: common.BaseRender
|
||||
__properties: PkgConfigProperties
|
||||
|
||||
def __init__(self, properties: PkgConfigProperties) -> None:
|
||||
"""Create the renderer with its Jinja2 backend and bound properties.
|
||||
|
||||
:param properties: Validated inputs reused by :meth:`render`.
|
||||
"""
|
||||
self.__render = common.BaseRender()
|
||||
self.__properties = properties
|
||||
|
||||
def render(self) -> str:
|
||||
payload: dict[str, Any] = {}
|
||||
"""Render the ``<pkg>.pc`` file.
|
||||
|
||||
:returns: The rendered ``.pc`` content as a string.
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"name": self.__properties.name,
|
||||
"description": self.__properties.description,
|
||||
"artifact": self.__properties.artifact,
|
||||
}
|
||||
return self.__render.render("XXX.pc.jinja", payload)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{#
|
||||
Utilize ${pcfiledir} to fetch the directory where current .pc file is.
|
||||
And back to parent twice to obtain the installation directory.
|
||||
-#}
|
||||
prefix=${pcfiledir}/../..
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: {{ name }}
|
||||
Description: {{ description }}
|
||||
Version: {{ version }}
|
||||
Libs: -L${libdir} -l{{ artifact }}
|
||||
Cflags: -I${includedir}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from pathlib import Path
|
||||
from semver import Version
|
||||
|
||||
VERSION: Version = Version(1, 0, 0)
|
||||
"""The current version of sarasacw-omrf-packer"""
|
||||
|
||||
def get_root_dir() -> Path:
|
||||
return Path(__file__).resolve().parent
|
||||
|
||||
Reference in New Issue
Block a user