Skip to content

Commit b795c7a

Browse files
committed
[WIP][Build Script Helper] On macOS make install action use CMake and install libSwiftDriver
Previously only the x86 `swift-driver` and `swift-help` executables were copied over to the specified toolchain's `bin` directory. With this change, libSwiftDriver shared library is also packaged and installed into the toolchain, as well as libSwiftOptions. To do so, we must build SwiftDriver and all of its dependencies using CMake for arm64 and x86_64 and create universal binaries, as documented here: https://developer.apple.com/documentation/xcode/building_a_universal_macos_binary
1 parent 2719bf4 commit b795c7a

File tree

1 file changed

+275
-21
lines changed

1 file changed

+275
-21
lines changed

Utilities/build-script-helper.py

Lines changed: 275 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,35 @@
33
from __future__ import print_function
44

55
import argparse
6+
from distutils import file_util
67
import os
8+
import json
79
import platform
810
import shutil
911
import subprocess
1012
import sys
13+
import errno
14+
15+
if platform.system() == 'Darwin':
16+
shared_lib_ext = '.dylib'
17+
else:
18+
shared_lib_ext = '.so'
19+
macos_deployment_target = '10.15'
20+
macos_target_architectures = ['x86_64','arm64']
1121

1222
# Tools constructed as a part of the a development build toolchain
1323
driver_toolchain_tools = ['swift', 'swift-frontend', 'clang', 'swift-help',
1424
'swift-autolink-extract', 'lldb']
1525

26+
def mkdir_p(path):
27+
"""Create the given directory, if it does not exist."""
28+
try:
29+
os.makedirs(path)
30+
except OSError as e:
31+
# Ignore EEXIST, which may occur during a race condition.
32+
if e.errno != errno.EEXIST:
33+
raise
34+
1635
def call_output(cmd, cwd=None, stderr=False, verbose=False):
1736
"""Calls a subprocess for its return data."""
1837
if verbose:
@@ -76,26 +95,26 @@ def get_swiftpm_options(args):
7695

7796
return swiftpm_args
7897

79-
def install(swiftpm_bin_path, toolchain):
80-
toolchain_bin = os.path.join(toolchain, 'bin')
81-
for exe in ['swift-driver', 'swift-help']:
82-
install_binary(exe, swiftpm_bin_path, toolchain_bin, toolchain, is_executable=True)
83-
84-
def install_binary(file, source_dir, install_dir, toolchain, is_executable):
98+
def install_binary(file, source_dir, install_dir, verbose):
99+
print('Installing %s into: %s' % (file, install_dir))
85100
cmd = ['rsync', '-a', os.path.join(source_dir.decode('UTF-8'), file), install_dir]
86-
print(' '.join(cmd))
87-
subprocess.check_call(cmd)
88-
89-
if platform.system() == 'Darwin' and is_executable:
90-
result_path = os.path.join(install_dir, file)
91-
stdlib_rpath = os.path.join(toolchain, 'lib', 'swift', 'macosx')
92-
delete_rpath(stdlib_rpath, result_path)
93-
94-
def delete_rpath(rpath, binary):
95-
cmd = ["install_name_tool", "-delete_rpath", rpath, binary]
96-
print(' '.join(cmd))
101+
if verbose:
102+
print(' '.join(cmd))
97103
subprocess.check_call(cmd)
98104

105+
def delete_rpath(rpath, binary, verbose):
106+
cmd = ['install_name_tool', '-delete_rpath', rpath, binary]
107+
if verbose:
108+
print(' '.join(cmd))
109+
installToolProcess = subprocess.Popen(cmd,
110+
stdout=subprocess.PIPE,
111+
stderr=subprocess.PIPE)
112+
stdout, stderr = installToolProcess.communicate()
113+
if installToolProcess.returncode != 0:
114+
print('install_name_tool command failed: ')
115+
print(stderr)
116+
if verbose:
117+
print(stdout)
99118

100119
def should_test_parallel():
101120
if platform.system() == 'Linux':
@@ -105,7 +124,6 @@ def should_test_parallel():
105124
return False
106125
return True
107126

108-
109127
def handle_invocation(toolchain_bin, args):
110128
swiftpm_args = get_swiftpm_options(args)
111129

@@ -138,19 +156,255 @@ def handle_invocation(toolchain_bin, args):
138156
test_args += ['--parallel']
139157
swiftpm('test', swift_exec, test_args, env)
140158
elif args.action == 'install':
141-
bin_path = swiftpm_bin_path(swift_exec, swiftpm_args, env)
142-
swiftpm('build', swift_exec, swiftpm_args, env)
143-
install(bin_path, args.toolchain)
159+
if platform.system() == 'Darwin':
160+
distribution_build_dir = os.path.join(args.build_path, 'dist')
161+
build_for_distribution(args, toolchain_bin, distribution_build_dir)
162+
install(args, distribution_build_dir)
163+
else:
164+
bin_path = swiftpm_bin_path(swift_exec, swiftpm_args, env)
165+
swiftpm('build', swift_exec, swiftpm_args, env)
166+
non_darwin_install(bin_path, args.toolchain, args.verbose)
144167
else:
145168
assert False, 'unknown action \'{}\''.format(args.action)
146169

147170

