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.
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}")
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.
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.
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.
:-)