feat: use rust triple instead of python sys module
after this change, cross compilation are supported in theory.
This commit is contained in:
@@ -41,7 +41,7 @@ class App:
|
|||||||
self.__opts = opts
|
self.__opts = opts
|
||||||
# build essential instances
|
# build essential instances
|
||||||
self.__extractor = MetadataExtractor(self.__opts.manifest)
|
self.__extractor = MetadataExtractor(self.__opts.manifest)
|
||||||
self.__ctx = ArtifactContext(self.__extractor)
|
self.__ctx = ArtifactContext(self.__opts, self.__extractor)
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Produce the distribution.
|
"""Produce the distribution.
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ header copies, library copies and generated CMake/pkg-config texts that the
|
|||||||
archiver then materializes.
|
archiver then materializes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from re import Pattern, compile
|
from re import Pattern, compile
|
||||||
|
from .cli import Cli
|
||||||
|
from .utils import Triple
|
||||||
from .metadata import MetadataExtractor, Metadata
|
from .metadata import MetadataExtractor, Metadata
|
||||||
from .renders.cmake import CMakeProperties, CMakeRender
|
from .renders.cmake import CMakeProperties, CMakeRender
|
||||||
from .renders.pkgconfig import PkgConfigProperties, PkgConfigRender
|
from .renders.pkgconfig import PkgConfigProperties, PkgConfigRender
|
||||||
@@ -19,17 +20,24 @@ from .renders.pkgconfig import PkgConfigProperties, PkgConfigRender
|
|||||||
class ArtifactContext:
|
class ArtifactContext:
|
||||||
"""A wrapper of metadata with proper fallback"""
|
"""A wrapper of metadata with proper fallback"""
|
||||||
|
|
||||||
|
__opts: Cli
|
||||||
__extractor: MetadataExtractor
|
__extractor: MetadataExtractor
|
||||||
__metadata: Metadata
|
__metadata: Metadata
|
||||||
|
|
||||||
def __init__(self, extractor: MetadataExtractor) -> None:
|
def __init__(self, opts: Cli, extractor: MetadataExtractor) -> None:
|
||||||
"""Wrap an extractor and eagerly fetch its :class:`Metadata`.
|
"""Wrap an extractor and eagerly fetch its :class:`Metadata`.
|
||||||
|
|
||||||
:param extractor: The extractor to wrap and read from.
|
:param extractor: The extractor to wrap and read from.
|
||||||
"""
|
"""
|
||||||
|
self.__opts = opts
|
||||||
self.__extractor = extractor
|
self.__extractor = extractor
|
||||||
self.__metadata = self.__extractor.get_metadata()
|
self.__metadata = self.__extractor.get_metadata()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self) -> Cli:
|
||||||
|
"""The wrapped :class:`Cli` holding all command line arguments"""
|
||||||
|
return self.__opts
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extractor(self) -> MetadataExtractor:
|
def extractor(self) -> MetadataExtractor:
|
||||||
"""The wrapped :class:`MetadataExtractor`."""
|
"""The wrapped :class:`MetadataExtractor`."""
|
||||||
@@ -112,20 +120,39 @@ def resolve_include_copy(ctx: ArtifactContext) -> Iterator[FileCopyInfo]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _generate_dll_artifact_name(name: str) -> str:
|
_WINDOWS_ENV_IDENTS: tuple[str, ...] = ("windows", "cygwin")
|
||||||
|
|
||||||
|
_LINUX_ENV_IDENTS: tuple[str, ...] = (
|
||||||
|
"linux",
|
||||||
|
"android",
|
||||||
|
"freebsd",
|
||||||
|
"openbsd",
|
||||||
|
"netbsd",
|
||||||
|
"haiku",
|
||||||
|
"hurd",
|
||||||
|
)
|
||||||
|
|
||||||
|
_MACOS_ENV_IDENTS: tuple[str, ...] = ("macos", "ios", "tvos", "visionos")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_windows_env(triple: Triple) -> bool:
|
||||||
|
return triple.operating_system in _WINDOWS_ENV_IDENTS
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_dll_artifact_name(name: str, triple: Triple) -> str:
|
||||||
"""Return the platform-appropriate dynamic-loadable file name for ``name``."""
|
"""Return the platform-appropriate dynamic-loadable file name for ``name``."""
|
||||||
match sys.platform:
|
match triple.operating_system:
|
||||||
case "win32" | "cygwin":
|
case x if x in _WINDOWS_ENV_IDENTS:
|
||||||
return f"{name}.dll"
|
return f"{name}.dll"
|
||||||
case "linux" | "android" | "freebsd":
|
case x if x in _LINUX_ENV_IDENTS:
|
||||||
return f"lib{name}.so"
|
return f"lib{name}.so"
|
||||||
case "darwin" | "ios":
|
case x if x in _MACOS_ENV_IDENTS:
|
||||||
return f"lib{name}.dylib"
|
return f"lib{name}.dylib"
|
||||||
case _:
|
case _:
|
||||||
raise RuntimeError("not supported system")
|
raise RuntimeError("not supported system")
|
||||||
|
|
||||||
|
|
||||||
def _generate_lib_artifact_name(name: str) -> str:
|
def _generate_lib_artifact_name(name: str, _: Triple) -> str:
|
||||||
"""Return the Windows import-library file name for ``name``.
|
"""Return the Windows import-library file name for ``name``.
|
||||||
|
|
||||||
Only meaningful on Windows; returned regardless of platform for use by the
|
Only meaningful on Windows; returned regardless of platform for use by the
|
||||||
@@ -141,15 +168,21 @@ def resolve_lib_copy(ctx: ArtifactContext) -> Iterator[FileCopyInfo]:
|
|||||||
target_directory = extractor.get_target_directory() / "release"
|
target_directory = extractor.get_target_directory() / "release"
|
||||||
target_name = extractor.get_target_name()
|
target_name = extractor.get_target_name()
|
||||||
|
|
||||||
|
# get triple info
|
||||||
|
target_triple = extractor.get_host_triple()
|
||||||
|
if ctx.options.target is not None:
|
||||||
|
target_triple = ctx.options.target
|
||||||
|
|
||||||
# copy artifact for redist
|
# copy artifact for redist
|
||||||
dll_artifact_filename = _generate_dll_artifact_name(target_name)
|
dll_artifact_filename = _generate_dll_artifact_name(target_name, target_triple)
|
||||||
yield FileCopyInfo(
|
yield FileCopyInfo(
|
||||||
target_directory / dll_artifact_filename,
|
target_directory / dll_artifact_filename,
|
||||||
Path("bin" if sys.platform == "win32" else "lib") / dll_artifact_filename,
|
Path("bin" if _is_windows_env(target_triple) else "lib")
|
||||||
|
/ dll_artifact_filename,
|
||||||
)
|
)
|
||||||
# copy artifact for linking only on windows
|
# copy artifact for linking only on windows
|
||||||
if sys.platform == "win32":
|
if _is_windows_env(target_triple):
|
||||||
lib_artifact_filename = _generate_lib_artifact_name(target_name)
|
lib_artifact_filename = _generate_lib_artifact_name(target_name, target_triple)
|
||||||
yield FileCopyInfo(
|
yield FileCopyInfo(
|
||||||
target_directory / lib_artifact_filename,
|
target_directory / lib_artifact_filename,
|
||||||
Path("lib") / lib_artifact_filename,
|
Path("lib") / lib_artifact_filename,
|
||||||
@@ -187,6 +220,11 @@ def resolve_cmake_copy(ctx: ArtifactContext) -> Iterator[TextCopyInfo]:
|
|||||||
metadata = ctx.metadata
|
metadata = ctx.metadata
|
||||||
target_name = extractor.get_target_name()
|
target_name = extractor.get_target_name()
|
||||||
|
|
||||||
|
# get triple info
|
||||||
|
target_triple = extractor.get_host_triple()
|
||||||
|
if ctx.options.target is not None:
|
||||||
|
target_triple = ctx.options.target
|
||||||
|
|
||||||
# compute fallback name
|
# compute fallback name
|
||||||
cmake_fallback_name = _sanitize_name(target_name)
|
cmake_fallback_name = _sanitize_name(target_name)
|
||||||
# get namespace and target name with fallback
|
# get namespace and target name with fallback
|
||||||
@@ -202,8 +240,8 @@ def resolve_cmake_copy(ctx: ArtifactContext) -> Iterator[TextCopyInfo]:
|
|||||||
properties = CMakeProperties(
|
properties = CMakeProperties(
|
||||||
cmake_namespace_name,
|
cmake_namespace_name,
|
||||||
cmake_target_name,
|
cmake_target_name,
|
||||||
_generate_dll_artifact_name(target_name),
|
_generate_dll_artifact_name(target_name, target_triple),
|
||||||
_generate_lib_artifact_name(target_name),
|
_generate_lib_artifact_name(target_name, target_triple),
|
||||||
extractor.get_version(),
|
extractor.get_version(),
|
||||||
)
|
)
|
||||||
render = CMakeRender(properties)
|
render = CMakeRender(properties)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ themselves, so the accepted options and their semantics live in one place.
|
|||||||
import argparse
|
import argparse
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from .utils import Triple
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -31,6 +32,10 @@ class Cli:
|
|||||||
"""Destination of the zip archive bundling the distribution tree, or
|
"""Destination of the zip archive bundling the distribution tree, or
|
||||||
``None`` when no archive is requested."""
|
``None`` when no archive is requested."""
|
||||||
|
|
||||||
|
target: Triple | None
|
||||||
|
"""Target triple to pack for (e.g. ``x86_64-pc-windows-msvc``), or ``None``
|
||||||
|
to pack for the host toolchain."""
|
||||||
|
|
||||||
|
|
||||||
def parse() -> Cli:
|
def parse() -> Cli:
|
||||||
"""Parse command-line arguments into a :class:`Cli` instance.
|
"""Parse command-line arguments into a :class:`Cli` instance.
|
||||||
@@ -77,9 +82,20 @@ def parse() -> Cli:
|
|||||||
help="Path of the zip archive created from the dist-dir contents",
|
help="Path of the zip archive created from the dist-dir contents",
|
||||||
metavar="ZIP",
|
metavar="ZIP",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--target",
|
||||||
|
dest="target",
|
||||||
|
action="store",
|
||||||
|
type=Triple.parse,
|
||||||
|
required=False,
|
||||||
|
help="Target triple to pack for (e.g. x86_64-pc-windows-msvc). Defaults to the host toolchain.",
|
||||||
|
metavar="TRIPLE",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
return Cli(
|
return Cli(
|
||||||
manifest=args.manifest,
|
manifest=args.manifest,
|
||||||
dist_dir=args.dist_dir,
|
dist_dir=args.dist_dir,
|
||||||
dist_zip=args.dist_zip,
|
dist_zip=args.dist_zip,
|
||||||
|
target=args.target,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import subprocess
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from re import Pattern, compile
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
from semver import Version
|
from semver import Version
|
||||||
from . import utils
|
from . import utils
|
||||||
from .utils import dict_chain_get, dict_typed_get, dict_typed_get_required, VERSION
|
from .utils import Triple, VERSION, dict_chain_get, dict_typed_get, dict_typed_get_required
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -192,6 +193,12 @@ def wrap_metadata_errors[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
_SUBPROCESS_TIMEOUT_SEC: int = 10
|
||||||
|
"""Timeout in seconds for the ``cargo``/``rustc`` subprocesses launched below."""
|
||||||
|
|
||||||
|
_HOST_TRIPLE_PATTERN: Pattern = compile(r"(?m)^host:\s*(\S+)$")
|
||||||
|
"""The pattern for match host triple in ``rustc -vV``. The group 1 is the triple result."""
|
||||||
|
|
||||||
class MetadataExtractor:
|
class MetadataExtractor:
|
||||||
"""Access layer over ``cargo metadata`` and the OMRF metadata table.
|
"""Access layer over ``cargo metadata`` and the OMRF metadata table.
|
||||||
|
|
||||||
@@ -208,6 +215,8 @@ class MetadataExtractor:
|
|||||||
"""The package item in cargo metadata's packages list pointing to user request package"""
|
"""The package item in cargo metadata's packages list pointing to user request package"""
|
||||||
__metadata_target: dict[str, Any]
|
__metadata_target: dict[str, Any]
|
||||||
"""The target item in package item's targets list pointing to the main target"""
|
"""The target item in package item's targets list pointing to the main target"""
|
||||||
|
__host_triple: Triple
|
||||||
|
"""The host toolchain triple resolved from ``rustc -vV``"""
|
||||||
|
|
||||||
def __init__(self, cargo_toml_path: Path) -> None:
|
def __init__(self, cargo_toml_path: Path) -> None:
|
||||||
"""Run ``cargo metadata`` and locate the package and main target.
|
"""Run ``cargo metadata`` and locate the package and main target.
|
||||||
@@ -221,6 +230,35 @@ class MetadataExtractor:
|
|||||||
self.__metadata_target = MetadataExtractor.__extract_metadata_target(
|
self.__metadata_target = MetadataExtractor.__extract_metadata_target(
|
||||||
self.__metadata_package, cargo_toml_path
|
self.__metadata_package, cargo_toml_path
|
||||||
)
|
)
|
||||||
|
self.__host_triple = MetadataExtractor.__extract_host_triple()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@wrap_metadata_errors
|
||||||
|
def __extract_host_triple() -> Triple:
|
||||||
|
"""Query ``rustc -vV`` and parse its ``host:`` line into a :class:`Triple`.
|
||||||
|
|
||||||
|
:raises RuntimeError: if rustc times out, exits with a non-zero status,
|
||||||
|
or its output carries no ``host:`` line.
|
||||||
|
"""
|
||||||
|
rustc = os.getenv("OMRF_PACKER_RUSTC", "rustc")
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[rustc, "-vV"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
stdout, stderr = proc.communicate(timeout=_SUBPROCESS_TIMEOUT_SEC)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
proc.kill()
|
||||||
|
proc.communicate()
|
||||||
|
raise RuntimeError("fail to fetch host triple: timed out")
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
"fail to fetch host triple: "
|
||||||
|
+ stderr.decode("utf-8", errors="ignore")
|
||||||
|
)
|
||||||
|
m = _HOST_TRIPLE_PATTERN.search(stdout.decode("utf-8", errors="strict"))
|
||||||
|
if m is None:
|
||||||
|
raise RuntimeError("can not find host triple in rustc -vV output")
|
||||||
|
return Triple.parse(m.group(1))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@wrap_metadata_errors
|
@wrap_metadata_errors
|
||||||
@@ -243,7 +281,7 @@ class MetadataExtractor:
|
|||||||
]
|
]
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
try:
|
try:
|
||||||
stdout, stderr = proc.communicate(timeout=10)
|
stdout, stderr = proc.communicate(timeout=_SUBPROCESS_TIMEOUT_SEC)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
proc.kill()
|
proc.kill()
|
||||||
proc.communicate()
|
proc.communicate()
|
||||||
@@ -349,3 +387,7 @@ class MetadataExtractor:
|
|||||||
def get_target_name(self) -> str:
|
def get_target_name(self) -> str:
|
||||||
"""Return the name of the main library target."""
|
"""Return the name of the main library target."""
|
||||||
return dict_typed_get_required(self.__metadata_target, "name", str)
|
return dict_typed_get_required(self.__metadata_target, "name", str)
|
||||||
|
|
||||||
|
def get_host_triple(self) -> Triple:
|
||||||
|
"""Return the host toolchain triple resolved at construction time."""
|
||||||
|
return self.__host_triple
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Provides version parsing/checking, typed dictionary access helpers, name
|
|||||||
validation and template-directory resolution.
|
validation and template-directory resolution.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from re import Pattern, compile
|
from re import Pattern, compile
|
||||||
from typing import Any, overload
|
from typing import Any, overload
|
||||||
@@ -129,3 +130,45 @@ def is_good_sentence(s: str) -> bool:
|
|||||||
:returns: True if given string don't contain any EOL chars, otherwise false.
|
:returns: True if given string don't contain any EOL chars, otherwise false.
|
||||||
"""
|
"""
|
||||||
return _EOL_PATTERN.search(s) is None
|
return _EOL_PATTERN.search(s) is None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Triple:
|
||||||
|
"""A Rust target triple (e.g. ``x86_64-pc-windows-msvc``).
|
||||||
|
|
||||||
|
Decomposes a triple into its ``arch``/``vendor``/``operating_system``/
|
||||||
|
``env`` components. Use :meth:`parse` to build one from a string and
|
||||||
|
``str(triple)`` to reconstruct the canonical dashed form.
|
||||||
|
"""
|
||||||
|
|
||||||
|
architecture: str
|
||||||
|
"""Architecture component (e.g. ``x86_64``, ``aarch64``)."""
|
||||||
|
vendor: str
|
||||||
|
"""Vendor component (e.g. ``pc``, ``unknown``, ``apple``)."""
|
||||||
|
operating_system: str
|
||||||
|
"""Operating-system component (e.g. ``windows``, ``linux``, ``darwin``)."""
|
||||||
|
environment: str | None
|
||||||
|
"""Environment/toolchain component (e.g. ``gnu``, ``msvc``), or ``None`` for
|
||||||
|
3-component triples."""
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
base = f"{self.architecture}-{self.vendor}-{self.operating_system}"
|
||||||
|
return f"{base}-{self.environment}" if self.environment is not None else base
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse(s: str) -> "Triple":
|
||||||
|
"""Parse a triple string into a :class:`Triple`.
|
||||||
|
|
||||||
|
:raises ValueError: if the string has fewer than 3 or more than 4
|
||||||
|
dash-separated components.
|
||||||
|
"""
|
||||||
|
parts = s.split("-")
|
||||||
|
if len(parts) < 3 or len(parts) > 4:
|
||||||
|
raise ValueError(f"invalid target triple: {s!r}")
|
||||||
|
env = parts[3] if len(parts) == 4 else None
|
||||||
|
return Triple(
|
||||||
|
architecture=parts[0],
|
||||||
|
vendor=parts[1],
|
||||||
|
operating_system=parts[2],
|
||||||
|
environment=env,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user