Skip to content

Commit 3448da6

Browse files
authored
Support Swift SDKs w/ many metadata files in same directory (#8638)
### Motivation: Swift SDK artifact bundle for WASI now supports both embedded and non-embedded modes. Majority of their content is overlapping, thus to avoid duplication they live in the same source tree. Currently, SwiftPM requires that Swift SDKs are located in separate directories, with `swift-sdk.json` metadata at the root of each directory. ### Modification: SwiftPM can now parse Swift SDK metadata that points directly to metadata JSON files instead of their root directories. ### Result: We can avoid changing file system layout in the artifact bundles for WASI and just place sibling "nonembedded.json" and "embedded.json" Swift SDK metadata files in the same directory in their existing layout. The change is transparent to Swift SDK users and is an optional and incremental addition for Swift SDK authors.
1 parent 9051756 commit 3448da6

File tree

2 files changed

+85
-12
lines changed

2 files changed

+85
-12
lines changed

Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -370,9 +370,13 @@ public final class SwiftSDKBundleStore {
370370
var variants = [SwiftSDKBundle.Variant]()
371371

372372
for variantMetadata in artifactMetadata.variants {
373-
let variantConfigurationPath = bundlePath
373+
var variantConfigurationPath = bundlePath
374374
.appending(variantMetadata.path)
375-
.appending("swift-sdk.json")
375+
376+
if variantConfigurationPath.extension != ".json" &&
377+
self.fileSystem.isDirectory(variantConfigurationPath) {
378+
variantConfigurationPath = variantConfigurationPath.appending("swift-sdk.json")
379+
}
376380

377381
guard self.fileSystem.exists(variantConfigurationPath) else {
378382
self.observabilityScope.emit(

Tests/PackageModelTests/SwiftSDKBundleTests.swift

+79-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2023-2024 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2023-2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -27,20 +27,26 @@ private let targetTriple = try! Triple("aarch64-unknown-linux")
2727
private let jsonEncoder = JSONEncoder()
2828

2929
private func generateBundleFiles(bundle: MockBundle) throws -> [(String, ByteString)] {
30-
try [
30+
return try [
3131
(
3232
"\(bundle.path)/info.json",
3333
ByteString(json: """
3434
{
3535
"artifacts" : {
3636
\(bundle.artifacts.map {
37-
"""
37+
let path = if let metadataPath = $0.metadataPath {
38+
metadataPath.pathString
39+
} else {
40+
"\($0.id)/\(targetTriple.triple)"
41+
}
42+
43+
return """
3844
"\($0.id)" : {
3945
"type" : "swiftSDK",
4046
"version" : "0.0.1",
4147
"variants" : [
4248
{
43-
"path" : "\($0.id)/\(targetTriple.triple)",
49+
"path" : "\(path)",
4450
"supportedTriples" : \($0.supportedTriples.map(\.tripleString))
4551
}
4652
]
@@ -55,14 +61,25 @@ private func generateBundleFiles(bundle: MockBundle) throws -> [(String, ByteStr
5561
),
5662

5763
] + bundle.artifacts.map {
58-
(
59-
"\(bundle.path)/\($0.id)/\(targetTriple.tripleString)/swift-sdk.json",
64+
let path = if let metadataPath = $0.metadataPath {
65+
"\(bundle.path)/\(metadataPath.pathString)"
66+
} else {
67+
"\(bundle.path)/\($0.id)/\(targetTriple.triple)/swift-sdk.json"
68+
}
69+
70+
return (
71+
path,
6072
ByteString(json: try generateSwiftSDKMetadata(jsonEncoder, createToolset: $0.toolsetRootPath != nil))
6173
)
6274
} + bundle.artifacts.compactMap { artifact in
63-
artifact.toolsetRootPath.map { path in
75+
let toolsetPath = if artifact.metadataPath != nil {
76+
"\(bundle.path)/toolset.json"
77+
} else {
78+
"\(bundle.path)/\(artifact.id)/\(targetTriple.triple)/toolset.json"
79+
}
80+
return artifact.toolsetRootPath.map { path in
6481
(
65-
"\(bundle.path)/\(artifact.id)/\(targetTriple.tripleString)/toolset.json",
82+
"\(toolsetPath)",
6683
ByteString(json: """
6784
{
6885
"schemaVersion": "1.0",
@@ -101,16 +118,18 @@ private struct MockBundle {
101118
private struct MockArtifact {
102119
let id: String
103120
let supportedTriples: [Triple]
121+
var metadataPath: RelativePath?
104122
var toolsetRootPath: AbsolutePath?
105123
}
106124

107-
private func generateTestFileSystem(bundleArtifacts: [MockArtifact]) throws -> (some FileSystem, [MockBundle], AbsolutePath) {
125+
private func generateTestFileSystem(
126+
bundleArtifacts: [MockArtifact]
127+
) throws -> (some FileSystem, [MockBundle], AbsolutePath) {
108128
let bundles = bundleArtifacts.enumerated().map { (i, artifacts) in
109129
let bundleName = "test\(i).\(artifactBundleExtension)"
110130
return MockBundle(name: "test\(i).\(artifactBundleExtension)", path: "/\(bundleName)", artifacts: [artifacts])
111131
}
112132

113-
114133
let fileSystem = try InMemoryFileSystem(
115134
files: Dictionary(
116135
uniqueKeysWithValues: bundles.flatMap {
@@ -475,4 +494,54 @@ final class SwiftSDKBundleTests: XCTestCase {
475494
)
476495
}
477496
}
497+
498+
func testMetadataJSONPaths() async throws {
499+
let toolsetRootPath = AbsolutePath("/path/to/toolpath")
500+
let (fileSystem, bundles, swiftSDKsDirectory) = try generateTestFileSystem(
501+
bundleArtifacts: [
502+
.init(
503+
id: "\(testArtifactID)1",
504+
supportedTriples: [arm64Triple],
505+
metadataPath: "metadata1.json"
506+
),
507+
.init(
508+
id: "\(testArtifactID)2",
509+
supportedTriples: [i686Triple],
510+
metadataPath: "metadata2.json",
511+
toolsetRootPath: toolsetRootPath
512+
),
513+
]
514+
)
515+
let system = ObservabilitySystem.makeForTesting()
516+
let archiver = MockArchiver()
517+
518+
var output = [SwiftSDKBundleStore.Output]()
519+
let store = SwiftSDKBundleStore(
520+
swiftSDKsDirectory: swiftSDKsDirectory,
521+
fileSystem: fileSystem,
522+
observabilityScope: system.topScope,
523+
outputHandler: { output.append($0) }
524+
)
525+
526+
for bundle in bundles {
527+
try await store.install(bundlePathOrURL: bundle.path, archiver)
528+
}
529+
530+
let validBundles = try store.allValidBundles
531+
532+
XCTAssertEqual(validBundles.count, bundles.count)
533+
534+
XCTAssertEqual(validBundles.sortedArtifactIDs, ["\(testArtifactID)1", "\(testArtifactID)2"])
535+
XCTAssertEqual(output.count, 2)
536+
XCTAssertEqual(output, [
537+
.installationSuccessful(
538+
bundlePathOrURL: bundles[0].path,
539+
bundleName: AbsolutePath(bundles[0].path).components.last!
540+
),
541+
.installationSuccessful(
542+
bundlePathOrURL: bundles[1].path,
543+
bundleName: AbsolutePath(bundles[1].path).components.last!
544+
),
545+
])
546+
}
478547
}

0 commit comments

Comments
 (0)