mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-04-26 14:57:37 +00:00
Merge pull request #267 from RobotsAndPencils/renameOnSelect
add option to rename Xcode to Xcode.app on select.
This commit is contained in:
commit
ee9ee59fc4
7 changed files with 133 additions and 9 deletions
|
|
@ -108,6 +108,7 @@
|
||||||
E8CBDB8927ADE32300B22292 /* unxip in Copy aria2c */ = {isa = PBXBuildFile; fileRef = E8CBDB8627ADD92000B22292 /* unxip */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
E8CBDB8927ADE32300B22292 /* unxip in Copy aria2c */ = {isa = PBXBuildFile; fileRef = E8CBDB8627ADD92000B22292 /* unxip */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */; };
|
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8CBDB8A27AE02FF00B22292 /* ExperiementsPreferencePane.swift */; };
|
||||||
E8D0296F284B029800647641 /* BottomStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D0296E284B029800647641 /* BottomStatusBar.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 */; };
|
E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; };
|
||||||
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
|
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
|
||||||
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
@ -479,6 +481,7 @@
|
||||||
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */,
|
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */,
|
||||||
E87DD6EA25D053FA00D86808 /* Progress+.swift */,
|
E87DD6EA25D053FA00D86808 /* Progress+.swift */,
|
||||||
E81D7E9F2805250100A205FC /* Collection+.swift */,
|
E81D7E9F2805250100A205FC /* Collection+.swift */,
|
||||||
|
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */,
|
||||||
);
|
);
|
||||||
path = Backend;
|
path = Backend;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -844,6 +847,7 @@
|
||||||
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */,
|
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */,
|
||||||
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */,
|
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */,
|
||||||
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
|
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
|
||||||
|
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
|
||||||
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
|
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */,
|
||||||
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
|
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */,
|
||||||
CAFBDB912598FE80003DCC5A /* SelectedXcode.swift in Sources */,
|
CAFBDB912598FE80003DCC5A /* SelectedXcode.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -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
|
// MARK: - Publisher Cancellables
|
||||||
|
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
|
@ -120,6 +134,7 @@ class AppState: ObservableObject {
|
||||||
localPath = Current.defaults.string(forKey: "localPath") ?? Path.defaultXcodesApplicationSupport.string
|
localPath = Current.defaults.string(forKey: "localPath") ?? Path.defaultXcodesApplicationSupport.string
|
||||||
unxipExperiment = Current.defaults.bool(forKey: "unxipExperiment") ?? false
|
unxipExperiment = Current.defaults.bool(forKey: "unxipExperiment") ?? false
|
||||||
createSymLinkOnSelect = Current.defaults.bool(forKey: "createSymLinkOnSelect") ?? false
|
createSymLinkOnSelect = Current.defaults.bool(forKey: "createSymLinkOnSelect") ?? false
|
||||||
|
onSelectActionType = SelectedActionType(rawValue: Current.defaults.string(forKey: "onSelectActionType") ?? "none") ?? .none
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Timer
|
// MARK: Timer
|
||||||
|
|
@ -492,10 +507,15 @@ class AppState: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let installedXcodePath = xcode.installedPath,
|
var installedXcodePath = xcode.installedPath,
|
||||||
selectPublisher == nil
|
selectPublisher == nil
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
|
if onSelectActionType == .rename {
|
||||||
|
guard let newDestinationXcodePath = renameToXcode(xcode: xcode) else { return }
|
||||||
|
installedXcodePath = newDestinationXcodePath
|
||||||
|
}
|
||||||
|
|
||||||
selectPublisher = installHelperIfNecessary()
|
selectPublisher = installHelperIfNecessary()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
Current.helper.switchXcodePath(installedXcodePath.string)
|
Current.helper.switchXcodePath(installedXcodePath.string)
|
||||||
|
|
@ -575,7 +595,39 @@ class AppState: ObservableObject {
|
||||||
self.error = error
|
self.error = error
|
||||||
self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.legibleLocalizedDescription)
|
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?) {
|
func updateAllXcodes(availableXcodes: [AvailableXcode], installedXcodes: [InstalledXcode], selectedXcodePath: String?) {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import Foundation
|
||||||
import Path
|
import Path
|
||||||
|
|
||||||
extension Entry {
|
extension Entry {
|
||||||
var isAppBundle: Bool {
|
static func isAppBundle(kind: Kind, path: Path) -> Bool {
|
||||||
kind == .directory &&
|
kind == .directory &&
|
||||||
path.extension == "app" &&
|
path.extension == "app" &&
|
||||||
!path.isSymlink
|
!path.isSymlink
|
||||||
}
|
}
|
||||||
|
static func infoPlist(kind: Kind, path: Path) -> InfoPlist? {
|
||||||
var infoPlist: InfoPlist? {
|
|
||||||
let infoPlistPath = path.join("Contents").join("Info.plist")
|
let infoPlistPath = path.join("Contents").join("Info.plist")
|
||||||
guard
|
guard
|
||||||
let infoPlistData = try? Data(contentsOf: infoPlistPath.url),
|
let infoPlistData = try? Data(contentsOf: infoPlistPath.url),
|
||||||
|
|
@ -17,4 +16,12 @@ extension Entry {
|
||||||
|
|
||||||
return infoPlist
|
return infoPlist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isAppBundle: Bool {
|
||||||
|
Entry.isAppBundle(kind: kind, path: path)
|
||||||
|
}
|
||||||
|
|
||||||
|
var infoPlist: InfoPlist? {
|
||||||
|
Entry.infoPlist(kind: kind, path: path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,16 @@ public struct Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var installedXcodes = _installedXcodes
|
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] {
|
private func _installedXcodes(destination: Path) -> [InstalledXcode] {
|
||||||
((try? destination.ls()) ?? [])
|
((try? destination.ls()) ?? [])
|
||||||
.filter { $0.isAppBundle && $0.infoPlist?.bundleID == "com.apple.dt.Xcode" }
|
.filter { $0.isAppBundle && $0.infoPlist?.bundleID == "com.apple.dt.Xcode" }
|
||||||
|
|
|
||||||
31
Xcodes/Backend/SelectedActionType.swift
Normal file
31
Xcodes/Backend/SelectedActionType.swift
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,13 +48,27 @@ struct AdvancedPreferencePane: View {
|
||||||
|
|
||||||
GroupBox(label: Text("Active/Select")) {
|
GroupBox(label: Text("Active/Select")) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Toggle(
|
Picker("OnSelect", selection: $appState.onSelectActionType) {
|
||||||
"AutomaticallyCreateSymbolicLink",
|
|
||||||
isOn: $appState.createSymLinkOnSelect
|
Text(SelectedActionType.none.description)
|
||||||
)
|
.tag(SelectedActionType.none)
|
||||||
Text("AutomaticallyCreateSymbolicLinkDescription")
|
Text(SelectedActionType.rename.description)
|
||||||
|
.tag(SelectedActionType.rename)
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
.pickerStyle(.inline)
|
||||||
|
|
||||||
|
Text(appState.onSelectActionType.detailedDescription)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.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)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +168,7 @@ struct AdvancedPreferencePane_Previews: PreviewProvider {
|
||||||
.environmentObject(AppState())
|
.environmentObject(AppState())
|
||||||
.frame(maxWidth: 500)
|
.frame(maxWidth: 500)
|
||||||
}
|
}
|
||||||
|
.frame(width: 500, height: 700, alignment: .center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,14 @@
|
||||||
"LocalCachePathDescription" = "Xcodes caches available Xcode versions and temporary downloads new versions to a directory";
|
"LocalCachePathDescription" = "Xcodes caches available Xcode versions and temporary downloads new versions to a directory";
|
||||||
"Change" = "Change";
|
"Change" = "Change";
|
||||||
"Active/Select" = "Active/Select";
|
"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";
|
"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";
|
"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";
|
"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.";
|
"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";
|
"Downloader" = "Downloader";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue