diff --git a/Sources/Build/LLBuildCommands.swift b/Sources/Build/LLBuildCommands.swift index 0e127959402..aeb9442eb19 100644 --- a/Sources/Build/LLBuildCommands.swift +++ b/Sources/Build/LLBuildCommands.swift @@ -15,6 +15,15 @@ import Foundation import LLBuildManifest import SPMBuildCore import SPMLLBuild +import PackageModel + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import SwiftDriver +@_implementationOnly import SwiftOptions +#else +import SwiftDriver +import SwiftOptions +#endif import class TSCBasic.LocalFileOutputByteStream @@ -426,13 +435,22 @@ final class PackageStructureCommand: CustomLLBuildCommand { /// For instance, building with or without `--verbose` should not cause a full rebuild. private func normalizeBuildParameters( _ buildParameters: BuildParameters - ) -> BuildParameters { + ) throws -> BuildParameters { var buildParameters = buildParameters buildParameters.outputParameters = BuildParameters.Output( isColorized: false, isVerbose: false ) buildParameters.workers = 1 + + let optionTable = OptionTable() + let parsedOptions = try optionTable.parse(Array(buildParameters.flags.swiftCompilerFlags), for: .batch) + let buildRecordInfoHash = BuildRecordArguments.computeHash(parsedOptions) + + // Replace the swiftCompilerFlags with a hash of themselves where arguments that + // do not affect incremental builds are removed. + buildParameters.flags.swiftCompilerFlags = [buildRecordInfoHash] + return buildParameters } } diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 0218326f213..f1cc0e3fa3f 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -1125,7 +1125,106 @@ struct BuildCommandTestCases { } } - + private static func buildSystemAndOutputLocation() throws -> [(BuildSystemProvider.Kind, Basics.RelativePath)] { + return try SupportedBuildSystemOnPlatform.map { buildSystem in + let triple = try UserToolchain.default.targetTriple.withoutVersion() + let base = try RelativePath(validating: ".build") + let debugFolderComponents = buildSystem.binPathSuffixes(for: .debug) + switch buildSystem { + case .xcode: + let path = base.appending(components: debugFolderComponents) + return ( + buildSystem, + triple.platformName() == "macosx" ? path.appending("ExecutableNew") : path + .appending("ExecutableNew.swiftmodule") + .appending("Project") + .appending("\(triple).swiftsourceinfo") + ) + case .swiftbuild: + let path = base.appending(triple.tripleString) + .appending(components: debugFolderComponents) + return ( + buildSystem, + triple.platformName() == "macosx" ? path.appending("ExecutableNew") : path + .appending("ExecutableNew.swiftmodule") + .appending("Project") + .appending("\(triple).swiftsourceinfo") + ) + case .native: + return ( + buildSystem, + base.appending(components: debugFolderComponents) + .appending("ExecutableNew.build") + .appending("main.swift.o") + ) + } + } + } + + @Test(arguments: try buildSystemAndOutputLocation()) + func doesNotRebuildWithVerboseFlag( + buildSystem: BuildSystemProvider.Kind, + outputFile: Basics.RelativePath + ) async throws { + try await withKnownIssue("Sometimes failed to build due to a possible path issue", isIntermittent: true) { + try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in + _ = try await self.build( + [], + packagePath: fixturePath, + cleanAfterward: false, + buildSystem: buildSystem, + ) + + let mainOFile = fixturePath.appending(outputFile) + let initialMainOMtime = try FileManager.default.attributesOfItem(atPath: mainOFile.pathString)[.modificationDate] as? Date + + _ = try await self.build( + ["--verbose"], + packagePath: fixturePath, + cleanAfterward: false, + buildSystem: buildSystem, + ) + + let subsequentMainOMtime = try FileManager.default.attributesOfItem(atPath: mainOFile.pathString)[.modificationDate] as? Date + #expect(initialMainOMtime == subsequentMainOMtime, "Expected no rebuild to occur when using the verbose flag, but the file was modified.") + } + } when: { + buildSystem == .swiftbuild && ProcessInfo.hostOperatingSystem == .windows + } + } + + @Test(arguments: try buildSystemAndOutputLocation()) + func doesNotRebuildWithSwiftcArgsThatDontAffectIncrementalBuilds( + buildSystem: BuildSystemProvider.Kind, + outputFile: Basics.RelativePath + ) async throws { + try await withKnownIssue("Sometimes failed to build due to a possible path issue", isIntermittent: true) { + try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in + _ = try await self.build( + [], + packagePath: fixturePath, + cleanAfterward: false, + buildSystem: buildSystem, + ) + + let mainOFile = fixturePath.appending(outputFile) + let initialMainOMtime = try FileManager.default.attributesOfItem(atPath: mainOFile.pathString)[.modificationDate] as? Date + + _ = try await self.build( + ["-Xswiftc", "-diagnostic-style=llvm"], + packagePath: fixturePath, + cleanAfterward: false, + buildSystem: buildSystem, + ) + + let subsequentMainOMtime = try FileManager.default.attributesOfItem(atPath: mainOFile.pathString)[.modificationDate] as? Date + #expect(initialMainOMtime == subsequentMainOMtime, "Expected no rebuild to occur when supplying -diagnostic-style, but the file was modified.") + } + } when: { + buildSystem == .swiftbuild && ProcessInfo.hostOperatingSystem == .windows + } + } + @Test( .SWBINTTODO("Test failed because of missing plugin support in the PIF builder. This can be reinvestigated after the support is there."), .tags( @@ -1233,5 +1332,16 @@ struct BuildCommandTestCases { } +extension Triple { + func withoutVersion() throws -> Triple { + if isDarwin() { + let stringWithoutVersion = tripleString(forPlatformVersion: "") + return try Triple(stringWithoutVersion) + } else { + return self + } + } +} +