5 个稳定版本
1.2.2 | 2022 年 1 月 9 日 |
---|---|
1.2.0 | 2020 年 8 月 30 日 |
1.1.0 | 2020 年 8 月 30 日 |
1.0.0 | 2020 年 8 月 30 日 |
#43 in 构建工具
133,692 每月下载量
在 119 个包中使用(5 个直接使用)
37KB
89 行
将 Info.plist
或 launchd.plist
文件直接嵌入到您的可执行二进制文件中,由 @NikolaiVazquez 提供!
如果您觉得这个库很有用,请考虑在 GitHub 上 赞助我。❤️
索引
动机
某些程序需要嵌入的 Info.plist
或 launchd.plist
文件才能正确工作。例如
-
Info.plist
在 macOS 10.15 及以后版本中需要获取某些权限。 -
launchd.plist
用于创建launchd
守护进程和用户代理。
使用 include_bytes!
手动执行此操作很麻烦。要了解原因,请参阅 实现。此库消除了执行此操作的烦恼。
用法
此库可在 crates.io 上找到,并可通过将以下内容添加到项目 Cargo.toml
中来使用
[dependencies]
embed_plist = "1.2"
...并将其添加到您的crate中的任何源文件
embed_plist::embed_info_plist!("Info.plist");
// If making a daemon:
embed_plist::embed_launchd_plist!("launchd.plist");
完成了!就这么简单。😉
有关此魔法的详细信息,请参阅 实现。
最低支持的 Rust 版本
此库的目标是支持 1.39 作为其最低支持的 Rust 版本(MSRV)。
要求更高版本的 Rust 被视为破坏性更改,并将导致“主要”库版本更新。换句话说:0.1.z
将变为 0.2.0
,或者 1.y.z
将变为 2.0.0
。
多目标考虑因素
此库仅适用于 Mach-O 二进制文件。当构建跨平台程序时,应在 #[cfg]
下方放置这些宏调用,以防止在其他目标上出现链接器错误。
#[cfg(target_os = "macos")]
embed_plist::embed_info_plist!("Info.plist");
获取嵌入属性列表
使用这些宏之后,您可以通过在程序中的任何地方调用 get_info_plist
或 get_launchd_plist
来获取其内容。
我们可以通过在运行时读取适当的文件来验证结果是否正确
embed_plist::embed_info_plist!("Info.plist");
let embedded_plist = embed_plist::get_info_plist();
let read_plist = std::fs::read("Info.plist")?;
assert_eq!(embedded_plist, read_plist.as_slice());
如果未调用适当的宏,则每个函数都会通过无法引用该宏定义的符号来创建编译时错误
// This fails to compile:
let embedded_plist = embed_plist::get_info_plist();
意外重用保护
在二进制文件中不应存在多个 Info.plist
或 launchd.plist
的副本。意外多次嵌入将破坏读取这些部分的工具。
幸运的是,此库将复用视为编译时错误!即使这些宏在不同的模块中重复使用,此保护也有效。
// This fails to compile:
embed_plist::embed_info_plist!("Info.plist");
embed_plist::embed_info_plist!("Info.plist");
此示例产生以下错误
error: symbol `_EMBED_INFO_PLIST` is already defined
--> src/main.rs:4:1
|
4 | embed_plist::embed_info_plist!("Info.plist");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
警告:尽管在这里可以看到名称
_EMBED_INFO_PLIST
,但您 不应该 使用例如extern "C"
块来引用此符号。我保留在 SemVer 兼容更新中更改此名称的权利。
实现
文件读取使用 include_bytes!
。这通常将数据放置在 __TEXT,__const
中,其中存储不可变数据。然而,属性列表数据应位于 __TEXT,__info_plist
或 __TEXT,__launchd_plist
中。本节将解释我是如何实现这一点的。
我们首先从磁盘读取文件
const BYTES: &[u8] = include_bytes!("Info.plist");
一个简单的方法是执行以下操作
#[used] // Prevent optimizing out
#[link_section = "__TEXT,__info_plist"]
static PLIST: &[u8] = BYTES;
这行不通,因为只有指针和长度被放置在 __TEXT,__info_plist
。引用的字节仍然放置在 __TEXT,__const
中。
相反,我们需要达到以下目标
#[used]
#[link_section = "__TEXT,__info_plist"]
static PLIST: [u8; N] = *BYTES;
我们可以通过使用 len
来获取 N
。截至 Rust 1.39,可以在 const
中获取切片的长度。
const N: usize = BYTES.len();
下一步是将字节解引用到 [u8; N]
。
有两种方法
-
再次调用
include_bytes!
。这个库没有使用这种方法,因为担心编译性能。有关这个库做了什么的更多信息,请参阅第二种方法。
以下是我们需要的所有内容
#[used] #[link_section = "__TEXT,__info_plist"] static PLIST: [u8; N] = *include_bytes!("Info.plist");
这之所以有效,是因为
include_bytes!
实际上返回一个&[u8; N]
。由于我们不知道调用时的大小,它通常用作&[u8]
。 -
通过指针转换来解引用当前的字节。
这比 第一种方法(以及有点受诅咒)更复杂。如果你了解我,那么你可以预测我会走这条路。
我们可以通过
as_ptr
获取字节的一个指针,这在const
中是可用的。const PTR: *const [u8; N] = BYTES.as_ptr() as *const [u8; N];
不幸的是,在 Rust 1.39(最低支持版本)中,这个指针不能直接解引用。
// This fails to compile: #[used] #[link_section = "__TEXT,__info_plist"] static PLIST: [u8; N] = unsafe { *PTR };
相反,我们必须将指针转换为一个引用。
你可能想使用
transmute
,它在 Rust 1.46 中被稳定用于const
。但是,需要支持更早的版本,所以这不是这个库的选项。这个位操作转换可以使用一个
union
来完成union Transmute { from: *const [u8; N], into: &'static [u8; N], } const REF: &[u8; N] = unsafe { Transmute { from: PTR }.into };
最后,我们可以解引用我们的字节
#[used] #[link_section = "__TEXT,__info_plist"] static PLIST: [u8; N] = *REF;
许可证
本项目可以以以下任一许可证发布
任由您选择。