Skip to content

Commit

Permalink
Merge branch 'main' into security-key-auth
Browse files Browse the repository at this point in the history
# Conflicts:
#	Xcodes.xcodeproj/project.pbxproj
  • Loading branch information
kinoroy committed Oct 13, 2024
2 parents 27bbf6f + c1670b2 commit f4567bd
Show file tree
Hide file tree
Showing 15 changed files with 359 additions and 39 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ on:

jobs:
test:
runs-on: macos-13
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Run tests
env:
DEVELOPER_DIR: /Applications/Xcode_15.0.1.app
DEVELOPER_DIR: /Applications/Xcode_16.app
run: xcodebuild test -scheme Xcodes
42 changes: 21 additions & 21 deletions Xcodes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@
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 */; };
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E83FDC432CBB649100679C6B /* Sparkle */; };
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; };
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */; };
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F532B333864003F3959 /* PlatformsListView.swift */; };
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E84E4F562B335094003F3959 /* OrderedCollections */; };
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86671262B309D2F0048559A /* PlatformsView.swift */; };
E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; };
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
E891A1C42B43ACF900A1B9D1 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E891A1C32B43ACF900A1B9D1 /* Sparkle */; };
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 */; };
Expand Down Expand Up @@ -357,7 +357,7 @@
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
E891A1C42B43ACF900A1B9D1 /* Sparkle in Frameworks */,
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */,
E8C0EB1A291EF43E0081528A /* XcodesKit in Frameworks */,
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */,
Expand Down Expand Up @@ -721,7 +721,7 @@
E8C0EB19291EF43E0081528A /* XcodesKit */,
E8F44A1D296B4CD7002D6592 /* Path */,
E84E4F562B335094003F3959 /* OrderedCollections */,
E891A1C32B43ACF900A1B9D1 /* Sparkle */,
E83FDC432CBB649100679C6B /* Sparkle */,
334A932B2CA885A400A5E079 /* LibFido2Swift */,
);
productName = XcodesMac;
Expand Down Expand Up @@ -810,7 +810,7 @@
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */,
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */,
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */,
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */,
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */,
);
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
Expand Down Expand Up @@ -1091,7 +1091,7 @@
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 27;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
Expand All @@ -1103,7 +1103,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.1.2;
MARKETING_VERSION = 2.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes;
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -1343,7 +1343,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 27;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -1355,7 +1355,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.1.2;
MARKETING_VERSION = 2.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 5.0;
Expand All @@ -1371,7 +1371,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 27;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -1383,7 +1383,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.1.2;
MARKETING_VERSION = 2.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 5.0;
Expand Down Expand Up @@ -1553,20 +1553,20 @@
minimumVersion = 4.3.1;
};
};
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-collections.git";
repositoryURL = "https://github.com/sparkle-project/Sparkle";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.5;
minimumVersion = 2.6.4;
};
};
E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
repositoryURL = "https://github.com/apple/swift-collections.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.5.2;
minimumVersion = 1.0.5;
};
};
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = {
Expand Down Expand Up @@ -1636,16 +1636,16 @@
package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */;
productName = DockProgress;
};
E83FDC432CBB649100679C6B /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
};
E84E4F562B335094003F3959 /* OrderedCollections */ = {
isa = XCSwiftPackageProductDependency;
package = E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */;
productName = OrderedCollections;
};
E891A1C32B43ACF900A1B9D1 /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
};
E8C0EB19291EF43E0081528A /* XcodesKit */ = {
isa = XCSwiftPackageProductDependency;
productName = XcodesKit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@
"repositoryURL": "https://github.com/sparkle-project/Sparkle/",
"state": {
"branch": null,
"revision": "47d3d90aee3c52b6f61d04ceae426e607df62347",
"version": "2.5.2"
"revision": "0ef1ee0220239b3776f433314515fd849025673f",
"version": "2.6.4"
}
},
{
Expand Down
64 changes: 64 additions & 0 deletions Xcodes/Backend/AppState+Runtimes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import OSLog
import Combine
import Path
import AppleAPI
import Version

extension AppState {
func updateDownloadableRuntimes() {
Expand Down Expand Up @@ -48,6 +49,69 @@ extension AppState {
}

func downloadRuntime(runtime: DownloadableRuntime) {
guard let selectedXcode = self.allXcodes.first(where: { $0.selected }) else {
Logger.appState.error("No selected Xcode")
DispatchQueue.main.async {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: "No selected Xcode. Please make an Xcode active")
}
return
}
// new runtimes
if runtime.contentType == .cryptexDiskImage {
// only selected xcodes > 16.1 beta 3 can download runtimes via a xcodebuild -downloadPlatform version
// only Runtimes coming from cryptexDiskImage can be downloaded via xcodebuild
if selectedXcode.version > Version(major: 16, minor: 0, patch: 0) {
downloadRuntimeViaXcodeBuild(runtime: runtime)
} else {
// not supported
Logger.appState.error("Trying to download a runtime we can't download")
DispatchQueue.main.async {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: "Sorry. Apple only supports downloading runtimes iOS 18+, tvOS 18+, watchOS 11+, visionOS 2+ with Xcode 16.1+. Please download and make active.")
}
return
}
} else {
downloadRuntimeObseleteWay(runtime: runtime)
}
}

func downloadRuntimeViaXcodeBuild(runtime: DownloadableRuntime) {
runtimePublishers[runtime.identifier] = Task {
do {
for try await progress in Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate) {
if progress.isIndeterminate {
DispatchQueue.main.async {
self.setInstallationStep(of: runtime, to: .installing, postNotification: false)
}
} else {
DispatchQueue.main.async {
self.setInstallationStep(of: runtime, to: .downloading(progress: progress), postNotification: false)
}
}

}
Logger.appState.debug("Done downloading runtime - \(runtime.name)")
DispatchQueue.main.async {
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
self.downloadableRuntimes[index].installState = .installed
self.update()
}

} catch {
Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
DispatchQueue.main.async {
self.error = error
if let error = error as? String {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error)
} else {
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
}
}
}
}
}

