diff --git a/Fixtures/Miscellaneous/TestableExe/Package.swift b/Fixtures/Miscellaneous/TestableExe/Package.swift new file mode 100644 index 00000000000..49c3074c0bf --- /dev/null +++ b/Fixtures/Miscellaneous/TestableExe/Package.swift @@ -0,0 +1,25 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "TestableExe", + targets: [ + .target( + name: "TestableExe1" + ), + .target( + name: "TestableExe2" + ), + .target( + name: "TestableExe3" + ), + .testTarget( + name: "TestableExeTests", + dependencies: [ + "TestableExe1", + "TestableExe2", + "TestableExe3", + ] + ), + ] +) diff --git a/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe1/main.swift b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe1/main.swift new file mode 100644 index 00000000000..f972db5a3d9 --- /dev/null +++ b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe1/main.swift @@ -0,0 +1,5 @@ +public func GetGreeting1() -> String { + return "Hello, world" +} + +print("\(GetGreeting1())!") diff --git a/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe2/main.swift b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe2/main.swift new file mode 100644 index 00000000000..dc78ad4440f --- /dev/null +++ b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe2/main.swift @@ -0,0 +1,5 @@ +public func GetGreeting2() -> String { + return "Hello, planet" +} + +print("\(GetGreeting2())!") diff --git a/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe3/include/TestableExe3.h b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe3/include/TestableExe3.h new file mode 100644 index 00000000000..2dba14234a6 --- /dev/null +++ b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe3/include/TestableExe3.h @@ -0,0 +1 @@ +const char * GetGreeting3(); \ No newline at end of file diff --git a/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe3/main.c b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe3/main.c new file mode 100644 index 00000000000..00cb979a120 --- /dev/null +++ b/Fixtures/Miscellaneous/TestableExe/Sources/TestableExe3/main.c @@ -0,0 +1,10 @@ +#include +#include "include/TestableExe3.h" + +const char * GetGreeting3() { + return "Hello, universe"; +} + +int main() { + printf("%s!\n", GetGreeting3()); +} diff --git a/Fixtures/Miscellaneous/TestableExe/Tests/TestableExeTests/TestableExeTests.swift b/Fixtures/Miscellaneous/TestableExe/Tests/TestableExeTests/TestableExeTests.swift new file mode 100644 index 00000000000..ee7713648e8 --- /dev/null +++ b/Fixtures/Miscellaneous/TestableExe/Tests/TestableExeTests/TestableExeTests.swift @@ -0,0 +1,73 @@ +import XCTest +import TestableExe1 +import TestableExe2 +// import TestableExe3 +import class Foundation.Bundle + +final class TestableExeTests: XCTestCase { + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + + print(GetGreeting1()) + XCTAssertEqual(GetGreeting1(), "Hello, world") + print(GetGreeting2()) + XCTAssertEqual(GetGreeting2(), "Hello, planet") + // XCTAssertEqual(String(cString: GetGreeting3()), "Hello, universe") + + // Some of the APIs that we use below are available in macOS 10.13 and above. + guard #available(macOS 10.13, *) else { + return + } + + var execPath = productsDirectory.appendingPathComponent("TestableExe1") + var process = Process() + process.executableURL = execPath + var pipe = Pipe() + process.standardOutput = pipe + try process.run() + process.waitUntilExit() + var data = pipe.fileHandleForReading.readDataToEndOfFile() + var output = String(data: data, encoding: .utf8) + XCTAssertEqual(output, "Hello, world!\n") + + execPath = productsDirectory.appendingPathComponent("TestableExe2") + process = Process() + process.executableURL = execPath + pipe = Pipe() + process.standardOutput = pipe + try process.run() + process.waitUntilExit() + data = pipe.fileHandleForReading.readDataToEndOfFile() + output = String(data: data, encoding: .utf8) + XCTAssertEqual(output, "Hello, planet!\n") + + execPath = productsDirectory.appendingPathComponent("TestableExe3") + process = Process() + process.executableURL = execPath + pipe = Pipe() + process.standardOutput = pipe + try process.run() + process.waitUntilExit() + data = pipe.fileHandleForReading.readDataToEndOfFile() + output = String(data: data, encoding: .utf8) + XCTAssertEqual(output, "Hello, universe!\n") + } + + /// Returns path to the built products directory. + var productsDirectory: URL { + #if os(macOS) + for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { + return bundle.bundleURL.deletingLastPathComponent() + } + fatalError("couldn't find the products directory") + #else + return Bundle.main.bundleURL + #endif + } + + static var allTests = [ + ("testExample", testExample), + ] +} diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 5b0931f8f22..3a0b4712870 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -499,8 +499,7 @@ public final class SwiftTargetBuildDescription { /// The path to the swiftmodule file after compilation. var moduleOutputPath: AbsolutePath { - let dirPath = (target.type == .executable) ? tempsPath : buildParameters.buildPath - return dirPath.appending(component: target.c99name + ".swiftmodule") + return buildParameters.buildPath.appending(component: target.c99name + ".swiftmodule") } /// The path to the wrapped swift module which is created using the modulewrap tool. This is required @@ -662,6 +661,14 @@ public final class SwiftTargetBuildDescription { args += buildParameters.sanitizers.compileSwiftFlags() args += ["-parseable-output"] + // If we're compiling the main module of an executable, we rename the `_main` + // entry point to `__main`. This will allow tests to link against + // them without conflicts. When we link the executable we will ask the linker + // to rename the entry point symbol to just `_main` again. + if target.type == .executable && !isTestTarget { + args += ["-Xfrontend", "-entry-point-function-name", "-Xfrontend", "\(target.c99name)_main"] + } + // Only add the build path to the framework search path if there are binary frameworks to link against. if !libraryBinaryPaths.isEmpty { args += ["-F", buildParameters.buildPath.pathString] @@ -1014,7 +1021,7 @@ public final class ProductBuildDescription { return buildParameters.binaryPath(for: product) } - /// The objects in this product. + /// All object files to link into this product. /// // Computed during build planning. public fileprivate(set) var objects = SortedArray() @@ -1133,6 +1140,23 @@ public final class ProductBuildDescription { } } args += ["-emit-executable"] + + // If we're linking an executable whose main module is implemented in Swift, + // we rename the `__main` entry point symbol to `_main` again. + // This is because executable modules implemented in Swift are compiled with + // a main symbol named that way to allow tests to link against it without + // conflicts. If we're using a linker that doesn't support symbol renaming, + // an alternate implementation could use a generated source file with a stub + // implementation of `_main` to call the renamed main symbol. + let execModule = product.executableModule + if execModule.underlyingTarget is SwiftTarget { + if buildParameters.triple.isDarwin() { + args += ["-Xlinker", "-alias", "-Xlinker", "_\(execModule.c99name)_main", "-Xlinker", "_main"] + } + else { + args += ["-Xlinker", "--defsym", "-Xlinker", "main=\(execModule.c99name)_main"] + } + } case .plugin: throw InternalError("unexpectedly asked to generate linker arguments for a plugin product") } @@ -1615,7 +1639,11 @@ public class BuildPlan { switch target.type { // Include executable and tests only if they're top level contents // of the product. Otherwise they are just build time dependency. - case .executable, .test: + case .executable: + if product.targets.contains(target) || (product.type == .test && target.underlyingTarget is SwiftTarget) { + staticTargets.append(target) + } + case .test: if product.targets.contains(target) { staticTargets.append(target) } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 542944da8b4..f2360195aaf 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors + Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information @@ -138,11 +138,12 @@ final class BuildPlanTests: XCTestCase { "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "-alias", "-Xlinker", "_exe_main", "-Xlinker", "_main", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@/path/to/build/debug/exe.product/Objects.LinkFileList", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx", "-target", "x86_64-apple-macosx10.10", "-Xlinker", "-add_ast_path", - "-Xlinker", "/path/to/build/debug/exe.build/exe.swiftmodule", "-Xlinker", "-add_ast_path", + "-Xlinker", "/path/to/build/debug/exe.swiftmodule", "-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/lib.swiftmodule", ] #else @@ -150,6 +151,7 @@ final class BuildPlanTests: XCTestCase { "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/exe", "-module-name", "exe", "-static-stdlib", "-emit-executable", + "-Xlinker", "--defsym", "-Xlinker", "main=exe_main", "-Xlinker", "-rpath=$ORIGIN", "@/path/to/build/debug/exe.product/Objects.LinkFileList", "-target", defaultTargetTriple, @@ -450,6 +452,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ "/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release", "-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "-alias", "-Xlinker", "_exe_main", "-Xlinker", "_main", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@/path/to/build/release/exe.product/Objects.LinkFileList", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx", @@ -459,6 +462,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ "/fake/path/to/swiftc", "-g", "-L", "/path/to/build/release", "-o", "/path/to/build/release/exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "--defsym", "-Xlinker", "main=exe_main", "-Xlinker", "-rpath=$ORIGIN", "@/path/to/build/release/exe.product/Objects.LinkFileList", "-target", defaultTargetTriple, @@ -779,16 +783,18 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "-alias", "-Xlinker", "_exe_main", "-Xlinker", "_main", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@/path/to/build/debug/exe.product/Objects.LinkFileList", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx", "-target", "x86_64-apple-macosx10.10", - "-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/exe.build/exe.swiftmodule", + "-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/exe.swiftmodule", ]) #else XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "--defsym", "-Xlinker", "main=exe_main", "-Xlinker", "-rpath=$ORIGIN", "@/path/to/build/debug/exe.product/Objects.LinkFileList", "-target", defaultTargetTriple, @@ -989,16 +995,18 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "-alias", "-Xlinker", "_exe_main", "-Xlinker", "_main", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@/path/to/build/debug/exe.product/Objects.LinkFileList", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx", "-target", "x86_64-apple-macosx10.10", - "-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/exe.build/exe.swiftmodule", + "-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/exe.swiftmodule", ]) #else XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "--defsym", "-Xlinker", "main=exe_main", "-Xlinker", "-rpath=$ORIGIN", "@/path/to/build/debug/exe.product/Objects.LinkFileList", "-target", defaultTargetTriple, @@ -1086,12 +1094,13 @@ final class BuildPlanTests: XCTestCase { #if os(macOS) XCTAssertEqual(fooLinkArgs, [ "/fake/path/to/swiftc", "-L", "/path/to/build/debug", - "-o", "/path/to/build/debug/Foo", "-module-name", "Foo", "-lBar-Baz", "-emit-executable", - "-Xlinker", "-rpath", "-Xlinker", "@loader_path", + "-o", "/path/to/build/debug/Foo", "-module-name", "Foo", "-lBar-Baz", "-emit-executable", + "-Xlinker", "-alias", "-Xlinker", "_Foo_main", "-Xlinker", "_main", + "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@/path/to/build/debug/Foo.product/Objects.LinkFileList", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift/macosx", "-target", "x86_64-apple-macosx10.10", - "-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/Foo.build/Foo.swiftmodule" + "-Xlinker", "-add_ast_path", "-Xlinker", "/path/to/build/debug/Foo.swiftmodule" ]) XCTAssertEqual(barLinkArgs, [ @@ -1108,6 +1117,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(fooLinkArgs, [ "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/Foo", "-module-name", "Foo", "-lBar-Baz", "-emit-executable", + "-Xlinker", "-alias", "-Xlinker", "_Foo_main", "-Xlinker", "_main", "-Xlinker", "-rpath=$ORIGIN", "@/path/to/build/debug/Foo.product/Objects.LinkFileList", "-target", defaultTargetTriple, @@ -1615,6 +1625,7 @@ final class BuildPlanTests: XCTestCase { "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/exe.exe", "-module-name", "exe", "-emit-executable", + "-Xlinker", "--defsym", "-Xlinker", "main=exe_main", "@/path/to/build/debug/exe.product/Objects.LinkFileList", "-target", "x86_64-unknown-windows-msvc", ]) @@ -1692,6 +1703,7 @@ final class BuildPlanTests: XCTestCase { "/fake/path/to/swiftc", "-L", "/path/to/build/debug", "-o", "/path/to/build/debug/app.wasm", "-module-name", "app", "-static-stdlib", "-emit-executable", + "-Xlinker", "--defsym", "-Xlinker", "main=app_main", "@/path/to/build/debug/app.product/Objects.LinkFileList", "-target", "wasm32-unknown-wasi" ] @@ -2340,10 +2352,10 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(contents, .contains(""" "/path/to/build/debug/exe.build/exe.swiftmodule.o": tool: shell - inputs: ["/path/to/build/debug/exe.build/exe.swiftmodule"] + inputs: ["/path/to/build/debug/exe.swiftmodule"] outputs: ["/path/to/build/debug/exe.build/exe.swiftmodule.o"] description: "Wrapping AST for exe for debugging" - args: ["/fake/path/to/swiftc","-modulewrap","/path/to/build/debug/exe.build/exe.swiftmodule","-o","/path/to/build/debug/exe.build/exe.swiftmodule.o","-target","x86_64-unknown-linux-gnu"] + args: ["/fake/path/to/swiftc","-modulewrap","/path/to/build/debug/exe.swiftmodule","-o","/path/to/build/debug/exe.build/exe.swiftmodule.o","-target","x86_64-unknown-linux-gnu"] """)) XCTAssertMatch(contents, .contains(""" "/path/to/build/debug/lib.build/lib.swiftmodule.o": diff --git a/Tests/FunctionalTests/MiscellaneousTests.swift b/Tests/FunctionalTests/MiscellaneousTests.swift index 4e93e157468..6ddd29edb6b 100644 --- a/Tests/FunctionalTests/MiscellaneousTests.swift +++ b/Tests/FunctionalTests/MiscellaneousTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors + Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information @@ -578,25 +578,15 @@ class MiscellaneousTestCase: XCTestCase { #endif } - func testErrorMessageWhenTestLinksExecutable() { + func testTestsCanLinkAgainstExecutable() { fixture(name: "Miscellaneous/ExeTest") { prefix in do { - try executeSwiftTest(prefix) - XCTFail() - } catch SwiftPMProductError.executionFailure(let error, let output, let stderr) { - XCTAssertMatch(stderr + output, .contains("Compiling Exe main.swift")) - XCTAssertMatch(stderr + output, .contains("Compiling ExeTests ExeTests.swift")) - XCTAssertMatch(stderr + output, .regex("error: no such module 'Exe'")) - XCTAssertMatch(stderr + output, .regex("note: module 'Exe' is the main module of an executable, and cannot be imported by tests and other targets")) - - if case ProcessResult.Error.nonZeroExit(let result) = error { - // if our code crashes we'll get an exit code of 256 - XCTAssertEqual(result.exitStatus, .terminated(code: 1)) - } else { - XCTFail("\(stderr + output)") - } + let (stdout, _) = try executeSwiftTest(prefix) + XCTAssertMatch(stdout, .contains("Compiling Exe main.swift")) + XCTAssertMatch(stdout, .contains("Compiling ExeTests ExeTests.swift")) + XCTAssertMatch(stdout, .contains("Linking ExeTestPackageTests")) } catch { - XCTFail() + XCTFail("\(error)") } } } diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index a35dfae7417..c3988007efe 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift.org open source project - Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors + Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors Licensed under Apache License v2.0 with Runtime Library Exception See http://swift.org/LICENSE.txt for license information @@ -90,7 +90,7 @@ class InitTests: XCTestCase { let triple = Resources.default.toolchain.triple let binPath = path.appending(components: ".build", triple.tripleString, "debug") XCTAssertFileExists(binPath.appending(component: "Foo")) - XCTAssertFileExists(binPath.appending(components: "Foo.build", "Foo.swiftmodule")) + XCTAssertFileExists(binPath.appending(components: "Foo.swiftmodule")) } }