Skip to content

Instantly share code, notes, and snippets.

@kientux
Last active March 30, 2022 08:05
Show Gist options
  • Save kientux/0029c7f8483fd280f9f994e8278fa8b9 to your computer and use it in GitHub Desktop.
Save kientux/0029c7f8483fd280f9f994e8278fa8b9 to your computer and use it in GitHub Desktop.
Remove old Xcode archives. Can be set up to run every day to free up CI machine disk. Default will remove all development/staging archives.
import Foundation
extension FileManager {
typealias FileExistResult = (exist: Bool, isDirectory: Bool)
func checkExists(at path: String) -> FileExistResult {
var fileIsDirectory: ObjCBool = false
let exist = fileExists(atPath: path, isDirectory: &fileIsDirectory)
return (exist, fileIsDirectory.boolValue)
}
func isDirectoryExists(at path: String) -> Bool {
let result = checkExists(at: path)
return result.exist && result.isDirectory
}
}
struct XCArchive: Codable {
var appName: String
var date: Date
var path: URL
}
func removeOldArchives() {
let fm = FileManager.default
let xcodeArchivePath = "Library/Developer/Xcode/Archives"
let archivesPath = URL(string: fm.homeDirectoryForCurrentUser.appendingPathComponent(xcodeArchivePath).path)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH.mm.ss"
do {
let contents = try fm.contentsOfDirectory(at: archivesPath, includingPropertiesForKeys: nil)
var archives: [XCArchive] = []
for path in contents where fm.isDirectoryExists(at: path.path) {
let subcontents = try fm.contentsOfDirectory(at: path, includingPropertiesForKeys: nil)
for subpath in subcontents where fm.isDirectoryExists(at: subpath.path) {
if subpath.pathExtension != "xcarchive" {
continue
}
let name = subpath.lastPathComponent
let regex = try NSRegularExpression(pattern: "(.+) ([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}\\.[0-9]{2}\\.[0-9]{2})\\.xcarchive")
let matches = regex.matches(in: name, options: [], range: NSRange(location: 0, length: name.count))
guard let match = matches.first else {
continue
}
var appName: String?
var date: Date?
for i in 1..<match.numberOfRanges {
let range = match.range(at: i)
let s = (name as NSString).substring(with: range)
switch i {
case 1:
appName = s
case 2:
date = dateFormatter.date(from: s)
default:
break
}
if let appName = appName, let date = date {
archives.append(XCArchive(appName: appName, date: date, path: subpath))
}
}
}
}
var groupedArchives: [String: [XCArchive]] = [:]
for archive in archives {
if groupedArchives[archive.appName] == nil {
groupedArchives[archive.appName] = []
}
groupedArchives[archive.appName]?.append(archive)
}
for (name, archives) in groupedArchives {
if name.lowercased().contains("dev")
|| name.lowercased().contains("stage")
|| name.lowercased().contains("staging") {
for archive in archives {
print("Remove dev/staging archive:", archive.path.path)
try fm.removeItem(at: archive.path)
}
continue
}
if archives.isEmpty || archives.count == 1 {
continue
}
var sorted = archives.sorted(by: { $0.date.timeIntervalSince1970 > $1.date.timeIntervalSince1970 })
sorted.removeFirst()
for archive in sorted {
print("Remove old release archive:", archive.path.path)
try fm.removeItem(at: archive.path)
}
}
} catch {
print(error)
}
}
removeOldArchives()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment