@@ -3,6 +3,8 @@ import PackagePlugin
3
3
4
4
@main
5
5
struct Link : CommandPlugin {
6
+ static let pluginName : String = " link "
7
+
6
8
func performCommand(
7
9
context: PluginContext ,
8
10
arguments: [ String ]
@@ -12,14 +14,13 @@ struct Link: CommandPlugin {
12
14
fileURLWithPath: clang. path. string,
13
15
isDirectory: false
14
16
)
17
+ Diagnostics . remark ( " [ \( Self . pluginName) ] clang: \( clang. path. string) " )
15
18
let commonClangArgs = [
16
19
" --target=armv6m-none-eabi " ,
17
20
" -mfloat-abi=soft " ,
18
21
" -march=armv6m " ,
19
22
" -O3 " ,
20
- " -DNDEBUG " ,
21
23
" -nostdlib " ,
22
- " -Wl,--build-id=none " ,
23
24
]
24
25
25
26
let buildParams = PackageManager . BuildParameters (
@@ -29,13 +30,14 @@ struct Link: CommandPlugin {
29
30
30
31
// Build RP2040 second-stage bootloader (boot2)
31
32
let boot2Product = try context. package . products ( named: [ " RP2040Boot2 " ] ) [ 0 ]
33
+ Diagnostics . remark ( " [ \( Self . pluginName) ] Building product ' \( boot2Product. name) ' " )
32
34
let boot2BuildResult = try packageManager. build (
33
35
. product( boot2Product. name) ,
34
36
parameters: buildParams
35
37
)
36
38
guard boot2BuildResult. succeeded else {
37
39
print ( boot2BuildResult. logText)
38
- Diagnostics . error ( " Building product ' \( boot2Product. name) ' failed " )
40
+ Diagnostics . error ( " [ \( Self . pluginName ) ] Building product '\( boot2Product. name) ' failed " )
39
41
// TODO: Exit with error code
40
42
return
41
43
}
@@ -44,13 +46,13 @@ struct Link: CommandPlugin {
44
46
// Create directory for intermediate files
45
47
let intermediatesDir = context. pluginWorkDirectory
46
48
. appending ( subpath: " intermediates " )
47
- let intermediatesDirURL = URL ( fileURLWithPath: intermediatesDir. string, isDirectory: true )
48
49
try FileManager . default. createDirectory (
49
- at: intermediatesDirURL ,
50
+ at: URL ( fileURLWithPath : intermediatesDir . string , isDirectory : true ) ,
50
51
withIntermediateDirectories: true
51
52
)
52
53
53
54
// Postprocess boot2
55
+ Diagnostics . remark ( " [ \( Self . pluginName) ] Boot2 processing " )
54
56
//
55
57
// 1. Extract .o file from static library build product (.a)
56
58
// For some reason, if I try to link the .a file with Clang, it doesn't work.
@@ -61,24 +63,13 @@ struct Link: CommandPlugin {
61
63
// info is back (according to `file`).
62
64
// How does SwiftPM create the .a file? Anything suspicious?
63
65
let ar = try context. tool ( named: " ar " )
64
- let arURL = URL ( fileURLWithPath: ar. path. string, isDirectory: false )
65
66
let boot2ObjFile = intermediatesDir. appending ( subpath: " compile_time_choice.S.o " )
66
67
let arArgs = [
67
68
" x " ,
68
69
boot2StaticLib. path. string,
69
70
boot2ObjFile. lastComponent // ar always extracts to the current dir
70
71
]
71
- let arProcess = Process ( )
72
- arProcess. executableURL = arURL
73
- arProcess. arguments = arArgs
74
- arProcess. currentDirectoryURL = intermediatesDirURL
75
- try arProcess. run ( )
76
- arProcess. waitUntilExit ( )
77
- guard arProcess. terminationStatus == 0 else {
78
- Diagnostics . error ( " ar failed " )
79
- // TODO: Exit with error code
80
- return
81
- }
72
+ try runProgram ( ar. path, arguments: arArgs, workingDirectory: intermediatesDir)
82
73
83
74
// 2. Apply boot2 linker script
84
75
let boot2ELF = intermediatesDir. appending ( subpath: " bs2_default.elf " )
@@ -88,18 +79,14 @@ struct Link: CommandPlugin {
88
79
. first ( where: { $0. type == . resource && $0. path. lastComponent == " boot_stage2.ld " } ) !
89
80
var boot2ELFClangArgs = commonClangArgs
90
81
boot2ELFClangArgs. append ( contentsOf: [
82
+ " -DNDEBUG " ,
83
+ " -Wl,--build-id=none " ,
91
84
" -Xlinker " , " --script= \( boot2LinkerScript. path. string) " ,
92
85
boot2ObjFile. string,
93
86
" -o " , boot2ELF. string
94
87
] )
95
88
boot2ELFClangArgs. append ( contentsOf: boot2BuildResult. builtArtifacts. map ( \. path. string) )
96
- let boot2ELFProcess = try Process . run ( clangURL, arguments: boot2ELFClangArgs)
97
- boot2ELFProcess. waitUntilExit ( )
98
- guard boot2ELFProcess. terminationStatus == 0 else {
99
- Diagnostics . error ( " Clang failed linking boot2 elf file before checksumming " )
100
- // TODO: Exit with error code
101
- return
102
- }
89
+ try runProgram ( clang. path, arguments: boot2ELFClangArgs)
103
90
104
91
// 3. Convert boot2.elf to boot2.bin
105
92
let boot2Bin = intermediatesDir. appending ( subpath: " bs2_default.bin " )
@@ -110,13 +97,7 @@ struct Link: CommandPlugin {
110
97
boot2ELF. string,
111
98
boot2Bin. string
112
99
]
113
- let objcopyProcess = try Process . run ( objcopyURL, arguments: objcopyArgs)
114
- objcopyProcess. waitUntilExit ( )
115
- guard objcopyProcess. terminationStatus == 0 else {
116
- Diagnostics . error ( " objcopy failed " )
117
- // TODO: Exit with error code
118
- return
119
- }
100
+ try runProgram ( objcopy. path, arguments: objcopyArgs)
120
101
121
102
// 4. Calculate checksum and write into assembly file
122
103
let boot2ChecksummedAsm = intermediatesDir
@@ -125,19 +106,12 @@ struct Link: CommandPlugin {
125
106
. sourceModules [ 0 ]
126
107
. sourceFiles
127
108
. first ( where: { $0. type == . resource && $0. path. lastComponent == " pad_checksum " } ) !
128
- let padChecksumURL = URL ( fileURLWithPath: padChecksumScript. path. string, isDirectory: false )
129
109
let padChecksumArgs = [
130
110
" -s " , " 0xffffffff " ,
131
111
boot2Bin. string,
132
112
boot2ChecksummedAsm. string
133
113
]
134
- let padChecksumProcess = try Process . run ( padChecksumURL, arguments: padChecksumArgs)
135
- padChecksumProcess. waitUntilExit ( )
136
- guard padChecksumProcess. terminationStatus == 0 else {
137
- Diagnostics . error ( " pad_checksum failed " )
138
- // TODO: Exit with error code
139
- return
140
- }
114
+ try runProgram ( padChecksumScript. path, arguments: padChecksumArgs)
141
115
142
116
// 5. Assemble checksummed boot2 loader
143
117
let boot2ChecksummedObj = intermediatesDir. appending ( subpath: " bs2_default_padded_checksummed.s.o " )
@@ -146,13 +120,7 @@ struct Link: CommandPlugin {
146
120
" -c " , boot2ChecksummedAsm. string,
147
121
" -o " , boot2ChecksummedObj. string
148
122
] )
149
- let boot2ObjProcess = try Process . run ( clangURL, arguments: boot2ObjClangArgs)
150
- boot2ObjProcess. waitUntilExit ( )
151
- guard boot2ObjProcess. terminationStatus == 0 else {
152
- Diagnostics . error ( " Clang failed linking boot2 obj file " )
153
- // TODO: Exit with error code
154
- return
155
- }
123
+ try runProgram ( clang. path, arguments: boot2ObjClangArgs)
156
124
157
125
// Build the app
158
126
let appProduct = try context. package . products ( named: [ " App " ] ) [ 0 ]
@@ -162,7 +130,7 @@ struct Link: CommandPlugin {
162
130
)
163
131
guard appBuildResult. succeeded else {
164
132
print ( appBuildResult. logText)
165
- Diagnostics . error ( " Building product ' \( appProduct. name) ' failed " )
133
+ Diagnostics . error ( " [ \( Self . pluginName ) ] Building product '\( appProduct. name) ' failed " )
166
134
// TODO: Exit with error code
167
135
return
168
136
}
@@ -181,6 +149,8 @@ struct Link: CommandPlugin {
181
149
. first ( where: { $0. type == . resource && $0. path. lastComponent == " memmap_default.ld " } ) !
182
150
var appClangArgs = commonClangArgs
183
151
appClangArgs. append ( contentsOf: [
152
+ " -DNDEBUG " ,
153
+ " -Wl,--build-id=none " ,
184
154
" -Xlinker " , " --gc-sections " ,
185
155
" -Xlinker " , " --script= \( appLinkerScript. path. string) " ,
186
156
" -Xlinker " , " -z " , " -Xlinker " , " max-page-size=4096 " ,
@@ -189,14 +159,67 @@ struct Link: CommandPlugin {
189
159
boot2ChecksummedObj. string,
190
160
" -o " , linkedExecutable. string,
191
161
] )
192
- let appClangProcess = try Process . run ( clangURL, arguments: appClangArgs)
193
- appClangProcess. waitUntilExit ( )
194
- guard appClangProcess. terminationStatus == 0 else {
195
- Diagnostics . error ( " Clang failed linking app executable " )
196
- // TODO: Exit with error code
197
- return
198
- }
162
+ try runProgram ( clang. path, arguments: appClangArgs)
199
163
200
164
print ( " Executable: \( linkedExecutable) " )
201
165
}
166
+
167
+ /// Runs an external program and waits for it to finish.
168
+ ///
169
+ /// Emits SwiftPM diagnostics:
170
+ /// - `remark` with the invocation (exectuable + arguments)
171
+ /// - `error` on non-zero exit code
172
+ ///
173
+ /// - Throws:
174
+ /// - When the program cannot be launched.
175
+ /// - Throws `ExitCode` when the program completes with a non-zero status.
176
+ private func runProgram(
177
+ _ executable: Path ,
178
+ arguments: [ String ] ,
179
+ workingDirectory: Path ? = nil
180
+ ) throws {
181
+ // If the command is longer than approx. one line, format it neatly
182
+ // on multiple lines for logging.
183
+ let fullCommand = " \( executable. string) \( arguments. joined ( separator: " " ) ) "
184
+ let logMessage = if fullCommand. count < 70 {
185
+ fullCommand
186
+ } else {
187
+ """
188
+ \( executable. string) \\
189
+ \( arguments. joined ( separator: " \\ \n " ) )
190
+ """
191
+ }
192
+ Diagnostics . remark ( " [ \( Self . pluginName) ] \( logMessage) " )
193
+
194
+ let process = Process ( )
195
+ process. executableURL = URL (
196
+ fileURLWithPath: executable. string,
197
+ isDirectory: false
198
+ )
199
+ process. arguments = arguments
200
+ if let workingDirectory {
201
+ process. currentDirectoryURL = URL (
202
+ fileURLWithPath: workingDirectory. string,
203
+ isDirectory: true
204
+ )
205
+ }
206
+ try process. run ( )
207
+ process. waitUntilExit ( )
208
+ guard process. terminationStatus == 0 else {
209
+ Diagnostics . error ( " [ \( Self . pluginName) ] \( executable. lastComponent) exited with code \( process. terminationStatus) " )
210
+ throw ExitCode ( process. terminationStatus)
211
+ }
212
+ }
213
+ }
214
+
215
+ struct ExitCode : RawRepresentable , Error {
216
+ var rawValue : Int32
217
+
218
+ init ( rawValue: Int32 ) {
219
+ self . rawValue = rawValue
220
+ }
221
+
222
+ init ( _ code: Int32 ) {
223
+ self . init ( rawValue: code)
224
+ }
202
225
}
0 commit comments