171+
# Installation flow for non-darwin platforms, only copies over swift-driver and swift-help
172+
# TODO: Unify CMake-based installation flow used on Darwin with this
173+
def non_darwin_install(swiftpm_bin_path, toolchain, verbose):
174+
toolchain_bin = os.path.join(toolchain, 'bin')
175+
for exe in ['swift-driver', 'swift-help']:
176+
install_binary(exe, swiftpm_bin_path, toolchain_bin, verbose)
177+
178+
def install(args, build_dir):
179+
# Construct and install universal swift-driver, swift-help executables
180+
# and libSwiftDriver, libSwiftOptions libraries
181+
toolchain_bin = os.path.join(args.toolchain, 'bin')
182+
toolchain_lib = os.path.join(args.toolchain, 'lib', 'swift', 'macosx')
183+
universal_dir = os.path.join(build_dir, 'universal-apple-macos%s' % macos_deployment_target)
184+
bin_dir = os.path.join(universal_dir, 'bin')
185+
lib_dir = os.path.join(universal_dir, 'lib')
186+
mkdir_p(universal_dir)
187+
mkdir_p(bin_dir)
188+
mkdir_p(lib_dir)
189+
190+
# swift-driver and swift-help
191+
install_executables(args, build_dir, bin_dir, toolchain_bin)
192+
193+
# libSwiftDriver and libSwiftOptions
194+
install_libraries(args, build_dir, lib_dir, toolchain_lib)
195+
196+
# SwiftDriver.swiftmodule and SwiftOptions.swiftmodule
197+
install_modules(args, build_dir, toolchain_lib)
198+
199+
def install_executables(args, build_dir, universal_bin_dir, toolchain_bin_dir):
200+
for exe in ['swift-driver', 'swift-help']:
201+
# Fixup rpaths
202+
for arch in macos_target_architectures:
203+
exe_bin_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
204+
'swift-driver', 'bin', exe)
205+
help_bin_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
206+
'swift-driver', 'bin', 'swift-help')
207+
driver_lib_dir_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
208+
'swift-driver', 'lib')
209+
delete_rpath(driver_lib_dir_path, exe_bin_path, args.verbose)
210+
# Only swift-driver requires libllbuild
211+
if exe == 'swift-driver':
212+
llbuild_lib_dir_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
213+
'llbuild', 'lib')
214+
delete_rpath(llbuild_lib_dir_path, exe_bin_path, args.verbose)
215+
216+
# Merge the multiple architecture binaries into a universal binary and install
217+
output_bin_path = os.path.join(universal_bin_dir, exe)
218+
lipo_cmd = ['lipo']
219+
# Inputs
220+
for arch in macos_target_architectures:
221+
input_bin_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
222+
'swift-driver', 'bin', exe)
223+
lipo_cmd.append(input_bin_path)
224+
lipo_cmd.extend(['-create', '-output', output_bin_path])
225+
subprocess.check_call(lipo_cmd)
226+
install_binary(exe, universal_bin_dir, toolchain_bin_dir, args.verbose)
227+
228+
def install_libraries(args, build_dir, universal_lib_dir, toolchain_lib_dir):
229+
# Fixup the rpath for libSwiftDriver (libSwiftOptions does not link against these libraries)
230+
for arch in macos_target_architectures:
231+
lib_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
232+
'swift-driver', 'lib', 'libSwiftDriver' + shared_lib_ext)
233+
driver_lib_dir_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
234+
'swift-driver', 'lib')
235+
llbuild_lib_dir_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
236+
'llbuild', 'lib')
237+
delete_rpath(driver_lib_dir_path, lib_path, args.verbose)
238+
delete_rpath(llbuild_lib_dir_path, lib_path, args.verbose)
239+
240+
# Install the libSwiftDriver and libSwiftOptions shared libraries into the toolchain lib
241+
for lib in ['libSwiftDriver', 'libSwiftOptions']:
242+
dylib_file = lib + shared_lib_ext
243+
output_dylib_path = os.path.join(universal_lib_dir, dylib_file)
244+
lipo_cmd = ['lipo']
245+
for arch in macos_target_architectures:
246+
input_lib_path = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
247+
'swift-driver', 'lib', dylib_file)
248+
lipo_cmd.append(input_lib_path)
249+
lipo_cmd.extend(['-create', '-output', output_dylib_path])
250+
subprocess.check_call(lipo_cmd)
251+
install_binary(dylib_file, universal_lib_dir, toolchain_lib_dir, args.verbose)
252+
253+
def install_modules(args, build_dir, toolchain_lib_dir):
254+
for module in ['SwiftDriver', 'SwiftOptions']:
255+
toolchain_module_dir = os.path.join(toolchain_lib_dir, module + '.swiftmodule')
256+
mkdir_p(toolchain_module_dir)
257+
for arch in macos_target_architectures:
258+
swift_dir = os.path.join(build_dir, arch + '-apple-macos' + macos_deployment_target,
259+
'swift-driver', 'swift')
260+
for fileext in ['.swiftmodule', '.swiftdoc']:
261+
install_binary(module + fileext, swift_dir, toolchain_module_dir, args.verbose)
262+
os.rename(os.path.join(toolchain_module_dir, module + fileext),
263+
os.path.join(toolchain_module_dir, arch + '-apple-macos' + fileext))
264+
265+
def build_for_distribution(args, toolchain_bin, build_dir):
266+
print('Preparing SwiftDriver for distribution using CMake.')
267+
swiftc_exec = os.path.join(toolchain_bin, 'swiftc')
268+
269+
# Targets for which we must build swift-driver to distribute
270+
if platform.system() == 'Darwin':
271+
targets = [x + '-apple-macos' + macos_deployment_target for x in macos_target_architectures]
272+
else:
273+
targets = [get_build_target(swiftc_path, args)]
274+
275+
for target in targets:
276+
base_cmake_flags = [
277+
'-DCMAKE_Swift_FLAGS=-target %s' % target,
278+
]
279+
if platform.system() == 'Darwin':
280+
base_cmake_flags.append('-DCMAKE_OSX_DEPLOYMENT_TARGET=%s' % macos_deployment_target)
281+
282+
# Target directory to build distribution artifacts
283+
cmake_target_dir = os.path.join(build_dir, target)
284+
# LLBuild
285+
build_llbuild_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags)
286+
# TSC
287+
build_tsc_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags)
288+
# Argument Parser
289+
build_argument_parser_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags)
290+
# Yams
291+
build_yams_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags)
292+
# SwiftDriver
293+
build_swift_driver_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags)
294+
295+
def build_llbuild_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags):
296+
print('Building llbuild for target: %s' % target)
297+
llbuild_source_dir = os.path.join(args.package_path, '..', 'llbuild')
298+
llbuild_build_dir = os.path.join(cmake_target_dir, 'llbuild')
299+
llbuild_api_dir = os.path.join(llbuild_build_dir, '.cmake/api/v1/query')
300+
mkdir_p(llbuild_api_dir)
301+
subprocess.check_call(['touch', os.path.join(llbuild_api_dir, 'codemodel-v2')])
302+
flags = [
303+
'-DBUILD_SHARED_LIBS=OFF',
304+
'-DCMAKE_C_COMPILER:=clang',
305+
'-DCMAKE_CXX_COMPILER:=clang++',
306+
'-DCMAKE_CXX_FLAGS=-target %s' % target,
307+
'-DLLBUILD_SUPPORT_BINDINGS:=Swift'
308+
]
309+
if platform.system() == 'Darwin':
310+
flags.append('-DCMAKE_OSX_ARCHITECTURES=%s' % target.split('-')[0])
311+
llbuild_cmake_flags = base_cmake_flags + flags
312+
if args.sysroot:
313+
llbuild_cmake_flags.append('-DSQLite3_INCLUDE_DIR=%s/usr/include' % args.sysroot)
314+
cmake_build(args, swiftc_exec, llbuild_cmake_flags, llbuild_source_dir, llbuild_build_dir)
315+
316+
def build_tsc_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags):
317+
print('Building TSC for target: %s' % target)
318+
tsc_source_dir = os.path.join(args.package_path, '..', 'swift-tools-support-core')
319+
tsc_build_dir = os.path.join(cmake_target_dir, 'swift-tools-support-core')
320+
tsc_flags = base_cmake_flags + ['-DBUILD_SHARED_LIBS=OFF']
321+
cmake_build(args, swiftc_exec, tsc_flags, tsc_source_dir, tsc_build_dir)
322+
323+
def build_yams_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags):
324+
print('Building Yams for target: %s' % target)
325+
yams_source_dir = os.path.join(args.package_path, '..', 'yams')
326+
yams_build_dir = os.path.join(cmake_target_dir, 'yams')
327+
yams_flags = base_cmake_flags + ['-DBUILD_SHARED_LIBS=OFF', '-DCMAKE_C_FLAGS=-target %s' % target]
328+
cmake_build(args, swiftc_exec, yams_flags, yams_source_dir, yams_build_dir)
329+
330+
def build_argument_parser_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags):
331+
print('Building Argument Parser for target: %s' % target)
332+
parser_source_dir = os.path.join(args.package_path, '..', 'swift-argument-parser')
333+
parser_build_dir = os.path.join(cmake_target_dir, 'swift-argument-parser')
334+
custom_flags = ['-DBUILD_SHARED_LIBS=OFF', '-DBUILD_TESTING=NO', '-DBUILD_EXAMPLES=NO']
335+
parser_flags = base_cmake_flags + custom_flags
336+
cmake_build(args, swiftc_exec, parser_flags, parser_source_dir, parser_build_dir)
337+
return
338+
339+
def build_swift_driver_using_cmake(args, target, swiftc_exec, cmake_target_dir, base_cmake_flags):
340+
print('Building Swift Driver for target: %s' % target)
341+
driver_source_dir = args.package_path
342+
driver_build_dir = os.path.join(cmake_target_dir, 'swift-driver')
343+
# TODO: Enable Library Evolution for swift-driver
344+
#swift_flags = '-DCMAKE_Swift_FLAGS=' + '-enable-library-evolution' + ' -emit-module-interface-path ' + os.path.join(cmake_target_dir, 'swift-driver', 'SwiftDriver.swiftinterface') + ' -Xfrontend -module-interface-preserve-types-as-written'
345+
swift_flags = ''
346+
flags = [
347+
'-DLLBuild_DIR=' + os.path.join(os.path.join(cmake_target_dir, 'llbuild'), 'cmake/modules'),
348+
'-DTSC_DIR=' + os.path.join(os.path.join(cmake_target_dir, 'swift-tools-support-core'), 'cmake/modules'),
349+
'-DYams_DIR=' + os.path.join(os.path.join(cmake_target_dir, 'yams'), 'cmake/modules'),
350+
'-DArgumentParser_DIR=' + os.path.join(os.path.join(cmake_target_dir, 'swift-argument-parser'), 'cmake/modules'),
351+
swift_flags
352+
]
353+
driver_cmake_flags = base_cmake_flags + flags
354+
cmake_build(args, swiftc_exec, driver_cmake_flags, driver_source_dir, driver_build_dir)
355+
356+
def cmake_build(args, swiftc_exec, cmake_args, source_path, build_dir):
357+
"""Configure with CMake and build with Ninja"""
358+
swift_flags = ''
359+
if args.sysroot:
360+
swift_flags = '-sdk %s' % args.sysroot
361+
cmd = [
362+
args.cmake_bin, '-G', 'Ninja',
363+
'-DCMAKE_MAKE_PROGRAM=%s' % args.ninja_bin,
364+
'-DCMAKE_BUILD_TYPE:=Release',
365+
'-DCMAKE_Swift_FLAGS=' + swift_flags,
366+
'-DCMAKE_Swift_COMPILER:=%s' % (swiftc_exec),
367+
] + cmake_args + [source_path]
368+
if args.verbose:
369+
print(' '.join(cmd))
370+
mkdir_p(build_dir)
371+
subprocess.check_output(cmd, cwd=build_dir)
372+
373+
# Invoke Ninja
374+
ninja_cmd = [args.ninja_bin]
375+
if args.verbose:
376+
ninja_cmd.append('-v')
377+
print(' '.join(ninja_cmd))
378+
ninjaProcess = subprocess.Popen(ninja_cmd, cwd=build_dir,
379+
stdout=subprocess.PIPE,
380+
stderr=subprocess.PIPE,
381+
env = os.environ)
382+
stdout, stderr = ninjaProcess.communicate()
383+
if ninjaProcess.returncode != 0:
384+
print('Ninja invocation failed: ')
385+
print(stderr)
386+
sys.exit(ninjaProcess.returncode)
387+
if args.verbose:
388+
print(stdout)
389+
390+
def get_build_target(swiftc_path, args):
391+
"""Returns the target-triple of the current machine."""
392+
try:
393+
target_info_json = subprocess.check_output([swiftc_path, '-print-target-info'],
394+
stderr=subprocess.PIPE,
395+
universal_newlines=True).strip()
396+
args.target_info = json.loads(target_info_json)
397+
return args.target_info['target']['unversionedTriple']
398+
except Exception as e:
399+
error(str(e))
400+
148401
def main():
149402
parser = argparse.ArgumentParser(description='Build along with the Swift build-script.')
150403
def add_common_args(parser):
151404
parser.add_argument('--package-path', metavar='PATH', help='directory of the package to build', default='.')
152405
parser.add_argument('--toolchain', required=True, metavar='PATH', help='build using the toolchain at PATH')
153406
parser.add_argument('--ninja-bin', metavar='PATH', help='ninja binary to use for testing')
407+
parser.add_argument('--cmake-bin', metavar='PATH', help='cmake binary to use for building')
154408
parser.add_argument('--build-path', metavar='PATH', default='.build', help='build in the given path')
155409
parser.add_argument('--configuration', '-c', default='debug', help='build using configuration (release|debug)')
156410
parser.add_argument('--no-local-deps', action='store_true', help='use normal remote dependencies when building')

0 commit comments

Comments
 (0)