mac: Use @available when using APIs that exist only in newer macOS versions

657 views
Skip to first unread message

Nico Weber

unread,
Jul 17, 2017, 11:03:45 AM7/17/17
to Chromium-dev, Peter Collingbourne, Erik Chen
Hi,

If you don’t write macOS-specific code, you can stop reading now.

Summary: Go read https://clang.llvm.org/docs/LanguageExtensions.html#objective-c-available to learn about now-mandatory @available; background and some notes on advanced usage below.

We have a minimum macOS version that we require for chrome (currently macOS 10.9). This is called the deployment target.

macOS SDKs are versioned: The 10.9 SDK contains declarations for all functions and classes available on 10.9+, the 10.10 SDK for functions and classes available on 10.10+, and so on.

We used to use the SDK version corresponding to our deployment target. However, Apple recommends always using the newest SDK, independent of your deployment target. Also, some frameworks behave in buggy-but-backwards-compatible ways if you use an older SDK (*).

So now we use a new-ish SDK (currently 10.10, but hopefully 10.12 soon).

This creates a new problem: Accidental unconditional calls to new APIs. We might call a 10.10+-only API. On 10.9, this would result in a runtime crash.
We didn’t want to risk this, so before we switched to using a newer SDK version than deployment target, we added a wrning called -Wpartial-availability to clang:
The CL description describes how it works. In a nut, you manually need to redeclare newer APIs to suppress warning, which requires manual opt-in in a way. This was kind of klunky, but was good enough for us, and we've used it for the last 2+ years. This is changing now.

Recently, Apple invented new, better, officially-supported way to address the same problem: -Wunguarded-availability. They also made -Wpartial-availability an alias for the new -Wunguarded-availability, and made it so that redeclaring things no longer suppresses the warning. So we had to switch to the new thing in a bit of a rush when we recently updated our compiler. We added some documentation for how the new thing, @available, at https://clang.llvm.org/docs/LanguageExtensions.html#objective-c-available. Go read that, I won’t repeat it here.

Shout-out to pcc for converting the codebase to @available when updating our clang to a version that added this requirement, and to erikchen who did the follow-up changes needed to keep building with -Wunguarded-availability in his upcoming switch to the 10.12 SDK. (This isn’t 100% done yet, but the final bits should hopefully make it into the tree today and unbreak building with th 10.12 SDK again.)

If you read the document I linked to above, you know most of what there is to know about @available. Here are some notes on complications we’ve seen:

* In rare cases, the availability annotation on an API might be overly conservative.  For example, [NSProcessInfo processInfo] secretly responds to -operatingSystemVersion as of macOS 10.9.2, but officially only starting with macOS 10.10.  Hence, its availability attribute claims that it exists as of 10.10.  If there are just few callers, do:

  void my_fun(NSSomeClass* var) {
    if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wunguarded-availability"
      NSOperatingSystemVersion version = [processInfo operatingSystemVersion];
  #pragma clang diagnostic pop
    }
  }

* If a frequently-used type is annotated incorrectly, repeating this all over the place gets annoying. In those cases (we know of just one), you can use a macro wrapping the type in a bunch of _Pragma()s like in https://cs.chromium.org/chromium/src/ui/accelerated_widget_mac/availability_macros.h?q=availability_macros.h&sq=package:chromium&dr&l=5

https://chromium-review.googlesource.com/c/573113 is an @available example CL, many more examples are on https://crbug.com/735328.

* API_AVAILABLE currently has the somewhat annoying side effect of also marking C++ (not Objective-C) functions and classes that aren’t in an unnamed namespace as visible. This means those classes have to explicitly be marked hidden, and then if they’re exported, as exported, in that order. This is luckily very rare in practice (2 examples in all of chrome), and also hopefully something that we can fix in the compiler: https://bugs.llvm.org/show_bug.cgi?id=33796

And that’s it!

What about iOS?  -Wunguarded-availability is generally opt-in, and chrome/ios doesn’t opt-in to this warning. On iOS, SDK version and deployment target are usually fairly close to each other, so the warning has less value there. However, for APIs in iOS 11 and newer, this warning is enabled. So chrome/iOS will have to use @available soon, too. Chrome/iOS uses Xcode’s clang instead of Chromium’s clang, and Xcode only supports @available in Xcode 9, which is currently still in beta.

Nico


*: Examples of system libraries behaving differently when linked against an older SDK:

1.) https://code.google.com/p/chromium/issues/detail?id=452707#c2 is one example (rounded corners work differently). Comment 4 on the same bug mentions that _NSViewLinkedOnZinDefaultValueFunction behaves differently depending on SDK, and comment 5 then lists a few places that call that function and are affected by it. This can even cause crashes, see https://code.google.com/p/chromium/issues/detail?id=428977#c12 and #c15 (you folks have the same bug).

2.) NSThemeFrame is layer backed (if the contentView is layer backed) when linking against 10.9. This doesn’t happen when linking against 10.8 or earlier.

3.) Fullscreen is buggy when linked against older sdks (https://code.google.com/p/chromium/issues/detail?id=396980#c33)


There are probably more cases.

Nico Weber

unread,
Aug 15, 2017, 12:18:25 PM8/15/17
to Chromium-dev, Peter Collingbourne, Erik Chen
(Short follow-up: This visibility problem has been fixed upstream and our compiler now has that fix.)
Reply all
Reply to author
Forward
0 new messages