@@ -222,6 +222,180 @@ fileprivate struct BuildOperationTests: CoreBasedTests {
222222 }
223223 }
224224
225+ @Test ( . requireSDKs( . host) , . requireThreadSafeWorkingDirectory)
226+ func debuggableCommandLineTool( ) async throws {
227+ try await withTemporaryDirectory { ( tmpDir: Path ) in
228+ let testProject = try await TestProject (
229+ " TestProject " ,
230+ sourceRoot: tmpDir,
231+ groupTree: TestGroup (
232+ " SomeFiles " ,
233+ children: [
234+ TestFile ( " main.swift " ) ,
235+ TestFile ( " dynamic.swift " ) ,
236+ TestFile ( " static.swift " ) ,
237+ ] ) ,
238+ buildConfigurations: [
239+ TestBuildConfiguration ( " Debug " , buildSettings: [
240+ " ARCHS " : " $(ARCHS_STANDARD) " ,
241+ " CODE_SIGNING_ALLOWED " : ProcessInfo . processInfo. hostOperatingSystem ( ) == . macOS ? " YES " : " NO " ,
242+ " CODE_SIGN_IDENTITY " : " - " ,
243+ " CODE_SIGN_ENTITLEMENTS " : " Entitlements.plist " ,
244+ " DEFINES_MODULE " : " YES " ,
245+ " PRODUCT_NAME " : " $(TARGET_NAME) " ,
246+ " SDKROOT " : " $(HOST_PLATFORM) " ,
247+ " SUPPORTED_PLATFORMS " : " $(HOST_PLATFORM) " ,
248+ " SWIFT_VERSION " : swiftVersion,
249+ " GCC_GENERATE_DEBUGGING_SYMBOLS " : " YES " ,
250+ ] )
251+ ] ,
252+ targets: [
253+ TestStandardTarget (
254+ " tool " ,
255+ type: . commandLineTool,
256+ buildConfigurations: [
257+ TestBuildConfiguration ( " Debug " , buildSettings: [
258+ " LD_RUNPATH_SEARCH_PATHS " : " @loader_path/ " ,
259+ ] )
260+ ] ,
261+ buildPhases: [
262+ TestSourcesBuildPhase ( [ " main.swift " ] ) ,
263+ TestFrameworksBuildPhase ( [
264+ TestBuildFile ( . target( " dynamiclib " ) ) ,
265+ TestBuildFile ( . target( " staticlib " ) ) ,
266+ ] )
267+ ] ,
268+ dependencies: [
269+ " dynamiclib " ,
270+ " staticlib " ,
271+ ]
272+ ) ,
273+ TestStandardTarget (
274+ " dynamiclib " ,
275+ type: . dynamicLibrary,
276+ buildConfigurations: [
277+ TestBuildConfiguration ( " Debug " , buildSettings: [
278+ " DYLIB_INSTALL_NAME_BASE " : " $ORIGIN " ,
279+ " DYLIB_INSTALL_NAME_BASE[sdk=macosx*] " : " @rpath " ,
280+
281+ // FIXME: Find a way to make these default
282+ " EXECUTABLE_PREFIX " : " lib " ,
283+ " EXECUTABLE_PREFIX[sdk=windows*] " : " " ,
284+ ] )
285+ ] ,
286+ buildPhases: [
287+ TestSourcesBuildPhase ( [ " dynamic.swift " ] ) ,
288+ ]
289+ ) ,
290+ TestStandardTarget (
291+ " staticlib " ,
292+ type: . staticLibrary,
293+ buildConfigurations: [
294+ TestBuildConfiguration ( " Debug " , buildSettings: [
295+ // FIXME: Find a way to make these default
296+ " EXECUTABLE_PREFIX " : " lib " ,
297+ " EXECUTABLE_PREFIX[sdk=windows*] " : " " ,
298+ ] )
299+ ] ,
300+ buildPhases: [
301+ TestSourcesBuildPhase ( [ " static.swift " ] ) ,
302+ ]
303+ ) ,
304+ ] )
305+ let core = try await getCore ( )
306+ let tester = try await BuildOperationTester ( core, testProject, simulated: false )
307+
308+ let projectDir = tester. workspace. projects [ 0 ] . sourceRoot
309+
310+ try await tester. fs. writeFileContents ( projectDir. join ( " main.swift " ) ) { stream in
311+ stream <<< " import dynamiclib \n "
312+ stream <<< " import staticlib \n "
313+ stream <<< " dynamicLib() \n "
314+ stream <<< " dynamicLib() \n "
315+ stream <<< " staticLib() \n "
316+ stream <<< " print( \" Hello world \" ) \n "
317+ }
318+
319+ try await tester. fs. writeFileContents ( projectDir. join ( " dynamic.swift " ) ) { stream in
320+ stream <<< " public func dynamicLib() { } "
321+ }
322+
323+ try await tester. fs. writeFileContents ( projectDir. join ( " static.swift " ) ) { stream in
324+ stream <<< " public func staticLib() { } "
325+ }
326+
327+ try await tester. fs. writePlist ( projectDir. join ( " Entitlements.plist " ) , . plDict( [ : ] ) )
328+
329+ let provisioningInputs = [
330+ " dynamiclib " : ProvisioningTaskInputs ( identityHash: " - " , signedEntitlements: . plDict( [ : ] ) , simulatedEntitlements: . plDict( [ : ] ) ) ,
331+ " staticlib " : ProvisioningTaskInputs ( identityHash: " - " , signedEntitlements: . plDict( [ : ] ) , simulatedEntitlements: . plDict( [ : ] ) ) ,
332+ " tool " : ProvisioningTaskInputs ( identityHash: " - " , signedEntitlements: . plDict( [ : ] ) , simulatedEntitlements: . plDict( [ : ] ) )
333+ ]
334+
335+ let destination : RunDestinationInfo = . host
336+ try await tester. checkBuild ( runDestination: destination, persistent: true , signableTargets: Set ( provisioningInputs. keys) , signableTargetInputs: provisioningInputs) { results in
337+ results. checkNoErrors ( )
338+ if core. hostOperatingSystem. imageFormat. requiresSwiftModulewrap {
339+ try results. checkTask ( . matchTargetName( " tool " ) , . matchRulePattern( [ " WriteAuxiliaryFile " , . suffix( " LinkFileList " ) ] ) ) { task in
340+ let auxFileAction = try #require( task. action as? AuxiliaryFileTaskAction )
341+ let contents = try tester. fs. read ( auxFileAction. context. input) . asString
342+ let files = contents. components ( separatedBy: " \n " ) . map { $0. trimmingCharacters ( in: . whitespacesAndNewlines) } . filter { !$0. isEmpty }
343+ #expect( files. count == 2 )
344+ #expect( files [ 0 ] . hasSuffix ( " tool.o " ) )
345+ #expect( files [ 1 ] . hasSuffix ( " main.o " ) )
346+ }
347+ let toolWrap = try #require( results. getTask ( . matchTargetName( " tool " ) , . matchRuleType( " SwiftModuleWrap " ) ) )
348+ try results. checkTask ( . matchTargetName( " tool " ) , . matchRuleType( " Ld " ) ) { task in
349+ try results. checkTaskFollows ( task, toolWrap)
350+ }
351+
352+ try results. checkTask ( . matchTargetName( " dynamiclib " ) , . matchRulePattern( [ " WriteAuxiliaryFile " , . suffix( " LinkFileList " ) ] ) ) { task in
353+ let auxFileAction = try #require( task. action as? AuxiliaryFileTaskAction )
354+ let contents = try tester. fs. read ( auxFileAction. context. input) . asString
355+ let files = contents. components ( separatedBy: " \n " ) . map { $0. trimmingCharacters ( in: . whitespacesAndNewlines) } . filter { !$0. isEmpty }
356+ #expect( files. count == 2 )
357+ #expect( files [ 0 ] . hasSuffix ( " dynamiclib.o " ) )
358+ #expect( files [ 1 ] . hasSuffix ( " dynamic.o " ) )
359+ }
360+ let dylibWrap = try #require( results. getTask ( . matchTargetName( " dynamiclib " ) , . matchRuleType( " SwiftModuleWrap " ) ) )
361+ try results. checkTask ( . matchTargetName( " dynamiclib " ) , . matchRuleType( " Ld " ) ) { task in
362+ try results. checkTaskFollows ( task, dylibWrap)
363+ }
364+
365+ try results. checkTask ( . matchTargetName( " staticlib " ) , . matchRulePattern( [ " WriteAuxiliaryFile " , . suffix( " LinkFileList " ) ] ) ) { task in
366+ let auxFileAction = try #require( task. action as? AuxiliaryFileTaskAction )
367+ let contents = try tester. fs. read ( auxFileAction. context. input) . asString
368+ let files = contents. components ( separatedBy: " \n " ) . map { $0. trimmingCharacters ( in: . whitespacesAndNewlines) } . filter { !$0. isEmpty }
369+ #expect( files. count == 2 )
370+ #expect( files [ 0 ] . hasSuffix ( " staticlib.o " ) )
371+ #expect( files [ 1 ] . hasSuffix ( " static.o " ) )
372+ }
373+ let staticWrap = try #require( results. getTask ( . matchTargetName( " staticlib " ) , . matchRuleType( " SwiftModuleWrap " ) ) )
374+ try results. checkTask ( . matchTargetName( " staticlib " ) , . matchRuleType( " Libtool " ) ) { task in
375+ try results. checkTaskFollows ( task, staticWrap)
376+ }
377+ }
378+
379+ let toolchain = try #require( try await getCore ( ) . toolchainRegistry. defaultToolchain)
380+ let environment : [ String : String ]
381+ if destination. platform == " linux " {
382+ environment = [ " LD_LIBRARY_PATH " : toolchain. path. join ( " usr/lib/swift/linux " ) . str]
383+ } else {
384+ environment = [ : ]
385+ }
386+
387+ let executionResult = try await Process . getOutput ( url: URL ( fileURLWithPath: projectDir. join ( " build " ) . join ( " Debug \( destination. builtProductsDirSuffix) " ) . join ( core. hostOperatingSystem. imageFormat. executableName ( basename: " tool " ) ) . str) , arguments: [ ] , environment: environment)
388+ #expect( executionResult. exitStatus == . exit( 0 ) )
389+ if core. hostOperatingSystem == . windows {
390+ #expect( String ( decoding: executionResult. stdout, as: UTF8 . self) == " Hello world \r \n " )
391+ } else {
392+ #expect( String ( decoding: executionResult. stdout, as: UTF8 . self) == " Hello world \n " )
393+ }
394+ #expect( String ( decoding: executionResult. stderr, as: UTF8 . self) == " " )
395+ }
396+ }
397+ }
398+
225399 /// Check that environment variables are propagated from the user environment correctly.
226400 @Test ( . requireSDKs( . host) , . skipHostOS( . windows) , . requireSystemPackages( apt: " yacc " , yum: " byacc " ) )
227401 func userEnvironment( ) async throws {
0 commit comments