add option to rename Xcode to Xcode.app on select.

This commit is contained in:
Matt Kiazyk 2022-07-24 15:01:03 -05:00
parent 491e810a3c
commit b64af2fc44
No known key found for this signature in database
GPG key ID: 850581D2373E4A99
7 changed files with 133 additions and 9 deletions

View file

@ -108,6 +108,7 @@
E8CBDB8927ADE32300B22292 /* unxip in Copy aria2c */ = {isa = PBXBuildFile; fileRef = E8CBDB8627ADD92000B22292 /* unxip */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */; };
E8D0296F284B029800647641 /* BottomStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D0296E284B029800647641 /* BottomStatusBar.swift */; };
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D655BF288DD04700A139C2 /* SelectedActionType.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 */; };
@ -296,6 +297,7 @@
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>"; };
E8D0296E284B029800647641 /* BottomStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomStatusBar.swift; sourceTree = "<group>"; };
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.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 */
@ -479,6 +481,7 @@
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */,
E87DD6EA25D053FA00D86808 /* Progress+.swift */,
E81D7E9F2805250100A205FC /* Collection+.swift */,
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */,
);
path = Backend;
sourceTree = "<group>";
@ -844,6 +847,7 @@
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */,
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */,
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
CAFBDB912598FE80003DCC5A /* SelectedXcode.swift in Sources */,

View file

