diff --git a/Fixtures/Miscellaneous/SwiftBuild/Package.swift b/Fixtures/Miscellaneous/SwiftBuild/Package.swift new file mode 100644 index 00000000000..ace9f61f0cc --- /dev/null +++ b/Fixtures/Miscellaneous/SwiftBuild/Package.swift @@ -0,0 +1,12 @@ +// swift-tools-version: 5.6 +import PackageDescription + +let package = Package( + name: "TestableExe", + targets: [ + .executableTarget( + name: "Test", + path: "." + ), + ] +) diff --git a/Fixtures/Miscellaneous/SwiftBuild/main.swift b/Fixtures/Miscellaneous/SwiftBuild/main.swift new file mode 100644 index 00000000000..621980f7b8b --- /dev/null +++ b/Fixtures/Miscellaneous/SwiftBuild/main.swift @@ -0,0 +1 @@ +print("done") \ No newline at end of file diff --git a/Sources/Basics/Observability.swift b/Sources/Basics/Observability.swift index 28c4401b9ff..404d42a85c4 100644 --- a/Sources/Basics/Observability.swift +++ b/Sources/Basics/Observability.swift @@ -443,6 +443,14 @@ public struct Diagnostic: Sendable, CustomStringConvertible { public var isBold: Bool { return true } + + public var isVerbose: Bool { + self <= .info + } + + public var isQuiet: Bool { + self >= .error + } } } diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 37761bd2e38..e0843b6cf00 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -1044,9 +1044,3 @@ extension BuildSubset { } } } - -extension Basics.Diagnostic.Severity { - var isVerbose: Bool { - return self <= .info - } -} diff --git a/Sources/Build/LLBuildProgressTracker.swift b/Sources/Build/LLBuildProgressTracker.swift index b203e8a1462..5f883b0597b 100644 --- a/Sources/Build/LLBuildProgressTracker.swift +++ b/Sources/Build/LLBuildProgressTracker.swift @@ -241,7 +241,9 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut } func commandStatusChanged(_ command: SPMLLBuild.Command, kind: CommandStatusKind) { - guard !self.logLevel.isVerbose else { return } + guard !self.logLevel.isVerbose, + !self.logLevel.isQuiet + else { return } guard command.shouldShowStatus else { return } guard !self.swiftParsers.keys.contains(command.name) else { return } @@ -285,7 +287,7 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut self.delegate?.buildSystem(self.buildSystem, didFinishCommand: BuildSystemCommand(command)) - if !self.logLevel.isVerbose { + if !self.logLevel.isVerbose && !self.logLevel.isQuiet { let targetName = self.swiftParsers[command.name]?.targetName self.taskTracker.commandFinished(command, result: result, targetName: targetName) self.updateProgress() @@ -395,6 +397,7 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut /// Invoked right before running an action taken before building. func preparationStepStarted(_ name: String) { + guard !self.logLevel.isQuiet else { return } self.queue.async { self.taskTracker.buildPreparationStepStarted(name) self.updateProgress() @@ -404,6 +407,7 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut /// Invoked when an action taken before building emits output. /// when verboseOnly is set to true, the output will only be printed in verbose logging mode func preparationStepHadOutput(_ name: String, output: String, verboseOnly: Bool) { + guard !logLevel.isQuiet else { return } self.queue.async { self.progressAnimation.clear() if !verboseOnly || self.logLevel.isVerbose { @@ -416,6 +420,7 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut /// Invoked right after running an action taken before building. The result /// indicates whether the action succeeded, failed, or was cancelled. func preparationStepFinished(_ name: String, result: CommandResult) { + guard !self.logLevel.isQuiet else { return } self.queue.async { self.taskTracker.buildPreparationStepFinished(name) self.updateProgress() @@ -431,7 +436,7 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut self.outputStream.send("\(text)\n") self.outputStream.flush() } - } else { + } else if !self.logLevel.isQuiet { self.taskTracker.swiftCompilerDidOutputMessage(message, targetName: parser.targetName) self.updateProgress() } @@ -466,6 +471,7 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut } func buildStart(configuration: BuildConfiguration) { + guard !logLevel.isQuiet else { return } self.queue.sync { self.progressAnimation.clear() self.outputStream.send("Building for \(configuration == .debug ? "debugging" : "production")...\n") @@ -484,7 +490,7 @@ final class LLBuildProgressTracker: LLBuildBuildSystemDelegate, SwiftCompilerOut self.progressAnimation.complete(success: success) self.delegate?.buildSystem(self.buildSystem, didFinishWithResult: success) - if success { + if !self.logLevel.isQuiet, success { let message = self.cancelled ? "Build \(subsetString)cancelled!" : "Build \(subsetString)complete!" self.progressAnimation.clear() self.outputStream.send("\(message) (\(duration.descriptionInSeconds))\n") diff --git a/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift b/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift index f555d2331d7..aa49fffcc94 100644 --- a/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift +++ b/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift @@ -221,9 +221,3 @@ extension ObservabilityMetadata { } } } - -extension Basics.Diagnostic.Severity { - fileprivate var isVerbose: Bool { - return self <= .info - } -} diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 5ccb814e118..4a9f36ce8a1 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -355,6 +355,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { } func emitEvent(_ message: SwiftBuild.SwiftBuildMessage, buildState: inout BuildState) throws { + guard !self.logLevel.isQuiet else { return } switch message { case .buildCompleted(let info): progressAnimation.complete(success: info.result == .ok) @@ -432,6 +433,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { switch operation.state { case .succeeded: + guard !self.logLevel.isQuiet else { return } progressAnimation.update(step: 100, total: 100, text: "") progressAnimation.complete(success: true) let duration = ContinuousClock.Instant.now - buildStartTime @@ -743,12 +745,6 @@ extension String { } } -extension Basics.Diagnostic.Severity { - var isVerbose: Bool { - self <= .info - } -} - fileprivate extension SwiftBuild.SwiftBuildMessage.DiagnosticInfo.Location { var userDescription: String? { switch self { diff --git a/Sources/XCBuildSupport/XCBuildDelegate.swift b/Sources/XCBuildSupport/XCBuildDelegate.swift index acf7f0c7317..3e139ce2eeb 100644 --- a/Sources/XCBuildSupport/XCBuildDelegate.swift +++ b/Sources/XCBuildSupport/XCBuildDelegate.swift @@ -69,11 +69,14 @@ extension XCBuildDelegate: XCBuildOutputParserDelegate { queue.async { self.didEmitProgressOutput = true let text = self.logLevel.isVerbose ? [info.executionDescription, info.commandLineDisplayString].compactMap { $0 }.joined(separator: "\n") : info.executionDescription - self.progressAnimation.update(step: self.percentComplete, total: 100, text: text) + if !self.logLevel.isQuiet { + self.progressAnimation.update(step: self.percentComplete, total: 100, text: text) + } self.buildSystem.delegate?.buildSystem(self.buildSystem, willStartCommand: BuildSystemCommand(name: "\(info.taskID)", description: info.executionDescription, verboseDescription: info.commandLineDisplayString)) self.buildSystem.delegate?.buildSystem(self.buildSystem, didStartCommand: BuildSystemCommand(name: "\(info.taskID)", description: info.executionDescription, verboseDescription: info.commandLineDisplayString)) } case .taskOutput(let info): + guard !self.logLevel.isQuiet else { return } queue.async { self.progressAnimation.clear() self.outputStream.send("\(info.data)\n") @@ -84,24 +87,28 @@ extension XCBuildDelegate: XCBuildOutputParserDelegate { self.buildSystem.delegate?.buildSystem(self.buildSystem, didFinishCommand: BuildSystemCommand(name: "\(info.taskID)", description: info.result.rawValue)) } case .buildDiagnostic(let info): + guard !self.logLevel.isQuiet else { return } queue.async { self.progressAnimation.clear() self.outputStream.send("\(info.message)\n") self.outputStream.flush() } case .taskDiagnostic(let info): + guard !self.logLevel.isQuiet else { return } queue.async { self.progressAnimation.clear() self.outputStream.send("\(info.message)\n") self.outputStream.flush() } case .targetDiagnostic(let info): + guard !self.logLevel.isQuiet else { return } queue.async { self.progressAnimation.clear() self.outputStream.send("\(info.message)\n") self.outputStream.flush() } case .buildOutput(let info): + guard !self.logLevel.isQuiet else { return } queue.async { self.progressAnimation.clear() self.outputStream.send("\(info.data)\n") @@ -121,7 +128,7 @@ extension XCBuildDelegate: XCBuildOutputParserDelegate { self.outputStream.flush() self.buildSystem.delegate?.buildSystem(self.buildSystem, didFinishWithResult: false) case .ok: - if self.didEmitProgressOutput { + if self.didEmitProgressOutput && !self.logLevel.isQuiet { self.progressAnimation.update(step: 100, total: 100, text: "Build succeeded") } self.buildSystem.delegate?.buildSystem(self.buildSystem, didFinishWithResult: true) diff --git a/Sources/XCBuildSupport/XcodeBuildSystem.swift b/Sources/XCBuildSupport/XcodeBuildSystem.swift index c6467c87bcf..8b5d478c287 100644 --- a/Sources/XCBuildSupport/XcodeBuildSystem.swift +++ b/Sources/XCBuildSupport/XcodeBuildSystem.swift @@ -240,6 +240,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { throw Diagnostics.fatalError } + guard !self.logLevel.isQuiet else { return } self.outputStream.send("Build complete!\n") self.outputStream.flush() } @@ -408,9 +409,3 @@ extension BuildSubset { } } } - -extension Basics.Diagnostic.Severity { - var isVerbose: Bool { - self <= .info - } -} diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index a9fa3e390bc..2bc886295a6 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -1330,6 +1330,85 @@ struct BuildCommandTestCases { } } + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8844"), + arguments: SupportedBuildSystemOnPlatform, BuildConfiguration.allCases + ) + func swiftBuildQuietLogLevel( + buildSystem: BuildSystemProvider.Kind, + configuration: BuildConfiguration + ) async throws { + try await withKnownIssue { + // GIVEN we have a simple test package + try await fixture(name: "Miscellaneous/SwiftBuild") { fixturePath in + //WHEN we build with the --quiet option + let (stdout, stderr) = try await executeSwiftBuild( + fixturePath, + configuration: configuration, + extraArgs: ["--quiet"], + buildSystem: buildSystem + ) + // THEN we should not see any output in stderr + #expect(stderr.isEmpty) + // AND no content in stdout + #expect(stdout.isEmpty) + } + } when: { + buildSystem == .swiftbuild && ( + ProcessInfo.hostOperatingSystem == .windows || ( + ProcessInfo.hostOperatingSystem == .linux && configuration == .release + ) + ) + } + } + + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8844"), + arguments: SupportedBuildSystemOnPlatform, BuildConfiguration.allCases + ) + func swiftBuildQuietLogLevelWithError( + buildSystem: BuildSystemProvider.Kind, + configuration: BuildConfiguration + ) async throws { + // GIVEN we have a simple test package + try await fixture(name: "Miscellaneous/SwiftBuild") { fixturePath in + let mainFilePath = fixturePath.appending("main.swift") + try localFileSystem.removeFileTree(mainFilePath) + try localFileSystem.writeFileContents( + mainFilePath, + string: """ + print("done" + """ + ) + + //WHEN we build with the --quiet option + let error = await #expect(throws: SwiftPMError.self) { + try await executeSwiftBuild( + fixturePath, + configuration: .debug, + extraArgs: ["--quiet"], + buildSystem: buildSystem + ) + } + + guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = try #require(error) else { + Issue.record("Incorrect error was raised.") + return + } + + if buildSystem == .swiftbuild { + // THEN we should see output in stderr + #expect(stderr.isEmpty == false) + // AND no content in stdout + #expect(stdout.isEmpty) + } else { + // THEN we should see content in stdout + #expect(stdout.isEmpty == false) + // AND no output in stderr + #expect(stderr.isEmpty) + } + } + } } extension Triple { diff --git a/Tests/CommandsTests/RunCommandTests.swift b/Tests/CommandsTests/RunCommandTests.swift index 6ab90b091a6..466b71c104a 100644 --- a/Tests/CommandsTests/RunCommandTests.swift +++ b/Tests/CommandsTests/RunCommandTests.swift @@ -395,4 +395,74 @@ struct RunCommandTests { } } + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8844"), + arguments: SupportedBuildSystemOnPlatform, BuildConfiguration.allCases + ) + func swiftRunQuietLogLevel( + buildSystem: BuildSystemProvider.Kind, + configuration: BuildConfiguration + ) async throws { + try await withKnownIssue(isIntermittent: true) { + // GIVEN we have a simple test package + try await fixture(name: "Miscellaneous/SwiftRun") { fixturePath in + //WHEN we run with the --quiet option + let (stdout, stderr) = try await executeSwiftRun( + fixturePath, + nil, + configuration: configuration, + extraArgs: ["--quiet"], + buildSystem: buildSystem + ) + // THEN we should not see any output in stderr + #expect(stderr.isEmpty) + // AND no content in stdout + #expect(stdout == "done\n") + } + } when: { + buildSystem == .swiftbuild && ProcessInfo.hostOperatingSystem == .linux + } + } + + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8844"), + arguments: SupportedBuildSystemOnPlatform, BuildConfiguration.allCases + ) + func swiftRunQuietLogLevelWithError( + buildSystem: BuildSystemProvider.Kind, + configuration: BuildConfiguration + ) async throws { + // GIVEN we have a simple test package + try await fixture(name: "Miscellaneous/SwiftRun") { fixturePath in + let mainFilePath = fixturePath.appending("main.swift") + try localFileSystem.removeFileTree(mainFilePath) + try localFileSystem.writeFileContents( + mainFilePath, + string: """ + print("done" + """ + ) + + //WHEN we run with the --quiet option + let error = await #expect(throws: SwiftPMError.self) { + try await executeSwiftRun( + fixturePath, + nil, + configuration: .debug, + extraArgs: ["--quiet"], + buildSystem: buildSystem + ) + } + + guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = try #require(error) else { + Issue.record("Incorrect error was raised.") + return + } + + // THEN we should see an output in stderr + #expect(stderr.isEmpty == false) + // AND no content in stdout + #expect(stdout.isEmpty) + } + } }