diff --git a/README.md b/README.md index d34f7f5..b822f10 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ Example output available in the [reporters](#reporters) section. | `--xcodeproj` | The path to the `xcodeproj` file if you don't know the path to the log and if the project doesn't have a `xcworkspace` file. It will generate the folder name for the project in the `DerivedData` folder using Xcode's hash algorithm and it will try to locate the latest Build Log inside that directory. | No * | | `--derived_data` | The path to the derived data folder if you are using `xcodebuild` to build your project with the `-derivedDataPath` option. | No | | `--output` | If specified, the JSON file will be written to the given path. If not defined, the command will output to the standard output. | No | + | `--rootOutput` | If specified, the HTML file will be written to the given folder, it has precedence over `output` if the folder doesn't exist will be created. It works with relative home path.`~` | No | | `--redacted` | If specified, the username will be replaced by the word `redacted` in the file paths contained in the logs. Useful for privacy reasons but slightly decreases the performance. | No | | `--without_build_specific_info` | If specified, build specific information will be removed from the logs (for example `bolnckhlbzxpxoeyfujluasoupft` will be removed from `DerivedData/Product-bolnckhlbzxpxoeyfujluasoupft/Build` ). Useful for grouping logs by its content. | No | | `--strictProjectName` | Used in conjunction with `--project`. If specified, a stricter name matching will be done for the project name. | No | diff --git a/Sources/XCLogParser/commands/ActionOptions.swift b/Sources/XCLogParser/commands/ActionOptions.swift index 2a50304..3a31bb3 100644 --- a/Sources/XCLogParser/commands/ActionOptions.swift +++ b/Sources/XCLogParser/commands/ActionOptions.swift @@ -43,15 +43,20 @@ public struct ActionOptions { /// With this option, a user can override it and provide a name that will be used in that identifier. public let machineName: String? + /// The rootOutput will generate the HTML output in the given folder + public let rootOutput: String + public init(reporter: Reporter, outputPath: String, redacted: Bool, withoutBuildSpecificInformation: Bool, - machineName: String? = nil) { + machineName: String? = nil, + rootOutput: String = "") { self.reporter = reporter self.outputPath = outputPath self.redacted = redacted self.withoutBuildSpecificInformation = withoutBuildSpecificInformation self.machineName = machineName + self.rootOutput = rootOutput } } diff --git a/Sources/XCLogParser/commands/CommandHandler.swift b/Sources/XCLogParser/commands/CommandHandler.swift index 9e648ee..1a1dc8d 100644 --- a/Sources/XCLogParser/commands/CommandHandler.swift +++ b/Sources/XCLogParser/commands/CommandHandler.swift @@ -45,7 +45,7 @@ public struct CommandHandler { let logManifestEntries = try logManifest.getWithLogOptions(logOptions) let reporterOutput = ReporterOutputFactory.makeReporterOutput(path: actionOptions.outputPath) let logReporter = actionOptions.reporter.makeLogReporter() - try logReporter.report(build: logManifestEntries, output: reporterOutput) + try logReporter.report(build: logManifestEntries, output: reporterOutput, rootOutput: actionOptions.rootOutput) } func handleDump(fromLogURL logURL: URL, options: ActionOptions) throws { @@ -54,7 +54,7 @@ public struct CommandHandler { withoutBuildSpecificInformation: options.withoutBuildSpecificInformation) // swiftlint:disable:this line_length let reporterOutput = ReporterOutputFactory.makeReporterOutput(path: options.outputPath) let logReporter = options.reporter.makeLogReporter() - try logReporter.report(build: activityLog, output: reporterOutput) + try logReporter.report(build: activityLog, output: reporterOutput, rootOutput: options.rootOutput) } func handleParse(fromLogURL logURL: URL, options: ActionOptions) throws { @@ -65,7 +65,7 @@ public struct CommandHandler { let buildSteps = try buildParser.parse(activityLog: activityLog) let reporterOutput = ReporterOutputFactory.makeReporterOutput(path: options.outputPath) let logReporter = options.reporter.makeLogReporter() - try logReporter.report(build: buildSteps, output: reporterOutput) + try logReporter.report(build: buildSteps, output: reporterOutput, rootOutput: options.rootOutput) } } diff --git a/Sources/XCLogParser/reporter/ChromeTracerReporter.swift b/Sources/XCLogParser/reporter/ChromeTracerReporter.swift index bf2b6d5..053d4b6 100644 --- a/Sources/XCLogParser/reporter/ChromeTracerReporter.swift +++ b/Sources/XCLogParser/reporter/ChromeTracerReporter.swift @@ -25,7 +25,7 @@ public struct ChromeTracerReporter: LogReporter { private let microSecondsPerSecond: Double = 1_000_000 - public func report(build: Any, output: ReporterOutput) throws { + public func report(build: Any, output: ReporterOutput, rootOutput: String) throws { guard let steps = build as? BuildStep else { throw XCLogParserError.errorCreatingReport("Type not supported \(type(of: build))") } diff --git a/Sources/XCLogParser/reporter/FlatJsonReporter.swift b/Sources/XCLogParser/reporter/FlatJsonReporter.swift index 01bc043..08c1dbd 100644 --- a/Sources/XCLogParser/reporter/FlatJsonReporter.swift +++ b/Sources/XCLogParser/reporter/FlatJsonReporter.swift @@ -21,7 +21,7 @@ import Foundation public struct FlatJsonReporter: LogReporter { - public func report(build: Any, output: ReporterOutput) throws { + public func report(build: Any, output: ReporterOutput, rootOutput: String) throws { guard let steps = build as? BuildStep else { throw XCLogParserError.errorCreatingReport("Type not supported \(type(of: build))") } diff --git a/Sources/XCLogParser/reporter/HtmlReporter.swift b/Sources/XCLogParser/reporter/HtmlReporter.swift index 3946bc3..fc17b39 100644 --- a/Sources/XCLogParser/reporter/HtmlReporter.swift +++ b/Sources/XCLogParser/reporter/HtmlReporter.swift @@ -23,7 +23,7 @@ import Foundation /// It uses the html and javascript files from the Resources folder as templates public struct HtmlReporter: LogReporter { - public func report(build: Any, output: ReporterOutput) throws { + public func report(build: Any, output: ReporterOutput, rootOutput: String) throws { guard let steps = build as? BuildStep else { throw XCLogParserError.errorCreatingReport("Type not supported \(type(of: build))") } @@ -33,16 +33,25 @@ public struct HtmlReporter: LogReporter { guard let jsonString = String(data: json, encoding: .utf8) else { throw XCLogParserError.errorCreatingReport("Can't generate the JSON file.") } - try writeHtmlReport(for: steps, jsonString: jsonString, output: output) + try writeHtmlReport(for: steps, jsonString: jsonString, output: output, rootOutput: rootOutput) } - private func writeHtmlReport(for build: BuildStep, jsonString: String, output: ReporterOutput) throws { + private func writeHtmlReport(for build: BuildStep, + jsonString: String, + output: ReporterOutput, + rootOutput: String) throws { var path = "build/xclogparser/reports" if let output = output as? FileOutput { path = output.path } + if !rootOutput.isEmpty { + path = FileOutput(path: rootOutput).path + } let fileManager = FileManager.default - let buildDir = "\(path)/\(dirFor(build: build))" + var buildDir = "\(path)/\(dirFor(build: build))" + if !rootOutput.isEmpty { + buildDir = path + } try fileManager.createDirectory( atPath: "\(buildDir)/css", withIntermediateDirectories: true, @@ -66,5 +75,4 @@ public struct HtmlReporter: LogReporter { dateFormatter.dateFormat = "YYYYMMddHHmmss" return dateFormatter.string(from: Date(timeIntervalSince1970: Double(build.startTimestamp))) } - } diff --git a/Sources/XCLogParser/reporter/IssuesReporter.swift b/Sources/XCLogParser/reporter/IssuesReporter.swift index 254e6ea..9bd4b68 100644 --- a/Sources/XCLogParser/reporter/IssuesReporter.swift +++ b/Sources/XCLogParser/reporter/IssuesReporter.swift @@ -26,7 +26,7 @@ struct Issues: Codable { public struct IssuesReporter: LogReporter { - public func report(build: Any, output: ReporterOutput) throws { + public func report(build: Any, output: ReporterOutput, rootOutput: String) throws { guard let steps = build as? BuildStep else { throw XCLogParserError.errorCreatingReport("Type not supported \(type(of: build))") } diff --git a/Sources/XCLogParser/reporter/JsonReporter.swift b/Sources/XCLogParser/reporter/JsonReporter.swift index c34c974..e5957a2 100644 --- a/Sources/XCLogParser/reporter/JsonReporter.swift +++ b/Sources/XCLogParser/reporter/JsonReporter.swift @@ -21,7 +21,7 @@ import Foundation public struct JsonReporter: LogReporter { - public func report(build: Any, output: ReporterOutput) throws { + public func report(build: Any, output: ReporterOutput, rootOutput: String) throws { switch build { case let steps as BuildStep: try report(encodable: steps, output: output) diff --git a/Sources/XCLogParser/reporter/LogReporter.swift b/Sources/XCLogParser/reporter/LogReporter.swift index d06c186..958f215 100644 --- a/Sources/XCLogParser/reporter/LogReporter.swift +++ b/Sources/XCLogParser/reporter/LogReporter.swift @@ -21,6 +21,6 @@ import Foundation public protocol LogReporter { - func report(build: Any, output: ReporterOutput) throws + func report(build: Any, output: ReporterOutput, rootOutput: String) throws } diff --git a/Sources/XCLogParser/reporter/SummaryJsonReporter.swift b/Sources/XCLogParser/reporter/SummaryJsonReporter.swift index f9a756a..06e9c0e 100644 --- a/Sources/XCLogParser/reporter/SummaryJsonReporter.swift +++ b/Sources/XCLogParser/reporter/SummaryJsonReporter.swift @@ -21,7 +21,7 @@ import Foundation public struct SummaryJsonReporter: LogReporter { - public func report(build: Any, output: ReporterOutput) throws { + public func report(build: Any, output: ReporterOutput, rootOutput: String) throws { guard let steps = build as? BuildStep else { throw XCLogParserError.errorCreatingReport("Type not supported \(type(of: build))") } diff --git a/Sources/XCLogParserApp/commands/CommonCommand.swift b/Sources/XCLogParserApp/commands/CommonCommand.swift index 4070bae..a99858f 100644 --- a/Sources/XCLogParserApp/commands/CommonCommand.swift +++ b/Sources/XCLogParserApp/commands/CommonCommand.swift @@ -69,3 +69,7 @@ let outputOption = Option( defaultValue: "", usage: "Optional. Path to which the report will be written to." + "If not specified, the report will be written to the standard output") +let rootOutputOption = Option(key: "rootOutput", + defaultValue: "", + usage: "Optional. Add the project output into the given current path" + + "i.e: myGivenPath/report.json") diff --git a/Sources/XCLogParserApp/commands/ParseCommand.swift b/Sources/XCLogParserApp/commands/ParseCommand.swift index 6eb04ba..00d3b28 100644 --- a/Sources/XCLogParserApp/commands/ParseCommand.swift +++ b/Sources/XCLogParserApp/commands/ParseCommand.swift @@ -62,7 +62,8 @@ struct ParseCommand: CommandProtocol { outputPath: options.output, redacted: options.redacted, withoutBuildSpecificInformation: options.withoutBuildSpecificInformation, - machineName: options.machineName.isEmpty ? nil : options.machineName) + machineName: options.machineName.isEmpty ? nil : options.machineName, + rootOutput: options.rootOutput) let action = Action.parse(options: actionOptions) let command = Command(logOptions: logOptions, action: action) do { @@ -87,6 +88,7 @@ struct ParseOptions: OptionsProtocol { let withoutBuildSpecificInformation: Bool let strictProjectName: Bool let output: String + let rootOutput: String static func create(_ logFile: String) -> (_ derivedData: String) @@ -98,9 +100,10 @@ struct ParseOptions: OptionsProtocol { -> (_ redacted: Bool) -> (_ withoutBuildSpecificInformation: Bool) -> (_ strictProjectName: Bool) - -> (_ output: String) -> ParseOptions { + -> (_ output: String) + -> (_ rootOutput: String) -> ParseOptions { return { derivedData in { projectName in { workspace in { xcodeproj in { reporter in { machineName - in { redacted in { withoutBuildSpecificInformation in { strictProjectName in { output in + in { redacted in { withoutBuildSpecificInformation in { strictProjectName in { output in { rootOutput in self.init(logFile: logFile, derivedData: derivedData, projectName: projectName, @@ -111,8 +114,9 @@ struct ParseOptions: OptionsProtocol { redacted: redacted, withoutBuildSpecificInformation: withoutBuildSpecificInformation, strictProjectName: strictProjectName, - output: output) - }}}}}}}}}} + output: output, + rootOutput: rootOutput) + }}}}}}}}}}} } static func evaluate(_ mode: CommandMode) -> Result>> { @@ -136,6 +140,8 @@ struct ParseOptions: OptionsProtocol { <*> mode <| withoutBuildSpecificInformationSwitch <*> mode <| strictProjectNameSwitch <*> mode <| outputOption + <*> mode <| rootOutputOption + } func hasValidLogOptions() -> Bool { diff --git a/Tests/XCLogParserTests/IssuesReporterTests.swift b/Tests/XCLogParserTests/IssuesReporterTests.swift index 3f290bc..c80f169 100644 --- a/Tests/XCLogParserTests/IssuesReporterTests.swift +++ b/Tests/XCLogParserTests/IssuesReporterTests.swift @@ -28,7 +28,7 @@ class IssuesReporterTests: XCTestCase { // Build with no issues let buildStep = getBuildStep() let fakeOutput = FakeOutput() - try issuesReporter.report(build: buildStep, output: fakeOutput) + try issuesReporter.report(build: buildStep, output: fakeOutput, rootOutput: "") guard let emptyIssues = try fakeOutput.getIssues() else { XCTFail("Issues should not be nil") return @@ -38,7 +38,7 @@ class IssuesReporterTests: XCTestCase { // Build with issues let stepWithIssues = getBuildStepWithIssues() let fakeOutputWithIssues = FakeOutput() - try issuesReporter.report(build: stepWithIssues, output: fakeOutputWithIssues) + try issuesReporter.report(build: stepWithIssues, output: fakeOutputWithIssues, rootOutput: "") guard let issues = try fakeOutputWithIssues.getIssues() else { XCTFail("Issues should not be nil") return