Here's another one, hopefully with a little more grounding than the debug test.
I welcome feedback both positive and critical, -- E
Adding an Build Configuration Import Test
Author(s): Erica Sadun <http://github.com/erica>
Review manager: TBD
Expanding the build configuration suite to test for the ability to import certain modules was first introduced <http://article.gmane.org/gmane.comp.lang.swift.evolution/7516/match=darwin> on the Swift-Evolution list by Kevin Ballard. Although his initial idea (checking for Darwin to differentiate Apple targets from non-Apple targets) proved problematic, developers warmly greeted the notion of an import-based configuration test. Dmitri Gribenko wrote, "There's a direction that we want to move to a unified name for the libc module for all platform, so 'can import Darwin' might not be a viable long-term strategy." Testing for imports offers advantages that stand apart from this one use-case: to test for API availability before use.
Swift's existing set of build configurations specify platform differences, not module commonalities. For example, UIKit enables you to write view code supported on both iOS and tvOS. SpriteKit allows common code to render on OS X, iOS, and tvOS that would require an alternate UI on Linux. Testing for Metal support or Media Player would guard code that will not function on the simulator. If the simulator adopted these modules at some future time, the code would naturally expand to provide compatible execution without source modification.
// UIKit-based code
// OSX code
// Workaround/text, whatever
Guarding code with operating system tests can be less future-proofed than testing for module support. Excluding OS X to use UIColor creates code that might eventually find its way to a Linux plaform. Targeting Apple platforms by inverting a test for Linux essentially broke after the introduction of Windows and FreeBSD build configurations:
// Exclusive os tests are brittle
// Matches OSX, iOS, watchOS, tvOS, Windows, FreeBSD
Inclusive OS tests (if os1 || os2 || os3...) must be audited each time the set of possible platforms expands. In addition, compound build statements are harder to write, to validate, and are more confusing to read. They are more prone to errors than a single test that's tied to the API capabilities used by the code it guards.
Evan Maloney writes, "Being able to test for the importability of a given module/framework at runtime would be extremely helpful. We use several frameworks that are only available in a subset of the platforms we support, and on only certain OS versions. To work around this problem now, we dynamically load frameworks from Obj-C only when we're running on an OS version we know is supported by the framework(s) in question. We can't dynamically load them from Swift because if they're included in an import, the runtime tries to load it right away, leading to a crash on any unsupported platform. The only way to selectively load dynamic frameworks at runtime is to do it via Obj-C. Some sort of check like the ones you propose should let us avoid this."
#if canImport(module-name) tests for module support by name. My proposed name uses lower camelCase, which is not currently used in the current build configuration vocabulary but is (in my opinion) clearer in intention than the other two terms brought up on the evolution list, #if imports() and #if supports().
This build configuration does not import the module it names
This build configuration is intended to differentiate API access
This build configuration should not be used to differentiate platforms
The supplied module token is an arbitrary string. It does not belong to an enumerated set of known members as this configuration test is intended for use with both first and third party modules for the greatest flexibility. At compile time, Swift determines whether the module can or cannot be linked and builds accordingly.
// use module APIs safely
// provide solution with module APIs
// provide alternative solution that does not depend on that module
Swift currently supports the following configuration tests:
The literals true and false
The os() function that tests for OSX, iOS, watchOS, tvOS, Linux, Windows, and FreeBSD
The arch() function that tests for x86_64, arm, arm64, i386, powerpc64, and powerpc64le
The swift() function that tests for specific Swift language releases, e.g. swift(>=2.2)
There are no alternatives considered.