Skip to content

Commit d760f10

Browse files
committed
Loosen the assumptions around product types, letting libSwiftPM clients provide additional ones
This starts to unwind some of the hardcoding around product types, and lets libSwiftPM clients provide additional product types through add-on libraries loaded on top of `PackageDescription`. Note that this isn’t yet plugin-provided product types (that will be a much larger effort). But it is a small step towards it. The existing product types of executable, library, etc are still kept throughout libSwiftPM, but the way in which they are encoded in PackageDescription and read by libSwiftPM is now more generic. Additionally, other types with custom properties are supported. The custom properties are kept in encoded form so that libSwiftPM doesn’t need to understand the details of them. They can be defined and encoded by additional libraries loaded by the manifest on top of PackageDescription, and clients of libSwiftPM can unpack them using the same structure definitions as in that library. libSwiftPM of course unpacks the properties of its built-in types (currently only whether a library is static, dynamic, or automatic). This also updates the manifest source generation to allow clients to generate source code fragments for custom product types. A future addition would be to automatically generate these source code fragments using Mirrors.
1 parent 1be68e8 commit d760f10

File tree

13 files changed

+260
-94
lines changed

13 files changed

+260
-94
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,8 @@ public final class ProductBuildDescription {
12391239
}
12401240
case .plugin:
12411241
throw InternalError("unexpectedly asked to generate linker arguments for a plugin product")
1242+
case .custom:
1243+
throw InternalError("unexpectedly asked to generate linker arguments for a custom product")
12421244
}
12431245

12441246
// Set rpath such that dynamic libraries are looked up
@@ -1261,6 +1263,8 @@ public final class ProductBuildDescription {
12611263
useStdlibRpath = true
12621264
case .plugin:
12631265
throw InternalError("unexpectedly asked to generate linker arguments for a plugin product")
1266+
case .custom:
1267+
throw InternalError("unexpectedly asked to generate linker arguments for a custom product")
12641268
}
12651269

12661270
if useStdlibRpath && buildParameters.triple.isDarwin() {
@@ -1562,6 +1566,10 @@ public class BuildPlan {
15621566
// for automatic libraries and plugins, because they don't produce any output.
15631567
for product in graph.allProducts where product.type != .library(.automatic) && product.type != .plugin {
15641568

1569+
// Custom product types are currently ignored; in the long run they
1570+
// should be supported using product type plugins.
1571+
if case .custom(_, _) = product.type { continue }
1572+
15651573
// Determine the appropriate tools version to use for the product.
15661574
// This can affect what flags to pass and other semantics.
15671575
let toolsVersion = graph.package(for: product)?.manifest.toolsVersion ?? .v5_5
@@ -1737,7 +1745,7 @@ public class BuildPlan {
17371745
switch product.type {
17381746
case .library(.automatic), .library(.static), .plugin:
17391747
return product.targets.map { .target($0, conditions: []) }
1740-
case .library(.dynamic), .test, .executable, .snippet:
1748+
case .library(.dynamic), .test, .executable, .snippet, .custom:
17411749
return []
17421750
}
17431751
}

Sources/Build/ManifestBuilder.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,11 @@ extension LLBuildManifestBuilder {
562562
try addStaticTargetInputs(target)
563563
}
564564

565+
// Custom product types are currently ignored; in the long run
566+
// they should be supported using product type plugins.
567+
case .custom:
568+
break
569+
565570
case .test:
566571
break
567572
}
@@ -680,6 +685,12 @@ extension LLBuildManifestBuilder {
680685
for target in product.targets {
681686
addStaticTargetInputs(target)
682687
}
688+
689+
// Custom product types are currently ignored; in the long run
690+
// they should be supported using product type plugins.
691+
case .custom:
692+
break
693+
683694
case .test:
684695
break
685696
}
@@ -851,6 +862,8 @@ extension ResolvedProduct {
851862
return "\(name)-\(config).exe"
852863
case .plugin:
853864
throw InternalError("unexpectedly asked for the llbuild target name of a plugin product")
865+
case .custom:
866+
throw InternalError("unexpectedly asked for the llbuild target name of a custom product")
854867
}
855868
}
856869

Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ extension PackageModel.ProductType {
404404
self = .snippet
405405
case .test:
406406
self = .test
407+
case .custom(let typeName, let propertyData):
408+
self = .custom(typeName, propertyData)
407409
}
408410
}
409411
}

Sources/PackageCollectionsModel/PackageCollectionModel+v1.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,15 @@ extension PackageCollectionModel.V1 {
333333

334334
/// A test product.
335335
case test
336+
337+
/// A custom type (the properties are encoded in opaque data).
338+
case custom(_ typeName: String, _ propertyData: Data)
336339
}
337340
}
338341

339342
extension PackageCollectionModel.V1.ProductType: Codable {
340343
private enum CodingKeys: String, CodingKey {
341-
case library, executable, plugin, snippet, test
344+
case library, executable, plugin, snippet, test, custom
342345
}
343346

344347
public func encode(to encoder: Encoder) throws {
@@ -355,6 +358,10 @@ extension PackageCollectionModel.V1.ProductType: Codable {
355358
try container.encodeNil(forKey: .snippet)
356359
case .test:
357360
try container.encodeNil(forKey: .test)
361+
case .custom(let a1, let a2):
362+
var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .custom)
363+
try unkeyedContainer.encode(a1)
364+
try unkeyedContainer.encode(a2)
358365
}
359366
}
360367

@@ -376,6 +383,11 @@ extension PackageCollectionModel.V1.ProductType: Codable {
376383
self = .snippet
377384
case .test:
378385
self = .test
386+
case .custom:
387+
var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key)
388+
let a1 = try unkeyedValues.decode(String.self)
389+
let a2 = try unkeyedValues.decode(Data.self)
390+
self = .custom(a1, a2)
379391
}
380392
}
381393
}

Sources/PackageDescription/Product.swift

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11+
import Foundation
12+
1113
/// The object that defines a package product.
1214
///
1315
/// A package product defines an externally visible build artifact that's
@@ -53,25 +55,17 @@
5355
/// ]
5456
/// )
5557
public class Product: Encodable {
56-
private enum ProductCodingKeys: String, CodingKey {
57-
case name
58-
case type = "product_type"
59-
}
60-
61-
/// The name of the package product.
58+
/// The name of the product.
6259
public let name: String
6360

6461
init(name: String) {
6562
self.name = name
6663
}
6764

68-
/// The executable product of a Swift package.
65+
/// A product that builds an executable binary (such as a command line tool).
6966
public final class Executable: Product {
70-
private enum ExecutableCodingKeys: CodingKey {
71-
case targets
72-
}
73-
74-
/// The names of the targets in this product.
67+
/// The names of the targets that comprise the executable product.
68+
/// There must be exactly one `executableTarget` among them.
7569
public let targets: [String]
7670

7771
init(name: String, targets: [String]) {
@@ -81,60 +75,57 @@ public class Product: Encodable {
8175

8276
public override func encode(to encoder: Encoder) throws {
8377
try super.encode(to: encoder)
84-
var productContainer = encoder.container(keyedBy: ProductCodingKeys.self)
85-
try productContainer.encode("executable", forKey: .type)
86-
var executableContainer = encoder.container(keyedBy: ExecutableCodingKeys.self)
87-
try executableContainer.encode(targets, forKey: .targets)
78+
var container = encoder.container(keyedBy: CodingKeys.self)
79+
try container.encode("executable", forKey: .type)
80+
try container.encode(targets, forKey: .targets)
8881
}
8982
}
9083

91-
/// The library product of a Swift package.
84+
/// A product that builds a library that other targets and products can link against.
9285
public final class Library: Product {
93-
private enum LibraryCodingKeys: CodingKey {
94-
case type
95-
case targets
96-
}
86+
/// The names of the targets that comprise the library product.
87+
public let targets: [String]
9788

9889
/// The different types of a library product.
9990
public enum LibraryType: String, Encodable {
100-
/// A statically linked library.
91+
/// A statically linked library (its code will be incorporated
92+
/// into clients that link to it).
10193
case `static`
102-
/// A dynamically linked library.
94+
/// A dynamically linked library (its code will be referenced
95+
/// by clients that link to it).
10396
case `dynamic`
10497
}
10598

106-
/// The names of the targets in this product.
107-
public let targets: [String]
108-
109-
/// The type of the library.
99+
/// The type of library.
110100
///
111101
/// If the type is unspecified, the Swift Package Manager automatically
112-
/// chooses a type based on the client's preference.
102+
/// chooses a type based on how the library is used by the client.
113103
public let type: LibraryType?
114104

115105
init(name: String, type: LibraryType? = nil, targets: [String]) {
116-
self.type = type
117106
self.targets = targets
107+
self.type = type
118108
super.init(name: name)
119109
}
120110

121111
public override func encode(to encoder: Encoder) throws {
122112
try super.encode(to: encoder)
123-
var productContainer = encoder.container(keyedBy: ProductCodingKeys.self)
124-
try productContainer.encode("library", forKey: .type)
125-
var libraryContainer = encoder.container(keyedBy: LibraryCodingKeys.self)
126-
try libraryContainer.encode(type, forKey: .type)
127-
try libraryContainer.encode(targets, forKey: .targets)
113+
var container = encoder.container(keyedBy: CodingKeys.self)
114+
try container.encode("library", forKey: .type)
115+
try container.encode(targets, forKey: .targets)
116+
let encoder = JSONEncoder()
117+
struct EncodedLibraryProperties: Encodable {
118+
public let type: LibraryType?
119+
}
120+
let properties = EncodedLibraryProperties(type: self.type)
121+
let encodedProperties = String(decoding: try encoder.encode(properties), as: UTF8.self)
122+
try container.encode(encodedProperties, forKey: .encodedProperties)
128123
}
129124
}
130125

131126
/// The plugin product of a Swift package.
132127
public final class Plugin: Product {
133-
private enum PluginCodingKeys: CodingKey {
134-
case targets
135-
}
136-
137-
/// The name of the plugin target to vend as a product.
128+
/// The name of the plugin targets to vend as a product.
138129
public let targets: [String]
139130

140131
init(name: String, targets: [String]) {
@@ -144,13 +135,35 @@ public class Product: Encodable {
144135

145136
public override func encode(to encoder: Encoder) throws {
146137
try super.encode(to: encoder)
147-
var productContainer = encoder.container(keyedBy: ProductCodingKeys.self)
148-
try productContainer.encode("plugin", forKey: .type)
149-
var pluginContainer = encoder.container(keyedBy: PluginCodingKeys.self)
150-
try pluginContainer.encode(targets, forKey: .targets)
138+
var container = encoder.container(keyedBy: CodingKeys.self)
139+
try container.encode("plugin", forKey: .type)
140+
try container.encode(targets, forKey: .targets)
151141
}
152142
}
153143

144+
/// The name of the product type to encode.
145+
private class var productTypeName: String { return "unknown" }
146+
147+
/// The string representation of any additional product properties. By
148+
/// storing these as a separate encoded blob, the properties can be a
149+
/// private contract between PackageDescription and whatever client will
150+
/// interprest them, without libSwiftPM needing to know the contents.
151+
private var encodedProperties: String? { return .none }
152+
153+
enum CodingKeys: String, CodingKey {
154+
case type, name, targets, encodedProperties
155+
}
156+
157+
public func encode(to encoder: Encoder) throws {
158+
var container = encoder.container(keyedBy: CodingKeys.self)
159+
try container.encode(Self.productTypeName, forKey: .type)
160+
try container.encode(name, forKey: .name)
161+
try container.encodeIfPresent(encodedProperties, forKey: .encodedProperties)
162+
}
163+
}
164+
165+
166+
extension Product {
154167
/// Creates a library product to allow clients that declare a dependency on this package
155168
/// to use the package's functionality.
156169
///
@@ -197,9 +210,4 @@ public class Product: Encodable {
197210
) -> Product {
198211
return Plugin(name: name, targets: targets)
199212
}
200-
201-
public func encode(to encoder: Encoder) throws {
202-
var container = encoder.container(keyedBy: ProductCodingKeys.self)
203-
try container.encode(name, forKey: .name)
204-
}
205213
}

Sources/PackageLoading/Diagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension Basics.Diagnostic {
3131
switch product.type {
3232
case .library(.automatic):
3333
typeString = ""
34-
case .executable, .snippet, .plugin, .test,
34+
case .executable, .snippet, .plugin, .test, .custom,
3535
.library(.dynamic), .library(.static):
3636
typeString = " (\(product.type))"
3737
}

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -415,34 +415,27 @@ extension SystemPackageProviderDescription {
415415

416416
extension PackageModel.ProductType {
417417
fileprivate init(v4 json: JSON) throws {
418-
let productType = try json.get(String.self, forKey: "product_type")
418+
let productType = try json.get(String.self, forKey: "product")
419419

420420
switch productType {
421421
case "executable":
422422
self = .executable
423423

424424
case "library":
425-
let libraryType: ProductType.LibraryType
426-
427-
let libraryTypeString: String? = json.get("type")
428-
switch libraryTypeString {
429-
case "static"?:
430-
libraryType = .static
431-
case "dynamic"?:
432-
libraryType = .dynamic
433-
case nil:
434-
libraryType = .automatic
435-
default:
436-
throw InternalError("invalid product type \(productType)")
425+
// Since library is still a built-in type, unpack its properties.
426+
struct EncodedLibraryProperties: Decodable {
427+
public let type: LibraryType?
437428
}
438-
439-
self = .library(libraryType)
429+
let encodedProperties = try json.get(String.self, forKey: "encodedProperties")
430+
let properties = try JSONDecoder().decode(EncodedLibraryProperties.self, from: Data(encodedProperties.utf8))
431+
self = .library(properties.type ?? .automatic)
440432

441433
case "plugin":
442434
self = .plugin
443435

444436
default:
445-
throw InternalError("unexpected product type: \(json)")
437+
let encodedProperties = try json.get(String.self, forKey: "encodedProperties")
438+
self = .custom(productType, Data(encodedProperties.utf8))
446439
}
447440
}
448441
}

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,11 @@ public final class PackageBuilder {
12471247
guard self.validatePluginProduct(product, with: targets) else {
12481248
continue
12491249
}
1250+
case .custom:
1251+
guard self.validateCustomProduct(product, with: targets) else {
1252+
continue
1253+
}
1254+
break
12501255
}
12511256

12521257
append(Product(name: product.name, type: product.type, targets: targets))
@@ -1260,7 +1265,7 @@ public final class PackageBuilder {
12601265
// for them.
12611266
let explicitProductsTargets = Set(self.manifest.products.flatMap{ product -> [String] in
12621267
switch product.type {
1263-
case .library, .plugin, .test:
1268+
case .library, .plugin, .test, .custom:
12641269
return []
12651270
case .executable, .snippet:
12661271
return product.targets
@@ -1346,6 +1351,12 @@ public final class PackageBuilder {
13461351
}
13471352
return true
13481353
}
1354+
1355+
private func validateCustomProduct(_ product: ProductDescription, with targets: [Target]) -> Bool {
1356+
// At this point there are no built-in restrictions on custom products.
1357+
// Here is where we would add them.
1358+
return true
1359+
}
13491360
}
13501361

13511362
/// We create this structure after scanning the filesystem for potential targets.

0 commit comments

Comments
 (0)