Compare commits

...
6 Commits
Author SHA1 Message Date
yyc12345 f25e5e7d0d fix: fix omrf packer template error 2026-08-18 20:30:37 +08:00
yyc12345 6e807fd62d doc: update README and DEVNOTES 2026-08-18 07:59:12 +08:00
yyc12345 da56800265 doc: update ffi design 2026-08-17 22:04:56 +08:00
yyc12345 b9e9039c83 feat: change packer dependency version restrictions 2026-08-17 15:51:09 +08:00
yyc12345 40fee18194 doc: update ffi design 2026-08-14 16:38:09 +08:00
yyc12345 d7e92d588f doc: update omrf docstring 2026-08-14 13:37:41 +08:00
16 changed files with 371 additions and 58 deletions
+1
View File
@@ -5,6 +5,7 @@
### Bump OMRF Version Up ### Bump OMRF Version Up
- Change the version declared in `Cargo.toml`. - Change the version declared in `Cargo.toml`.
- Change the version of dependency example in FFI design manual `rust-side/misc.md`
### Bump OMRF Packer Version Up ### Bump OMRF Packer Version Up
+2 -2
View File
@@ -16,7 +16,7 @@ So we develop it for our special requirements.
### Not `cbindgen` ### Not `cbindgen`
`cbindgen` is a good tool but it only simply resolve only one Rust source file. `cbindgen` is a good tool but it only simply resolve only one Rust source file.
It can't handle `use` syntax and means that we need put all things into single file. It can't handle `use` syntax. This means that we need put all things into single file.
For a large-scale FFI interface, this behavior is unacceptable. For a large-scale FFI interface, this behavior is unacceptable.
### Not `Diplomat` ### Not `Diplomat`
@@ -25,7 +25,7 @@ Mozilla developed `Diplomat` is another great tool but it still doesn't fit our
`Diplomat` prefers integrating Rust in workflow rather than distributing Rust built artifacts. `Diplomat` prefers integrating Rust in workflow rather than distributing Rust built artifacts.
Although `Diplomat` generated C/C++ header files can correctly process module relation and `use` syntax, Although `Diplomat` generated C/C++ header files can correctly process module relation and `use` syntax,
it generated header files involve too much hacks and memory layout assumption based on target triple. it generated header files involve too much hacks and memory layout assumption based on target triple.
This behavior causes that it generated header files only works on build machine and can not be distributed. This behavior causes that it generated header files only works on target machine and can not be distributed.
It violates our requirements that we want our developed Rust projects can be distributed like a normal CMake project. It violates our requirements that we want our developed Rust projects can be distributed like a normal CMake project.
Sarasas Chip Workshop developed Rust projects has no requirement that exposing complex structs like Sarasas Chip Workshop developed Rust projects has no requirement that exposing complex structs like
@@ -5,25 +5,6 @@ Rust FFI library. The Rust-side counterpart of each topic is covered in
[FFI Function Signatures](ffi-function-signatures.md) and [FFI Function Signatures](ffi-function-signatures.md) and
[Allowed Data Types](allowed-data-types.md). [Allowed Data Types](allowed-data-types.md).
## Include Guard
When writing the include guard of a C header file, we require using both the modern `#pragma once` approach and the traditional approach based on the `#ifndef`, `#define` and `#endif` preprocessing directives. The file should be written like this:
```c
#pragma once
#ifndef SOME_HEADER_H_
#define SOME_HEADER_H_
// The actual content of the header file...
#endif // SOME_HEADER_H_
```
The `SOME_HEADER` prefix is usually related to the name of the header file. It must consist of all uppercase characters, but underscores are allowed. The `_H_` suffix is fixed and cannot be changed; this suffix style originates from the Linux kernel.
For example, when the header file is named `wfassoc.h`, the macro name would be `WFASSOC_H_`.
However, if the name of the header file is too simple and may collide with other header files, it would be better to prepend something to the prefix to remove this ambiguity. For example, for a project called MyRust with a header file named `utils.h`, a good macro name would be `MY_RUST_UTILS_H_`.
## Function Declaration Style ## Function Declaration Style
FFI functions must be exported under C names, that is, they must not go through the FFI functions must be exported under C names, that is, they must not go through the
@@ -1,6 +1,2 @@
# C++ Header # C++ Header
## Include Guard
The include guard requirement in the C++ header files is identical to the one in the C header files.
@@ -78,7 +78,7 @@ An example is shown below:
* @param[out] out_c Example output parameter. * @param[out] out_c Example output parameter.
* @return Example return value. * @return Example return value.
*/ */
int FlFooBar(OMRF_IN(int) in_a, OMRF_IN(int) in_b, OMRF_OUT(int) out_c); int FlFooBar(OMRF_IN_PARAM_TY(int) in_a, OMRF_IN_PARAM_TY(int) in_b, OMRF_OUT_PARAM_TY(int) out_c);
``` ```
### Object Docstring ### Object Docstring
@@ -352,7 +352,7 @@ When the following situations occur, additional requirements apply to the corres
When a parameter carries a pointer, the `@param` annotation of that parameter must state whether the pointer could be NULL (for output) or whether passing NULL is allowed (for input). When a parameter carries a pointer, the `@param` annotation of that parameter must state whether the pointer could be NULL (for output) or whether passing NULL is allowed (for input).
Note that the pointer mentioned here refers to the case where the type decorated by `OMRF_IN` or `OMRF_OUT` is itself a pointer type. Since every output parameter is passed by pointer, the pointer mentioned above does not refer to that pointer created by the output decoration. Note that the pointer mentioned here refers to the case where the type decorated by `OMRF_IN_PARAM_TY` or `OMRF_OUT_PARAM_TY` is itself a pointer type. Since every output parameter is passed by pointer, the pointer mentioned above does not refer to that pointer created by the output decoration.
### String Lifetime ### String Lifetime
@@ -1 +1,47 @@
# Miscellaneous Stuff # Miscellaneous Stuff
## Include Guard
When writing the include guard of a C/C++ header file, we require using both the modern `#pragma once` approach and the traditional approach based on the `#ifndef`, `#define` and `#endif` preprocessing directives. The file should be written like this:
```c
#pragma once
#ifndef SOME_HEADER_H_
#define SOME_HEADER_H_
// The actual content of the header file...
#endif // SOME_HEADER_H_
```
The `SOME_HEADER` prefix is usually related to the name of the header file. It must consist of all uppercase characters, but underscores are allowed. The `_H_` suffix is fixed and cannot be changed; this suffix style originates from the Linux kernel.
For example, when the header file is named `wfassoc.h`, the macro name would be `WFASSOC_H_`.
However, if the name of the header file is too simple and may collide with other header files, it would be better to prepend something to the prefix to remove this ambiguity. For example, for a project called MyRust with a header file named `utils.h`, a good macro name would be `MY_RUST_UTILS_H_`.
## Block Closing Annotations
When we use preprocessor directives such as `#if` or `#ifdef` to conditionally enable or disable some code, we require you to annotate the condition after its matching `#else` and `#endif`, as shown below:
```c
#ifdef __cplusplus
// Contents omitted...
#else // __cplusplus
// Contents omitted...
#endif // __cplusplus
```
Besides the preprocessor directives, you also need to write similar closing annotations for `namespace` (C++ only) and `extern "C"` blocks (C++ only), for example:
```c++
extern "C" {
// Contents omitted...
} // extern "C"
namespace foobar {
// Contents omitted...
} // namespace foobar
```
Note that the spaces around the `//` comment markers in the examples are required.
+1
View File
@@ -10,6 +10,7 @@ to write the accompanying C/C++ header files.
* [Rust Side](rust-side.md) * [Rust Side](rust-side.md)
- [FFI Function Signature](rust-side/func-signature.md) - [FFI Function Signature](rust-side/func-signature.md)
- [Library Architecture](rust-side/library-architecture.md)
- [Allowed Data Types](rust-side/data-types.md) - [Allowed Data Types](rust-side/data-types.md)
- [FFI Example](rust-side/example.md) - [FFI Example](rust-side/example.md)
- [Miscellaneous Stuff](rust-side/misc.md) - [Miscellaneous Stuff](rust-side/misc.md)
@@ -1,18 +1,12 @@
# Allowed Data Types # Allowed Data Types
Under this section we discuss which data types are allowed to appear in an FFI 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.
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 Please note that all "type" word are referred to the argument of `in_param_ty!` or `out_param_ty!`. These "type"s are not the final types in FFI signature. They will be decorated by these macros.
This subsection covers the conversion of primitive types. The rules described here ## Directly Passed Primitive Types
apply to both parameter and return value positions.
### Types Allowed to Cross the Boundary Directly The following Rust primitive types are allowed to be passed directly across the FFI boundary:
The following Rust types are allowed to be passed directly across the FFI boundary:
- `bool` - `bool`
- `f32` - `f32`
@@ -28,23 +22,196 @@ The following Rust types are allowed to be passed directly across the FFI bounda
- `usize` - `usize`
- `isize` - `isize`
### The u128 and i128 Problem ## `u128` and `i128` Types
The `u128` and `i128` types have very poor support in the C/C++ standard libraries. 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.
Therefore they cannot be passed directly across the FFI boundary.
To transfer data of these types, refer to the later section about passing opaque To transfer data of these types, refer to the later section about passing opaque structs.
structs.
### The char Problem ## `char` Type
The Rust `char` type only holds valid Unicode scalar values. The Rust official 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.
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 solution is to use the Rust `u32` type for passing. Under this scheme:
- The function signature uses `u32` as the parameter type. - The function signature uses `u32` as the parameter type.
- For input parameters, use `char::try_from` to perform a fallible conversion that - For input parameters, use `char::try_from` to perform a fallible conversion that validates the incoming character.
validates the incoming character.
- For output parameters, use `u32::from` for the conversion. - For output parameters, use `u32::from` for the conversion.
## Enum Types
Enum types in Rust are quite diverse, so we need to adopt different strategies for different variants.
### Plain Enums
The plain enum type is the simplest kind, as shown below:
```rust
enum Plain {
Entry1,
Entry2,
}
```
For a plain enum type, we first need to choose an underlying type for it, just like the `enum class` in C++. The chosen underlying type needs to be specified on this enum type via the `repr` attribute. Then, when passing across the FFI boundary, we use this underlying type. Specifically:
- Use the chosen underlying type as the parameter type in the function signature.
- For input parameters, convert from the chosen underlying type to our enum, and verify that it is a valid enum field value.
- For output parameters, convert the enum to the chosen underlying type, and then pass it out.
These conversion and validation methods for input and output parameters can be written by hand, but we recommend using an existing package to accomplish this more conveniently. Choose the library suitable for your FFI project according to the following situations:
- Use the `num_enum` package (lightweight; use it when your project does not use `strum`).
- Use the `strum` and `strum_macros` packages (heavier; use them when your project already uses `strum`).
#### Using `num_enum`
An example of using the `num_enum` package to conveniently perform conversion and validation is as follows:
```rust
#[derive(Debug, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
enum Number {
Zero,
One,
}
fn foobar() {
// Convert the enum to the chosen underlying type.
let zero: u8 = Number::Zero.into();
assert_eq!(zero, 0u8);
// Convert from the chosen underlying type to the enum (success case).
let zero = Number::try_from(0u8);
assert_eq!(zero, Ok(Number::Zero));
// Convert from the chosen underlying type to the enum (failure case).
let three = Number::try_from(3u8);
assert!(three.is_err());
}
```
#### Using `strum` and `strum_macros`
An example of using the `strum` and `strum_macros` packages to conveniently perform conversion and validation is as follows:
```rust
#[derive(Debug, PartialEq, FromRepr)]
#[repr(u8)]
enum Number {
One = 1,
Three = 3,
}
fn foobar() {
// Convert the enum to the chosen underlying type.
let one: u8 = Number::One as u8;
assert_eq!(one, 1u8);
// Convert from the chosen underlying type to the enum (success case).
assert_eq!(Some(Number::One), Number::from_repr(1));
// Convert from the chosen underlying type to the enum (failure case).
assert_eq!(None, Number::from_repr(0));
}
```
#### Enum Types from Other Crates
In most cases, the enum type comes from another crate (because we develop with the two-project structure). In this case, Rust does not allow you to attach attribute tags to a type from outside your own crate. The solution is to create a wrapper type in your own crate, decorate it with attributes, and implement the bidirectional `From` traits. As shown below:
```rust
use another_crate::Example as InternExample;
#[repr(u8)]
enum Example {
// Entries omitted...
}
impl From<Example> for InternExample {
// Implementation omitted...
}
impl From<InternExample> for Example {
// Implementation omitted...
}
```
### Flag Enums
The best practice for flag-style enums in Rust is `bitflags`, as shown below. We assume that you are using `bitflags` to implement all flag-style enums in your project.
```rust
bitflags! {
pub struct Flags: u32 {
const A = 0b00000001;
const B = 0b00000010;
const C = 0b00000100;
}
}
```
Similarly, we also need to choose an underlying type for it. The difference is that when using `bitflags` we have already specified this underlying type, so there is no need to specify it additionally. When passing across the FFI boundary, follow the same requirements as the plain enum types.
We also need to establish the conversion and validation connection between this enum type and the chosen underlying type. Fortunately, `bitflags` already provides these functions for us, for example the automatically generated `from_bits` method and the `bits` method from the `Flags` trait. For example:
```rust
bitflags! {
pub struct Flags: u32 {
const A = 0b00000001;
const B = 0b00000010;
const C = 0b00000100;
}
}
fn foobar() {
// Convert the enum to the chosen underlying type.
let flags = Flags::A | Flags::B;
let bits: u32 = flags.bits();
assert_eq!(bits, 0b00000011u32);
// Convert from the chosen underlying type to the enum (success case).
let success = Flags::from_bits(0b00000101u32);
assert_eq!(success, Some(Flags::A | Flags::C));
// Convert from the chosen underlying type to the enum (failure case).
let failure = Flags::from_bits(0b00001000u32);
assert_eq!(failure, None);
}
```
### Payload Enums
The payload enum is the most complex kind of enum type, for example:
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
```
We require that payload enums be converted into opaque structs for access. Put the payload enum into a struct, use a plain enum type to distinguish the different payload entries, and provide different methods to access the different entries. For example:
```rust
use some_crate::Message as InternMessage;
struct Message {
inner: InternMessage,
}
enum MessageKind {
// Entries omitted...
}
impl Message {
pub fn get_kind(&self) -> MessageKind {
// Contents omitted...
}
pub fn get_write_entry(&self) -> &str {
// Contents omitted...
}
// The access functions for the following entries are omitted...
}
```
@@ -133,7 +133,7 @@ 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 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. 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. By convention, input parameters are prefixed with `in`, but this is not enforced.
#### Output Parameters #### Output Parameters
@@ -149,7 +149,7 @@ conveniently dereference an output parameter for assignment, or directly use the
need to do that, because in later chapters you will be guided on how to correctly 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. use `cffi_wrapper!` to maximize the efficiency of writing FFI functions.
By convention, output parameters are prefixed with `out_`, but this is not enforced. By convention, output parameters are prefixed with `out`, but this is not enforced.
### The Return Value ### The Return Value
@@ -0,0 +1,63 @@
# Library Architecture
The functions exported in a library can be roughly divided into two categories: auxiliary functions and functional functions.
## Auxiliary Functions
Auxiliary functions do not perform the main functionality of the library. They help the functional functions work properly.
### Startup/Shutdown Functions
Startup/shutdown functions are a category of auxiliary functions, as shown in the following example:
```rust
#[unsafe(no_mangle)]
pub extern "C" fn WFStartup() -> CError {
// Function body omitted...
}
#[unsafe(no_mangle)]
pub extern "C" fn WFShutdown() -> () {
// Function body omitted...
}
```
Here `WF` is the `<PREFIX>`, indicating that these are startup/shutdown functions exclusive to this library. Usually the word `startup` is used as the `<FUNC>` of the startup function and the word `shutdown` is used as the `<FUNC>` of the shutdown function. Other names may be chosen according to the preference of the library author.
These functions usually have no input or output parameters, but this is not mandatory. The library author may add parameters as needed.
Among these functions, the return value of the startup function is `CError`, and the return value of the shutdown function is `()` (that is, `void` in C/C++). The return value types of these two cannot be changed. The reason for setting the return values this way is: when the startup function runs, it may return some errors to explicitly inform the user that initialization failed, so its return value is `CError`. As for the shutdown function, in order to more closely match the semantics of Rust's `Drop` and C++ destructors, its return value is `()`.
When implementing these functions on the Rust side and using them in C/C++, you need to guarantee:
- Any call to a functional function must be after the call to the startup function and before the call to the shutdown function. Otherwise these functional functions must return an error code to indicate this error.
- The startup/shutdown functions can be called repeatedly, but the startup and shutdown functions must appear in pairs. This is similar to Win32 COM's `CoInitialize` and `CoUninitialize`.
You may use the `library_lifecycle` module provided by the crate to simplify the writing of the startup/shutdown functions.
Startup/shutdown functions are an optional kind of auxiliary function. If your library will not fail when initializing DLL-level resources, there is no need to add startup/shutdown functions. You can directly use Rust's lazy loading mechanism (for example `LazyLock`) so that the resources are loaded when the DLL is loaded and released when the DLL is released.
### Error Information Functions
Error information functions are used to return the error information of the last call. Here is an example:
```rust
#[unsafe(no_mangle)]
pub extern "C" fn WFGetErrorMessage() -> CStrPtr {
// Function body omitted...
}
```
Here `WF` is the `<PREFIX>`, indicating that this is the error information function exclusive to this library. Usually the phrase "get last error" or "get error message" is used as the `<FUNC>` of the error information function. Other names may be chosen according to the preference of the library author.
The return value of the error information function is not `CError` but `CStrPtr`. `CStrPtr` is a type in the `cstr_ffi` module provided by the crate. This is among the few functions in the whole library whose return value is not `CError`.
When implementing the error information function, you should guarantee that its return value is based on the last call on the current thread rather than the last call on the process.
You may use the `last_error` module provided by the crate to simplify the writing of the error information function. Furthermore, by properly using `cffi_wrapper!`, you can simplify the writing of the error information function to the greatest extent, without even touching the `last_error` module.
## Functional Functions
Functional functions are the main part of the exported functions. They are responsible for actually performing the main functionality of the library.
Functional functions can be ordinary functions or opaque struct functions, depending on the needs of the function itself.
@@ -1 +1,53 @@
# Miscellaneous Stuff # Miscellaneous Stuff
## Two-Project Workspace Structure
When writing FFI, we require you to develop with two projects using the Rust workspace mechanism. For example:
- An ordinary Rust library project named `foobar`. In this project you write your library logic in a pure Rust way.
- An FFI-specific library project named `foobar-ffi` (the name does not have to be exactly this; you may choose any name that fits your need). This project references `foobar` within the workspace, writes the relevant code according to this design, wraps `foobar` into a form suitable for FFI export, and finally exports it in FFI form.
## Panic Policy
In the FFI project, we enforce that every panic causes the process to exit immediately, just like executing `std::abort` in C++. So you need to specify the following in the Cargo.toml of the FFI project, so that this feature is enabled only in the production environment (that is, the release mode):
```toml
[profile.release]
panic = "abort"
```
The reason for enabling this feature only in the release mode is that in the debug mode you may need to perform operations such as stack tracing to debug the project, and enabling this feature would make such operations impossible.
You may think this operation would cause the user's process to crash frequently. But a well-designed library should be able to catch all recoverable errors and try not to trigger any unrecoverable errors. This operation forces developers to design a well-functioning library and eliminate potential errors during the development stage.
## Artifact Type
The build artifact of the FFI project should meet the requirements of the C language FFI. So you need to specify the following in the Cargo.toml of the FFI project to set the produced artifact type:
```toml
[lib]
crate-type = ["cdylib"]
```
## Module Organization
In the FFI project, do not try to pile everything into `lib.rs`. You need to make reasonable use of the Rust module mechanism. For example:
- When combining multiple Rust types to create an opaque struct dedicated to FFI, you can write it in a module named like `wrapper` (the name does not have to be exactly this; you may choose any name that fits your need).
- When re-wrapping Rust types, you can write it in a module named like `ffi_types` (the name does not have to be exactly this; you may choose any name that fits your need).
`lib.rs` should contain only the following:
- User-defined error types, `Result` types, `CError` constants and so on.
- The constants and types to export, including those defined directly with `pub type` and `pub const`, and those defined indirectly with `pub use` from other modules.
- All the FFI functions to export.
The re-wrapped contents can be written in a separate module `wrapper.rs`. `lib.rs` only contains the error definitions, the types to export, the constants and the functions.
## Dependency Specification
When adding `sarasacw-omrf` to the dependencies of the FFI project, we require specifying it as a git repository, supplemented with a tag and the `version` field, to ensure the version is correct. As shown below:
```toml
sarasacw-omrf = { version="1.0.0", git = "https://github.com/SarasasChipWorkshop/sarasacw-omrf.git", tag = "omrf/1.0.0" }
```
+1 -1
View File
@@ -75,7 +75,7 @@ macro_rules! set_out_param {
/// ///
/// - `Result<T>` -- a type alias for `core::result::Result<T, UserError>`. /// - `Result<T>` -- a type alias for `core::result::Result<T, UserError>`.
/// - `UserError` -- the project's error type, which must implement [`Display`] and /// - `UserError` -- the project's error type, which must implement [`Display`] and
/// `From<UserError> for `[`CError`](crate::last_error::CError)` so it can be handed to /// `From<UserError>` for `[`CError`](crate::last_error::CError)` so it can be handed to
/// [`set_last_error`](crate::last_error::set_last_error). /// [`set_last_error`](crate::last_error::set_last_error).
/// ///
/// On success the wrapper clears the thread-local last error and yields /// On success the wrapper clears the thread-local last error and yields
+10 -4
View File
@@ -14,10 +14,16 @@
//! //!
//! # Typical usage //! # Typical usage
//! //!
//! A single instance is usually stored in a `static LazyLock` and shared across all FFI entry //! A single [`LibraryLifecycle`] instance is usually stored in a `static LazyLock` and shared
//! points. The state `S` typically aggregates the library's DLL-level resources -- for example a //! across all FFI entry points. The state `S` aggregates the library's DLL-level resources -- for
//! number of [`ObjectPool`](crate::object_pool::ObjectPool)s and other globals that must be //! example a number of [`ObjectPool`](crate::object_pool::ObjectPool)s and other globals.
//! constructed on first startup and torn down on last shutdown. //!
//! Reach for [`LibraryLifecycle`] only when acquiring those resources **can fail** and you want to
//! surface that failure to the caller explicitly through the `startup` / `shutdown` functions. When
//! resource construction is infallible (or its failure need not be reported), a plain
//! `static LazyLock<S>` is sufficient -- it lazily initializes `S` as the DLL loads, with no
//! lifecycle machinery required. Collecting the library's state into a single `S` struct is
//! recommended either way.
//! //!
//! # Call order //! # Call order
//! //!
+1 -1
View File
@@ -22,7 +22,7 @@ classifiers = [
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
] ]
dependencies = [ dependencies = [
"jinja2==3.1.6", "jinja2>=3.1.6",
"semver>=3.0.4", "semver>=3.0.4",
] ]
@@ -11,7 +11,7 @@ endif()
Get the path to directory where current XXXConfig.cmake file is (i.e. /path/to/installation/lib/cmake/XXX) Get the path to directory where current XXXConfig.cmake file is (i.e. /path/to/installation/lib/cmake/XXX)
And compute installation root directory (back to parent 3 times, i.e. /path/to/installation) And compute installation root directory (back to parent 3 times, i.e. /path/to/installation)
-#} -#}
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}/../../../" ABSOLUTE) get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
{# Setup library and header file paths -#} {# Setup library and header file paths -#}
set(MyRust_LIBRARY "${_IMPORT_PREFIX}/lib/{{ artifact_dll }}") set(MyRust_LIBRARY "${_IMPORT_PREFIX}/lib/{{ artifact_dll }}")
+1 -1
View File
@@ -77,7 +77,7 @@ dependencies = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "jinja2", specifier = "==3.1.6" }, { name = "jinja2", specifier = ">=3.1.6" },
{ name = "semver", specifier = ">=3.0.4" }, { name = "semver", specifier = ">=3.0.4" },
] ]