Skip to content

Commit b5e2108

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 95ff950 commit b5e2108

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/LLBuildManifestBuilder.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,11 @@ extension LLBuildManifestBuilder {
582582
try addStaticTargetInputs(target)
583583
}
584584

585+
// Custom product types are currently ignored; in the long run
586+
// they should be supported using product type plugins.
587+
case .custom:
588+
break
589+
585590
case .test:
586591
break
587592
}
@@ -727,6 +732,12 @@ extension LLBuildManifestBuilder {
727732
for target in product.targets {
728733
addStaticTargetInputs(target)
729734
}
735+
736+
// Custom product types are currently ignored; in the long run
737+
// they should be supported using product type plugins.
738+
case .custom:
739+
break
740+
730741
case .test:
731742
break
732743
}
@@ -898,6 +909,8 @@ extension ResolvedProduct {
898909
return "\(name)-\(config).exe"
899910
case .plugin:
900911
throw InternalError("unexpectedly asked for the llbuild target name of a plugin product")
912+
case .custom:
913+
throw InternalError("unexpectedly asked for the llbuild target name of a custom product")
901914
}
902915
}
903916

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
@@ -30,7 +30,7 @@ extension Basics.Diagnostic {
3030
switch product.type {
3131
case .library(.automatic):
3232
typeString = ""
33-
case .executable, .snippet, .plugin, .test,
33+
case .executable, .snippet, .plugin, .test, .custom,
3434
.library(.dynamic), .library(.static):
3535
typeString = " (\(product.type))"
3636
}

Sources/PackageLoading/ManifestJSONParser.swift

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

444444
extension PackageModel.ProductType {
445445
fileprivate init(v4 json: JSON) throws {
446-
let productType = try json.get(String.self, forKey: "product_type")
446+
let productType = try json.get(String.self, forKey: "product")
447447

448448
switch productType {
449449
case "executable":
450450
self = .executable
451451

452452
case "library":
453-
let libraryType: ProductType.LibraryType
454-
455-
let libraryTypeString: String? = json.get("type")
456-
switch libraryTypeString {
457-
case "static"?:
458-
libraryType = .static
459-
case "dynamic"?:
460-
libraryType = .dynamic
461-
case nil:
462-
libraryType = .automatic
463-
default:
464-
throw InternalError("invalid product type \(productType)")
453+
// Since library is still a built-in type, unpack its properties.
454+
struct EncodedLibraryProperties: Decodable {
455+
public let type: LibraryType?
465456
}
466-
467-
self = .library(libraryType)
457+
let encodedProperties = try json.get(String.self, forKey: "encodedProperties")
458+
let properties = try JSONDecoder().decode(EncodedLibraryProperties.self, from: Data(encodedProperties.utf8))
459+
self = .library(properties.type ?? .automatic)
468460

469461
case "plugin":
470462
self = .plugin
471463

472464
default:
473-
throw InternalError("unexpected product type: \(json)")
465+
let encodedProperties = try json.get(String.self, forKey: "encodedProperties")
466+
self = .custom(productType, Data(encodedProperties.utf8))
474467
}
475468
}
476469
}

Sources/PackageLoading/PackageBuilder.swift

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

12511256
append(Product(name: product.name, type: product.type, targets: targets))
@@ -1259,7 +1264,7 @@ public final class PackageBuilder {
12591264
// for them.
12601265
let explicitProductsTargets = Set(self.manifest.products.flatMap{ product -> [String] in
12611266
switch product.type {
1262-
case .library, .plugin, .test:
1267+
case .library, .plugin, .test, .custom:
12631268
return []
12641269
case .executable, .snippet:
12651270
return product.targets
@@ -1345,6 +1350,12 @@ public final class PackageBuilder {
13451350
}
13461351
return true
13471352
}
1353+
1354+
private func validateCustomProduct(_ product: ProductDescription, with targets: [Target]) -> Bool {
1355+
// At this point there are no built-in restrictions on custom products.
1356+
// Here is where we would add them.
1357+
return true
1358+
}
13481359
}
13491360

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

0 commit comments

Comments
 (0)