add a unxip experiment for faster unxipping

This commit is contained in:
Matt Kiazyk 2022-02-04 22:11:07 -06:00
parent c0974edc98
commit ce001c8e68
No known key found for this signature in database
GPG key ID: 850581D2373E4A99
13 changed files with 139 additions and 9 deletions

View file

@ -18,6 +18,13 @@ _If you're looking for a command-line version of Xcodes.app, try [`xcodes`](http
- View release notes, OS compatibility, included SDKs and compilers from [Xcode Releases](https://xcodereleases.com).
- Dark/Light Mode supported
## Experiments
- Thanks to the wonderful work of [https://github.com/saagarjha/unxip](https://github.com/saagarjha/unxip), turn on the experiment to increase your unxipping time by up to 70%! More can be found on his repo, but bugs, high memory may occur if used.
![](experiment_light.png#gh-light-mode-only)
![](experiment_dark.png#gh-dark-mode-only)
## Installation
Xcodes.app runs on macOS Big Sur 11.0 or later.
@ -80,7 +87,10 @@ scripts/package_release.sh
# Notarize the app
# Do this from the Product directory so the app is zipped without being nested inside Product
# Create a app specific password on appleid.apple.com if you haven't already
# % xcrun altool --store-password-in-keychain-item "AC_PASSWORD" -u "<appleiduseremail>" -p <app_specific_secret>
# xcrun notarytool store-credentials "AC_PASSWORD" \
# --apple-id "test@example.com" \
# --team-id "teamid" \
# --password "app specific password"
pushd Product
../scripts/notarize.sh "test@example.com" "@keychain:AC_PASSWORD" <MyOrg> Xcodes.zip

View file

@ -104,6 +104,9 @@
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; };
E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; };
E8CBDB8727ADD92000B22292 /* unxip in Resources */ = {isa = PBXBuildFile; fileRef = E8CBDB8627ADD92000B22292 /* unxip */; };
E8CBDB8927ADE32300B22292 /* unxip in Copy aria2c */ = {isa = PBXBuildFile; fileRef = E8CBDB8627ADD92000B22292 /* unxip */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */; };
E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; };
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; };
@ -153,6 +156,7 @@
dstPath = "";
dstSubfolderSpec = 6;
files = (
E8CBDB8927ADE32300B22292 /* unxip in Copy aria2c */,
CAA8589325A2B77E00ACF8C0 /* aria2c in Copy aria2c */,
);
name = "Copy aria2c";
@ -272,6 +276,8 @@
E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = "<group>"; };
E89342F925EDCC17007CF557 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
E8CBDB8627ADD92000B22292 /* unxip */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = unxip; sourceTree = "<group>"; };
E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperiementsPreferencePane.swift; sourceTree = "<group>"; };
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -484,6 +490,7 @@
CA9FF83E2594FBC000E47BAF /* Licenses.rtf */,
CAD2E7AE2449575000113D76 /* Xcodes.entitlements */,
CA8FB64D256E17B100469DA5 /* XcodesTest.entitlements */,
E8CBDB8627ADD92000B22292 /* unxip */,
);
path = Resources;
sourceTree = "<group>";
@ -561,6 +568,7 @@
CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */,
E8977EA225C11E1500835F80 /* PreferencesView.swift */,
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */,
E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */,
);
path = Preferences;
sourceTree = "<group>";
@ -707,6 +715,7 @@
files = (
CAD2E7A92449575000113D76 /* Preview Assets.xcassets in Resources */,
CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */,
E8CBDB8727ADD92000B22292 /* unxip in Resources */,
CAA858DB25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz in Resources */,
CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */,
);
@ -785,6 +794,7 @@
CABFA9BD2592EEEA00380FEE /* Environment.swift in Sources */,
CABFA9C32592EEEA00380FEE /* Downloads.swift in Sources */,
CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */,
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */,
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */,
CA378F992466567600A58CE0 /* AppState.swift in Sources */,
CAD2E7A42449574E00113D76 /* XcodeListView.swift in Sources */,

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -238,8 +238,8 @@ extension AppState {
func unarchiveAndMoveXIP(availableXcode: AvailableXcode, at source: URL, to destination: URL) -> AnyPublisher<URL, Swift.Error> {
self.setInstallationStep(of: availableXcode.version, to: .unarchiving)
return Current.shell.unxip(source)
return unxipOrUnxipExperiment(source)
.catch { error -> AnyPublisher<ProcessOutput, Swift.Error> in
if let executionError = error as? ProcessExecutionError {
if executionError.standardError.contains("damaged and cant be expanded") {
@ -278,6 +278,16 @@ extension AppState {
})
.eraseToAnyPublisher()
}
func unxipOrUnxipExperiment(_ source: URL) -> AnyPublisher<ProcessOutput, Error> {
if unxipExperiment {
// All hard work done by https://github.com/saagarjha/unxip
// Compiled to binary with `swiftc -parse-as-library -O unxip.swift`
return Current.shell.unxipExperiment(source)
} else {
return Current.shell.unxip(source)
}
}
public func verifySecurityAssessment(of xcode: InstalledXcode) -> AnyPublisher<Void, Error> {
return Current.shell.spctlAssess(xcode.path.url)

View file

@ -62,6 +62,12 @@ class AppState: ObservableObject {
Current.defaults.set(localPath, forKey: "localPath")
}
}
@Published var unxipExperiment = false {
didSet {
Current.defaults.set(unxipExperiment, forKey: "unxipExperiment")
}
}
// MARK: - Publisher Cancellables
@ -96,6 +102,7 @@ class AppState: ObservableObject {
func setupDefaults() {
localPath = Current.defaults.string(forKey: "localPath") ?? Path.defaultXcodesApplicationSupport.string
unxipExperiment = Current.defaults.bool(forKey: "unxipExperiment") ?? false
}
// MARK: Timer

View file

@ -111,6 +111,12 @@ public struct Shell {
return (progress, publisher)
}
public var unxipExperiment: (URL) -> AnyPublisher<ProcessOutput, Error> = { url in
let unxipPath = Path(url: Bundle.main.url(forAuxiliaryExecutable: "unxip")!)!
return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"])
}
}
public struct Files {
@ -238,6 +244,11 @@ public struct Defaults {
public func get(forKey key: String) -> Any? {
get(key)
}
public var bool: (String) -> Bool? = { UserDefaults.standard.bool(forKey: $0) }
public func bool(forKey key: String) -> Bool? {
bool(key)
}
}
private let helperClient = HelperClient()

View file

@ -14,9 +14,6 @@ struct AboutView: View {
Text("Version \(Bundle.main.shortVersion!) (\(Bundle.main.version!))")
Color.clear
.frame(width: 300, height: 16)
HStack(spacing: 32) {
Button(action: {
openURL(URL(string: "https://github.com/RobotsAndPencils/XcodesApp/")!)
@ -30,6 +27,24 @@ struct AboutView: View {
}
.buttonStyle(LinkButtonStyle())
}
Color.clear
.frame(width: 300, height: 0)
Label("Unxip Experiment", systemImage: "testtube.2")
HStack(spacing: 32) {
Button(action: {
openURL(URL(string: "https://github.com/saagarjha/unxip/")!)
}) {
Label("Github Repo", systemImage: "link")
}
.buttonStyle(LinkButtonStyle())
Button(action: {
openURL(URL(string: "https://github.com/saagarjha/unxip/blob/main/LICENSE")!)
}) {
Label("License", systemImage: "link")
}
.buttonStyle(LinkButtonStyle())
}
Text(Bundle.main.humanReadableCopyright!)
.font(.footnote)

View file

@ -0,0 +1,55 @@
import AppleAPI
import SwiftUI
import Path
struct ExperimentsPreferencePane: View {
@EnvironmentObject var appState: AppState
var body: some View {
VStack(alignment: .leading, spacing: 20) {
GroupBox(label: Text("Faster Unxip")) {
VStack(alignment: .leading) {
Toggle(
"When unxipping, use experiment",
isOn: $appState.unxipExperiment
)
AttributedText(unxipFootnote)
}
.fixedSize(horizontal: false, vertical: true)
}
.groupBoxStyle(PreferencesGroupBoxStyle())
Divider()
}
.frame(width: 500)
}
private var unxipFootnote: NSAttributedString {
let string = """
Thanks to @_saagarjha, this experiment can increase unxipping
speed by up to 70% for some systems.
More information on how this is accomplished can be seen
on the unxip repo - https://github.com/saagarjha/unxip
"""
let attributedString = NSMutableAttributedString(
string: string,
attributes: [
.font: NSFont.preferredFont(forTextStyle: .footnote, options: [:]),
.foregroundColor: NSColor.labelColor
]
)
attributedString.addAttribute(.link, value: URL(string: "https://twitter.com/_saagarjha")!, range: NSRange(string.range(of: "@_saagarjha")!, in: string))
attributedString.addAttribute(.link, value: URL(string: "https://github.com/saagarjha/unxip")!, range: NSRange(string.range(of: "https://github.com/saagarjha/unxip")!, in: string))
return attributedString
}
}
struct ExperimentsPreferencePane_Previews: PreviewProvider {
static var previews: some View {
Group {
ExperimentsPreferencePane()
.environmentObject(AppState())
}
}
}

View file

@ -2,7 +2,7 @@ import SwiftUI
struct PreferencesView: View {
private enum Tabs: Hashable {
case general, updates, advanced
case general, updates, advanced, experiment
}
@EnvironmentObject var appState: AppState
@ -25,6 +25,11 @@ struct PreferencesView: View {
Label("Advanced", systemImage: "gearshape.2")
}
.tag(Tabs.advanced)
ExperimentsPreferencePane()
.tabItem {
Label("Experiments", systemImage: "testtube.2")
}
.tag(Tabs.experiment)
}
.padding(20)
}

View file

@ -25,7 +25,7 @@
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Robots and Pencils.</string>
<string>Copyright © 2022 Robots and Pencils.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key>
@ -35,7 +35,7 @@
<key>SMPrivilegedExecutables</key>
<dict>
<key>com.robotsandpencils.XcodesApp.Helper</key>
<string>identifier "com.robotsandpencils.XcodesApp.Helper" and info [CFBundleShortVersionString] &gt;= "1.0.0" and anchor apple generic and certificate leaf[subject.OU] = "$(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)"</string>
<string>identifier &quot;com.robotsandpencils.XcodesApp.Helper&quot; and info [CFBundleShortVersionString] &gt;= &quot;1.0.0&quot; and anchor apple generic and certificate leaf[subject.OU] = &quot;$(CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT)&quot;</string>
</dict>
<key>SUFeedURL</key>
<string>https://robotsandpencils.github.io/XcodesApp/appcast.xml</string>

BIN
Xcodes/Resources/unxip Executable file

Binary file not shown.

BIN
experiment_dark.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
experiment_light.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB