1import re
2import string
3import os.path
4from waflib import Utils
5from distutils.version import StrictVersion
6
7
8def __run(cmd):
9    try:
10        output = Utils.subprocess.check_output(cmd, stderr=Utils.subprocess.STDOUT, universal_newlines=True)
11        return output.strip()
12    except Exception:
13        return ""
14
15
16def __add_swift_flags(ctx):
17    ctx.env.SWIFT_FLAGS = [
18        "-frontend", "-c", "-sdk", ctx.env.MACOS_SDK,
19        "-enable-objc-interop", "-emit-objc-header", "-parse-as-library",
20    ]
21
22    verRe = re.compile("(?i)version\s?([\d.]+)")
23    ctx.env.SWIFT_VERSION = verRe.search(__run([ctx.env.SWIFT, '-version'])).group(1)
24
25    # prevent possible breakages with future swift versions
26    if StrictVersion(ctx.env.SWIFT_VERSION) >= StrictVersion("6.0"):
27        ctx.env.SWIFT_FLAGS.extend(["-swift-version", "5"])
28
29    if ctx.is_debug_build():
30        ctx.env.SWIFT_FLAGS.append("-g")
31
32    if ctx.is_optimization():
33        ctx.env.SWIFT_FLAGS.append("-O")
34
35
36def __add_static_swift_library_linking_flags(ctx, swift_library):
37    ctx.env.append_value('LINKFLAGS', [
38        '-L%s' % swift_library,
39        '-Xlinker', '-force_load_swift_libs', '-lc++',
40    ])
41
42
43def __add_dynamic_swift_library_linking_flags(ctx, swift_library):
44    ctx.env.append_value('LINKFLAGS', ['-L%s' % swift_library])
45
46    # ABI compatibility
47    if StrictVersion(ctx.env.SWIFT_VERSION) >= StrictVersion("5.0"):
48        ctx.env.append_value('LINKFLAGS', [
49            '-Xlinker', '-rpath', '-Xlinker', '/usr/lib/swift',
50            '-L/usr/lib/swift',
51        ])
52
53    ctx.env.append_value('LINKFLAGS', [
54        '-Xlinker', '-rpath', '-Xlinker', swift_library,
55    ])
56
57
58def __find_swift_library(ctx):
59    swift_libraries = {}
60    # look for set lib paths in passed environment variables
61    if 'SWIFT_LIB_DYNAMIC' in ctx.environ:
62        swift_libraries['SWIFT_LIB_DYNAMIC'] = ctx.environ['SWIFT_LIB_DYNAMIC']
63    if 'SWIFT_LIB_STATIC' in ctx.environ:
64        swift_libraries['SWIFT_LIB_STATIC'] = ctx.environ['SWIFT_LIB_STATIC']
65
66    # search for swift libs relative to the swift compiler executable
67    swift_library_relative_paths = {
68        'SWIFT_LIB_DYNAMIC': '../../lib/swift/macosx',
69        'SWIFT_LIB_STATIC': '../../lib/swift_static/macosx'
70    }
71
72    for lib_type, path in swift_library_relative_paths.items():
73        if lib_type not in swift_libraries:
74            lib_path = os.path.join(ctx.env.SWIFT, path)
75            swift_library = ctx.root.find_dir(lib_path)
76            if swift_library is not None:
77                swift_libraries[lib_type] = swift_library.abspath()
78
79    # fall back to xcode-select path
80    swift_library_paths = {
81        'SWIFT_LIB_DYNAMIC': [
82            'Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx',
83            'usr/lib/swift/macosx'
84        ],
85        'SWIFT_LIB_STATIC': [
86            'Toolchains/XcodeDefault.xctoolchain/usr/lib/swift_static/macosx',
87            'usr/lib/swift_static/macosx'
88        ]
89    }
90    dev_path = __run(['xcode-select', '-p'])[1:]
91
92    for lib_type, paths in swift_library_paths.items():
93        for path in paths:
94            if lib_type not in swift_libraries:
95                swift_library = ctx.root.find_dir([dev_path, path])
96                if swift_library is not None:
97                    swift_libraries[lib_type] = swift_library.abspath()
98                    break
99            else:
100                break
101
102    # check if library paths were found
103    ctx.start_msg('Checking for dynamic Swift Library')
104    if 'SWIFT_LIB_DYNAMIC' in swift_libraries:
105        ctx.end_msg(swift_libraries['SWIFT_LIB_DYNAMIC'])
106    else:
107        ctx.end_msg(False)
108
109    ctx.start_msg('Checking for static Swift Library')
110    if 'SWIFT_LIB_STATIC' in swift_libraries:
111        ctx.end_msg(swift_libraries['SWIFT_LIB_STATIC'])
112        ctx.env['SWIFT_LIB_STATIC'] = swift_libraries['SWIFT_LIB_STATIC']
113    else:
114        ctx.end_msg(False)
115
116    enableStatic = getattr(ctx.options, 'enable_swift-static')
117    if (enableStatic) and 'SWIFT_LIB_STATIC' in swift_libraries:
118        __add_static_swift_library_linking_flags(ctx, swift_libraries['SWIFT_LIB_STATIC'])
119    else:
120        __add_dynamic_swift_library_linking_flags(ctx, swift_libraries['SWIFT_LIB_DYNAMIC'])
121
122
123def __find_macos_sdk(ctx):
124    ctx.start_msg('Checking for macOS SDK')
125    sdk = None
126    sdk_build_version = None
127    sdk_version = None
128
129    # look for set macOS SDK paths and version in passed environment variables
130    if 'MACOS_SDK' in ctx.environ:
131        sdk = ctx.environ['MACOS_SDK']
132    if 'MACOS_SDK_VERSION' in ctx.environ:
133        ctx.env.MACOS_SDK_VERSION = ctx.environ['MACOS_SDK_VERSION']
134
135    # find macOS SDK paths and version
136    if not sdk:
137        sdk = __run(['xcrun', '--sdk', 'macosx', '--show-sdk-path'])
138    if not ctx.env.MACOS_SDK_VERSION:
139        # show-sdk-build-version: is not available on older command line tools, but return a build version (eg 17A360)
140        # show-sdk-version: is always available, but on older dev tools it's only the major version
141        sdk_build_version = __run(['xcrun', '--sdk', 'macosx', '--show-sdk-build-version'])
142        sdk_version = __run(['xcrun', '--sdk', 'macosx', '--show-sdk-version'])
143
144    if sdk:
145        ctx.env.MACOS_SDK = sdk
146        build_version = '10.10.0'
147
148        if not ctx.env.MACOS_SDK_VERSION:
149            # convert build version to a version string
150            # first 2 two digits are the major version, starting with 15 which is 10.11 (offset of 4)
151            # 1 char is the minor version, A => 0, B => 1 and ongoing
152            # last digits are bugfix version, which are not relevant for us
153            # eg 16E185 => 10.12.4, 17A360 => 10.13, 18B71 => 10.14.1
154            if sdk_build_version and isinstance(sdk_build_version, str):
155                verRe = re.compile("(\d+)(\D+)(\d+)")
156                version_parts = verRe.search(sdk_build_version)
157                major = int(version_parts.group(1)) - 4
158                minor = string.ascii_lowercase.index(version_parts.group(2).lower())
159                build_version = '10.' + str(major) + '.' + str(minor)
160                # from 20 onwards macOS 11.0 starts
161                if int(version_parts.group(1)) >= 20:
162                    build_version = '11.' + str(minor)
163
164            if not isinstance(sdk_version, str):
165                sdk_version = '10.10.0'
166
167            # pick the higher version, always pick sdk over build if newer
168            if StrictVersion(build_version) > StrictVersion(sdk_version):
169                ctx.env.MACOS_SDK_VERSION = build_version
170            else:
171                ctx.env.MACOS_SDK_VERSION = sdk_version
172
173        ctx.end_msg(sdk + ' (version found: ' + ctx.env.MACOS_SDK_VERSION + ')')
174    else:
175        ctx.end_msg(False)
176
177
178def __find_swift_compiler(ctx):
179    ctx.start_msg('Checking for swift (Swift compiler)')
180    swift = ''
181
182    # look for set swift paths in passed environment variables
183    if 'SWIFT' in ctx.environ:
184        swift = ctx.environ['SWIFT']
185
186    # find swift executable
187    if not swift:
188        swift = __run(['xcrun', '-find', 'swift'])
189
190    if swift:
191        ctx.end_msg(swift)
192        ctx.env.SWIFT = swift
193        __add_swift_flags(ctx)
194        __find_swift_library(ctx)
195    else:
196        ctx.end_msg(False)
197
198
199def configure(ctx):
200    if ctx.env.DEST_OS == "darwin":
201        __find_macos_sdk(ctx)
202        if ctx.options.enable_swift is not False:
203            __find_swift_compiler(ctx)
204