Building standalone OpenGL application without Xcode, with bundled separate shader file
Published: 2018-05-07 16:27:47

---
title: Building standalone OpenGL application without Xcode, with bundled separate shader file
published: true
description: 
tags: 
---
#### Background
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 `gcc-8`, `glfw` and `glew`.

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>

Currently, after `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.
#### Problem
I don't quite want to expose shader files, and if I read this [SO answer][3] 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][2], attempted the operations mentioned, namely:

1. Build with the `-headerpad_max_install_names` flag.
1. Create `Info.plist` and structre the directory as macOS dictates.
1. Find all dylibs needed using `otool -L` on executable, and recursively use `otool -L` on 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:
	- libglfw.3.dylib  
	- libGLEW.2.1.dylib
	- libstdc++.6.dylib
	- libgcc_s.1.dylib 
2. Use `install_name_tool -change` to modify the reference to dylibs in binary file to those copied.
3. Wrap the entry point in `Info.plist` into 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][1] 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 `dyld` complains.

I suspect the former is mostly right, since when I blindly change that folder name from standard `Contents` to `content` (or any other name), it will run if I invoke the binary from terminal, and even when I `brew unlink` `glew` and `glfw`, suggesting the binary knows where to look up dylibs and shader files. 

What did I miss? 

#### Probing

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][5] on SO. 

#### The real caveat
By pure chance I saw yet another [SO question][6] which finally saved my day. **Turns out, if GLFW sees `Resources` in bundle, it changes working dir to that dir!**

#### Solution
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?

[1]: http://seaneshbaugh.com/posts/creating-an-opengl-4-1-program-with-glew-and-glfw-in-xcode 
[2]:https://stackoverflow.com/questions/1596945/building-osx-app-bundle
[3]:https://stackoverflow.com/a/20443864
[4]:https://stackoverflow.com/questions/8768217/how-can-i-find-the-path-to-a-file-in-an-application-bundle-nsbundle-using-c
[5]:https://stackoverflow.com/q/50214471/4652262
[6]:https://stackoverflow.com/questions/17001488/osx-bundle-throwing-exception-only-when-app-in-macos-folder-and-resources-in-res