Compare commits
11
Commits
0b4ecccb71
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44adb10989 | ||
|
|
65e825f8db | ||
|
|
8eb24a0b71 | ||
|
|
0c21166e53 | ||
|
|
bad72120f9 | ||
|
|
0141c461da | ||
|
|
eb6453362d | ||
|
|
d5c99be4d8 | ||
|
|
d769af9efe | ||
|
|
f9333c72be | ||
|
|
bf0837e4a6 |
+14
-1
@@ -2,4 +2,17 @@
|
|||||||
|
|
||||||
## Bump Version Up
|
## Bump Version Up
|
||||||
|
|
||||||
TODO
|
### Bump OMRF Version Up
|
||||||
|
|
||||||
|
- Change the version declared in `Cargo.toml`.
|
||||||
|
|
||||||
|
### Bump OMRF Packer Version Up
|
||||||
|
|
||||||
|
- Change the version declared in `pyproject.toml`.
|
||||||
|
- Change the version constant `VERSION` declared in `utils.py`.
|
||||||
|
- Change the minimum required version declared in the metadata example section of `README.md`.
|
||||||
|
|
||||||
|
## Version Tag
|
||||||
|
|
||||||
|
- Use `omrf/x.y.z` to tag the version of `sarasacw-omrf`.
|
||||||
|
- Use `omrf-packer/x.y.z` to tag the version of `sarasacw-omrf-packer`.
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Allowed Data Types
|
||||||
|
|
||||||
|
Under this section we discuss which data types are allowed to appear in an FFI
|
||||||
|
interface and how to convert Rust data types into these allowed types. The
|
||||||
|
correspondence between these types and their C/C++ counterparts is covered in
|
||||||
|
[C/C++ Headers](c-cpp-headers.md).
|
||||||
|
|
||||||
|
## Primitive Type Conversion
|
||||||
|
|
||||||
|
This subsection covers the conversion of primitive types. The rules described here
|
||||||
|
apply to both parameter and return value positions.
|
||||||
|
|
||||||
|
### Types Allowed to Cross the Boundary Directly
|
||||||
|
|
||||||
|
The following Rust types are allowed to be passed directly across the FFI boundary:
|
||||||
|
|
||||||
|
- `bool`
|
||||||
|
- `f32`
|
||||||
|
- `f64`
|
||||||
|
- `i8`
|
||||||
|
- `i16`
|
||||||
|
- `i32`
|
||||||
|
- `i64`
|
||||||
|
- `u8`
|
||||||
|
- `u16`
|
||||||
|
- `u32`
|
||||||
|
- `u64`
|
||||||
|
- `usize`
|
||||||
|
- `isize`
|
||||||
|
|
||||||
|
### The u128 and i128 Problem
|
||||||
|
|
||||||
|
The `u128` and `i128` types have very poor support in the C/C++ standard libraries.
|
||||||
|
Therefore they cannot be passed directly across the FFI boundary.
|
||||||
|
|
||||||
|
To transfer data of these types, refer to the later section about passing opaque
|
||||||
|
structs.
|
||||||
|
|
||||||
|
### The char Problem
|
||||||
|
|
||||||
|
The Rust `char` type only holds valid Unicode scalar values. The Rust official
|
||||||
|
documentation explicitly states that the `char` type does not have FFI safety, so it
|
||||||
|
cannot be used as a value passed across the FFI boundary.
|
||||||
|
|
||||||
|
The solution is to use the Rust `u32` type for passing. Under this scheme:
|
||||||
|
|
||||||
|
- The function signature uses `u32` as the parameter type.
|
||||||
|
- For input parameters, use `char::try_from` to perform a fallible conversion that
|
||||||
|
validates the incoming character.
|
||||||
|
- For output parameters, use `u32::from` for the conversion.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# C/C++ Headers
|
||||||
|
|
||||||
|
Under this section we discuss how to write the C/C++ header files that accompany the
|
||||||
|
Rust FFI library. The Rust-side counterpart of each topic is covered in
|
||||||
|
[FFI Function Signatures](ffi-function-signatures.md) and
|
||||||
|
[Allowed Data Types](allowed-data-types.md).
|
||||||
|
|
||||||
|
## Function Declaration Style
|
||||||
|
|
||||||
|
FFI functions must be exported under C names, that is, they must not go through the
|
||||||
|
C++ name mangling mechanism. In the generated C header file, you need to use the
|
||||||
|
`extern "C"` specifier, the `__cplusplus` macro and macro conditionals to import the
|
||||||
|
declarations correctly, for example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
CError WFStartup(void);
|
||||||
|
|
||||||
|
// More FFI functions omitted...
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif // __cplusplus
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameter Declarations
|
||||||
|
|
||||||
|
A declaration of an exported FFI function in the C header file generally looks like
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
CError FBAdd(
|
||||||
|
OMRF_IN_PARAM_TY(uint32_t, in_a),
|
||||||
|
OMRF_IN_PARAM_TY(uint32_t, in_b),
|
||||||
|
OMRF_OUT_PARAM_TY(uint32_t, out_rv),
|
||||||
|
OMRF_OUT_PARAM_TY(bool, out_overflow));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required Header Files
|
||||||
|
|
||||||
|
The generated C/C++ header files must include the appropriate header files for the
|
||||||
|
types used in the declarations.
|
||||||
|
|
||||||
|
- In C, the `bool` type is provided by `<stddef.h>`. C++ supports `bool` natively
|
||||||
|
and does not need any header file.
|
||||||
|
- In C, the integer types are provided by `<stdint.h>`. In C++, they are provided
|
||||||
|
by `<cstdint>`.
|
||||||
|
- In C, the `uintptr_t` and `intptr_t` types are provided by `<stdint.h>`. In C++,
|
||||||
|
they are provided by `<cstdint>`.
|
||||||
|
- The floating-point types do not require any header file.
|
||||||
|
|
||||||
|
## Type Correspondences
|
||||||
|
|
||||||
|
The following table shows the correspondence between the Rust types that can cross
|
||||||
|
the FFI boundary directly (listed in [Allowed Data Types](allowed-data-types.md))
|
||||||
|
and their C/C++ counterparts. The left column lists the Rust type and the right
|
||||||
|
column lists the corresponding C/C++ type.
|
||||||
|
|
||||||
|
| Rust | C/C++ |
|
||||||
|
|------|-------|
|
||||||
|
| `bool` | `bool` |
|
||||||
|
| `f32` | `float` |
|
||||||
|
| `f64` | `double` |
|
||||||
|
| `i8` | `int8_t` |
|
||||||
|
| `i16` | `int16_t` |
|
||||||
|
| `i32` | `int32_t` |
|
||||||
|
| `i64` | `int64_t` |
|
||||||
|
| `u8` | `uint8_t` |
|
||||||
|
| `u16` | `uint16_t` |
|
||||||
|
| `u32` | `uint32_t` |
|
||||||
|
| `u64` | `uint64_t` |
|
||||||
|
| `usize` | `uintptr_t` |
|
||||||
|
| `isize` | `intptr_t` |
|
||||||
|
|
||||||
|
## The char Problem
|
||||||
|
|
||||||
|
For the Rust `char` type, the C/C++ header side uses `char32_t` as the parameter
|
||||||
|
type. Note that `char32_t` can hold any 32-bit unsigned integer, which is broader
|
||||||
|
than the set of valid Unicode scalar values carried by the Rust `char` type. The
|
||||||
|
Rust side is responsible for validating the incoming value; see
|
||||||
|
[The char Problem](allowed-data-types.md#the-char-problem) in
|
||||||
|
[Allowed Data Types](allowed-data-types.md).
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# FFI Function Signatures
|
||||||
|
|
||||||
|
Under this section we discuss the signature requirements of the exported FFI
|
||||||
|
functions.
|
||||||
|
|
||||||
|
## C-style Name Export
|
||||||
|
|
||||||
|
FFI functions must be exported under C names, that is, they must not go through the
|
||||||
|
C++ name mangling mechanism.
|
||||||
|
|
||||||
|
In Rust, you need to decorate the function with the `#[unsafe(no_mangle)]` attribute
|
||||||
|
together with `extern "C"`, for example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFStartup() -> CError {
|
||||||
|
// Function body omitted...
|
||||||
|
}
|
||||||
|
|
||||||
|
// More FFI functions omitted...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Export Name Style
|
||||||
|
|
||||||
|
In this subsection, we discuss the naming style of exported functions.
|
||||||
|
|
||||||
|
In this design, there are exactly two styles to choose from. We call them the
|
||||||
|
Windows style and the POSIX style. Either style can be chosen freely, but within a
|
||||||
|
single library the two styles must not be mixed.
|
||||||
|
|
||||||
|
We recommend the following selection rule: if your library is Windows-only, choose
|
||||||
|
the Windows style; otherwise, choose the POSIX style.
|
||||||
|
|
||||||
|
### Windows Style
|
||||||
|
|
||||||
|
The Windows style format is `<PREFIX><FUNC>` for ordinary functions, and
|
||||||
|
`<PREFIX><STRUCT><FUNC>` for opaque struct functions.
|
||||||
|
|
||||||
|
In the format, `<STRUCT>` is the name of the opaque struct and `<FUNC>` is the name
|
||||||
|
of the function. Both of them must use PascalCase naming.
|
||||||
|
|
||||||
|
For `<PREFIX>`, it is a prefix identifier that is specific to this library. As we
|
||||||
|
all know, C has no namespace concept, and all functions are exported into the same
|
||||||
|
space. Therefore, avoiding name collisions between functions is an important task.
|
||||||
|
By prepending `<PREFIX>` to the function name, name duplication can be avoided as
|
||||||
|
much as possible. For this reason, you need to choose `<PREFIX>` carefully and try
|
||||||
|
to pick a prefix that is unlikely to collide.
|
||||||
|
|
||||||
|
In the Windows style, there are two spellings for `<PREFIX>`. One is the
|
||||||
|
all-uppercase style and the other is the style in which only the first letter is
|
||||||
|
uppercase and all remaining letters are lowercase. Choose according to your own
|
||||||
|
needs. In addition, the length of `<PREFIX>` must not exceed 5 characters, and
|
||||||
|
it must not contain anything other than letters and digits (that is, underscores are
|
||||||
|
not allowed).
|
||||||
|
|
||||||
|
The following examples show some legal and illegal Windows style function names:
|
||||||
|
|
||||||
|
| Name | Verdict | Reason |
|
||||||
|
|------|---------|--------|
|
||||||
|
| `WFStartup` | Legal | An ordinary (non opaque struct) function. |
|
||||||
|
| `WFIconGetHandle` | Legal | An opaque struct function whose `<STRUCT>` is `Icon`. |
|
||||||
|
| `WFWordHandleGetHandle` | Legal | An opaque struct function whose `<STRUCT>` is `WordHandle`. |
|
||||||
|
| `FlGetMessage` | Legal | Uses a `<PREFIX>`, which is `Fl`, that capitalizes only its first letter. |
|
||||||
|
| `MyRustGetMessage` | Illegal | `<PREFIX>`, which is `MyRust`, does not keep all remaining letters lowercase. |
|
||||||
|
| `My_RustGetMessage` | Illegal | `<PREFIX>` contains an underscore. |
|
||||||
|
| `IconGetHandle` | Illegal | Missing `<PREFIX>`. |
|
||||||
|
| `QwertyuiopGetData` | Illegal | `<PREFIX>`, which is `Qwertyuiop`, is too long. |
|
||||||
|
| `WFget_message` | Illegal | `<FUNC>`, which is `get_message`, is not PascalCase. |
|
||||||
|
| `WFicon_handleGetMessage` | Illegal | `<STRUCT>`, which is `icon_handle`, is not PascalCase. |
|
||||||
|
|
||||||
|
### POSIX Style
|
||||||
|
|
||||||
|
The POSIX style format is `<PREFIX>_<FUNC>` for ordinary functions, and
|
||||||
|
`<PREFIX>_<STRUCT>_<FUNC>` for opaque struct functions.
|
||||||
|
|
||||||
|
The `<PREFIX>`, `<STRUCT>` and `<FUNC>` components in the format have the same
|
||||||
|
meaning as in the Windows style, but the format is different. `<STRUCT>` and `<FUNC>`
|
||||||
|
must use snake_case naming. `<PREFIX>` must be all lowercase, and it must not contain
|
||||||
|
anything other than letters and digits (that is, underscores are not allowed). Its
|
||||||
|
length and selection requirements are the same as in the Windows style.
|
||||||
|
|
||||||
|
The following examples show some legal and illegal POSIX style function names:
|
||||||
|
|
||||||
|
| Name | Verdict | Reason |
|
||||||
|
|------|---------|--------|
|
||||||
|
| `wf_startup` | Legal | An ordinary (non opaque struct) function. |
|
||||||
|
| `wf_icon_get_handle` | Legal | An opaque struct function whose `<STRUCT>` is `icon`. |
|
||||||
|
| `wf_word_handle_get_handle` | Legal | An opaque struct function whose `<STRUCT>` is `word_handle`. |
|
||||||
|
| `Fl_get_message` | Illegal | `<PREFIX>`, which is `Fl`, is not all lowercase. |
|
||||||
|
| `my_rust_get_message` | Illegal | `<PREFIX>`, which is `my_rust`, contains an underscore. |
|
||||||
|
| `icon_get_handle` | Illegal | Missing `<PREFIX>`. |
|
||||||
|
| `qwertyuiop_get_data` | Illegal | `<PREFIX>`, which is `qwertyuiop`, is too long. |
|
||||||
|
| `wf_GetMessage` | Illegal | `<FUNC>`, which is `GetMessage`, is not snake_case. |
|
||||||
|
| `wf_Icon_get_message` | Illegal | `<STRUCT>`, which is `Icon`, is not snake_case. |
|
||||||
|
|
||||||
|
## Parameters and Return Value
|
||||||
|
|
||||||
|
Under this section we discuss the parameters and return value of the exported
|
||||||
|
functions. A standard FFI function looks like the following on the Rust side:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn FBAdd(
|
||||||
|
in_a: in_param_ty!(u32),
|
||||||
|
in_b: in_param_ty!(u32),
|
||||||
|
out_rv: out_param_ty!(u32),
|
||||||
|
out_overflow: out_param_ty!(bool),
|
||||||
|
) -> CError {
|
||||||
|
// Function body omitted...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As shown in the example, this function has four parameters. The first two are input
|
||||||
|
parameters and the last two are output parameters. The return value of the function
|
||||||
|
is `CError`.
|
||||||
|
|
||||||
|
### Parameter Rules
|
||||||
|
|
||||||
|
To follow the best practice of C for the signature of functions with any number of
|
||||||
|
input parameters and output parameters, we must design the function signature this
|
||||||
|
way. To implement such a function that accepts any number of inputs and outputs, we
|
||||||
|
need to put both the input parameters and the output parameters into the parameter
|
||||||
|
list. All input parameters must precede any output parameter. The return value,
|
||||||
|
`CError`, is used only to indicate whether the function succeeded or failed.
|
||||||
|
|
||||||
|
The naming style of the function parameters (both input and output) must be
|
||||||
|
snake_case and must not start with an underscore.
|
||||||
|
|
||||||
|
#### Input Parameters
|
||||||
|
|
||||||
|
Input parameters are passed directly by value (for example, integers, floats or
|
||||||
|
pointers). In the example, `in_a` and `in_b` are both of type `u32`. You can use
|
||||||
|
the `in_param_ty!` macro provided by the crate to conveniently mark this.
|
||||||
|
|
||||||
|
By convention, input parameters are prefixed with `in_`, but this is not enforced.
|
||||||
|
|
||||||
|
#### Output Parameters
|
||||||
|
|
||||||
|
Output parameters are passed as pointers. In the example, `out_rv` is actually of
|
||||||
|
type `*mut u32` and `out_overflow` is actually of type `*mut bool`. To avoid
|
||||||
|
repeatedly writing these pointer types and to avoid writing them incorrectly, you
|
||||||
|
can use the `out_param_ty!` macro provided by the crate, which is symmetric with
|
||||||
|
`in_param_ty!`.
|
||||||
|
|
||||||
|
At the same time, you can use the `deref_out_param!` macro provided by the crate to
|
||||||
|
conveniently dereference an output parameter for assignment, or directly use the
|
||||||
|
`set_out_param!` macro provided by the crate for assignment. But you usually do not
|
||||||
|
need to do that, because in later chapters you will be guided on how to correctly
|
||||||
|
use `cffi_wrapper!` to maximize the efficiency of writing FFI functions.
|
||||||
|
|
||||||
|
By convention, output parameters are prefixed with `out_`, but this is not enforced.
|
||||||
|
|
||||||
|
### The Return Value
|
||||||
|
|
||||||
|
The return value of an FFI function must be of the `CError` type.
|
||||||
|
|
||||||
|
`CError` is defined in `last_error::CError` as a plain `u32`. It is not an enum with
|
||||||
|
a fixed set of members. Instead, `CERROR_OK` is the single value that represents
|
||||||
|
absolute, unambiguous success. Every other value's meaning is decided by the
|
||||||
|
consuming project: it may denote a failure, or, like some Win32 functions, a
|
||||||
|
partial success.
|
||||||
|
|
||||||
|
#### Migrating from a bool Return Value
|
||||||
|
|
||||||
|
Projects that previously returned a plain `bool` can adopt the error-code scheme
|
||||||
|
with minimal change: define exactly one non-success code, for example
|
||||||
|
`const CERROR_FAIL: CError = 1;`, and map every error type to it. The result is
|
||||||
|
equivalent to a `bool`: `CERROR_OK` for success and `CERROR_FAIL` for any failure,
|
||||||
|
while leaving room to introduce finer-grained codes later.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# C-friendly FFI Design
|
||||||
|
|
||||||
|
This document is a guide for authoring Rust libraries that expose a C-friendly FFI
|
||||||
|
and distributing them as ordinary CMake/pkg-config packages. It covers how to design
|
||||||
|
C-friendly FFI signatures, how to pass primitive types, enums, strings and
|
||||||
|
Rust-specific constructs such as `Option` and `Result` across the boundary, and how
|
||||||
|
to write the accompanying C/C++ header files.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- [FFI Function Signatures](ffi-function-signatures.md)
|
||||||
|
- [Allowed Data Types](allowed-data-types.md)
|
||||||
|
- [C/C++ Headers](c-cpp-headers.md)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sarasacw-omrf"
|
name = "sarasacw-omrf"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
authors = ["yyc12345 <yyc12321@outlook.com>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
description = "The facilities for Rust, specifically designed for the SarasaCW OMRF project, to support C-friendly FFI."
|
description = "The facilities for Rust, specifically designed for the SarasaCW OMRF project, to support C-friendly FFI."
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod cffi;
|
pub mod cffi;
|
||||||
pub mod cstr_ffi;
|
pub mod cstr_ffi;
|
||||||
pub mod last_error;
|
pub mod last_error;
|
||||||
|
pub mod library_lifecycle;
|
||||||
pub mod object_pool;
|
pub mod object_pool;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
//! Reference-counted startup/shutdown lifecycle for a DLL-style FFI library, in the spirit of
|
||||||
|
//! COM's `CoInitialize` / `CoUninitialize`.
|
||||||
|
//!
|
||||||
|
//! [`LibraryLifecycle`] holds an internal reference count together with a piece of state `S`:
|
||||||
|
//!
|
||||||
|
//! - on the `0 -> 1` transition ([`startup`](LibraryLifecycle::startup)) the supplied init closure
|
||||||
|
//! runs and its produced `S` is stored;
|
||||||
|
//! - on the `-> 0` transition ([`shutdown`](LibraryLifecycle::shutdown)) the stored `S` is taken
|
||||||
|
//! out and handed to the supplied destroy closure.
|
||||||
|
//!
|
||||||
|
//! Repeated calls are allowed as long as they are paired. The count and the state are guarded by a
|
||||||
|
//! [`RwLock`]; [`with_state`](LibraryLifecycle::with_state) takes a read lock (read access proceeds
|
||||||
|
//! concurrently) while `startup` / `shutdown` take a write lock.
|
||||||
|
//!
|
||||||
|
//! # Typical usage
|
||||||
|
//!
|
||||||
|
//! A single instance is usually stored in a `static LazyLock` and shared across all FFI entry
|
||||||
|
//! points. The state `S` typically aggregates the library's DLL-level resources -- for example a
|
||||||
|
//! number of [`ObjectPool`](crate::object_pool::ObjectPool)s and other globals that must be
|
||||||
|
//! constructed on first startup and torn down on last shutdown.
|
||||||
|
//!
|
||||||
|
//! # Call order
|
||||||
|
//!
|
||||||
|
//! Every `shutdown` must match a preceding successful `startup`. Calling `shutdown` when the count
|
||||||
|
//! is already zero is an error.
|
||||||
|
//!
|
||||||
|
//! # Closures
|
||||||
|
//!
|
||||||
|
//! Init and destroy closures are supplied **per call** (not at construction). Only the closure
|
||||||
|
//! passed to the transition call (`0 -> 1` for init, `-> 0` for destroy) is actually run; on any
|
||||||
|
//! other call the passed closure is dropped without being run, so callers should pass the same
|
||||||
|
//! init/destroy each time.
|
||||||
|
//!
|
||||||
|
//! The init closure may fail; its error is type-erased. The destroy closure must never fail
|
||||||
|
//! (destructor semantics) -- if its cleanup can fail it must be handled inside the closure.
|
||||||
|
//!
|
||||||
|
//! # Re-entrancy
|
||||||
|
//!
|
||||||
|
//! Closures run while the internal lock is held and **must not re-enter** the same
|
||||||
|
//! [`LibraryLifecycle`] (the lock is not re-entrant and would deadlock).
|
||||||
|
|
||||||
|
use std::sync::RwLock;
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
|
struct Inner<S> {
|
||||||
|
count: usize,
|
||||||
|
state: Option<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reference-counted startup/shutdown lifecycle holding a piece of state `S`.
|
||||||
|
///
|
||||||
|
/// See the module-level documentation for the overall contract, call order, and typical usage.
|
||||||
|
pub struct LibraryLifecycle<S> {
|
||||||
|
inner: RwLock<Inner<S>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error returned by [`LibraryLifecycle`] operations.
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
pub enum Error {
|
||||||
|
/// The initialization closure failed; the original error is type-erased.
|
||||||
|
#[error("initialization failed: {0}")]
|
||||||
|
Init(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
/// `shutdown` was called when the reference count was already zero (unbalanced call).
|
||||||
|
#[error("shutdown called when reference count is already zero")]
|
||||||
|
Underflow,
|
||||||
|
/// The state was accessed while the lifecycle is not active (before `startup` or after the
|
||||||
|
/// matching `shutdown`).
|
||||||
|
#[error("state accessed while not active")]
|
||||||
|
NotActive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> LibraryLifecycle<S> {
|
||||||
|
/// Create a new lifecycle with reference count zero and no state.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: RwLock::new(Inner {
|
||||||
|
count: 0,
|
||||||
|
state: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increment the reference count.
|
||||||
|
///
|
||||||
|
/// On the `0 -> 1` transition the supplied `init` closure runs and its produced `S` is stored;
|
||||||
|
/// the call returns `Ok(true)`. If `init` fails, neither the count nor the state is changed and
|
||||||
|
/// an error is returned.
|
||||||
|
///
|
||||||
|
/// On any other call (count already `> 0`) the supplied `init` is **dropped without being run**,
|
||||||
|
/// the count is simply incremented, and the call returns `Ok(false)`.
|
||||||
|
///
|
||||||
|
/// The init closure runs under the write lock and must not re-enter this lifecycle. Its error
|
||||||
|
/// type `E` is type-erased.
|
||||||
|
pub fn startup<E>(&self, init: impl FnOnce() -> Result<S, E>) -> Result<bool, Error>
|
||||||
|
where
|
||||||
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let mut inner = self.inner.write().expect("unexpected poison lock");
|
||||||
|
if inner.count == 0 {
|
||||||
|
let s = init().map_err(|e| Error::Init(Box::new(e)))?;
|
||||||
|
inner.state = Some(s);
|
||||||
|
inner.count = 1;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
inner.count += 1;
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrement the reference count.
|
||||||
|
///
|
||||||
|
/// On the `-> 0` transition the stored `S` is taken out and handed to the supplied `destroy`
|
||||||
|
/// closure, which must never fail (destructor semantics), and the call returns `Ok(true)`. On
|
||||||
|
/// any other call (count stays `> 0`) the supplied `destroy` is **dropped without being run**,
|
||||||
|
/// the count is simply decremented, and the call returns `Ok(false)`.
|
||||||
|
///
|
||||||
|
/// Returns an error if the count was already zero.
|
||||||
|
///
|
||||||
|
/// The destroy closure runs under the write lock and must not re-enter this lifecycle.
|
||||||
|
pub fn shutdown(&self, destroy: impl FnOnce(S)) -> Result<bool, Error> {
|
||||||
|
let mut inner = self.inner.write().expect("unexpected poison lock");
|
||||||
|
if inner.count == 0 {
|
||||||
|
return Err(Error::Underflow);
|
||||||
|
}
|
||||||
|
inner.count -= 1;
|
||||||
|
if inner.count == 0 {
|
||||||
|
// Invariant: state is Some whenever count > 0.
|
||||||
|
let s = inner.state.take().expect("state present while count > 0");
|
||||||
|
destroy(s);
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the stored state by shared reference under a read lock.
|
||||||
|
///
|
||||||
|
/// Multiple `with_state` calls can run concurrently. Returns an error if the lifecycle is not
|
||||||
|
/// currently active (count is zero).
|
||||||
|
///
|
||||||
|
/// `S` is immutable from the outside; if mutation is needed, build it into `S` via interior
|
||||||
|
/// mutability (e.g. `Mutex`, atomics). The closure must not re-enter this lifecycle.
|
||||||
|
pub fn with_state<R>(&self, f: impl FnOnce(&S) -> R) -> Result<R, Error> {
|
||||||
|
let inner = self.inner.read().expect("unexpected poison lock");
|
||||||
|
match &inner.state {
|
||||||
|
Some(s) => Ok(f(s)),
|
||||||
|
None => Err(Error::NotActive),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current reference count.
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.inner.read().expect("unexpected poison lock").count
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
//! Integration tests for the `library_lifecycle` module.
|
||||||
|
|
||||||
|
use sarasacw_omrf::library_lifecycle::LibraryLifecycle;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
|
#[derive(Debug, TeError)]
|
||||||
|
enum TestErr {
|
||||||
|
#[error("boom")]
|
||||||
|
Boom,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct State {
|
||||||
|
value: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage: a single paired startup/shutdown runs init once and destroy once, destroy receives the
|
||||||
|
/// state produced by init, both report that they ran the closure, and the count returns to zero.
|
||||||
|
#[test]
|
||||||
|
fn paired_startup_shutdown() {
|
||||||
|
let inits = AtomicUsize::new(0);
|
||||||
|
let destroys = AtomicUsize::new(0);
|
||||||
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
|
||||||
|
let ran_init = lc
|
||||||
|
.startup(|| -> Result<State, TestErr> {
|
||||||
|
inits.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(State { value: 42 })
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(ran_init);
|
||||||
|
|
||||||
|
let ran_destroy = lc
|
||||||
|
.shutdown(|s| {
|
||||||
|
destroys.fetch_add(1, Ordering::SeqCst);
|
||||||
|
assert_eq!(s.value, 42);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(ran_destroy);
|
||||||
|
|
||||||
|
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
||||||
|
assert_eq!(destroys.load(Ordering::SeqCst), 1);
|
||||||
|
assert_eq!(lc.count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage: multiple startups followed by matching shutdowns run init and destroy only on the
|
||||||
|
/// boundary transitions (once each); the init/destroy closures passed on non-transition calls are
|
||||||
|
/// dropped without being run, and the bool return reflects exactly which calls ran a closure.
|
||||||
|
#[test]
|
||||||
|
fn multiple_startups_shutdowns_run_only_on_boundaries() {
|
||||||
|
let inits = AtomicUsize::new(0);
|
||||||
|
let destroys = AtomicUsize::new(0);
|
||||||
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
|
||||||
|
let startup_ran: Vec<bool> = (0..3)
|
||||||
|
.map(|_| {
|
||||||
|
lc.startup(|| -> Result<State, TestErr> {
|
||||||
|
inits.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(State { value: 7 })
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert_eq!(startup_ran, vec![true, false, false]);
|
||||||
|
assert_eq!(lc.count(), 3);
|
||||||
|
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
||||||
|
|
||||||
|
let shutdown_ran: Vec<bool> = (0..3)
|
||||||
|
.map(|_| {
|
||||||
|
lc.shutdown(|_s| {
|
||||||
|
destroys.fetch_add(1, Ordering::SeqCst);
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert_eq!(shutdown_ran, vec![false, false, true]);
|
||||||
|
|
||||||
|
assert_eq!(inits.load(Ordering::SeqCst), 1);
|
||||||
|
assert_eq!(destroys.load(Ordering::SeqCst), 1);
|
||||||
|
assert_eq!(lc.count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage: after a full cycle, a new cycle re-runs init and destroy (re-initialization works).
|
||||||
|
#[test]
|
||||||
|
fn reinitialization_runs_init_and_destroy_again() {
|
||||||
|
let inits = AtomicUsize::new(0);
|
||||||
|
let destroys = AtomicUsize::new(0);
|
||||||
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
|
||||||
|
for _ in 0..2 {
|
||||||
|
let ran_init = lc
|
||||||
|
.startup(|| -> Result<State, TestErr> {
|
||||||
|
inits.fetch_add(1, Ordering::SeqCst);
|
||||||
|
Ok(State { value: 1 })
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(ran_init);
|
||||||
|
let ran_destroy = lc
|
||||||
|
.shutdown(|_s| {
|
||||||
|
destroys.fetch_add(1, Ordering::SeqCst);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(ran_destroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(inits.load(Ordering::SeqCst), 2);
|
||||||
|
assert_eq!(destroys.load(Ordering::SeqCst), 2);
|
||||||
|
assert_eq!(lc.count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage: `shutdown` with the count already at zero is an error (unbalanced call).
|
||||||
|
#[test]
|
||||||
|
fn shutdown_when_idle_is_error() {
|
||||||
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
assert!(lc.shutdown(|_| ()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage: `with_state` before any `startup` (and after the matching `shutdown`) is an error
|
||||||
|
/// because there is no active state.
|
||||||
|
#[test]
|
||||||
|
fn with_state_when_not_active_is_error() {
|
||||||
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
assert!(lc.with_state(|_s| ()).is_err());
|
||||||
|
|
||||||
|
lc.startup(|| -> Result<State, TestErr> { Ok(State { value: 5 }) })
|
||||||
|
.unwrap();
|
||||||
|
lc.shutdown(|_| ()).unwrap();
|
||||||
|
assert!(lc.with_state(|_s| ()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage: a failing init closure leaves the count at zero and the state absent, and the error is
|
||||||
|
/// propagated (type-erased into `Error::Init`).
|
||||||
|
#[test]
|
||||||
|
fn init_failure_keeps_count_zero_and_propagates_error() {
|
||||||
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
let r = lc.startup(|| -> Result<State, TestErr> { Err(TestErr::Boom) });
|
||||||
|
assert!(r.is_err());
|
||||||
|
assert_eq!(lc.count(), 0);
|
||||||
|
// State is absent, so accessing it errors.
|
||||||
|
assert!(lc.with_state(|_s| ()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coverage: `with_state` reads the stored state during an active lifecycle.
|
||||||
|
#[test]
|
||||||
|
fn with_state_reads_active_state() {
|
||||||
|
let lc: LibraryLifecycle<State> = LibraryLifecycle::new();
|
||||||
|
lc.startup(|| -> Result<State, TestErr> { Ok(State { value: 99 }) })
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let observed = lc.with_state(|s| s.value).unwrap();
|
||||||
|
assert_eq!(observed, 99);
|
||||||
|
|
||||||
|
lc.shutdown(|_| ()).unwrap();
|
||||||
|
}
|
||||||
+51
-2
@@ -1,5 +1,52 @@
|
|||||||
# sarasacw-omrf-packer
|
# sarasacw-omrf-packer
|
||||||
|
|
||||||
|
`sarasacw-omrf-packer` is the distribution packer of the Sarasas Chip Workshop Oh My Rust FFI (OMRF) toolset.
|
||||||
|
Given a Rust project that exposes a C-friendly FFI as a `cdylib`, it assembles a CMake-style redistributable tree.
|
||||||
|
It involves header files, the built dynamic library, and generated CMake and pkg-config package files,
|
||||||
|
and it can additionally bundle the result into a zip archive.
|
||||||
|
|
||||||
|
It targets Rust FFI projects that need to be distributed and consumed like ordinary CMake/pkg-config packages,
|
||||||
|
including on MSVC-only Windows toolchains where pkg-config alone is not enough.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Barely works for the specific workflow of Sarasas Chip Workshop.
|
||||||
|
And will be improved by new requirement coming from Sarasas Chip Workshop devlopment.
|
||||||
|
|
||||||
|
Projects declare the minimum required packer version through the `min_version` field in their `[package.metadata.omrf]` table;
|
||||||
|
running an older packer than requested fails fast with a clear error.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under the MIT License.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Install from PyPI (the project is built with `uv`, but `pipx install` or `pip install` work just as well on the published wheel):
|
||||||
|
|
||||||
|
```console
|
||||||
|
uv tool install sarasacw-omrf-packer
|
||||||
|
```
|
||||||
|
|
||||||
|
The packer does not build the project itself. Build it first -- `cargo build --release`, adding `--target <triple>` for cross-compilation -- then run:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sarasacw-omrf-packer -m path/to/Cargo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo` and `rustc` are resolved from `PATH`; override them with the environment variables listed in the [Environment Variables](#environment-variables) section.
|
||||||
|
|
||||||
|
### Command-line options
|
||||||
|
|
||||||
|
| Option | Required | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `-m`, `--manifest <CARGO.TOML>` | yes | Path to the `Cargo.toml` of the project to pack. |
|
||||||
|
| `-d`, `--dist-dir <DIR>` | no | Directory where the redistributable tree is materialized (`bin/`, `include/`, `lib/`, ...). |
|
||||||
|
| `-z`, `--dist-zip <ZIP>` | no | Path of the zip archive produced from the redistributable tree. |
|
||||||
|
| `-t`, `--target <TRIPLE>` | no | Target triple to pack for (e.g. `x86_64-pc-windows-msvc`). Defaults to the host toolchain triple. |
|
||||||
|
|
||||||
|
`--dist-dir` and `--dist-zip` are independent: pass either, both, or neither. Omitting both performs a dry run that validates the configuration without writing any output. Run `sarasacw-omrf-packer --help` for the authoritative list.
|
||||||
|
|
||||||
## Metadata
|
## 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.
|
||||||
@@ -77,5 +124,7 @@ requires = ["libfoo >= 1.0", "libbar"]
|
|||||||
|
|
||||||
## Environment Variables
|
## 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`.
|
- `OMRF_PACKER_CARGO`: Path to the `cargo` executable used to collect project metadata.
|
||||||
- `OMRF_PACKER_RUSTC`: Path to the `rustc` executable used to resolve the host toolchain triple. When unset, the packer invokes `rustc` as resolved from `PATH`.
|
When unset, the packer invokes `cargo` as resolved from `PATH`.
|
||||||
|
- `OMRF_PACKER_RUSTC`: Path to the `rustc` executable used to resolve the host toolchain triple.
|
||||||
|
When unset, the packer invokes `rustc` as resolved from `PATH`.
|
||||||
|
|||||||
+20
-1
@@ -1,12 +1,26 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "sarasacw-omrf-packer"
|
name = "sarasacw-omrf-packer"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
description = "Add your description here"
|
description = "The packer for Rust, specifically designed for the SarasaCW OMRF project, to distribute C-friendly FFI dynamic library as CMake package."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "yyc12345", email = "yyc12321@outlook.com" }
|
{ name = "yyc12345", email = "yyc12321@outlook.com" }
|
||||||
]
|
]
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
license = { text = "MIT" }
|
||||||
|
keywords = ["rust", "ffi", "cmake", "pkg-config", "packaging", "omrf"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Topic :: Software Development :: Build Tools",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: Microsoft :: Windows",
|
||||||
|
"Operating System :: POSIX",
|
||||||
|
"Operating System :: MacOS",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jinja2==3.1.6",
|
"jinja2==3.1.6",
|
||||||
"semver>=3.0.4",
|
"semver>=3.0.4",
|
||||||
@@ -15,6 +29,11 @@ dependencies = [
|
|||||||
[project.scripts]
|
[project.scripts]
|
||||||
sarasacw-omrf-packer = "sarasacw_omrf_packer:main"
|
sarasacw-omrf-packer = "sarasacw_omrf_packer:main"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/SarasasChipWorkshop/sarasacw-omrf"
|
||||||
|
Repository = "https://github.com/SarasasChipWorkshop/sarasacw-omrf"
|
||||||
|
Issues = "https://github.com/SarasasChipWorkshop/sarasacw-omrf/issues"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build>=0.8.0,<0.9"]
|
requires = ["uv_build>=0.8.0,<0.9"]
|
||||||
build-backend = "uv_build"
|
build-backend = "uv_build"
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ class PkgConfigRender:
|
|||||||
payload: dict[str, Any] = {
|
payload: dict[str, Any] = {
|
||||||
"name": self.__properties.name,
|
"name": self.__properties.name,
|
||||||
"description": self.__properties.description,
|
"description": self.__properties.description,
|
||||||
|
"version": str(self.__properties.version),
|
||||||
"artifact": self.__properties.artifact,
|
"artifact": self.__properties.artifact,
|
||||||
"requires": self.__properties.requires,
|
"requires": self.__properties.requires,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# This file is created by sarasacw-omrf-packer and should not be changed manually.
|
||||||
|
|
||||||
{#
|
{#
|
||||||
Utilize ${pcfiledir} to fetch the directory where current .pc file is.
|
Utilize ${pcfiledir} to fetch the directory where current .pc file is.
|
||||||
And back to parent twice to obtain the installation directory.
|
And back to parent twice to obtain the installation directory.
|
||||||
@@ -10,7 +12,7 @@ includedir=${prefix}/include
|
|||||||
Name: {{ name }}
|
Name: {{ name }}
|
||||||
Description: {{ description }}
|
Description: {{ description }}
|
||||||
Version: {{ version }}
|
Version: {{ version }}
|
||||||
{% if len(requires) != 0 -%}
|
{%- if requires | length != 0 %}
|
||||||
Requires: {{ requires | join(", ") }}
|
Requires: {{ requires | join(", ") }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
Libs: -L${libdir} -l{{ artifact }}
|
Libs: -L${libdir} -l{{ artifact }}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# This file is created by sarasacw-omrf-packer and should not be changed manually.
|
||||||
|
|
||||||
{# Check whether this target is already exported -#}
|
{# Check whether this target is already exported -#}
|
||||||
if(TARGET {{ namespace }}::{{ target }})
|
if(TARGET {{ namespace }}::{{ target }})
|
||||||
message(STATUS "Target {{ namespace }}::{{ target }} is already defined.")
|
message(STATUS "Target {{ namespace }}::{{ target }} is already defined.")
|
||||||
@@ -16,32 +18,45 @@ set(MyRust_LIBRARY "${_IMPORT_PREFIX}/lib/{{ artifact_dll }}")
|
|||||||
set(MyRust_INCLUDE_DIR "${_IMPORT_PREFIX}/include")
|
set(MyRust_INCLUDE_DIR "${_IMPORT_PREFIX}/include")
|
||||||
|
|
||||||
{# Create modern CMake target (IMPORTED target) -#}
|
{# Create modern CMake target (IMPORTED target) -#}
|
||||||
add_library({{ namespace }}::{{ target }} SHARED IMPORTED)
|
add_library({{ target }} SHARED IMPORTED)
|
||||||
set_target_properties({{ namespace }}::{{ target }} PROPERTIES
|
add_library({{ namespace }}::{{ target }} ALIAS {{ target }})
|
||||||
|
set_target_properties({{ target }} PROPERTIES
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
|
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
|
||||||
)
|
)
|
||||||
{# Handle Windows and UNIX-like respectively -#}
|
{# Handle Windows and UNIX-like respectively -#}
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set_target_properties({{ namespace }}::{{ target }} PROPERTIES
|
set_target_properties({{ target }} PROPERTIES
|
||||||
IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/{{ artifact_dll }}"
|
IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/{{ artifact_dll }}"
|
||||||
IMPORTED_IMPLIB "${_IMPORT_PREFIX}/lib/{{ artifact_lib }}"
|
IMPORTED_IMPLIB "${_IMPORT_PREFIX}/lib/{{ artifact_lib }}"
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
set_target_properties({{ namespace }}::{{ target }} PROPERTIES
|
set_target_properties({{ target }} PROPERTIES
|
||||||
IMPORTED_LOCATION "${_IMPORT_PREFIX}/lib/{{ artifact_dll }}"
|
IMPORTED_LOCATION "${_IMPORT_PREFIX}/lib/{{ artifact_dll }}"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
{% if len(dependencies) != 0 -%}
|
{# Cleanup temporary variables -#}
|
||||||
|
set(_IMPORT_PREFIX)
|
||||||
|
|
||||||
|
{% if dependencies | length != 0 -%}
|
||||||
{# Pull in declared dependencies and wire them into the imported target -#}
|
{# Pull in declared dependencies and wire them into the imported target -#}
|
||||||
include(CMakeFindDependencyMacro)
|
include(CMakeFindDependencyMacro)
|
||||||
{% for dep in dependencies -%}
|
{% for dep in dependencies -%}
|
||||||
find_dependency({{ dep.package }})
|
find_dependency({{ dep.package }})
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
set_target_properties({{ namespace }}::{{ target }} PROPERTIES
|
set_target_properties({{ target }} PROPERTIES
|
||||||
INTERFACE_LINK_LIBRARIES "{{ dependencies | map(attribute='target') | join(';') }}"
|
INTERFACE_LINK_LIBRARIES "{{ dependencies | map(attribute='target') | join(';') }}"
|
||||||
)
|
)
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{# Cleanup temporary variables -#}
|
{# Verify component (although there is no component) -#}
|
||||||
set(_IMPORT_PREFIX)
|
macro(check_required_components _NAME)
|
||||||
|
foreach(comp ${${_NAME}_FIND_COMPONENTS})
|
||||||
|
if(NOT ${_NAME}_${comp}_FOUND)
|
||||||
|
if(${_NAME}_FIND_REQUIRED_${comp})
|
||||||
|
set(${_NAME}_FOUND FALSE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
check_required_components({{ target }})
|
||||||
|
|||||||
Reference in New Issue
Block a user