Set APPLICATION_EXTENSION_API_ONLY on a SPM package

Is there a way to specify APPLICATION_EXTENSION_API_ONLY on a SPM package? I've tried the following but I don't get any warnings when compiling when a missing API is used.

    swiftSettings: [
                .define("APPLICATION_EXTENSION_API_ONLY=YES"),
    ]),

For context, here's a link to the App Extension Programming Guide

2 Likes

This setting should already be true in most cases, see swift-package-manager/PIFBuilder.swift at main · apple/swift-package-manager · GitHub

Did you see any issues with that?

I have not seen that. It is very helpful.

I am not sure if it helps though. If I understand correctly the APPLICATION_EXTENSION_API_ONLY=YES is automatically added if there are no system modules linked to the SPM package. Assuming that UIKit is a system module, this would mean that for a SPM package that uses UIKit the APPLICATION_EXTENSION_API_ONLY would not be set. So for example if I had common UI code within a SPM package that imported UIKit used both by an iOS app and an extension, no warnings are triggered.

This refers to using packages which use things like Apple Developer Documentation, for libraries that are part of the SDK, you explicitly do not have to use this API.

for libraries that are part of the SDK, you explicitly do not have to use this API.

I'm not sure I understand what you mean by that. Would you mind clarifying it for me?

If a package is using a library that's part of the SDK, such as UIKit, it won't use the systemLibrary(name:path:pkgConfig:providers:) API at all and therefore would not end up being a package that depends on a system module for the purpose of the check in swift-package-manager/PIFBuilder.swift at main · apple/swift-package-manager · GitHub

If UIKit is not considered system module under this scenario, then I understand that APPLICATION_EXTENSION_API_ONLY=YES is being set when my package is built.

However, using an API that is not available does not give any warnings/errors. For example, I can write in an SPM package:

public func someFunction() {
    let app = UIApplication.shared
    print(app)        
}

And I can import that package from the extension and call this function. There is no runtime crash and the extension runs normally, even printing the application but I'd like to think that there is a reason this is marked as NS_EXTENSION_UNAVAILABLE.

If I write that function in the extension target it triggers a build error preventing me from building that extension altogether. This is what I would expect when defining such a function in a SPM package. Am I misunderstanding something?

This is indeed a bug and should produce a build error as you mentioned - can you please also file an issue using Feedback Assistant which we can use to track this?

Thank you for the quick responses. I've submitted a bug with a link to this thread, Feedback Assistant.

Ummm..

In today's Xcode 13 beta 3, it seems that the compiler fails when using a library containing UIApplication.shared through SPM, even in an app target with APPLICATION_EXTENSION_API_ONLY set to "NO". So it just breaks our build with a few dependencies which are using UIApplication.shared.

It should be safe and not an error in an app target. Is there a way to keep the original correct behavior? (Allowing using UIApplication.shared in SPM dependency for app target.)

6 Likes

As stated in the Xcode 13 beta 3 release notes:

  • Linking Swift packages from application extension targets or watchOS applications no longer emits unresolvable warnings about linking to libraries not safe for use in application extensions. This means that code referencing APIs annotated as unavailable for use in app extensions must now themselves be annotated as unavailable for use in application extensions, in order to allow that code to be used in both apps and app extensions. (66928265)

You can add the:

@available(iOSApplicationExtension, unavailable)

attribute to declarations using app extension unavailable APIs in order to get them to compile in a way that works for both apps and app extensions.

1 Like

What if I want my package to only work in applications, not extensions? Does it mean that now I must include these annotations on every type, extension, etc?

You would only need to include those annotations on methods which themselves call an extension-unsafe API, not on every single type within the package.

But if the type which uses these unsafe API is used in many other places, these annotations will spread.

I’ve unfortunately run into this same issue with Xcode 13 Beta 3’s new behavior. While I was able to workaround it by adding the annotation in my own packages, it required the annotation in dozens of files even though the actual direct call to UIApplication.shared only occurred in one or two files. I tried using an #if available condition to avoid calling the code in unsupported situations (and remove the need for the annotation), but that didn’t work either.

Working around this seems cumbersome, and that’s without involving 3rd party code which might also suffer from this (AWS Amplify’s iOS library is one such example). Being able to opt out of the new behavior (perhaps via the swiftSettings parameter from earlier in the thread) would be preferable for situations where the user of the package knows that it isn’t used in an app extension.

6 Likes

This causes errors in many third party Libraries (Firebase App Distribution too). Will report an issue in those projects, but it is very hard to check going one by one with each library.

I have tried to use #available, but the Swift package still does not compile when used in both the app target and extension targets:

        if #available(iOSApplicationExtension 14, *) {
            
        } else {
            infos.dynamicTextSize = UIApplication.shared.preferredContentSizeCategory.rawValue
        }

Any ideas how to do this?

1 Like

I too am unpleased with the new changes. It makes sense, but the @available(iOSApplicationExtension, unavailable) is a pain to deal with. A #if available would be much more practical.

I'm maintaining an app which is heavily modularized with SPM (~50 swift packages). Most of the packages are meant to be used within the app itself – and only. Only a few packages are shared with an extension.
Since Xcode 13 Beta 3, I have to annotate most of my code with @available(iOSApplicationExtension, unavailable) – which feels wrong.

1 Like

What is the translation for Objective C libraries using [UIApplication sharedApplication]?

1 Like

The main outstanding issue is that libraries very commonly have shared API that should be available to both application targets as well as extension targets, but which at runtime should be able to make use of application-only APIs if operating inside an application container. These annotations are great but they do not allow for runtime scoped checks, which can cause the taint of unavailable API to quickly spread to unrelated APIs.

The only two options that I can see are:

  1. We need runtime checks for guarding usage of unavailable API and limiting the spread of the annotations.
  2. We force countless libraries into re-engineering their current ABI for the sake of explicitly splitting out unavailable components of shared APIs.
@available(iOSApplicationExtension, unavailable)
static func start( withConfig: LibraryConfig )
static func startButMakeItAnAppExtension( withConfig: LibraryConfig )
4 Likes