From f389887e37f9a9094854954d40abb040bcee46d1 Mon Sep 17 00:00:00 2001 From: Bartosz Polaczyk Date: Mon, 12 Jun 2023 22:22:59 -0700 Subject: [PATCH 1/7] Read also dependencies from assetcatalog_dependencies output --- .../Commands/Postbuild/XCPostbuild.swift | 7 +- .../AssetsFileDependenciesReader.swift | 70 +++++++++++++++++++ .../Dependencies/TargetDepdenciesReader.swift | 33 +++++++-- .../TargetDependenciesReaderTests.swift | 9 ++- 4 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift diff --git a/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift b/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift index 235f56c1..3f0dd94b 100644 --- a/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift +++ b/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift @@ -153,9 +153,14 @@ public class XCPostbuild { let fileReaderFactory: (URL) -> DependenciesReader = { FileDependenciesReader($0, accessor: fileManager) } + let assetsFileDependenciesFactory: (URL) -> DependenciesReader = { + AssetsFileDependenciesReader($0, accessor: fileManager) + } let dependenciesReader = TargetDependenciesReader( - context.compilationTempDir, + compilationOutputDir: context.compilationTempDir, + assetsCatalogOutputDir: context.targetTempDir, fileDependeciesReaderFactory: fileReaderFactory, + assetsDependeciesReaderFactory: assetsFileDependenciesFactory, dirScanner: fileManager ) var remappers: [DependenciesRemapper] = [] diff --git a/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift new file mode 100644 index 00000000..180f82d7 --- /dev/null +++ b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift @@ -0,0 +1,70 @@ +// Copyright (c) 2023 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 Foundation + +/// Parser for `assetcatalog_dependencies` file +public class AssetsFileDependenciesReader: DependenciesReader { + private let file: URL + private let fileManager: FileManager + + public init(_ file: URL, accessor: FileManager) { + self.file = file + fileManager = accessor + } + + public func findDependencies() throws -> [String] { + return try Array(findAllDependencies()) + } + + public func findInputs() throws -> [String] { + exit(1, "TODO: implement") + } + + public func readFilesAndDependencies() throws -> [String : [String]] { + return try ["": findAllDependencies()] + } + + private func findAllDependencies() throws -> [String] { + let fileData = try getFileData() + // all dependency files are separated by the \0 byte + let pathDatas = fileData.split(separator: 0x0) + let paths = pathDatas + .map { String(data: $0, encoding: .utf8)! } + .map (URL.init(fileURLWithPath:)) + let xcassetsPaths = paths.filter { path in + path.pathExtension == "xcassets" + } + return try xcassetsPaths.flatMap { try findAssetsContentJsons(xcasset: $0) } + } + + private func findAssetsContentJsons(xcasset: URL) throws -> [String] { + return try fileManager.recursiveItems(at: xcasset).filter { url in + url.lastPathComponent == "Contents.json" + }.map(\.path) + } + + private func getFileData() throws -> Data { + guard let fileData = fileManager.contents(atPath: file.path) else { + throw DependenciesReaderError.readingError + } + return fileData + } + +} diff --git a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift index c5c3908c..e3e07474 100644 --- a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift @@ -21,26 +21,33 @@ import Foundation /// Reads and aggregates all compilation dependencies from a single directory class TargetDependenciesReader: DependenciesReader { - private let directory: URL + private let compilationDirectory: URL + private let assetsCatalogOutputDir: URL private let dirScanner: DirScanner private let fileDependeciesReaderFactory: (URL) -> DependenciesReader + private let assetsDependeciesReaderFactory: (URL) -> DependenciesReader public init( - _ directory: URL, + compilationOutputDir: URL, + assetsCatalogOutputDir: URL, fileDependeciesReaderFactory: @escaping (URL) -> DependenciesReader, + assetsDependeciesReaderFactory: @escaping (URL) -> DependenciesReader, dirScanner: DirScanner ) { - self.directory = directory + self.compilationDirectory = compilationOutputDir + self.assetsCatalogOutputDir = assetsCatalogOutputDir self.dirScanner = dirScanner self.fileDependeciesReaderFactory = fileDependeciesReaderFactory + self.assetsDependeciesReaderFactory = assetsDependeciesReaderFactory } // Optimized way of finding dependencies only for files that have corresponding .o file on a disk + // includes also inputs to the `actool` assets generator public func findDependencies() throws -> [String] { // Not calling `readFilesAndDependencies` as it may unnecessary call expensive `findDependencies()` for // files that eventually will not be considered - let allURLs = try dirScanner.items(at: directory) - let mergedDependencies = try allURLs.reduce(Set()) { (prev: Set, file) in + let allCompilationOutputURLs = try dirScanner.items(at: compilationDirectory) + var mergedDependencies = try allCompilationOutputURLs.reduce(Set()) { (prev: Set, file) in // include only these .d files that either have corresponding .o file (incremental) or end // with '-master' (whole-module) // Otherwise .d is probably just a leftover from previous builds @@ -55,15 +62,29 @@ class TargetDependenciesReader: DependenciesReader { return try prev.union(fileDependeciesReaderFactory(file).findDependencies()) } + try mergedDependencies.formUnion(findAssetsCatalogDependencies()) return Array(mergedDependencies).sorted() } + // find all + private func findAssetsCatalogDependencies() throws -> Set{ + let allAssetsOutputDirURLs = try dirScanner.items(at: assetsCatalogOutputDir) + let mergedDependencies = try allAssetsOutputDirURLs.reduce(Set()) { (prev: Set, file) in + if file.lastPathComponent == "assetcatalog_dependencies" { + // as of Xcode 15, the dependencies in assets are always created in the same path + return try prev.union(assetsDependeciesReaderFactory(file).findDependencies()) + } + return prev + } + return mergedDependencies + } + public func findInputs() throws -> [String] { fatalError("TODO: implement") } public func readFilesAndDependencies() throws -> [String: [String]] { - let allURLs = try dirScanner.items(at: directory) + let allURLs = try dirScanner.items(at: compilationDirectory) return try allURLs.reduce([String: [String]]()) { prev, file in var new = prev new[file.path] = try fileDependeciesReaderFactory(file).findDependencies() diff --git a/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift b/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift index 0437a929..31c10751 100644 --- a/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift +++ b/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift @@ -23,6 +23,7 @@ import XCTest class TargetDependenciesReaderTests: XCTestCase { private let workingURL: URL = "/test" + private let assetsWorkingURL: URL = "/assetsTest" private var dirAccessor: DirAccessorFake! private var reader: TargetDependenciesReader! @@ -35,9 +36,15 @@ class TargetDependenciesReaderTests: XCTestCase { let fakeDependency = url.deletingPathExtension().appendingPathExtension("swift") return DependenciesReaderFake(dependencies: ["": [fakeDependency.path]]) } + let assetsFakeDependencyReaderFactory: (URL) -> DependenciesReader = { url in + let fakeDependency = url.deletingLastPathComponent().appendingPathComponent("Contents.json") + return DependenciesReaderFake(dependencies: ["": [fakeDependency.path]]) + } reader = TargetDependenciesReader( - workingURL, + compilationOutputDir: workingURL, + assetsCatalogOutputDir: assetsWorkingURL, fileDependeciesReaderFactory: swiftFakeDependencyReaderFactory, + assetsDependeciesReaderFactory: assetsFakeDependencyReaderFactory, dirScanner: dirAccessor ) } From 37e144c36adabbd98c94c9b9b0a3889cf610f57a Mon Sep 17 00:00:00 2001 From: Bartosz Polaczyk Date: Mon, 12 Jun 2023 22:58:05 -0700 Subject: [PATCH 2/7] Add unit tests --- .../Commands/Postbuild/XCPostbuild.swift | 2 +- .../AssetsFileDependenciesReader.swift | 19 +++--- .../Dependencies/TargetDepdenciesReader.swift | 17 +++--- .../AssetsFileDependenciesReaderTests.swift | 55 ++++++++++++++++++ .../assetcatalog_dependencies_sample | Bin 0 -> 501 bytes 5 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift create mode 100644 Tests/XCRemoteCacheTests/TestData/Dependencies/AssetsFileDependenciesReaderTests/assetcatalog_dependencies_sample diff --git a/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift b/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift index 3f0dd94b..b062cf51 100644 --- a/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift +++ b/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift @@ -154,7 +154,7 @@ public class XCPostbuild { FileDependenciesReader($0, accessor: fileManager) } let assetsFileDependenciesFactory: (URL) -> DependenciesReader = { - AssetsFileDependenciesReader($0, accessor: fileManager) + AssetsFileDependenciesReader($0, dirAccessor: fileManager) } let dependenciesReader = TargetDependenciesReader( compilationOutputDir: context.compilationTempDir, diff --git a/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift index 180f82d7..5ad0bf06 100644 --- a/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift @@ -20,13 +20,13 @@ import Foundation /// Parser for `assetcatalog_dependencies` file -public class AssetsFileDependenciesReader: DependenciesReader { +class AssetsFileDependenciesReader: DependenciesReader { private let file: URL - private let fileManager: FileManager + private let dirAccessor: DirAccessor - public init(_ file: URL, accessor: FileManager) { + public init(_ file: URL, dirAccessor: DirAccessor) { self.file = file - fileManager = accessor + self.dirAccessor = dirAccessor } public func findDependencies() throws -> [String] { @@ -44,9 +44,14 @@ public class AssetsFileDependenciesReader: DependenciesReader { private func findAllDependencies() throws -> [String] { let fileData = try getFileData() // all dependency files are separated by the \0 byte + // each path has a file type prefix: + // 0x10 - directory + // 0x40 - file + // We only care about dirs, as *.xcassets is a folder let pathDatas = fileData.split(separator: 0x0) let paths = pathDatas - .map { String(data: $0, encoding: .utf8)! } + .filter { !$0.isEmpty && $0.first == 0x10 } + .map { String(data: $0.dropFirst(), encoding: .utf8)! } .map (URL.init(fileURLWithPath:)) let xcassetsPaths = paths.filter { path in path.pathExtension == "xcassets" @@ -55,13 +60,13 @@ public class AssetsFileDependenciesReader: DependenciesReader { } private func findAssetsContentJsons(xcasset: URL) throws -> [String] { - return try fileManager.recursiveItems(at: xcasset).filter { url in + return try dirAccessor.recursiveItems(at: xcasset).filter { url in url.lastPathComponent == "Contents.json" }.map(\.path) } private func getFileData() throws -> Data { - guard let fileData = fileManager.contents(atPath: file.path) else { + guard let fileData = try dirAccessor.contents(atPath: file.path) else { throw DependenciesReaderError.readingError } return fileData diff --git a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift index e3e07474..ef643b50 100644 --- a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift @@ -21,6 +21,8 @@ import Foundation /// Reads and aggregates all compilation dependencies from a single directory class TargetDependenciesReader: DependenciesReader { + // As of Xcode15, the filename is static + private static let assetsDependenciesFilename = "assetcatalog_dependencies" private let compilationDirectory: URL private let assetsCatalogOutputDir: URL private let dirScanner: DirScanner @@ -62,21 +64,18 @@ class TargetDependenciesReader: DependenciesReader { return try prev.union(fileDependeciesReaderFactory(file).findDependencies()) } + // include also dependencies from optional assets compilation (`actool`) try mergedDependencies.formUnion(findAssetsCatalogDependencies()) return Array(mergedDependencies).sorted() } - // find all + // find all assets dependencies, which are always private func findAssetsCatalogDependencies() throws -> Set{ - let allAssetsOutputDirURLs = try dirScanner.items(at: assetsCatalogOutputDir) - let mergedDependencies = try allAssetsOutputDirURLs.reduce(Set()) { (prev: Set, file) in - if file.lastPathComponent == "assetcatalog_dependencies" { - // as of Xcode 15, the dependencies in assets are always created in the same path - return try prev.union(assetsDependeciesReaderFactory(file).findDependencies()) - } - return prev + let expectedAssetsDepsFile = assetsCatalogOutputDir.appendingPathComponent(Self.assetsDependenciesFilename) + guard try dirScanner.itemType(atPath: assetsCatalogOutputDir.path) == .file else { + return [] } - return mergedDependencies + return try Set(assetsDependeciesReaderFactory(expectedAssetsDepsFile).findDependencies()) } public func findInputs() throws -> [String] { diff --git a/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift b/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift new file mode 100644 index 00000000..79042d38 --- /dev/null +++ b/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift @@ -0,0 +1,55 @@ +// Copyright (c) 2023 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. + +@testable import XCRemoteCache + +import XCTest + +class AssetsFileDependenciesReaderTests: FileXCTestCase { + private static let resourcesSubdirectory = "TestData/Dependencies/AssetsFileDependenciesReaderTests" + + private func pathForTestData(name: String) throws -> URL { + return try XCTUnwrap(Bundle.module.url( + forResource: name, + withExtension: "", + subdirectory: AssetsFileDependenciesReaderTests.resourcesSubdirectory + )) + } + + func testParsingSampleFile() throws { + let file = try pathForTestData(name: "assetcatalog_dependencies_sample") + let fileData = try Data(contentsOf: file) + let xcassetsPath: URL = + """ + /Users/bartosz/Development/XCRemoteCache/e2eTests/\ + StandaloneSampleApp/StandaloneApp/Assets.xcassets + """ + let contentsJson = xcassetsPath.appendingPathComponent("Contents.json") + + let dirAccessorFake = DirAccessorFake() + try dirAccessorFake.write(toPath: file.path, contents: fileData) + try dirAccessorFake.write(toPath: contentsJson.path, contents: Data()) + + let reader = AssetsFileDependenciesReader(file, dirAccessor: dirAccessorFake) + + let dependencies = try reader.findDependencies() + + XCTAssertEqual(dependencies, [contentsJson.path]) + } +} diff --git a/Tests/XCRemoteCacheTests/TestData/Dependencies/AssetsFileDependenciesReaderTests/assetcatalog_dependencies_sample b/Tests/XCRemoteCacheTests/TestData/Dependencies/AssetsFileDependenciesReaderTests/assetcatalog_dependencies_sample new file mode 100644 index 0000000000000000000000000000000000000000..d1a03b5dd3b232ba36b76eb9ea3b063a3281b321 GIT binary patch literal 501 zcmchUJ#ND=426A!TqF|gULZeSx)n_eWD98N)e#_xf=CBJPhZDN+M+{;q8mv(@bP==1GjmW6;a(@c>jZKL`^uOSqD*MUJ@&D#_x Date: Mon, 12 Jun 2023 23:25:37 -0700 Subject: [PATCH 3/7] Fix reference path --- Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift index ef643b50..fd7370d9 100644 --- a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift @@ -72,7 +72,7 @@ class TargetDependenciesReader: DependenciesReader { // find all assets dependencies, which are always private func findAssetsCatalogDependencies() throws -> Set{ let expectedAssetsDepsFile = assetsCatalogOutputDir.appendingPathComponent(Self.assetsDependenciesFilename) - guard try dirScanner.itemType(atPath: assetsCatalogOutputDir.path) == .file else { + guard try dirScanner.itemType(atPath: expectedAssetsDepsFile.path) == .file else { return [] } return try Set(assetsDependeciesReaderFactory(expectedAssetsDepsFile).findDependencies()) From 47ad8a890b6eda42dd8685712a29a0ce451688c0 Mon Sep 17 00:00:00 2001 From: Bartosz Polaczyk Date: Mon, 12 Jun 2023 23:33:09 -0700 Subject: [PATCH 4/7] Fix swiftc allowed paths --- Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift b/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift index 9bc70687..465daf14 100644 --- a/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift +++ b/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift @@ -86,6 +86,13 @@ class Swiftc: SwiftcProtocol { self.plugins = plugins } + // TODO: consider refactoring to a separate entity + private func assetsGeneratedSources(inputFiles: [URL]) -> [URL] { + return inputFiles.filter { url in + url.lastPathComponent == "GeneratedAssetSymbols.swift" + } + } + // swiftlint:disable:next function_body_length func mockCompilation() throws -> SwiftCResult { let rcModeEnabled = markerReader.canRead() @@ -96,13 +103,14 @@ class Swiftc: SwiftcProtocol { let inputFilesInputs = try inputFileListReader.listFilesURLs() let markerAllowedFiles = try markerReader.listFilesURLs() + let generatedAssetsFiles = assetsGeneratedSources(inputFiles: inputFilesInputs) let cachedDependenciesWriterFactory = CachedFileDependenciesWriterFactory( - dependencies: markerAllowedFiles, + dependencies: markerAllowedFiles + generatedAssetsFiles, fileManager: fileManager, writerFactory: dependenciesWriterFactory ) // Verify all input files to be present in a marker fileList - let disallowedInputs = try inputFilesInputs.filter { try !allowedFilesListScanner.contains($0) } + let disallowedInputs = try inputFilesInputs.filter { try !allowedFilesListScanner.contains($0) && !generatedAssetsFiles.contains($0) } if !disallowedInputs.isEmpty { // New file (disallowedFile) added without modifying the rest of the feature. Fallback to swiftc and From bc13f58e73158d7ebaba9b967ef441d0528ffd32 Mon Sep 17 00:00:00 2001 From: Bartosz Polaczyk Date: Tue, 13 Jun 2023 21:08:56 -0700 Subject: [PATCH 5/7] Add unit tests --- .../Commands/Postbuild/XCPostbuild.swift | 4 +- .../FilenameBasedAllowedInputDeterminer.swift | 38 ++++++ .../Commands/Swiftc/Swiftc.swift | 21 ++-- .../Commands/Swiftc/XCSwiftc.swift | 5 +- .../Dependencies/TargetDepdenciesReader.swift | 18 +-- .../Commands/SwiftcTests.swift | 114 ++++++++++++++++-- .../TargetDependenciesReaderTests.swift | 13 +- 7 files changed, 176 insertions(+), 37 deletions(-) create mode 100644 Sources/XCRemoteCache/Commands/Swiftc/FilenameBasedAllowedInputDeterminer.swift diff --git a/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift b/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift index b062cf51..ac93a90d 100644 --- a/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift +++ b/Sources/XCRemoteCache/Commands/Postbuild/XCPostbuild.swift @@ -159,8 +159,8 @@ public class XCPostbuild { let dependenciesReader = TargetDependenciesReader( compilationOutputDir: context.compilationTempDir, assetsCatalogOutputDir: context.targetTempDir, - fileDependeciesReaderFactory: fileReaderFactory, - assetsDependeciesReaderFactory: assetsFileDependenciesFactory, + fileDependenciesReaderFactory: fileReaderFactory, + assetsDependenciesReaderFactory: assetsFileDependenciesFactory, dirScanner: fileManager ) var remappers: [DependenciesRemapper] = [] diff --git a/Sources/XCRemoteCache/Commands/Swiftc/FilenameBasedAllowedInputDeterminer.swift b/Sources/XCRemoteCache/Commands/Swiftc/FilenameBasedAllowedInputDeterminer.swift new file mode 100644 index 00000000..d4d349b2 --- /dev/null +++ b/Sources/XCRemoteCache/Commands/Swiftc/FilenameBasedAllowedInputDeterminer.swift @@ -0,0 +1,38 @@ +// Copyright (c) 2023 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 Foundation + +/// Decides if an input to the compilation step should allow reusing the cached artifact +protocol AllowedInputDeterminer { + /// Decides if the input file is allowed to be compiled, even not specified in the dependency list + func allowedNonDependencyInput(file: URL) -> Bool +} + +class FilenameBasedAllowedInputDeterminer: AllowedInputDeterminer { + private let filenames: [String] + + init(_ filenames: [String]) { + self.filenames = filenames + } + + func allowedNonDependencyInput(file: URL) -> Bool { + return filenames.contains(file.lastPathComponent) + } +} diff --git a/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift b/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift index 465daf14..860ab09d 100644 --- a/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift +++ b/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift @@ -57,6 +57,7 @@ class Swiftc: SwiftcProtocol { private let dependenciesWriterFactory: (URL, FileManager) -> DependenciesWriter private let touchFactory: (URL, FileManager) -> Touch private let plugins: [SwiftcProductGenerationPlugin] + private let allowedInputDeterminer: AllowedInputDeterminer init( inputFileListReader: ListReader, @@ -70,7 +71,8 @@ class Swiftc: SwiftcProtocol { fileManager: FileManager, dependenciesWriterFactory: @escaping (URL, FileManager) -> DependenciesWriter, touchFactory: @escaping (URL, FileManager) -> Touch, - plugins: [SwiftcProductGenerationPlugin] + plugins: [SwiftcProductGenerationPlugin], + allowedInputDeterminer: AllowedInputDeterminer ) { self.inputFileListReader = inputFileListReader self.markerReader = markerReader @@ -84,14 +86,8 @@ class Swiftc: SwiftcProtocol { self.dependenciesWriterFactory = dependenciesWriterFactory self.touchFactory = touchFactory self.plugins = plugins + self.allowedInputDeterminer = allowedInputDeterminer } - - // TODO: consider refactoring to a separate entity - private func assetsGeneratedSources(inputFiles: [URL]) -> [URL] { - return inputFiles.filter { url in - url.lastPathComponent == "GeneratedAssetSymbols.swift" - } - } // swiftlint:disable:next function_body_length func mockCompilation() throws -> SwiftCResult { @@ -103,14 +99,17 @@ class Swiftc: SwiftcProtocol { let inputFilesInputs = try inputFileListReader.listFilesURLs() let markerAllowedFiles = try markerReader.listFilesURLs() - let generatedAssetsFiles = assetsGeneratedSources(inputFiles: inputFilesInputs) + let allDependencies = Set(markerAllowedFiles + inputFilesInputs) let cachedDependenciesWriterFactory = CachedFileDependenciesWriterFactory( - dependencies: markerAllowedFiles + generatedAssetsFiles, + dependencies: Array(allDependencies), fileManager: fileManager, writerFactory: dependenciesWriterFactory ) // Verify all input files to be present in a marker fileList - let disallowedInputs = try inputFilesInputs.filter { try !allowedFilesListScanner.contains($0) && !generatedAssetsFiles.contains($0) } + let disallowedInputs = try inputFilesInputs.filter { file in + try !allowedFilesListScanner.contains(file) && + !allowedInputDeterminer.allowedNonDependencyInput(file: file) + } if !disallowedInputs.isEmpty { // New file (disallowedFile) added without modifying the rest of the feature. Fallback to swiftc and diff --git a/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift b/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift index ee40becc..4f096514 100644 --- a/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift +++ b/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift @@ -152,6 +152,8 @@ public class XCSwiftAbstract { retrieveIgnoredCommands: [swiftcCommand] ) let shellOut = ProcessShellOut() + // Always allow an input file from the actool generation step + let allowedInputDeterminer = FilenameBasedAllowedInputDeterminer(["GeneratedAssetSymbols.swift"]) let swiftc = Swiftc( inputFileListReader: fileListReader, @@ -165,7 +167,8 @@ public class XCSwiftAbstract { fileManager: fileManager, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) let orchestrator = SwiftcOrchestrator( mode: context.mode, diff --git a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift index fd7370d9..4aeff612 100644 --- a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift @@ -26,21 +26,21 @@ class TargetDependenciesReader: DependenciesReader { private let compilationDirectory: URL private let assetsCatalogOutputDir: URL private let dirScanner: DirScanner - private let fileDependeciesReaderFactory: (URL) -> DependenciesReader - private let assetsDependeciesReaderFactory: (URL) -> DependenciesReader + private let fileDependenciesReaderFactory: (URL) -> DependenciesReader + private let assetsDependenciesReaderFactory: (URL) -> DependenciesReader public init( compilationOutputDir: URL, assetsCatalogOutputDir: URL, - fileDependeciesReaderFactory: @escaping (URL) -> DependenciesReader, - assetsDependeciesReaderFactory: @escaping (URL) -> DependenciesReader, + fileDependenciesReaderFactory: @escaping (URL) -> DependenciesReader, + assetsDependenciesReaderFactory: @escaping (URL) -> DependenciesReader, dirScanner: DirScanner ) { self.compilationDirectory = compilationOutputDir self.assetsCatalogOutputDir = assetsCatalogOutputDir self.dirScanner = dirScanner - self.fileDependeciesReaderFactory = fileDependeciesReaderFactory - self.assetsDependeciesReaderFactory = assetsDependeciesReaderFactory + self.fileDependenciesReaderFactory = fileDependenciesReaderFactory + self.assetsDependenciesReaderFactory = assetsDependenciesReaderFactory } // Optimized way of finding dependencies only for files that have corresponding .o file on a disk @@ -62,7 +62,7 @@ class TargetDependenciesReader: DependenciesReader { return prev } - return try prev.union(fileDependeciesReaderFactory(file).findDependencies()) + return try prev.union(fileDependenciesReaderFactory(file).findDependencies()) } // include also dependencies from optional assets compilation (`actool`) try mergedDependencies.formUnion(findAssetsCatalogDependencies()) @@ -75,7 +75,7 @@ class TargetDependenciesReader: DependenciesReader { guard try dirScanner.itemType(atPath: expectedAssetsDepsFile.path) == .file else { return [] } - return try Set(assetsDependeciesReaderFactory(expectedAssetsDepsFile).findDependencies()) + return try Set(assetsDependenciesReaderFactory(expectedAssetsDepsFile).findDependencies()) } public func findInputs() throws -> [String] { @@ -86,7 +86,7 @@ class TargetDependenciesReader: DependenciesReader { let allURLs = try dirScanner.items(at: compilationDirectory) return try allURLs.reduce([String: [String]]()) { prev, file in var new = prev - new[file.path] = try fileDependeciesReaderFactory(file).findDependencies() + new[file.path] = try fileDependenciesReaderFactory(file).findDependencies() return new } } diff --git a/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift b/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift index 50bf67a2..1e2ae1db 100644 --- a/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift +++ b/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift @@ -40,6 +40,7 @@ class SwiftcTests: FileXCTestCase { private var workingDir: URL! private var remoteCommitLocation: URL! private let sampleRemoteCommit = "bdb321" + private var allowedInputDeterminer = FilenameBasedAllowedInputDeterminer([]) override func setUpWithError() throws { @@ -93,7 +94,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) let compilation = try swiftc.mockCompilation() @@ -115,7 +117,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) let compilation = try swiftc.mockCompilation() @@ -137,7 +140,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -161,7 +165,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -187,7 +192,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -216,7 +222,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -226,6 +233,30 @@ class SwiftcTests: FileXCTestCase { XCTAssertEqual(writerSpy.wroteSkipForSha, sampleRemoteCommit) } + func testAllowsNotAllowedInputFileThatAreAllowedByDeterminer() throws { + inputFileListReader = ListReaderFake(files: [URL(fileURLWithPath: "specialFile.swift")]) + allowedInputDeterminer = FilenameBasedAllowedInputDeterminer(["specialFile.swift"]) + let swiftc = Swiftc( + inputFileListReader: inputFileListReader, + markerReader: markerReader, + allowedFilesListScanner: allowedFilesListScanner, + artifactOrganizer: artifactOrganizer, + inputReader: swiftcInputReader, + context: context, + markerWriter: markerWriter, + productsGenerator: productsGenerator, + fileManager: FileManager.default, + dependenciesWriterFactory: dependenciesWriterFactory, + touchFactory: touchFactory, + plugins: [], + allowedInputDeterminer: allowedInputDeterminer + ) + + let result = try swiftc.mockCompilation() + + XCTAssertEqual(result, .success) + } + func testRCTouchesOutputFile() throws { let compilationURL = URL(fileURLWithPath: "old.swift") inputFileListReader = ListReaderFake(files: [compilationURL]) @@ -265,7 +296,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -305,7 +337,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -340,7 +373,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: dependenciesWriterFactory, touchFactory: touchFactory, - plugins: [plugin] + plugins: [plugin], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -382,7 +416,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: FileDependenciesWriter.init, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) _ = try swiftc.mockCompilation() @@ -399,6 +434,59 @@ class SwiftcTests: FileXCTestCase { ) } + func testGeneratesDFilesPerModuleAndIndividualFilesWithAdditionallyAllowedFiles() throws { + let outputFilesDir = workingDir.appendingPathComponent("outputFiles") + try fileManager.spt_createEmptyDir(outputFilesDir) + let moduleDFile = outputFilesDir.appendingPathComponent("master.d") + let fileDFile = outputFilesDir.appendingPathComponent("magicalFile.d") + let input = SwiftCompilationInfo( + info: SwiftModuleCompilationInfo( + dependencies: moduleDFile, + swiftDependencies: outputFilesDir.appendingPathComponent("master.swiftdeps") + ), + files: [ + SwiftFileCompilationInfo( + file: "/magicalFile.swift", + dependencies: fileDFile, + object: outputFilesDir.appendingPathComponent("maficalFile.o"), + swiftDependencies: nil + ), + ] + ) + inputFileListReader = ListReaderFake(files: ["/magicalFile.swift"]) + swiftcInputReader = SwiftcInputReaderStub(info: input) + markerReader = ListReaderFake(files: []) + allowedInputDeterminer = FilenameBasedAllowedInputDeterminer(["magicalFile.swift"]) + let swiftc = Swiftc( + inputFileListReader: inputFileListReader, + markerReader: markerReader, + allowedFilesListScanner: allowedFilesListScanner, + artifactOrganizer: artifactOrganizer, + inputReader: swiftcInputReader, + context: context, + markerWriter: markerWriter, + productsGenerator: productsGenerator, + fileManager: FileManager.default, + dependenciesWriterFactory: FileDependenciesWriter.init, + touchFactory: touchFactory, + plugins: [], + allowedInputDeterminer: allowedInputDeterminer + ) + + _ = try swiftc.mockCompilation() + + let moduleDReader = FileDependenciesReader(moduleDFile, accessor: .default) + let fileDReader = FileDependenciesReader(fileDFile, accessor: .default) + XCTAssertEqual( + try moduleDReader.readFilesAndDependencies(), + ["dependencies": ["/magicalFile.swift"]] + ) + XCTAssertEqual( + try fileDReader.readFilesAndDependencies(), + ["dependencies": ["/magicalFile.swift"]] + ) + } + func testSkipsGeneratingDFilesWhenNotProvidedInCompilationInfo() throws { let outputFilesDir = workingDir.appendingPathComponent("outputFiles") try fileManager.spt_createEmptyDir(outputFilesDir) @@ -429,7 +517,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: FileDependenciesWriter.init, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) XCTAssertNoThrow(try swiftc.mockCompilation()) @@ -465,7 +554,8 @@ class SwiftcTests: FileXCTestCase { fileManager: FileManager.default, dependenciesWriterFactory: FileDependenciesWriter.init, touchFactory: touchFactory, - plugins: [] + plugins: [], + allowedInputDeterminer: allowedInputDeterminer ) XCTAssertNoThrow(try swiftc.mockCompilation()) diff --git a/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift b/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift index 31c10751..48a59f12 100644 --- a/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift +++ b/Tests/XCRemoteCacheTests/Dependencies/TargetDependenciesReaderTests.swift @@ -43,8 +43,8 @@ class TargetDependenciesReaderTests: XCTestCase { reader = TargetDependenciesReader( compilationOutputDir: workingURL, assetsCatalogOutputDir: assetsWorkingURL, - fileDependeciesReaderFactory: swiftFakeDependencyReaderFactory, - assetsDependeciesReaderFactory: assetsFakeDependencyReaderFactory, + fileDependenciesReaderFactory: swiftFakeDependencyReaderFactory, + assetsDependenciesReaderFactory: assetsFakeDependencyReaderFactory, dirScanner: dirAccessor ) } @@ -77,4 +77,13 @@ class TargetDependenciesReaderTests: XCTestCase { XCTAssertEqual(deps, ["/test/some-master.swift"]) } + + func testFindsAssetsCatalogDependencies() throws { + let assetsContentFile: URL = "/assetsTest/assetcatalog_dependencies" + try dirAccessor.write(toPath: assetsContentFile.path, contents: Data()) + + let deps = try reader.findDependencies() + + XCTAssertEqual(deps, ["/assetsTest/Contents.json"]) + } } From 344b1f13ca53cbd7c817431c109e1267ef990fef Mon Sep 17 00:00:00 2001 From: Bartosz Polaczyk Date: Tue, 13 Jun 2023 21:25:01 -0700 Subject: [PATCH 6/7] Cleanup --- .../Commands/Swiftc/Swiftc.swift | 4 +-- .../Commands/Swiftc/XCSwiftc.swift | 1 + .../AssetsFileDependenciesReader.swift | 4 ++- .../Dependencies/TargetDepdenciesReader.swift | 6 ++-- .../AssetsFileDependenciesReaderTests.swift | 31 ++++++++++++++---- .../assetcatalog_dependencies_sample | Bin 501 -> 295 bytes 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift b/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift index 860ab09d..f548cbaf 100644 --- a/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift +++ b/Sources/XCRemoteCache/Commands/Swiftc/Swiftc.swift @@ -57,7 +57,7 @@ class Swiftc: SwiftcProtocol { private let dependenciesWriterFactory: (URL, FileManager) -> DependenciesWriter private let touchFactory: (URL, FileManager) -> Touch private let plugins: [SwiftcProductGenerationPlugin] - private let allowedInputDeterminer: AllowedInputDeterminer + private let allowedInputDeterminer: AllowedInputDeterminer init( inputFileListReader: ListReader, @@ -88,7 +88,7 @@ class Swiftc: SwiftcProtocol { self.plugins = plugins self.allowedInputDeterminer = allowedInputDeterminer } - + // swiftlint:disable:next function_body_length func mockCompilation() throws -> SwiftCResult { let rcModeEnabled = markerReader.canRead() diff --git a/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift b/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift index 4f096514..2f688de6 100644 --- a/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift +++ b/Sources/XCRemoteCache/Commands/Swiftc/XCSwiftc.swift @@ -153,6 +153,7 @@ public class XCSwiftAbstract { ) let shellOut = ProcessShellOut() // Always allow an input file from the actool generation step + // As of Xcode15, the filename is confirmed to be static let allowedInputDeterminer = FilenameBasedAllowedInputDeterminer(["GeneratedAssetSymbols.swift"]) let swiftc = Swiftc( diff --git a/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift index 5ad0bf06..8d6c3479 100644 --- a/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift @@ -19,7 +19,8 @@ import Foundation -/// Parser for `assetcatalog_dependencies` file +/// Parser for `assetcatalog_dependencies` file: an output of the `actool` +/// that lists all dependencies of this command class AssetsFileDependenciesReader: DependenciesReader { private let file: URL private let dirAccessor: DirAccessor @@ -34,6 +35,7 @@ class AssetsFileDependenciesReader: DependenciesReader { } public func findInputs() throws -> [String] { + // XCRemoteCache doesn't use it yet exit(1, "TODO: implement") } diff --git a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift index 4aeff612..3657e9fb 100644 --- a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift @@ -69,9 +69,11 @@ class TargetDependenciesReader: DependenciesReader { return Array(mergedDependencies).sorted() } - // find all assets dependencies, which are always + // finds all assets compilation's dependencies, which are always appended to the list of + // files to compare on the consumer side (in the fingerprint comparison) private func findAssetsCatalogDependencies() throws -> Set{ - let expectedAssetsDepsFile = assetsCatalogOutputDir.appendingPathComponent(Self.assetsDependenciesFilename) + let expectedAssetsDepsFile = assetsCatalogOutputDir + .appendingPathComponent(Self.assetsDependenciesFilename) guard try dirScanner.itemType(atPath: expectedAssetsDepsFile.path) == .file else { return [] } diff --git a/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift b/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift index 79042d38..c5de5da4 100644 --- a/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift +++ b/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift @@ -23,6 +23,7 @@ import XCTest class AssetsFileDependenciesReaderTests: FileXCTestCase { private static let resourcesSubdirectory = "TestData/Dependencies/AssetsFileDependenciesReaderTests" + private let dirAccessorFake = DirAccessorFake() private func pathForTestData(name: String) throws -> URL { return try XCTUnwrap(Bundle.module.url( @@ -35,14 +36,9 @@ class AssetsFileDependenciesReaderTests: FileXCTestCase { func testParsingSampleFile() throws { let file = try pathForTestData(name: "assetcatalog_dependencies_sample") let fileData = try Data(contentsOf: file) - let xcassetsPath: URL = - """ - /Users/bartosz/Development/XCRemoteCache/e2eTests/\ - StandaloneSampleApp/StandaloneApp/Assets.xcassets - """ + let xcassetsPath: URL = "/StandaloneApp/Assets.xcassets" let contentsJson = xcassetsPath.appendingPathComponent("Contents.json") - let dirAccessorFake = DirAccessorFake() try dirAccessorFake.write(toPath: file.path, contents: fileData) try dirAccessorFake.write(toPath: contentsJson.path, contents: Data()) @@ -52,4 +48,27 @@ class AssetsFileDependenciesReaderTests: FileXCTestCase { XCTAssertEqual(dependencies, [contentsJson.path]) } + + func testThrowsWhenFileIsMissing() throws { + let file: URL = "/nonExistingFile" + + let reader = AssetsFileDependenciesReader(file, dirAccessor: dirAccessorFake) + + XCTAssertThrowsError(try reader.findDependencies()) { error in + guard case DependenciesReaderError.readingError = error else { + XCTFail("Invalid error \(error)") + return + } + } + } + + func testReturnsEmptyArrayIsFileIsMalformed() throws { + let file: URL = "/nonExistingFile" + try dirAccessorFake.write(toPath: file.path, contents: Data()) + let reader = AssetsFileDependenciesReader(file, dirAccessor: dirAccessorFake) + + let dependencies = try reader.findDependencies() + + XCTAssertEqual(dependencies, []) + } } diff --git a/Tests/XCRemoteCacheTests/TestData/Dependencies/AssetsFileDependenciesReaderTests/assetcatalog_dependencies_sample b/Tests/XCRemoteCacheTests/TestData/Dependencies/AssetsFileDependenciesReaderTests/assetcatalog_dependencies_sample index d1a03b5dd3b232ba36b76eb9ea3b063a3281b321..1a55c4a36db511f749b8c57e4cc6f28dc0b120bb 100644 GIT binary patch delta 37 tcmey$yqrmpAu+imKR-v;$jH#pSkI6_U}Bi!#62@6?opV$lhKBe3jp1e3%CFP literal 501 zcmchUJ#ND=426A!TqF|gULZeSx)n_eWD98N)e#_xf=CBJPhZDN+M+{;q8mv(@bP==1GjmW6;a(@c>jZKL`^uOSqD*MUJ@&D#_x Date: Tue, 13 Jun 2023 21:30:12 -0700 Subject: [PATCH 7/7] Fix linters --- .../Dependencies/AssetsFileDependenciesReader.swift | 4 ++-- .../Dependencies/TargetDepdenciesReader.swift | 2 +- Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift | 8 +++----- .../Dependencies/AssetsFileDependenciesReaderTests.swift | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift index 8d6c3479..31bde3d7 100644 --- a/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/AssetsFileDependenciesReader.swift @@ -39,7 +39,7 @@ class AssetsFileDependenciesReader: DependenciesReader { exit(1, "TODO: implement") } - public func readFilesAndDependencies() throws -> [String : [String]] { + public func readFilesAndDependencies() throws -> [String: [String]] { return try ["": findAllDependencies()] } @@ -54,7 +54,7 @@ class AssetsFileDependenciesReader: DependenciesReader { let paths = pathDatas .filter { !$0.isEmpty && $0.first == 0x10 } .map { String(data: $0.dropFirst(), encoding: .utf8)! } - .map (URL.init(fileURLWithPath:)) + .map(URL.init(fileURLWithPath:)) let xcassetsPaths = paths.filter { path in path.pathExtension == "xcassets" } diff --git a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift index 3657e9fb..5688a231 100644 --- a/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift +++ b/Sources/XCRemoteCache/Dependencies/TargetDepdenciesReader.swift @@ -71,7 +71,7 @@ class TargetDependenciesReader: DependenciesReader { // finds all assets compilation's dependencies, which are always appended to the list of // files to compare on the consumer side (in the fingerprint comparison) - private func findAssetsCatalogDependencies() throws -> Set{ + private func findAssetsCatalogDependencies() throws -> Set { let expectedAssetsDepsFile = assetsCatalogOutputDir .appendingPathComponent(Self.assetsDependenciesFilename) guard try dirScanner.itemType(atPath: expectedAssetsDepsFile.path) == .file else { diff --git a/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift b/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift index 1e2ae1db..c9e8cc88 100644 --- a/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift +++ b/Tests/XCRemoteCacheTests/Commands/SwiftcTests.swift @@ -434,7 +434,7 @@ class SwiftcTests: FileXCTestCase { ) } - func testGeneratesDFilesPerModuleAndIndividualFilesWithAdditionallyAllowedFiles() throws { + func testGeneratesDFilesAndIndividualFilesWithAdditionallyAllowedFiles() throws { let outputFilesDir = workingDir.appendingPathComponent("outputFiles") try fileManager.spt_createEmptyDir(outputFilesDir) let moduleDFile = outputFilesDir.appendingPathComponent("master.d") @@ -475,14 +475,12 @@ class SwiftcTests: FileXCTestCase { _ = try swiftc.mockCompilation() - let moduleDReader = FileDependenciesReader(moduleDFile, accessor: .default) - let fileDReader = FileDependenciesReader(fileDFile, accessor: .default) XCTAssertEqual( - try moduleDReader.readFilesAndDependencies(), + try FileDependenciesReader(moduleDFile, accessor: .default).readFilesAndDependencies(), ["dependencies": ["/magicalFile.swift"]] ) XCTAssertEqual( - try fileDReader.readFilesAndDependencies(), + try FileDependenciesReader(fileDFile, accessor: .default).readFilesAndDependencies(), ["dependencies": ["/magicalFile.swift"]] ) } diff --git a/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift b/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift index c5de5da4..fe8f57d0 100644 --- a/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift +++ b/Tests/XCRemoteCacheTests/Dependencies/AssetsFileDependenciesReaderTests.swift @@ -54,7 +54,7 @@ class AssetsFileDependenciesReaderTests: FileXCTestCase { let reader = AssetsFileDependenciesReader(file, dirAccessor: dirAccessorFake) - XCTAssertThrowsError(try reader.findDependencies()) { error in + XCTAssertThrowsError(try reader.findDependencies()) { error in guard case DependenciesReaderError.readingError = error else { XCTFail("Invalid error \(error)") return