Automatically open a PR to bump the calver (#4167)
* Update our tools package to Swift 6.1
Also improves the package layout with subdirectories 📁
* Update GenerateSDKMocks to be an Async command.
* Add a tool to bump the project CalVer every month.
* Add a workflow to automatically bump the calendar version.
Note: This only does year & month, the patch is handled by the release script.
This commit is contained in:
42
.github/workflows/automatic-calendar-version.yml
vendored
Normal file
42
.github/workflows/automatic-calendar-version.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Automatic Calendar Version
|
||||
on:
|
||||
schedule:
|
||||
# At 03:00 UTC every Tuesday in preparation for an RC.
|
||||
# The tool assumes the release is published in 6-days (the following Monday).
|
||||
# Note: Most of these runs will be no-op until the release month changes.
|
||||
- cron: '0 3 * * 2'
|
||||
workflow_dispatch:
|
||||
|
||||
# Bumps the year and month, resetting the patch.
|
||||
# Patch bumps are handled by the release script.
|
||||
jobs:
|
||||
automatic-calendar-version:
|
||||
runs-on: macos-15
|
||||
# Skip in forks
|
||||
if: github.repository == 'element-hq/element-x-ios'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup environment
|
||||
run:
|
||||
source ci_scripts/ci_common.sh && setup_github_actions_environment
|
||||
|
||||
- name: Bump the CalVer if needed
|
||||
run: swift run tools bump-calendar-version
|
||||
|
||||
- name: Create a PR for the new version
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
author: ElementRobot <releases@riot.im>
|
||||
commit-message: Bump the calendar version ready for the next release
|
||||
title: Bump the calendar version ready for the next release
|
||||
body: |
|
||||
- Version bump
|
||||
labels: pr-build
|
||||
branch: version/bump
|
||||
base: develop
|
||||
add-paths: |
|
||||
*.yml
|
||||
*.xcodeproj
|
||||
1
.github/workflows/translations-pr.yml
vendored
1
.github/workflows/translations-pr.yml
vendored
@@ -31,6 +31,7 @@ jobs:
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
author: ElementRobot <releases@riot.im>
|
||||
commit-message: Translations update
|
||||
title: Translations update
|
||||
body: |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version: 5.7
|
||||
// swift-tools-version: 6.1
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
@@ -6,7 +6,7 @@ import PackageDescription
|
||||
let package = Package(
|
||||
name: "Element Swift",
|
||||
platforms: [
|
||||
.macOS(.v13)
|
||||
.macOS(.v14)
|
||||
],
|
||||
products: [
|
||||
.executable(name: "tools", targets: ["Tools"])
|
||||
|
||||
@@ -2,9 +2,7 @@ import ArgumentParser
|
||||
import SwiftUI
|
||||
|
||||
struct AppIconBanner: AsyncParsableCommand {
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "A Swift command-line tool to add a banner to an app icons."
|
||||
)
|
||||
static let configuration = CommandConfiguration(abstract: "A Swift command-line tool to add a banner to an app icons.")
|
||||
|
||||
@Argument(help: "Path to the input image.")
|
||||
var path: String
|
||||
@@ -17,7 +17,7 @@ enum Target: String, ExpressibleByArgument, CaseIterable {
|
||||
}
|
||||
|
||||
struct BuildSDK: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(abstract: "A tool to checkout and build MatrixRustSDK locally for development.")
|
||||
static let 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?
|
||||
54
Tools/Sources/Commands/BumpCalendarVersion.swift
Normal file
54
Tools/Sources/Commands/BumpCalendarVersion.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct BumpCalendarVersion: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A tool that bumps the CalVer every month (if needed), setting the patch back to 0.",
|
||||
discussion: "The tool assumes the release will be published in 6-days so bumps early.")
|
||||
|
||||
func run() throws {
|
||||
try updateProjectYAML()
|
||||
try Zsh.run(command: "xcodegen")
|
||||
}
|
||||
|
||||
/// Updates the project YAML with the new version.
|
||||
private func updateProjectYAML() throws {
|
||||
let yamlURL = URL.projectDirectory.appendingPathComponent("project.yml")
|
||||
let yamlString = try String(contentsOf: yamlURL)
|
||||
|
||||
// Use regex instead of Yams to preserve any whitespace, comments etc in the file.
|
||||
let marketingVersionRegex = /MARKETING_VERSION:\s*([^\s]+)/
|
||||
var updatedYAMLString = ""
|
||||
|
||||
yamlString.enumerateLines { line, _ in
|
||||
let processedLine = if let match = line.firstMatch(of: marketingVersionRegex),
|
||||
let newVersion = try? generateNewVersion(from: String(match.1)) {
|
||||
line.replacingOccurrences(of: match.1, with: newVersion)
|
||||
} else {
|
||||
line
|
||||
}
|
||||
|
||||
updatedYAMLString.append(processedLine + "\n")
|
||||
}
|
||||
|
||||
try updatedYAMLString.write(to: yamlURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
/// Returns the new version string if a change is necessary.
|
||||
///
|
||||
/// **Note:** This tool does *not* handle patch bumps, those are done automatically in the release script.
|
||||
private func generateNewVersion(from currentVersion: String) throws -> String? {
|
||||
let releaseDate = Date.now.addingTimeInterval(6 * 24 * 60 * 60) // Always assume we're building the RC.
|
||||
let releaseYear = Calendar.current.component(.year, from: releaseDate) % 1000 // We use the short year.
|
||||
let releaseMonth = Calendar.current.component(.month, from: releaseDate)
|
||||
let versionComponents = currentVersion.split(separator: ".").compactMap { Int($0) }
|
||||
|
||||
guard versionComponents.count == 3 else { fatalError("Unexpected version format: \(currentVersion)") }
|
||||
|
||||
if versionComponents[0] != releaseYear || versionComponents[1] != releaseMonth {
|
||||
return "\(releaseYear).\(String(format: "%02d", releaseMonth)).0"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct DownloadStrings: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(abstract: "A tool to download localizable strings from localazy")
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to download localizable strings from localazy")
|
||||
|
||||
@Flag(help: "Use to download translation keys for all languages")
|
||||
var allLanguages = false
|
||||
@@ -2,7 +2,7 @@ import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
struct GenerateSAS: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(abstract: "A tool to download and generate SAS localization strings")
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to download and generate SAS localization strings")
|
||||
private static let defaultLanguage = "en"
|
||||
|
||||
@Flag(name: .shortAndLong, help: "Increase output verbosity.")
|
||||
44
Tools/Sources/Commands/GenerateSDKMocks.swift
Normal file
44
Tools/Sources/Commands/GenerateSDKMocks.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct GenerateSDKMocks: AsyncParsableCommand {
|
||||
enum GenerateSDKMocksError: Error {
|
||||
case invalidFileUrl
|
||||
}
|
||||
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to setup the mocks for the Matrix Rust SDK")
|
||||
|
||||
@Argument(help: "The argument to specify a branch of the SDK. Use `local` to use your local version")
|
||||
var version: String
|
||||
|
||||
private var fileURLFormat = "https://raw.githubusercontent.com/element-hq/matrix-rust-components-swift/%@/Sources/MatrixRustSDK/matrix_sdk_ffi.swift"
|
||||
|
||||
func run() async throws {
|
||||
if version == "local" {
|
||||
try generateSDKMocks(ffiPath: "\(URL.sdkDirectory.path)/bindings/apple/generated/swift")
|
||||
} else {
|
||||
let path = try await downloadSDK(version: version)
|
||||
try generateSDKMocks(ffiPath: path)
|
||||
try FileManager.default.removeItem(atPath: path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the SDK mocks using Sourcery.
|
||||
func generateSDKMocks(ffiPath: String) throws {
|
||||
try Zsh.run(command: "sourcery --sources \(ffiPath) --templates Tools/Sourcery/SDKAutoMockable.stencil --output ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift")
|
||||
}
|
||||
|
||||
/// Downloads the specified version of the `matrix_sdk_ffi.swift` file and returns the path to the downloaded file.
|
||||
func downloadSDK(version: String) async throws -> String {
|
||||
let fileURLString = String(format: fileURLFormat, version)
|
||||
guard let fileURL = URL(string: fileURLString) else {
|
||||
throw GenerateSDKMocksError.invalidFileUrl
|
||||
}
|
||||
|
||||
let (tempURL, _) = try await URLSession.shared.download(from: fileURL)
|
||||
let sdkFilePath = NSTemporaryDirectory().appending("matrix_sdk_ffi.swift")
|
||||
try FileManager.default.moveItem(at: tempURL, to: URL(fileURLWithPath: sdkFilePath))
|
||||
return sdkFilePath
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ struct Locheck: ParsableCommand {
|
||||
}
|
||||
}
|
||||
|
||||
static var configuration = CommandConfiguration(abstract: "A tool that verifies bad strings contained in localization files")
|
||||
static let configuration = CommandConfiguration(abstract: "A tool that verifies bad strings contained in localization files")
|
||||
|
||||
private var stringsDirectoryURL: URL {
|
||||
.projectDirectory.appendingPathComponent("ElementX/Resources/Localizations")
|
||||
@@ -3,7 +3,7 @@ import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct OutdatedPackages: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(abstract: "A tool to check outdated package dependencies. Please make sure you have already run setup-project before using this tool.")
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to check outdated package dependencies. Please make sure you have already run setup-project before using this tool.")
|
||||
|
||||
private var projectSwiftPMDirectoryURL: URL { .projectDirectory.appendingPathComponent("ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm") }
|
||||
|
||||
@@ -3,7 +3,7 @@ import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct SetupProject: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(abstract: "A tool to setup the required components to efficiently run and contribute to Element X iOS")
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to setup the required components to efficiently run and contribute to Element X iOS")
|
||||
|
||||
func run() throws {
|
||||
try setupGitHooks()
|
||||
@@ -3,7 +3,7 @@ import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct UnusedStrings: ParsableCommand {
|
||||
static var configuration = CommandConfiguration(abstract: "Generates a report showing which strings aren't used in the project.")
|
||||
static let configuration = CommandConfiguration(abstract: "Generates a report showing which strings aren't used in the project.")
|
||||
|
||||
@Flag(help: "Save the results to disk instead of printing them.")
|
||||
var saveToFile = false
|
||||
@@ -1,68 +0,0 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct GenerateSDKMocks: ParsableCommand {
|
||||
enum GenerateSDKMocksError: Error {
|
||||
case invalidFileUrl
|
||||
}
|
||||
|
||||
static var configuration = CommandConfiguration(abstract: "A tool to setup the mocks for the Matrix Rust SDK")
|
||||
|
||||
@Argument(help: "The argument to specify a branch of the SDK. Use `local` to use your local version")
|
||||
var version: String
|
||||
|
||||
private var fileURLFormat = "https://raw.githubusercontent.com/element-hq/matrix-rust-components-swift/%@/Sources/MatrixRustSDK/matrix_sdk_ffi.swift"
|
||||
|
||||
func run() throws {
|
||||
if version == "local" {
|
||||
try generateSDKMocks(ffiPath: "\(URL.sdkDirectory.path)/bindings/apple/generated/swift")
|
||||
} else {
|
||||
try downloadSDK(version: version) { path in
|
||||
try generateSDKMocks(ffiPath: path)
|
||||
try FileManager.default.removeItem(atPath: path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the SDK mocks using Sourcery.
|
||||
func generateSDKMocks(ffiPath: String) throws {
|
||||
try Zsh.run(command: "sourcery --sources \(ffiPath) --templates Tools/Sourcery/SDKAutoMockable.stencil --output ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift")
|
||||
}
|
||||
|
||||
/// Downloads the specified version of the `matrix_sdk_ffi.swift` file and returns the path to the downloaded file.
|
||||
func downloadSDK(version: String, completionHandler: @escaping (String) throws -> Void) throws {
|
||||
var sdkFilePath = ""
|
||||
let fileURLString = String(format: fileURLFormat, version)
|
||||
guard let fileURL = URL(string: fileURLString) else {
|
||||
throw GenerateSDKMocksError.invalidFileUrl
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
||||
let task = URLSession.shared.downloadTask(with: fileURL) { tempURL, _, error in
|
||||
guard let tempURL = tempURL else {
|
||||
if let error = error {
|
||||
print("Error downloading SDK file: \(error)")
|
||||
} else {
|
||||
print("Unknown error occurred while downloading SDK file.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
sdkFilePath = NSTemporaryDirectory().appending("matrix_sdk_ffi.swift")
|
||||
try FileManager.default.moveItem(at: tempURL, to: URL(fileURLWithPath: sdkFilePath))
|
||||
try completionHandler(sdkFilePath)
|
||||
semaphore.signal()
|
||||
} catch {
|
||||
print("Error setting up SDK: \(error)")
|
||||
semaphore.signal()
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
_ = semaphore.wait(timeout: .distantFuture)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import Foundation
|
||||
|
||||
@main
|
||||
struct Tools: AsyncParsableCommand {
|
||||
static var configuration = CommandConfiguration(abstract: "A collection of command line tools for ElementX",
|
||||
static let configuration = CommandConfiguration(abstract: "A collection of command line tools for ElementX",
|
||||
subcommands: [BuildSDK.self,
|
||||
SetupProject.self,
|
||||
OutdatedPackages.self,
|
||||
@@ -12,5 +12,6 @@ struct Tools: AsyncParsableCommand {
|
||||
GenerateSDKMocks.self,
|
||||
GenerateSAS.self,
|
||||
AppIconBanner.self,
|
||||
UnusedStrings.self])
|
||||
UnusedStrings.self,
|
||||
BumpCalendarVersion.self])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user