1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import os
5
+ import pathlib
6
+ import platform
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ import zipfile
12
+ from collections import namedtuple
13
+
14
+ DEPS = {"llvm" : ["LLVMSupport" ],
15
+ "swift" : ["swiftFrontendTool" ]}
16
+
17
+
18
+ def getoptions ():
19
+ parser = argparse .ArgumentParser (description = "package swift for codeql compilation" )
20
+ for p in DEPS :
21
+ parser .add_argument (f"--{ p } " , required = True , type = resolve ,
22
+ metavar = "DIR" , help = f"path to { p } build root" )
23
+ default_output = f"swift-prebuilt-{ get_platform ()} .zip"
24
+ parser .add_argument ("--keep-tmp-dir" , "-K" , action = "store_true" ,
25
+ help = "do not clean up the temporary directory" )
26
+ parser .add_argument ("--output" , "-o" , type = pathlib .Path , metavar = "DIR_OR_ZIP" ,
27
+ help = "output zip file or directory "
28
+ f"(by default the filename is { default_output } )" )
29
+ update_help_fmt = "Only update the {} library in DIR, triggering rebuilds of required files"
30
+ parser .add_argument ("--update-shared" , "-u" , metavar = "DIR" , type = pathlib .Path ,
31
+ help = update_help_fmt .format ("shared" ))
32
+ parser .add_argument ("--update-static" , "-U" , metavar = "DIR" , type = pathlib .Path ,
33
+ help = update_help_fmt .format ("static" ))
34
+ opts = parser .parse_args ()
35
+ if opts .output and (opts .update_shared or opts .update_static ):
36
+ parser .error ("provide --output or one of --update-*, not both" )
37
+ if opts .output is None :
38
+ opts .output = pathlib .Path ()
39
+ opts .output = get_tgt (opts .output , default_output )
40
+ return opts
41
+
42
+
43
+ Libs = namedtuple ("Libs" , ("archive" , "static" , "shared" ))
44
+
45
+ DEPLIST = [x for d in DEPS .values () for x in d ]
46
+
47
+ CMAKELISTS_DUMMY = f"""
48
+ cmake_minimum_required(VERSION 3.12.4)
49
+
50
+ project(dummy C CXX)
51
+
52
+ find_package(LLVM REQUIRED CONFIG PATHS ${{LLVM_ROOT}}/lib/cmake/llvm NO_DEFAULT_PATH)
53
+ find_package(Clang REQUIRED CONFIG PATHS ${{LLVM_ROOT}}/lib/cmake/clang NO_DEFAULT_PATH)
54
+ find_package(Swift REQUIRED CONFIG PATHS ${{SWIFT_ROOT}}/lib/cmake/swift NO_DEFAULT_PATH)
55
+
56
+ add_executable(dummy empty.cpp)
57
+ target_link_libraries(dummy PRIVATE { " " .join (DEPLIST )} )
58
+ """
59
+
60
+ EXPORTED_LIB = "swiftAndLlvmSupport"
61
+
62
+ CMAKELISTS_EXPORTED_FMT = """
63
+ add_library({exported} INTERFACE)
64
+
65
+ if (BUILD_SHARED_LIBS)
66
+ if (APPLE)
67
+ set(EXT "dylib")
68
+ else()
69
+ set(EXT "so")
70
+ endif()
71
+ else()
72
+ set(EXT "a")
73
+ endif()
74
+
75
+ set (SwiftLLVMWrapperLib libswiftAndLlvmSupportReal.${{EXT}})
76
+ set (input ${{CMAKE_CURRENT_LIST_DIR}}/${{SwiftLLVMWrapperLib}})
77
+ set (output ${{CMAKE_BINARY_DIR}}/${{SwiftLLVMWrapperLib}})
78
+
79
+ add_custom_command(OUTPUT ${{output}}
80
+ COMMAND ${{CMAKE_COMMAND}} -E copy_if_different ${{input}} ${{output}}
81
+ DEPENDS ${{input}})
82
+ add_custom_target(copy-llvm-swift-wrapper DEPENDS ${{output}})
83
+
84
+ target_include_directories({exported} INTERFACE ${{CMAKE_CURRENT_LIST_DIR}}/include)
85
+ target_link_libraries({exported} INTERFACE
86
+ ${{output}}
87
+ {libs}
88
+ )
89
+ add_dependencies(swiftAndLlvmSupport copy-llvm-swift-wrapper)
90
+ """
91
+
92
+
93
+ class TempDir :
94
+ def __init__ (self , cleanup = True ):
95
+ self .path = None
96
+ self .cleanup = cleanup
97
+
98
+ def __enter__ (self ):
99
+ self .path = pathlib .Path (tempfile .mkdtemp ())
100
+ return self .path
101
+
102
+ def __exit__ (self , * args ):
103
+ if self .cleanup :
104
+ shutil .rmtree (self .path )
105
+
106
+
107
+ def resolve (p ):
108
+ return pathlib .Path (p ).resolve ()
109
+
110
+
111
+ def run (prog , * , cwd , env = None , input = None ):
112
+ print ("running" , " " .join (prog ), f"(cwd={ cwd } )" )
113
+ if env is not None :
114
+ runenv = dict (os .environ )
115
+ runenv .update (env )
116
+ else :
117
+ runenv = None
118
+ subprocess .run (prog , cwd = cwd , env = runenv , input = input , text = True )
119
+
120
+
121
+ def build (dir , targets ):
122
+ print (f"building { ' ' .join (targets )} in { dir } " )
123
+ cmd = ["cmake" , "--build" , "." , "--" ]
124
+ cmd .extend (targets )
125
+ run (cmd , cwd = dir )
126
+
127
+
128
+ def get_platform ():
129
+ return "linux" if platform .system () == "Linux" else "macos"
130
+
131
+
132
+ def create_empty_cpp (path ):
133
+ with open (path / "empty.cpp" , "w" ):
134
+ pass
135
+
136
+
137
+ def install (tmp , opts ):
138
+ print ("installing dependencies" )
139
+ tgt = tmp / "install"
140
+ for p in DEPS :
141
+ builddir = getattr (opts , p )
142
+ run (["cmake" , "--build" , "." , "--" , "install" ], cwd = builddir , env = {"DESTDIR" : tgt })
143
+ if sys .platform != 'linux' :
144
+ return tgt / "Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain"
145
+ return tgt
146
+
147
+
148
+ def configure_dummy_project (tmp , * , llvm = None , swift = None , installed = None ):
149
+ print ("configuring dummy cmake project" )
150
+ if installed is not None :
151
+ swift = llvm = installed / "usr"
152
+ with open (tmp / "CMakeLists.txt" , "w" ) as out :
153
+ out .write (CMAKELISTS_DUMMY )
154
+ create_empty_cpp (tmp )
155
+ tgt = tmp / "build"
156
+ tgt .mkdir ()
157
+ run (["cmake" , f"-DLLVM_ROOT={ llvm } " , f"-DSWIFT_ROOT={ swift } " , "-DBUILD_SHARED_LIBS=OFF" , ".." ],
158
+ cwd = tgt )
159
+ return tgt
160
+
161
+
162
+ def get_libs (configured ):
163
+ print ("extracting linking information from dummy project" )
164
+ cut = 8
165
+ if sys .platform == 'linux' :
166
+ cut = 4
167
+ with open (configured / "CMakeFiles" / "dummy.dir" / "link.txt" ) as link :
168
+ libs = link .read ().split ()[cut :] # skip up to -o dummy
169
+ ret = Libs ([], [], [])
170
+ for l in libs :
171
+ if l .endswith (".a" ):
172
+ ret .static .append (str ((configured / l ).resolve ()))
173
+ elif l .endswith (".so" ) or l .endswith (".tbd" ) or l .endswith (".dylib" ):
174
+ l = pathlib .Path (l ).stem
175
+ ret .shared .append (f"-l{ l [3 :]} " ) # drop 'lib' prefix and '.so' suffix
176
+ elif l .startswith ("-l" ):
177
+ ret .shared .append (l )
178
+ else :
179
+ raise ValueError (f"cannot understand link.txt: " + l )
180
+ # move direct dependencies into archive
181
+ ret .archive [:] = ret .static [:len (DEPLIST )]
182
+ ret .static [:len (DEPLIST )] = []
183
+ return ret
184
+
185
+
186
+ def get_tgt (tgt , filename ):
187
+ if tgt .is_dir ():
188
+ tgt /= filename
189
+ return tgt .resolve ()
190
+
191
+
192
+ def create_static_lib (tgt , libs ):
193
+ tgt = get_tgt (tgt , f"lib{ EXPORTED_LIB } Real.a" )
194
+ print (f"packaging { tgt .name } " )
195
+ if sys .platform == 'linux' :
196
+ includedlibs = "\n " .join (f"addlib { l } " for l in libs .archive + libs .static )
197
+ mriscript = f"create { tgt } \n { includedlibs } \n save\n end"
198
+ run (["ar" , "-M" ], cwd = tgt .parent , input = mriscript )
199
+ else :
200
+ includedlibs = " " .join (f"{ l } " for l in libs .archive + libs .static )
201
+ libtool_args = ["libtool" , "-static" ]
202
+ libtool_args .extend (libs .archive )
203
+ libtool_args .extend (libs .static )
204
+ libtool_args .append ("-o" )
205
+ libtool_args .append (str (tgt ))
206
+ run (libtool_args , cwd = tgt .parent )
207
+ return tgt
208
+
209
+
210
+ def create_shared_lib (tgt , libs ):
211
+ ext = "so"
212
+ if sys .platform != 'linux' :
213
+ ext = "dylib"
214
+ libname = f"lib{ EXPORTED_LIB } Real.{ ext } "
215
+ tgt = get_tgt (tgt , libname )
216
+ print (f"packaging { libname } " )
217
+ compiler = os .environ .get ("CC" , "clang" )
218
+ cmd = [compiler , "-shared" ]
219
+
220
+ if sys .platform == 'linux' :
221
+ cmd .append ("-Wl,--whole-archive" )
222
+ else :
223
+ cmd .append ("-Wl,-all_load" )
224
+
225
+ cmd .append (f"-o{ tgt } " )
226
+ cmd .extend (libs .archive )
227
+
228
+ if sys .platform == 'linux' :
229
+ cmd .append ("-Wl,--no-whole-archive" )
230
+ else :
231
+ cmd .append ("-lc++" )
232
+
233
+ cmd .extend (libs .static )
234
+ cmd .extend (libs .shared )
235
+ run (cmd , cwd = tgt .parent )
236
+ if sys .platform != "linux" :
237
+ run (["install_name_tool" , "-id" , f"@executable_path/{ libname } " , libname ], cwd = tgt .parent )
238
+ return tgt
239
+
240
+
241
+ def copy_includes (src , tgt ):
242
+ print ("copying includes" )
243
+ for dir , exts in (("include" , ("h" , "def" , "inc" )), ("stdlib" , ("h" ,))):
244
+ srcdir = src / "usr" / dir
245
+ for ext in exts :
246
+ for srcfile in srcdir .rglob (f"*.{ ext } " ):
247
+ tgtfile = tgt / dir / srcfile .relative_to (srcdir )
248
+ tgtfile .parent .mkdir (parents = True , exist_ok = True )
249
+ shutil .copy (srcfile , tgtfile )
250
+
251
+
252
+ def create_sdk (installed , tgt ):
253
+ print ("assembling sdk" )
254
+ srcdir = installed / "usr" / "lib" / "swift"
255
+ tgtdir = tgt / "usr" / "lib" / "swift"
256
+ if get_platform () == "linux" :
257
+ srcdir /= "linux"
258
+ tgtdir /= "linux/x86_64"
259
+ else :
260
+ srcdir /= "macosx"
261
+ for mod in srcdir .glob ("*.swiftmodule" ):
262
+ shutil .copytree (mod , tgtdir / mod .name )
263
+ shutil .copytree (installed / "usr" / "stdlib" / "public" / "SwiftShims" ,
264
+ tgt / "usr" / "include" / "SwiftShims" )
265
+
266
+
267
+ def create_export_dir (tmp , installed , libs ):
268
+ print ("assembling prebuilt directory" )
269
+ exportedlibs = [create_static_lib (tmp , libs ), create_shared_lib (tmp , libs )]
270
+ tgt = tmp / "exported"
271
+ tgt .mkdir ()
272
+ for l in exportedlibs :
273
+ l .rename (tgt / l .name )
274
+ with open (tgt / "swift_llvm_prebuilt.cmake" , "w" ) as out :
275
+ # drop -l prefix here
276
+ sharedlibs = " " .join (l [2 :] for l in libs .shared )
277
+ out .write (CMAKELISTS_EXPORTED_FMT .format (exported = EXPORTED_LIB , libs = sharedlibs ))
278
+ copy_includes (installed , tgt )
279
+ create_sdk (installed , tgt / "sdk" )
280
+ return tgt
281
+
282
+
283
+ def zip_dir (src , tgt ):
284
+ print (f"compressing { src .name } to { tgt } " )
285
+ tgt = get_tgt (tgt , f"swift-prebuilt-{ get_platform ()} .zip" )
286
+ with zipfile .ZipFile (tgt , "w" ,
287
+ compression = zipfile .ZIP_DEFLATED ,
288
+ compresslevel = 6 ) as archive :
289
+ for srcfile in src .rglob ("*" ):
290
+ if srcfile .is_file ():
291
+ print (f"deflating { srcfile .relative_to (src )} " )
292
+ archive .write (srcfile , arcname = srcfile .relative_to (src ))
293
+ print (f"created { tgt } " )
294
+
295
+
296
+ def main (opts ):
297
+ tmp = pathlib .Path ('/tmp/llvm-swift' )
298
+ if os .path .exists (tmp ):
299
+ shutil .rmtree (tmp )
300
+ os .mkdir (tmp )
301
+ if opts .update_shared or opts .update_static :
302
+ for project , deps in DEPS .items ():
303
+ build (getattr (opts , project ), deps )
304
+ configured = configure_dummy_project (tmp , llvm = opts .llvm , swift = opts .swift )
305
+ libs = get_libs (configured )
306
+ if opts .update_shared :
307
+ create_shared_lib (opts .update_shared , libs )
308
+ if opts .update_static :
309
+ create_static_lib (opts .update_static , libs )
310
+ else :
311
+ installed = install (tmp , opts )
312
+ swift_syntax_build = opts .swift / "include/swift/Syntax/"
313
+ swift_syntax_install = installed / "usr/include/swift/Syntax/"
314
+ for header in os .listdir (swift_syntax_build ):
315
+ if header .endswith ('.h' ) or header .endswith ('.def' ):
316
+ shutil .copy (swift_syntax_build / header , swift_syntax_install / header )
317
+ configured = configure_dummy_project (tmp , installed = installed )
318
+ libs = get_libs (configured )
319
+ exported = create_export_dir (tmp , installed , libs )
320
+ zip_dir (exported , opts .output )
321
+
322
+
323
+ if __name__ == "__main__" :
324
+ main (getoptions ())
0 commit comments