mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
Merge pull request #448 from XcodesOrg/matt/runtimeDownload
Support Runtime/Platforms Downloading and Install 🚀
This commit is contained in:
commit
c5ada02a31
40 changed files with 1187 additions and 85 deletions
|
|
@ -9,7 +9,7 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */; };
|
||||
36741BFF291E50F500A85AAE /* FileError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFE291E50F500A85AAE /* FileError.swift */; };
|
||||
536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD1263C94DE00026CE0 /* SignedInView.swift */; };
|
||||
536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD1263C94DE00026CE0 /* SignedInView.swift */; };
|
||||
536CFDD4263C9A8000026CE0 /* XcodesSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */; };
|
||||
53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */; };
|
||||
63EAA4EB259944450046AB8F /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63EAA4EA259944450046AB8F /* ProgressButton.swift */; };
|
||||
|
|
@ -29,7 +29,6 @@
|
|||
CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA25192925A9644800F08414 /* XcodeInstallState.swift */; };
|
||||
CA378F992466567600A58CE0 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA378F982466567600A58CE0 /* AppState.swift */; };
|
||||
CA39711924495F0E00AFFB77 /* AppStoreButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */; };
|
||||
CA42DD6E25AEA8B200BC0B0C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA42DD6D25AEA8B200BC0B0C /* Logger.swift */; };
|
||||
CA42DD7325AEB04300BC0B0C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA42DD7225AEB04300BC0B0C /* Logger.swift */; };
|
||||
CA44901F2463AD34003D8213 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA44901E2463AD34003D8213 /* Tag.swift */; };
|
||||
CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */; };
|
||||
|
|
@ -82,7 +81,6 @@
|
|||
CABFA9CD2592EEEA00380FEE /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9AC2592EEE900380FEE /* Foundation.swift */; };
|
||||
CABFA9CE2592EEEA00380FEE /* Version+Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9A62592EEE900380FEE /* Version+Xcode.swift */; };
|
||||
CABFA9CF2592EEEA00380FEE /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9B42592EEEA00380FEE /* Process.swift */; };
|
||||
CABFA9DF2592F07A00380FEE /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = CABFA9DE2592F07A00380FEE /* Path */; };
|
||||
CABFA9E42592F08E00380FEE /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = CABFA9E32592F08E00380FEE /* Version */; };
|
||||
CABFA9EE2592F0CC00380FEE /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = CABFA9ED2592F0CC00380FEE /* SwiftSoup */; };
|
||||
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = CABFA9F72592F0F900380FEE /* KeychainAccess */; };
|
||||
|
|
@ -92,7 +90,6 @@
|
|||
CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */; };
|
||||
CAC28188259EE27200B8AB0B /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = CAC28187259EE27200B8AB0B /* CombineExpectations */; };
|
||||
CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */; };
|
||||
CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281D9259F985100B8AB0B /* InstallationStep.swift */; };
|
||||
CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */; };
|
||||
CAC281E7259FA45A00B8AB0B /* Environment+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E6259FA45A00B8AB0B /* Environment+Mock.swift */; };
|
||||
CAC9F92D25BCDA4400B4965F /* HelperInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC9F92C25BCDA4400B4965F /* HelperInstallState.swift */; };
|
||||
|
|
@ -115,11 +112,16 @@
|
|||
CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; };
|
||||
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */ = {isa = PBXBuildFile; productRef = E689540225BE8C64000EBCEA /* DockProgress */; };
|
||||
E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; };
|
||||
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
|
||||
E84CF8C12B0FEB8300ECA259 /* RuntimesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */; };
|
||||
E872EE4E2808D4F100D3DD8B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E872EE502808D4F100D3DD8B /* Localizable.strings */; };
|
||||
E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; };
|
||||
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
|
||||
E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; };
|
||||
E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; };
|
||||
E8B20CBF2A2EDEC20057D816 /* SDKs+Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B20CBE2A2EDEC20057D816 /* SDKs+Xcode.swift */; };
|
||||
E8C0EB1A291EF43E0081528A /* XcodesKit in Frameworks */ = {isa = PBXBuildFile; productRef = E8C0EB19291EF43E0081528A /* XcodesKit */; };
|
||||
E8C0EB1C291EF9A10081528A /* AppState+Runtimes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C0EB1B291EF9A10081528A /* AppState+Runtimes.swift */; };
|
||||
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 */; };
|
||||
|
|
@ -127,7 +129,9 @@
|
|||
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 */; };
|
||||
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = E8F44A1D296B4CD7002D6592 /* Path */; };
|
||||
E8F81FC4282D8A17006CBD0F /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E8F81FC3282D8A17006CBD0F /* Sparkle */; };
|
||||
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */ = {isa = PBXBuildFile; productRef = E8FD5726291EE4AC001E004C /* AsyncNetworkService */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -221,7 +225,6 @@
|
|||
CA25192925A9644800F08414 /* XcodeInstallState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeInstallState.swift; sourceTree = "<group>"; };
|
||||
CA378F982466567600A58CE0 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||
CA39711824495F0E00AFFB77 /* AppStoreButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreButtonStyle.swift; sourceTree = "<group>"; };
|
||||
CA42DD6D25AEA8B200BC0B0C /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
CA42DD7225AEB04300BC0B0C /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
CA44901E2463AD34003D8213 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||
CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -289,7 +292,6 @@
|
|||
CABFAA422593104F00380FEE /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+InfoPlistValues.swift"; sourceTree = "<group>"; };
|
||||
CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingProgressIndicator.swift; sourceTree = "<group>"; };
|
||||
CAC281D9259F985100B8AB0B /* InstallationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStep.swift; sourceTree = "<group>"; };
|
||||
CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+XcodesTests.swift"; sourceTree = "<group>"; };
|
||||
CAC281E6259FA45A00B8AB0B /* Environment+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+Mock.swift"; sourceTree = "<group>"; };
|
||||
CAC9F92C25BCDA4400B4965F /* HelperInstallState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperInstallState.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -322,11 +324,16 @@
|
|||
CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = "<group>"; };
|
||||
E2AFDCCA28F024D000864ADD /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = "<group>"; };
|
||||
E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = "<group>"; };
|
||||
E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimesView.swift; sourceTree = "<group>"; };
|
||||
E856BB73291EDD3D00DC438B /* XcodesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = XcodesKit; path = Xcodes/XcodesKit; sourceTree = "<group>"; };
|
||||
E872EE4F2808D4F100D3DD8B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
E87AB3C42939B65E00D72F43 /* Hardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hardware.swift; sourceTree = "<group>"; };
|
||||
E87DD6EA25D053FA00D86808 /* Progress+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+.swift"; sourceTree = "<group>"; };
|
||||
E89342F925EDCC17007CF557 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||
E8977EA225C11E1500835F80 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||
E8B20CBE2A2EDEC20057D816 /* SDKs+Xcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SDKs+Xcode.swift"; sourceTree = "<group>"; };
|
||||
E8C0EB1B291EF9A10081528A /* AppState+Runtimes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppState+Runtimes.swift"; 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>"; };
|
||||
E8D0296E284B029800647641 /* BottomStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomStatusBar.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -354,9 +361,11 @@
|
|||
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
|
||||
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
|
||||
CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */,
|
||||
E8C0EB1A291EF43E0081528A /* XcodesKit in Frameworks */,
|
||||
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */,
|
||||
CAA1CB2D255A5262003FD669 /* AppleAPI in Frameworks */,
|
||||
CABFA9DF2592F07A00380FEE /* Path in Frameworks */,
|
||||
CABFA9EE2592F0CC00380FEE /* SwiftSoup in Frameworks */,
|
||||
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -478,6 +487,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
CA378F982466567600A58CE0 /* AppState.swift */,
|
||||
E8C0EB1B291EF9A10081528A /* AppState+Runtimes.swift */,
|
||||
CAE424B3259A764700B8B246 /* AppState+Install.swift */,
|
||||
CABFA9A72592EEE900380FEE /* AppState+Update.swift */,
|
||||
CAA8589A25A2B83000ACF8C0 /* Aria2CError.swift */,
|
||||
|
|
@ -496,10 +506,8 @@
|
|||
CABFA9AC2592EEE900380FEE /* Foundation.swift */,
|
||||
CA9FF9352595B44700E47BAF /* HelperClient.swift */,
|
||||
CAC9F92C25BCDA4400B4965F /* HelperInstallState.swift */,
|
||||
CAC281D9259F985100B8AB0B /* InstallationStep.swift */,
|
||||
CA9FF8862595607900E47BAF /* InstalledXcode.swift */,
|
||||
CAA8587B25A2B37900ACF8C0 /* IsTesting.swift */,
|
||||
CA42DD6D25AEA8B200BC0B0C /* Logger.swift */,
|
||||
E89342F925EDCC17007CF557 /* NotificationManager.swift */,
|
||||
CAE4248B259A68B800B8B246 /* Optional+IsNotNil.swift */,
|
||||
CABFA9AE2592EEE900380FEE /* Path+.swift */,
|
||||
|
|
@ -518,6 +526,7 @@
|
|||
E81D7E9F2805250100A205FC /* Collection+.swift */,
|
||||
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */,
|
||||
E87AB3C42939B65E00D72F43 /* Hardware.swift */,
|
||||
E8B20CBE2A2EDEC20057D816 /* SDKs+Xcode.swift */,
|
||||
);
|
||||
path = Backend;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -558,6 +567,7 @@
|
|||
CAD2E7952449574E00113D76 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E856BB73291EDD3D00DC438B /* XcodesKit */,
|
||||
CA8FB5F8256E0F9400469DA5 /* README.md */,
|
||||
CABFA9D42592EF6300380FEE /* DECISIONS.md */,
|
||||
CABFA9A02592EAF500380FEE /* R&PLogo.png */,
|
||||
|
|
@ -650,6 +660,8 @@
|
|||
B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */,
|
||||
B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */,
|
||||
B0C6AD0C2AD91D7900E64698 /* IconView.swift */,
|
||||
E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */,
|
||||
E84CF8C02B0FEB8300ECA259 /* RuntimesView.swift */,
|
||||
);
|
||||
path = InfoPane;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -693,7 +705,6 @@
|
|||
name = Xcodes;
|
||||
packageProductDependencies = (
|
||||
CAA1CB2C255A5262003FD669 /* AppleAPI */,
|
||||
CABFA9DE2592F07A00380FEE /* Path */,
|
||||
CABFA9E32592F08E00380FEE /* Version */,
|
||||
CABFA9ED2592F0CC00380FEE /* SwiftSoup */,
|
||||
CABFA9F72592F0F900380FEE /* KeychainAccess */,
|
||||
|
|
@ -702,6 +713,9 @@
|
|||
CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */,
|
||||
E8F81FC3282D8A17006CBD0F /* Sparkle */,
|
||||
E689540225BE8C64000EBCEA /* DockProgress */,
|
||||
E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
|
||||
E8C0EB19291EF43E0081528A /* XcodesKit */,
|
||||
E8F44A1D296B4CD7002D6592 /* Path */,
|
||||
);
|
||||
productName = XcodesMac;
|
||||
productReference = CAD2E79E2449574E00113D76 /* Xcodes.app */;
|
||||
|
|
@ -777,7 +791,6 @@
|
|||
);
|
||||
mainGroup = CAD2E7952449574E00113D76;
|
||||
packageReferences = (
|
||||
CABFA9DD2592F07A00380FEE /* XCRemoteSwiftPackageReference "Path" */,
|
||||
CABFA9E22592F08E00380FEE /* XCRemoteSwiftPackageReference "Version" */,
|
||||
CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||
|
|
@ -787,6 +800,8 @@
|
|||
CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */,
|
||||
E8F81FC2282D8A17006CBD0F /* XCRemoteSwiftPackageReference "Sparkle" */,
|
||||
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
|
||||
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */,
|
||||
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */,
|
||||
);
|
||||
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -868,6 +883,7 @@
|
|||
CA9FF8CF25959A9700E47BAF /* HelperXPCShared.swift in Sources */,
|
||||
CA735109257BF96D00EA9CF8 /* AttributedText.swift in Sources */,
|
||||
CAFBDC4E2599B33D003DCC5A /* MainToolbar.swift in Sources */,
|
||||
E84CF8C12B0FEB8300ECA259 /* RuntimesView.swift in Sources */,
|
||||
CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */,
|
||||
CAA8589B25A2B83000ACF8C0 /* Aria2CError.swift in Sources */,
|
||||
536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */,
|
||||
|
|
@ -886,7 +902,6 @@
|
|||
CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */,
|
||||
CABFA9BD2592EEEA00380FEE /* Environment.swift in Sources */,
|
||||
CABFA9C32592EEEA00380FEE /* Downloads.swift in Sources */,
|
||||
CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */,
|
||||
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */,
|
||||
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */,
|
||||
CA378F992466567600A58CE0 /* AppState.swift in Sources */,
|
||||
|
|
@ -900,13 +915,13 @@
|
|||
B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */,
|
||||
B0403CFE2ADA712C00137C09 /* InfoPaneControls.swift in Sources */,
|
||||
53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */,
|
||||
CA42DD6E25AEA8B200BC0B0C /* Logger.swift in Sources */,
|
||||
CA61A6E0259835580008926E /* Xcode.swift in Sources */,
|
||||
CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */,
|
||||
CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */,
|
||||
B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */,
|
||||
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */,
|
||||
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */,
|
||||
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */,
|
||||
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
|
||||
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
|
||||
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */,
|
||||
|
|
@ -918,6 +933,7 @@
|
|||
E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */,
|
||||
CAA1CB35255A5AD5003FD669 /* SignInCredentialsView.swift in Sources */,
|
||||
E81D7EA02805250100A205FC /* Collection+.swift in Sources */,
|
||||
E8B20CBF2A2EDEC20057D816 /* SDKs+Xcode.swift in Sources */,
|
||||
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */,
|
||||
CABFAA2D2592FBFC00380FEE /* Configure.swift in Sources */,
|
||||
CA73510D257BFCEF00EA9CF8 /* NSAttributedString+.swift in Sources */,
|
||||
|
|
@ -946,6 +962,7 @@
|
|||
CABFA9C92592EEEA00380FEE /* URLRequest+Apple.swift in Sources */,
|
||||
CABFAA432593104F00380FEE /* AboutView.swift in Sources */,
|
||||
E8D0296F284B029800647641 /* BottomStatusBar.swift in Sources */,
|
||||
E8C0EB1C291EF9A10081528A /* AppState+Runtimes.swift in Sources */,
|
||||
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */,
|
||||
CABFA9CC2592EEEA00380FEE /* Path+.swift in Sources */,
|
||||
CAD2E7A22449574E00113D76 /* XcodesApp.swift in Sources */,
|
||||
|
|
@ -1095,7 +1112,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -1336,7 +1353,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
|
@ -1360,7 +1377,7 @@
|
|||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.robotsandpencils.XcodesApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
|
||||
PRODUCT_NAME = Xcodes;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
|
@ -1458,8 +1475,8 @@
|
|||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/xcodereleases/data";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = a43ad89e536d7a3da525fcc23fb182c37b756ecc;
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */ = {
|
||||
|
|
@ -1470,14 +1487,6 @@
|
|||
minimumVersion = 0.1.0;
|
||||
};
|
||||
};
|
||||
CABFA9DD2592F07A00380FEE /* XCRemoteSwiftPackageReference "Path" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mxcl/Path.swift";
|
||||
requirement = {
|
||||
kind = upToNextMinorVersion;
|
||||
minimumVersion = 0.16.0;
|
||||
};
|
||||
};
|
||||
CABFA9E22592F08E00380FEE /* XCRemoteSwiftPackageReference "Version" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mxcl/Version";
|
||||
|
|
@ -1526,6 +1535,14 @@
|
|||
minimumVersion = 3.2.0;
|
||||
};
|
||||
};
|
||||
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mxcl/Path.swift";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.0;
|
||||
};
|
||||
};
|
||||
E8F81FC2282D8A17006CBD0F /* XCRemoteSwiftPackageReference "Sparkle" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/sparkle-project/Sparkle";
|
||||
|
|
@ -1534,6 +1551,14 @@
|
|||
minimumVersion = 2.0.0;
|
||||
};
|
||||
};
|
||||
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/RobotsAndPencils/AsyncHTTPNetworkService";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
|
@ -1551,11 +1576,6 @@
|
|||
package = CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */;
|
||||
productName = ErrorHandling;
|
||||
};
|
||||
CABFA9DE2592F07A00380FEE /* Path */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CABFA9DD2592F07A00380FEE /* XCRemoteSwiftPackageReference "Path" */;
|
||||
productName = Path;
|
||||
};
|
||||
CABFA9E32592F08E00380FEE /* Version */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CABFA9E22592F08E00380FEE /* XCRemoteSwiftPackageReference "Version" */;
|
||||
|
|
@ -1586,11 +1606,25 @@
|
|||
package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */;
|
||||
productName = DockProgress;
|
||||
};
|
||||
E8C0EB19291EF43E0081528A /* XcodesKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = XcodesKit;
|
||||
};
|
||||
E8F44A1D296B4CD7002D6592 /* Path */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */;
|
||||
productName = Path;
|
||||
};
|
||||
E8F81FC3282D8A17006CBD0F /* Sparkle */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E8F81FC2282D8A17006CBD0F /* XCRemoteSwiftPackageReference "Sparkle" */;
|
||||
productName = Sparkle;
|
||||
};
|
||||
E8FD5726291EE4AC001E004C /* AsyncNetworkService */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */;
|
||||
productName = AsyncNetworkService;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = CAD2E7962449574E00113D76 /* Project object */;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "AsyncNetworkService",
|
||||
"repositoryURL": "https://github.com/RobotsAndPencils/AsyncHTTPNetworkService",
|
||||
"state": {
|
||||
"branch": "main",
|
||||
"revision": "97770856c4e429f880d4b4dd68cfaf286dc00c30",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "CombineExpectations",
|
||||
"repositoryURL": "https://github.com/groue/CombineExpectations",
|
||||
|
|
@ -14,7 +23,7 @@
|
|||
"package": "XcodeReleases",
|
||||
"repositoryURL": "https://github.com/xcodereleases/data",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"branch": "main",
|
||||
"revision": "a43ad89e536d7a3da525fcc23fb182c37b756ecc",
|
||||
"version": null
|
||||
}
|
||||
|
|
@ -60,8 +69,8 @@
|
|||
"repositoryURL": "https://github.com/mxcl/Path.swift",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "dac007e907a4f4c565cfdc55a9ce148a761a11d5",
|
||||
"version": "0.16.3"
|
||||
"revision": "8e355c28e9393c42e58b18c54cace2c42c98a616",
|
||||
"version": "1.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Version
|
|||
import LegibleError
|
||||
import os.log
|
||||
import DockProgress
|
||||
import XcodesKit
|
||||
|
||||
/// Downloads and installs Xcodes
|
||||
extension AppState {
|
||||
|
|
@ -489,7 +490,7 @@ extension AppState {
|
|||
|
||||
// MARK: -
|
||||
|
||||
func setInstallationStep(of version: Version, to step: InstallationStep) {
|
||||
func setInstallationStep(of version: Version, to step: XcodeInstallationStep) {
|
||||
DispatchQueue.main.async {
|
||||
guard let index = self.allXcodes.firstIndex(where: { $0.version.isEquivalent(to: version) }) else { return }
|
||||
self.allXcodes[index].installState = .installing(step)
|
||||
|
|
@ -498,6 +499,15 @@ extension AppState {
|
|||
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep) {
|
||||
DispatchQueue.main.async {
|
||||
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
|
||||
self.downloadableRuntimes[index].installState = .installing(step)
|
||||
|
||||
Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppState {
|
||||
|
|
|
|||
183
Xcodes/Backend/AppState+Runtimes.swift
Normal file
183
Xcodes/Backend/AppState+Runtimes.swift
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import Foundation
|
||||
import XcodesKit
|
||||
import OSLog
|
||||
import Combine
|
||||
import Path
|
||||
import AppleAPI
|
||||
|
||||
extension AppState {
|
||||
func updateDownloadableRuntimes() {
|
||||
Task {
|
||||
do {
|
||||
|
||||
let downloadableRuntimes = try await self.runtimeService.downloadableRuntimes()
|
||||
let runtimes = downloadableRuntimes.downloadables.map { runtime in
|
||||
var updatedRuntime = runtime
|
||||
|
||||
// This loops through and matches up the simulatorVersion to the mappings
|
||||
let simulatorBuildUpdate = downloadableRuntimes.sdkToSimulatorMappings.first { SDKToSimulatorMapping in
|
||||
SDKToSimulatorMapping.simulatorBuildUpdate == runtime.simulatorVersion.buildUpdate
|
||||
}
|
||||
updatedRuntime.sdkBuildUpdate = simulatorBuildUpdate?.sdkBuildUpdate
|
||||
return updatedRuntime
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.downloadableRuntimes = runtimes
|
||||
}
|
||||
try? cacheDownloadableRuntimes(runtimes)
|
||||
} catch {
|
||||
Logger.appState.error("Error downloading runtimes: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateInstalledRuntimes() {
|
||||
Task {
|
||||
do {
|
||||
let runtimes = try await self.runtimeService.localInstalledRuntimes()
|
||||
DispatchQueue.main.async {
|
||||
self.installedRuntimes = runtimes
|
||||
}
|
||||
} catch {
|
||||
Logger.appState.error("Error loading installed runtimes: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRuntime(runtime: DownloadableRuntime) {
|
||||
Task {
|
||||
do {
|
||||
try await downloadRunTimeFull(runtime: runtime)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
|
||||
self.downloadableRuntimes[index].installState = .installed
|
||||
}
|
||||
|
||||
updateInstalledRuntimes()
|
||||
}
|
||||
catch {
|
||||
Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
|
||||
DispatchQueue.main.async {
|
||||
self.error = error
|
||||
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRunTimeFull(runtime: DownloadableRuntime) async throws {
|
||||
// sets a proper cookie for runtimes
|
||||
try await validateADCSession(path: runtime.downloadPath)
|
||||
|
||||
let downloader = Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2
|
||||
Logger.appState.info("Downloading \(runtime.visibleIdentifier) with \(downloader)")
|
||||
|
||||
|
||||
let url = try await self.downloadRuntime(for: runtime, downloader: downloader, progressChanged: { [unowned self] progress in
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
|
||||
}
|
||||
}).async()
|
||||
|
||||
Logger.appState.debug("Done downloading: \(url)")
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .installing)
|
||||
}
|
||||
switch runtime.contentType {
|
||||
case .package:
|
||||
// not supported yet (do we need to for old packages?)
|
||||
throw "Installing via package not support - please install manually from \(url.description)"
|
||||
case .diskImage:
|
||||
try await self.installFromImage(dmgURL: url)
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .trashingArchive)
|
||||
}
|
||||
try Current.files.removeItem(at: url)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func downloadRuntime(for runtime: DownloadableRuntime, downloader: Downloader, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
|
||||
// Check to see if the dmg is in the expected path in case it was downloaded but failed to install
|
||||
|
||||
// call https://developerservices2.apple.com/services/download?path=/Developer_Tools/watchOS_10_beta/watchOS_10_beta_Simulator_Runtime.dmg 1st to get cookie
|
||||
// use runtime.url for final with cookies
|
||||
|
||||
// Check to see if the archive is in the expected path in case it was downloaded but failed to install
|
||||
let url = URL(string: runtime.source)!
|
||||
let expectedRuntimePath = Path.xcodesApplicationSupport/"\(url.lastPathComponent)"
|
||||
// aria2 downloads directly to the destination (instead of into /tmp first) so we need to make sure that the download isn't incomplete
|
||||
let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2")
|
||||
var aria2DownloadIsIncomplete = false
|
||||
if case .aria2 = downloader, aria2DownloadMetadataPath.exists {
|
||||
aria2DownloadIsIncomplete = true
|
||||
}
|
||||
if Current.files.fileExistsAtPath(expectedRuntimePath.string), aria2DownloadIsIncomplete == false {
|
||||
Logger.appState.info("Found existing runtime that will be used for installation at \(expectedRuntimePath).")
|
||||
return Just(expectedRuntimePath.url)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
else {
|
||||
|
||||
Logger.appState.info("Downloading runtime: \(url.lastPathComponent)")
|
||||
switch downloader {
|
||||
case .aria2:
|
||||
let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
|
||||
return downloadRuntimeWithAria2(
|
||||
runtime,
|
||||
to: expectedRuntimePath,
|
||||
aria2Path: aria2Path,
|
||||
progressChanged: progressChanged)
|
||||
|
||||
case .urlSession:
|
||||
// TODO: Support runtime download via URL Session
|
||||
return Just(runtime.url)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func downloadRuntimeWithAria2(_ runtime: DownloadableRuntime, to destination: Path, aria2Path: Path, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
|
||||
let cookies = AppleAPI.Current.network.session.configuration.httpCookieStorage?.cookies(for: runtime.url) ?? []
|
||||
|
||||
let (progress, publisher) = Current.shell.downloadWithAria2(
|
||||
aria2Path,
|
||||
runtime.url,
|
||||
destination,
|
||||
cookies
|
||||
)
|
||||
progressChanged(progress)
|
||||
return publisher
|
||||
.map { _ in destination.url }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func installFromImage(dmgURL: URL) async throws {
|
||||
try await self.runtimeService.installRuntimeImage(dmgURL: dmgURL)
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyPublisher {
|
||||
func async() async throws -> Output {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
var cancellable: AnyCancellable?
|
||||
|
||||
cancellable = first()
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
break
|
||||
case let .failure(error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
cancellable?.cancel()
|
||||
} receiveValue: { value in
|
||||
continuation.resume(with: .success(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import Version
|
|||
import SwiftSoup
|
||||
import struct XCModel.Xcode
|
||||
import AppleAPI
|
||||
import XcodesKit
|
||||
|
||||
extension AppState {
|
||||
|
||||
|
|
@ -36,6 +37,8 @@ extension AppState {
|
|||
|
||||
func update() {
|
||||
guard !isUpdating else { return }
|
||||
updateDownloadableRuntimes()
|
||||
updateInstalledRuntimes()
|
||||
updatePublisher = updateSelectedXcodePath()
|
||||
.flatMap { _ in
|
||||
self.updateAvailableXcodes(from: self.dataSource)
|
||||
|
|
@ -125,6 +128,21 @@ extension AppState {
|
|||
withIntermediateDirectories: true)
|
||||
try data.write(to: Path.cacheFile.url)
|
||||
}
|
||||
|
||||
// MARK: Runtime Cache
|
||||
|
||||
func loadCacheDownloadableRuntimes() throws {
|
||||
guard let data = Current.files.contents(atPath: Path.runtimeCacheFile.string) else { return }
|
||||
let runtimes = try JSONDecoder().decode([DownloadableRuntime].self, from: data)
|
||||
self.downloadableRuntimes = runtimes
|
||||
}
|
||||
|
||||
func cacheDownloadableRuntimes(_ runtimes: [DownloadableRuntime]) throws {
|
||||
let data = try JSONEncoder().encode(runtimes)
|
||||
try FileManager.default.createDirectory(at: Path.runtimeCacheFile.url.deletingLastPathComponent(),
|
||||
withIntermediateDirectories: true)
|
||||
try data.write(to: Path.runtimeCacheFile.url)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppState {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import Path
|
|||
import Version
|
||||
import os.log
|
||||
import DockProgress
|
||||
import XcodesKit
|
||||
|
||||
class AppState: ObservableObject {
|
||||
private let client = AppleAPI.Client()
|
||||
internal let runtimeService = RuntimeService()
|
||||
|
||||
// MARK: - Published Properties
|
||||
|
||||
|
|
@ -100,10 +102,17 @@ class AppState: ObservableObject {
|
|||
Current.defaults.set(showOpenInRosettaOption, forKey: "showOpenInRosettaOption")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Runtimes
|
||||
|
||||
@Published var downloadableRuntimes: [DownloadableRuntime] = []
|
||||
@Published var installedRuntimes: [CoreSimulatorImage] = []
|
||||
|
||||
// MARK: - Publisher Cancellables
|
||||
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
private var installationPublishers: [Version: AnyCancellable] = [:]
|
||||
internal var runtimePublishers: [String: AnyCancellable] = [:]
|
||||
private var selectPublisher: AnyCancellable?
|
||||
private var uninstallPublisher: AnyCancellable?
|
||||
private var autoInstallTimer: Timer?
|
||||
|
|
@ -150,9 +159,11 @@ class AppState: ObservableObject {
|
|||
init() {
|
||||
guard !isTesting else { return }
|
||||
try? loadCachedAvailableXcodes()
|
||||
try? loadCacheDownloadableRuntimes()
|
||||
checkIfHelperIsInstalled()
|
||||
setupAutoInstallTimer()
|
||||
setupDefaults()
|
||||
updateInstalledRuntimes()
|
||||
}
|
||||
|
||||
func setupDefaults() {
|
||||
|
|
@ -180,11 +191,23 @@ class AppState: ObservableObject {
|
|||
func validateADCSession(path: String) -> AnyPublisher<Void, Error> {
|
||||
return Current.network.dataTask(with: URLRequest.downloadADCAuth(path: path))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.tryMap { _ in
|
||||
.tryMap { result -> Void in
|
||||
let httpResponse = result.response as! HTTPURLResponse
|
||||
if httpResponse.statusCode == 401 {
|
||||
throw AuthenticationError.notAuthorized
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func validateADCSession(path: String) async throws {
|
||||
let result = try await Current.network.dataTaskAsync(with: URLRequest.downloadADCAuth(path: path))
|
||||
let httpResponse = result.1 as! HTTPURLResponse
|
||||
if httpResponse.statusCode == 401 {
|
||||
throw AuthenticationError.notAuthorized
|
||||
}
|
||||
}
|
||||
|
||||
func validateSession() -> AnyPublisher<Void, Error> {
|
||||
|
||||
return Current.network.validateSession()
|
||||
|
|
@ -799,6 +822,19 @@ class AppState: ObservableObject {
|
|||
self.allXcodes = newAllXcodes.sorted { $0.version > $1.version }
|
||||
}
|
||||
|
||||
// MARK: Runtimes
|
||||
func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
|
||||
if let coreSimulatorInfo = installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first {
|
||||
let urlString = coreSimulatorInfo.path["relative"]!
|
||||
// app was not allowed to open up file:// url's so remove
|
||||
let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
|
||||
let url = URL(fileURLWithPath: fileRemovedString)
|
||||
|
||||
return Path(url: url)!
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func uninstallXcode(path: Path) -> AnyPublisher<Void, Error> {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import Foundation
|
||||
import Path
|
||||
|
||||
extension Entry {
|
||||
static func isAppBundle(kind: Kind, path: Path) -> Bool {
|
||||
kind == .directory &&
|
||||
extension Path {
|
||||
static func isAppBundle(path: Path) -> Bool {
|
||||
path.isDirectory &&
|
||||
path.extension == "app" &&
|
||||
!path.isSymlink
|
||||
}
|
||||
static func infoPlist(kind: Kind, path: Path) -> InfoPlist? {
|
||||
static func infoPlist(path: Path) -> InfoPlist? {
|
||||
let infoPlistPath = path.join("Contents").join("Info.plist")
|
||||
guard
|
||||
let infoPlistData = try? Data(contentsOf: infoPlistPath.url),
|
||||
|
|
@ -18,10 +18,10 @@ extension Entry {
|
|||
}
|
||||
|
||||
var isAppBundle: Bool {
|
||||
Entry.isAppBundle(kind: kind, path: path)
|
||||
Path.isAppBundle(path: self)
|
||||
}
|
||||
|
||||
var infoPlist: InfoPlist? {
|
||||
Entry.infoPlist(kind: kind, path: path)
|
||||
Path.infoPlist(path: self)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Foundation
|
|||
import Path
|
||||
import AppleAPI
|
||||
import KeychainAccess
|
||||
|
||||
import XcodesKit
|
||||
/**
|
||||
Lightweight dependency injection using global mutable state :P
|
||||
|
||||
|
|
@ -111,6 +111,9 @@ public struct Shell {
|
|||
|
||||
return (progress, publisher)
|
||||
}
|
||||
// TODO: Support using aria2 using AysncStream/AsyncSequence
|
||||
// public var downloadWithAria2Async: (Path, URL, Path, [HTTPCookie]) async throws -> Progress = { aria2Path, url, destination, cookies in
|
||||
|
||||
|
||||
public var unxipExperiment: (URL) -> AnyPublisher<ProcessOutput, Error> = { url in
|
||||
let unxipPath = Path(url: Bundle.main.url(forAuxiliaryExecutable: "unxip")!)!
|
||||
|
|
@ -166,18 +169,24 @@ 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" {
|
||||
if Path.isAppBundle(path: destination) && Path.infoPlist(path: destination)?.bundleID == "com.apple.dt.Xcode" {
|
||||
return InstalledXcode.init(path: destination)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var write: (Data, URL) throws -> Void = { try $0.write(to: $1) }
|
||||
|
||||
public func write(_ data: Data, to url: URL) throws {
|
||||
try write(data, url)
|
||||
}
|
||||
}
|
||||
|
||||
private func _installedXcodes(destination: Path) -> [InstalledXcode] {
|
||||
((try? destination.ls()) ?? [])
|
||||
destination.ls()
|
||||
.filter { $0.isAppBundle && $0.infoPlist?.bundleID == "com.apple.dt.Xcode" }
|
||||
.map { $0.path }
|
||||
.map { $0 }
|
||||
.compactMap(InstalledXcode.init)
|
||||
}
|
||||
|
||||
|
|
@ -189,10 +198,15 @@ public struct Network {
|
|||
.mapError { $0 as Error }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func dataTask(with request: URLRequest) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Error> {
|
||||
dataTask(request)
|
||||
}
|
||||
|
||||
|
||||
public func dataTaskAsync(with request: URLRequest) async throws -> (Data, URLResponse) {
|
||||
return try await AppleAPI.Current.network.session.data(for: request)
|
||||
}
|
||||
|
||||
public var downloadTask: (URL, URL, Data?) -> (Progress, AnyPublisher<(saveLocation: URL, response: URLResponse), Error>) = { AppleAPI.Current.network.session.downloadTask(with: $0, to: $1, resumingWith: $2) }
|
||||
|
||||
public func downloadTask(with url: URL, to saveLocation: URL, resumingWith resumeData: Data?) -> (progress: Progress, publisher: AnyPublisher<(saveLocation: URL, response: URLResponse), Error>) {
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import Foundation
|
||||
import os.log
|
||||
|
||||
extension Logger {
|
||||
private static var subsystem = Bundle.main.bundleIdentifier!
|
||||
|
||||
static let appState = Logger(subsystem: subsystem, category: "appState")
|
||||
static let helperClient = Logger(subsystem: subsystem, category: "helperClient")
|
||||
static let subprocess = Logger(subsystem: subsystem, category: "subprocess")
|
||||
}
|
||||
|
|
@ -28,4 +28,19 @@ extension Path {
|
|||
}
|
||||
return path
|
||||
}
|
||||
|
||||
static var runtimeCacheFile: Path {
|
||||
return xcodesApplicationSupport/"downloadable-runtimes.json"
|
||||
}
|
||||
|
||||
static var xcodesCaches: Path {
|
||||
return caches/"com.xcodesorg.xcodesapp"
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func setCurrentUserAsOwner() -> Path {
|
||||
let user = ProcessInfo.processInfo.environment["SUDO_USER"] ?? NSUserName()
|
||||
try? FileManager.default.setAttributes([.ownerAccountName: user], ofItemAtPath: string)
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import Combine
|
|||
import Foundation
|
||||
import os.log
|
||||
import Path
|
||||
import XcodesKit
|
||||
|
||||
public typealias ProcessOutput = (status: Int32, out: String, err: String)
|
||||
|
||||
extension Process {
|
||||
@discardableResult
|
||||
static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) -> AnyPublisher<ProcessOutput, Error> {
|
||||
static func run(_ executable: any Pathish, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) -> AnyPublisher<ProcessOutput, Error> {
|
||||
return run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
|
||||
}
|
||||
|
||||
|
|
@ -67,9 +68,3 @@ extension Process {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
struct ProcessExecutionError: Error {
|
||||
let process: Process
|
||||
let standardOutput: String
|
||||
let standardError: String
|
||||
}
|
||||
|
|
|
|||
35
Xcodes/Backend/SDKs+Xcode.swift
Normal file
35
Xcodes/Backend/SDKs+Xcode.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// SDKs+Xcode.swift
|
||||
// Xcodes
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-06-05.
|
||||
// Copyright © 2023 Robots and Pencils. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import struct XCModel.SDKs
|
||||
|
||||
extension SDKs {
|
||||
/// Loops through all SDK's and returns an array of buildNumbers (to be used to correlate runtimes)
|
||||
func allBuilds() -> [String] {
|
||||
var buildNumbers: [String] = []
|
||||
|
||||
if let iOS = self.iOS?.compactMap({ $0.build }) {
|
||||
buildNumbers += iOS
|
||||
}
|
||||
if let tvOS = self.tvOS?.compactMap({ $0.build }) {
|
||||
buildNumbers += tvOS
|
||||
}
|
||||
if let macOS = self.macOS?.compactMap({ $0.build }) {
|
||||
buildNumbers += macOS
|
||||
}
|
||||
if let watchOS = self.watchOS?.compactMap({ $0.build }) {
|
||||
buildNumbers += watchOS
|
||||
}
|
||||
if let visionOS = self.visionOS?.compactMap({ $0.build }) {
|
||||
buildNumbers += visionOS
|
||||
}
|
||||
|
||||
return buildNumbers
|
||||
}
|
||||
}
|
||||
|
|
@ -67,4 +67,5 @@ struct Xcode: Identifiable, CustomStringConvertible {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import SwiftUI
|
||||
import XcodesKit
|
||||
|
||||
// MARK: - CommandMenu
|
||||
|
||||
|
|
@ -208,6 +209,23 @@ struct CreateSymbolicLinkButton: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct DownloadRuntimeButton: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
let runtime: DownloadableRuntime?
|
||||
|
||||
var body: some View {
|
||||
Button(action: install) {
|
||||
Text("Install")
|
||||
.help("Install")
|
||||
}
|
||||
}
|
||||
|
||||
private func install() {
|
||||
guard let runtime = runtime else { return }
|
||||
appState.downloadRuntime(runtime: runtime)
|
||||
}
|
||||
}
|
||||
|
||||
struct CreateSymbolicBetaLinkButton: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
let xcode: Xcode?
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import Foundation
|
||||
import Path
|
||||
import XcodesKit
|
||||
|
||||
enum XcodeInstallState: Equatable {
|
||||
case notInstalled
|
||||
case installing(InstallationStep)
|
||||
case installing(XcodeInstallationStep)
|
||||
case installed(Path)
|
||||
|
||||
var notInstalled: Bool {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ struct InfoPane: View {
|
|||
Divider()
|
||||
|
||||
Group {
|
||||
ReleaseNotesView(url: xcode.releaseNotesURL)
|
||||
RuntimesView(xcode: xcode)
|
||||
ReleaseDateView(date: xcode.releaseDate)
|
||||
ReleaseNotesView(url: xcode.releaseNotesURL)
|
||||
IdenticalBuildsView(builds: xcode.identicalBuilds)
|
||||
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
|
||||
SDKsView(sdks: xcode.sdks)
|
||||
|
|
@ -44,13 +45,12 @@ struct InfoPane: View {
|
|||
|
||||
private func makePreviewContent(for index: Int) -> some View {
|
||||
let name = PreviewName.allCases[index]
|
||||
|
||||
return InfoPane(xcode: xcodeDict[name]!)
|
||||
.environmentObject(configure(AppState()) {
|
||||
$0.allXcodes = [xcodeDict[name]!]
|
||||
})
|
||||
.frame(width: 300, height: 400)
|
||||
.padding()
|
||||
return InfoPane(xcode: xcodeDict[name]!)
|
||||
.environmentObject(configure(AppState()) {
|
||||
$0.allXcodes = [xcodeDict[name]!]
|
||||
})
|
||||
.frame(width: 300, height: 400)
|
||||
.padding()
|
||||
}
|
||||
|
||||
enum PreviewName: String, CaseIterable, Identifiable {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import SwiftUI
|
||||
import XcodesKit
|
||||
|
||||
struct InstallationStepDetailView: View {
|
||||
let installationStep: InstallationStep
|
||||
let installationStep: XcodeInstallationStep
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// RuntimeInstallationStepDetailView.swift
|
||||
// Xcodes
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-11-23.
|
||||
// Copyright © 2023 Robots and Pencils. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import XcodesKit
|
||||
|
||||
struct RuntimeInstallationStepDetailView: View {
|
||||
let installationStep: RuntimeInstallationStep
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(String(format: localizeString("InstallationStepDescription"), installationStep.stepNumber, installationStep.stepCount, installationStep.message))
|
||||
|
||||
switch installationStep {
|
||||
case let .downloading(progress):
|
||||
ObservingProgressIndicator(
|
||||
progress,
|
||||
controlSize: .regular,
|
||||
style: .bar,
|
||||
showsAdditionalDescription: true
|
||||
)
|
||||
|
||||
case .installing, .trashingArchive:
|
||||
ProgressView()
|
||||
.scaleEffect(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Downloading") {
|
||||
RuntimeInstallationStepDetailView(
|
||||
installationStep: .downloading(
|
||||
progress: configure(Progress()) {
|
||||
$0.kind = .file
|
||||
$0.fileOperationKind = .downloading
|
||||
$0.estimatedTimeRemaining = 123
|
||||
$0.totalUnitCount = 11944848484
|
||||
$0.completedUnitCount = 848444920
|
||||
$0.throughput = 9211681
|
||||
}
|
||||
))
|
||||
}
|
||||
#Preview("Installing") {
|
||||
RuntimeInstallationStepDetailView(
|
||||
installationStep: .installing
|
||||
)
|
||||
}
|
||||
65
Xcodes/Frontend/InfoPane/RuntimesView.swift
Normal file
65
Xcodes/Frontend/InfoPane/RuntimesView.swift
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// RuntimesView.swift
|
||||
// Xcodes
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-11-23.
|
||||
// Copyright © 2023 Robots and Pencils. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RuntimesView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
let xcode: Xcode
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Platforms")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
let builds = xcode.sdks?.allBuilds()
|
||||
let runtimes = builds?.flatMap { sdkBuild in
|
||||
appState.downloadableRuntimes.filter {
|
||||
$0.sdkBuildUpdate == sdkBuild
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(runtimes ?? [], id: \.simulatorVersion.buildUpdate) { runtime in
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(runtime.visibleIdentifier)")
|
||||
.font(.subheadline)
|
||||
Spacer()
|
||||
Text(runtime.downloadFileSizeString)
|
||||
.font(.subheadline)
|
||||
|
||||
// it's installed if we have a path
|
||||
if let path = appState.runtimeInstallPath(xcode: xcode, runtime: runtime) {
|
||||
Button(action: { appState.reveal(path: path.string) }) {
|
||||
Image(systemName: "arrow.right.circle.fill")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.help("RevealInFinder")
|
||||
} else {
|
||||
DownloadRuntimeButton(runtime: runtime)
|
||||
}
|
||||
}
|
||||
switch runtime.installState {
|
||||
|
||||
case .installing(let installationStep):
|
||||
RuntimeInstallationStepDetailView(installationStep: installationStep)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// RuntimesView()
|
||||
//}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import SwiftUI
|
||||
import XcodesKit
|
||||
|
||||
struct InstallationStepRowView: View {
|
||||
let installationStep: InstallationStep
|
||||
let installationStep: XcodeInstallationStep
|
||||
let highlighted: Bool
|
||||
let cancel: () -> Void
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,37 @@
|
|||
{\rtf1\ansi\ansicpg1252\cocoartf2639
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2758
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
{\*\expandedcolortbl;;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
|
||||
|
||||
\f0\fs34 \cf0 SwiftSoup\
|
||||
\f0\fs34 \cf0 AsyncHTTPNetworkService\
|
||||
\
|
||||
|
||||
\fs26 MIT License\
|
||||
\
|
||||
Copyright (c) 2022 Robots and Pencils\
|
||||
\
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy\
|
||||
of this software and associated documentation files (the "Software"), to deal\
|
||||
in the Software without restriction, including without limitation the rights\
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
|
||||
copies of the Software, and to permit persons to whom the Software is\
|
||||
furnished to do so, subject to the following conditions:\
|
||||
\
|
||||
The above copyright notice and this permission notice shall be included in all\
|
||||
copies or substantial portions of the Software.\
|
||||
\
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
|
||||
SOFTWARE.\
|
||||
\
|
||||
\
|
||||
|
||||
\fs34 SwiftSoup\
|
||||
\
|
||||
|
||||
\fs26 MIT License\
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
"Compilers" = "Compilers";
|
||||
"DownloadSize" = "Download Size";
|
||||
"NoXcodeSelected" = "No Xcode Selected";
|
||||
"Platforms" = "Platforms";
|
||||
|
||||
// Installation Steps
|
||||
// When localizing. Items will be replaced in order. ie "Step 1 of 6: Downloading"
|
||||
|
|
|
|||
9
Xcodes/XcodesKit/.gitignore
vendored
Normal file
9
Xcodes/XcodesKit/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
33
Xcodes/XcodesKit/Package.swift
Normal file
33
Xcodes/XcodesKit/Package.swift
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// swift-tools-version: 5.7
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "XcodesKit",
|
||||
platforms: [.macOS(.v11)],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "XcodesKit",
|
||||
targets: ["XcodesKit"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/RobotsAndPencils/AsyncHTTPNetworkService", branch: "main"),
|
||||
.package(url: "https://github.com/mxcl/Path.swift", from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "XcodesKit",
|
||||
dependencies: [
|
||||
.product(name: "AsyncNetworkService", package: "AsyncHTTPNetworkService"),
|
||||
.product(name: "Path", package: "Path.swift")
|
||||
]),
|
||||
.testTarget(
|
||||
name: "XcodesKitTests",
|
||||
dependencies: ["XcodesKit"]),
|
||||
]
|
||||
)
|
||||
3
Xcodes/XcodesKit/README.md
Normal file
3
Xcodes/XcodesKit/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# XcodesKit
|
||||
|
||||
A description of this package.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import Foundation
|
||||
|
||||
extension NSRegularExpression {
|
||||
func firstString(in string: String, options: NSRegularExpression.MatchingOptions = []) -> String? {
|
||||
let range = NSRange(location: 0, length: string.utf16.count)
|
||||
guard let firstMatch = firstMatch(in: string, options: options, range: range),
|
||||
let resultRange = Range(firstMatch.range, in: string) else {
|
||||
return nil
|
||||
}
|
||||
return String(string[resultRange])
|
||||
}
|
||||
}
|
||||
10
Xcodes/XcodesKit/Sources/XcodesKit/Extensions/Logger.swift
Normal file
10
Xcodes/XcodesKit/Sources/XcodesKit/Extensions/Logger.swift
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import Foundation
|
||||
import os.log
|
||||
|
||||
extension Logger {
|
||||
private static var subsystem = Bundle.main.bundleIdentifier!
|
||||
|
||||
static public let appState = Logger(subsystem: subsystem, category: "appState")
|
||||
static public let helperClient = Logger(subsystem: subsystem, category: "helperClient")
|
||||
static public let subprocess = Logger(subsystem: subsystem, category: "subprocess")
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// CoreSimulatorImage.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-01-08.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct CoreSimulatorPlist: Decodable {
|
||||
public let images: [CoreSimulatorImage]
|
||||
}
|
||||
|
||||
public struct CoreSimulatorImage: Decodable {
|
||||
public let uuid: String
|
||||
public let path: [String: String]
|
||||
public let runtimeInfo: CoreSimulatorRuntimeInfo
|
||||
}
|
||||
|
||||
public struct CoreSimulatorRuntimeInfo: Decodable {
|
||||
public let build: String
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// RuntimeInstallState.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-11-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Path
|
||||
|
||||
public enum RuntimeInstallState: Equatable {
|
||||
case notInstalled
|
||||
case installing(RuntimeInstallationStep)
|
||||
case installed
|
||||
|
||||
var notInstalled: Bool {
|
||||
switch self {
|
||||
case .notInstalled: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
var installing: Bool {
|
||||
switch self {
|
||||
case .installing: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
var installed: Bool {
|
||||
switch self {
|
||||
case .installed: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// RuntimeInstallationStep.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-11-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum RuntimeInstallationStep: Equatable, CustomStringConvertible {
|
||||
case downloading(progress: Progress)
|
||||
case installing
|
||||
case trashingArchive
|
||||
|
||||
public var description: String {
|
||||
"(\(stepNumber)/\(stepCount)) \(message)"
|
||||
}
|
||||
|
||||
public var message: String {
|
||||
switch self {
|
||||
case .downloading:
|
||||
return localizeString("Downloading")
|
||||
case .installing:
|
||||
return localizeString("Installing")
|
||||
case .trashingArchive:
|
||||
return localizeString("TrashingArchive")
|
||||
}
|
||||
}
|
||||
|
||||
public var stepNumber: Int {
|
||||
switch self {
|
||||
case .downloading: return 1
|
||||
case .installing: return 2
|
||||
case .trashingArchive: return 3
|
||||
}
|
||||
}
|
||||
|
||||
public var stepCount: Int { 3 }
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
import Foundation
|
||||
|
||||
public struct DownloadableRuntimesResponse: Codable {
|
||||
public let sdkToSimulatorMappings: [SDKToSimulatorMapping]
|
||||
public let sdkToSeedMappings: [SDKToSeedMapping]
|
||||
public let refreshInterval: Int
|
||||
public let downloadables: [DownloadableRuntime]
|
||||
public let version: String
|
||||
}
|
||||
|
||||
public struct DownloadableRuntime: Codable {
|
||||
public let category: Category
|
||||
public let simulatorVersion: SimulatorVersion
|
||||
public let source: String
|
||||
public let dictionaryVersion: Int
|
||||
public let contentType: ContentType
|
||||
public let platform: Platform
|
||||
public let identifier: String
|
||||
public let version: String
|
||||
public let fileSize: Int
|
||||
public let hostRequirements: HostRequirements?
|
||||
public let name: String
|
||||
public let authentication: Authentication?
|
||||
public var url: URL {
|
||||
return URL(string: source)!
|
||||
}
|
||||
public var downloadPath: String {
|
||||
url.path
|
||||
}
|
||||
|
||||
// dynamically updated - not decoded
|
||||
public var installState: RuntimeInstallState = .notInstalled
|
||||
public var sdkBuildUpdate: String?
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
case category
|
||||
case simulatorVersion
|
||||
case source
|
||||
case dictionaryVersion
|
||||
case contentType
|
||||
case platform
|
||||
case identifier
|
||||
case version
|
||||
case fileSize
|
||||
case hostRequirements
|
||||
case name
|
||||
case authentication
|
||||
case sdkBuildUpdate
|
||||
}
|
||||
|
||||
var betaNumber: Int? {
|
||||
enum Regex { static let shared = try! NSRegularExpression(pattern: "b[0-9]+$") }
|
||||
guard var foundString = Regex.shared.firstString(in: identifier) else { return nil }
|
||||
foundString.removeFirst()
|
||||
return Int(foundString)!
|
||||
}
|
||||
|
||||
var completeVersion: String {
|
||||
makeVersion(for: simulatorVersion.version, betaNumber: betaNumber)
|
||||
}
|
||||
|
||||
public var visibleIdentifier: String {
|
||||
return platform.shortName + " " + completeVersion
|
||||
}
|
||||
|
||||
func makeVersion(for osVersion: String, betaNumber: Int?) -> String {
|
||||
let betaSuffix = betaNumber.flatMap { "-beta\($0)" } ?? ""
|
||||
return osVersion + betaSuffix
|
||||
}
|
||||
|
||||
public var downloadFileSizeString: String {
|
||||
return ByteCountFormatter.string(fromByteCount: Int64(fileSize), countStyle: .file)
|
||||
}
|
||||
}
|
||||
|
||||
public struct SDKToSeedMapping: Codable {
|
||||
public let buildUpdate: String
|
||||
public let platform: DownloadableRuntime.Platform
|
||||
public let seedNumber: Int
|
||||
}
|
||||
|
||||
public struct SDKToSimulatorMapping: Codable {
|
||||
public let sdkBuildUpdate: String
|
||||
public let simulatorBuildUpdate: String
|
||||
public let sdkIdentifier: String
|
||||
}
|
||||
|
||||
extension DownloadableRuntime {
|
||||
public struct SimulatorVersion: Codable {
|
||||
public let buildUpdate: String
|
||||
public let version: String
|
||||
}
|
||||
|
||||
public struct HostRequirements: Codable {
|
||||
let maxHostVersion: String?
|
||||
let excludedHostArchitectures: [String]?
|
||||
let minHostVersion: String?
|
||||
let minXcodeVersion: String?
|
||||
}
|
||||
|
||||
public enum Authentication: String, Codable {
|
||||
case virtual = "virtual"
|
||||
}
|
||||
|
||||
public enum Category: String, Codable {
|
||||
case simulator = "simulator"
|
||||
}
|
||||
|
||||
public enum ContentType: String, Codable {
|
||||
case diskImage = "diskImage"
|
||||
case package = "package"
|
||||
}
|
||||
|
||||
public enum Platform: String, Codable {
|
||||
case iOS = "com.apple.platform.iphoneos"
|
||||
case macOS = "com.apple.platform.macosx"
|
||||
case watchOS = "com.apple.platform.watchos"
|
||||
case tvOS = "com.apple.platform.appletvos"
|
||||
case visionOS = "com.apple.platform.xros"
|
||||
|
||||
var order: Int {
|
||||
switch self {
|
||||
case .iOS: return 1
|
||||
case .macOS: return 2
|
||||
case .watchOS: return 3
|
||||
case .tvOS: return 4
|
||||
case .visionOS: return 5
|
||||
}
|
||||
}
|
||||
|
||||
var shortName: String {
|
||||
switch self {
|
||||
case .iOS: return "iOS"
|
||||
case .macOS: return "macOS"
|
||||
case .watchOS: return "watchOS"
|
||||
case .tvOS: return "tvOS"
|
||||
case .visionOS: return "visionOS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstalledRuntime: Decodable {
|
||||
let build: String
|
||||
let deletable: Bool
|
||||
let identifier: UUID
|
||||
let kind: Kind
|
||||
let lastUsedAt: Date?
|
||||
let path: String
|
||||
let platformIdentifier: Platform
|
||||
let runtimeBundlePath: String
|
||||
let runtimeIdentifier: String
|
||||
let signatureState: String
|
||||
let state: String
|
||||
let version: String
|
||||
let sizeBytes: Int?
|
||||
}
|
||||
|
||||
extension InstalledRuntime {
|
||||
enum Kind: String, Decodable {
|
||||
case diskImage = "Disk Image"
|
||||
case bundled = "Bundled with Xcode"
|
||||
case legacyDownload = "Legacy Download"
|
||||
}
|
||||
|
||||
enum Platform: String, Decodable {
|
||||
case tvOS = "com.apple.platform.appletvsimulator"
|
||||
case iOS = "com.apple.platform.iphonesimulator"
|
||||
case watchOS = "com.apple.platform.watchsimulator"
|
||||
case visionOS = "com.apple.platform.xrsimulator"
|
||||
|
||||
var asPlatformOS: DownloadableRuntime.Platform {
|
||||
switch self {
|
||||
case .watchOS: return .watchOS
|
||||
case .iOS: return .iOS
|
||||
case .tvOS: return .tvOS
|
||||
case .visionOS: return .visionOS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// InstallState.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-06-06.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Path
|
||||
|
||||
public enum XcodeInstallState: Equatable {
|
||||
case notInstalled
|
||||
case installing(XcodeInstallationStep)
|
||||
case installed(Path)
|
||||
|
||||
var notInstalled: Bool {
|
||||
switch self {
|
||||
case .notInstalled: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
var installing: Bool {
|
||||
switch self {
|
||||
case .installing: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
var installed: Bool {
|
||||
switch self {
|
||||
case .installed: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
//
|
||||
// InstallationStep.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-06-06.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A numbered step
|
||||
enum InstallationStep: Equatable, CustomStringConvertible {
|
||||
// A numbered step
|
||||
public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
|
||||
case downloading(progress: Progress)
|
||||
case unarchiving
|
||||
case moving(destination: String)
|
||||
|
|
@ -9,11 +16,11 @@ enum InstallationStep: Equatable, CustomStringConvertible {
|
|||
case checkingSecurity
|
||||
case finishing
|
||||
|
||||
var description: String {
|
||||
public var description: String {
|
||||
"(\(stepNumber)/\(stepCount)) \(message)"
|
||||
}
|
||||
|
||||
var message: String {
|
||||
public var message: String {
|
||||
switch self {
|
||||
case .downloading:
|
||||
return localizeString("Downloading")
|
||||
|
|
@ -30,7 +37,7 @@ enum InstallationStep: Equatable, CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
var stepNumber: Int {
|
||||
public var stepNumber: Int {
|
||||
switch self {
|
||||
case .downloading: return 1
|
||||
case .unarchiving: return 2
|
||||
|
|
@ -41,5 +48,14 @@ enum InstallationStep: Equatable, CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
var stepCount: Int { 6 }
|
||||
public var stepCount: Int { 6 }
|
||||
}
|
||||
|
||||
func localizeString(_ key: String, comment: String = "") -> String {
|
||||
if #available(macOS 12, *) {
|
||||
return String(localized: String.LocalizationValue(key))
|
||||
} else {
|
||||
return NSLocalizedString(key, comment: comment)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import Foundation
|
||||
import AsyncNetworkService
|
||||
import Path
|
||||
|
||||
extension URL {
|
||||
static let downloadableRuntimes = URL(string: "https://devimages-cdn.apple.com/downloads/xcode/simulators/index2.dvtdownloadableindex")!
|
||||
}
|
||||
|
||||
public struct RuntimeService {
|
||||
var networkService: AsyncHTTPNetworkService
|
||||
public enum Error: LocalizedError, Equatable {
|
||||
case unavailableRuntime(String)
|
||||
case failedMountingDMG
|
||||
}
|
||||
|
||||
public init() {
|
||||
networkService = AsyncHTTPNetworkService()
|
||||
}
|
||||
|
||||
public func downloadableRuntimes() async throws -> DownloadableRuntimesResponse {
|
||||
let urlRequest = URLRequest(url: .downloadableRuntimes)
|
||||
|
||||
// Apple gives a plist for download
|
||||
let (data, _) = try await networkService.requestData(urlRequest, validators: [])
|
||||
let decodedResponse = try PropertyListDecoder().decode(DownloadableRuntimesResponse.self, from: data)
|
||||
|
||||
return decodedResponse
|
||||
}
|
||||
|
||||
public func installedRuntimes() async throws -> [InstalledRuntime] {
|
||||
// This only uses the Selected Xcode, so we don't know what other SDK's have been installed in previous versions
|
||||
let output = try await Current.shell.installedRuntimes()
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .iso8601
|
||||
let outputDictionary = try decoder.decode([String: InstalledRuntime].self, from: output.out.data(using: .utf8)!)
|
||||
|
||||
return outputDictionary.values.sorted { first, second in
|
||||
return first.identifier.uuidString.compare(second.identifier.uuidString, options: .numeric) == .orderedAscending
|
||||
}
|
||||
}
|
||||
|
||||
/// Loops through `/Library/Developer/CoreSimulator/images/images.plist` which contains a list of downloaded Simuator Runtimes
|
||||
/// This is different then using `simctl` (`installedRuntimes()`) which only returns the installed runtimes for the selected xcode version.
|
||||
public func localInstalledRuntimes() async throws -> [CoreSimulatorImage] {
|
||||
guard let path = Path("/Library/Developer/CoreSimulator/images/images.plist") else { throw "Could not find images.plist for CoreSimulators" }
|
||||
guard let infoPlistData = FileManager.default.contents(atPath: path.string) else { throw "Could not get data from \(path.string)" }
|
||||
|
||||
do {
|
||||
let infoPlist: CoreSimulatorPlist = try PropertyListDecoder().decode(CoreSimulatorPlist.self, from: infoPlistData)
|
||||
return infoPlist.images
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public func installRuntimeImage(dmgURL: URL) async throws {
|
||||
_ = try await Current.shell.installRuntimeImage(dmgURL)
|
||||
}
|
||||
|
||||
public func mountDMG(dmgUrl: URL) async throws -> URL {
|
||||
let resultPlist = try await Current.shell.mountDmg(dmgUrl)
|
||||
|
||||
let dict = try? (PropertyListSerialization.propertyList(from: resultPlist.out.data(using: .utf8)!, format: nil) as? NSDictionary)
|
||||
let systemEntities = dict?["system-entities"] as? NSArray
|
||||
guard let path = systemEntities?.compactMap ({ ($0 as? NSDictionary)?["mount-point"] as? String }).first else {
|
||||
throw Error.failedMountingDMG
|
||||
}
|
||||
return URL(fileURLWithPath: path)
|
||||
}
|
||||
|
||||
public func unmountDMG(mountedURL: URL) async throws {
|
||||
_ = try await Current.shell.unmountDmg(mountedURL)
|
||||
}
|
||||
|
||||
public func expand(pkgPath: Path, expandedPkgPath: Path) async throws {
|
||||
_ = try await Current.shell.expandPkg(pkgPath.url, expandedPkgPath.url)
|
||||
}
|
||||
|
||||
public func createPkg(pkgPath: Path, expandedPkgPath: Path) async throws {
|
||||
_ = try await Current.shell.createPkg(pkgPath.url, expandedPkgPath.url)
|
||||
}
|
||||
|
||||
public func installPkg(pkgPath: Path, expandedPkgPath: Path) async throws {
|
||||
_ = try await Current.shell.installPkg(pkgPath.url, expandedPkgPath.url.absoluteString)
|
||||
}
|
||||
}
|
||||
|
||||
extension String: Error {}
|
||||
66
Xcodes/XcodesKit/Sources/XcodesKit/Shell/Process.swift
Normal file
66
Xcodes/XcodesKit/Sources/XcodesKit/Shell/Process.swift
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import Foundation
|
||||
import Path
|
||||
import os.log
|
||||
|
||||
public typealias ProcessOutput = (status: Int32, out: String, err: String)
|
||||
|
||||
extension Process {
|
||||
static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) async throws -> ProcessOutput {
|
||||
return try await run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
|
||||
}
|
||||
|
||||
static func run(_ executable: URL, workingDirectory: URL? = nil, input: String? = nil, _ arguments: [String]) async throws -> ProcessOutput {
|
||||
|
||||
let process = Process()
|
||||
process.currentDirectoryURL = workingDirectory ?? executable.deletingLastPathComponent()
|
||||
process.executableURL = executable
|
||||
process.arguments = arguments
|
||||
|
||||
let (stdout, stderr) = (Pipe(), Pipe())
|
||||
process.standardOutput = stdout
|
||||
process.standardError = stderr
|
||||
|
||||
if let input = input {
|
||||
let inputPipe = Pipe()
|
||||
process.standardInput = inputPipe.fileHandleForReading
|
||||
inputPipe.fileHandleForWriting.write(Data(input.utf8))
|
||||
inputPipe.fileHandleForWriting.closeFile()
|
||||
}
|
||||
|
||||
do {
|
||||
Logger.subprocess.info("Process.run executable: \(executable), input: \(input ?? ""), arguments: \(arguments.joined(separator: ", "))")
|
||||
|
||||
try process.run()
|
||||
process.waitUntilExit()
|
||||
|
||||
let output = String(data: stdout.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
|
||||
let error = String(data: stderr.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
|
||||
|
||||
Logger.subprocess.info("Process.run output: \(output)")
|
||||
if !error.isEmpty {
|
||||
Logger.subprocess.error("Process.run error: \(error)")
|
||||
}
|
||||
|
||||
guard process.terminationReason == .exit, process.terminationStatus == 0 else {
|
||||
throw ProcessExecutionError(process: process, standardOutput: output, standardError: error)
|
||||
}
|
||||
|
||||
return (process.terminationStatus, output, error)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct ProcessExecutionError: Error {
|
||||
public let process: Process
|
||||
public let standardOutput: String
|
||||
public let standardError: String
|
||||
|
||||
public init(process: Process, standardOutput: String, standardError: String) {
|
||||
self.process = process
|
||||
self.standardOutput = standardOutput
|
||||
self.standardError = standardError
|
||||
}
|
||||
}
|
||||
26
Xcodes/XcodesKit/Sources/XcodesKit/Shell/XcodesShell.swift
Normal file
26
Xcodes/XcodesKit/Sources/XcodesKit/Shell/XcodesShell.swift
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import Foundation
|
||||
import Path
|
||||
|
||||
public struct XcodesShell {
|
||||
public var installedRuntimes: () async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "list", "-j")
|
||||
}
|
||||
public var mountDmg: (URL) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("hdiutil"), "attach", "-nobrowse", "-plist", $0.path)
|
||||
}
|
||||
public var unmountDmg: (URL) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("hdiutil"), "detach", $0.path)
|
||||
}
|
||||
public var expandPkg: (URL, URL) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.sbin.join("pkgutil"), "--verbose", "--expand", $0.path, $1.path)
|
||||
}
|
||||
public var createPkg: (URL, URL) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.sbin.join("pkgutil"), "--flatten", $0.path, $1.path)
|
||||
}
|
||||
public var installPkg: (URL, String) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.sbin.join("installer"), "-pkg", $0.path, "-target", $1)
|
||||
}
|
||||
public var installRuntimeImage: (URL) async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "add", $0.path)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import Foundation
|
||||
|
||||
public struct XcodesKitEnvironment {
|
||||
public var shell = XcodesShell()
|
||||
}
|
||||
|
||||
public var Current = XcodesKitEnvironment()
|
||||
11
Xcodes/XcodesKit/Tests/XcodesKitTests/XcodesKitTests.swift
Normal file
11
Xcodes/XcodesKit/Tests/XcodesKitTests/XcodesKitTests.swift
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import XCTest
|
||||
@testable import XcodesKit
|
||||
|
||||
final class XcodesKitTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(XcodesKit().text, "Hello, World!")
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import CombineExpectations
|
|||
import Path
|
||||
import Version
|
||||
import XCTest
|
||||
import XcodesKit
|
||||
|
||||
@testable import Xcodes
|
||||
|
||||
class AppStateTests: XCTestCase {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import Combine
|
|||
import Foundation
|
||||
@testable import Xcodes
|
||||
|
||||
extension Environment {
|
||||
static var mock = Environment(
|
||||
extension Xcodes.Environment {
|
||||
static var mock = Xcodes.Environment(
|
||||
shell: .mock,
|
||||
files: .mock,
|
||||
network: .mock,
|
||||
|
|
|
|||
Loading…
Reference in a new issue