[Pitch] Resource Targets

In order to bring the discussion around Swift PM, Bundles and Resources forward (see also the related bug report SR-2866) and since I'm not really happy with the approach taken within Anders Bertelrud's draft proposal where we would basically simply place resources into existing targets and reference them via Bundle.moduleResources.path(forResource: "MyInfo", ofType: "plist"), I took some time to write an alternate proposal:

While it's definitely not yet complete (e.g. the entire "detailed design" section is missing) and I'm also not completely happy with the design myself, I feel like it already gets through the basic idea:

Using a more type-safe approach with some built-in types supported for free but flexible enough to be extended for supporting any (custom) type. The draft proposes a new target type (resourceTarget) to do that which is not necessarily required though to allow type-safe access to resources. Someone else could write a draft based off of mine which could get rid of the new target type. This is only intended as a starting point for further discussions, so feel free to express your thoughts of any kind or propose new ideas, which we might then use together to improve upon the proposal or write a new one.

Here are some things I personally think should be tackled by resources support in SwiftPM:

  • allow type-safe access with resource availability check from the compiler, preventing dynamic strings (removing the need for tools like SwiftGen entirely)
  • allow folder-based types for different contexts, e.g. to make it possible to initialize different UIImage resources from a .xcassets folder or a String from a directory containing .lproj folders

My proposal only solves the first requirement. That's what I'm planning to work on in an update of the proposal. What other goals do you have in mind that could be solved by SwiftPM's resources support?

2 Likes

Moved to Package Manager section.

What you're proposing is definitely one of the goal but is orthogonal to basic support for resources. This draft proposal is basically tackling how resources are declared in the manifest and how they should be "built" by the build system. From the draft proposal:

Historically, macOS and iO S resources have been accessed primarily by name, using untyped Bundle APIs such as path(forResource:ofType:) that have assumed that each resource is stored as a separate file. Missing resources or mismatched types (e.g. trying to load a font as an image) have historically resulted in runtime errors.

In the long run, we would like to do better. Because SwiftPM knows the names and types of resource files, it should be able provide type-safe access to those resources by, for example, generating the right declarations that the package code could then reference. Missing or mismatched resources would produce build-time errors, leading to earlier detection of problems.

This would also serve to separate the reference to a resource from the details about how that resource is packaged; instead of getting the path to an image, for example, and then loading that image using a separate API (which assumes that the image is stored in a separate file on disk), the image accessor could do whatever is needed to load the image based on the platform and client packaging choices that were made at build time, so that the package code doesn't have to be aware of such details.

In the short term, however, we want to keep things simple and allow existing code to work without major modifications. Therefore, the short-term approach suggested by this draft proposal is to stay with Bundle APIs for the actual resource access, and to provide a very simple way for code in a package to access the bundle associated with that code. A follow-on proposal would introduce typed resource references as additional functionality.

Thank you for putting thought into this.

I tend to agree with @Aciid that the general ideas of your proposal and your core principles of type safety and folder‐based types are good (and I kind of doubt anyone would disagree with that). But I also agree with him that it does not all need to be part of the initial proposal.


:+1: Thank you for insisting on generated constants though. I realize you probably already read it, but I am worried about the alternative dynamic string‐based access causing issues with future optimization.


The one part I don’t quite understand is the benefit to having resources in separate targets.

If they are within the same target as related source, then access control works as normal. They can default to internal and for the time being can be made public when desired by forwarding:

extension UIImage {
    public var coolImageForOthersToUse: NSImage {
        return MyResources.coolImage // internal
    }
}

Some future proposal might add a way elevate them to public automatically instead, but it isn’t immediately necessary. (I have no objection to such a future, but thinking about it now, I would probably use forwarding instead even if that future arrived, since having generated code in the public API seems like a headache for version compatibility. Forwarding allows (1) that it be exposed somewhere more intuitive in the API and (2) that the implementation is open to changing to something dynamically constructed without affecting the API.)

However, if the resources live in a separate target, then they have to be public and cannot be restricted to a particular target. (That has implications for incremental builds, and at the moment it also means clients can access every resource, since the package manager doesn’t gate import statements according to whether they are actually exposed in a product or not.)

I am not very strongly opposed to this direction though (partly I am just already accustomed to the other way), so if you have a good reason, please share it.

The main reason for me to give resources their own target type are the current naming conventions for Swift packages:

As of now there are by default two folders in the root of a package, Sources and Tests. These folders are directly related to the existing two target types, target and testTarget. For me, Sources are an entirely different concept than Resources and my team agrees with me on that, which is why we are already separating code from resource files today in every project, as you can see here:

So, my initial thought naturally was to put a Resources folder to the side of the Sources folder and to keep things consistent with current conventions the logical conclusion was to introduce a separate target type for resources.

I hope this answers your question.

On the file system side, that happens to be exactly what I do already too:

Resources/SomeTarget/Resource.png
Sources/SomeTarget/Source.swift

Partly, as you say, it was just intuitive. And partly, it was motivated by staying out of the package manager’s domain in order to seamlessly include resources the package manager would otherwise mistake for source (e.g. Template.swift).

But regardless of the file system side of things, it is the location of the synthesized constants that matters more to me. Synthesizing them directly in the associated module would be much simpler for both the manifest and the access control model from what I can see. I think the file system layout and the synthesized access are completely orthogonal. (Indeed my resources are currently separate on the file system but directly embedded in the target, so it is certainly possible to treat the two decisions independently.)

Terms of Service

Privacy Policy

Cookie Policy