diff --git a/Sources/XCLogParser/parser/BuildStep.swift b/Sources/XCLogParser/parser/BuildStep.swift index 4c082f5..d97a1b1 100644 --- a/Sources/XCLogParser/parser/BuildStep.swift +++ b/Sources/XCLogParser/parser/BuildStep.swift @@ -98,6 +98,11 @@ public enum DetailStepType: String, Encodable { return .cCompilation case Prefix("CompileSwift "): return .swiftCompilation + // The same task under the name the integrated driver uses, for a single file and for a batch + // of them. Both spellings show up in one log: targets that compile whole module keep + // emitting `CompileSwift`. + case Prefix("SwiftCompile "): + return .swiftCompilation case Prefix("Ld "): return .linker case Prefix("PhaseScriptExecution "): @@ -122,6 +127,10 @@ public enum DetailStepType: String, Encodable { return .XIBCompilation case Prefix("CompileSwiftSources "): return .swiftAggregatedCompilation + // The driver job that replaced `CompileSwiftSources`. The trailing space matters: without it + // the prefix also swallows `SwiftDriverJobDiscovery`, which is not a compilation step. + case Prefix("SwiftDriver "): + return .swiftAggregatedCompilation case Prefix("PrecompileSwiftBridgingHeader "): return .precompileBridgingHeader case Prefix("ValidateEmbeddedBinary "): diff --git a/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift b/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift index 189a1eb..328241f 100644 --- a/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift +++ b/Sources/XCLogParser/parser/IDEActivityLogSection+Parsing.swift @@ -86,7 +86,10 @@ extension IDEActivityLogSection { public func getSwiftIndividualSteps(buildStep: BuildStep, parentCommandDetailDesc: String, currentIndex: inout Int) -> [BuildStep]? { - let pattern = #"^CompileSwift\s\w+\s\w+\s.+\.swift\s"# + // Both spellings of the task, which appear side by side in a single log. Without the + // integrated driver's one the guard stops recognizing a command that already names the + // files, and the steps synthesized below duplicate the subSections the log provides. + let pattern = #"^(?:CompileSwift|SwiftCompile)\s\w+\s\w+\s.+\.swift\s"# guard commandDetailDesc.range(of: pattern, options: .regularExpression) == nil else { return nil } diff --git a/Tests/XCLogParserTests/DetailStepTypeTests.swift b/Tests/XCLogParserTests/DetailStepTypeTests.swift new file mode 100644 index 0000000..6f01cb1 --- /dev/null +++ b/Tests/XCLogParserTests/DetailStepTypeTests.swift @@ -0,0 +1,70 @@ +// Copyright (c) 2019 Spotify AB. +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import XCTest +@testable import XCLogParser + +/// Signatures in this file are taken from an Xcode 26.5 build log, with paths and module names +/// replaced. A single log carries both spellings of the Swift compilation tasks, so both have to +/// be recognized. +class DetailStepTypeTests: XCTestCase { + + func testSwiftCompileOfASingleFileIsASwiftCompilation() { + let signature = "SwiftCompile normal arm64 /path/to/Foo/Bar.swift " + + "(in target 'Foo' from project 'Foo')" + + XCTAssertEqual(.swiftCompilation, DetailStepType.getDetailType(signature: signature)) + } + + func testSwiftCompileOfABatchIsASwiftCompilation() { + let signature = "SwiftCompile normal arm64 Compiling\\ Bar.swift,\\ Baz.swift " + + "(in target 'Foo' from project 'Foo')" + + XCTAssertEqual(.swiftCompilation, DetailStepType.getDetailType(signature: signature)) + } + + func testCompileSwiftIsStillASwiftCompilation() { + let signature = "CompileSwift normal arm64 /path/to/Foo/Bar.swift" + + XCTAssertEqual(.swiftCompilation, DetailStepType.getDetailType(signature: signature)) + } + + func testSwiftDriverIsAnAggregatedCompilation() { + let signature = "SwiftDriver Foo normal arm64 com.apple.xcode.tools.swift.compiler " + + "(in target 'Foo' from project 'Foo')" + + XCTAssertEqual(.swiftAggregatedCompilation, DetailStepType.getDetailType(signature: signature)) + } + + func testCompileSwiftSourcesIsStillAnAggregatedCompilation() { + let signature = "CompileSwiftSources normal arm64 com.apple.xcode.tools.swift.compiler" + + XCTAssertEqual(.swiftAggregatedCompilation, DetailStepType.getDetailType(signature: signature)) + } + + /// `SwiftDriverJobDiscovery` shares its first eleven characters with `SwiftDriver`, and it is + /// not a compilation step: a prefix without the trailing space would swallow it. + func testSwiftDriverJobDiscoveryIsNotAnAggregatedCompilation() { + let signature = "SwiftDriverJobDiscovery normal arm64 Emitting module for Foo " + + "(in target 'Foo' from project 'Foo')" + + XCTAssertEqual(.other, DetailStepType.getDetailType(signature: signature)) + } + +} diff --git a/Tests/XCLogParserTests/SwiftIndividualStepsTests.swift b/Tests/XCLogParserTests/SwiftIndividualStepsTests.swift new file mode 100644 index 0000000..42a56a3 --- /dev/null +++ b/Tests/XCLogParserTests/SwiftIndividualStepsTests.swift @@ -0,0 +1,87 @@ +// Copyright (c) 2019 Spotify AB. +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import XCTest +@testable import XCLogParser + +class SwiftIndividualStepsTests: XCTestCase { + + /// From Xcode 14 on, a batch compilation names its files in the command and the log carries a + /// subSection per file. Synthesizing steps here would report every file twice. + func testNoStepsAreSynthesizedWhenTheCommandNamesTheFiles() { + let command = "SwiftCompile normal arm64 Compiling\\ Bar.swift,\\ Baz.swift " + + "/path/to/Foo/Bar.swift /path/to/Foo/Baz.swift (in target 'Foo' from project 'Foo')" + let section = makeSection(commandDetailDesc: command) + var index = 0 + + let steps = section.getSwiftIndividualSteps(buildStep: makeStep(), + parentCommandDetailDesc: "", + currentIndex: &index) + + XCTAssertNil(steps) + } + + /// A whole module command lists the files without compiling them as separate sections, so the + /// per-file steps have to be derived from the command itself. + func testStepsAreSynthesizedForAWholeModuleCommand() { + let command = "/usr/bin/swiftc -module-name Foo -whole-module-optimization " + + "/path/to/Foo/Bar.swift /path/to/Foo/Baz.swift" + let section = makeSection(commandDetailDesc: command) + var index = 0 + + let steps = section.getSwiftIndividualSteps(buildStep: makeStep(), + parentCommandDetailDesc: "", + currentIndex: &index) + + XCTAssertEqual(["Compile /path/to/Foo/Bar.swift", "Compile /path/to/Foo/Baz.swift"], + steps?.map { $0.title }) + } + + private func makeStep() -> BuildStep { + makeFakeBuildStep(title: "Compiling Bar.swift, Baz.swift", + type: .detail, + detailStepType: .swiftCompilation, + startTimestamp: 0, + fetchedFromCache: false) + } + + private func makeSection(commandDetailDesc: String) -> IDEActivityLogSection { + IDEActivityLogSection(sectionType: 1, + domainType: "", + title: "Compiling Bar.swift, Baz.swift", + signature: "SwiftCompile normal arm64", + timeStartedRecording: 0, + timeStoppedRecording: 0, + subSections: [], + text: "", + messages: [], + wasCancelled: false, + isQuiet: false, + wasFetchedFromCache: false, + subtitle: "", + location: DVTDocumentLocation(documentURLString: "", timestamp: 0), + commandDetailDesc: commandDetailDesc, + uniqueIdentifier: "", + localizedResultString: "", + xcbuildSignature: "", + attachments: [], + unknown: 0) + } + +}