func downloadRuntimeObseleteWay(runtime: DownloadableRuntime) {
runtimePublishers[runtime.identifier] = Task {
do {
let downloadedURL = try await downloadRunTimeFull(runtime: runtime)
Expand Down
5 changes: 5 additions & 0 deletions Xcodes/Backend/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,11 @@ class AppState: ObservableObject {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return }

installationPublishers[id] = signInIfNeeded()
.handleEvents(
receiveSubscription: { [unowned self] _ in
self.setInstallationStep(of: availableXcode.version, to: .authenticating)
}
)
.flatMap { [unowned self] in
// signInIfNeeded might finish before the user actually authenticates if UI is involved.
// This publisher will wait for the @Published authentication state to change to authenticated or unauthenticated before finishing,
Expand Down
71 changes: 71 additions & 0 deletions Xcodes/Backend/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,77 @@ public struct Shell {
return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"])
}

public var downloadRuntime: (String, String) -> AsyncThrowingStream<Progress, Error> = { platform, version in
return AsyncThrowingStream<Progress, Error> { continuation in
Task {
// Assume progress will not have data races, so we manually opt-out isolation checks.
nonisolated(unsafe) var progress = Progress()
progress.kind = .file
progress.fileOperationKind = .downloading

let process = Process()
let xcodeBuildPath = Path.root.usr.bin.join("xcodebuild").url

process.executableURL = xcodeBuildPath
process.arguments = [
"-downloadPlatform",
"\(platform)",
"-buildVersion",
"\(version)"
]

let stdOutPipe = Pipe()
process.standardOutput = stdOutPipe
let stdErrPipe = Pipe()
process.standardError = stdErrPipe

let observer = NotificationCenter.default.addObserver(
forName: .NSFileHandleDataAvailable,
object: nil,
queue: OperationQueue.main
) { note in
guard
// This should always be the case for Notification.Name.NSFileHandleDataAvailable
let handle = note.object as? FileHandle,
handle === stdOutPipe.fileHandleForReading || handle === stdErrPipe.fileHandleForReading
else { return }

defer { handle.waitForDataInBackgroundAndNotify() }

let string = String(decoding: handle.availableData, as: UTF8.self)

// TODO: fix warning. ObservingProgressView is currently tied to an updating progress
progress.updateFromXcodebuild(text: string)

continuation.yield(progress)
}

stdOutPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
stdErrPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()

continuation.onTermination = { @Sendable _ in
process.terminate()
NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
}

do {
try process.run()
} catch {
continuation.finish(throwing: error)
}

process.waitUntilExit()

NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)

guard process.terminationReason == .exit, process.terminationStatus == 0 else {
continuation.finish(throwing: ProcessExecutionError(process: process, standardOutput: "", standardError: ""))
return
}
continuation.finish()
}
}
}
}

public struct Files {
Expand Down
33 changes: 33 additions & 0 deletions Xcodes/Backend/Progress+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,38 @@ extension Progress {
}

}

func updateFromXcodebuild(text: String) {
self.totalUnitCount = 100
self.completedUnitCount = 0
self.localizedAdditionalDescription = "" // to not show the addtional

do {

let downloadPattern = #"(\d+\.\d+)% \(([\d.]+ (?:MB|GB)) of ([\d.]+ GB)\)"#
let downloadRegex = try NSRegularExpression(pattern: downloadPattern)

// Search for matches in the text
if let match = downloadRegex.firstMatch(in: text, range: NSRange(text.startIndex..., in: text)) {
// Extract the percentage - simpler then trying to extract size MB/GB and convert to bytes.
if let percentRange = Range(match.range(at: 1), in: text), let percentDouble = Double(text[percentRange]) {
let percent = Int64(percentDouble.rounded())
self.completedUnitCount = percent
}
}

// "Downloading tvOS 18.1 Simulator (22J5567a): Installing..." or
// "Downloading tvOS 18.1 Simulator (22J5567a): Installing (registering download)..."
if text.range(of: "Installing") != nil {
// sets the progress to indeterminite to show animating progress
self.totalUnitCount = 0
self.completedUnitCount = 0
}

} catch {
Logger.appState.error("Invalid regular expression")
}

}
}

4 changes: 1 addition & 3 deletions Xcodes/Backend/XcodeCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ struct XcodeCommands: Commands {

struct InstallButton: View {
@EnvironmentObject var appState: AppState
@State private var isLoading = false

let xcode: Xcode?

var body: some View {
ProgressButton(isInProgress: isLoading) {
Button {
install()
} label: {
Text("Install")
Expand All @@ -49,7 +48,6 @@ struct InstallButton: View {
}

private func install() {
isLoading = true
guard let xcode = xcode else { return }
appState.checkMinVersionAndInstall(id: xcode.id)
}
Expand Down
Loading

0 comments on commit f4567bd

Please sign in to comment.