1414@testable import PackageModel
1515
1616import Basics
17+ import LLBuildManifest
18+ @_spi ( DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly)
19+ import PackageGraph
1720import SPMTestSupport
1821import XCTest
1922
2023import class TSCBasic. BufferedOutputByteStream
2124import class TSCBasic. InMemoryFileSystem
2225
26+ private func mockBuildOperation(
27+ buildParameters: BuildParameters ,
28+ cacheBuildManifest: Bool = false ,
29+ packageGraphLoader: @escaping ( ) -> ModulesGraph = { fatalError ( ) } ,
30+ scratchDirectory: AbsolutePath ,
31+ fs: any Basics . FileSystem ,
32+ observabilityScope: ObservabilityScope
33+ ) -> BuildOperation {
34+ return BuildOperation (
35+ productsBuildParameters: buildParameters,
36+ toolsBuildParameters: buildParameters,
37+ cacheBuildManifest: cacheBuildManifest,
38+ packageGraphLoader: packageGraphLoader,
39+ scratchDirectory: scratchDirectory,
40+ additionalFileRules: [ ] ,
41+ pkgConfigDirectories: [ ] ,
42+ dependenciesByRootPackageIdentity: [ : ] ,
43+ targetsByRootPackageIdentity: [ : ] ,
44+ outputStream: BufferedOutputByteStream ( ) ,
45+ logLevel: . info,
46+ fileSystem: fs,
47+ observabilityScope: observabilityScope
48+ )
49+ }
50+
2351final class BuildOperationTests : XCTestCase {
2452 func testDetectUnexpressedDependencies( ) throws {
25- let buildParameters = mockBuildParameters ( shouldDisableLocalRpath: false )
53+ let scratchDirectory = AbsolutePath ( " /path/to/build " )
54+ let triple = hostTriple
55+ let buildParameters = mockBuildParameters (
56+ buildPath: scratchDirectory. appending ( triple. tripleString) ,
57+ shouldDisableLocalRpath: false ,
58+ triple: triple
59+ )
2660
2761 let fs = InMemoryFileSystem ( files: [
2862 " \( buildParameters. dataPath) /debug/Lunch.build/Lunch.d " : " /Best.framework "
2963 ] )
3064
3165 let observability = ObservabilitySystem . makeForTesting ( )
32- let buildOp = BuildOperation (
33- productsBuildParameters: buildParameters,
34- toolsBuildParameters: buildParameters,
35- cacheBuildManifest: false ,
36- packageGraphLoader: { fatalError ( ) } ,
37- additionalFileRules: [ ] ,
38- pkgConfigDirectories: [ ] ,
39- dependenciesByRootPackageIdentity: [ : ] ,
40- targetsByRootPackageIdentity: [ : ] ,
41- outputStream: BufferedOutputByteStream ( ) ,
42- logLevel: . info,
43- fileSystem: fs,
44- observabilityScope: observability. topScope
66+ let buildOp = mockBuildOperation (
67+ buildParameters: buildParameters,
68+ scratchDirectory: scratchDirectory,
69+ fs: fs, observabilityScope: observability. topScope
4570 )
4671 buildOp. detectUnexpressedDependencies (
4772 availableLibraries: [
@@ -65,4 +90,85 @@ final class BuildOperationTests: XCTestCase {
6590 [ " target 'Lunch' has an unexpressed depedency on 'foo' " ]
6691 )
6792 }
93+
94+ func testDetectProductTripleChange( ) throws {
95+ let observability = ObservabilitySystem . makeForTesting ( )
96+ let fs = InMemoryFileSystem (
97+ emptyFiles: " /Pkg/Sources/ATarget/foo.swift "
98+ )
99+ let packageGraph = try loadModulesGraph (
100+ fileSystem: fs,
101+ manifests: [
102+ . createRootManifest(
103+ displayName: " SwitchTriple " ,
104+ path: " /Pkg " ,
105+ targets: [
106+ TargetDescription ( name: " ATarget " ) ,
107+ ]
108+ ) ,
109+ ] ,
110+ observabilityScope: observability. topScope
111+ )
112+ try withTemporaryDirectory { tmpDir in
113+ let scratchDirectory = tmpDir. appending ( " .build " )
114+ let fs = localFileSystem
115+ let triples = try [ Triple ( " x86_64-unknown-linux-gnu " ) , Triple ( " wasm32-unknown-wasi " ) ]
116+ var llbuildManifestByTriple : [ String : String ] = [ : ]
117+
118+ // Perform initial builds for each triple
119+ for triple in triples {
120+ let buildParameters = mockBuildParameters (
121+ buildPath: scratchDirectory. appending ( triple. tripleString) ,
122+ config: . debug,
123+ triple: triple
124+ )
125+ let buildOp = mockBuildOperation (
126+ buildParameters: buildParameters,
127+ cacheBuildManifest: false ,
128+ packageGraphLoader: { packageGraph } ,
129+ scratchDirectory: scratchDirectory,
130+ fs: fs, observabilityScope: observability. topScope
131+ )
132+ // Generate initial llbuild manifest
133+ let _ = try buildOp. getBuildDescription ( )
134+ // Record the initial llbuild manifest as expected one
135+ llbuildManifestByTriple [ triple. tripleString] = try fs. readFileContents ( buildParameters. llbuildManifest)
136+ }
137+
138+ XCTAssertTrue ( fs. exists ( scratchDirectory. appending ( " debug.yaml " ) ) )
139+ // FIXME: There should be a build database with manifest cache after the initial build.
140+ // The initial build usually triggered with `cacheBuildManifest=false` because llbuild
141+ // manifest file and description.json are not found. However, with `cacheBuildManifest=false`,
142+ // `BuildOperation` does not trigger "PackageStructure" build, thus the initial build does
143+ // not record the manifest cache. So "getBuildDescription" doesn't create build.db for the
144+ // initial planning and the second build always need full-planning.
145+ //
146+ // XCTAssertTrue(fs.exists(scratchDirectory.appending("build.db")))
147+
148+ // Perform incremental build several times and switch triple for each time
149+ for _ in 0 ..< 4 {
150+ for triple in triples {
151+ let buildParameters = mockBuildParameters (
152+ buildPath: scratchDirectory. appending ( triple. tripleString) ,
153+ config: . debug,
154+ triple: triple
155+ )
156+ let buildOp = mockBuildOperation (
157+ buildParameters: buildParameters,
158+ cacheBuildManifest: true ,
159+ packageGraphLoader: { packageGraph } ,
160+ scratchDirectory: scratchDirectory,
161+ fs: fs, observabilityScope: observability. topScope
162+ )
163+ // Generate llbuild manifest
164+ let _ = try buildOp. getBuildDescription ( )
165+
166+ // Ensure that llbuild manifest is updated to the expected one
167+ let actualManifest : String = try fs. readFileContents ( buildParameters. llbuildManifest)
168+ let expectedManifest = try XCTUnwrap ( llbuildManifestByTriple [ triple. tripleString] )
169+ XCTAssertEqual ( actualManifest, expectedManifest)
170+ }
171+ }
172+ }
173+ }
68174}
0 commit comments