I have a "Hello Rectangle" project which runs as expected when invoked in Terminal.app. But I'm having trouble creating a standalone macOS bundle out of it.
Let's just say using full Xcode is out of question. I'm very tight on disk space and I'm not doing iOS dev.
Instead I'm using brewed toolchain, including
The code base looks like this:
src ├── include │ ├── draw.hpp │ └── public.hpp ├── resc │ ├── rect.frag │ └── rect.vert ├── draw.cpp ├── init-shader.cpp └── window.cpp
The actual compile command is:
$ g++-8 -lglew -lglfw -framework OpenGL -o <binary> <sources>
make, the binary can be invoked in terminal, providing both shader files are present in the same directory. The file reader in
init-shader.cpp accepts just filename without preceding path.
I don't quite want to expose shader files, and if I read this SO answer right, it suggests either you pack shader files in final release (what I'm trying), or you hard code them in other sources (even more undesired). Plus, I want to make sure when I distribute it as a standalone
.app bundle, it can run out of box (as long as lowest OpenGL version requirement is met) instead of having to get all brewed dependencies.
I read through this SO question, attempted the operations mentioned, namely:
- Build with the
Info.plistand structre the directory as macOS dictates.
- Find all dylibs needed using
otool -Lon executable, and recursively use
otool -Lon them to avoid DLL hell (for a want of macOS-specific name), exclude what Apple have guaranteed, and copy them to
a.app/Contents/Frameworks. Four of them are found in my case, all in Homebrew's cellar:
install_name_tool -changeto modify the reference to dylibs in binary file to those copied.
- Wrap the entry point in
Info.plistinto a shell script so that working directory is right, wherever it's invoked.
Terminal didn't complain all the way through. But it won't run, whether using
open -a a.app or directly invoke the actual binary or that tiny script.
And I found this blog post which implies I also need to invoke
install_name_tool -id between (2) and (3), and
codesign them between (3) and (4).
But it still won't run.
In the former case, the shader linker says shader programs are corrupted, but compilation will succeed, but compilation log is empty; in the latter case the app crashes and
I suspect the former is mostly right, since when I blindly change that folder name from standard
content (or any other name), it will run if I invoke the binary from terminal, and even when I
glfw, suggesting the binary knows where to look up dylibs and shader files.
What did I miss?
At this time my release dir looked like this:
Contents ├── Frameworks │ ├── libGLEW.2.1.dylib │ ├── libgcc_s.1.dylib │ ├── libglfw.3.dylib │ └── libstdc++.6.dylib ├── MacOS │ ├── hello-rect <- the actual binary │ ├── launcher.sh <- entry wrapper for working dir as the above link suggests │ ├── rect.frag │ └── rect.vert └── Info.plist
Because the unbundled version obviously looks nowhere but
cwd, I thought in bundles they would do the same. Later on Google lead me to believe that I need
CoreFoundation API to correctly read files in bundle, and it murdered my day. I learned that there should be a
Resources folder in bundle, and then added CF codes, which for some reason always give segfault 11. I even brought up one new question on SO.
By pure chance I saw yet another SO question which finally saved my day. Turns out, if GLFW sees
Resources in bundle, it changes working dir to that dir!
So the take away is this: structure the release folder like following:
Contents ├── Frameworks │ └── <dylibs> ├── MacOS │ ├── hello-rect │ └── launcher.sh ├── Resources │ └── <shader files> └── Info.plist
Then, walk through the previous list, without
install_name_tool -id or
codesign, and we're done.
One final question: is
make suitable for wrapping all these into a script?