Skip to content

Commit 0a2ac74

Browse files
authored
[5.5] Build PackageDescription and PackagePlugin universal on macOS platform (#3536)
* [5.5] Bootstrap script needs to build PackageDescription and PackagePlugin libraries universal when cross-compiling. Also unify and clean up some of the logic by making the helper function that installs libSwiftPM be more generic, and also apply to PackageDescription and PackagePlugin. rdar://75186958 (cherry picked from commit 637fa7a) * [5.5] Only pass `-enable-library-evolution` for PackageDescription and PackagePlugin on macOS Because PackageDescription unintentionally exports Foundation (for which a fix was attempted but then reverted after it broke some packages), we can only enable library evolution on Darwin platforms. rdar://78827075 (cherry picked from commit 6e7bb87) * [5.5] Enable swift module interfaces if the package author enables library evolution via unsafe flags There isn't currently a way for package authors to enable library evolution or module interfaces from the package manifest. They can pass `-enable-library-evolution` in their unsafe flags, but because `-emit-module-interface` requires a path parameter, it isn't something that can be set in the manifest. This adds a way to infer XCBuild settings based on values set in manifest-declared settings. The idea is to implement semantics appropriately for each platform based on generalized flags passed from the manifest. rdar://78773077 (cherry picked from commit 4569c7f) * Filter out `*.swiftmodule` and `Project` entries when installing binaries, since these should not be part of the installed toolchain. (cherry picked from commit 3741792)
1 parent 2ccaf04 commit 0a2ac74

File tree

12 files changed

+159
-68
lines changed

12 files changed

+159
-68
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// swift-tools-version:5.1
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "LibraryEvolution",
6+
products: [
7+
],
8+
targets: [
9+
.target(name: "A", dependencies: [], swiftSettings: [.unsafeFlags(["-enable-library-evolution"])]),
10+
.target(name: "B", dependencies: ["A"], swiftSettings: [.unsafeFlags(["-enable-library-evolution"])]),
11+
])

Fixtures/Miscellaneous/LibraryEvolution/Sources/A/A.swift

Whitespace-only changes.

Fixtures/Miscellaneous/LibraryEvolution/Sources/B/B.swift

Whitespace-only changes.

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ let package = Package(
120120
name: "PackageDescription",
121121
swiftSettings: [
122122
.unsafeFlags(["-package-description-version", "999.0"]),
123-
.unsafeFlags(["-enable-library-evolution"])
123+
.unsafeFlags(["-enable-library-evolution"], .when(platforms: [.macOS]))
124124
]),
125125

126126
// The `PackagePlugin` target provides the API that is available to
@@ -130,7 +130,7 @@ let package = Package(
130130
name: "PackagePlugin",
131131
swiftSettings: [
132132
.unsafeFlags(["-package-description-version", "999.0"]),
133-
.unsafeFlags(["-enable-library-evolution"])
133+
.unsafeFlags(["-enable-library-evolution"], .when(platforms: [.macOS]))
134134
]),
135135

136136
// MARK: SwiftPM specific support libraries

Sources/Build/BuildPlan.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -783,14 +783,14 @@ public final class SwiftTargetBuildDescription {
783783
args += ["-color-diagnostics"]
784784
}
785785

786-
// Add the output for the `.swiftinterface`, if requested.
787-
if buildParameters.enableParseableModuleInterfaces {
788-
args += ["-emit-parseable-module-interface-path", parseableModuleInterfaceOutputPath.pathString]
789-
}
790-
791786
// Add agruments from declared build settings.
792787
args += self.buildSettingsFlags()
793788

789+
// Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other way.
790+
if buildParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") {
791+
args += ["-emit-module-interface-path", parseableModuleInterfaceOutputPath.pathString]
792+
}
793+
794794
// User arguments (from -Xswiftc) should follow generated arguments to allow user overrides
795795
args += buildParameters.swiftCompilerFlags
796796
return args

Sources/PackageDescription/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ add_library(PackageDescription
2121

2222
target_compile_options(PackageDescription PUBLIC
2323
$<$<COMPILE_LANGUAGE:Swift>:-package-description-version$<SEMICOLON>999.0>)
24-
target_compile_options(PackageDescription PUBLIC
25-
$<$<COMPILE_LANGUAGE:Swift>:-enable-library-evolution>)
2624

2725
if(CMAKE_HOST_SYSTEM_NAME STREQUAL Darwin)
26+
target_compile_options(PackageDescription PUBLIC
27+
$<$<COMPILE_LANGUAGE:Swift>:-enable-library-evolution>)
2828
set(SWIFT_INTERFACE_PATH ${CMAKE_BINARY_DIR}/pm/ManifestAPI/PackageDescription.swiftinterface)
2929
target_compile_options(PackageDescription PUBLIC
3030
$<$<COMPILE_LANGUAGE:Swift>:-emit-module-interface-path$<SEMICOLON>${SWIFT_INTERFACE_PATH}>)

Sources/PackagePlugin/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ add_library(PackagePlugin
1717

1818
target_compile_options(PackagePlugin PUBLIC
1919
$<$<COMPILE_LANGUAGE:Swift>:-package-description-version$<SEMICOLON>999.0>)
20-
target_compile_options(PackagePlugin PUBLIC
21-
$<$<COMPILE_LANGUAGE:Swift>:-enable-library-evolution>)
2220

2321
if(CMAKE_HOST_SYSTEM_NAME STREQUAL Darwin)
22+
target_compile_options(PackagePlugin PUBLIC
23+
$<$<COMPILE_LANGUAGE:Swift>:-enable-library-evolution>)
2424
set(SWIFT_INTERFACE_PATH ${CMAKE_BINARY_DIR}/pm/PluginAPI/PackagePlugin.swiftinterface)
2525
target_compile_options(PackagePlugin PUBLIC
2626
$<$<COMPILE_LANGUAGE:Swift>:-emit-module-interface-path$<SEMICOLON>${SWIFT_INTERFACE_PATH}>)

Sources/XCBuildSupport/PIF.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,7 @@ public enum PIF {
936936
case WATCHOS_DEPLOYMENT_TARGET
937937
case MARKETING_VERSION
938938
case CURRENT_PROJECT_VERSION
939+
case SWIFT_EMIT_MODULE_INTERFACE
939940
}
940941

941942
public enum MultipleValueSetting: String, Codable {

Sources/XCBuildSupport/PIFBuilder.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,21 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
812812

813813
return bundleName
814814
}
815+
816+
// Add inferred build settings for a particular value for a manifest setting and value.
817+
private func addInferredBuildSettings(
818+
for setting: PIF.BuildSettings.MultipleValueSetting,
819+
value: [String],
820+
platform: PIF.BuildSettings.Platform? = nil,
821+
configuration: BuildConfiguration,
822+
settings: inout PIF.BuildSettings
823+
) {
824+
// Automatically set SWIFT_EMIT_MODULE_INTERFACE if the package author uses unsafe flags to enable
825+
// library evolution (this is needed until there is a way to specify this in the package manifest).
826+
if setting == .OTHER_SWIFT_FLAGS && value.contains("-enable-library-evolution") {
827+
settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES"
828+
}
829+
}
815830

816831
// Apply target-specific build settings defined in the manifest.
817832
private func addManifestBuildSettings(
@@ -833,8 +848,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
833848
switch configuration {
834849
case .debug:
835850
debugSettings[setting, for: platform, default: ["$(inherited)"]] += value
851+
addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .debug, settings: &debugSettings)
836852
case .release:
837853
releaseSettings[setting, for: platform, default: ["$(inherited)"]] += value
854+
addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .release, settings: &releaseSettings)
838855
}
839856
}
840857

@@ -847,8 +864,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
847864
switch configuration {
848865
case .debug:
849866
debugSettings[setting, default: ["$(inherited)"]] += value
867+
addInferredBuildSettings(for: setting, value: value, configuration: .debug, settings: &debugSettings)
850868
case .release:
851869
releaseSettings[setting, default: ["$(inherited)"]] += value
870+
addInferredBuildSettings(for: setting, value: value, configuration: .release, settings: &releaseSettings)
852871
}
853872
}
854873

Tests/CommandsTests/BuildToolTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,18 @@ final class BuildToolTests: XCTestCase {
253253
}
254254
}
255255

256+
func testAutomaticParseableInterfacesWithLibraryEvolution() {
257+
fixture(name: "Miscellaneous/LibraryEvolution") { path in
258+
do {
259+
let result = try build([], packagePath: path)
260+
XCTAssert(result.binContents.contains("A.swiftinterface"))
261+
XCTAssert(result.binContents.contains("B.swiftinterface"))
262+
} catch SwiftPMProductError.executionFailure(_, _, let stderr) {
263+
XCTFail(stderr)
264+
}
265+
}
266+
}
267+
256268
func testBuildCompleteMessage() {
257269
fixture(name: "DependencyResolution/Internal/Simple") { path in
258270
do {

Tests/XCBuildSupportTests/PIFBuilderTests.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2224,6 +2224,68 @@ class PIFBuilderTests: XCTestCase {
22242224
}
22252225
}
22262226
}
2227+
2228+
/// Tests that the inference of XCBuild build settings based on the package manifest's declared unsafe settings
2229+
/// works as expected.
2230+
func testUnsafeFlagsBuildSettingInference() throws {
2231+
let fs = InMemoryFileSystem(emptyFiles:
2232+
"/MyLib/Sources/MyLib/Foo.swift"
2233+
)
2234+
2235+
let diagnostics = DiagnosticsEngine()
2236+
let graph = try loadPackageGraph(
2237+
fs: fs,
2238+
diagnostics: diagnostics,
2239+
manifests: [
2240+
Manifest.createManifest(
2241+
name: "MyLib",
2242+
path: "/MyLib",
2243+
packageKind: .root,
2244+
packageLocation: "/MyLib",
2245+
v: .v5,
2246+
products: [
2247+
.init(name: "MyLib", type: .library(.automatic), targets: ["MyLib"]),
2248+
],
2249+
targets: [
2250+
.init(name: "MyLib", settings: [
2251+
.init(
2252+
tool: .swift,
2253+
name: .unsafeFlags,
2254+
value: ["-enable-library-evolution"],
2255+
condition: .init(config: "release")),
2256+
]),
2257+
]),
2258+
],
2259+
shouldCreateMultipleTestProducts: true
2260+
)
2261+
2262+
let builder = PIFBuilder(graph: graph, parameters: .mock(), diagnostics: diagnostics)
2263+
let pif = try builder.construct()
2264+
2265+
XCTAssertNoDiagnostics(diagnostics)
2266+
2267+
PIFTester(pif) { workspace in
2268+
workspace.checkProject("PACKAGE:/MyLib") { project in
2269+
project.checkTarget("PACKAGE-TARGET:MyLib") { target in
2270+
target.checkBuildConfiguration("Debug") { configuration in
2271+
configuration.checkBuildSettings { settings in
2272+
// Check that the `-enable-library-evolution` setting for Release didn't affect Debug.
2273+
XCTAssertEqual(settings[.SWIFT_EMIT_MODULE_INTERFACE], nil)
2274+
XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil)
2275+
}
2276+
}
2277+
target.checkBuildConfiguration("Release") { configuration in
2278+
configuration.checkBuildSettings { settings in
2279+
// Check that the `-enable-library-evolution` setting for Release also set SWIFT_EMIT_MODULE_INTERFACE.
2280+
XCTAssertEqual(settings[.SWIFT_EMIT_MODULE_INTERFACE], "YES")
2281+
XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], ["$(inherited)", "-enable-library-evolution"])
2282+
}
2283+
}
2284+
}
2285+
}
2286+
}
2287+
}
2288+
22272289
#endif
22282290
}
22292291

Utilities/bootstrap

Lines changed: 43 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
from __future__ import print_function
1616

1717
import argparse
18-
from distutils import dir_util
19-
from distutils import file_util
2018
import json
2119
import os
2220
import platform
@@ -27,10 +25,11 @@ from helpers import note, error, symlink_force, mkdir_p, call, call_output
2725

2826
g_macos_deployment_target = '10.15'
2927

28+
g_shared_lib_prefix = "lib"
3029
if platform.system() == 'Darwin':
31-
g_shared_lib_ext = ".dylib"
30+
g_shared_lib_suffix = ".dylib"
3231
else:
33-
g_shared_lib_ext = ".so"
32+
g_shared_lib_suffix = ".so"
3433

