Hello all,

I wanted to talk about some new features in Apple Clang that will affect us 
once Xcode 9 is released in 1 to 2 weeks.

Basically, there are new language extensions for C/C++ and Objective-C 
(__builtin_available() and @available()) that allow you to perform OS version 
runtime checks when calling APIs that may not be available on your deployment 
target. The key point is, the compiler warns you if the checks aren't present 
where they need to be.

The LLVM documentation can explain these best: 
http://clang.llvm.org/docs/LanguageExtensions.html#objective-c-available

So basically, we should no longer use QSysInfo or QOperatingSystemVersion to 
perform these checks, but instead use the compiler-provided functions. There 
are two new warnings in conjunction with this, -Wunguarded-availability (not 
enabled by default) and -Wunguarded-availability-new (enabled by default for 
APIs introduced in 2017 releases of Apple's OSes).

The problem is that these new builtins are only available in Xcode 9 and above, 
and we must still be able to build Qt with the version of Xcode we use on the 
macOS 10.10 CI (Xcode 7?). We have to use them when they're available, because 
the compiler can't recognize QSysInfo/QOperatingSystemVersion as a valid 
version check and therefore will still emit warnings. We also don't want to 
disable the warnings entirely for obvious reasons.

So my question is, for those of you who know the C++ preprocessor well enough, 
is there a way to transform this:

__builtin_available(macOS 10.13, iOS 11, *)

into some other form, using a C++ preprocessor macro and/or template magic? 
Note that the order and number of arguments is variable, the number of 
components in each dot-separated version number is variable, and the last 
argument must always be '*'. Maybe one of you can come up with some Alexei 
Alexandrescu-level crazy solution with macros and templates, but I'm thinking 
this is likely infeasible.

If it's not possible, we'll have to go with a solution like this:

#if __has_builtin(__builtin_available)
#define QT_AVAILABLE_MACOS(macos_major, macos_minor, macos_patch) \
    __builtin_available(macOS macos_major ## . ## macos_minor ## . ## 
macos_patch, *)
#else
#define QT_AVAILABLE_MACOS(macos_major, macos_minor, macos_patch) \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::MacOS, macos_major, 
macos_minor, macos_patch)
#endif

Because the semantics of using multiple @available / __builtin_available calls 
in a single if statement is not defined (you'll get a warning like this):

warning: @available does not guard availability here; use if (@available) 
instead [-Wunsupported-availability-guard]
    if (@available(macOS 10.12, *) || @available(iOS 10.11, *)) {

we must have a version of that QT_AVAILABLE_ macro for every combination of 
OSes we want to check at once. So, at least 6 - each of the four OSes by 
themselves, macOS and iOS together, and all four Apple platforms together. 
There's technically 8 platforms if you include app extensions but I've ignored 
that for now. For example:

#if __has_builtin(__builtin_available)
#define QT_AVAILABLE_MACOS(macos_major, macos_minor, macos_patch) \
    __builtin_available(macOS macos_major ## . ## macos_minor ## . ## 
macos_patch, *)
#define QT_AVAILABLE_IOS(ios_major, ios_minor, ios_patch) \
    __builtin_available(iOS ios_major ## . ## ios_minor ## . ## ios_patch, *)
#define QT_AVAILABLE_TVOS(tvos_major, tvos_minor, tvos_patch) \
    __builtin_available(tvOS tvos_major ## . ## tvos_minor ## . ## tvos_patch, 
*)
#define QT_AVAILABLE_WATCHOS(watchos_major, watchos_minor, watchos_patch) \
    __builtin_available(watchOS watchos_major ## . ## watchos_minor ## . ## 
watchos_patch, *)
#define QT_AVAILABLE_MACOS_IOS(macos_major, macos_minor, macos_patch, 
ios_major, ios_minor, ios_patch) \
    __builtin_available(macOS macos_major ## . ## macos_minor ## . ## 
macos_patch, iOS ios_major ## . ## ios_minor ## . ## ios_patch, *)
#define QT_AVAILABLE_DARWIN(macos_major, macos_minor, macos_patch, ios_major, 
ios_minor, ios_patch, tvos_major, tvos_minor, tvos_patch, watchos_major, 
watchos_minor, watchos_patch) \
    __builtin_available(macOS macos_major ## . ## macos_minor ## . ## 
macos_patch, iOS ios_major ## . ## ios_minor ## . ## ios_patch, tvOS tvos_major 
## . ## tvos_minor ## . ## tvos_patch, watchOS watchos_major ## . ## 
watchos_minor ## . ## watchos_patch, *)
#else
#define QT_AVAILABLE_MACOS(macos_major, macos_minor, macos_patch) \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::MacOS, macos_major, 
macos_minor, macos_patch)
#define QT_AVAILABLE_IOS(ios_major, ios_minor, ios_patch) \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::IOS, ios_major, ios_minor, 
ios_patch)
#define QT_AVAILABLE_TVOS(tvos_major, tvos_minor, tvos_patch) \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::TvOS, tvos_major, tvos_minor, 
tvos_patch)
#define QT_AVAILABLE_WATCHOS(watchos_major, watchos_minor, watchos_patch) \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::WatchOS, watchos_major, 
watchos_minor, watchos_patch)
#define QT_AVAILABLE_MACOS_IOS(macos_major, macos_minor, macos_patch, 
ios_major, ios_minor, ios_patch) \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::MacOS, macos_major, 
macos_minor, macos_patch) || \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::IOS, ios_major, ios_minor, 
ios_patch)
#define QT_AVAILABLE_DARWIN(macos_major, macos_minor, macos_patch, ios_major, 
ios_minor, ios_patch, tvos_major, tvos_minor, tvos_patch, watchos_major, 
watchos_minor, watchos_patch) \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::MacOS, macos_major, 
macos_minor, macos_patch) || \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::IOS, ios_major, ios_minor, 
ios_patch) || \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::TvOS, tvos_major, tvos_minor, 
tvos_patch) || \
    QOperatingSystemVersion::current() >= 
QOperatingSystemVersion(QOperatingSystemVersion::WatchOS, watchos_major, 
watchos_minor, watchos_patch)
#endif

and a typical usage of the last one would look like:

    if (QT_AVAILABLE_DARWIN(/*macOS*/ 10,13,0, /*iOS*/ 11,0,0, /*tvOS*/ 11,0,0, 
/*watchOS*/ 4,0,0)) {
        // use new APIs...
    }

My other attempt at a macro which instead "backports" the new language feature 
looks like this:

#define QT_AVAILABLE_LPAREN (
#define QT_AVAILABLE_RPAREN )
#define QT_AVAILABLE_COMMA ,
#define QT_AVAILABLE_CAT(L, R) QT_AVAILABLE_CAT_(L, R)
#define QT_AVAILABLE_CAT_(L, R) L ## R
#define QT_AVAILABLE_EXPAND(...) __VA_ARGS__
#define QT_AVAILABLE_SPLIT(OP, D) QT_AVAILABLE_EXPAND(OP 
QT_AVAILABLE_CAT(QT_AVAILABLE_SPLIT_, D) QT_AVAILABLE_RPAREN)
#define QT_AVAILABLE_SPLIT_macOS QT_AVAILABLE_LPAREN 
QOperatingSystemVersion(QOperatingSystemVersion::MacOS QT_AVAILABLE_COMMA
#define QT_AVAILABLE_SPLIT_iOS QT_AVAILABLE_LPAREN 
QOperatingSystemVersion(QOperatingSystemVersion::IOS QT_AVAILABLE_COMMA
#define QT_AVAILABLE_SPLIT_tvOS QT_AVAILABLE_LPAREN 
QOperatingSystemVersion(QOperatingSystemVersion::TvOS QT_AVAILABLE_COMMA
#define QT_AVAILABLE_SPLIT_watchOS QT_AVAILABLE_LPAREN 
QOperatingSystemVersion(QOperatingSystemVersion::WatchOS QT_AVAILABLE_COMMA

static bool qtAvailable(const std::vector<QOperatingSystemVersion> &versions)
{
    std::vector<QOperatingSystemVersion::Type> types;
    const auto current = QOperatingSystemVersion::current();
    for (const auto &v : versions) {
        types.push_back(v.type());
        if (current >= v)
            return true;
    }
    return !types.contains(current.type());
}

#if __has_builtin(__builtin_available) && 0
#define __builtin_available2 __builtin_available
#define __builtin_available3 __builtin_available
#define __builtin_available4 __builtin_available
#else
#define __builtin_available(a, e) \
    qtAvailable({QT_AVAILABLE_SPLIT(, a)})
#define __builtin_available2(a, b, e) \
    qtAvailable({QT_AVAILABLE_SPLIT(, a), QT_AVAILABLE_SPLIT(, b)})
#define __builtin_available3(a, b, c, e) \
    qtAvailable({QT_AVAILABLE_SPLIT(, a), QT_AVAILABLE_SPLIT(, b), 
QT_AVAILABLE_SPLIT(, c)})
#define __builtin_available4(a, b, c, d, e) \
    qtAvailable({QT_AVAILABLE_SPLIT(, a), QT_AVAILABLE_SPLIT(, b), 
QT_AVAILABLE_SPLIT(, c), QT_AVAILABLE_SPLIT(, d)})
#endif

But I don't know how to transform i.e. "10.10" into 10,10" in the expanded 
macro, nor do I know any way to support variable arguments in a way that lets 
us drop the last argument.

So... can anyone do better than my versions? Patches very welcome. :)
-- 
Jake Petroules - jake.petrou...@qt.io
The Qt Company - Silicon Valley
Qbs build tool evangelist - qbs.io

_______________________________________________
Development mailing list
Development@qt-project.org
http://lists.qt-project.org/mailman/listinfo/development

Reply via email to