From 272dc0a2f06292396e89724ed45b43dc74dcfcb5 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Thu, 15 Dec 2022 09:35:00 +0000 Subject: [PATCH] Add a tool to build MatrixRustSDK. (#369) --- CONTRIBUTING.md | 14 +- .../xcshareddata/swiftpm/Package.resolved | 18 +++ Package.resolved | 18 +++ Package.swift | 25 +++- Tools/Sources/BuildSDK.swift | 129 ++++++++++++++++++ Tools/Sources/Tools.swift | 8 ++ changelog.d/362.build | 1 + 7 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 Tools/Sources/BuildSDK.swift create mode 100644 Tools/Sources/Tools.swift create mode 100644 changelog.d/362.build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 273140585..7240d4785 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,17 @@ The Xcode project itself is generated through [xcodegen](https://github.com/yona Dependencies will be automatically fetched through the Swift Package Manager, including a release version of the MatrixRustSDK. If you encounter issues while resolving the package graph please attempt a cache reset through `File -> Packages -> Reset Package Caches`. -For instructions on how to setup the RustSDK in development mode please refer to the [matrix-rust-components-swift](https://github.com/matrix-org/matrix-rust-components-swift) repository. +To setup the RustSDK in local development mode run the following command + +``` +swift run tools build-sdk +``` + +This will clone a copy of the SDK if needed, build it for all supported architectures and configure ElementX to use the built framework. To learn about additional options run + +``` +swift run tools build-sdk --help +``` ### Tools @@ -109,7 +119,7 @@ $ towncrier build --draft --version 1.2.3 For Swift coding style we use [SwiftLint](https://github.com/realm/SwiftLint) to check some conventions at compile time (rules are located in the `.swiftlint.yml` file). Otherwise please have a look to [Apple Swift conventions](https://swift.org/documentation/api-design-guidelines.html#conventions). We are also using some of the conventions of [raywenderlich.com Swift style guide](https://github.com/raywenderlich/swift-style-guide). -We enforce the coding style by running checks on the CI for every PR through [Danger](Dangerfile.swift), [SwiftLint](.swiftlint.yml) and [SonarCloud](https://sonarcloud.io/project/overview?id=vector-im_element-x-ios) +We enforce the coding style by running checks on the CI for every PR through [Danger](Dangerfile.swift), [SwiftLint](.swiftlint.yml), [SwiftFormat](.swiftformat) and [SonarCloud](https://sonarcloud.io/project/overview?id=vector-im_element-x-ios) We also gather coverage reports on every PR through [Codecov](https://app.codecov.io/gh/vector-im/element-x-ios) and will eventually start enforcing minimums. diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ae7b76585..c6b4d6dba 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -108,6 +108,15 @@ "version" : "7.30.2" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "fddd1c00396eed152c45a46bea9f47b98e59301d", + "version" : "1.2.0" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", @@ -143,6 +152,15 @@ "revision" : "12b5acf96d98f91d50de447369bd18df74600f1a", "version" : "1.9.6" } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams", + "state" : { + "revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6", + "version" : "5.0.1" + } } ], "version" : 2 diff --git a/Package.resolved b/Package.resolved index 54808346a..d1eb8040e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -9,6 +9,15 @@ "version" : "0.0.3" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "fddd1c00396eed152c45a46bea9f47b98e59301d", + "version" : "1.2.0" + } + }, { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", @@ -17,6 +26,15 @@ "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", "version" : "0.1.4" } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6", + "version" : "5.0.1" + } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index fbf541dfd..3d08c6f51 100644 --- a/Package.swift +++ b/Package.swift @@ -1,17 +1,24 @@ -// swift-tools-version: 5.6 +// swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( - name: "DesignKit", - platforms: [.iOS(.v14)], + name: "Element Swift", + platforms: [ + .iOS(.v14), + .macOS(.v13) + ], products: [ - .library(name: "DesignKit", targets: ["DesignKit"]) + .library(name: "DesignKit", targets: ["DesignKit"]), + .executable(name: "tools", targets: ["Tools"]) ], dependencies: [ .package(url: "https://github.com/vector-im/element-design-tokens.git", exact: "0.0.3"), - .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4") + .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"), + /* Command line tools dependencies */ + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"), + .package(url: "https://github.com/jpsim/Yams", from: "5.0.1") ], targets: [ .target(name: "DesignKit", @@ -22,6 +29,12 @@ let package = Package( path: "DesignKit"), .testTarget(name: "DesignKitTests", dependencies: ["DesignKit"], - path: "DesignKitTests") + path: "DesignKitTests"), + .executableTarget(name: "Tools", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Yams", package: "Yams") + ], + path: "Tools/Sources") ] ) diff --git a/Tools/Sources/BuildSDK.swift b/Tools/Sources/BuildSDK.swift new file mode 100644 index 000000000..8600050b3 --- /dev/null +++ b/Tools/Sources/BuildSDK.swift @@ -0,0 +1,129 @@ +import ArgumentParser +import Foundation +import Yams + +struct BuildSDK: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "A tool to checkout and build MatrixRustSDK locally for development.") + + @Argument(help: "An optional argument to specify a branch of the SDK.") + var branch: String? + + @Option(help: "The target to build for such as aarch64-apple-ios. Omit this option to build for all targets.") + var target: String? + + private var projectDirectoryURL: URL { URL(filePath: FileManager.default.currentDirectoryPath) } + private var parentDirectoryURL: URL { projectDirectoryURL.deletingLastPathComponent() } + private var sdkDirectoryURL: URL { parentDirectoryURL.appending(path: "matrix-rust-sdk") } + + enum Error: LocalizedError { + case scriptFailed + case rustupOutputFailure + case missingRustTargets([String]) + case failureParsingProjectYAML + + var errorDescription: String? { + switch self { + case .missingRustTargets(let missingTargets): + return """ + Rust is missing the necessary targets to build the SDK. + Run the following command to install them: + + rustup target add \(missingTargets.joined(separator: " ")) --toolchain nightly + + """ + default: + return nil + } + } + } + + func run() throws { + try checkRustupTargets() + try cloneSDKIfNeeded() + try checkoutBranchIfSupplied() + try buildFramework() + try updateXcodeProject() + } + + /// Checks that all of the required targets have been added through rustup + /// but only when the ``target`` option hasn't been supplied. + func checkRustupTargets() throws { + guard target == nil else { return } + guard let output = try zsh("rustup show", workingDirectoryURL: projectDirectoryURL) else { throw Error.rustupOutputFailure } + + var requiredTargets = [ + "aarch64-apple-darwin": false, + "aarch64-apple-ios": false, + "aarch64-apple-ios-sim": false, + "x86_64-apple-darwin": false, + "x86_64-apple-ios": false + ] + output.enumerateLines { line, _ in + if requiredTargets.keys.contains(line) { + requiredTargets[line] = true + } + } + + let missingTargets = requiredTargets.compactMap { !$0.value ? $0.key : nil } + guard missingTargets.isEmpty else { throw Error.missingRustTargets(missingTargets) } + } + + /// Clones the Rust SDK if a copy isn't found in the parent directory. + func cloneSDKIfNeeded() throws { + guard !FileManager.default.fileExists(atPath: sdkDirectoryURL.path) else { return } + try zsh("git clone https://github.com/matrix-org/matrix-rust-sdk", workingDirectoryURL: parentDirectoryURL) + } + + /// Checkout the specified branch of the SDK if supplied. + func checkoutBranchIfSupplied() throws { + guard let branch else { return } + try zsh("git checkout \(branch)", workingDirectoryURL: sdkDirectoryURL) + } + + /// Build the Rust SDK as an XCFramework with the debug profile. + func buildFramework() throws { + var buildCommand = "cargo xtask swift build-framework --profile dbg" + if let target { + buildCommand.append(" --only-target \(target)") + } + try zsh(buildCommand, workingDirectoryURL: sdkDirectoryURL) + } + + /// Update the Xcode project to use the build of the SDK. + func updateXcodeProject() throws { + try updateProjectYAML() + try zsh("xcodegen", workingDirectoryURL: projectDirectoryURL) + } + + /// Update project.yml with the local path of the SDK. + func updateProjectYAML() throws { + let yamlURL = projectDirectoryURL.appending(path: "project.yml") + let yamlString = try String(contentsOf: yamlURL) + guard var projectConfig = try Yams.compose(yaml: yamlString) else { throw Error.failureParsingProjectYAML } + + projectConfig["packages"]?.mapping?["MatrixRustSDK"]? = ["path": "../matrix-rust-sdk"] + + let updatedYAMLString = try Yams.serialize(node: projectConfig) + try updatedYAMLString.write(to: yamlURL, atomically: true, encoding: .utf8) + } + + /// Runs a command in zsh. + @discardableResult + func zsh(_ command: String, workingDirectoryURL: URL) throws -> String? { + let process = Process() + process.executableURL = URL(filePath: "/bin/zsh") + process.arguments = ["-c", command] + process.currentDirectoryURL = workingDirectoryURL + + let outputPipe = Pipe() + process.standardOutput = outputPipe + + try process.run() + process.waitUntilExit() + + guard process.terminationReason == .exit, process.terminationStatus == 0 else { throw Error.scriptFailed } + + guard let outputData = try outputPipe.fileHandleForReading.readToEnd() else { return nil } + return String(data: outputData, encoding: .utf8) + } +} diff --git a/Tools/Sources/Tools.swift b/Tools/Sources/Tools.swift new file mode 100644 index 000000000..40cf0e852 --- /dev/null +++ b/Tools/Sources/Tools.swift @@ -0,0 +1,8 @@ +import ArgumentParser +import Foundation + +@main +struct Tools: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "A collection of command line tools for ElementX", + subcommands: [BuildSDK.self]) +} diff --git a/changelog.d/362.build b/changelog.d/362.build new file mode 100644 index 000000000..31a9b7676 --- /dev/null +++ b/changelog.d/362.build @@ -0,0 +1 @@ +Tools: Add a command line tool to build a local copy of the SDK for debugging.