@ -74,6 +74,20 @@ class AppState: ObservableObject {
}
}
var createSymLinkOnSelectDisabled: Bool {
return onSelectActionType == .rename
}
@Published var onSelectActionType = SelectedActionType.none {
didSet {
Current.defaults.set(onSelectActionType.rawValue, forKey: "onSelectActionType")
if onSelectActionType == .rename {
createSymLinkOnSelect = false
}
}
}
// MARK: - Publisher Cancellables
var cancellables = Set<AnyCancellable>()
@ -120,6 +134,7 @@ class AppState: ObservableObject {
localPath = Current.defaults.string(forKey: "localPath") ?? Path.defaultXcodesApplicationSupport.string
unxipExperiment = Current.defaults.bool(forKey: "unxipExperiment") ?? false
createSymLinkOnSelect = Current.defaults.bool(forKey: "createSymLinkOnSelect") ?? false
onSelectActionType = SelectedActionType(rawValue: Current.defaults.string(forKey: "onSelectActionType") ?? "none") ?? .none
}
// MARK: Timer
@ -492,10 +507,15 @@ class AppState: ObservableObject {
}
guard
let installedXcodePath = xcode.installedPath,
var installedXcodePath = xcode.installedPath,
selectPublisher == nil
else { return }
if onSelectActionType == .rename {
guard let newDestinationXcodePath = renameToXcode(xcode: xcode) else { return }
installedXcodePath = newDestinationXcodePath
}
selectPublisher = installHelperIfNecessary()
.flatMap {
Current.helper.switchXcodePath(installedXcodePath.string)
@ -575,7 +595,39 @@ class AppState: ObservableObject {
self.error = error
self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.legibleLocalizedDescription)
}
}
func renameToXcode(xcode: Xcode) -> Path? {
guard let installedXcodePath = xcode.installedPath else { return nil }
let destinationPath: Path = Path.installDirectory/"Xcode.app"
// rename any old named `Xcode.app` to the Xcodes versioned named files
if FileManager.default.fileExists(atPath: destinationPath.string) {
if let originalXcode = Current.files.installedXcode(destination: destinationPath) {
let newName = "Xcode-\(originalXcode.version.descriptionWithoutBuildMetadata).app"
Logger.appState.debug("Found Xcode.app - renaming back to \(newName)")
do {
try destinationPath.rename(to: newName)
} catch {
Logger.appState.error("Unable to create rename Xcode.app back to original")
self.error = error
// TODO UPDATE MY ERROR STRING
self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.legibleLocalizedDescription)
}
}
}
// rename passed in xcode to xcode.app
Logger.appState.debug("Found Xcode.app - renaming back to Xcode.app")
do {
return try installedXcodePath.rename(to: "Xcode.app")
} catch {
Logger.appState.error("Unable to create rename Xcode.app back to original")
self.error = error
// TODO UPDATE MY ERROR STRING
self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.legibleLocalizedDescription)
}
return nil
}
func updateAllXcodes(availableXcodes: [AvailableXcode], installedXcodes: [InstalledXcode], selectedXcodePath: String?) {

View file

@ -2,13 +2,12 @@ import Foundation
import Path
extension Entry {
var isAppBundle: Bool {
static func isAppBundle(kind: Kind, path: Path) -> Bool {
kind == .directory &&
path.extension == "app" &&
!path.isSymlink
}
var infoPlist: InfoPlist? {
static func infoPlist(kind: Kind, path: Path) -> InfoPlist? {
let infoPlistPath = path.join("Contents").join("Info.plist")
guard
let infoPlistData = try? Data(contentsOf: infoPlistPath.url),
@ -17,4 +16,12 @@ extension Entry {
return infoPlist
}
var isAppBundle: Bool {
Entry.isAppBundle(kind: kind, path: path)
}
var infoPlist: InfoPlist? {
Entry.infoPlist(kind: kind, path: path)
}
}

View file

@ -164,7 +164,16 @@ public struct Files {
}
public var installedXcodes = _installedXcodes
public func installedXcode(destination: Path) -> InstalledXcode? {
if Entry.isAppBundle(kind: destination.isDirectory ? .directory : .file, path: destination) && Entry.infoPlist(kind: destination.isDirectory ? .directory : .file, path: destination)?.bundleID == "com.apple.dt.Xcode" {
return InstalledXcode.init(path: destination)
} else {
return nil
}
}
}
private func _installedXcodes(destination: Path) -> [InstalledXcode] {
((try? destination.ls()) ?? [])
.filter { $0.isAppBundle && $0.infoPlist?.bundleID == "com.apple.dt.Xcode" }

View file

@ -0,0 +1,31 @@
//
// SelectedActionType.swift
// Xcodes
//
// Created by Matt Kiazyk on 2022-07-24.
// Copyright © 2022 Robots and Pencils. All rights reserved.
//
import Foundation
public enum SelectedActionType: String, CaseIterable, Identifiable, CustomStringConvertible {
case none
case rename
public var id: Self { self }
public static var `default` = SelectedActionType.none
public var description: String {
switch self {
case .none: return localizeString("OnSelectDoNothing")
case .rename: return localizeString("OnSelectRenameXcode")
}
}
public var detailedDescription: String {
switch self {
case .none: return localizeString("OnSelectDoNothingDescription")
case .rename: return localizeString("OnSelectRenameXcodeDescription")
}
}
}

View file

@ -48,13 +48,27 @@ struct AdvancedPreferencePane: View {
GroupBox(label: Text("Active/Select")) {
VStack(alignment: .leading) {
Toggle(
"AutomaticallyCreateSymbolicLink",
isOn: $appState.createSymLinkOnSelect
)
Text("AutomaticallyCreateSymbolicLinkDescription")
Picker("OnSelect", selection: $appState.onSelectActionType) {
Text(SelectedActionType.none.description)
.tag(SelectedActionType.none)
Text(SelectedActionType.rename.description)
.tag(SelectedActionType.rename)
}
.labelsHidden()
.pickerStyle(.inline)
Text(appState.onSelectActionType.detailedDescription)
.font(.footnote)
.fixedSize(horizontal: false, vertical: true)
Spacer()
.frame(height: 20)
Toggle("AutomaticallyCreateSymbolicLink", isOn: $appState.createSymLinkOnSelect)
.disabled(appState.createSymLinkOnSelectDisabled)
Text("AutomaticallyCreateSymbolicLinkDescription")
.font(.footnote)
.fixedSize(horizontal: false, vertical: true)
}
.fixedSize(horizontal: false, vertical: true)
}
@ -154,6 +168,7 @@ struct AdvancedPreferencePane_Previews: PreviewProvider {
AdvancedPreferencePane()
.environmentObject(AppState())
}
.frame(width: 500, height: 700, alignment: .center)
}
}

View file

@ -78,8 +78,14 @@
"LocalCachePathDescription" = "Xcodes caches available Xcode versions and temporary downloads new versions to a directory";
"Change" = "Change";
"Active/Select" = "Active/Select";
"OnSelectDoNothing" = "Keep name as Xcode-X.X.X.app";
"OnSelectDoNothingDescription" = "On select, will keep the name as the version eg. Xcode-13.4.1.app";
"AutomaticallyCreateSymbolicLink" = "Automatically create symbolic link to Xcode.app";
"AutomaticallyCreateSymbolicLinkDescription" = "When making an Xcode version Active/Selected, try and create a symbolic link named Xcode.app in the installation directory";
"OnSelectRenameXcode" = "Always rename to Xcode.app";
"OnSelectRenameXcodeDescription" = "On select, will automatically try and rename the active Xcode to Xcode.app, renaming the previous Xcode.app to the version name.";
"DataSource" = "Data Source";
"DataSourceDescription" = "The Apple data source scrapes the Apple Developer website. It will always show the latest releases that are available, but is more fragile.\n\nXcode Releases is an unofficial list of Xcode releases. It's provided as well-formed data, contains extra information that is not readily available from Apple, and is less likely to break if Apple redesigns their developer website.";
"Downloader" = "Downloader";