README.md 12.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# GTK Mac Bundler
  
The script **gtk-mac-bundler** is a helper script that creates application
bundles from GTK+ executables for Mac OS X. The resulting bundle
contains a complete self-hosting GTK+ installation, ready to run on any
computer running Mac OS X 10.4 or later.

GTK+ and its companion libraries are automatically put into the
bundle, but the application packager must tell the script what other
files and directories to copy.

**Note:** This tool is written to work with a jhbuild built GTK+, not
Macports. If you build with Macports, make sure that the Pango atsui
module is built in to the Pango library, by using the configure flag:

    --with-included-modules=basic-atsui.


## Setting up

21
22
23
24
Run `make install`; this installs the script into `~/bin` folder. Make sure you
have that directory in your `PATH`, or use the absolute path when starting
the script.

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

## Prerequisites

You need to have a GTK+ installation as done for example by using
jhbuild as described on the [GTK+ OS X project site](http://wiki.gnome.org/Projects/GTK%2B/OSX/Building)

The `gtk-mac-bundler` command needs to be run inside an environment
setup for running the GTK+, for example inside a jhbuild shell.

For the more in-depth parts described here, you are expected to be
familiar with the layout of OS X bundles.


## Quick introduction

You need to create a configuration file describing the application
bundle. The very simple example, `example.bundle`:

    <?xml version="1.0"?> <!--*- mode: xml -*-->
    <app-bundle>
      <meta>
        <prefix>/opt/gtk</prefix>
      </meta>
    
      <plist>${project}/Info.plist</plist>

      <!-- Optionally specify a launcher script to use. Builtin script is used if not specified.  -->
      <!--launcher-script>${project}/launcher.sh</launcher-script-->

      <!-- The executable for the application -->
      <main-binary>${prefix}/bin/my-app</main-binary>
    
      <!-- Modules for GTK+ (image loaders, etc) -->
      <binary>${prefix}/lib/gtk-2.0</binary>
    
      <!-- Any additional data, like images, or Glade files -->
      <data>
        ${prefix}/share/my-app
      </data>
    
    </app-bundle>

Put this file into a directory, together with the standard `Info.plist`
file that all Mac OS X bundles need. Then run the script with the
bundle configuration path as argument. This will create a bundle in
the current directory.


## In-depth look at file format

The simple example above works for small and simple applications, but
often you will need to specify more data to copy in, or to have more
detailed control over what is copied. 

Stefan Weil's avatar
Stefan Weil committed
79
Here we go through in more depth how this can be achieved. Every file
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
and directory to copy is specified with a source path, and an optional
destination path. An example that copies an entire directory
recursively, from the installation prefix:

    <data>
      ${prefix}/share/my-data
    </data>

Since no destination path is specified, the directory will be copied
into the standard location. Note that the special value `${prefix}` is
used in the source path, which makes the default destination path be
relative to the `bundle prefix`, which is the `Contents/Resources`
directory inside the bundle. Applications that use the free desktop
`data dir` specification to find their data will automatically find
its data this way (applications can also use the Carbon or Cocoa
bundle APIs to find data).

Another useful "special value" that can be used in source paths is
`${project}`, which refers to the directory where the XML file is
located. An example:

    <data dest="${bundle}/Contents/Resources/etc/gtk-2.0/gtkrc">
      ${project}/gtkrc
    </data>

Here you notice that a destination path is supplied. This must be done
since there is no way to figure out where to put the file that doesn't
come from a `${prefix}` location. You can also see another variable
used, this time in the destination path, `${bundle}`. All destination
paths must be either unset or start with `${bundle}`.

The remaining variables are:

 * `${env:name}` - evaluates to the environment variable `name`

 * `${pkg:module:name}` - evaluates to the value of the pkg-config
                      variable `name` in the module `module`

An example use case of the latter is for finding files that are
located in a versioned directory without having to maintain the
directory name manually. For example:

    <binary>
      ${prefix}/lib/gtk-2.0/${pkg:gtk+-2.0:gtk_binary_version}/loaders
    </binary>


## Metadata

Now that we know how paths can be specified, let's back up and see the
beginning of a more extensive example. The first thing to setup is some
metadata:

    <app-bundle>
    
      <meta>
        <prefix name="default">${env:PREFIX}</prefix>
        <destination overwrite="yes">${env:HOME}/Desktop</destination>
      </meta>
    
      [...]

    </app-bundle>

We use the `${env}` variable to get the installation prefix (which comes
from the **jhbuild** build script). We also use it to set the destination
of the app bundle on the current user's desktop.

You can set additional prefixes and refer to them in paths:

      <meta>
        <prefix name="default">${env:PREFIX}</prefix>
        <prefix name="gst">/opt/gstreamer</prefix>
        <prefix name="stuff">/opt/stuff</prefix>
      </meta>

The additional prefixes are referred to by using `${prefix:name}`, where
`name` is one of the names defined above.


## Installed data

Next you need to list the data to install. Some is required for the
app bundle to be complete:

    <plist>${project}/../data/Info.plist</plist>
    
    <launcher-script>${project}/launcher.sh</launcher-script>
    
    <main-binary>${prefix}/bin/giggle</main-binary>

171
172
173
174
175
176
177
178
179
180
181
The file `Info.plist` is the standard Mac OS file for bundles. See
[documentation](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html)
on those.

The launcher script may be used to setup the necessary environment for
the application to work, but see the section on Code Signing below: A
compiled executable is genererally necessary instead. An example
launcher script for Gtk3 based applications is in the examples
directory; there's also a python-launcher.c program there that can be
compiled to supply the executable for a Gtk3 application written in
Python.
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

Unsurprisingly, the main-binary tag specifies the executable to launch
when starting the application.


## General application data

Next we handle any general data to copy into the bundle. A
straight-forward example:

    <data dest="${bundle}/Contents/Resources">
      ${project}/Giggle.icns
    </data>
    
    <data dest="${bundle}/Contents/Resources/etc/gtk-2.0/gtkrc">
      ${project}/gtkrc
    </data>


## Binaries

When it comes to binaries (executables and loadable modules), the tag
`binary` should be used. The difference between `binary` and `data` is
that all copied binaries are scanned for library dependencies, which
are automatically copied into the bundle. This way, you only need to
list your executables and plugins. Again, an example:

    <binary>
      ${prefix}/lib/pango/${pkg:pango:pango_module_version}/modules/pango-basic-atsui.so
    </binary>

This will copy the ATSUI font module for Pango. This in turn will pull
in any needed libraries that it links to.

Note that you can use wildcards for all data and binary tags, but only
in the last path component, for example:

    <binary>
      ${prefix}/lib/gtk/2.10.0/loaders/*.so
    </binary>

Stefan Weil's avatar
Stefan Weil committed
223
An interesting twist is that some libraries that are built as dylibs
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
are used as loadable modules. Dlopen doesn't have a problem with this,
it will cheerfully open either. The problem comes because unlike
Linux, Mac OS X uses different file extensions and formats, so libtool
will set up dlopen to search for **libfoo.so** after it built
**libfoo.dylib**. Libtool also makes **libfoo.la** which will tell dlopen
where to look, but **gtk-mac-bundler** deletes those files from the
application bundle. If you're bundling an app that needs **libfoo.la**,
just put it in a data element and **gtk-mac-integration** (version 0.5.2
and later) will copy it in *after* doing the `*.l?a cleanup: <data>
${prefix}/lib/libfoo*.la </data>`


## Code Signing

Until mid 2016 **gtk-mac-bundler** depended upon a launcher shell script
to configure the environment. You can still do this, but it causes
problems with code-signing. Apple recommends that one not install
scripts into the Contents/MacOS folder of a bundle, nor should one
attempt to sign scripts. Doing so will produce signatures that are
incompatible across different versions of MacOS.

That means that for compiled-executable programs you need to launch
first and then configure the environment before starting gettext,
Gtk+, and any other libraries that read the environment for their
configuration. There are a variety of ways to do this, including
adding a module which reads configuration data from MacOS's *'defaults'*
preferences system or reading a YAML (a.k.a *'ini'*) file and exporting
the results to the environment with `setenv()`.

For script-based programs one must create a small executable program
which prepares the interpreter, launches it, and points it at a
Stefan Weil's avatar
Stefan Weil committed
255
startup script which configures the environment. Such a program,
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
written in C, is provided in `examples/python-launcher.c`; a companion
startup script, `gtk_launcher.py`, does the environment
configuration. `python-launcher.c` should work as-is for most python
programs; `gtk_launcher.py` will require a bit of specializing to work,
in particular the import statement and startup call at the end of
the file.

To build `python-launcher.c`, start a jhbuild shell for your target and run:

    gcc -L$PREFIX/lib `python-config --cflags --ldflags` -o $PREFIX/bin/your-launcher \path/to/gtk-mac-bundler/examples/python-launcher.c

Remove the `<launcher-script>` element from your bundle file and change
the main-binary element to:

     <main-binary>
         ${prefix}/bin/your-launcher
     </main-binary>
     
In this case, leaving the name as `your-launcher` will actually work:
The bundler will rename the file to the value of the
CFBundleExecutable key in `Info.plist`.

Copy `gtk_launcher.py` to the folder where your bundle file is, rename
it to your liking (`your_launcher.py`, from now on), and edit it as
necessary.

Add a data element for `your_launcher.py` to your bundle file:

    <data dest=${bundle}/Contents/Resources>
      ${project}/your_launcher.py
    </data>

Add the following key to the top dict in `Info.plist` for your project:
    
    <key>GtkOSXLaunchScriptFile</key>
    <string>your_launcher.py</string>


## Icon themes

GTK+ icon themes have their own tag, `icon-theme`. The name of the
theme (which currently must reside in the default prefix) specifies
which theme to copy, and the `icons` property specifies which icons to
copy. The valid values are:

  * `auto` - tries to copy all icons names that match strings in all
         copied binaries; this will not always work perfectly but is
         good for getting started, and for simple applications

  * `all` - copies all icons

  * `none` - copies no icons, this can be used in combination with
         specifying icons manually with a regular data tag; the icon
         theme itself must be listed in order to get the index theme
         copied and an icon cache generated

Note that the base theme `hicolor` is always copied, since it is
required by GTK+. An example:

    <icon-theme icons="auto">
      Tango
    </icon-theme>


## Debugging the bundle

In order to debug the created app bundle (most notably the launcher
script), you can set the environment variable `GTK_DEBUG_LAUNCHER` before
starting the applications directly from a terminal. e.g.:

    GTK_DEBUG_LAUNCHER=yes MyApp.app/Contents/MacOS/MyApp

This will print out the steps performed by the launcher script before
the application executable is started.

Note also that the Console.app program that comes with OS X is very
useful when debugging app bundle problems. You can use it to see any
output from the application the console log window.

To run the application under gdb, do:

    GTK_DEBUG_GDB=yes MyApp.app/Contents/MacOS/MyApp


## License

The script is Copyright (C) 2007, 2008 Imendio AB, and licensed under
the GNU General Public License version 2. Note that the resulting bundle created
by the script is not covered by that, each invidiual library has its
own license. See `COPYING` for more.