@@ -82,7 +82,7 @@ public final class XcodeInstaller {
82
82
case downloading( version: String , progress: String ? , willInstall: Bool )
83
83
case unarchiving( experimentalUnxip: Bool )
84
84
case moving( destination: String )
85
- case trashingArchive ( archiveName: String )
85
+ case cleaningArchive ( archiveName: String , shouldDelete : Bool )
86
86
case checkingSecurity
87
87
case finishing
88
88
@@ -114,7 +114,10 @@ public final class XcodeInstaller {
114
114
"""
115
115
case . moving( let destination) :
116
116
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
+ }
118
121
return " Moving Xcode archive \( archiveName) to the Trash "
119
122
case . checkingSecurity:
120
123
return " Checking security assessment and code signing "
@@ -128,7 +131,7 @@ public final class XcodeInstaller {
128
131
case . downloading: return 1
129
132
case . unarchiving: return 2
130
133
case . moving: return 3
131
- case . trashingArchive : return 4
134
+ case . cleaningArchive : return 4
132
135
case . checkingSecurity: return 5
133
136
case . finishing: return 6
134
137
}
@@ -163,22 +166,22 @@ public final class XcodeInstaller {
163
166
case aria2( Path )
164
167
}
165
168
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 > {
167
170
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 )
169
172
}
170
173
. done { xcode in
171
174
Current . logging. log ( " \n Xcode \( xcode. version. descriptionWithoutBuildMetadata) has been installed to \( xcode. path. string) " . green)
172
175
Current . shell. exit ( 0 )
173
176
}
174
177
}
175
178
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 > {
177
180
return firstly { ( ) -> Promise < ( Xcode , URL ) > in
178
181
return self . getXcodeArchive ( installationType, dataSource: dataSource, downloader: downloader, destination: destination, willInstall: true )
179
182
}
180
183
. 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 )
182
185
}
183
186
. recover { error -> Promise < InstalledXcode > in
184
187
switch error {
@@ -195,7 +198,7 @@ public final class XcodeInstaller {
195
198
Current . logging. log ( error. legibleLocalizedDescription. red)
196
199
Current . logging. log ( " Removing damaged XIP and re-attempting installation. \n " )
197
200
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 )
199
202
}
200
203
}
201
204
default :
@@ -287,7 +290,15 @@ public final class XcodeInstaller {
287
290
288
291
private func downloadXcode( version: Version , dataSource: DataSource , downloader: Downloader , willInstall: Bool ) -> Promise < ( Xcode , URL ) > {
289
292
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
+ }
291
302
}
292
303
. then { version -> Promise < Version > in
293
304
if self . xcodeList. shouldUpdate {
@@ -297,14 +308,6 @@ public final class XcodeInstaller {
297
308
return Promise . value ( version)
298
309
}
299
310
}
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
- }
308
311
. then { version -> Promise < ( Xcode , URL ) > in
309
312
guard let xcode = self . xcodeList. availableXcodes. first ( withVersion: version) else {
310
313
throw Error . unavailableVersion ( version)
@@ -334,7 +337,11 @@ public final class XcodeInstaller {
334
337
. map { return ( xcode, $0) }
335
338
}
336
339
}
337
-
340
+
341
+ func validateADCSession( path: String ) -> Promise < Void > {
342
+ return Current . network. dataTask ( with: URLRequest . downloadADCAuth ( path: path) ) . asVoid ( )
343
+ }
344
+
338
345
func loginIfNeeded( withUsername providedUsername: String ? = nil , shouldPromptForPassword: Bool = false ) -> Promise < Void > {
339
346
return firstly { ( ) -> Promise < Void > in
340
347
return Current . network. validateSession ( )
@@ -528,15 +535,7 @@ public final class XcodeInstaller {
528
535
}
529
536
}
530
537
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 > {
540
539
return firstly { ( ) -> Promise < InstalledXcode > in
541
540
let destinationURL = destination. join ( " Xcode- \( xcode. version. descriptionWithoutBuildMetadata) .app " ) . url
542
541
switch archiveURL. pathExtension {
@@ -556,15 +555,38 @@ public final class XcodeInstaller {
556
555
}
557
556
}
558
557
. 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
+ }
561
565
Current . logging. log ( InstallationStep . checkingSecurity. description)
562
566
563
567
return when ( fulfilled: self . verifySecurityAssessment ( of: xcode) ,
564
568
self . verifySigningCertificate ( of: xcode. path. url) )
565
569
. map { xcode }
566
570
}
567
571
. 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
568
590
Current . logging. log ( InstallationStep . finishing. description)
569
591
570
592
return self . enableDeveloperMode ( passwordInput: passwordInput) . map { xcode }
@@ -577,7 +599,7 @@ public final class XcodeInstaller {
577
599
}
578
600
}
579
601
580
- public func uninstallXcode( _ versionString: String , directory: Path ) -> Promise < Void > {
602
+ public func uninstallXcode( _ versionString: String , directory: Path , emptyTrash : Bool ) -> Promise < Void > {
581
603
return firstly { ( ) -> Promise < InstalledXcode > in
582
604
guard let version = Version ( xcodeVersion: versionString) else {
583
605
Current . logging. log ( Error . invalidVersion ( versionString) . legibleLocalizedDescription)
@@ -591,11 +613,17 @@ public final class XcodeInstaller {
591
613
592
614
return Promise . value ( installedXcode)
593
615
}
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
596
624
// If we just uninstalled the selected Xcode, try to select the latest installed version so things don't accidentally break
597
625
Current . shell. xcodeSelectPrintPath ( )
598
- . then { output -> Promise < ( InstalledXcode , URL ) > in
626
+ . then { output -> Promise < ( InstalledXcode , URL ? ) > in
599
627
if output. out. hasPrefix ( installedXcode. path. string) ,
600
628
let latestInstalledXcode = Current . files. installedXcodes ( directory) . sorted ( by: { $0. version < $1. version } ) . last {
601
629
return selectXcodeAtPath ( latestInstalledXcode. path. string)
@@ -610,17 +638,26 @@ public final class XcodeInstaller {
610
638
}
611
639
}
612
640
. 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
+ }
614
647
Current . shell. exit ( 0 )
615
648
}
616
649
}
617
650
618
651
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)
624
661
}
625
662
}
626
663
0 commit comments