3534
def main():
3635
parser = argparse.ArgumentParser(description="""
@@ -380,7 +379,7 @@ def install(args):
380379
"PackageGraph", "SPMBuildCore", "Build",
381380
"Xcodeproj", "Workspace"
382381
]
383-
install_libswiftpm_dylib(args, "SwiftPM", args.libswiftpm_install_dir, libswiftpm_modules)
382+
install_dylib(args, "SwiftPM", args.libswiftpm_install_dir, libswiftpm_modules)
384383

385384
# Install libSwiftPMDataModel if an install directory was provided.
386385
if args.libswiftpmdatamodel_install_dir:
@@ -391,82 +390,59 @@ def install(args):
391390
"PackageGraph", "SPMBuildCore",
392391
"Xcodeproj", "Workspace"
393392
]
394-
install_libswiftpm_dylib(args, "SwiftPMDataModel", args.libswiftpmdatamodel_install_dir, libswiftpmdatamodel_modules)
393+
install_dylib(args, "SwiftPMDataModel", args.libswiftpmdatamodel_install_dir, libswiftpmdatamodel_modules)
395394

395+
# Installs the SwiftPM tools and runtime support libraries.
396396
def install_swiftpm(prefix, args):
397397
# Install swiftpm binaries.
398398
for binary in ["swift-build", "swift-test", "swift-run", "swift-package", "swift-package-collection"]:
399399
dest = os.path.join(prefix, "bin")
400400
install_binary(args, binary, dest)
401401

402+
# On Darwin, also install the swiftpm-xctest-helper tool.
402403
if platform.system() == 'Darwin':
403404
dest = os.path.join(prefix, "libexec", "swift", "pm")
404405
install_binary(args, "swiftpm-xctest-helper", dest)
405406

406-
# Install PackageDescription runtime libraries.
407-
runtime_lib_dest = os.path.join(prefix, "lib", "swift", "pm")
408-
runtime_lib_src = os.path.join(args.bootstrap_dir, "pm")
407+
# Install the PackageDescription library and associated modules.
408+
dest = os.path.join(prefix, "lib", "swift", "pm", "ManifestAPI")
409+
install_dylib(args, "PackageDescription", dest, ["PackageDescription"])
409410

410-
files_to_install = ["libPackageDescription" + g_shared_lib_ext]
411-
if platform.system() == 'Darwin':
412-
files_to_install.append("PackageDescription.swiftinterface")
413-
else:
414-
files_to_install.append("PackageDescription.swiftmodule")
415-
files_to_install.append("PackageDescription.swiftdoc")
416-
417-
for file in files_to_install:
418-
src = os.path.join(runtime_lib_src, "ManifestAPI", file)
419-
dest = os.path.join(runtime_lib_dest, "ManifestAPI", file)
420-
mkdir_p(os.path.dirname(dest))
421-
422-
note("Installing %s to %s" % (src, dest))
423-
424-
file_util.copy_file(src, dest, update=1)
425-
426-
files_to_install = ["libPackagePlugin" + g_shared_lib_ext]
427-
if platform.system() == 'Darwin':
428-
files_to_install.append("PackagePlugin.swiftinterface")
429-
else:
430-
files_to_install.append("PackagePlugin.swiftmodule")
431-
files_to_install.append("PackagePlugin.swiftdoc")
432-
433-
for file in files_to_install:
434-
src = os.path.join(runtime_lib_src, "PluginAPI", file)
435-
dest = os.path.join(runtime_lib_dest, "PluginAPI", file)
436-
mkdir_p(os.path.dirname(dest))
437-
438-
note("Installing %s to %s" % (src, dest))
439-
440-
file_util.copy_file(src, dest, update=1)
411+
# Install the PackagePlugin library and associated modules.
412+
dest = os.path.join(prefix, "lib", "swift", "pm", "PluginAPI")
413+
install_dylib(args, "PackagePlugin", dest, ["PackagePlugin"])
441414

442415

443-
def install_libswiftpm_dylib(args, library_name, install_dir, module_names):
444-
# FIXME: Don't hardcode the prefix and suffix.
445-
install_binary(args, "lib" + library_name + ".dylib", install_dir)
416+
# Helper function that installs a dynamic library and a set of modules to a particular directory.
417+
def install_dylib(args, library_name, install_dir, module_names):
418+
# Install the dynamic library itself.
419+
install_binary(args, g_shared_lib_prefix + library_name + g_shared_lib_suffix, install_dir)
446420

447-
# Install the swiftmodule and swiftdoc files.
421+
# Install the swiftmodule/swiftinterface and swiftdoc files for all the modules.
448422
for module in module_names:
449-
install_binary(args, module + ".swiftmodule", install_dir)
450-
if not args.cross_compile_hosts: # When compiling for multiple arches, swiftdoc is part of the swiftmodule directory
423+
# If we're cross-compiling, we expect the .swiftmodule to be a directory that contains everything.
424+
if args.cross_compile_hosts:
425+
install_binary(args, module + ".swiftmodule", install_dir, ['Project', '*.swiftmodule'])
426+
else:
427+
# Otherwise we have either a .swiftinterface or a .swiftmodule, plus a .swiftdoc.
428+
if os.path.exists(os.path.join(args.bin_dir, module + ".swiftinterface")):
429+
install_binary(args, module + ".swiftinterface", install_dir)
430+
else:
431+
install_binary(args, module + ".swiftmodule", install_dir)
451432
install_binary(args, module + ".swiftdoc", install_dir)
452433

453-
# Install the C headers.
454-
tscclibc_include_dir = os.path.join(args.tsc_source_dir, "Sources/TSCclibc/include")
455-
tscclibc_include_dir_dest = os.path.join(install_dir, "TSCclibc")
456-
dir_util.copy_tree(tscclibc_include_dir, tscclibc_include_dir_dest)
457-
458434

459-
def install_binary(args, binary, dest_dir):
435+
# Helper function that installs a single built artifact to a particular directory. The source may be either a file or a directory.
436+
def install_binary(args, binary, dest_dir, ignored_patterns=[]):
460437
src = os.path.join(args.bin_dir, binary)
461438
dest = os.path.join(dest_dir, binary)
462439

463440
note("Installing %s to %s" % (src, dest))
464-
465441
mkdir_p(os.path.dirname(dest))
466-
if os.path.isdir(src) and args.cross_compile_hosts: # Handle swiftmodule directories if compiling for multiple arches.
467-
dir_util.copy_tree(src, dest)
442+
if os.path.isdir(src):
443+
shutil.copytree(src, dest, ignore=shutil.ignore_patterns(*ignored_patterns))
468444
else:
469-
file_util.copy_file(src, dest, update=1)
445+
shutil.copy2(src, dest)
470446

471447
# -----------------------------------------------------------
472448
# Build functions
@@ -653,6 +629,7 @@ def build_swiftpm_with_swiftpm(args, integrated_swift_driver):
653629
if integrated_swift_driver:
654630
swiftpm_args.append("--use-integrated-swift-driver")
655631

632+
# Build SwiftPM, including libSwiftPM, all the command line tools, and the current variant of PackageDescription.
656633
call_swiftpm(args, swiftpm_args)
657634

658635
# Setup symlinks that'll allow using swiftpm from the build directory.
@@ -778,7 +755,7 @@ def get_swiftpm_flags(args):
778755
swift_library_rpath_prefix = "$ORIGIN/../"
779756
platform_path = None
780757
for path in args.target_info["paths"]["runtimeLibraryPaths"]:
781-
platform_path = re.search(r"(lib/swift/[^/]+)$", path)
758+
platform_path = re.search(r"(lib/swift/([^/]+))$", path)
782759
if platform_path:
783760
build_flags.extend(
784761
[
@@ -788,6 +765,15 @@ def get_swiftpm_flags(args):
788765
swift_library_rpath_prefix + platform_path.group(1),
789766
]
790767
)
768+
if platform.system() == 'Linux':
769+
build_flags.extend(
770+
[
771+
"-Xlinker",
772+
"-rpath",
773+
"-Xlinker",
774+
swift_library_rpath_prefix + '../' + platform_path.group(2),
775+
]
776+
)
791777
break
792778

793779
if not platform_path:

0 commit comments

Comments
 (0)