[Proposal] [Foundation] Allowing for system bundles on Linux

All,

I’m looking for feedback on a proposal I'm working on for Foundation on Linux. On Darwin OSes, we are able to provide resources from system-installed bundles in /System and /Library and locate them through the various bundle APIs (CFBundle or Foundation’s Bundle class); on Linuxes, BSDs and other Unix-y systems those paths do not exist and system resources are organized differently. We would like to start shipping resource files for Core Foundation for tasks such as providing localized content, but also store them in a way that's idiomatic when taking the system install conventions on these platforms into account, and also enable other frameworks to do so if they need to.

To this end, I’ve just sent out a PR for a proposal for installed bundles that would allow CFBundle and higher-level APIs to find code and resources installed in /usr/local or in the prefix of your choice (e.g. /opt/myapp/). In short:

- Directories of the form <anywhere>/share/<name>.resources are recognized as installed bundles.

- Their main executable is <anywhere>/lib/lib<name>.so (for frameworks), or <anywhere>/bin/<name> (for executable bundles). Fallbacks from/to lib32/lib64 or sbin are also allowed, and <name> will be sourced from Info.plist’s CFBundleExecutable key if present, as usual.

- The resources path is <anywhere>/share/<name>.resources (the same as the bundle location).

- Auxiliary executables and embedded frameworks are in <anywhere>/libexec/<name>.executables.

- Framework headers are in <anywhere>/include/<name>/*.h, to support #include <name/name.h> syntax.

- The layout of these directories is the same as that of an iOS bundle for their respective slice of content (e.g.: <anywhere>/share/<name>.resources/en.lproj/Localizable.strings; <anywhere>/libexec/<name>.executables/Frameworks/MyFramework.…)

Despite being at separate paths, as required by the relevant specs, these disparate locations become a single logical CFBundle; you can find the correct path using the existing CFBundle… accessor API (or the existing Bundle properties). Any path that is optional in Darwin is also optional under this scheme.

In addition to this, we will still support a ‘freestanding’ bundle structure that can be used for private or embedded frameworks, where almost all files are grouped into one directory, and Bundle APIs will continue supporting any bundle structure they already do.

The full text of the proposal is here: <https://github.com/apple/swift-corelibs-foundation/pull/1392&gt;, and an implementation will follow shortly. Let me know if you have any feedback! ✾

5 Likes

I have no horse in this race, but as a curiosity, what in
`<prefix>/share/<name>.resources/` and
`<anywhere>/libexec/<name>.executables/` is not sufficiently described
by `share` and `libexec`, respectively?
Best,
  Zachary Waldowski
  zach@waldowski.me

1 Like

Reposting my answer, as it was mistakenly not sent to the list:

The intent here is to preserve the ability for multiple bundles to have separate resource namespaces — for example, InfoPlist.strings is a fixed name and we expect each bundle that wants to opt into Info.plist localization to have a file with that name. Just using share/ won’t really cut it.

The extensions on subdirectory names are there to mark them as intentionally part of a bundle; I have gone back and forth on this before sending the proposal (a previous version used the presence of Info.plist, e.g. share/MyBundle/Info.plist to note that the directory is intended to be a bundle’s resources directory) but requiring all bundles to have Info.plists is a requirement that Darwin doesn’t have. I’m open to feedback on this.

1 Like

I am rather curious on why the CF team chose this Bundle architecture on Windows:

/CoreGraphics.dll
/CoreGraphics.resources

Instead of something more Darwin/NextStep-like as:

/CoreGraphics.bundle/CoreGraphics.dll
/CoreGraphics.bundle/Resources

Any historic hints/idea?

And about the Linux bundle support:

  • First, I think you are doing a great job implementing and laying out this proposal!
  • Second, this bundles could be initializated from the executable/library path?

For example:

let cgBundle = Bundle(path: "/usr/local/lib/CoreGraphics.so")!
cgBundle.resourcePath // "/usr/local/share/CoreGraphics.resources"

We considered that, but the issue there is that means that the .bundleURL would not be a directory URL, which is pretty sure to trip someone up.

Yes, that would be a problem. I suppose a second phase of FHS/Freestanding Bundle support would be to locate the bundle directory from the main executable location extending _CFBundleCopyBundleURLForExecutablePath (which is used by Bundle.main, Bundle(class:) and Bundle(identifier:)).

Once this is implemented we could debate the introduction of a new initializer Bundle(executableURL:) / Bundle(executablePath:) which would solve the aforementioned problem.

let bundle = Bundle(executablePath: "/usr/local/lib/libCoreGraphics.so")!
bundle.bundlePath // "/usr/local/share/CoreGraphics.resources"
bundle.resourcesPath // "/usr/local/share/CoreGraphics.resources"
bundle.executablePath // "/usr/local/lib/libCoreGraphics.so"

let bundle = Bundle(executablePath: "CoreGraphics.dll")!
bundle.bundlePath // "CoreGraphics.resources"
bundle.resourcesPath // "CoreGraphics.resources"
bundle.executablePath // "CoreGraphics.dll"

let bundle = Bundle(executablePath: "Bundle.bundle/Contents/MacOS/Bundle")!
bundle.bundlePath // "Bundle.bundle"
bundle.resourcesPath // "Bundle.bundle/Contents/Resources"
bundle.executablePath // "Bundle.bundle/Contents/MacOS/Bundle"

This new initializer would also allow to retrieve a Mach-O binary-embedded Info.plist in executables without relying in Core Foundation. For example, now if you what to get the embedded Info.plist you have to use:

CFBundleCopyInfoDictionaryForURL(URL(fileURLWithPath:"/usr/libexec/secd") as CFURL)

While with the new API could be done directly with Foundation:

Bundle(executablePath: "/usr/libexec/secd")?.infoDictionary

Radar Reference: 38359891

You are correct on the _CFBundleCopyBundleURLForExecutablePath() bit. Bundle(identifier:) is a little more involved — we may need to query or parse the main executable's linkages to find any bundles corresponding to linked executables (which would make e.g. Bundle(identifier: "com.apple.CoreFoundation") work.)

I'm very ambivalent on the idea of introducing API surface on Linux that we do not also provide on Darwin.

I agree that we should maintain parity with Darwin Foundation. Maybe Swift Foundation and Darwin Foundation should have some form of Evolution process ala Swift (I understand that Darwin Foundation is a proprietary closed-source framework but Apple could benefit from this too).

Also I am curious to know if there is any chance/aim to substitute Obj-C Foundation with the unified open-source Swift Foundation once ABI stability allows that.

Just want to say I think this proposal is really great, and I'm looking forward to it. I'm not a distro expert, but it seems like an elegant solution which embraces the host platform rather than working despite it.