|
| 1 | +# Installed Bundles on UNIX-like Systems |
| 2 | + |
| 3 | +This document covers {CF,NS,}Bundle behavior on Linux, with an eye to keeping things working for non-Linux OSes that have similar POSIX-y setups (in particular, the BSD family.) It covers _two_ different styles of bundle, which are suitable both for things that are installed systemwide (e.g. as part of a platform-supplied package manager), and for freestanding bundles that are embedded (e.g. resource bundles) or executed (e.g. app bundles) but not installed systemwide. |
| 4 | + |
| 5 | +The aim of this proposal is to provide idiomatic ways for POSIX-y systems to use bundle API, including resource access, in a way equivalent to what Darwin OSes do for bundles installed in `/System` or `/Library`. |
| 6 | + |
| 7 | +## Installed bundles |
| 8 | + |
| 9 | +An installed bundle is intended to be installed in a directory hierarchy that models the use of `--prefix=…` in autoconf-like configuration tools. This is suitable for the kind of bundles that we would install in `/System/Library` or in `/Applications`, systemwide, on Darwin OSes: system frameworks, apps installed for all users, and so on. |
| 10 | + |
| 11 | +This setup complies with the [Filesystem Hierarchy Standard (FHS) 3.0](https://refspecs.linuxfoundation.org/fhs.shtml), used by most Linux distributions. It also fits with the intent of the `/usr` and `/usr/local` directories of many BSD systems (e.g. from FreeBSD's [hier(7)][].) |
| 12 | + |
| 13 | +[hier(7)]: https://www.freebsd.org/cgi/man.cgi?hier(7) |
| 14 | + |
| 15 | +### Definition |
| 16 | + |
| 17 | +An installed bundle exists if there is: |
| 18 | + |
| 19 | + - A directory with the `.resources` extension; |
| 20 | + - contained in a directory named `share`. |
| 21 | + |
| 22 | +The base name of this bundle is the name of this directory, removing the extension. (E.g., for `/usr/share/MyFramework.resources`, the base name is `MyFramework`.) |
| 23 | + |
| 24 | +### Bundle Paths |
| 25 | + |
| 26 | +The bundle's `.resources` directory contains resources much like an [iOS-style flat bundle](https://developer.apple.com/library/content/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1). Unlike on iOS, however, executables are not contained directly inside this directory. Instead, given that the bundle exists in `share/MyFramework.resources`, then an installed bundle will source from the following additional paths within the prefix the bundle is installed in (the parent directory of the `share` directory): |
| 27 | + |
| 28 | + - The main executable will be searched in the `bin`, `sbin`, or `lib…` directories. |
| 29 | + - Executable bundles will search `bin`, then `sbin`, for an executable with the same base name as the resources folder, plus any platform prefix or suffix for executables. e.g.: `bin/MyApp`. |
| 30 | + - Framework bundles will search the appropriate `lib…` directories for a library of the same name, adding appropriate platform prefixes or suffixes for shared library file (falling back to just `lib` otherwise). e.g.: `lib/libMyFramework.so`. |
| 31 | + - Auxiliary executables and libraries will be in, or in subdirectories of, `libexec/MyFramework.executables`; |
| 32 | + - For framework bundles, `include/MyFramework` will contain headers, though this directory does affect the return values of the runtime API. |
| 33 | + |
| 34 | +Both paths are optional. If they don't exist, the API will behave much like on Darwin platforms. |
| 35 | + |
| 36 | +For installed bundles, the bundle's main executable is not searched within its `.resources` path. Instead, we expect that it be installed in the `bin` or `sbin` directory, if executable, or the appropriate `lib…` directory, if a shared library file. These files should be named the same as the main executable name (from Info.plist, or the framework's base name as a default value) plus any appropriate executable or library suffixes or prefixes for the platform. For example: |
| 37 | + |
| 38 | +``` |
| 39 | +share/MyApp.resources |
| 40 | +bin/MyApp |
| 41 | +``` |
| 42 | + |
| 43 | +or: |
| 44 | + |
| 45 | +``` |
| 46 | +share/MyFramework.resources |
| 47 | +lib64/libMyFramework.so |
| 48 | +``` |
| 49 | + |
| 50 | +The bundle path for an installed bundle is the same as its resources path. That's the path that needs to be passed into `CFBundleCreate()` and equivalents for the bundle to be successfully created; passing associated directory names will return `NULL`. [⊗](#nullForInnerPaths) |
| 51 | + |
| 52 | +As an example, the following values are produced by invoking these functions on the bundle created with `CFBundleCreate(NULL, …(…"/usr/local/share/MyFramework.resources"))`: |
| 53 | + |
| 54 | +Function | Path returned |
| 55 | +---|--- |
| 56 | +`CFBundleCopyBundleURL` | `/usr/local/share/MyFramework.resources` |
| 57 | +`CFBundleCopyExecutableURL` | `/usr/local/lib/libMyFramework.so` |
| 58 | +`CFBundleCopyResourcesDirectoryURL` | `/usr/local/share/MyFramework.resources` |
| 59 | +`CFBundleCopySharedSupportURL` |`/usr/local/share/MyFramework.resources/SharedSupport` |
| 60 | +`CFBundleCopyPrivateFrameworksURL` | `/usr/local/libexec/MyFramework.executables/Frameworks` |
| 61 | +`CFBundleCopySharedFrameworksURL` | `/usr/local/libexec/MyFramework.executables/SharedFrameworks` |
| 62 | +`CFBundleCopyBuiltInPlugInsURL` | `/usr/local/libexec/MyFramework.executables/PlugIns` |
| 63 | +`CFBundleCopyAuxiliaryExecutableURL(…, CFSTR("myexec"))` | `/usr/local/libexec/MyFramework.executables/myexec` |
| 64 | +`CFBundleCopyResourceURL(…, CFSTR("Welcome"), CFSTR("txt") …)` | `/usr/local/share/MyFramework.resources/en.lproj/Welcome.txt` |
| 65 | + |
| 66 | +The structure inside any of these paths is the same as that of an iOS bundle containing the appropriate subset of files for its location; for example, the resources folder contains localization `.lproj` subdirectories, the `Frameworks` directory inside the `….executables` contains frameworks, and so on. |
| 67 | + |
| 68 | +## Freestanding bundles |
| 69 | + |
| 70 | +We will also support freestanding bundles on platforms that also support installed bundles. These bundles are structured much like their Windows counterparts, where there is a binary and a `.resources` directory on the side, like so: |
| 71 | + |
| 72 | +``` |
| 73 | +./MyFramework.resources |
| 74 | +./libMyFramework.so |
| 75 | +``` |
| 76 | + |
| 77 | +### Bundle Paths |
| 78 | + |
| 79 | +The bundle path for an installed bundle is the same as its resources path. That's the path that needs to be passed into `CFBundleCreate()` and equivalents for the bundle to be successfully created; passing associated paths, including the path to the executable, will return `NULL`. [⊗](#nullForInnerPaths) |
| 80 | + |
| 81 | +The `.resources` directory functions exactly like an iOS bundle, returning the same paths. By way of example, for a freestanding bundle created with `CFBundleCreate(NULL, …(…"/opt/myapp/MyFramework.resources"))`: |
| 82 | + |
| 83 | +Function | Path returned |
| 84 | +---|--- |
| 85 | +`CFBundleCopyBundleURL` | `/opt/myapp/MyFramework.resources` |
| 86 | +`CFBundleCopyExecutableURL` | `/opt/myapp/libMyFramework.so` |
| 87 | +`CFBundleCopyResourcesDirectoryURL` | `/opt/myapp/MyFramework.resources` |
| 88 | +`CFBundleCopySharedSupportURL` |`/opt/myapp/MyFramework.resources/SharedSupport` |
| 89 | +`CFBundleCopyPrivateFrameworksURL` | `/opt/myapp/MyFramework.resources/Frameworks` |
| 90 | +`CFBundleCopySharedFrameworksURL` | `/opt/myapp/MyFramework.resources/SharedFrameworks` |
| 91 | +`CFBundleCopyBuiltInPlugInsURL` | `/opt/myapp/MyFramework.resources/PlugIns` |
| 92 | +`CFBundleCopyAuxiliaryExecutableURL(…, CFSTR("myexec"))` | `/opt/myapp/MyFramework.resources/myexec` |
| 93 | +`CFBundleCopyResourceURL(…, CFSTR("Welcome"), CFSTR("txt") …)` | `/opt/myapp/MyFramework.resources/en.lproj/Welcome.txt` |
| 94 | + |
| 95 | +### Embedded Frameworks |
| 96 | + |
| 97 | +The two formats interact in the case of embedded bundles. Since the inside of any bundle is not compliant with the [LHS](https://refspecs.linuxfoundation.org/fhs.shtml), bundles inside other bundles _must_ be freestanding frameworks. This includes frameworks in the private or shared frameworks paths, built-in plug-ins, and so on. |
| 98 | + |
| 99 | +## Alternatives considered |
| 100 | + |
| 101 | +### XDG |
| 102 | + |
| 103 | +There are two filesystem specs that are relevant to Linux distribution: the Filesystem Hierarchy Specification (FHS), maintained by the Linux Foundation; and the XDG Base Directory Specification, maintained by freedesktop.org. |
| 104 | + |
| 105 | +While both define where files can be installed on a Linux system, they differ substantially: |
| 106 | + |
| 107 | +- The FHS defines the directory structure for `/`, the root directory. In particular, it dictates where software and libraries are installed and the structure of `/usr` and `/usr/local`, which is where autoconf-style installations end up by default, and whose structure is mimicked when using `./configure --prefix=…` to isolate app and library environments to a new prefix. |
| 108 | + |
| 109 | +- The XDG spec defines the directory structure for _user data_, such as preferences, within a single user's home directory. Compatible desktop systems will set environment variables to guide an application to write and read per-user information in paths the current user can read and write to. No part of this spec defines where _code_ should be located, though we will have to heed it for such things as `NSSearchPathsFor…` and current-user defaults reading and writing. |
| 110 | + |
| 111 | +In comparing the two, I produced a bundle structure suitable for use with the FHS so that it can be incorporated mostly as-is into the lifecycle of autoconf or CMake-driven development on Linux (i.e., making `make install` mostly just work). |
| 112 | + |
| 113 | +Applications, both UI and server, are usually installed systemwide on Linux (in `/usr` or `/usr/local`), or in appropriate prefixes (generally under `/opt`) for specialized needs, and there isn't a concept of a standalone bundle that may end up in the home directory as it may happen with Mac apps, so the XDG spec becomes a little less relevant. |
| 114 | + |
| 115 | +### Use the executable's path as the bundle location |
| 116 | + |
| 117 | +We tried this as our first attempt to avoid using the containing directory path for installed bundles, since all bundles would then have the same location. We moved away from this to the use of the `.resources` directory because it is likely that code will hardcode the assumption of a bundle location being a directory. |
| 118 | + |
| 119 | +## Footnotes |
| 120 | + |
| 121 | +<span id="shouldWeFallBack">⊖</span>: Should we mandate that an executable that _could_ be in an installed bundle (because the binary is in a bin or lib… directory) _must_ be in an installed bundle, e.g. that we won't fall back to searching for a freestanding bundle? |
| 122 | + |
| 123 | +<span id="nullForInnerPaths">⊗</span>: This is consistent with Darwin OSes' behavior of returning `NULL` for any path beside the bundle path, even if that path _is_ the auxiliary or main executable path for some bundle. |
0 commit comments