Skip to content

Commit 1ccff62

Browse files
committed
[Build] Add code generation for the resources bundle accessor
<rdar://problem/56585517>
1 parent 6d42d95 commit 1ccff62

File tree

12 files changed

+151
-11
lines changed

12 files changed

+151
-11
lines changed

Sources/Build/BuildDelegate.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import TSCBasic
1212
import TSCUtility
1313
import SPMLLBuild
14+
import PackageModel
1415
import Dispatch
1516
import Foundation
1617

Sources/Build/BuildPlan.swift

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -497,13 +497,20 @@ public final class SwiftTargetBuildDescription {
497497
let buildParameters: BuildParameters
498498

499499
/// Path to the temporary directory for this target.
500-
var tempsPath: AbsolutePath {
501-
return buildParameters.buildPath.appending(component: target.c99name + ".build")
502-
}
500+
let tempsPath: AbsolutePath
501+
502+
/// The directory containing derived sources of this target.
503+
///
504+
/// These are the source files generated during the build.
505+
private var derivedSources: Sources
506+
507+
/// The list of all source files in the target, including the derived ones.
508+
var sources: [AbsolutePath] { target.sources.paths + derivedSources.paths }
503509

504510
/// The objects in this target.
505511
var objects: [AbsolutePath] {
506-
return target.sources.relativePaths.map({ tempsPath.appending(RelativePath("\($0.pathString).o")) })
512+
let relativePaths = target.sources.relativePaths + derivedSources.relativePaths
513+
return relativePaths.map{ tempsPath.appending(RelativePath("\($0.pathString).o")) }
507514
}
508515

509516
/// The path to the swiftmodule file after compilation.
@@ -558,10 +565,46 @@ public final class SwiftTargetBuildDescription {
558565
self.isTestTarget = isTestTarget ?? (target.type == .test)
559566
self.testDiscoveryTarget = testDiscoveryTarget
560567
self.fs = fs
568+
self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build")
569+
self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources"))
561570

562571
if shouldEmitObjCCompatibilityHeader {
563572
self.moduleMap = try self.generateModuleMap()
564573
}
574+
575+
try self.generateResourceAccessor()
576+
}
577+
578+
/// Generate the resource bundle accessor, if appropriate.
579+
private func generateResourceAccessor() throws {
580+
// Do nothing if we're not generating a bundle.
581+
guard let bundleName = target.underlyingTarget.bundleName else { return }
582+
583+
// Compute the basename of the bundle.
584+
let bundleBasename = bundleName + buildParameters.triple.nsbundleExtension
585+
586+
let stream = BufferedOutputByteStream()
587+
stream <<< """
588+
import class Foundation.Bundle
589+
590+
extension Foundation.Bundle {
591+
static var moduleResources: Bundle = {
592+
return Bundle(path: Bundle.main.bundlePath + "/" + "\(bundleBasename)")!
593+
}()
594+
}
595+
"""
596+
597+
let subpath = RelativePath("resource_bundle_accessor.swift")
598+
599+
// Add the file to the dervied sources.
600+
derivedSources.relativePaths.append(subpath)
601+
602+
// Write this file out.
603+
// FIXME: We should generate this file during the actual build.
604+
let path = derivedSources.root.appending(subpath)
605+
606+
try fs.createDirectory(path.parentDirectory, recursive: true)
607+
try fs.writeFileContents(path, bytes: stream.bytes)
565608
}
566609

567610
/// The arguments needed to compile this target.

Sources/Build/ToolProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ struct SwiftCompilerTool: ToolProtocol {
169169
stream <<< " other-args: "
170170
<<< Format.asJSON(target.compileArguments()) <<< "\n"
171171
stream <<< " sources: "
172-
<<< Format.asJSON(target.target.sources.paths.map{$0.pathString}) <<< "\n"
172+
<<< Format.asJSON(target.sources.map{$0.pathString}) <<< "\n"
173173
stream <<< " is-library: "
174174
<<< Format.asJSON(target.target.type == .library || target.target.type == .test) <<< "\n"
175175
stream <<< " enable-whole-module-optimization: "

Sources/Build/Triple.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,15 @@ extension Triple {
164164
return ".dll"
165165
}
166166
}
167+
168+
/// The file extension for Foundation-style bundle.
169+
public var nsbundleExtension: String {
170+
switch os {
171+
case .darwin, .macOS:
172+
return ".bundle"
173+
default:
174+
// See: https://github.com/apple/swift-corelibs-foundation/blob/master/Docs/FHS%20Bundles.md
175+
return ".resources"
176+
}
177+
}
167178
}

Sources/Build/llbuild.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ public final class LLBuildManifestGenerator {
240240
private func createSwiftCompileTarget(_ target: SwiftTargetBuildDescription) -> Target {
241241
// Compute inital inputs.
242242
var inputs = SortedArray<String>()
243-
inputs += target.target.sources.paths.map({ $0.pathString })
243+
inputs += target.sources.map{ $0.pathString }
244244

245245
func addStaticTargetInputs(_ target: ResolvedTarget) {
246246
// Ignore C Modules.

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,9 +724,19 @@ public final class PackageBuilder {
724724
guard !swiftSources.isEmpty else { return nil }
725725
let swiftSources = Array(swiftSources)
726726
try validateSourcesOverlapping(forTarget: potentialModule.name, sources: swiftSources)
727+
728+
// The name of the bundle, if one is being generated.
729+
var bundleName: String?
730+
// FIXME: This needs to depend on if we have *any* resources, not just explicitly
731+
// declared ones.
732+
if manifestTarget?.resources.isEmpty == false {
733+
bundleName = manifest.name + "_" + potentialModule.name
734+
}
735+
727736
// No C sources, so we expect to have Swift sources, and we create a Swift target.
728737
return SwiftTarget(
729738
name: potentialModule.name,
739+
bundleName: bundleName,
730740
platforms: self.platforms(),
731741
isTest: potentialModule.isTest,
732742
sources: Sources(paths: swiftSources, root: potentialModule.path),

Sources/PackageModel/Manifest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ public struct TargetDescription: Equatable, Codable {
238238
public let sources: [String]?
239239

240240
/// The explicitly declared resources of the target.
241-
public let resources: [Resource]?
241+
public let resources: [Resource]
242242

243243
/// The exclude patterns.
244244
public let exclude: [String]
@@ -274,7 +274,7 @@ public struct TargetDescription: Equatable, Codable {
274274
path: String? = nil,
275275
exclude: [String] = [],
276276
sources: [String]? = nil,
277-
resources: [Resource]? = nil,
277+
resources: [Resource] = [],
278278
publicHeadersPath: String? = nil,
279279
type: TargetType = .regular,
280280
pkgConfig: String? = nil,

Sources/PackageModel/Sources.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ import TSCUtility
1313

1414
/// A grouping of related source files.
1515
public struct Sources {
16-
public let relativePaths: [RelativePath]
16+
/// The root of the sources.
1717
public let root: AbsolutePath
1818

19+
/// The subpaths within the root.
20+
public var relativePaths: [RelativePath]
21+
22+
/// The list of absolute paths of all files.
1923
public var paths: [AbsolutePath] {
2024
return relativePaths.map({ root.appending($0) })
2125
}

Sources/PackageModel/Target.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public class Target: ObjectIdentifierProtocol {
3434
/// The language-level target name.
3535
public let c99name: String
3636

37+
/// The bundle name, if one is being generated.
38+
public let bundleName: String?
39+
3740
/// Suffix that's expected for test targets.
3841
public static let testModuleNameSuffix = "Tests"
3942

@@ -56,6 +59,7 @@ public class Target: ObjectIdentifierProtocol {
5659

5760
fileprivate init(
5861
name: String,
62+
bundleName: String? = nil,
5963
platforms: [SupportedPlatform],
6064
type: Kind,
6165
sources: Sources,
@@ -64,6 +68,7 @@ public class Target: ObjectIdentifierProtocol {
6468
buildSettings: BuildSettings.AssignmentTable
6569
) {
6670
self.name = name
71+
self.bundleName = bundleName
6772
self.platforms = platforms
6873
self.type = type
6974
self.sources = sources
@@ -127,6 +132,7 @@ public class SwiftTarget: Target {
127132

128133
public init(
129134
name: String,
135+
bundleName: String? = nil,
130136
platforms: [SupportedPlatform] = [],
131137
isTest: Bool = false,
132138
sources: Sources,
@@ -139,6 +145,7 @@ public class SwiftTarget: Target {
139145
self.swiftVersion = swiftVersion
140146
super.init(
141147
name: name,
148+
bundleName: bundleName,
142149
platforms: platforms,
143150
type: type,
144151
sources: sources,

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,6 +1749,69 @@ final class BuildPlanTests: XCTestCase {
17491749
"""))
17501750
}
17511751
}
1752+
1753+
func testSwiftBundleAccessor() throws {
1754+
// This has a Swift and ObjC target in the same package.
1755+
let fs = InMemoryFileSystem(emptyFiles:
1756+
"/PkgA/Sources/Foo/Foo.swift",
1757+
"/PkgA/Sources/Foo/foo.txt",
1758+
"/PkgA/Sources/Foo/bar.txt",
1759+
"/PkgA/Sources/Bar/Bar.swift"
1760+
)
1761+
1762+
let diagnostics = DiagnosticsEngine()
1763+
1764+
let graph = loadPackageGraph(
1765+
root: "/PkgA",
1766+
fs: fs,
1767+
diagnostics: diagnostics,
1768+
manifests: [
1769+
Manifest.createManifest(
1770+
name: "PkgA",
1771+
path: "/PkgA",
1772+
url: "/PkgA",
1773+
v: .v5_2,
1774+
targets: [
1775+
TargetDescription(
1776+
name: "Foo",
1777+
resources: [
1778+
.init(rule: .copy, path: "foo.txt"),
1779+
.init(rule: .process, path: "bar.txt"),
1780+
]
1781+
),
1782+
TargetDescription(
1783+
name: "Bar"
1784+
),
1785+
]
1786+
)
1787+
]
1788+
)
1789+
1790+
XCTAssertNoDiagnostics(diagnostics)
1791+
1792+
let plan = try BuildPlan(
1793+
buildParameters: mockBuildParameters(),
1794+
graph: graph,
1795+
diagnostics: diagnostics,
1796+
fileSystem: fs
1797+
)
1798+
let result = BuildPlanResult(plan: plan)
1799+
1800+
let fooTarget = try result.target(for: "Foo").swiftTarget()
1801+
XCTAssertEqual(fooTarget.objects.map{ $0.pathString }, [
1802+
"/path/to/build/debug/Foo.build/Foo.swift.o",
1803+
"/path/to/build/debug/Foo.build/resource_bundle_accessor.swift.o"
1804+
])
1805+
1806+
let resourceAccessor = fooTarget.sources.first{ $0.basename == "resource_bundle_accessor.swift" }!
1807+
let contents = try fs.readFileContents(resourceAccessor).cString
1808+
XCTAssertTrue(contents.contains("extension Foundation.Bundle"), contents)
1809+
1810+
let barTarget = try result.target(for: "Bar").swiftTarget()
1811+
XCTAssertEqual(barTarget.objects.map{ $0.pathString }, [
1812+
"/path/to/build/debug/Bar.build/Bar.swift.o",
1813+
])
1814+
}
17521815
}
17531816

17541817
// MARK:- Test Helpers

Tests/BuildTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ extension BuildPlanTests {
3030
("testPlatforms", testPlatforms),
3131
("testPlatformsValidation", testPlatformsValidation),
3232
("testREPLArguments", testREPLArguments),
33+
("testSwiftBundleAccessor", testSwiftBundleAccessor),
3334
("testSwiftCAsmMixed", testSwiftCAsmMixed),
3435
("testSwiftCMixed", testSwiftCMixed),
3536
("testSystemPackageBuildPlan", testSystemPackageBuildPlan),

Tests/PackageLoadingTests/PD5LoadingTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,8 @@ class PackageDescription5LoadingTests: XCTestCase {
450450

451451
loadManifest(stream.bytes, manifestVersion: .v5_2) { manifest in
452452
let resources = manifest.targets[0].resources
453-
XCTAssertEqual(resources?[0], TargetDescription.Resource(rule: .copy, path: "foo.txt"))
454-
XCTAssertEqual(resources?[1], TargetDescription.Resource(rule: .process, path: "bar.txt"))
453+
XCTAssertEqual(resources[0], TargetDescription.Resource(rule: .copy, path: "foo.txt"))
454+
XCTAssertEqual(resources[1], TargetDescription.Resource(rule: .process, path: "bar.txt"))
455455
}
456456
}
457457
}

0 commit comments

Comments
 (0)