Adds option to create symbolic link at Path Xcode.app

This commit is contained in:
Matt Kiazyk 2022-04-11 19:37:11 -05:00
parent e628d04beb
commit 4cee0f2f15
No known key found for this signature in database
GPG key ID: 850581D2373E4A99
6 changed files with 75 additions and 5 deletions

View file

@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.4
import PackageDescription

View file

@ -88,7 +88,7 @@ extension AppState {
private func getXcodeArchive(_ installationType: InstallationType, downloader: Downloader) -> AnyPublisher<(AvailableXcode, URL), Error> {
switch installationType {
case .version(let availableXcode):
if let installedXcode = Current.files.installedXcodes(Path.root/"Applications").first(where: { $0.version.isEquivalent(to: availableXcode.version) }) {
if let installedXcode = Current.files.installedXcodes(Path.installDirectory).first(where: { $0.version.isEquivalent(to: availableXcode.version) }) {
return Fail(error: InstallationError.versionAlreadyInstalled(installedXcode))
.eraseToAnyPublisher()
}
@ -178,7 +178,7 @@ extension AppState {
public func installArchivedXcode(_ availableXcode: AvailableXcode, at archiveURL: URL) -> AnyPublisher<InstalledXcode, Error> {
do {
let destinationURL = Path.root.join("Applications").join("Xcode-\(availableXcode.version.descriptionWithoutBuildMetadata).app").url
let destinationURL = Path.installDirectory.join("Xcode-\(availableXcode.version.descriptionWithoutBuildMetadata).app").url
switch archiveURL.pathExtension {
case "xip":
return unarchiveAndMoveXIP(availableXcode: availableXcode, at: archiveURL, to: destinationURL)

View file

@ -21,7 +21,7 @@ class AppState: ObservableObject {
}
updateAllXcodes(
availableXcodes: newValue,
installedXcodes: Current.files.installedXcodes(Path.root/"Applications"),
installedXcodes: Current.files.installedXcodes(Path.installDirectory),
selectedXcodePath: selectedXcodePath
)
}
@ -34,7 +34,7 @@ class AppState: ObservableObject {
willSet {
updateAllXcodes(
availableXcodes: availableXcodes,
installedXcodes: Current.files.installedXcodes(Path.root/"Applications"),
installedXcodes: Current.files.installedXcodes(Path.installDirectory),
selectedXcodePath: newValue
)
}
@ -495,6 +495,41 @@ class AppState: ObservableObject {
NSPasteboard.general.writeObjects([installedXcodePath.url as NSURL])
NSPasteboard.general.setString(installedXcodePath.string, forType: .string)
}
func createSymbolicLink(xcode: Xcode) {
guard let installedXcodePath = xcode.installedPath else { return }
let destinationPath: Path = Path.installDirectory/"Xcode.app"
// does an Xcode.app file exist?
if FileManager.default.fileExists(atPath: destinationPath.string) {
do {
// if it's not a symlink, error because we don't want to delete an actual xcode.app file
let attributes: [FileAttributeKey : Any]? = try? FileManager.default.attributesOfItem(atPath: destinationPath.string)
if attributes?[.type] as? FileAttributeType == FileAttributeType.typeSymbolicLink {
try FileManager.default.removeItem(atPath: destinationPath.string)
Logger.appState.info("Successfully deleted old symlink")
} else {
let message = "Xcode.app exists and is not a symbolic link"
self.presentedAlert = .generic(title: "Unable to create symbolic Link", message: message)
return
}
} catch {
self.presentedAlert = .generic(title: "Unable to create symbolic Link", message: error.localizedDescription)
}
}
do {
try FileManager.default.createSymbolicLink(atPath: destinationPath.string, withDestinationPath: installedXcodePath.string)
Logger.appState.info("Successfully created symbolic link with Xcode.app")
} catch {
Logger.appState.error("Unable to create symbolic Link")
self.error = error
self.presentedAlert = .generic(title: "Unable to create symbolic Link", message: error.legibleLocalizedDescription)
}
}
func updateAllXcodes(availableXcodes: [AvailableXcode], installedXcodes: [InstalledXcode], selectedXcodePath: String?) {
var adjustedAvailableXcodes = availableXcodes

View file

@ -16,4 +16,8 @@ extension Path {
static var cacheFile: Path {
return xcodesApplicationSupport/"available-xcodes.json"
}
static var installDirectory: Path {
return Path.root/"Applications"
}
}

View file

@ -18,6 +18,7 @@ struct XcodeCommands: Commands {
OpenCommand()
RevealCommand()
CopyPathCommand()
CreateSymbolicLinkCommand()
Divider()
@ -153,6 +154,23 @@ struct CopyPathButton: View {
}
}
struct CreateSymbolicLinkButton: View {
@EnvironmentObject var appState: AppState
let xcode: Xcode?
var body: some View {
Button(action: createSymbolicLink) {
Text("Create SymLink as Xcode.app")
}
.help("Create SymLink as Xcode.app")
}
private func createSymbolicLink() {
guard let xcode = xcode else { return }
appState.createSymbolicLink(xcode: xcode)
}
}
// MARK: - Commands
struct InstallCommand: View {
@ -225,3 +243,15 @@ struct UninstallCommand: View {
.disabled(selectedXcode.unwrapped?.installState.installed != true)
}
}
struct CreateSymbolicLinkCommand: View {
@EnvironmentObject var appState: AppState
@FocusedValue(\.selectedXcode) private var selectedXcode: SelectedXcode?
var body: some View {
CreateSymbolicLinkButton(xcode: selectedXcode.unwrapped)
.keyboardShortcut("s", modifiers: [.command, .option])
.disabled(selectedXcode.unwrapped?.installState.installed != true)
}
}

View file

@ -53,6 +53,7 @@ struct XcodeListViewRow: View {
OpenButton(xcode: xcode)
RevealButton(xcode: xcode)
CopyPathButton(xcode: xcode)
CreateSymbolicLinkButton(xcode: xcode)
Divider()
UninstallButton(xcode: xcode)