-
Notifications
You must be signed in to change notification settings - Fork 2
/
release.swift
executable file
·160 lines (118 loc) · 4.45 KB
/
release.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/swift
import Foundation
struct Version: Equatable, CustomStringConvertible, Comparable {
let major: Int
let minor: Int
private static let matcher = try! NSRegularExpression(pattern: "^(\\d+)\\.(\\d+)$")
init?(_ value: String) {
let nsValue = value as NSString
guard let match = Version.matcher.firstMatch(in: value, range: NSRange(location:0, length: nsValue.length)) else {
return nil
}
self.major = Int(nsValue.substring(with: match.range(at: 1)))!
self.minor = Int(nsValue.substring(with: match.range(at: 2)))!
}
public static func < (lhs: Version, rhs: Version) -> Bool {
return lhs.major < rhs.major || (lhs.major == rhs.major && lhs.minor < rhs.minor)
}
var description: String {
return "\(major).\(minor)"
}
}
class InfoPlist {
let filePath: URL
private let propertyList: NSMutableDictionary
init(in directory: String) {
filePath = URL(fileURLWithPath:directory, isDirectory: true).appendingPathComponent("Info.plist", isDirectory: false)
guard let inputStream = InputStream(url: filePath) else {
fatalError("Unable to find Info.plist in \(directory) directory")
}
inputStream.open()
defer { inputStream.close() }
var format = PropertyListSerialization.PropertyListFormat.xml
propertyList = try! PropertyListSerialization.propertyList(with: inputStream, options: .mutableContainersAndLeaves, format: &format) as! NSMutableDictionary
}
var version: Version {
get {
return Version(propertyList["CFBundleShortVersionString"]! as! String)!
}
set {
propertyList["CFBundleShortVersionString"] = newValue.description
}
}
var revision: Int {
get {
return Int(propertyList["CFBundleVersion"]! as! String)!
}
set {
// This must be a string; if it’s written as an integer in the bundle, some system components will crash
propertyList["CFBundleVersion"] = String(newValue)
}
}
func save() {
guard let outputStream = OutputStream(url: filePath, append: false) else {
fatalError("Unable to open \(filePath)")
}
outputStream.open()
defer { outputStream.close() }
let err: ErrorPointer = nil
if PropertyListSerialization.writePropertyList(propertyList, to: outputStream, format: .xml, options: 0, error: err) == 0 {
fatalError("Failed to write property list to \(filePath)")
}
}
}
func runCmd(_ cmdPath: String, _ args: [String]) {
let cmdUrl = URL(fileURLWithPath: cmdPath)
let process = try! Process.run(cmdUrl, arguments: args)
process.waitUntilExit()
guard process.terminationStatus == 0 else {
fatalError("\(cmdPath) failed with exit code \(process.terminationStatus)")
}
}
func runCmd(_ cmdPath: String, _ args: String...) {
runCmd(cmdPath, args)
}
func runGit(_ args: String...) {
runCmd("/usr/bin/git", args)
}
let args = CommandLine.arguments
let progName = args[0]
guard args.count == 2, let newVersion = Version(args[1]) else {
print("Usage: \(progName) <new version>")
exit(1)
}
let infoPlists = [InfoPlist(in: "AudioSwitchPrefs"), InfoPlist(in: "AudioSwitchHelper")]
guard newVersion > infoPlists[0].version else {
print("New version \(newVersion) must be greater than previous version \(infoPlists[0].version)")
exit(1)
}
let newRevision = infoPlists[1].revision + 1
print("New version: \(newVersion), new revision: \(newRevision)")
infoPlists.forEach { info in
info.version = newVersion
info.revision = newRevision
info.save()
}
let cachePath = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let buildSuffix = ProcessInfo().globallyUniqueString
let buildPathUrl = cachePath.appendingPathComponent("net.duvert.ADBuild.\(buildSuffix)", isDirectory: true)
defer {
do {
try FileManager.default.removeItem(at: buildPathUrl)
} catch {
print("Warning, removing the build directory failed: \(error)")
}
}
runCmd("/usr/bin/xcodebuild", "-configuration", "Release", "-scheme", "AudioSwitch Preferences", "CONFIGURATION_BUILD_DIR=\(buildPathUrl.path)")
let outputArchivePath = URL(fileURLWithPath: "AudioSwitch_v\(newVersion).zip").path
do {
let cwd = FileManager.default.currentDirectoryPath
defer { FileManager.default.changeCurrentDirectoryPath(cwd) }
FileManager.default.changeCurrentDirectoryPath(buildPathUrl.path)
runCmd("/usr/bin/zip", "-r", outputArchivePath, "AudioSwitch Preferences.app")
}
infoPlists.forEach { info in
runGit("add", info.filePath.path)
}
runGit("commit", "-m", "Release v\(newVersion)")
runGit("tag", "-a", "v\(newVersion)", "-m", "Release v\(newVersion)")