Making MacOSX App bundles from ROOT GUI applications

Good Morning rooters,

has anyone had experiences with compiling a ROOT-program into a stand-alone osx app bundle so that others without ROOT installation can run it? Just like any other software that one downloads from internet?

ok, I started by understanding the “anatomy of app bundles” which is fairly easy. I used the standard ROOT GUI example from the tutorials which now works perfectly on my own system. You can try it from the attachment by

make make osxapp
But the problem seems to be the libraries that the program needs. Using

otool -L application

which is the counterpart of ldd under linux for MacOSX, one can see all of the libraries that the application needs, and they are a lot! Entries like:

@rpath/libTree.so (compatibility version 0.0.0, current version 0.0.0)

Is there a way to just manually bundle these libraries so that the program can work on every machine out there? or a kind of installer?

cheers!
:mrgreen:
application.tar.gz (2.16 KB)

A (possibly) related discussion for Windows: [url=https://root-forum.cern.ch/t/how-to-build-a-standalone-root-application/15379/1 to build a standalone ROOT application?[/url].

well, in that thread, it is still the discussion is still not complete. But here are some new clues. I have not fully finished yet and not fully achieved a result, but here is my progress report so far. The Makefile in the attachment is now updated to include all the changes discussed below:

Assuming one has working stand-alone ROOT GUI application, the goal is to make an OSX App Bundle, that will run on every machine out there without ROOT installed, i.e. stand-alone in true sense. Apparently these dynamically loadable libraries should be bundled together with the application. It turns out that OSX actually does support this feature, and all so called “dylibs” should go to the subdirectory “Framework” of a bundle. But how to do this? There seems to be a command for this. After recognizing which dynamic dependencies your binary file has using

otool -L exefile

you have to apply the following command on each of them:

install_name_tool

But fortunately, a project on source forge called mac dylib bundler http://macdylibbundler.sourceforge.net/ actually does this all for you. It will check through all dependencies and copies them automatically to the “Framework” folder inside your bundle:

dylibbundler -od -b -x application.app/Contents/MacOS/application -d application.app/Contents/Frameworks/

You will now have a complete bundle with all dynamical ROOT library dependencies. After that you can make a disk image and distribute the software by using:

hdiutil create application.dmg -volname "application" -fs HFS+ -srcfolder application.app

At some point I needed to add a compiler flag to take care of older Mac OSX versions:

-mmacosx-version-min=10.7

So far I came and am stuck at the moment, because when I run the bundle on another machine, I get an error message that says:

dyld: Library not loaded, Reason: image not found

According to this post http://stackoverflow.com/questions/20455492/dyld-library-not-loaded-reason-image-not-found it seems that one needs to compile ROOT by using:

./configure --with-cxx=g++ --with-cc=gcc --with-ld=g++

instead of clang. I tried it, but contrary to the standard configurations (clang), ROOT doesn’t compile using gcc/g++.

So we are almost there. . .
any ideas would be welcome.

cheers!
:mrgreen:
application.tar.gz (2.25 KB)

Helle again everyone,

just like to report on the progress. I was able to fix all of the dependencies in order to make a truly standalone ROOT Gui application inside a Mac OS X application bundle. The program macdylibbundler didn’t really work well, so I wrote a ruby script that fixes the dependencies (see attachment). You can run the example by using make dist Of course you should run thisroot.sh of your local root installation first to set the root environment. The ruby script automatically copies the libraries to the bundle and fixes the IDs and dependencies and dependencies of dependencies.

Now otool -L shows that all dependencies are set correctly set and no errors are shown in this respect, and the program should work, even if the ROOT environment is not set, OR on a different machine without ROOT installation.

Unfortunately I still get a segmentation fault. But the program runs of course with the ROOT environment set. So for me this means that the GUI application still demands something more (another library hidden or something which is not visible to otool) after running.

Any ideas what that component might be? Is there anything more that a ROOT GUI application demands, other than dynamically loadable libraries?

thanks.
application.tar.gz (8.06 KB)

I ran your program on Mac and I do not get any seg fault.

Hi,
thanks for checking :smiley: . After running make dist you will have a binary in the current directory, and a binary inside the bundle, i.e. in application.app/Contents/MacOS/application. Both are the same except that the one inside the bundle has corrected dependencies which point to the libraries inside the bundle. In order to test the bundle, you should open a terminal where ROOT environment is not set, or use a computer which doesn’t have a root installation. Or you should be able to double click on the App and see the results. Can you do that?

For me that is not the case, I still get the seg fault.

Ah ok… I just ran the application after having done make…
Now I tried make dist… I see you do a .dmg … I open it … move the app inside on my desktop … run it by double clicking on it … and nothing happens.

that is exactly what I mean. Now in order to see the segmentation fault, assuming that the application.app is on your desktop, open a terminal and type:
~/Desktop/application.app/Contents/MacOS/application
and there it is the error…
:frowning:

Ah yes… if the ROOT environment is not defined I see that seg fault …

No sure knowledge, but some ideas:
[ul]
[li]cint-folder (how cint will behave if it doesn’t have include-files?)[/li]
[li]fonts-folder (will ROOT need fonts from here?)[/li]
[li]icons-folder (does GUI load icons from here?)[/li]
[li]etc/plugins-folder (use of plugins?)[/li]
[li]etc/system.rootrc (ROOT’s behaviour without settings?)[/li]
[li]ROOTSYS in process environment?[/li]
[li]…[/li][/ul]

Hi everyone,

some more progress: I have been using GDB to find out where I get the segmentation fault. It seems that it happens whennew Frame(gClient->GetRoot(),200,200);is called. I found an old post here in the forum, but no clues from there either. [url]Compiling ROOT Gui applications I satisfy all dependencies described there.

Is there any dynamically loadable library that is responsible for gClient?

I almost lost hope, but at last I figured out how to do it.

Here is how you can deploy a truly stand alone OSX application bundle out of a stand alone ROOT GUI application which you can distribute to other colleagues without a ROOT installation.

Libraries

First get ROOT from git and compile it as described in http://root.cern.ch/drupal/content/installing-root-source you don’t need to install. After that make clean and make a duplicate copy of the whole root directory and name it e.g. root_runtime. In root_runtime, delete everything except the directories lib, icons, fonts, etc and bin. In the bin directory delete everything except the script thisroot.sh and root-config.

Then go inside the lib directory and check for the dependencies of all libs to see if anyone depends on some library other than those in the /ur/lib, which come automatically with OSX. Specifically you can grep for any dependency that is in /opt/ for systems that have macports installed.

If you see any output, that means that some library has a dependency on a non-OSX-standard library and needs to be fixed. In my case these were:

/opt/local/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.8 ) /opt/local/lib/liblzma.5.dylib (compatibility version 6.0.0, current version 6.5.0) /opt/local/lib/libpcre.1.dylib (compatibility version 4.0.0, current version 4.1.0) /opt/local/lib/libssl.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0) /opt/local/lib/libssl.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0) /opt/local/lib/libcrypto.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0) /opt/local/lib/libsqlite3.0.dylib (compatibility version 9.0.0, current version 9.6.0) /opt/local/lib/libssl.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0) /opt/local/lib/libcrypto.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0)
Copy these libraries to your root_runtime/lib/ directory using sudo and correct the ownership to yourself using chown and the permissions using chmod 644. Then apply the following ruby script for fix:

libs=Dir["*.dylib"] +Dir["*.so"] libs.permutation(2).each do |first, second| puts "Correcting #{first} inside #{second}" system("install_name_tool -change '/opt/local/lib/#{first}' './#{first}' ./#{second}")

The Application

Open a terminal, set the environment to your original root directory (thisroot.sh) and build your own project. I took the example from the ROOT User Guide, GUI section. Then compile it and make sure it works on your system. I put the code here for convenience:

Application.cxx

[code]#include <TApplication.h>
#include “Frame.h”

void showFrame() {
new Frame(gClient->GetRoot(),200,200);
}

int main(int argc, char **argv) {
TApplication theApp(“App”,&argc,argv);
showFrame();
theApp.Run();
return 0;
}
[/code]
Frame.h

#include <TGFrame.h> class TGWindow; class TGMainFrame; class TRootEmbeddedCanvas; class Frame : public TGMainFrame { private: TRootEmbeddedCanvas *fEcanvas; public: Frame(const TGWindow *p,UInt_t w,UInt_t h); virtual ~Frame(); void DoDraw(); virtual void CloseWindow(); ClassDef(Frame,0) };
Frame_LinkDef.h

#pragma link C++ class Frame;
Frame.cxx

[code]#include <TGClient.h>
#include <TCanvas.h>
#include <TF1.h>
#include <TRandom.h>
#include <TGButton.h>
#include <TRootEmbeddedCanvas.h>
#include <TApplication.h>
#include “Frame.h”

Frame::Frame(const TGWindow *p,UInt_t w,UInt_t h) : TGMainFrame(p,w,h) {
fEcanvas = new TRootEmbeddedCanvas (“Ecanvas”,this,200,200);
AddFrame(fEcanvas, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY,
10,10,10,1));
TGHorizontalFrame *hframe=new TGHorizontalFrame(this, 200,40);
TGTextButton *draw = new TGTextButton(hframe,"&Draw");
draw->Connect(“Clicked()”,“Frame”,this,“DoDraw()”);
hframe->AddFrame(draw, new TGLayoutHints(kLHintsCenterX,5,5,3,4));
TGTextButton *exit = new TGTextButton(hframe,"&Exit ",
“gApplication->Terminate()”);
hframe->AddFrame(exit, new TGLayoutHints(kLHintsCenterX,5,5,3,4));
AddFrame(hframe,new TGLayoutHints(kLHintsCenterX,2,2,2,2));
SetWindowName(“Simple Example”);
MapSubwindows();
Resize(GetDefaultSize());
MapWindow();
}
void Frame::DoDraw() {
TF1 *f1 = new TF1(“f1”,“sin(x)/x”,0,gRandom->Rndm()*10);
f1->SetFillColor(19);
f1->SetFillStyle(1);
f1->SetLineWidth(3);
f1->Draw();
TCanvas *fCanvas = fEcanvas->GetCanvas();
fCanvas->cd();
fCanvas->Update();
}
void Frame::CloseWindow()
{
TGMainFrame::CloseWindow();
gApplication->Terminate(0);
}
Frame::~Frame() {}
[/code]
Compile the code as described in the ROOT User Guide.

The Bundle

This has been adapted from this post: http://apple.stackexchange.com/questions/23725/is-automator-intended-to-create-distributable-stand-alone-apps. Open the automator, choose to start a new Application, then drag the following 2 actions:

  • Run apple script:
    Inside you write

on run {input, parameters} set p to POSIX path of (path to me) return {p} end run
in order to pass the current path to the next step. The you insert the second action:

  • Run shell script:
    inside you write

source $1Contents/Resources/root_runtime/bin/thisroot.sh $1Contents/Resources/application make sure to select /bin/bash as shell and pass input: as argument. Replace application with the name of your binary which you produced in the previous step. Save it as an app bundle.

Final step

copy the executable binary of your code and the whole root_runtime directory inside the bundle’s Resources directory, leave everything else there. You can use Finder to “show package content” or just use terminal “cp -r”.

Voila! that’s it. It is big (about 130 MB), but it works everywhere. You can make a DMG file to compress (about half size) it and send it to colleagues:

You can further trim your root_runtime by eliminating all the libs that you are sure are not needed. The program might not run on older versions of OSX, only the version that you actually compiled your code on. For me that is fine, because most colleagues already have the latest version. You might want to experiment with compiler flags such as:

or similar to make compatibility for older versions of OSX, but I didn’t try it.

Hope someone out there likes this too.

:slight_smile:

Though I haven’t bundled a Root based application using it, there is also the CPack tool which is part of CMake. Together with CMake, that can create the required bundle structure and copy/fixup dependencies with the BundleUtilities module. Here’s a short example using Qt, but should be adaptable to Root: http://www.cmake.org/Wiki/BundleUtilitiesExample