Skip to content

Commit e3a2168

Browse files
Show traits package subcommand (#9213)
One of the benefits of traits comes from their declarative nature with a description that helps to explain their purpose. Users should be able to discover, and understand them so that they can enable them for the current package, and also dependencies too so that they can enable them through the package dependencies. Add a new show-traits package subcommand that defaults to listing the traits for the current package, and optionally for other packages with an option. swift package show-traits [--package-id=<packageId>] Add a format option to set either a text, or JSON output format. --------- Co-authored-by: Bri Peticca <bripeticca@gmail.com>
1 parent d89692b commit e3a2168

File tree

11 files changed

+552
-0
lines changed

11 files changed

+552
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// swift-tools-version:6.1
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "Dealer",
6+
products: [
7+
.executable(
8+
name: "dealer",
9+
targets: ["Dealer"]
10+
),
11+
],
12+
traits: [
13+
.trait(name: "trait1", description: "this trait is the default in app"),
14+
.trait(name: "trait2", description: "this trait is not the default in app"),
15+
.default(enabledTraits: ["trait1"]),
16+
],
17+
dependencies: [
18+
.package(path: "../deck-of-playing-cards", traits: ["trait3"]),
19+
],
20+
targets: [
21+
.executableTarget(
22+
name: "Dealer",
23+
path: "./"
24+
),
25+
]
26+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("I'm the dealer")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// swift-tools-version:6.1
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "deck-of-playing-cards",
6+
products: [
7+
.executable(
8+
name: "deck",
9+
targets: ["Deck"]
10+
),
11+
],
12+
traits: [
13+
.trait(name: "trait3", description: "This trait is in a different package and not default.")
14+
],
15+
targets: [
16+
.executableTarget(
17+
name: "Deck",
18+
path: "./"
19+
),
20+
]
21+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print("I'm a deck of cards")

Sources/Commands/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_library(Commands
3131
PackageCommands/Resolve.swift
3232
PackageCommands/ShowDependencies.swift
3333
PackageCommands/ShowExecutables.swift
34+
PackageCommands/ShowTraits.swift
3435
PackageCommands/SwiftPackageCommand.swift
3536
PackageCommands/ToolsVersionCommand.swift
3637
PackageCommands/Update.swift
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
import Basics
15+
import CoreCommands
16+
import Foundation
17+
import PackageModel
18+
import PackageGraph
19+
import Workspace
20+
21+
struct ShowTraits: AsyncSwiftCommand {
22+
static let configuration = CommandConfiguration(
23+
abstract: "List the available traits for a package.")
24+
25+
@OptionGroup(visibility: .hidden)
26+
var globalOptions: GlobalOptions
27+
28+
@Option(help: "Show traits for any package id in the transitive dependencies.")
29+
var packageId: String?
30+
31+
@Option(help: "Set the output format.")
32+
var format: ShowTraitsMode = .text
33+
34+
func run(_ swiftCommandState: SwiftCommandState) async throws {
35+
let packageGraph = try await swiftCommandState.loadPackageGraph()
36+
37+
let traits = if let packageId {
38+
packageGraph.packages.filter({ $0.identity.description == packageId }).flatMap( { $0.manifest.traits } ).sorted(by: {$0.name < $1.name} )
39+
} else {
40+
packageGraph.rootPackages.flatMap( { $0.manifest.traits } ).sorted(by: {$0.name < $1.name} )
41+
}
42+
43+
switch self.format {
44+
case .text:
45+
let defaultTraits = traits.filter( { $0.isDefault } ).flatMap( { $0.enabledTraits })
46+
47+
for trait in traits {
48+
guard !trait.isDefault else {
49+
continue
50+
}
51+
52+
print("\(trait.name)\(trait.description ?? "" != "" ? " - " + trait.description! : "")\(defaultTraits.contains(trait.name) ? " (default)" : "")")
53+
}
54+
55+
case .json:
56+
let encoder = JSONEncoder()
57+
encoder.outputFormatting = .sortedKeys
58+
59+
let data = try encoder.encode(traits)
60+
if let output = String(data: data, encoding: .utf8) {
61+
print(output)
62+
}
63+
}
64+
}
65+
66+
enum ShowTraitsMode: String, RawRepresentable, CustomStringConvertible, ExpressibleByArgument, CaseIterable {
67+
case text, json
68+
69+
public init?(rawValue: String) {
70+
switch rawValue.lowercased() {
71+
case "text":
72+
self = .text
73+
case "json":
74+
self = .json
75+
default:
76+
return nil
77+
}
78+
}
79+
80+
public var description: String {
81+
switch self {
82+
case .text: return "text"
83+
case .json: return "json"
84+
}
85+
}
86+
}
87+
}

Sources/Commands/PackageCommands/SwiftPackageCommand.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public struct SwiftPackageCommand: AsyncParsableCommand {
6666

6767
ShowDependencies.self,
6868
ShowExecutables.self,
69+
ShowTraits.self,
6970
ToolsVersionCommand.self,
7071
ComputeChecksum.self,
7172
ArchiveSource.self,

0 commit comments

Comments
 (0)