<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
不久前,我正在工作中開發一項新服務,該服務由 Swift Package 組成,該 Package 公開了一個類似於Decodable協定,供我們應用程式的其餘部分使用。事實上,該協定是從Decodable本身繼承下來的,看起來像這樣:
Fetchable.swit
protocol Fetchable: Decodable, Equatable {}
新的 package 將採用符合Fetchable的型別來嘗試從遠端或快取的JSON資料塊中解碼它們。
由於這項服務對應用程式的正確執行至關重要,作為這項工作的一部分,我們希望確保始終存在故障安全( fail-safe)。因此,我們讓該應用程式附帶了一個備用的JSON檔案,如果遠端和快取的資料解碼失敗,將使用該檔案,來保證程式的正常執行。
無論如何,我們需要符合Fetchable的新型別從備用資料中正確解碼。然而,有一個問題,有時很難發現備用JSON檔案或模型本身是否有任何錯誤,因為解碼錯誤會在執行時發生,並且只有在存取某些螢幕/功能時才會發生。
為了讓我們對我們要傳送的程式碼更有信心,我們新增了一些單元測試,試圖根據我們附帶的備用JSON解碼符合Fetchable協定的每個模型。這些將使我們在CI上有一個早期指示,表明備用資料或模型中存在錯誤,如果所有測試都通過,我們將確定,一旦我們釋出新服務,它始終具有故障安全功能。
我們手動編寫了這些測試,但我們很快就意識到這個解決方案是不可延伸的,因為隨著越來越多的符合Fetchable協定的型別被新增,我們引入了大量的程式碼複製,並可能有人最終忘記為特定功能編寫這些測試。
我們考慮過自動化該過程,但由於我們的程式碼庫的性質,我們遇到了一些問題,程式碼庫高度模組化,混合了Xcode專案和Swift Package。一些架構決策還意味著我們必須收集大量符號資訊,才能獲得生成測試的正確型別。
在我忘記了這件事一段時間後,Xcode 14的公告允許在Xcode專案中使用 Swift Package 外掛,以及一些架構更改使提取型別資訊變得容易得多,這讓我有動力再次開始研究這個問題。
請注意,Xcode專案的構建工具外掛尚未按照發布說明在Xcode 14 Beta 2中提供,但將在Xcode 14的未來版本中提供。
圖片取自 Xcode Beta 2 版的釋出說明
在過去的幾周裡,我一直在研究如何使用軟體包外掛生成單元測試,在這篇文章中,我將解釋我在向哪個方向嘗試以及它涉及了什麼。
我開始了一項任務,即建立一個構建工具外掛,與 Xcode 14 引入的命令外掛不同,該外掛可以任意執行並依賴使用者輸入,作為Swift軟體包構建過程的一部分執行。
我知道我需要建立一個可執行檔案,因為 Build Tool 外掛依賴這些來執行操作。這個指令碼將完全用 Swift 編寫,因為這是我最熟悉的語言,並承擔以下職責:
與所有 Swift Package 一樣,最簡單的入門方法是在命令列上執行swift package init。
這建立了兩個目標,一個是包含Fetchable協定定義和符合該定義的型別的實現程式碼,另一個是應用外掛為此類型別生成單元測試的測試目標。
Package.swit
// swift-tools-version: 5.6 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ) ] )
如前所述,所有構建工具外掛都需要可執行檔案來執行所有必要的操作。
為了幫助開發此命令列,將使用幾個依賴項。第一個是SourceKitten——特別是其SourceKitten框架庫,這是一個Swift包裝器,用於幫助使用Swift程式碼編寫sourcekit請求,第二個是快速引數解析器,這是蘋果提供的軟體包,可以輕鬆建立命令列工具,並以更快、更安全的方式解析在執行過程中傳遞的命令列引數。
在建立executableTarget並賦予它兩個依賴項後,Package.swift就是這個樣子:
Package.swift
// swift-tools-version: 5.6 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ .package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: ["CodeGenSample"] ), .executableTarget( name: "PluginExecutable", dependencies: [ .product(name: "SourceKittenFramework", package: "SourceKitten"), .product(name: "ArgumentParser", package: "swift-argument-parser") ] ) ] )
可執行目標需要一個入口點,因此,在PluginExecutable目標的源目錄下,必須建立一個名為PluginExecutable.swift的檔案,其中所有可執行邏輯都需要建立。
請注意,這個檔案可以隨心所欲地命名,我傾向於以與我在Package.swift中建立的目標相同的方式命名它。
如下所示的指令碼匯入必要的依賴項,並建立可執行檔案的入口點(必須用@main裝飾),並宣告在執行時傳遞的4個輸入。
所有邏輯和方法呼叫都存在於run函數中,該函數是呼叫可執行檔案時執行的方法。這是ArgumentParser語法的一部分,如果您想了解更多資訊,Andy Ibañez有一篇關於該主題的精彩文章,可能非常有幫助。
PluginExecutable.swift
import SourceKittenFramework import ArgumentParser import Foundation @main struct PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { // 1 let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) // 2 setenv("IN_PROCESS_SOURCEKIT", "YES", 1) let structures = try files.map { try Structure(file: File(path: $0.path)!) } // 3 var matchedTypes = [String]() structures.forEach { walkTree(dictionary: $0.dictionary, acc: &matchedTypes) } // 4 try createOutputFile(withContent: matchedTypes) } // ... }
現在讓我們專注於上面的run方法,以瞭解當外掛執行可執行檔案時會發生什麼:
Xcode附帶兩個版本的sourcekit可執行檔案,一個版本解析程序中的檔案,另一個使用XPC向解析程序外檔案的守護行程傳送請求。後者是mac上的預設版本,為了能夠將sourcekit用作外掛程序的一部分,必須選擇程序中版本。這最近在SourceKitten上作為環境變數實現,是執行引擎蓋下使用sourcekit的其他可執行檔案的關鍵,例如SwiftLint。
請注意,上面沒有重點介紹每個呼叫的具體細節,但如果你對實現感興趣,包含所有程式碼的repo現在已經在Github上公開了!
與可執行檔案一樣,必須向Package.swift新增.plugin目標,並且必須建立包含外掛實現的.swift檔案(Plugins/SourceKitPlugin/SourceKitPlugin.swift)。
Package.swift
// swift-tools-version: 5.6 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "CodeGenSample", platforms: [.macOS(.v10_11)], products: [ .library( name: "CodeGenSample", targets: ["CodeGenSample"]), ], dependencies: [ .package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0") ], targets: [ .target( name: "CodeGenSample", dependencies: [] ), .testTarget( name: "CodeGenSampleTests", dependencies: [「CodeGenSample"], plugins: [「SourceKitPlugin」], ), .executableTarget( name: "PluginExecutable", dependencies: [ .product(name: "SourceKittenFramework", package: "SourceKitten"), .product(name: "ArgumentParser", package: "swift-argument-parser") ] ), .plugin( name: "SourceKitPlugin", capability: .buildTool(), dependencies: [.target(name: "PluginExecutable")] ) ] )
以下程式碼顯示了外掛的初始實現,其struct符合BuildToolPlugin的協定。這需要實現一個返回具有單個構建命令的陣列的createBuildCommands方法。
此外掛使用buildCommand而不是preBuildCommand,因為它需要作為構建過程的一部分執行,而不是在它之前執行,因此它有機會構建和使用它所依賴的可執行檔案。在這種情況下,支援使用buildCommand的另一點是,它只會在輸入檔案更改時執行,而不是每次構建目標時執行。
此命令必須為要執行的可執行檔案提供名稱和路徑,這可以在外掛的上下文中找到:
SourceKitPlugin.swift
import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "FindThis", , "--input", , "--output", ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [ ] ) ] } }
如上面的程式碼所示,還有一些空白需要填充( ):
SourceKitPlugin.swift
import PackagePlugin @main struct SourceKitPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { let outputPath = context.pluginWorkDirectory.appending(「GeneratedTests.swift」) guard let dependencyTarget = target .dependencies .compactMap { dependency -> Target? in switch dependency { case .target(let target): return target default: return nil } } .filter { "($0.name)Tests" == target.name } .first else { Diagnostics.error("Could not get a dependency to scan!」) return [] } return [ .buildCommand( displayName: "Protocol Extraction!", executable: try context.tool(named: "PluginExecutable").path, arguments: [ "Fetchable", dependencyTarget.name, "--input", dependencyTarget.directory, "--output", outputPath ], environment: ["IN_PROCESS_SOURCEKIT": "YES"], outputFiles: [outputPath] ) ] } }
注意上述可選性處理方式。如果在測試目標的依賴項中找不到 合適的 目標,則使用Diagnostics API將錯誤轉發回Xcode,並告訴它完成構建過程。
外掛這就完成了!現在讓我們在 Xcode 中執行它!為了測試這種方法,將包含以下內容的檔案新增到CodeGenSample目標中:
CodeGenSample.swift
import Foundation protocol Fetchable: Decodable, Equatable {} struct FeatureABlock: Fetchable { let featureA: FeatureA struct FeatureA: Fetchable { let url: URL } } enum Root { struct RootBlock: Fetchable { let url: URL let areAllFeaturesEnabled: Bool } }
請注意,指令碼將在結構中首次出現Fetchable協定時停止。這意味著任何巢狀的符合Fetchable協定的型別都將被測試,只是外部模型。
給定此輸入並在主目標上執行測試,生成並執行XCTestCase,其中包含符合Fetchable協定的兩種型別的測試。
GeneratedTests.swift
import XCTest @testable import CodeGenSample class GeneratedTests: XCTestCase { func testFeatureABlock() { assertCanParseFromDefaults(FeatureABlock.self) } func testRoot_RootBlock() { assertCanParseFromDefaults(Root.RootBlock.self) } private func assertCanParseFromDefaults<T: Fetchable>(_ type: T.Type) { // Logic goes here... } }
所有測試都通過了:sweat_smile::white_check_mark:而且,儘管他們目前沒有做很多事情,但可以擴充套件實現,以提供一些範例資料和一個JSONDecoder範例來對每個單元測試進行解析。
到此這篇關於使用 Swift Package 外掛生成程式碼的文章就介紹到這了,更多相關Swift Package 外掛生成程式碼內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45