Skip to content

Commit 4f182dc

Browse files
authored
Merge pull request #597 from ahoppen/pr/unified-build
Several changes in build-script-helper.py to support building SourceKit-LSP in a unified build
2 parents a15bd61 + ac24fd6 commit 4f182dc

File tree

2 files changed

+67
-39
lines changed

2 files changed

+67
-39
lines changed

.flake8

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[flake8]
2+
3+
ignore =
4+
E501,

Utilities/build-script-helper.py

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import shutil
99
import subprocess
1010
import sys
11-
from typing import Dict, List, Optional
11+
from typing import Dict, List
1212

1313

1414
# -----------------------------------------------------------------------------
@@ -27,23 +27,45 @@ def escapeCmdArg(arg: str) -> str:
2727
return arg
2828

2929

30-
def check_call(cmd: List[str], env: Optional[Dict[str, str]], cwd: Optional[str] = None, verbose: bool = False):
30+
def print_cmd(cmd: List[str], additional_env: Dict[str, str]) -> None:
31+
env_str = " ".join([f"{key}={escapeCmdArg(str(value))}" for (key, value) in additional_env.items()])
32+
command_str = " ".join([escapeCmdArg(str(arg)) for arg in cmd])
33+
print(f"{env_str} {command_str}")
34+
35+
36+
def env_with_additional_env(additional_env: Dict[str, str]) -> Dict[str, str]:
37+
env = dict(os.environ)
38+
for (key, value) in additional_env.items():
39+
env[key] = str(value)
40+
return env
41+
42+
43+
def check_call(cmd: List[str], additional_env: Dict[str, str] = {}, verbose: bool = False) -> None:
3144
if verbose:
32-
print(" ".join([escapeCmdArg(arg) for arg in cmd]))
33-
return subprocess.check_call(cmd, cwd=cwd, env=env, stderr=subprocess.STDOUT)
45+
print_cmd(cmd=cmd, additional_env=additional_env)
46+
47+
subprocess.check_call(cmd, env=env_with_additional_env(additional_env), stderr=subprocess.STDOUT)
48+
49+
50+
def check_output(cmd: List[str], additional_env: Dict[str, str] = {}, capture_stderr: bool = True, verbose: bool = False) -> str:
51+
if verbose:
52+
print_cmd(cmd=cmd, additional_env=additional_env)
53+
if capture_stderr:
54+
stderr = subprocess.STDOUT
55+
else:
56+
stderr = subprocess.DEVNULL
57+
return subprocess.check_output(cmd, env=env_with_additional_env(additional_env), stderr=stderr, encoding='utf-8')
3458

3559
# -----------------------------------------------------------------------------
3660
# SwiftPM wrappers
3761

3862

39-
def swiftpm_bin_path(swift_exec: str, swiftpm_args: List[str], env: Optional[Dict[str, str]], verbose: bool = False) -> str:
63+
def swiftpm_bin_path(swift_exec: str, swiftpm_args: List[str], additional_env: Dict[str, str], verbose: bool = False) -> str:
4064
"""
4165
Return the path of the directory that contains the binaries produced by this package.
4266
"""
4367
cmd = [swift_exec, 'build', '--show-bin-path'] + swiftpm_args
44-
if verbose:
45-
print(" ".join([escapeCmdArg(arg) for arg in cmd]))
46-
return subprocess.check_output(cmd, env=env, universal_newlines=True).strip()
68+
return check_output(cmd, additional_env=additional_env, capture_stderr=False, verbose=verbose).strip()
4769

4870

4971
def get_build_target(swift_exec: str, args: argparse.Namespace) -> str:
@@ -69,10 +91,13 @@ def get_build_target(swift_exec: str, args: argparse.Namespace) -> str:
6991
def get_swiftpm_options(swift_exec: str, args: argparse.Namespace) -> List[str]:
7092
swiftpm_args = [
7193
'--package-path', args.package_path,
72-
'--build-path', args.build_path,
94+
'--scratch-path', args.build_path,
7395
'--configuration', args.configuration,
7496
]
7597

98+
if args.multiroot_data_file:
99+
swiftpm_args += ['--multiroot-data-file', args.multiroot_data_file]
100+
76101
if args.verbose:
77102
swiftpm_args += ['--verbose']
78103

@@ -129,10 +154,12 @@ def get_swiftpm_environment_variables(swift_exec: str, args: argparse.Namespace)
129154
'swift test' invocation.
130155
"""
131156

132-
env = dict(os.environ)
133-
# Set the toolchain used in tests at runtime
134-
env['SOURCEKIT_TOOLCHAIN_PATH'] = args.toolchain
135-
env['INDEXSTOREDB_TOOLCHAIN_BIN_PATH'] = args.toolchain
157+
env = {
158+
# Set the toolchain used in tests at runtime
159+
'SOURCEKIT_TOOLCHAIN_PATH': args.toolchain,
160+
'INDEXSTOREDB_TOOLCHAIN_BIN_PATH': args.toolchain,
161+
'SWIFT_EXEC': f'{swift_exec}c'
162+
}
136163
# Use local dependencies (i.e. checked out next sourcekit-lsp).
137164
if not args.no_local_deps:
138165
env['SWIFTCI_USE_LOCAL_DEPS'] = "1"
@@ -152,29 +179,27 @@ def get_swiftpm_environment_variables(swift_exec: str, args: argparse.Namespace)
152179
if args.action == 'test' and not args.skip_long_tests:
153180
env['SOURCEKIT_LSP_ENABLE_LONG_TESTS'] = '1'
154181

155-
env['SWIFT_EXEC'] = '%sc' % (swift_exec)
156-
157182
return env
158183

159184

160-
def build(swift_exec: str, args: argparse.Namespace) -> None:
185+
def build_single_product(product: str, swift_exec: str, args: argparse.Namespace) -> None:
161186
"""
162187
Build one product in the package
163188
"""
164189
swiftpm_args = get_swiftpm_options(swift_exec, args)
165-
env = get_swiftpm_environment_variables(swift_exec, args)
166-
cmd = [swift_exec, 'build'] + swiftpm_args
167-
check_call(cmd, env=env, verbose=args.verbose)
190+
additional_env = get_swiftpm_environment_variables(swift_exec, args)
191+
cmd = [swift_exec, 'build', '--product', product] + swiftpm_args
192+
check_call(cmd, additional_env=additional_env, verbose=args.verbose)
168193

169194

170195
def run_tests(swift_exec: str, args: argparse.Namespace) -> None:
171196
"""
172197
Run all tests in the package
173198
"""
174199
swiftpm_args = get_swiftpm_options(swift_exec, args)
175-
env = get_swiftpm_environment_variables(swift_exec, args)
200+
additional_env = get_swiftpm_environment_variables(swift_exec, args)
176201

177-
bin_path = swiftpm_bin_path(swift_exec, swiftpm_args, env)
202+
bin_path = swiftpm_bin_path(swift_exec, swiftpm_args, additional_env=additional_env)
178203
tests = os.path.join(bin_path, 'sk-tests')
179204
print('Cleaning ' + tests)
180205
shutil.rmtree(tests, ignore_errors=True)
@@ -185,27 +210,21 @@ def run_tests(swift_exec: str, args: argparse.Namespace) -> None:
185210
'--disable-testable-imports',
186211
'--test-product', 'SourceKitLSPPackageTests'
187212
] + swiftpm_args
188-
check_call(cmd, env=env, verbose=args.verbose)
213+
check_call(cmd, additional_env=additional_env, verbose=args.verbose)
189214

190215

191216
def install_binary(exe: str, source_dir: str, install_dir: str, verbose: bool) -> None:
192217
cmd = ['rsync', '-a', os.path.join(source_dir, exe), install_dir]
193-
check_call(cmd, env=None, verbose=verbose)
218+
check_call(cmd, verbose=verbose)
194219

195220

196221
def install(swift_exec: str, args: argparse.Namespace) -> None:
197-
swiftpm_args = get_swiftpm_options(swift_exec, args)
198-
env = get_swiftpm_environment_variables(swift_exec, args)
199-
200-
bin_path = swiftpm_bin_path(swift_exec, swiftpm_args, env)
201-
swiftpm_args += ['-Xswiftc', '-no-toolchain-stdlib-rpath']
202-
check_call([
203-
swift_exec, 'build'
204-
] + swiftpm_args, env=env)
205-
206-
if not args.install_prefixes:
207-
args.install_prefixes = [args.toolchain]
222+
build_single_product('sourcekit-lsp', swift_exec, args)
208223

224+
swiftpm_args = get_swiftpm_options(swift_exec, args)
225+
additional_env = get_swiftpm_environment_variables(swift_exec, args)
226+
bin_path = swiftpm_bin_path(swift_exec, swiftpm_args=swiftpm_args, additional_env=additional_env)
227+
209228
for prefix in args.install_prefixes:
210229
install_binary('sourcekit-lsp', bin_path, os.path.join(prefix, 'bin'), verbose=args.verbose)
211230

@@ -214,12 +233,12 @@ def handle_invocation(swift_exec: str, args: argparse.Namespace) -> None:
214233
"""
215234
Depending on the action in 'args', build the package, installs the package or run tests.
216235
"""
217-
if not args.no_clean:
218-
print('Cleaning ' + args.build_path)
219-
shutil.rmtree(args.build_path, ignore_errors=True)
220-
221236
if args.action == 'build':
222-
build(swift_exec, args)
237+
# Build SourceKitLSPPackageTests to build all source code in sourcekit-lsp.
238+
# Build _SourceKitLSP and sourcekit-lsp because they are products (dylib, executable) that can be used from the build.
239+
products = ["SourceKitLSPPackageTests", "_SourceKitLSP", "sourcekit-lsp"]
240+
for product in products:
241+
build_single_product(product, swift_exec, args)
223242
elif args.action == 'test':
224243
run_tests(swift_exec, args)
225244
elif args.action == 'install':
@@ -247,6 +266,7 @@ def add_common_args(parser: argparse.ArgumentParser) -> None:
247266
parser.add_argument('--verbose', '-v', action='store_true', help='enable verbose output')
248267
parser.add_argument('--cross-compile-host', help='cross-compile for another host instead')
249268
parser.add_argument('--cross-compile-config', help='an SPM JSON destination file containing Swift cross-compilation flags')
269+
parser.add_argument('--multiroot-data-file', help='path to an Xcode workspace to create a unified build of all of Swift\'s SwiftPM projects')
250270

251271
if sys.version_info >= (3, 7, 0):
252272
subparsers = parser.add_subparsers(title='subcommands', dest='action', required=True, metavar='action')
@@ -274,6 +294,10 @@ def add_common_args(parser: argparse.ArgumentParser) -> None:
274294
args.build_path = os.path.abspath(args.build_path)
275295
args.toolchain = os.path.abspath(args.toolchain)
276296

297+
if args.action == 'install':
298+
if not args.install_prefixes:
299+
args.install_prefixes = [args.toolchain]
300+
277301
return args
278302

279303

0 commit comments

Comments
 (0)