diff --git a/Fixtures/Resources/Simple/Package.swift b/Fixtures/Resources/Simple/Package.swift index 67e874e9d63..322a30d23ad 100644 --- a/Fixtures/Resources/Simple/Package.swift +++ b/Fixtures/Resources/Simple/Package.swift @@ -24,5 +24,12 @@ let package = Package( .copy("foo.txt"), ] ), + + .target( + name: "MixedClangResource", + resources: [ + .copy("foo.txt"), + ] + ), ] ) diff --git a/Fixtures/Resources/Simple/Sources/MixedClangResource/Foo.m b/Fixtures/Resources/Simple/Sources/MixedClangResource/Foo.m new file mode 100644 index 00000000000..ad40e2f5199 --- /dev/null +++ b/Fixtures/Resources/Simple/Sources/MixedClangResource/Foo.m @@ -0,0 +1,6 @@ +#import + +#import "Foo.h" + +@implementation Foo +@end diff --git a/Fixtures/Resources/Simple/Sources/MixedClangResource/bar.c b/Fixtures/Resources/Simple/Sources/MixedClangResource/bar.c new file mode 100644 index 00000000000..6a403234f44 --- /dev/null +++ b/Fixtures/Resources/Simple/Sources/MixedClangResource/bar.c @@ -0,0 +1 @@ +#include "bar.h" diff --git a/Fixtures/Resources/Simple/Sources/MixedClangResource/bar.h b/Fixtures/Resources/Simple/Sources/MixedClangResource/bar.h new file mode 100644 index 00000000000..34c6822ef36 --- /dev/null +++ b/Fixtures/Resources/Simple/Sources/MixedClangResource/bar.h @@ -0,0 +1,6 @@ +#ifndef foo_h +#define foo_h + +#include + +#endif /* foo_h */ diff --git a/Fixtures/Resources/Simple/Sources/MixedClangResource/foo.txt b/Fixtures/Resources/Simple/Sources/MixedClangResource/foo.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Fixtures/Resources/Simple/Sources/MixedClangResource/include/Foo.h b/Fixtures/Resources/Simple/Sources/MixedClangResource/include/Foo.h new file mode 100644 index 00000000000..f080770e048 --- /dev/null +++ b/Fixtures/Resources/Simple/Sources/MixedClangResource/include/Foo.h @@ -0,0 +1,4 @@ +#import + +@interface Foo : NSObject +@end diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index f00b131dd11..a077fd0ca8a 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -356,7 +356,10 @@ public final class ClangTargetBuildDescription { /// Builds up basic compilation arguments for a source file in this target; these arguments may be different for C++ vs non-C++. /// NOTE: The parameter to specify whether to get C++ semantics is currently optional, but this is only for revlock avoidance with clients. Callers should always specify what they want based either the user's indication or on a default value (possibly based on the filename suffix). - public func basicArguments(isCXX isCXXOverride: Bool? = .none) throws -> [String] { + public func basicArguments( + isCXX isCXXOverride: Bool? = .none, + isC: Bool = false + ) throws -> [String] { // For now fall back on the hold semantics if the C++ nature isn't specified. This is temporary until clients have been updated. let isCXX = isCXXOverride ?? clangTarget.isCXX @@ -419,7 +422,10 @@ public final class ClangTargetBuildDescription { // Add arguments from declared build settings. args += try self.buildSettingsFlags() - if let resourceAccessorHeaderFile = self.resourceAccessorHeaderFile { + // Include the path to the resource header unless the arguments are + // being evaluated for a C file. A C file cannot depend on the resource + // accessor header due to it exporting a Foundation type (`NSBundle`). + if let resourceAccessorHeaderFile = self.resourceAccessorHeaderFile, !isC { args += ["-include", resourceAccessorHeaderFile.pathString] } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 83530e24bae..ff269c6d519 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -769,7 +769,9 @@ extension LLBuildManifestBuilder { for path in try target.compilePaths() { let isCXX = path.source.extension.map{ SupportedLanguageExtension.cppExtensions.contains($0) } ?? false - var args = try target.basicArguments(isCXX: isCXX) + let isC = path.source.extension.map { $0 == SupportedLanguageExtension.c.rawValue } ?? false + + var args = try target.basicArguments(isCXX: isCXX, isC: isC) args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString] diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index c647e471c1a..d08f8186319 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -3952,6 +3952,81 @@ final class BuildPlanTests: XCTestCase { ]) } + func testClangBundleAccessor() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Pkg/Sources/Foo/include/Foo.h", + "/Pkg/Sources/Foo/Foo.m", + "/Pkg/Sources/Foo/bar.h", + "/Pkg/Sources/Foo/bar.c", + "/Pkg/Sources/Foo/resource.txt" + ) + + let observability = ObservabilitySystem.makeForTesting() + + let graph = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + name: "Pkg", + path: .init(path: "/Pkg"), + toolsVersion: .current, + targets: [ + TargetDescription( + name: "Foo", + resources: [ + .init( + rule: .process(localization: .none), + path: "resource.txt" + ) + ] + ) + ] + ) + ], + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + + let plan = try BuildPlan( + buildParameters: mockBuildParameters(), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + let result = try BuildPlanResult(plan: plan) + + let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(component: "debug") + + let fooTarget = try result.target(for: "Foo").clangTarget() + XCTAssertEqual(try fooTarget.objects.map(\.pathString).sorted(), [ + buildPath.appending(components: "Foo.build", "Foo.m.o").pathString, + buildPath.appending(components: "Foo.build", "bar.c.o").pathString, + buildPath.appending(components: "Foo.build", "resource_bundle_accessor.m.o").pathString + ].sorted()) + + let resourceAccessorDirectory = buildPath.appending(components: + "Foo.build", + "DerivedSources" + ) + + let resourceAccessorHeader = resourceAccessorDirectory + .appending(component: "resource_bundle_accessor.h") + let headerContents: String = try fs.readFileContents(resourceAccessorHeader) + XCTAssertMatch( + headerContents, + .contains("#define SWIFTPM_MODULE_BUNDLE Foo_SWIFTPM_MODULE_BUNDLE()") + ) + + let resourceAccessorImpl = resourceAccessorDirectory + .appending(component: "resource_bundle_accessor.m") + let implContents: String = try fs.readFileContents(resourceAccessorImpl) + XCTAssertMatch( + implContents, + .contains("NSBundle* Foo_SWIFTPM_MODULE_BUNDLE() {") + ) + } + func testShouldLinkStaticSwiftStdlib() throws { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", diff --git a/Tests/FunctionalTests/ResourcesTests.swift b/Tests/FunctionalTests/ResourcesTests.swift index c34183c2433..e4b96486fb4 100644 --- a/Tests/FunctionalTests/ResourcesTests.swift +++ b/Tests/FunctionalTests/ResourcesTests.swift @@ -49,6 +49,17 @@ class ResourcesTests: XCTestCase { } } + func testResourcesInMixedClangPackage() throws { + #if !os(macOS) + // Running swift-test fixtures on linux is not yet possible. + try XCTSkipIf(true, "test is only supported on macOS") + #endif + + try fixture(name: "Resources/Simple") { fixturePath in + XCTAssertBuilds(fixturePath, extraArgs: ["--target", "MixedClangResource"]) + } + } + func testMovedBinaryResources() throws { try fixture(name: "Resources/Moved") { fixturePath in var executables = ["SwiftyResource"]