In immediate mode, detect the host OS version on Apple platforms.

This allows script mode to pick up the current version of macOS
instead of defaulting to 10.9, making it unnecessary to write #available.
A -target flag can still override this if you're trying to write a
portable script.

The logic is a little tortured to avoid having to actually link to
Foundation.framework or libobjc.

Finishes rdar://problem/29433205.
This commit is contained in:
Jordan Rose
2016-12-21 19:54:22 -08:00
parent fdd02f61d3
commit 23f25e1de7
6 changed files with 160 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
//===--- AppleHostVersionDetection.h - NSProcessInfo interface --*- c++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_FRONTEND_APPLEHOSTVERSIONDETECTION_H
#define SWIFT_FRONTEND_APPLEHOSTVERSIONDETECTION_H
#import "clang/Basic/VersionTuple.h"
namespace swift {
/// Returns a string in a form suitable for llvm::Triple's OS component
/// representing the current host OS.
///
/// Returns an empty version if the host OS version cannot be detected.
///
/// Note that this will load additional code into the process to detect the
/// OS version properly.
clang::VersionTuple inferAppleHostOSVersion();
} // end namespace swift
#endif

View File

@@ -0,0 +1,81 @@
//===--- AppleHostVersionDetection.mm - Interface to NSProcessInfo --------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "AppleHostVersionDetection.h"
#define OBJC_OLD_DISPATCH_PROTOTYPES 0
#import <Foundation/NSProcessInfo.h>
#include <objc/message.h>
#include <objc/runtime.h>
#include <dlfcn.h>
using namespace swift;
// Note that these conditions must come in this order. TARGET_OS_MAC is set on
// nearly all Apple platforms; it's in contrast to things like TARGET_OS_WIN32.
#if TARGET_OS_IPHONE
# define REQUIRED_CF_VERSION kCFCoreFoundationVersionNumber_iOS_8_0
#elif TARGET_OS_MAC
# define REQUIRED_CF_VERSION kCFCoreFoundationVersionNumber10_10
#else
# error "Unknown Apple platform"
#endif
#define DLSYM(LIBRARY, SYMBOL) \
reinterpret_cast<decltype(SYMBOL) *>(dlsym(LIBRARY, #SYMBOL))
clang::VersionTuple swift::inferAppleHostOSVersion() {
// Simulate [[NSProcessInfo processInfo] operatingSystemVersion].
// DYLD_PRINT_STATISTICS shows that the cost of linking Foundation when we
// don't need to is a non-trivial percentage of our pre-main startup time.
// Which, to be fair, is pretty small anyway, but even so.
// Use RTLD_GLOBAL here, even though we don't need it, because the JIT might
// end up importing Foundation later, and at that point it /does/ need to be
// global. (This is arguably a bug in macOS's implementation of dlopen.)
auto *foundation =
dlopen("/System/Library/Frameworks/Foundation.framework/Foundation",
RTLD_LAZY | RTLD_GLOBAL);
if (!foundation)
return {};
auto *cfVersionPtr = DLSYM(foundation, kCFCoreFoundationVersionNumber);
if (!cfVersionPtr || *cfVersionPtr < REQUIRED_CF_VERSION)
return {};
auto objcGetClass = DLSYM(foundation, objc_getClass);
if (!objcGetClass)
return {};
Class nsProcessInfo = objcGetClass("NSProcessInfo");
if (!nsProcessInfo)
return {};
auto objcMsgSendProcessInfo =
reinterpret_cast<NSProcessInfo *(*)(Class, SEL)>(
DLSYM(foundation, objc_msgSend));
NSProcessInfo *sharedProcessInfo =
objcMsgSendProcessInfo(nsProcessInfo, @selector(processInfo));
if (!sharedProcessInfo)
return {};
auto objcMsgSendVersion =
reinterpret_cast<NSOperatingSystemVersion(*)(NSProcessInfo *, SEL)>(
DLSYM(foundation, objc_msgSend_stret));
NSOperatingSystemVersion version =
objcMsgSendVersion(sharedProcessInfo, @selector(operatingSystemVersion));
return clang::VersionTuple(static_cast<unsigned>(version.majorVersion),
static_cast<unsigned>(version.minorVersion),
static_cast<unsigned>(version.patchVersion));
}

View File

@@ -1,3 +1,9 @@
if(APPLE)
set(AppleHostVersionDetection AppleHostVersionDetection.mm)
else()
set(AppleHostVersionDetection)
endif()
add_swift_library(swiftFrontend STATIC
CompilerInvocation.cpp
DiagnosticVerifier.cpp
@@ -5,6 +11,7 @@ add_swift_library(swiftFrontend STATIC
FrontendOptions.cpp
PrintingDiagnosticConsumer.cpp
SerializedDiagnosticConsumer.cpp
${AppleHostVersionDetection}
DEPENDS SwiftOptions
LINK_LIBRARIES
swiftSIL

View File

@@ -12,6 +12,10 @@
#include "swift/Frontend/Frontend.h"
#if __APPLE__
# include "AppleHostVersionDetection.h"
#endif
#include "swift/Strings.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/Basic/Platform.h"
@@ -904,6 +908,21 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
Target = llvm::Triple(A->getValue());
TargetArg = A->getValue();
}
#if __APPLE__
else if (FrontendOpts.actionIsImmediate()) {
clang::VersionTuple currentOSVersion = inferAppleHostOSVersion();
if (currentOSVersion.getMajor() != 0) {
llvm::Triple::OSType currentOS = Target.getOS();
if (currentOS == llvm::Triple::Darwin)
currentOS = llvm::Triple::MacOSX;
SmallString<16> newOSBuf;
llvm::raw_svector_ostream newOS(newOSBuf);
newOS << llvm::Triple::getOSTypeName(currentOS) << currentOSVersion;
Target.setOSName(newOS.str());
}
}
#endif
Opts.EnableObjCInterop = Target.isOSDarwin();
if (auto A = Args.getLastArg(OPT_enable_objc_interop,

View File

@@ -0,0 +1,6 @@
__attribute__((availability(macosx,introduced=10.9)))
static inline int mavericks() { return 9; }
__attribute__((availability(macosx,introduced=10.10)))
static inline int yosemite() { return 10; }
__attribute__((availability(macosx,introduced=10.99)))
static inline int todosSantos() { return 99; }

View File

@@ -0,0 +1,16 @@
// Note: This deliberately uses the script interpreter rather than build/run.
// RUN: %swift_driver -import-objc-header %S/Inputs/availability_host_os.h -DFAIL -Xfrontend -verify %s
// RUN: %swift_driver -import-objc-header %S/Inputs/availability_host_os.h %s | %FileCheck %s
// RUN: not %swift -typecheck -import-objc-header %S/Inputs/availability_host_os.h %s 2>&1 | %FileCheck -check-prefix=CHECK-NOT-INFERRED %s
// REQUIRES: OS=macosx
// REQUIRES: executable_test
print(mavericks()) // CHECK: {{^9$}}
print(yosemite()) // CHECK-NEXT: {{^10$}}
// CHECK-NOT-INFERRED: 'yosemite()' is only available on OS X 10.10 or newer
#if FAIL
print(todosSantos()) // expected-error {{'todosSantos()' is only available on OS X 10.99 or newer}}
// expected-note@-1 {{add 'if #available' version check}}
#endif