swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.3k stars 10.34k forks source link

Availability annotations for third party libraries when using Library evolution/ resilience #60458

Open hassila opened 2 years ago

hassila commented 2 years ago

Is your feature request related to a problem? Please describe.

We’ve ended up realizing we’d like to use the library evolution features for resilient api surfaces for our product (yes, needed for an enterprise solution where clients of libraries must be able to be replaced independently and don’t have source access. Both macOS and Linux. Aware of tool chain impedance matching on Linux required - that’s ok).

The question is how we can evolve our api with availability annotations?

Current annotations for availability is tied to platform or swift version only, but we'd want to tie it to our products version numbers instead.

Not sure the best way to express it, but if a typical swift macro would be:

@available(swift 5.1)

we'd want something like:

@available(ourProductName 2.0)

which we could use with e.g.

    if #available (ourProductName 2.0) {
        return x
    } else {
        return y
    }

or e.g. @available(ourProductName , deprecated: 3, renamed: "newNameForThis")

Describe the solution you'd like Add support for third-party availability annotations to allow tying availability to our product releases instead of platform/swift versions.

Describe alternatives you've considered As a stop-gap we considered to tie it to Swift releases instead, which is not really a good workaround, but AFAWU it's fundamentally the only option we have (for it to work on both Linux and macOS) - and not very good conceptually.

Additional context Some background discussion here: https://forums.swift.org/t/availability-when-using-library-evolution-resilience-for-third-party-libraries/59341

tbkka commented 2 years ago

CC: @tshortli

hassila commented 2 years ago

Jordan Rose helpfully pointed out:

https://github.com/apple/swift/blob/main/docs/LibraryEvolutionManifesto.md#generalized-availability-model

nkcsgexi commented 2 years ago

yup. I think we should be able to allow custom definitions of environment strings, and piggybacks on the existing availability checker to diagnose violations.

nkcsgexi commented 2 years ago

@hassila, currently, Swift's compile-time and runtime availability checking don't support third-party libraries well. While we are still investigating the proper solutions, you could start using -user-module-version flag to compile your libraries. These versions are compiled into the Swift module and they are currently used in the versioned #canImport() check. In the future, we could potentially support library version checks at run time, for instance:

if #available(ourProductName, version: 2) {
  // perform new behavior because ourProductName has a version greater or equal to 2
} else {
  // perform old behavior because ourProductName has a version less than 2
}
hassila commented 2 years ago

Thanks! I'll play with it - but unfortunately that won't cover two of the more important use cases for availability (depreciations and renaming) - we want to be able to evolve API for our customers in a graceful way and those are very nice.

hassila commented 2 years ago

Note for others who wants to try it - -user-module-version seems to work for checking versions like this, adding it to the library gives a .swiftinterface containing:

hassila@max ~/G/prototype-library-evolution-client (main)> more .build/arm64-apple-macosx/debug/PrototypeLibraryEvolution.swiftinterface
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.123.8 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx10.13 -enable-objc-interop -enable-library-evolution -swift-version 5 -Onone -module-name PrototypeLibraryEvolution
// swift-module-flags-ignorable: -user-module-version 1.0
...

and the check is done thusly (with currently underscored version - there's also an _underlyingVersion for module overlays, but must admit I have no idea when/why that would be used, as far as I could see it is for getting at the underlying version when exposing e.g. ObjC API?):

#if canImport(PrototypeLibraryEvolution, _version: 1.0)
import PrototypeLibraryEvolution
#endif
nkcsgexi commented 2 years ago

Thanks for the instruction, @hassila ! the version number for ObjC library is pulled from .tbd file, which contains a field indicating user-specified version keyed by current-version.

nkcsgexi commented 2 years ago

I was mulling over potentially using user module version for @available attribute, e.g. something like @available(moduleVersion 2, *). The challenge of supporting this is that, unlike OS versions, the user module version is local and cannot penetrate through module boundaries. This may be doable if the library owners can provide information about how the user module versions of different modules are aligned with each other.

hassila commented 2 years ago

I was mulling over potentially using user module version for @available attribute, e.g. something like @available(moduleVersion 2, *).

That'd be great if possible I think.

The challenge of supporting this is that, unlike OS versions, the user module version is local and cannot penetrate through module boundaries. This may be doable if the library owners can provide information about how the user module versions of different modules are aligned with each other.

Sorry, I don't quite understand the problem here, is it about using modules that are implicitly re-exported by a dependency (how Swift currently 'leaks' imports), or something else that causes problems?

How would information about how 'user module versions of different modules are aligned with each other' be used?

hassila commented 10 months ago

Ok, I couldn't help noticing the definition: https://github.com/apple/swift-foundation/blob/cd3d29426fee11d02d58363ad16683eb44a38985/Package.swift#L13

And usage: https://github.com/search?q=repo%3Aapple%2Fswift-foundation+available%28FoundationPreview&type=code

Looking forward to the pitch and we will happily test this out when you feel it's ready.

tshortli commented 10 months ago

The "availability macros" feature used in swift-foundation doesn't really represent progress towards this feature request as far as I understand it. Availability macros have existed for quite a while and were originally designed to aid the authors of the standard library in managing @available annotations (see availability-macros.def). These macros are just conveniences that get expanded to traditional, platform based availability. For example, a SwiftStdlib 5.9 annotation in the standard library expands to macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0.

The feature requested here requires introducing a notion of availability that is fully separate from platform based availability.

hassila commented 10 months ago

Ah, thanks for the link, I didn't realize the mapping to platform versions when looking at the usage site (which was my focus) - you are absolutely right it doesn't address this issue then, sorry for the noise.

swiftyfinch commented 1 month ago

Hello! Are there any updates?

It would be nice to have a similar option as in the Doc generator: https://forums.swift.org/t/setting-a-documentation-article-s-platform-availability/61144/8


Right now, we can describe the version in which certain features became available using:

@Metadata {
    @Available("Swift DocC", introduced: "1.0")
}

And it would be great to describe symbol availability in a similar way:

@available("Swift DocC", introduced: "1.0")
public something: Some

UPDATE: It looks related: