diff --git a/Resources/js/app.js b/Resources/js/app.js index d7c5bf2b..f46a6523 100644 --- a/Resources/js/app.js +++ b/Resources/js/app.js @@ -195,14 +195,9 @@ function loadMainData() { targets = buildData.filter(function (step) { return step.type === 'target' && step.fetchedFromCache === false; }); - cFiles = buildData.filter(function (step) { - return step.type === 'detail' && step.detailStepType === cCompilation - && step.fetchedFromCache === false; - }); - swiftFiles = buildData.filter(function (step) { - return step.type === 'detail' && step.detailStepType === swiftCompilation - && step.fetchedFromCache === false; - }); + // buildData only has the main step and the targets here, the counts come from top_files.js + cFiles = new Array(cFilesCount); + swiftFiles = new Array(swiftFilesCount); } function loadTargetData(target) { diff --git a/Sources/XCLogParser/generated/HtmlReporterResources.swift b/Sources/XCLogParser/generated/HtmlReporterResources.swift index 6610aed0..9849ee7f 100644 --- a/Sources/XCLogParser/generated/HtmlReporterResources.swift +++ b/Sources/XCLogParser/generated/HtmlReporterResources.swift @@ -287,14 +287,9 @@ function loadMainData() { targets = buildData.filter(function (step) { return step.type === 'target' && step.fetchedFromCache === false; }); - cFiles = buildData.filter(function (step) { - return step.type === 'detail' && step.detailStepType === cCompilation - && step.fetchedFromCache === false; - }); - swiftFiles = buildData.filter(function (step) { - return step.type === 'detail' && step.detailStepType === swiftCompilation - && step.fetchedFromCache === false; - }); + // buildData only has the main step and the targets here, the counts come from top_files.js + cFiles = new Array(cFilesCount); + swiftFiles = new Array(swiftFilesCount); } function loadTargetData(target) { diff --git a/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift b/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift index 189a1eb7..048a1b8a 100644 --- a/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift +++ b/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift @@ -44,27 +44,18 @@ extension IDEActivityLogSection { // for the `(in target 'ABC' from project Project)` string let isFlatten = subSections.contains { $0.getTargetFromCommand() != nil } if isFlatten { - let mainTarget = "$MainTarget" let targetsDictionary = subSections.reduce( [String: IDEActivityLogSection]()) { targets, subSection -> [String: IDEActivityLogSection] in - // some substeps belong to the root project, we use a fixed name for them - let targetName = subSection.getTargetFromCommand() ?? mainTarget + // sections without a target (build description, module scans) are grouped as one + let targetName = subSection.getTargetFromCommand() ?? Self.buildSystemTarget let target = getOrBuildTarget(targetName, in: targets, using: subSection) target.subSections.append(subSection) var updatedTargets = targets updatedTargets[targetName] = target return updatedTargets } - let nonMainTargets = targetsDictionary.filter { (key: String, _) -> Bool in - return key != mainTarget - } - var subSections = [IDEActivityLogSection]() - if let mainTarget = targetsDictionary[mainTarget] { - subSections.append(contentsOf: mainTarget.subSections) - } - subSections.append(contentsOf: nonMainTargets.values) let withTargets = self - withTargets.subSections = subSections.sorted { lhs, rhs -> Bool in + withTargets.subSections = targetsDictionary.values.sorted { lhs, rhs -> Bool in lhs.timeStartedRecording < rhs.timeStartedRecording } return withTargets @@ -136,10 +127,12 @@ extension IDEActivityLogSection { return buildTargetSection(name, with: section) } + static let buildSystemTarget = "Build system" + private func buildTargetSection(_ name: String, with section: IDEActivityLogSection) -> IDEActivityLogSection { return IDEActivityLogSection(sectionType: 2, domainType: section.domainType, - title: "Build target \(name)", + title: name == Self.buildSystemTarget ? name : "Build target \(name)", signature: name, timeStartedRecording: section.timeStartedRecording, timeStoppedRecording: section.timeStoppedRecording, diff --git a/Sources/XCLogParser/reporter/HtmlReporter.swift b/Sources/XCLogParser/reporter/HtmlReporter.swift index c3b59250..821ba872 100644 --- a/Sources/XCLogParser/reporter/HtmlReporter.swift +++ b/Sources/XCLogParser/reporter/HtmlReporter.swift @@ -243,17 +243,16 @@ public struct HtmlReporter: LogReporter { swiftTypeCheckTimes.sort { $0.durationMS > $1.durationMS } let topTypeChecks = Array(swiftTypeCheckTimes.prefix(Self.maxSwifTypeChecks)) - try write( - slowestSwiftFiles: Array(swiftSteps.prefix(Self.maxSlowestFiles)), - slowestCFiles: Array(cSteps.prefix(Self.maxSlowestFiles)), - swiftFunctionTimes: topFunctions, - swiftTypeCheckTimes: topTypeChecks, - toDir: buildDir) + try write(swiftSteps: swiftSteps, + cSteps: cSteps, + swiftFunctionTimes: topFunctions, + swiftTypeCheckTimes: topTypeChecks, + toDir: buildDir) } - private func write(slowestSwiftFiles: [BuildStep], - slowestCFiles: [BuildStep], + private func write(swiftSteps: [BuildStep], + cSteps: [BuildStep], swiftFunctionTimes: [SwiftFunctionTime], swiftTypeCheckTimes: [SwiftTypeCheck], toDir buildDir: String) throws { @@ -261,8 +260,8 @@ public struct HtmlReporter: LogReporter { encoder.outputFormatting = .prettyPrinted let functionsJson = try encoder.encode(swiftFunctionTimes) let typeChecksJson = try encoder.encode(swiftTypeCheckTimes) - let swiftJson = try encoder.encode(slowestSwiftFiles) - let cJson = try encoder.encode(slowestCFiles) + let swiftJson = try encoder.encode(Array(swiftSteps.prefix(Self.maxSlowestFiles))) + let cJson = try encoder.encode(Array(cSteps.prefix(Self.maxSlowestFiles))) guard let functionsString = String(data: functionsJson, encoding: .utf8), let typeChecksString = String(data: typeChecksJson, encoding: .utf8), let swiftString = String(data: swiftJson, encoding: .utf8), @@ -278,6 +277,10 @@ public struct HtmlReporter: LogReporter { const topSwiftFunctions = \(functionsString); const topSwifTypeChecks = \(typeChecksString); + + const swiftFilesCount = \(swiftSteps.count); + + const cFilesCount = \(cSteps.count); """.write(toFile: "\(buildDir)/js/top_files.js", atomically: true, encoding: .utf8) } } diff --git a/Tests/XCLogParserTests/GroupedByTargetTests.swift b/Tests/XCLogParserTests/GroupedByTargetTests.swift new file mode 100644 index 00000000..250bf087 --- /dev/null +++ b/Tests/XCLogParserTests/GroupedByTargetTests.swift @@ -0,0 +1,61 @@ +import XCTest +@testable import XCLogParser + +class GroupedByTargetTests: XCTestCase { + + func testUntaggedSectionsAreGroupedUnderOneTarget() { + let main = makeSection(title: "Build", subSections: [ + makeSection(title: "Create build description", start: 0), + makeSection(title: "Compiling Clang module Foundation", start: 1), + makeSection(title: "CompileC a.o", command: "CompileC a.o (in target 'Foo' from project 'P')", start: 2), + makeSection(title: "Compiling Clang module UIKit", start: 3), + makeSection(title: "CompileC b.o", command: "CompileC b.o (in target 'Bar' from project 'P')", start: 4), + ]) + + let grouped = main.groupedByTarget() + + XCTAssertEqual(["Build system", "Build target Foo", "Build target Bar"], + grouped.subSections.map { $0.title }) + let buildSystem = grouped.subSections[0] + XCTAssertEqual(["Create build description", + "Compiling Clang module Foundation", + "Compiling Clang module UIKit"], + buildSystem.subSections.map { $0.title }) + } + + func testTargetsKeepTheirOrder() { + let main = makeSection(title: "Build", subSections: [ + makeSection(title: "Ld Bar", command: "Ld Bar (in target 'Bar' from project 'P')", start: 2), + makeSection(title: "CompileC a.o", command: "CompileC a.o (in target 'Foo' from project 'P')", start: 1), + ]) + + XCTAssertEqual(["Build target Foo", "Build target Bar"], + main.groupedByTarget().subSections.map { $0.title }) + } + + private func makeSection(title: String, + command: String = "", + start: Double = 0, + subSections: [IDEActivityLogSection] = []) -> IDEActivityLogSection { + return IDEActivityLogSection(sectionType: 1, + domainType: "", + title: title, + signature: "", + timeStartedRecording: start, + timeStoppedRecording: start, + subSections: subSections, + text: "", + messages: [], + wasCancelled: false, + isQuiet: false, + wasFetchedFromCache: false, + subtitle: "", + location: DVTDocumentLocation(documentURLString: "", timestamp: 0.0), + commandDetailDesc: command, + uniqueIdentifier: "", + localizedResultString: "", + xcbuildSignature: "", + attachments: [], + unknown: 0) + } +} diff --git a/Tests/XCLogParserTests/HtmlReporterTests.swift b/Tests/XCLogParserTests/HtmlReporterTests.swift new file mode 100644 index 00000000..801da5fb --- /dev/null +++ b/Tests/XCLogParserTests/HtmlReporterTests.swift @@ -0,0 +1,30 @@ +import XCTest +@testable import XCLogParser + +class HtmlReporterTests: XCTestCase { + + func testIndexFileCountsComeFromAllTargets() throws { + let compile = { (title: String, type: DetailStepType) -> BuildStep in + makeFakeBuildStep(title: title, type: .detail, detailStepType: type, + startTimestamp: 0, fetchedFromCache: false) + } + let foo = makeFakeBuildStep(title: "Build target Foo", type: .target, detailStepType: .none, + startTimestamp: 0, fetchedFromCache: false) + .with(subSteps: [compile("Compile a.swift", .swiftCompilation), + compile("Compile b.swift", .swiftCompilation), + compile("Compile c.c", .cCompilation)]) + let bar = makeFakeBuildStep(title: "Build target Bar", type: .target, detailStepType: .none, + startTimestamp: 0, fetchedFromCache: false) + .with(subSteps: [compile("Compile d.swift", .swiftCompilation)]) + let build = makeFakeBuildStep(title: "Build", type: .main, detailStepType: .none, + startTimestamp: 0, fetchedFromCache: false) + .with(subSteps: [foo, bar]) + let dir = try TestUtils.createRandomTestDir() + + try HtmlReporter().report(build: build, output: FileOutput(path: dir.path), rootOutput: dir.path) + + let topFiles = try String(contentsOf: dir.appendingPathComponent("js/top_files.js")) + XCTAssertTrue(topFiles.contains("const swiftFilesCount = 3;")) + XCTAssertTrue(topFiles.contains("const cFilesCount = 1;")) + } +} diff --git a/Tests/XCLogParserTests/XCTestManifests.swift b/Tests/XCLogParserTests/XCTestManifests.swift index e18faedd..deaad404 100644 --- a/Tests/XCLogParserTests/XCTestManifests.swift +++ b/Tests/XCLogParserTests/XCTestManifests.swift @@ -28,6 +28,25 @@ extension ChromeTracerOutputTests { ] } +extension GroupedByTargetTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__GroupedByTargetTests = [ + ("testTargetsKeepTheirOrder", testTargetsKeepTheirOrder), + ("testUntaggedSectionsAreGroupedUnderOneTarget", testUntaggedSectionsAreGroupedUnderOneTarget), + ] +} + +extension HtmlReporterTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__HtmlReporterTests = [ + ("testIndexFileCountsComeFromAllTargets", testIndexFileCountsComeFromAllTargets), + ] +} + extension IssuesReporterTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -146,6 +165,8 @@ public func __allTests() -> [XCTestCaseEntry] { return [ testCase(ActivityParserTests.__allTests__ActivityParserTests), testCase(ChromeTracerOutputTests.__allTests__ChromeTracerOutputTests), + testCase(GroupedByTargetTests.__allTests__GroupedByTargetTests), + testCase(HtmlReporterTests.__allTests__HtmlReporterTests), testCase(IssuesReporterTests.__allTests__IssuesReporterTests), testCase(LexRedactorTests.__allTests__LexRedactorTests), testCase(LexerTests.__allTests__LexerTests),