@@ -82,7 +82,7 @@ public final class XcodeInstaller {
8282 case downloading( version: String , progress: String ? , willInstall: Bool )
8383 case unarchiving( experimentalUnxip: Bool )
8484 case moving( destination: String )
85- case trashingArchive ( archiveName: String )
85+ case cleaningArchive ( archiveName: String , shouldDelete : Bool )
8686 case checkingSecurity
8787 case finishing
8888
@@ -114,7 +114,10 @@ public final class XcodeInstaller {
114114 """
115115 case . moving( let destination) :
116116 return " Moving Xcode to \( destination) "
117- case . trashingArchive( let archiveName) :
117+ case . cleaningArchive( let archiveName, let shouldDelete) :
118+ if shouldDelete {
119+ return " Deleting Xcode archive \( archiveName) "
120+ }
118121 return " Moving Xcode archive \( archiveName) to the Trash "
119122 case . checkingSecurity:
120123 return " Checking security assessment and code signing "
@@ -128,7 +131,7 @@ public final class XcodeInstaller {
128131 case . downloading: return 1
129132 case . unarchiving: return 2
130133 case . moving: return 3
131- case . trashingArchive : return 4
134+ case . cleaningArchive : return 4
132135 case . checkingSecurity: return 5
133136 case . finishing: return 6
134137 }
@@ -163,22 +166,22 @@ public final class XcodeInstaller {
163166 case aria2( Path )
164167 }
165168
166- public func install( _ installationType: InstallationType , dataSource: DataSource , downloader: Downloader , destination: Path , experimentalUnxip: Bool = false , shouldExpandXipInplace: Bool ) -> Promise < Void > {
169+ public func install( _ installationType: InstallationType , dataSource: DataSource , downloader: Downloader , destination: Path , experimentalUnxip: Bool = false , shouldExpandXipInplace: Bool , emptyTrash : Bool , noSuperuser : Bool ) -> Promise < Void > {
167170 return firstly { ( ) -> Promise < InstalledXcode > in
168- return self . install ( installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: 0 , experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace)
171+ return self . install ( installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: 0 , experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace, emptyTrash : emptyTrash , noSuperuser : noSuperuser )
169172 }
170173 . done { xcode in
171174 Current . logging. log ( " \n Xcode \( xcode. version. descriptionWithoutBuildMetadata) has been installed to \( xcode. path. string) " . green)
172175 Current . shell. exit ( 0 )
173176 }
174177 }
175178
176- private func install( _ installationType: InstallationType , dataSource: DataSource , downloader: Downloader , destination: Path , attemptNumber: Int , experimentalUnxip: Bool , shouldExpandXipInplace: Bool ) -> Promise < InstalledXcode > {
179+ private func install( _ installationType: InstallationType , dataSource: DataSource , downloader: Downloader , destination: Path , attemptNumber: Int , experimentalUnxip: Bool , shouldExpandXipInplace: Bool , emptyTrash : Bool , noSuperuser : Bool ) -> Promise < InstalledXcode > {
177180 return firstly { ( ) -> Promise < ( Xcode , URL ) > in
178181 return self . getXcodeArchive ( installationType, dataSource: dataSource, downloader: downloader, destination: destination, willInstall: true )
179182 }
180183 . then { xcode, url -> Promise < InstalledXcode > in
181- return self . installArchivedXcode ( xcode, at: url, to: destination, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace)
184+ return self . installArchivedXcode ( xcode, at: url, to: destination, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace, emptyTrash : emptyTrash , noSuperuser : noSuperuser )
182185 }
183186 . recover { error -> Promise < InstalledXcode > in
184187 switch error {
@@ -195,7 +198,7 @@ public final class XcodeInstaller {
195198 Current . logging. log ( error. legibleLocalizedDescription. red)
196199 Current . logging. log ( " Removing damaged XIP and re-attempting installation. \n " )
197200 try Current . files. removeItem ( at: damagedXIPURL)
198- return self . install ( installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: attemptNumber + 1 , experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace)
201+ return self . install ( installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: attemptNumber + 1 , experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace, emptyTrash : emptyTrash , noSuperuser : noSuperuser )
199202 }
200203 }
201204 default :
@@ -287,7 +290,15 @@ public final class XcodeInstaller {
287290
288291 private func downloadXcode( version: Version , dataSource: DataSource , downloader: Downloader , willInstall: Bool ) -> Promise < ( Xcode , URL ) > {
289292 return firstly { ( ) -> Promise < Version > in
290- loginIfNeeded ( ) . map { version }
293+ if dataSource == . apple {
294+ return loginIfNeeded ( ) . map { version }
295+ } else {
296+ guard let xcode = self . xcodeList. availableXcodes. first ( withVersion: version) else {
297+ throw Error . unavailableVersion ( version)
298+ }
299+
300+ return validateADCSession ( path: xcode. downloadPath) . map { version }
301+ }
291302 }
292303 . then { version -> Promise < Version > in
293304 if self . xcodeList. shouldUpdate {
@@ -297,14 +308,6 @@ public final class XcodeInstaller {
297308 return Promise . value ( version)
298309 }
299310 }
300- . then { version -> Promise < Version > in
301- // This request would've already been made if the Apple data source were being used.
302- // That's not the case for the Xcode Releases data source.
303- // We need the cookies from its response in order to download Xcodes though,
304- // so perform it here first just to be sure.
305- Current . network. dataTask ( with: URLRequest . downloads)
306- . map { _ in version }
307- }
308311 . then { version -> Promise < ( Xcode , URL ) > in
309312 guard let xcode = self . xcodeList. availableXcodes. first ( withVersion: version) else {
310313 throw Error . unavailableVersion ( version)
@@ -334,7 +337,11 @@ public final class XcodeInstaller {
334337 . map { return ( xcode, $0) }
335338 }
336339 }
337-
340+
341+ func validateADCSession( path: String ) -> Promise < Void > {
342+ return Current . network. dataTask ( with: URLRequest . downloadADCAuth ( path: path) ) . asVoid ( )
343+ }
344+
338345 func loginIfNeeded( withUsername providedUsername: String ? = nil , shouldPromptForPassword: Bool = false ) -> Promise < Void > {
339346 return firstly { ( ) -> Promise < Void > in
340347 return Current . network. validateSession ( )
@@ -528,15 +535,7 @@ public final class XcodeInstaller {
528535 }
529536 }
530537
531- public func installArchivedXcode( _ xcode: Xcode , at archiveURL: URL , to destination: Path , experimentalUnxip: Bool = false , shouldExpandXipInplace: Bool ) -> Promise < InstalledXcode > {
532- let passwordInput = {
533- Promise< String> { seal in
534- Current . logging. log ( " xcodes requires superuser privileges in order to finish installation. " )
535- guard let password = Current . shell. readSecureLine ( prompt: " macOS User Password: " ) else { seal. reject ( Error . missingSudoerPassword) ; return }
536- seal. fulfill ( password + " \n " )
537- }
538- }
539-
538+ public func installArchivedXcode( _ xcode: Xcode , at archiveURL: URL , to destination: Path , experimentalUnxip: Bool = false , shouldExpandXipInplace: Bool , emptyTrash: Bool , noSuperuser: Bool ) -> Promise < InstalledXcode > {
540539 return firstly { ( ) -> Promise < InstalledXcode > in
541540 let destinationURL = destination. join ( " Xcode- \( xcode. version. descriptionWithoutBuildMetadata) .app " ) . url
542541 switch archiveURL. pathExtension {
@@ -556,15 +555,38 @@ public final class XcodeInstaller {
556555 }
557556 }
558557 . then { xcode -> Promise < InstalledXcode > in
559- Current . logging. log ( InstallationStep . trashingArchive ( archiveName: archiveURL. lastPathComponent) . description)
560- try Current . files. trashItem ( at: archiveURL)
558+ Current . logging. log ( InstallationStep . cleaningArchive ( archiveName: archiveURL. lastPathComponent, shouldDelete: emptyTrash) . description)
559+ if emptyTrash {
560+ try Current . files. removeItem ( at: archiveURL)
561+ }
562+ else {
563+ try Current . files. trashItem ( at: archiveURL)
564+ }
561565 Current . logging. log ( InstallationStep . checkingSecurity. description)
562566
563567 return when ( fulfilled: self . verifySecurityAssessment ( of: xcode) ,
564568 self . verifySigningCertificate ( of: xcode. path. url) )
565569 . map { xcode }
566570 }
567571 . then { xcode -> Promise < InstalledXcode > in
572+ if noSuperuser {
573+ Current . logging. log ( InstallationStep . finishing. description)
574+ Current . logging. log ( " Skipping asking for superuser privileges. " )
575+ return Promise . value ( xcode)
576+ }
577+ return self . postInstallXcode ( xcode)
578+ }
579+ }
580+
581+ public func postInstallXcode( _ xcode: InstalledXcode ) -> Promise < InstalledXcode > {
582+ let passwordInput = {
583+ Promise< String> { seal in
584+ Current . logging. log ( " xcodes requires superuser privileges in order to finish installation. " )
585+ guard let password = Current . shell. readSecureLine ( prompt: " macOS User Password: " ) else { seal. reject ( Error . missingSudoerPassword) ; return }
586+ seal. fulfill ( password + " \n " )
587+ }
588+ }
589+ return firstly { ( ) -> Promise < InstalledXcode > in
568590 Current . logging. log ( InstallationStep . finishing. description)
569591
570592 return self . enableDeveloperMode ( passwordInput: passwordInput) . map { xcode }
@@ -577,7 +599,7 @@ public final class XcodeInstaller {
577599 }
578600 }
579601
580- public func uninstallXcode( _ versionString: String , directory: Path ) -> Promise < Void > {
602+ public func uninstallXcode( _ versionString: String , directory: Path , emptyTrash : Bool ) -> Promise < Void > {
581603 return firstly { ( ) -> Promise < InstalledXcode > in
582604 guard let version = Version ( xcodeVersion: versionString) else {
583605 Current . logging. log ( Error . invalidVersion ( versionString) . legibleLocalizedDescription)
@@ -591,11 +613,17 @@ public final class XcodeInstaller {
591613
592614 return Promise . value ( installedXcode)
593615 }
594- . map { ( $0, try Current . files. trashItem ( at: $0. path. url) ) }
595- . then { ( installedXcode, trashURL) -> Promise < ( InstalledXcode , URL ) > in
616+ . map { installedXcode -> ( InstalledXcode , URL ? ) in
617+ if emptyTrash {
618+ try Current . files. removeItem ( at: installedXcode. path. url)
619+ return ( installedXcode, nil )
620+ }
621+ return ( installedXcode, try Current . files. trashItem ( at: installedXcode. path. url) )
622+ }
623+ . then { ( installedXcode, trashURL) -> Promise < ( InstalledXcode , URL ? ) > in
596624 // If we just uninstalled the selected Xcode, try to select the latest installed version so things don't accidentally break
597625 Current . shell. xcodeSelectPrintPath ( )
598- . then { output -> Promise < ( InstalledXcode , URL ) > in
626+ . then { output -> Promise < ( InstalledXcode , URL ? ) > in
599627 if output. out. hasPrefix ( installedXcode. path. string) ,
600628 let latestInstalledXcode = Current . files. installedXcodes ( directory) . sorted ( by: { $0. version < $1. version } ) . last {
601629 return selectXcodeAtPath ( latestInstalledXcode. path. string)
@@ -610,17 +638,26 @@ public final class XcodeInstaller {
610638 }
611639 }
612640 . done { ( installedXcode, trashURL) in
613- Current . logging. log ( " Xcode \( installedXcode. version. appleDescription) moved to Trash: \( trashURL. path) " . green)
641+ if let trashURL = trashURL {
642+ Current . logging. log ( " Xcode \( installedXcode. version. appleDescription) moved to Trash: \( trashURL. path) " . green)
643+ }
644+ else {
645+ Current . logging. log ( " Xcode \( installedXcode. version. appleDescription) deleted " . green)
646+ }
614647 Current . shell. exit ( 0 )
615648 }
616649 }
617650
618651 func update( dataSource: DataSource ) -> Promise < [ Xcode ] > {
619- return firstly { ( ) -> Promise < Void > in
620- loginIfNeeded ( )
621- }
622- . then { ( ) -> Promise < [ Xcode ] > in
623- self . xcodeList. update ( dataSource: dataSource)
652+ if dataSource == . apple {
653+ return firstly { ( ) -> Promise < Void > in
654+ loginIfNeeded ( )
655+ }
656+ . then { ( ) -> Promise < [ Xcode ] > in
657+ self . xcodeList. update ( dataSource: dataSource)
658+ }
659+ } else {
660+ return self . xcodeList. update ( dataSource: dataSource)
624661 }
625662 }
626663
0 commit comments