tomassedovic / tcod-rs

Rust bindings for libtcod 1.6.3 (the Doryen library/roguelike toolkit)
Do What The F*ck You Want To Public License
229 stars 45 forks source link

Cargo run works but running the binary directly segfaults #271

Closed sminez closed 5 years ago

sminez commented 5 years ago

Sorry if this is a Rust noob question but I'm having problems running the binary generated by cargo build --release. If I just run cargo run or cargo run --release my game works fine. If I instead try to execute the binary directly (./target/release/foo) I get a segfault whenever I try to read input from the keyboard. The initial game screen renders fine using an offscreen console blitted to the root console but the moment I call root.wait_for_keypress(true) I get a segfault.

I've tried hunting around online and I can't find any help for debugging this as everything talks about running the program through Cargo but that works fine! While that means that I can play the game, I currently can't produce a stand alone binary to distribute.

Any help would be greatly appreciated.

tomassedovic commented 5 years ago

Hi! What you're seeing is a little weird. Of course you should not be getting any segfaults. But actually, depending on what exactly you're doing, the game shouldn't start at all when run directly.

First, a few questions: what OS are you running this on? Looks like either Linux or OSX, but which version exactly?

Also, what's your tcod entry in Cargo.toml. Is it "0.12" or the github link? If the code is up somewhere, can I have a look?

I've got a hunch, though. tcod-rs links a dynamic library in C++ called libtcod. During cargo build, we build it (tcod-sys contains the libtcod's C++ sources to be able to do this). It's a version that's known to work.

When you run a program (any program) that links a dynamic library, the OS will look for that library (libtcod.so in our case) in a well known location (e.g. /usr/lib64 on my machine) and prints an error when it's not there.

cargo run knows about the library it built and passes it to the executable. When you run it directly without Cargo, the OS should not be able to find it and it should print out a message.

Since you are able to run the program even without Cargo, here's what I think is happening:

Your OS already has libtcod installed somewhere. And it's an incompatible version (I'd guess 1.5 whereas tcod-rs requires 1.6 -- I actually seem to remember that keypresses were the one place where you would get a crash like that). So when you run it without Cargo, your OS tries to link the library you've got installed rather than the one Cargo built.

Here's how you can find out (these are instructions for Linux, you can do something similar on OSX, but it will be a little different and I don't know the exact details):

$ cd foo   # your project
$ find . -type f -name '*.so*'
# this will print something like: `./target/release/build/tcod-sys-514f00e827243f96/out/libtcod.so`
$ env LD_LIBRARY_PATH=target/release/build/tcod-sys-514f00e827243f96/out/ target/release/roguelike

That should work fine (this is basically what cargo run does under the hood).

Now as for the reason why you're asking about this -- distributing your game. That is a complex topic that depends on a lot of factors, but if you just want to zip the game folder up and have someone else play it without installing anything, there are basically 3 options:

  1. Create a wrapper script that sets the right LD_LIBRARY_PATH value
  2. Build your binary with RPATH set
  3. Build the library statically

The first option is the easiest one and what I've seen a lot of Linux games do. Instead of running your foo executable directly, you would ship a foo bash script that would run it properly (and of course, you'd ship the libtcod library with it).

The second option is a bit more complex, but basically: you can instruct your linker (which gets invoked by Cargo at some point) to tell your binary to look into a specific directory relative to the executable.

I like it because it works with any dynamic library and you still get to run your executable directly without wrapper scripts. But distro package maintainers are not keen on this for some reason (I'm not sure why -- never looked it up).

You could do:

$ cargo clean
$ env LD_RUN_PATH='$ORIGIN/lib' cargo build --release
$ mkdir -p target/release/lib
$ cp target/debug/build/tcod-sys-*/out/libtcod.so target/release/lib
$ target/release/foo

(and when you distribute the game, you must of course ship both the executable and the lib directory)

But before you do all that, do try the latest checkout of tcod-rs, because we're building libtcod statically now, which should solve all these problems (as the libtcod code is embedded directly in your roguelike, no dynamic library necessary).

To do that, use this in your Cargo.toml instead:

tcod = { git = "https://github.com/tomassedovic/tcod-rs" }

If that works for you, that executable should be transferable to any other system as well. This is not on crates.io yet which is why you need to use the github link directly. I'll try to publish it soon.

You can read more about distributing Linux programs with dynamically linked libraries in this old blog post of mine:

https://aimlesslygoingforward.com/blog/2014/01/19/bundling-shared-libraries-on-linux/

And I'm sorry if you knew all that or if it turns out to be irrelevant (if that case please do let me know more about your system and program!).

sminez commented 5 years ago

Thank you for the amazingly detailed response!

You are right about me being on Linux and it looks like your hunch was correct. It also didn't help that I was using the crates.io version of tcod-rs but reading the github docs...

I've written libtcod games in python before (which I think uses a bundled .so as well?) and recently I've been tinkering with writing a game in other languages. One of which was C so I have an installed version of libtcod that was getting picked up incorrectly as you said. Switching to the git version and then building the binary statically works fine :+1: (I thought that I was already doing that which is why I was so confused that it worked under cargo but not directly!)

tomassedovic commented 5 years ago

Awesome, that's really good to hear!

I've just published a new version (0.13.0) which has all the new changes, so you can put:

tcod = "0.13.0"

to your Cargo.toml and it should just work now.

https://crates.io/crates/tcod