Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

debug mode on linux does not load assets from relative paths #10

Closed
benjamin-sieffert opened this issue Apr 1, 2024 · 5 comments
Closed

Comments

@benjamin-sieffert
Copy link

benjamin-sieffert commented Apr 1, 2024

Hey there, thank you for this cool library.

I have noticed one issue when running debug build on Linux, specifically what is profile "live" here.

[profile.release]
lto = "thin"

[profile.live]
inherits = "release"
debug-assertions = true # to enable live-embeds

I am building on Linux, then moving the executable into a different folder with different assets.
E.g.

cd ~/projects/my_project/
cargo build --profile=live
mv target/live/executable ~/my_different_env
cd ~/my_different_env
./executable

In this case, executable will not load assets relative to my_different_env, but it will still load all assets relative to ~/projects/my_project/! Meaning it probably uses absolute system paths - although in the ifree macro invocations I am only using relative paths.
Interestingly, if I cross-build for windows (--target x86_64-pc-windows-gnu) the produced executable has the correct behavior when I move it to a Windows PC and run it there. (But I am cross-compiling for windows with some adjustment, see #11, so this may be due to the fact that the paths in Windows case are impossible to resolve at build time.)

On Linux, I only figured out a workaround, if e.g. my assets are in ~/projects/my_project/assets, then I can temporarily rename that folder mv assets __assets, and link the desired ln -s ~/my_different_env/assets ~/projects/my_project/assets.

@evolutics
Copy link
Owner

evolutics commented Apr 2, 2024

Glad the library is of help!

You're using the standard field get_bytes (or get_str), right?

In that case, if debug_assertions is disabled, then the assets are included in your binary at compile time (your release profile). The assets' file paths don't matter any more at runtime.

If debug_assertions is enabled (your live profile), then the assets aren't included in your binary. Instead, they are looked up at runtime. Relative paths are resolved to absolute paths at compile time, as you suspect.

Possible solutions, but it depends on your overall problem:

  • Use the root_folder_variable configuration: set it to the name of an environment variable that is set to a relative path. E.g.: #[iftree::include_file_tree("root_folder_variable = 'MY_ENV_VAR'")] with export MY_ENV_VAR='.' (untested). I don't remember why the documentation says it should be an absolute path but give it a try.
  • Work with the standard field relative_path and implement your own asset lookup based on it.
  • Use template.initializer, see example.
  • Forgo live asset loading and use the standard field contents_bytes (or contents_str) instead.

Let me know if that helps.

@benjamin-sieffert
Copy link
Author

Relative paths are resolved to absolute paths at compile time, as you suspect.

Hmm, quite unintuitive. Especially seeing as my Windows workaround in #11 (which was unable to find any of the paths at compile-time) does relative path lookup at runtime.

Perhaps to shed some light on my use-case. I have an artist collaborator that is on Windows, while I compile everything on Linux (even the Windows binaries) and was in fact never able to even get Rust compilation working on Windows itself.
I need an executable for them to try out the changes they do to assets.
And I need one for myself to try out their changes, but I don’t just want to replace my whole assets folder locally. I have a file sync in a different location. Now as we found out, I cannot move my exe into the file sync dir, rather I can only symlink the filesync to where my assets folder was at compile time.

Maybe I try out the env var, but it’s basically same amount of effort as symlinking.

@benjamin-sieffert
Copy link
Author

which was unable to find any of the paths at compile-time

This can’t be right, or? I wouldn’t have any relative_path: &'static str if no files were found. Still somehow the files included via \\ path seperator on Linux get loaded from runtime-relative paths on Windows.

@evolutics
Copy link
Owner

Relative paths are resolved to absolute paths at compile time, as you suspect.

Hmm, quite unintuitive.

Agreed, it can be confusing.

The reason for this has to do with the library generating code that contains include_str! (or include_bytes!) calls. include_str! locates the file relative to the current source file (docs). For example, a source file src/main.rs with include_str!("x/y") in it looks for a file src/x/y. However, as far as I know, a macro (like Iftree) currently can't know the path of the source file where it's called from (see discussion, also rust-lang/rust#54725). So Iftree calls include_str! with an absolute path, and for that it relies on CARGO_MANIFEST_DIR.

Note that the standard field relative_path is the path relative to CARGO_MANIFEST_DIR by default, not relative to the source file containing #[iftree::include_file_tree(…)].

So, a root_folder_variable with a relative path (say . like suggested above) doesn't really work.

But a custom template.initializer does the trick (src/main.rs below):

macro_rules! my_initialize {
    ($relative_path:literal, $absolute_path:literal) => {
        Asset {
            my_get_str: {
                fn get() -> std::borrow::Cow<'static, str> {
                    if cfg!(debug_assertions) {
                        // Nota bene:
                        std::fs::read_to_string($relative_path).unwrap().into()
                    } else {
                        include_str!($absolute_path).into()
                    }
                }

                get
            },
        }
    };
}

#[iftree::include_file_tree(
    "
debug = true
paths = '/my_assets/**'
template.initializer = 'my_initialize'
"
)]
pub struct Asset {
    my_get_str: fn() -> std::borrow::Cow<'static, str>,
}

fn main() {
    eprintln!("---\n{DEBUG}\n---");
    use base::my_assets;
    println!("my_assets/my_file: {:?}", (my_assets::MY_FILE.my_get_str)());
}

Demo:

$ git ls-files
.gitignore
Cargo.lock
Cargo.toml
blue/my_assets/my_file
green/my_assets/my_file
my_assets/my_file
src/main.rs

$ cat Cargo.toml
[package]
name = "tmp"
edition = "2021"

[dependencies]
iftree = "1.0"

$ cat my_assets/my_file
… original contents
$ cat blue/my_assets/my_file
… blue contents
$ cat green/my_assets/my_file
… green contents

$ cargo build
$ cargo build --release

$ echo '… changed contents' > my_assets/my_file
$ target/debug/tmp
my_assets/my_file: "… changed contents\n"
$ target/release/tmp
my_assets/my_file: "… original contents\n"

$ (cd blue/ && ../target/debug/tmp)
my_assets/my_file: "… blue contents\n"
$ (cd blue/ && ../target/release/tmp)
my_assets/my_file: "… original contents\n"

$ (cd green/ && ../target/debug/tmp)
my_assets/my_file: "… green contents\n"
$ (cd green/ && ../target/release/tmp)
my_assets/my_file: "… original contents\n"

You can use debug = true to print the generated code via variable DEBUG, which I recommend to clear up some confusion.

By the way, in this context forward slashes / should work as path separators on Windows, too. If there's an issue with that, please provide a reproduction case. Thanks.

evolutics added a commit that referenced this issue May 4, 2024
@evolutics
Copy link
Owner

Closing this. Feel free to reopen if not addressed adequately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants