Skip to content

[5.5] Build PackageDescription and PackagePlugin universal on macOS platform #3536

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Fixtures/Miscellaneous/LibraryEvolution/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// swift-tools-version:5.1
import PackageDescription

let package = Package(
name: "LibraryEvolution",
products: [
],
targets: [
.target(name: "A", dependencies: [], swiftSettings: [.unsafeFlags(["-enable-library-evolution"])]),
.target(name: "B", dependencies: ["A"], swiftSettings: [.unsafeFlags(["-enable-library-evolution"])]),
])
Empty file.
Empty file.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ let package = Package(
name: "PackageDescription",
swiftSettings: [
.unsafeFlags(["-package-description-version", "999.0"]),
.unsafeFlags(["-enable-library-evolution"])
.unsafeFlags(["-enable-library-evolution"], .when(platforms: [.macOS]))
]),

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

// MARK: SwiftPM specific support libraries
Expand Down
10 changes: 5 additions & 5 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -778,14 +778,14 @@ public final class SwiftTargetBuildDescription {
args += ["-color-diagnostics"]
}

// Add the output for the `.swiftinterface`, if requested.
if buildParameters.enableParseableModuleInterfaces {
args += ["-emit-parseable-module-interface-path", parseableModuleInterfaceOutputPath.pathString]
}

// Add agruments from declared build settings.
args += self.buildSettingsFlags()

// Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other way.
if buildParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") {
args += ["-emit-module-interface-path", parseableModuleInterfaceOutputPath.pathString]
}

// User arguments (from -Xswiftc) should follow generated arguments to allow user overrides
args += buildParameters.swiftCompilerFlags
return args
Expand Down
4 changes: 2 additions & 2 deletions Sources/PackageDescription/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ add_library(PackageDescription

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

if(CMAKE_HOST_SYSTEM_NAME STREQUAL Darwin)
target_compile_options(PackageDescription PUBLIC
$<$<COMPILE_LANGUAGE:Swift>:-enable-library-evolution>)
set(SWIFT_INTERFACE_PATH ${CMAKE_BINARY_DIR}/pm/ManifestAPI/PackageDescription.swiftinterface)
target_compile_options(PackageDescription PUBLIC
$<$<COMPILE_LANGUAGE:Swift>:-emit-module-interface-path$<SEMICOLON>${SWIFT_INTERFACE_PATH}>)
Expand Down
4 changes: 2 additions & 2 deletions Sources/PackagePlugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ add_library(PackagePlugin

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

if(CMAKE_HOST_SYSTEM_NAME STREQUAL Darwin)
target_compile_options(PackagePlugin PUBLIC
$<$<COMPILE_LANGUAGE:Swift>:-enable-library-evolution>)
set(SWIFT_INTERFACE_PATH ${CMAKE_BINARY_DIR}/pm/PluginAPI/PackagePlugin.swiftinterface)
target_compile_options(PackagePlugin PUBLIC
$<$<COMPILE_LANGUAGE:Swift>:-emit-module-interface-path$<SEMICOLON>${SWIFT_INTERFACE_PATH}>)
Expand Down
1 change: 1 addition & 0 deletions Sources/XCBuildSupport/PIF.swift
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,7 @@ public enum PIF {
case WATCHOS_DEPLOYMENT_TARGET
case MARKETING_VERSION
case CURRENT_PROJECT_VERSION
case SWIFT_EMIT_MODULE_INTERFACE
}

public enum MultipleValueSetting: String, Codable {
Expand Down
19 changes: 19 additions & 0 deletions Sources/XCBuildSupport/PIFBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,21 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {

return bundleName
}

// Add inferred build settings for a particular value for a manifest setting and value.
private func addInferredBuildSettings(
for setting: PIF.BuildSettings.MultipleValueSetting,
value: [String],
platform: PIF.BuildSettings.Platform? = nil,
configuration: BuildConfiguration,
settings: inout PIF.BuildSettings
) {
// Automatically set SWIFT_EMIT_MODULE_INTERFACE if the package author uses unsafe flags to enable
// library evolution (this is needed until there is a way to specify this in the package manifest).
if setting == .OTHER_SWIFT_FLAGS && value.contains("-enable-library-evolution") {
settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES"
}
}

// Apply target-specific build settings defined in the manifest.
private func addManifestBuildSettings(
Expand All @@ -833,8 +848,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
switch configuration {
case .debug:
debugSettings[setting, for: platform, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .debug, settings: &debugSettings)
case .release:
releaseSettings[setting, for: platform, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .release, settings: &releaseSettings)
}
}

Expand All @@ -847,8 +864,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
switch configuration {
case .debug:
debugSettings[setting, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, configuration: .debug, settings: &debugSettings)
case .release:
releaseSettings[setting, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, configuration: .release, settings: &releaseSettings)
}
}

Expand Down
12 changes: 12 additions & 0 deletions Tests/CommandsTests/BuildToolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@ final class BuildToolTests: XCTestCase {
}
}

func testAutomaticParseableInterfacesWithLibraryEvolution() {
fixture(name: "Miscellaneous/LibraryEvolution") { path in
do {
let result = try build([], packagePath: path)
XCTAssert(result.binContents.contains("A.swiftinterface"))
XCTAssert(result.binContents.contains("B.swiftinterface"))
} catch SwiftPMProductError.executionFailure(_, _, let stderr) {
XCTFail(stderr)
}
}
}

func testBuildCompleteMessage() {
fixture(name: "DependencyResolution/Internal/Simple") { path in
do {
Expand Down
62 changes: 62 additions & 0 deletions Tests/XCBuildSupportTests/PIFBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,68 @@ class PIFBuilderTests: XCTestCase {
}
}
}

/// Tests that the inference of XCBuild build settings based on the package manifest's declared unsafe settings
/// works as expected.
func testUnsafeFlagsBuildSettingInference() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/MyLib/Sources/MyLib/Foo.swift"
)

let diagnostics = DiagnosticsEngine()
let graph = try loadPackageGraph(
fs: fs,
diagnostics: diagnostics,
manifests: [
Manifest.createManifest(
name: "MyLib",
path: "/MyLib",
packageKind: .root,
packageLocation: "/MyLib",
v: .v5,
products: [
.init(name: "MyLib", type: .library(.automatic), targets: ["MyLib"]),
],
targets: [
.init(name: "MyLib", settings: [
.init(
tool: .swift,
name: .unsafeFlags,
value: ["-enable-library-evolution"],
condition: .init(config: "release")),
]),
]),
],
shouldCreateMultipleTestProducts: true
)

let builder = PIFBuilder(graph: graph, parameters: .mock(), diagnostics: diagnostics)
let pif = try builder.construct()

XCTAssertNoDiagnostics(diagnostics)

PIFTester(pif) { workspace in
workspace.checkProject("PACKAGE:/MyLib") { project in
project.checkTarget("PACKAGE-TARGET:MyLib") { target in
target.checkBuildConfiguration("Debug") { configuration in
configuration.checkBuildSettings { settings in
// Check that the `-enable-library-evolution` setting for Release didn't affect Debug.
XCTAssertEqual(settings[.SWIFT_EMIT_MODULE_INTERFACE], nil)
XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil)
}
}
target.checkBuildConfiguration("Release") { configuration in
configuration.checkBuildSettings { settings in
// Check that the `-enable-library-evolution` setting for Release also set SWIFT_EMIT_MODULE_INTERFACE.
XCTAssertEqual(settings[.SWIFT_EMIT_MODULE_INTERFACE], "YES")
XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], ["$(inherited)", "-enable-library-evolution"])
}
}
}
}
}
}

#endif
}

Expand Down
96 changes: 42 additions & 54 deletions Utilities/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ from helpers import note, error, symlink_force, mkdir_p, call, call_output

g_macos_deployment_target = '10.15'

g_shared_lib_prefix = "lib"
if platform.system() == 'Darwin':
g_shared_lib_ext = ".dylib"
g_shared_lib_suffix = ".dylib"
else:
g_shared_lib_ext = ".so"
g_shared_lib_suffix = ".so"

def main():
parser = argparse.ArgumentParser(description="""
Expand Down Expand Up @@ -366,7 +367,7 @@ def install(args):
"PackageGraph", "SPMBuildCore", "Build",
"Xcodeproj", "Workspace"
]
install_libswiftpm_dylib(args, "SwiftPM", args.libswiftpm_install_dir, libswiftpm_modules)
install_dylib(args, "SwiftPM", args.libswiftpm_install_dir, libswiftpm_modules)

# Install libSwiftPMDataModel if an install directory was provided.
if args.libswiftpmdatamodel_install_dir:
Expand All @@ -377,82 +378,59 @@ def install(args):
"PackageGraph", "SPMBuildCore",
"Xcodeproj", "Workspace"
]
install_libswiftpm_dylib(args, "SwiftPMDataModel", args.libswiftpmdatamodel_install_dir, libswiftpmdatamodel_modules)
install_dylib(args, "SwiftPMDataModel", args.libswiftpmdatamodel_install_dir, libswiftpmdatamodel_modules)

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

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

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

files_to_install = ["libPackageDescription" + g_shared_lib_ext]
if platform.system() == 'Darwin':
files_to_install.append("PackageDescription.swiftinterface")
else:
files_to_install.append("PackageDescription.swiftmodule")
files_to_install.append("PackageDescription.swiftdoc")

for file in files_to_install:
src = os.path.join(runtime_lib_src, "ManifestAPI", file)
dest = os.path.join(runtime_lib_dest, "ManifestAPI", file)
mkdir_p(os.path.dirname(dest))

note("Installing %s to %s" % (src, dest))

file_util.copy_file(src, dest, update=1)

files_to_install = ["libPackagePlugin" + g_shared_lib_ext]
if platform.system() == 'Darwin':
files_to_install.append("PackagePlugin.swiftinterface")
else:
files_to_install.append("PackagePlugin.swiftmodule")
files_to_install.append("PackagePlugin.swiftdoc")

for file in files_to_install:
src = os.path.join(runtime_lib_src, "PluginAPI", file)
dest = os.path.join(runtime_lib_dest, "PluginAPI", file)
mkdir_p(os.path.dirname(dest))

note("Installing %s to %s" % (src, dest))

file_util.copy_file(src, dest, update=1)
# Install the PackagePlugin library and associated modules.
dest = os.path.join(prefix, "lib", "swift", "pm", "PluginAPI")
install_dylib(args, "PackagePlugin", dest, ["PackagePlugin"])


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

# Install the swiftmodule and swiftdoc files.
# Install the swiftmodule/swiftinterface and swiftdoc files for all the modules.
for module in module_names:
install_binary(args, module + ".swiftmodule", install_dir)
if not args.cross_compile_hosts: # When compiling for multiple arches, swiftdoc is part of the swiftmodule directory
# If we're cross-compiling, we expect the .swiftmodule to be a directory that contains everything.
if args.cross_compile_hosts:
install_binary(args, module + ".swiftmodule", install_dir)
else:
# Otherwise we have either a .swiftinterface or a .swiftmodule, plus a .swiftdoc.
if os.path.exists(os.path.join(args.bin_dir, module + ".swiftinterface")):
install_binary(args, module + ".swiftinterface", install_dir)
else:
install_binary(args, module + ".swiftmodule", install_dir)
install_binary(args, module + ".swiftdoc", install_dir)

# Install the C headers.
tscclibc_include_dir = os.path.join(args.tsc_source_dir, "Sources/TSCclibc/include")
tscclibc_include_dir_dest = os.path.join(install_dir, "TSCclibc")
dir_util.copy_tree(tscclibc_include_dir, tscclibc_include_dir_dest)


# Helper function that installs a single built artifact to a particular directory. The source may be either a file or a directory.
def install_binary(args, binary, dest_dir):
src = os.path.join(args.bin_dir, binary)
dest = os.path.join(dest_dir, binary)

note("Installing %s to %s" % (src, dest))

mkdir_p(os.path.dirname(dest))
if os.path.isdir(src) and args.cross_compile_hosts: # Handle swiftmodule directories if compiling for multiple arches.
dir_util.copy_tree(src, dest)
if os.path.isdir(src):
dir_util.copy_tree(src, dest, update=1, verbose=1)
else:
file_util.copy_file(src, dest, update=1)
file_util.copy_file(src, dest, update=1, verbose=1)

# -----------------------------------------------------------
# Build functions
Expand Down Expand Up @@ -647,6 +625,7 @@ def build_swiftpm_with_swiftpm(args, integrated_swift_driver):
if integrated_swift_driver:
swiftpm_args.append("--use-integrated-swift-driver")

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

# Setup symlinks that'll allow using swiftpm from the build directory.
Expand Down Expand Up @@ -772,7 +751,7 @@ def get_swiftpm_flags(args):
swift_library_rpath_prefix = "$ORIGIN/../"
platform_path = None
for path in args.target_info["paths"]["runtimeLibraryPaths"]:
platform_path = re.search(r"(lib/swift/[^/]+)$", path)
platform_path = re.search(r"(lib/swift/([^/]+))$", path)
if platform_path:
build_flags.extend(
[
Expand All @@ -782,6 +761,15 @@ def get_swiftpm_flags(args):
swift_library_rpath_prefix + platform_path.group(1),
]
)
if platform.system() == 'Linux':
build_flags.extend(
[
"-Xlinker",
"-rpath",
"-Xlinker",
swift_library_rpath_prefix + '../' + platform_path.group(2),
]
)
break

if not platform_path:
Expand Down