Skip to content

Instantly share code, notes, and snippets.

@xaratustrah
Last active January 22, 2019 10:55
Show Gist options
  • Save xaratustrah/aa0fde262bf827242db2 to your computer and use it in GitHub Desktop.
Save xaratustrah/aa0fde262bf827242db2 to your computer and use it in GitHub Desktop.
root_gui_application.md

Stand alone GUI OSX Application Bundle using ROOT Libraries

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 it. 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.

otool -L *.so | grep opt

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

#include
#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;
}

Frame.h

#include
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

#include
#include
#include
#include
#include
#include
#include
#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() {}

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:

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

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:

-mmacosx-version-min=10.7

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

Hope someone out there likes this too.

:-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment