GithubHelp home page GithubHelp logo

nvzqz / embed-plist-rs Goto Github PK

View Code? Open in Web Editor NEW
38.0 4.0 0.0 195 KB

Embed property list files like Info.plist directly in your Rust executable binary

Home Page: https://docs.rs/embed_plist

License: Apache License 2.0

Rust 100.00%
rust apple include info launchd plist file

embed-plist-rs's Introduction


Embed an Info.plist or launchd.plist file directly in your executable binary, brought to you by @NikolaiVazquez!

If you found this library useful, please consider sponsoring me on GitHub. ❤️

Index

  1. Motivation
  2. Usage
  3. Minimum Supported Rust Version
  4. Multi-Target Considerations
  5. Get Embedded Property Lists
  6. Accidental Reuse Protection
  7. Implementation
  8. License

Motivation

Certain programs require an embedded Info.plist or launchd.plist file to work correctly. For example:

Doing this manually with include_bytes! is cumbersome. To understand why, see the implementation. This library removes the headache of doing this.

Usage

This library is available on crates.io and can be used by adding the following to your project's Cargo.toml:

[dependencies]
embed_plist = "1.2"

...and this to any source file in your crate:

embed_plist::embed_info_plist!("Info.plist");

// If making a daemon:
embed_plist::embed_launchd_plist!("launchd.plist");

Done! It's that simple. 🙂

See implementation for details on this sorcery.

Minimum Supported Rust Version

This library targets 1.39 as its minimum supported Rust version (MSRV).

Requiring a newer Rust version is considered a breaking change and will result in a "major" library version update. In other words: 0.1.z would become 0.2.0, or 1.y.z would become 2.0.0.

Multi-Target Considerations

This library only works for Mach-O binaries. When building a cross-platform program, these macro calls should be placed behind a #[cfg] to prevent linker errors on other targets.

#[cfg(target_os = "macos")]
embed_plist::embed_info_plist!("Info.plist");

Get Embedded Property Lists

After using these macros, you can get their contents by calling get_info_plist or get_launchd_plist from anywhere in your program.

We can verify that the result is correct by checking it against reading the appropriate file at runtime:

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());

If the appropriate macro has not been called, each function creates a compile-time error by failing to reference the symbol defined by that macro:

// This fails to compile:
let embedded_plist = embed_plist::get_info_plist();

Accidental Reuse Protection

Only one copy of Info.plist or launchd.plist should exist in a binary. Accidentally embedding them multiple times would break tools that read these sections.

Fortunately, this library makes reuse a compile-time error! This protection works even if these macros are reused in different modules.

// This fails to compile:
embed_plist::embed_info_plist!("Info.plist");
embed_plist::embed_info_plist!("Info.plist");

This example produces the following error:

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

Warning: Although the name _EMBED_INFO_PLIST can be seen here, you should not reference this symbol with e.g. an extern "C" block. I reserve the right to change this name in a SemVer-compatible update.

Implementation

Files are read using include_bytes!. This normally places data in __TEXT,__const, where immutable data is kept. However, property list data is expected to be in __TEXT,__info_plist or __TEXT,__launchd_plist. This section will explain how I accomplish that.

We begin by reading the file from disk:

const BYTES: &[u8] = include_bytes!("Info.plist");

A naïve approach is to do the following:

#[used] // Prevent optimizing out
#[link_section = "__TEXT,__info_plist"]
static PLIST: &[u8] = BYTES;

This doesn't work because only the slice's pointer and length that are placed in __TEXT,__info_plist. The referenced bytes are still placed in __TEXT,__const.

Instead, we need to arrive at the following:

#[used]
#[link_section = "__TEXT,__info_plist"]
static PLIST: [u8; N] = *BYTES;

We can get N by using len. As of Rust 1.39, it is possible to get the length of a slice within a const.

const N: usize = BYTES.len();

The next step is to dereference the bytes into a [u8; N].

There are two approaches:

  1. Call include_bytes! again.

    This is not the approach used by this library because of concerns about compile performance. See the second approach for what this library does.

    The following is all we need:

    #[used]
    #[link_section = "__TEXT,__info_plist"]
    static PLIST: [u8; N] = *include_bytes!("Info.plist");

    This works because include_bytes! actually returns a &[u8; N]. It's often used as a &[u8] because we don't know the size when calling it.

  2. Dereference our current bytes through pointer casting.

    This is tricker than the first approach (and somewhat cursed). If you know me, then it's predictable I'd go this route.

    We can get a pointer to the bytes via as_ptr, which is usable in const:

    const PTR: *const [u8; N] = BYTES.as_ptr() as *const [u8; N];

    Unfortunately, this pointer can't be directly dereferenced in Rust 1.39 (minimum supported version).

    // This fails to compile:
    #[used]
    #[link_section = "__TEXT,__info_plist"]
    static PLIST: [u8; N] = unsafe { *PTR };

    Instead, we must cast the pointer to a reference.

    You may want to reach for transmute, which was stabilized for use in const in Rust 1.46. However, earlier versions need to be supported, so that is not an option for this library.

    This bitwise cast can be accomplished with a union:

    union Transmute {
        from: *const [u8; N],
        into: &'static [u8; N],
    }
    
    const REF: &[u8; N] = unsafe { Transmute { from: PTR }.into };

    Finally, we can dereference our bytes:

    #[used]
    #[link_section = "__TEXT,__info_plist"]
    static PLIST: [u8; N] = *REF;

License

This project is released under either:

at your choosing.

embed-plist-rs's People

Contributors

nvzqz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

embed-plist-rs's Issues

symbol `_EMBED_INFO_PLIST` is already defined

Type: Bug
Getting error on making a call to

embed_plist::embed_info_plist!("../Info.plist");

Here's a screenshot let me know if I am doing something wrong.
I am actually new to rust and still learning and finding my way around let me if I am missing something.

Screenshot 2022-01-15 at 11 34 50 PM

Rust Version: 1.57.0
Tauri: { version = "1.0.0-beta.8", features = ["api-all"] }
OS: Mac Os Big Sur (11.6.1)
embed-plist: 1.2.0

Embedded Info.plist doesn't seem to be recognised

Hello,
I'm not very familiar with Mac OS, so it's entirely possible I'm misunderstanding or doing something wrong, but it appears that the Info.plist embedded by this crate isn't being recognised by the OS.

Context:
I'm working on a Rust Bluetooth library. Since Big Sur, accessing CoreBluetooth APIs apparently requires that the Info.plist contain a string NSBluetoothAlwaysUsageDescription saying why the program wants to use it. When I embed an appropriate Info.plist with this crate and then run my binary from the command-line (either via cargo run or directly), it crashes with Abort trap: 6. If I run it inside lldb, I get an error message:

(lldb) run
Process 76410 launched: '/Users/qwandor/src/btleplug/target/debug/examples/lights' (x86_64)
2021-02-11 17:44:15.350059+0000 lights[76410:891761] [access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSBluetoothAlwaysUsageDescription key with a string value explaining to the user how the app uses this data.
Process 76410 stopped
* thread #12, queue = 'com.apple.root.default-qos', stop reason = signal SIGABRT
    frame #0: 0x00007fff2059a792 libsystem_kernel.dylib`__abort_with_payload + 10
libsystem_kernel.dylib`__abort_with_payload:
->  0x7fff2059a792 <+10>: jae    0x7fff2059a79c            ; <+20>
    0x7fff2059a794 <+12>: movq   %rax, %rdi
    0x7fff2059a797 <+15>: jmp    0x7fff205796a1            ; cerror_nocancel
    0x7fff2059a79c <+20>: retq
Target 0: (lights) stopped.

If I instead copy the Info.plist to target/debug/examples/ alongside the binary, then running it directly still fails, but running in lldb then it prompts for the permission and runs correctly.

I'm on MacOS 11.2, x86_64, Rust 1.49.0, embed_plist 1.2.0. You can see my code trying to use it in deviceplug/btleplug#116.

So I guess there are two questions:

  • Why does embedding the Info.plist not work, while putting it in the same directory does? (Is this a bug in the embed_plist crate?)
  • Why does it only work when I run my binary under lldb? (Am I doing something wrong?)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.