As we start to use Swift to build our application on Windows, we've written a few helpers to help us start migrating packages to have cross platform implementations. The helpers are typically simple static functions that looks similar to the following:
/// A helper to only return the passed items if the current platform is either macOS or iOS.
///
/// - parameter items: The items that will be returned if, and only if, the platform is macOS or iOS.
static func onApple(_ items: [Element]) -> [Element] {
#if os(macOS) || os(iOS)
return items
#else
return []
#endif
}
The problem however, is that we have many packages spread across a few Package.swift files and we'd like to share these helpers since we will have to do similar work across all of our packages. From what I can tell and from my searching there isn't any way to share these helpers.
Is there some way to accomplish this, or would we need to build something new in to the package manager to allow for this behavior?
I can give that a try, but on the face of it I don't completely understand how that would work since we need to reference the functions in the Package.swift file, not in a target that is defined by the Package.swift file. I've included a sample repo here to demonstrate the code we're trying to reuse in various Package.swift files https://github.com/brianmichel/SwiftPackageManagerSharedHelpers/blob/main/Frameworks/App/Package.swift#L6-L49.
After reading a bit about the @_implementationOnly directive I'm still unsure how this would make this shared code available for use within the Package.swift file, but I can see if there's something here maybe I'm missing.
Yeah I don't think this is possible at the moment. This has cropped up a few times (there may have even been a GSoC pitch to look into it) but currently there is no way to pull in external files or modules into the manifest (it's a bit of bootstrapping conundrum)
Yeah I figured as much (the bootstrapping conundrum that is), our work around for the time being can be just copying these helpers in, it's not the end of the world, but being able to more easily bifurcate things based on the platform would certainly be a welcome change in the future of SPM.
If you end up coming across that GSoC pitch I'd love to read it!
Yeah FWIW I do the same things with Swift scripts - I have a load of helper functions I just copy into every script, mainly to make shelling out to external processes easier.
Here's the GSOC pitch which I think is what I was thinking of but that might help solve the same problem of pulling in external files/modules from a top level Swift file (script or manifest)
The current solution for me is to manually copy and paste the contents to different Package.swift files (Or we use a tool to manage and append the helper func to different Package.swift files)
At one time I used the strategy of putting it all in an external tool. The manifest would have a generated portion marked at its beginning and end by some reliably unique commented token. The tool would find them and replace everything in between with the latest state of the utility code. You still have to check the generated code into source control, but at least you are only defining it in one place. These days, I would probably implement the tool as a command plugin.
For the sake of any human readers, I prefer as much as possible to keep the basic, uncluttered package declaration at the top. I then apply any repetitive aspects algorithmically in retroactive steps at the bottom. (See the post linked below.) Hence I would advise, where possible, dumping the utility code in between, so that it doesnāt displace legible information but is still available in time for any algorithms to use.
Your initial example contains this:
#if os(macOS) || os(iOS)
#if should only be used in a manifest as a workaround if the intended effect is otherwise impossible. Generally you should aim instead to use condition: .when(platforms: [...]). Using #if switches based on the host that is loading the manifest, whereas .when(platforms:) switches based on the target platform for which you are building. The latter keeps a consistent package definition with a nuanced build, whereas the former creates a different package depending on where the manifest is loaded, which can create havoc for pins, break crossācompilation and cause other related problems.
Eventually, we'd like to support this as part of what I call "Generalized support for additional manifest API". This is currently more of a vague idea, but it's definitely something that's needed for both extensibility of SwiftPM itself as well as more localized use cases such as yours.