Skip to content

Commit 04adfef

Browse files
committed
Add Swift package script
1 parent c6fc167 commit 04adfef

File tree

1 file changed

+324
-0
lines changed

1 file changed

+324
-0
lines changed

pkg_swift_llvm.py

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
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}\nsave\nend"
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

Comments
 (0)