Compare commits

...

56 commits

Author SHA1 Message Date
Matt Kiazyk
1a0d3353b9 update readme with v3 changes 2025-09-19 23:53:54 -05:00
Matt Kiazyk
da8d22c6d3 bad package.resolved 2025-09-19 19:56:41 -05:00
Matt Kiazyk
4f9b7f8f6a remove old libfido2swift fix 2025-09-19 19:31:16 -05:00
Matt Kiazyk
f34e291433 3.0.2 2025-09-19 19:20:34 -05:00
Matt Kiazyk
789eedcdb7 add some glass effect... why not? 2025-09-19 19:19:58 -05:00
Matt Kiazyk
f9262d9a05 fix for codesign issues with libfido2swift 2025-09-19 19:19:48 -05:00
Matt Kiazyk
4776af75ee 3.0.1 2025-09-17 23:01:03 -05:00
Matt Kiazyk
cab5533b49
Merge pull request #756 from neetrath/main
Add Thai translations
2025-09-17 22:55:26 -05:00
Matt Kiazyk
fc50db1cee fix how platforms are architecture filtered 2025-09-17 22:37:19 -05:00
Ruttanachai Auitragool
576585b7de feat: Add Thai translations 2025-09-16 23:22:44 +07:00
Matt Kiazyk
6cf9647840
Merge pull request #755 from XcodesOrg/newIcon
New Icon - who dis?
2025-09-16 10:43:43 -05:00
Matt Kiazyk
90dccd8180 new fancy app icon! 2025-09-15 23:08:51 -05:00
Matt Kiazyk
372d5b1f4e
Merge pull request #753 from XcodesOrg/matt/FixXcoded26Check
fix: xcode 26 check for Apple Silicon installs
2025-09-15 22:59:56 -05:00
Matt Kiazyk
4063796833
Merge pull request #727 from megabitsenmzq/main
Update zh_Hans translations.
2025-09-15 22:42:13 -05:00
Matt Kiazyk
be2eea0532
Merge pull request #746 from LiYanan2004/filter-ux
Improve filtering UI/UX on Xcode List
2025-09-15 22:41:32 -05:00
Matt Kiazyk
a6dec75603 fix libfido2 errors on Xcode26 thanks @anciltech 2025-09-15 22:34:19 -05:00
Matt Kiazyk
9527fb2961 fix: xcode 26 check for Apple Silicon installs 2025-09-15 22:08:49 -05:00
Matt Kiazyk
084bd8f4a1
Merge pull request #744 from leschlogl/feature/portuguese-translations
Fixes Brazilian Portuguese translations
2025-09-15 22:06:46 -05:00
Lucas Eduardo Schlögl
f93da1c502 small translation improvements 2025-09-10 14:43:20 +02:00
LiYanan2004
79ef494504 Add icon for "Installed Only" filter 2025-09-02 22:22:55 +08:00
LiYanan2004
cdf3675a5e Improve filtering UI 2025-09-02 14:40:35 +08:00
Jinyu Meng
5974a85800 Update zh_Hans translations.
# Conflicts:
#	Xcodes/Resources/Localizable.xcstrings
2025-08-29 11:45:06 +09:00
Matt Kiazyk
4ccb6e7f06
Merge pull request #741 from r1b2ns/feat/redirect-new-user-to-login
feat: improve user experience with a redirect window to sign-in
2025-08-27 08:37:19 -05:00
Rubens
22e4991f9a
chore: add new localized string in all languages 2025-08-27 10:32:20 -03:00
Lucas Eduardo Schlögl
059c51b5dd Fixes Brazilian Portuguese translations 2025-08-27 13:30:47 +02:00
Matt Kiazyk
00dcf3816e v3.0.0 2025-08-26 22:22:11 -05:00
Matt Kiazyk
08738d6912
Merge pull request #743 from XcodesOrg/XcodeArchitectures
Support Showing and Downloading Multiple Xcode Architectures.
2025-08-26 20:49:45 -05:00
Matt Kiazyk
472e36ed0f adjust names 2025-08-25 22:59:28 -05:00
Matt Kiazyk
bfb8c2cbb8 fix tests 2025-08-25 22:45:21 -05:00
Matt Kiazyk
a7b3f78813 clean up localizable strings 2025-08-25 22:37:51 -05:00
Matt Kiazyk
2e2b16e759 warning if users download silicon runtime without xcode 26 selected 2025-08-25 22:26:41 -05:00
Matt Kiazyk
0bc8e42a9b have both arch versions as the same version on list 2025-08-23 21:51:13 -06:00
Matt Kiazyk
ceae881d9a show installed xcodes specific with architectures 2025-08-23 20:11:57 -06:00
Matt Kiazyk
debc41f688 support downloading individual xcode architecture versions 2025-08-23 14:56:40 -06:00
Matt Kiazyk
4b9d86b22e Support showing multiple architectures 2025-08-23 13:35:49 -06:00
Rubens
bbec397613
feat: add button to redirect user when this try to install a xcode version without signed 2025-08-20 08:38:16 -03:00
Matt Kiazyk
a434d26921
Merge pull request #730 from XcodesOrg/matt/multipleArch
Support Xcode 26 multiple Architectures
2025-07-09 21:46:04 -05:00
Matt Kiazyk
78615418b5 update ci image 2025-07-09 21:40:33 -05:00
Matt Kiazyk
54cca28dbe clean up localizations 2025-07-09 21:36:23 -05:00
Matt Kiazyk
10ce2b8511 support filtering 2025-07-07 23:03:20 -05:00
Matt Kiazyk
14b2b3da10 support displaying multiple architectures in runtimes 2025-06-25 15:31:51 -05:00
Matt Kiazyk
f3f3bf0e4f remove cache 2025-06-09 23:04:33 -05:00
Matt Kiazyk
3621601658 update jekyll gem 2025-06-09 23:02:54 -05:00
Matt Kiazyk
9630a68224 update jekyll 2025-06-09 22:59:06 -05:00
Matt Kiazyk
227f42af37 v2.4.2b31 2025-06-09 22:21:44 -05:00
Matt Kiazyk
8fbf9eef8a
Merge pull request #678 from mahadshahib/patch-1
Update AppState.swift
2025-06-09 22:18:41 -05:00
Matt Kiazyk
b302365454
Merge pull request #655 from MultiColourPixel/main
Support FIDO2 authentication with devices that don’t have a PIN code
2025-06-09 22:10:12 -05:00
Mohammad ShahibZadeh
a61338804c
Update AppState.swift
Fix the issue where entering an email address with capital letters causes the login to fail.
2024-12-24 12:31:56 +03:30
Edgar Story
259ad0789a
Improve the support for PIN-less FIDO2 devices
- We now check if in the handling of two factor option, the option to be used is a SecurityKey. If so, check if a FIDO2 device is attached and if it needs a PIN.
- When a PIN is not required, we can just move straight onto assertation, the code for which will present the touch key UI.
- Otherwise we fallback to the original flow.
2024-11-12 09:20:38 +00:00
Edgar Story
cc03660576
Push the setting of authError to happen on MainActor
- The Xcode “Run Time Issue” breakpoint was being hit whenever an error was being set, complaining about this being set outside of the main thread.
2024-11-12 09:14:51 +00:00
Edgar Story
a43bf63aab
Add function to check if a FIDO2 device is even connected 2024-11-12 09:12:44 +00:00
Edgar Story
cfef2879b5
Add function to check if fido2 device needs a PIN 2024-11-12 09:12:21 +00:00
Edgar Story
36424a78e0
Make fido2 property a lazy var
- This object was being kept around after being created and as we need it in some other functions it made sense to make it lazy and keep it around that way.
- Arguably the FIDO2 instance could be removed after each time it’s been used, but as the FIDO2 class doesn’t have any state stored in it, it seems benign keeping it about for now.
2024-11-12 09:08:26 +00:00
Edgar Story
3d9cf73fc1
Require LibFido2Swift to be 0.1.4 at least
- This has the new functionality for checking if a device is attached.
2024-11-12 09:02:45 +00:00
Edgar Story
2dc1bcdcbb
Update requirement for LibFido2Swift library to be at least 0.1.3 2024-11-10 22:06:14 +00:00
Edgar Story
afa6dff0b9
Support FIDO authentication with devices that don’t have a PIN code set 2024-11-10 12:16:17 +00:00
58 changed files with 2880 additions and 272 deletions

View file

@ -14,13 +14,13 @@ jobs:
# If you're using actions/checkout@v4 you must set persist-credentials to false in most cases for the deployment to work correctly. # If you're using actions/checkout@v4 you must set persist-credentials to false in most cases for the deployment to work correctly.
persist-credentials: false persist-credentials: false
- name: Cache 📦 # - name: Cache 📦
uses: actions/cache@v4.1.1 # uses: actions/cache@v4.1.1
with: # with:
path: AppCast/vendor/bundle # path: AppCast/vendor/bundle
key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }} # key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }}
restore-keys: | # restore-keys: |
${{ runner.os }}-gems- # ${{ runner.os }}-gems-
- name: Setup Ruby, JRuby and TruffleRuby - name: Setup Ruby, JRuby and TruffleRuby
uses: ruby/setup-ruby@v1.197.0 uses: ruby/setup-ruby@v1.197.0

View file

@ -8,10 +8,10 @@ on:
jobs: jobs:
test: test:
runs-on: macos-14 runs-on: macos-15
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Run tests - name: Run tests
env: env:
DEVELOPER_DIR: /Applications/Xcode_16.app DEVELOPER_DIR: /Applications/Xcode_16.4.app
run: xcodebuild test -scheme Xcodes run: xcodebuild test -scheme Xcodes

View file

@ -8,7 +8,7 @@ source "https://rubygems.org"
# #
# This will help ensure the proper Jekyll version is running. # This will help ensure the proper Jekyll version is running.
# Happy Jekylling! # Happy Jekylling!
gem "jekyll", "~> 3.9.0" gem "jekyll", "~> 4.4.1"
gem "jekyll-github-metadata", group: :jekyll_plugins gem "jekyll-github-metadata", group: :jekyll_plugins

View file

@ -1,88 +1,110 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
addressable (2.8.0) addressable (2.8.7)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 7.0)
base64 (0.3.0)
bigdecimal (3.2.2)
colorator (1.1.0) colorator (1.1.0)
concurrent-ruby (1.1.7) concurrent-ruby (1.3.5)
em-websocket (0.5.2) csv (3.3.5)
em-websocket (0.5.3)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0)
eventmachine (1.2.7) eventmachine (1.2.7)
faraday (1.3.0) faraday (1.3.0)
faraday-net_http (~> 1.0) faraday-net_http (~> 1.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ruby2_keywords ruby2_keywords
faraday-net_http (1.0.1) faraday-net_http (1.0.1)
ffi (1.14.2) ffi (1.17.2)
ffi (1.17.2-x86_64-darwin)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
http_parser.rb (0.6.0) google-protobuf (4.31.1)
i18n (0.9.5) bigdecimal
rake (>= 13)
google-protobuf (4.31.1-x86_64-darwin)
bigdecimal
rake (>= 13)
http_parser.rb (0.8.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jekyll (3.9.0) jekyll (4.4.1)
addressable (~> 2.4) addressable (~> 2.4)
base64 (~> 0.2)
colorator (~> 1.0) colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5) em-websocket (~> 0.5)
i18n (~> 0.7) i18n (~> 1.0)
jekyll-sass-converter (~> 1.0) jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0) jekyll-watch (~> 2.0)
kramdown (>= 1.17, < 3) json (~> 2.6)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0) liquid (~> 4.0)
mercenary (~> 0.3.3) mercenary (~> 0.3, >= 0.3.6)
pathutil (~> 0.9) pathutil (~> 0.9)
rouge (>= 1.7, < 4) rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0) safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-github-metadata (2.13.0) jekyll-github-metadata (2.13.0)
jekyll (>= 3.4, < 5.0) jekyll (>= 3.4, < 5.0)
octokit (~> 4.0, != 4.4.0) octokit (~> 4.0, != 4.4.0)
jekyll-sass-converter (1.5.2) jekyll-sass-converter (3.1.0)
sass (~> 3.4) sass-embedded (~> 1.75)
jekyll-watch (2.2.1) jekyll-watch (2.2.1)
listen (~> 3.0) listen (~> 3.0)
kramdown (2.3.1) json (2.12.2)
rexml kramdown (2.5.1)
rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
liquid (4.0.3) liquid (4.0.4)
listen (3.4.1) listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6) mercenary (0.4.0)
multipart-post (2.1.1) multipart-post (2.1.1)
octokit (4.20.0) octokit (4.20.0)
faraday (>= 0.9) faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (4.0.6) public_suffix (6.0.2)
rb-fsevent (0.10.4) rake (13.3.0)
rb-inotify (0.10.1) rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0) ffi (~> 1.0)
rexml (3.2.5) rexml (3.4.1)
rouge (3.26.0) rouge (4.5.2)
ruby2_keywords (0.0.2) ruby2_keywords (0.0.2)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sass (3.7.4) sass-embedded (1.89.2)
sass-listen (~> 4.0.0) google-protobuf (~> 4.31)
sass-listen (4.0.0) rake (>= 13)
rb-fsevent (~> 0.9, >= 0.9.4) sass-embedded (1.89.2-x86_64-darwin)
rb-inotify (~> 0.9, >= 0.9.7) google-protobuf (~> 4.31)
sawyer (0.8.2) sawyer (0.8.2)
addressable (>= 2.3.5) addressable (>= 2.3.5)
faraday (> 0.8, < 2.0) faraday (> 0.8, < 2.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
thread_safe (0.3.6) thread_safe (0.3.6)
tzinfo (1.2.10) tzinfo (1.2.10)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2020.6) tzinfo-data (1.2020.6)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unicode-display_width (2.6.0)
wdm (0.1.1) wdm (0.1.1)
webrick (1.9.1)
PLATFORMS PLATFORMS
ruby ruby
x86_64-darwin-20 x86_64-darwin-20
DEPENDENCIES DEPENDENCIES
jekyll (~> 3.9.0) jekyll (~> 4.4.1)
jekyll-github-metadata jekyll-github-metadata
kramdown-parser-gfm kramdown-parser-gfm
tzinfo (~> 1.2) tzinfo (~> 1.2)
@ -90,4 +112,4 @@ DEPENDENCIES
wdm (~> 0.1.0) wdm (~> 0.1.0)
BUNDLED WITH BUNDLED WITH
2.2.5 2.6.9

BIN
IconDark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
IconMono.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,4 +1,4 @@
<h1><img src="icon.png" align="center" width=50 height=50 /> Xcodes.app</h1> <h1><img src="icon.png" align="center" width=50 height=50 /> <img src="IconDark.png" align="center" width=50 height=50 /> <img src="IconMono.png" align="center" width=50 height=50 /> Xcodes.app</h1>
The easiest way to install and switch between multiple versions of Xcode. The easiest way to install and switch between multiple versions of Xcode.
@ -23,6 +23,8 @@ XcodesApp is now part of the `XcodesOrg` - [read more here](nextstep.md)
- View release notes, OS compatibility, included SDKs and compilers from [Xcode Releases](https://xcodereleases.com). - View release notes, OS compatibility, included SDKs and compilers from [Xcode Releases](https://xcodereleases.com).
- Dark/Light Mode supported - Dark/Light Mode supported
- Security Key Authentication supported - Security Key Authentication supported
- Support installing Platforms/Runtimes
- Support installing Apple Silicon variants
## Platforms/Runtimes ## Platforms/Runtimes
@ -30,6 +32,10 @@ XcodesApp is now part of the `XcodesOrg` - [read more here](nextstep.md)
**Note: iOS 18+, tvOS 18+, watchOS 11+, visionOS 2+ requires that Xcode 16.1 Beta 3+ be installed and active.** **Note: iOS 18+, tvOS 18+, watchOS 11+, visionOS 2+ requires that Xcode 16.1 Beta 3+ be installed and active.**
## Apple Silicon Variants
As of Xcode 26, Apple provides Apple Silicon as well as Universal variants for Xcode versions as well as each runtime. Simply tap on which variant you want installed. To install the Apple Silicon runtime variant Xcode 26 is required to be active.
## Experiments ## Experiments
- Thanks to the wonderful work of [https://github.com/saagarjha/unxip](https://github.com/saagarjha/unxip), turn on the experiment to increase your unxipping time by up to 70%! More can be found on his repo, but bugs, high memory may occur if used. - Thanks to the wonderful work of [https://github.com/saagarjha/unxip](https://github.com/saagarjha/unxip), turn on the experiment to increase your unxipping time by up to 70%! More can be found on his repo, but bugs, high memory may occur if used.
@ -54,12 +60,14 @@ The following languages are supported because of the following community users!
|German 🇩🇪|[@drct](https://github.com/drct)|Dutch 🇳🇱|[@jfversluis](https://github/com/jfversluis)| |German 🇩🇪|[@drct](https://github.com/drct)|Dutch 🇳🇱|[@jfversluis](https://github/com/jfversluis)|
|Brazilian Portuguese 🇧🇷|[@brunomunizaf](https://github.com/brunomunizaf)|Polish 🇵🇱|[@jakex7](https://github.com/jakex7)| |Brazilian Portuguese 🇧🇷|[@brunomunizaf](https://github.com/brunomunizaf)|Polish 🇵🇱|[@jakex7](https://github.com/jakex7)|
|Catalan|[@ferranabello](https://github.com/ferranabello)|Greek 🇬🇷|[@alladinian](https://github.com/alladinian) |Catalan|[@ferranabello](https://github.com/ferranabello)|Greek 🇬🇷|[@alladinian](https://github.com/alladinian)
|Thai 🇹🇭|[@neetrath](https://github.com/neetrath)|
Want to add more languages? Simply create a PR with the updated strings file. Want to add more languages? Simply create a PR with the updated strings file.
## Installation ## Installation
v1.X - requires macOS 11 or newer v1.X - requires macOS 11 or newer
v2.X - requires macOS 13 v2.X - requires macOS 13
v3.X - requires macOS 13 - architecture variants and updated icon.
### Install with Homebrew ### Install with Homebrew
@ -84,7 +92,7 @@ Xcodes.app and CLI is updated, maintained with contributors like yourself. Even
## Development ## Development
You'll need macOS 13.5 Ventura and Xcode 15 in order to build and run Xcodes.app. You'll need macOS 15.6 Ventura and Xcode 26 in order to build and run Xcodes.app.
`Unxip` and `aria2` must be compiled as a universal binary `Unxip` and `aria2` must be compiled as a universal binary
``` ```
@ -99,7 +107,6 @@ You'll need macOS 13.5 Ventura and Xcode 15 in order to build and run Xcodes.app
lipo -archs unxip lipo -archs unxip
``` ```
Notable design decisions are recorded in [DECISIONS.md](./DECISIONS.md). The Apple authentication flow is described in [Apple.paw](./Apple.paw), which will allow you to play with the API endpoints that are involved using the [Paw](https://paw.cloud) app.
[`xcode-install`](https://github.com/xcpretty/xcode-install) and [fastlane/spaceship](https://github.com/fastlane/fastlane/tree/master/spaceship) both deserve credit for figuring out the hard parts of what makes this possible. [`xcode-install`](https://github.com/xcpretty/xcode-install) and [fastlane/spaceship](https://github.com/fastlane/fastlane/tree/master/spaceship) both deserve credit for figuring out the hard parts of what makes this possible.

View file

@ -0,0 +1,26 @@
#!/bin/sh
# Fix libfido2.framework structure for macOS validation
# If this script is not run, the build will fail because xcodebuild is expecting the library in a specific structure
FRAMEWORK_PATH="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Frameworks/libfido2.framework"
if [ -d "$FRAMEWORK_PATH" ] && [ -f "$FRAMEWORK_PATH/Info.plist" ] && [ ! -d "$FRAMEWORK_PATH/Versions" ]; then
echo "Fixing libfido2.framework bundle structure..."
# Create proper bundle structure
mkdir -p "$FRAMEWORK_PATH/Versions/A/Resources"
# Move files to proper locations
mv "$FRAMEWORK_PATH/Info.plist" "$FRAMEWORK_PATH/Versions/A/Resources/"
mv "$FRAMEWORK_PATH/libfido2" "$FRAMEWORK_PATH/Versions/A/"
if [ -f "$FRAMEWORK_PATH/LICENSE" ]; then
mv "$FRAMEWORK_PATH/LICENSE" "$FRAMEWORK_PATH/Versions/A/"
fi
# Create symbolic links
ln -sf A "$FRAMEWORK_PATH/Versions/Current"
ln -sf Versions/Current/libfido2 "$FRAMEWORK_PATH/libfido2"
ln -sf Versions/Current/Resources "$FRAMEWORK_PATH/Resources"
echo "libfido2.framework structure fixed"
fi

View file

@ -48,7 +48,6 @@
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; }; CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; };
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; }; CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; };
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; }; CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; };
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */ = {isa = PBXBuildFile; productRef = CA9FF86C25951C6E00E47BAF /* XCModel */; };
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; }; CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; };
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; }; CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; };
CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; }; CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; };
@ -115,6 +114,7 @@
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4AB325B7D3AF0064FE51 /* AdvancedPreferencePane.swift */; }; CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4AB325B7D3AF0064FE51 /* AdvancedPreferencePane.swift */; };
CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */; }; CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */; };
CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; }; CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; };
D93F95C12E0C8C1A00238FB5 /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93F95C02E0C8C1A00238FB5 /* TagView.swift */; };
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */ = {isa = PBXBuildFile; productRef = E689540225BE8C64000EBCEA /* DockProgress */; }; E689540325BE8C64000EBCEA /* DockProgress in Frameworks */ = {isa = PBXBuildFile; productRef = E689540225BE8C64000EBCEA /* DockProgress */; };
E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; }; E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; };
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; }; E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
@ -139,6 +139,7 @@
E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; }; E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; };
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; }; E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; }; E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; };
E8EEAD1D2E79174F00BE67E8 /* XcodesIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = E8EEAD1C2E79174F00BE67E8 /* XcodesIcon.icon */; };
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = E8F44A1D296B4CD7002D6592 /* Path */; }; E8F44A1E296B4CD7002D6592 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = E8F44A1D296B4CD7002D6592 /* Path */; };
E8FA00542B5B109800769CE0 /* com.xcodesorg.xcodesapp.Helper in Copy Helper */ = {isa = PBXBuildFile; fileRef = CA9FF8AE2595967A00E47BAF /* com.xcodesorg.xcodesapp.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; E8FA00542B5B109800769CE0 /* com.xcodesorg.xcodesapp.Helper in Copy Helper */ = {isa = PBXBuildFile; fileRef = CA9FF8AE2595967A00E47BAF /* com.xcodesorg.xcodesapp.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */ = {isa = PBXBuildFile; productRef = E8FD5726291EE4AC001E004C /* AsyncNetworkService */; }; E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */ = {isa = PBXBuildFile; productRef = E8FD5726291EE4AC001E004C /* AsyncNetworkService */; };
@ -321,6 +322,7 @@
CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesPreferencePane.swift; sourceTree = "<group>"; }; CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesPreferencePane.swift; sourceTree = "<group>"; };
CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListViewRow.swift; sourceTree = "<group>"; }; CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListViewRow.swift; sourceTree = "<group>"; };
CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = "<group>"; }; CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = "<group>"; };
D93F95C02E0C8C1A00238FB5 /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = "<group>"; };
E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; 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>"; }; E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = "<group>"; };
E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = "<group>"; }; E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = "<group>"; };
@ -340,6 +342,7 @@
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = "<group>"; }; E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = "<group>"; };
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; }; E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; }; E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
E8EEAD1C2E79174F00BE67E8 /* XcodesIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = XcodesIcon.icon; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -359,7 +362,6 @@
CABFA9E42592F08E00380FEE /* Version in Frameworks */, CABFA9E42592F08E00380FEE /* Version in Frameworks */,
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */, CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */, E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */, CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */, E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */, E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */,
@ -387,6 +389,7 @@
63EAA4E9259944340046AB8F /* Common */ = { 63EAA4E9259944340046AB8F /* Common */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D93F95C02E0C8C1A00238FB5 /* TagView.swift */,
CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */, CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */,
63EAA4EA259944450046AB8F /* ProgressButton.swift */, 63EAA4EA259944450046AB8F /* ProgressButton.swift */,
CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */, CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */,
@ -559,6 +562,7 @@
CABFAA1D2592F7F200380FEE /* Resources */ = { CABFAA1D2592F7F200380FEE /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E8EEAD1C2E79174F00BE67E8 /* XcodesIcon.icon */,
CAA858DA25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz */, CAA858DA25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz */,
CAA8588025A2B63A00ACF8C0 /* aria2c */, CAA8588025A2B63A00ACF8C0 /* aria2c */,
CAA8588A25A2B69300ACF8C0 /* aria2c.LICENSE */, CAA8588A25A2B69300ACF8C0 /* aria2c.LICENSE */,
@ -720,7 +724,6 @@
CABFA9ED2592F0CC00380FEE /* SwiftSoup */, CABFA9ED2592F0CC00380FEE /* SwiftSoup */,
CABFA9F72592F0F900380FEE /* KeychainAccess */, CABFA9F72592F0F900380FEE /* KeychainAccess */,
CABFA9FC2592F13300380FEE /* LegibleError */, CABFA9FC2592F13300380FEE /* LegibleError */,
CA9FF86C25951C6E00E47BAF /* XCModel */,
CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */, CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */,
E689540225BE8C64000EBCEA /* DockProgress */, E689540225BE8C64000EBCEA /* DockProgress */,
E8FD5726291EE4AC001E004C /* AsyncNetworkService */, E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
@ -803,6 +806,7 @@
nl, nl,
pl, pl,
ar, ar,
th,
); );
mainGroup = CAD2E7952449574E00113D76; mainGroup = CAD2E7952449574E00113D76;
packageReferences = ( packageReferences = (
@ -810,7 +814,6 @@
CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */, CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */,
CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */, CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */,
CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */, CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */,
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */,
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */, CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */,
CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */, CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */,
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */, E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
@ -838,6 +841,7 @@
files = ( files = (
CAD2E7A92449575000113D76 /* Preview Assets.xcassets in Resources */, CAD2E7A92449575000113D76 /* Preview Assets.xcassets in Resources */,
9DD4FFCB2B13EC1800C974F1 /* Localizable.xcstrings in Resources */, 9DD4FFCB2B13EC1800C974F1 /* Localizable.xcstrings in Resources */,
E8EEAD1D2E79174F00BE67E8 /* XcodesIcon.icon in Resources */,
CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */, CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */,
CAA858DB25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz in Resources */, CAA858DB25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz in Resources */,
CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */, CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */,
@ -932,6 +936,7 @@
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */, E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */,
B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */, B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */,
B0403CFE2ADA712C00137C09 /* InfoPaneControls.swift in Sources */, B0403CFE2ADA712C00137C09 /* InfoPaneControls.swift in Sources */,
D93F95C12E0C8C1A00238FB5 /* TagView.swift in Sources */,
53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */, 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */,
332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */, 332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */,
CA61A6E0259835580008926E /* Xcode.swift in Sources */, CA61A6E0259835580008926E /* Xcode.swift in Sources */,
@ -1092,13 +1097,14 @@
CA8FB636256E154800469DA5 /* Test */ = { CA8FB636256E154800469DA5 /* Test */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = XcodesIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY; CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/XcodesTest.entitlements; CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/XcodesTest.entitlements;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30; CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO; ENABLE_HARDENED_RUNTIME = NO;
@ -1110,7 +1116,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.4.1; MARKETING_VERSION = 3.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp; PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes; PRODUCT_NAME = Xcodes;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1344,13 +1350,14 @@
CAD2E7BD2449575100113D76 /* Debug */ = { CAD2E7BD2449575100113D76 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = XcodesIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY; CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements; CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30; CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY; DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@ -1362,7 +1369,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.4.1; MARKETING_VERSION = 3.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp; PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes; PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -1372,13 +1379,14 @@
CAD2E7BE2449575100113D76 /* Release */ = { CAD2E7BE2449575100113D76 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = XcodesIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY; CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements; CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 30; CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY; DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@ -1390,7 +1398,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.4.1; MARKETING_VERSION = 3.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp; PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes; PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -1490,15 +1498,7 @@
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */ = { 33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kinoroy/LibFido2Swift.git"; repositoryURL = "https://github.com/kinoroy/LibFido2Swift";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 0.1.0;
};
};
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/xcodereleases/data";
requirement = { requirement = {
branch = main; branch = main;
kind = branch; kind = branch;
@ -1599,11 +1599,6 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = LibFido2Swift; productName = LibFido2Swift;
}; };
CA9FF86C25951C6E00E47BAF /* XCModel */ = {
isa = XCSwiftPackageProductDependency;
package = CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */;
productName = XCModel;
};
CAA1CB2C255A5262003FD669 /* AppleAPI */ = { CAA1CB2C255A5262003FD669 /* AppleAPI */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = AppleAPI; productName = AppleAPI;

View file

@ -28,15 +28,6 @@
"version": "0.6.0" "version": "0.6.0"
} }
}, },
{
"package": "XcodeReleases",
"repositoryURL": "https://github.com/xcodereleases/data",
"state": {
"branch": "main",
"revision": "a43ad89e536d7a3da525fcc23fb182c37b756ecc",
"version": null
}
},
{ {
"package": "DockProgress", "package": "DockProgress",
"repositoryURL": "https://github.com/sindresorhus/DockProgress", "repositoryURL": "https://github.com/sindresorhus/DockProgress",
@ -75,11 +66,11 @@
}, },
{ {
"package": "LibFido2Swift", "package": "LibFido2Swift",
"repositoryURL": "https://github.com/kinoroy/LibFido2Swift.git", "repositoryURL": "https://github.com/kinoroy/LibFido2Swift",
"state": { "state": {
"branch": null, "branch": "main",
"revision": "b77e5c6451bea69d15615d6578936b11777d9a6c", "revision": "b87a93300c5b35307c9f26ae490963196bd927f1",
"version": "0.1.2" "version": null
} }
}, },
{ {

View file

@ -502,7 +502,7 @@ extension AppState {
self.allXcodes[index].installState = .installing(step) self.allXcodes[index].installState = .installing(step)
let xcode = self.allXcodes[index] let xcode = self.allXcodes[index]
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal) Current.notificationManager.scheduleNotification(title: xcode.version.major.description + "." + xcode.version.appleDescription, body: step.description, category: .normal)
} }
} }

View file

@ -61,7 +61,23 @@ extension AppState {
// only selected xcodes > 16.1 beta 3 can download runtimes via a xcodebuild -downloadPlatform version // 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 // only Runtimes coming from cryptexDiskImage can be downloaded via xcodebuild
if selectedXcode.version > Version(major: 16, minor: 0, patch: 0) { if selectedXcode.version > Version(major: 16, minor: 0, patch: 0) {
if runtime.architectures?.isAppleSilicon ?? false {
// Need Xcode 26 but with some RC/Beta's its simpler to just to greater > 25
if selectedXcode.version > Version(major: 25, minor: 0, patch: 0) {
downloadRuntimeViaXcodeBuild(runtime: runtime) 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: localizeString("Alert.Install.Error.Need.Xcode26"))
}
return
}
} else {
downloadRuntimeViaXcodeBuild(runtime: runtime)
}
} else { } else {
// not supported // not supported
Logger.appState.error("Trying to download a runtime we can't download") Logger.appState.error("Trying to download a runtime we can't download")
@ -77,7 +93,8 @@ extension AppState {
func downloadRuntimeViaXcodeBuild(runtime: DownloadableRuntime) { func downloadRuntimeViaXcodeBuild(runtime: DownloadableRuntime) {
let downloadRuntimeTask = Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate) let downloadRuntimeTask = Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate, runtime.architectures?.isAppleSilicon ?? false ? Architecture.arm64.rawValue : nil)
runtimePublishers[runtime.identifier] = Task { [weak self] in runtimePublishers[runtime.identifier] = Task { [weak self] in
guard let self = self else { return } guard let self = self else { return }
do { do {
@ -258,7 +275,10 @@ extension AppState {
} }
func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? { func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? {
return installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first return installedRuntimes.filter({
$0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate &&
((runtime.architectures ?? []).isEmpty ? true :
$0.runtimeInfo.supportedArchitectures == runtime.architectures )}).first
} }
func deleteRuntime(runtime: DownloadableRuntime) async throws { func deleteRuntime(runtime: DownloadableRuntime) async throws {

View file

@ -3,7 +3,6 @@ import Foundation
import Path import Path
import Version import Version
import SwiftSoup import SwiftSoup
import struct XCModel.Xcode
import AppleAPI import AppleAPI
import XcodesKit import XcodesKit
@ -211,7 +210,7 @@ extension AppState {
private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> { private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> {
Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!)) Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!))
.map(\.data) .map(\.data)
.decode(type: [XCModel.Xcode].self, decoder: JSONDecoder()) .decode(type: [XcodeRelease].self, decoder: JSONDecoder())
.map { xcReleasesXcodes in .map { xcReleasesXcodes in
let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in
guard guard
@ -233,7 +232,8 @@ extension AppState {
requiredMacOSVersion: xcReleasesXcode.requires, requiredMacOSVersion: xcReleasesXcode.requires,
releaseNotesURL: xcReleasesXcode.links?.notes?.url, releaseNotesURL: xcReleasesXcode.links?.notes?.url,
sdks: xcReleasesXcode.sdks, sdks: xcReleasesXcode.sdks,
compilers: xcReleasesXcode.compilers compilers: xcReleasesXcode.compilers,
architectures: xcReleasesXcode.architectures
) )
} }
return xcodes return xcodes

View file

@ -26,6 +26,7 @@ enum PreferenceKey: String {
case xcodeListCategory case xcodeListCategory
case allowedMajorVersions case allowedMajorVersions
case hideSupportXcodes case hideSupportXcodes
case xcodeListArchitectures
func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) } func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) }
} }
@ -146,7 +147,7 @@ class AppState: ObservableObject {
// MARK: - Publisher Cancellables // MARK: - Publisher Cancellables
var cancellables = Set<AnyCancellable>() var cancellables = Set<AnyCancellable>()
private var installationPublishers: [Version: AnyCancellable] = [:] private var installationPublishers: [XcodeID: AnyCancellable] = [:]
internal var runtimePublishers: [String: Task<(), any Error>] = [:] internal var runtimePublishers: [String: Task<(), any Error>] = [:]
private var selectPublisher: AnyCancellable? private var selectPublisher: AnyCancellable?
private var uninstallPublisher: AnyCancellable? private var uninstallPublisher: AnyCancellable?
@ -277,7 +278,7 @@ class AppState: ObservableObject {
func signIn(username: String, password: String) { func signIn(username: String, password: String) {
authError = nil authError = nil
signIn(username: username, password: password) signIn(username: username.lowercased(), password: password)
.sink( .sink(
receiveCompletion: { _ in }, receiveCompletion: { _ in },
receiveValue: { _ in } receiveValue: { _ in }
@ -305,12 +306,18 @@ class AppState: ObservableObject {
} }
func handleTwoFactorOption(_ option: TwoFactorOption, authOptions: AuthOptionsResponse, serviceKey: String, sessionID: String, scnt: String) { func handleTwoFactorOption(_ option: TwoFactorOption, authOptions: AuthOptionsResponse, serviceKey: String, sessionID: String, scnt: String) {
let sessionData = AppleSessionData(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt)
if option == .securityKey, fido2DeviceIsPresent() && !fido2DeviceNeedsPin() {
createAndSubmitSecurityKeyAssertationWithPinCode(nil, sessionData: sessionData, authOptions: authOptions)
} else {
self.presentedSheet = .twoFactor(.init( self.presentedSheet = .twoFactor(.init(
option: option, option: option,
authOptions: authOptions, authOptions: authOptions,
sessionData: AppleSessionData(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt) sessionData: sessionData
)) ))
} }
}
func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) { func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) {
isProcessingAuthRequest = true isProcessingAuthRequest = true
@ -355,9 +362,9 @@ class AppState: ObservableObject {
.store(in: &cancellables) .store(in: &cancellables)
} }
var fido2: FIDO2? private lazy var fido2 = FIDO2()
func createAndSubmitSecurityKeyAssertationWithPinCode(_ pinCode: String, sessionData: AppleSessionData, authOptions: AuthOptionsResponse) { func createAndSubmitSecurityKeyAssertationWithPinCode(_ pinCode: String?, sessionData: AppleSessionData, authOptions: AuthOptionsResponse) {
self.presentedSheet = .securityKeyTouchToConfirm self.presentedSheet = .securityKeyTouchToConfirm
guard let fsaChallenge = authOptions.fsaChallenge else { guard let fsaChallenge = authOptions.fsaChallenge else {
@ -379,8 +386,6 @@ class AppState: ObservableObject {
Task { Task {
do { do {
let fido2 = FIDO2()
self.fido2 = fido2
let response = try fido2.respondToChallenge(args: ChallengeArgs(rpId: rpId, validCredentials: validCreds, devPin: pinCode, challenge: challenge, origin: origin)) let response = try fido2.respondToChallenge(args: ChallengeArgs(rpId: rpId, validCredentials: validCreds, devPin: pinCode, challenge: challenge, origin: origin))
Task { @MainActor in Task { @MainActor in
@ -407,13 +412,31 @@ class AppState: ObservableObject {
// we don't have to show an error // we don't have to show an error
// because the sheet will already be dismissed // because the sheet will already be dismissed
} catch { } catch {
Task { @MainActor in
authError = error authError = error
} }
} }
} }
}
func fido2DeviceIsPresent() -> Bool {
fido2.hasDeviceAttached()
}
func fido2DeviceNeedsPin() -> Bool {
do {
return try fido2.deviceHasPin()
} catch {
Task { @MainActor in
authError = error
}
return true
}
}
func cancelSecurityKeyAssertationRequest() { func cancelSecurityKeyAssertationRequest() {
self.fido2?.cancel() self.fido2.cancel()
} }
private func handleAuthenticationFlowCompletion(_ completion: Subscribers.Completion<Error>) { private func handleAuthenticationFlowCompletion(_ completion: Subscribers.Completion<Error>) {
@ -501,8 +524,8 @@ class AppState: ObservableObject {
// MARK: - Install // MARK: - Install
func checkMinVersionAndInstall(id: Xcode.ID) { func checkMinVersionAndInstall(id: XcodeID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
// Check to see if users macOS is supported // Check to see if users macOS is supported
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion { if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
@ -528,8 +551,8 @@ class AppState: ObservableObject {
return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion) return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
} }
func install(id: Xcode.ID) { func install(id: XcodeID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
installationPublishers[id] = signInIfNeeded() installationPublishers[id] = signInIfNeeded()
.handleEvents( .handleEvents(
@ -588,7 +611,11 @@ class AppState: ObservableObject {
self.installationPublishers[id] = nil self.installationPublishers[id] = nil
if case let .failure(error) = completion { if case let .failure(error) = completion {
// Prevent setting the app state error if it is an invalid session, we will present the sign in view instead // Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
if error as? AuthenticationError != .invalidSession { if let error = error as? AuthenticationError, case .notAuthorized = error {
self.error = error
self.presentedAlert = .unauthenticated
} else if error as? AuthenticationError != .invalidSession {
self.error = error self.error = error
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription) self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
} }
@ -604,7 +631,7 @@ class AppState: ObservableObject {
/// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading /// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading
/// As of Nov 2022 this was returning a 403 forbidden /// As of Nov 2022 this was returning a 403 forbidden
func installWithoutLogin(id: Xcode.ID) { func installWithoutLogin(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2) installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
@ -627,7 +654,7 @@ class AppState: ObservableObject {
} }
func cancelInstall(id: Xcode.ID) { func cancelInstall(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
// Cancel the publisher // Cancel the publisher
installationPublishers[id] = nil installationPublishers[id] = nil
@ -745,7 +772,7 @@ class AppState: ObservableObject {
config.allowsRunningApplicationSubstitution = false config.allowsRunningApplicationSubstitution = false
NSWorkspace.shared.openApplication(at: path.url, configuration: config) NSWorkspace.shared.openApplication(at: path.url, configuration: config)
default: default:
Logger.appState.error("\(xcode.id) is not installed") Logger.appState.error("\(xcode.id.version) is not installed")
return return
} }
} }
@ -841,7 +868,7 @@ class AppState: ObservableObject {
// If build metadata matches exactly, replace the available version with the installed version. // If build metadata matches exactly, replace the available version with the installed version.
// This should handle Apple versions from /downloads/more which don't have build metadata identifiers. // This should handle Apple versions from /downloads/more which don't have build metadata identifiers.
if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) { if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
adjustedAvailableXcodes[index].version = installedXcode.version adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
} }
// If an installed version is the same as one that's listed online which doesn't have build metadata, replace it with the installed version // If an installed version is the same as one that's listed online which doesn't have build metadata, replace it with the installed version
// Not all prerelease Apple versions available online include build metadata // Not all prerelease Apple versions available online include build metadata
@ -849,7 +876,7 @@ class AppState: ObservableObject {
availableXcode.version.isEquivalent(to: installedXcode.version) && availableXcode.version.isEquivalent(to: installedXcode.version) &&
availableXcode.version.buildMetadataIdentifiers.isEmpty availableXcode.version.buildMetadataIdentifiers.isEmpty
}) { }) {
adjustedAvailableXcodes[index].version = installedXcode.version adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
} }
} }
} }
@ -866,14 +893,21 @@ class AppState: ObservableObject {
// Include this version if there's only one with this build identifier // Include this version if there's only one with this build identifier
return availableXcodesWithIdenticalBuildIdentifiers.count == 1 || return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
// Or if there's more than one with this build identifier and this is the release version // Or if there's more than one with this build identifier and this is the release version
availableXcodesWithIdenticalBuildIdentifiers.count > 1 && availableXcode.version.prereleaseIdentifiers.isEmpty
availableXcodesWithIdenticalBuildIdentifiers.count > 1 && (availableXcode.version.prereleaseIdentifiers.isEmpty || availableXcode.architectures?.count ?? 0 != 0)
} }
.map { availableXcode -> Xcode in .map { availableXcode -> Xcode in
let installedXcode = installedXcodes.first(where: { installedXcode in let installedXcode = installedXcodes.first(where: { installedXcode in
availableXcode.version.isEquivalent(to: installedXcode.version) // if we want to have only specific Xcodes as selected instead of the Architecture Equivalent.
// if availableXcode.architectures == nil {
// return availableXcode.version.isEquivalent(to: installedXcode.version)
// } else {
// return availableXcode.xcodeID == installedXcode.xcodeID
// }
return availableXcode.version.isEquivalent(to: installedXcode.version)
}) })
let identicalBuilds: [Version] let identicalBuilds: [XcodeID]
let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
.filter { .filter {
return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers && return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
@ -883,13 +917,13 @@ class AppState: ObservableObject {
} }
// If this is the release version, add the identical builds to it // If this is the release version, add the identical builds to it
if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty { if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
identicalBuilds = [availableXcode.version] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.version) identicalBuilds = [availableXcode.xcodeID] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.xcodeID)
} else { } else {
identicalBuilds = [] identicalBuilds = []
} }
// If the existing install state is "installing", keep it // If the existing install state is "installing", keep it
let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installState.installing }?.installState let existingXcodeInstallState = allXcodes.first { $0.id == availableXcode.xcodeID && $0.installState.installing }?.installState
// Otherwise, determine it from whether there's an installed Xcode // Otherwise, determine it from whether there's an installed Xcode
let defaultXcodeInstallState: XcodeInstallState = installedXcode.map { .installed($0.path) } ?? .notInstalled let defaultXcodeInstallState: XcodeInstallState = installedXcode.map { .installed($0.path) } ?? .notInstalled
@ -904,7 +938,8 @@ class AppState: ObservableObject {
releaseDate: availableXcode.releaseDate, releaseDate: availableXcode.releaseDate,
sdks: availableXcode.sdks, sdks: availableXcode.sdks,
compilers: availableXcode.compilers, compilers: availableXcode.compilers,
downloadFileSize: availableXcode.fileSize downloadFileSize: availableXcode.fileSize,
architectures: availableXcode.architectures
) )
} }

View file

@ -1,11 +1,12 @@
import Foundation import Foundation
import Version import Version
import struct XCModel.SDKs import XcodesKit
import struct XCModel.Compilers
/// A version of Xcode that's available for installation /// A version of Xcode that's available for installation
public struct AvailableXcode: Codable { public struct AvailableXcode: Codable {
public var version: Version public var version: Version {
return xcodeID.version
}
public let url: URL public let url: URL
public let filename: String public let filename: String
public let releaseDate: Date? public let releaseDate: Date?
@ -14,9 +15,11 @@ public struct AvailableXcode: Codable {
public let sdks: SDKs? public let sdks: SDKs?
public let compilers: Compilers? public let compilers: Compilers?
public let fileSize: Int64? public let fileSize: Int64?
public let architectures: [Architecture]?
public var downloadPath: String { public var downloadPath: String {
return url.path return url.path
} }
public var xcodeID: XcodeID
public init( public init(
version: Version, version: Version,
@ -27,9 +30,9 @@ public struct AvailableXcode: Codable {
releaseNotesURL: URL? = nil, releaseNotesURL: URL? = nil,
sdks: SDKs? = nil, sdks: SDKs? = nil,
compilers: Compilers? = nil, compilers: Compilers? = nil,
fileSize: Int64? = nil fileSize: Int64? = nil,
architectures: [Architecture]? = nil
) { ) {
self.version = version
self.url = url self.url = url
self.filename = filename self.filename = filename
self.releaseDate = releaseDate self.releaseDate = releaseDate
@ -38,5 +41,7 @@ public struct AvailableXcode: Codable {
self.sdks = sdks self.sdks = sdks
self.compilers = compilers self.compilers = compilers
self.fileSize = fileSize self.fileSize = fileSize
self.architectures = architectures
self.xcodeID = XcodeID(version: version, architectures: architectures)
} }
} }

View file

@ -196,7 +196,7 @@ public struct Shell {
return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"]) return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"])
} }
public var downloadRuntime: (String, String) -> AsyncThrowingStream<Progress, Error> = { platform, version in public var downloadRuntime: (String, String, String?) -> AsyncThrowingStream<Progress, Error> = { platform, version, architecture in
return AsyncThrowingStream<Progress, Error> { continuation in return AsyncThrowingStream<Progress, Error> { continuation in
Task { Task {
// Assume progress will not have data races, so we manually opt-out isolation checks. // Assume progress will not have data races, so we manually opt-out isolation checks.
@ -204,7 +204,7 @@ public struct Shell {
progress.kind = .file progress.kind = .file
progress.fileOperationKind = .downloading progress.fileOperationKind = .downloading
let process = Process() var process = Process()
let xcodeBuildPath = Path.root.usr.bin.join("xcodebuild").url let xcodeBuildPath = Path.root.usr.bin.join("xcodebuild").url
process.executableURL = xcodeBuildPath process.executableURL = xcodeBuildPath
@ -215,6 +215,13 @@ public struct Shell {
"\(version)" "\(version)"
] ]
if let architecture {
process.arguments?.append(contentsOf: [
"-architectureVariant",
"\(architecture)"
])
}
let stdOutPipe = Pipe() let stdOutPipe = Pipe()
process.standardOutput = stdOutPipe process.standardOutput = stdOutPipe
let stdErrPipe = Pipe() let stdErrPipe = Pipe()

View file

@ -1,12 +1,17 @@
import Foundation import Foundation
import Version import Version
import Path import Path
import XcodesKit
/// A version of Xcode that's already installed /// A version of Xcode that's already installed
public struct InstalledXcode: Equatable { public struct InstalledXcode: Equatable {
public let path: Path public let path: Path
public let xcodeID: XcodeID
/// Composed of the bundle short version from Info.plist and the product build version from version.plist /// Composed of the bundle short version from Info.plist and the product build version from version.plist
public let version: Version public var version: Version {
return xcodeID.version
}
public init?(path: Path) { public init?(path: Path) {
self.path = path self.path = path
@ -32,11 +37,20 @@ public struct InstalledXcode: Equatable {
prereleaseIdentifiers = ["beta"] prereleaseIdentifiers = ["beta"]
} }
self.version = Version(major: bundleVersion.major, let archsString = try? XcodesKit.Current.shell.archs(path.url.appending(path: "Contents/MacOS/Xcode")).out
let architectures = archsString?
.trimmingCharacters(in: .whitespacesAndNewlines)
.split(separator: " ")
.compactMap { Architecture(rawValue: String($0)) }
let version = Version(major: bundleVersion.major,
minor: bundleVersion.minor, minor: bundleVersion.minor,
patch: bundleVersion.patch, patch: bundleVersion.patch,
prereleaseIdentifiers: prereleaseIdentifiers, prereleaseIdentifiers: prereleaseIdentifiers,
buildMetadataIdentifiers: [versionPlist.productBuildVersion].compactMap { $0 }) buildMetadataIdentifiers: [versionPlist.productBuildVersion].compactMap { $0 })
self.xcodeID = XcodeID(version: version, architectures: architectures)
} }
} }

View file

@ -7,7 +7,6 @@
// //
import Foundation import Foundation
import struct XCModel.SDKs
import XcodesKit import XcodesKit
import SwiftUI import SwiftUI

View file

@ -1,11 +1,11 @@
import Version import Version
import struct XCModel.Xcode import XcodesKit
extension Version { extension Version {
/// Initialize a Version from an XcodeReleases' XCModel.Xcode /// Initialize a Version from an XcodeReleases' XCModel.Xcode
/// ///
/// This is kinda quick-and-dirty, and it would probably be better for us to adopt something closer to XCModel.Xcode under the hood and map the scraped data to it instead. /// This is kinda quick-and-dirty, and it would probably be better for us to adopt something closer to XCModel.Xcode under the hood and map the scraped data to it instead.
init?(xcReleasesXcode: XCModel.Xcode) { init?(xcReleasesXcode: XcodeRelease) {
var versionString = xcReleasesXcode.version.number ?? "" var versionString = xcReleasesXcode.version.number ?? ""
// Append trailing ".0" in order to get a fully-specified version string // Append trailing ".0" in order to get a fully-specified version string

View file

@ -1,14 +1,30 @@
import AppKit import AppKit
import Foundation import Foundation
import Version import Version
import struct XCModel.SDKs
import struct XCModel.Compilers
import Path import Path
import XcodesKit
public struct XcodeID: Codable, Hashable, Identifiable {
public let version: Version
public let architectures: [Architecture]?
public var id: String {
let architectures = architectures?.map { $0.rawValue}.joined() ?? ""
return version.description + architectures
}
public init(version: Version, architectures: [Architecture]? = nil) {
self.version = version
self.architectures = architectures
}
}
struct Xcode: Identifiable, CustomStringConvertible { struct Xcode: Identifiable, CustomStringConvertible {
let version: Version var version: Version {
return id.version
}
/// Other Xcode versions that have the same build identifier /// Other Xcode versions that have the same build identifier
let identicalBuilds: [Version] let identicalBuilds: [XcodeID]
var installState: XcodeInstallState var installState: XcodeInstallState
let selected: Bool let selected: Bool
let icon: NSImage? let icon: NSImage?
@ -18,10 +34,12 @@ struct Xcode: Identifiable, CustomStringConvertible {
let sdks: SDKs? let sdks: SDKs?
let compilers: Compilers? let compilers: Compilers?
let downloadFileSize: Int64? let downloadFileSize: Int64?
let architectures: [Architecture]?
let id: XcodeID
init( init(
version: Version, version: Version,
identicalBuilds: [Version] = [], identicalBuilds: [XcodeID] = [],
installState: XcodeInstallState, installState: XcodeInstallState,
selected: Bool, selected: Bool,
icon: NSImage?, icon: NSImage?,
@ -30,9 +48,9 @@ struct Xcode: Identifiable, CustomStringConvertible {
releaseDate: Date? = nil, releaseDate: Date? = nil,
sdks: SDKs? = nil, sdks: SDKs? = nil,
compilers: Compilers? = nil, compilers: Compilers? = nil,
downloadFileSize: Int64? = nil downloadFileSize: Int64? = nil,
architectures: [Architecture]? = nil
) { ) {
self.version = version
self.identicalBuilds = identicalBuilds self.identicalBuilds = identicalBuilds
self.installState = installState self.installState = installState
self.selected = selected self.selected = selected
@ -43,10 +61,10 @@ struct Xcode: Identifiable, CustomStringConvertible {
self.sdks = sdks self.sdks = sdks
self.compilers = compilers self.compilers = compilers
self.downloadFileSize = downloadFileSize self.downloadFileSize = downloadFileSize
self.architectures = architectures
self.id = XcodeID(version: version, architectures: architectures)
} }
var id: Version { version }
var description: String { var description: String {
version.appleDescription version.appleDescription
} }

View file

@ -0,0 +1,24 @@
//
// TagView.swift
// Xcodes
//
// Created by Matt Kiazyk on 2025-06-25.//
import SwiftUI
struct TagView: View {
let text: String
var body: some View {
Text(text)
.font(.system(size: 10))
.foregroundColor(.primary)
.padding(.horizontal, 5)
.padding(.vertical, 2)
.background(
Capsule()
.fill(.quaternary)
)
}
}

View file

@ -7,6 +7,7 @@ enum XcodesAlert: Identifiable {
case privilegedHelper case privilegedHelper
case generic(title: String, message: String) case generic(title: String, message: String)
case checkMinSupportedVersion(xcode: AvailableXcode, macOS: String) case checkMinSupportedVersion(xcode: AvailableXcode, macOS: String)
case unauthenticated
var id: Int { var id: Int {
switch self { switch self {
@ -15,6 +16,7 @@ enum XcodesAlert: Identifiable {
case .generic: return 3 case .generic: return 3
case .checkMinSupportedVersion: return 4 case .checkMinSupportedVersion: return 4
case .cancelRuntimeInstall: return 5 case .cancelRuntimeInstall: return 5
case .unauthenticated: return 6
} }
} }
} }

View file

@ -7,7 +7,7 @@
// //
import SwiftUI import SwiftUI
import struct XCModel.Compilers import XcodesKit
struct CompilersView: View { struct CompilersView: View {
let compilers: Compilers? let compilers: Compilers?

View file

@ -3,8 +3,6 @@ import XcodesKit
import Path import Path
import SwiftUI import SwiftUI
import Version import Version
import struct XCModel.Compilers
import struct XCModel.SDKs
struct InfoPane: View { struct InfoPane: View {
let xcode: Xcode let xcode: Xcode
@ -35,20 +33,14 @@ struct InfoPane: View {
} }
.xcodesBackground() .xcodesBackground()
VStack {
Text("Platforms")
.font(.title3)
.frame(maxWidth: .infinity, alignment: .leading)
PlatformsView(xcode: xcode) PlatformsView(xcode: xcode)
} }
.xcodesBackground()
}
.frame(minWidth: 380) .frame(minWidth: 380)
VStack(alignment: .leading) { VStack(alignment: .leading) {
ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL) ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL)
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
IdenticalBuildsView(builds: xcode.identicalBuilds) IdenticalBuildsView(builds: xcode.identicalBuilds.map { $0.version })
SDKandCompilers SDKandCompilers
} }
.frame(width: 200) .frame(width: 200)

View file

@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
import Version import Version
import XCModel import XcodesKit
import Path import Path
struct InstalledStateButtons: View { struct InstalledStateButtons: View {

View file

@ -11,7 +11,7 @@ import Version
struct NotInstalledStateButtons: View { struct NotInstalledStateButtons: View {
let downloadFileSizeString: String? let downloadFileSizeString: String?
let id: Version let id: XcodeID
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@ -20,7 +20,11 @@ struct NotInstalledStateButtons: View {
Button { Button {
appState.checkMinVersionAndInstall(id: id) appState.checkMinVersionAndInstall(id: id)
} label: { } label: {
Text("Install") .help("Install") if id.architectures?.isAppleSilicon ?? false {
Text("Install Apple Silicon").help("Install")
} else {
Text("Install Universal").help("Install")
}
} }
if let size = downloadFileSizeString { if let size = downloadFileSizeString {
@ -38,7 +42,7 @@ struct NotInstalledStateButtons: View {
#Preview { #Preview {
NotInstalledStateButtons( NotInstalledStateButtons(
downloadFileSizeString: "1,19 GB", downloadFileSizeString: "1,19 GB",
id: Version(major: 12, minor: 3, patch: 0) id: XcodeID(version: Version(major: 12, minor: 3, patch: 0), architectures: nil)
) )
.padding() .padding()
} }

View file

@ -11,6 +11,7 @@ import XcodesKit
struct PlatformsView: View { struct PlatformsView: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@AppStorage("selectedRuntimeArchitecture") private var selectedVariant: ArchitectureVariant = .universal
let xcode: Xcode let xcode: Xcode
@ -19,11 +20,39 @@ struct PlatformsView: View {
let builds = xcode.sdks?.allBuilds() let builds = xcode.sdks?.allBuilds()
let runtimes = builds?.flatMap { sdkBuild in let runtimes = builds?.flatMap { sdkBuild in
appState.downloadableRuntimes.filter { appState.downloadableRuntimes.filter {
$0.sdkBuildUpdate?.contains(sdkBuild) ?? false $0.sdkBuildUpdate?.contains(sdkBuild) ?? false &&
($0.architectures?.isEmpty ?? true ||
($0.architectures?.isUniversal ?? false && selectedVariant == .universal) ||
($0.architectures?.isAppleSilicon ?? false && selectedVariant == .appleSilicon)
)
} }
} }
ForEach(runtimes ?? [], id: \.simulatorVersion.buildUpdate) { runtime in let architectures = Set((runtimes ?? []).flatMap { $0.architectures ?? [] })
VStack {
HStack {
Text("Platforms")
.font(.title3)
.frame(maxWidth: .infinity, alignment: .leading)
if !architectures.isEmpty {
Spacer()
Picker("Architecture", selection: $selectedVariant) {
ForEach(ArchitectureVariant.allCases, id: \.self) { arch in
Label(arch.displayString, systemImage: arch.iconName)
.tag(arch)
}
.labelStyle(.trailingIcon)
}
.pickerStyle(.menu)
.menuStyle(.button)
.buttonStyle(.borderless)
.fixedSize()
.labelsHidden()
}
}
ForEach(runtimes ?? [], id: \.identifier) { runtime in
runtimeView(runtime: runtime) runtimeView(runtime: runtime)
.frame(minWidth: 200) .frame(minWidth: 200)
.padding() .padding()
@ -31,6 +60,10 @@ struct PlatformsView: View {
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
} }
} }
.xcodesBackground()
}
@ViewBuilder @ViewBuilder
func runtimeView(runtime: DownloadableRuntime) -> some View { func runtimeView(runtime: DownloadableRuntime) -> some View {
@ -39,6 +72,10 @@ struct PlatformsView: View {
runtime.icon() runtime.icon()
Text("\(runtime.visibleIdentifier)") Text("\(runtime.visibleIdentifier)")
.font(.headline) .font(.headline)
ForEach(runtime.architectures ?? [], id: \.self) { architecture in
TagView(text: architecture.displayString)
}
pathIfAvailable(xcode: xcode, runtime: runtime) pathIfAvailable(xcode: xcode, runtime: runtime)
if runtime.installState == .notInstalled { if runtime.installState == .notInstalled {

View file

@ -7,7 +7,7 @@
// //
import SwiftUI import SwiftUI
import struct XCModel.SDKs import XcodesKit
struct SDKsView: View { struct SDKsView: View {
let content: String let content: String

View file

@ -15,11 +15,12 @@ struct MainWindow: View {
// FB8979533 SceneStorage doesn't restore value after app is quit by user // FB8979533 SceneStorage doesn't restore value after app is quit by user
@AppStorage("isShowingInfoPane") private var isShowingInfoPane = false @AppStorage("isShowingInfoPane") private var isShowingInfoPane = false
@AppStorage("xcodeListCategory") private var category: XcodeListCategory = .all @AppStorage("xcodeListCategory") private var category: XcodeListCategory = .all
@AppStorage("xcodeListArchitecture") private var architecture: XcodeListArchitecture = .universal
@AppStorage("isInstalledOnly") private var isInstalledOnly = false @AppStorage("isInstalledOnly") private var isInstalledOnly = false
var body: some View { var body: some View {
NavigationSplitViewWrapper { NavigationSplitViewWrapper {
XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly) XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly, architecture: architecture)
.layoutPriority(1) .layoutPriority(1)
.alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in .alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in
Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)), Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)),
@ -31,7 +32,8 @@ struct MainWindow: View {
.mainToolbar( .mainToolbar(
category: $category, category: $category,
isInstalledOnly: $isInstalledOnly, isInstalledOnly: $isInstalledOnly,
isShowingInfoPane: $isShowingInfoPane isShowingInfoPane: $isShowingInfoPane,
architecture: $architecture
) )
} detail: { } detail: {
Group { Group {
@ -188,14 +190,28 @@ struct MainWindow: View {
action: { appState.presentedAlert = nil } action: { appState.presentedAlert = nil }
) )
) )
case .unauthenticated:
return Alert(
title: Text("Alert.Install.Error.Title"),
message: Text("Alert.Install.AuthError.Message"),
primaryButton: .default(
Text("OK"),
action: {
appState.presentedSheet = .signIn
}
),
secondaryButton: .cancel(
Text("Cancel")
)
)
case let .checkMinSupportedVersion(xcode, deviceVersion): case let .checkMinSupportedVersion(xcode, deviceVersion):
return Alert( return Alert(
title: Text("Alert.MinSupported.Title"), title: Text("Alert.MinSupported.Title"),
message: Text(String(format: localizeString("Alert.MinSupported.Message"), xcode.version.descriptionWithoutBuildMetadata, xcode.requiredMacOSVersion ?? "", deviceVersion)), message: Text(String(format: localizeString("Alert.MinSupported.Message"), xcode.xcodeID.version.descriptionWithoutBuildMetadata, xcode.requiredMacOSVersion ?? "", deviceVersion)),
primaryButton: .default( primaryButton: .default(
Text("Install"), Text("Install"),
action: { action: {
self.appState.install(id: xcode.version) self.appState.install(id: xcode.xcodeID)
} }
), ),
secondaryButton: .cancel(Text("Cancel")) secondaryButton: .cancel(Text("Cancel"))
@ -223,7 +239,7 @@ struct MainWindow_Previews: PreviewProvider {
MainWindow().environmentObject({ () -> AppState in MainWindow().environmentObject({ () -> AppState in
let a = AppState() let a = AppState()
a.allXcodes = [ a.allXcodes = [
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0+1234A")!), XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil), Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil), Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil), Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),

View file

@ -32,6 +32,9 @@ struct SignInSecurityKeyPinView: View {
Button("Cancel", action: { isPresented = false }) Button("Cancel", action: { isPresented = false })
.keyboardShortcut(.cancelAction) .keyboardShortcut(.cancelAction)
Spacer() Spacer()
Button("PIN not set", action: submitWithoutPinCode)
ProgressButton(isInProgress: appState.isProcessingAuthRequest, ProgressButton(isInProgress: appState.isProcessingAuthRequest,
action: submitPinCode) { action: submitPinCode) {
Text("Continue") Text("Continue")
@ -50,6 +53,10 @@ struct SignInSecurityKeyPinView: View {
func submitPinCode() { func submitPinCode() {
appState.createAndSubmitSecurityKeyAssertationWithPinCode(pin, sessionData: sessionData, authOptions: authOptions) appState.createAndSubmitSecurityKeyAssertationWithPinCode(pin, sessionData: sessionData, authOptions: authOptions)
} }
func submitWithoutPinCode() {
appState.createAndSubmitSecurityKeyAssertationWithPinCode(nil, sessionData: sessionData, authOptions: authOptions)
}
} }
#Preview { #Preview {

View file

@ -34,7 +34,7 @@ struct BottomStatusModifier: ViewModifier {
} }
} }
} }
Text("\(Bundle.main.shortVersion!) (\(Bundle.main.version!))") Text(verbatim: "\(Bundle.main.shortVersion!) (\(Bundle.main.version!))")
.font(.subheadline) .font(.subheadline)
} }
.frame(maxWidth: .infinity, maxHeight: 30, alignment: .leading) .frame(maxWidth: .infinity, maxHeight: 30, alignment: .leading)

View file

@ -5,6 +5,7 @@ struct MainToolbarModifier: ViewModifier {
@Binding var category: XcodeListCategory @Binding var category: XcodeListCategory
@Binding var isInstalledOnly: Bool @Binding var isInstalledOnly: Bool
@Binding var isShowingInfoPane: Bool @Binding var isShowingInfoPane: Bool
@Binding var architectures: XcodeListArchitecture
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
@ -21,42 +22,44 @@ struct MainToolbarModifier: ViewModifier {
} }
.keyboardShortcut(KeyEquivalent("r")) .keyboardShortcut(KeyEquivalent("r"))
.help("RefreshDescription") .help("RefreshDescription")
Spacer() Spacer()
Button(action: { let isFiltering = isInstalledOnly || category != .all || architectures != .universal
switch category { Menu("Filter", systemImage: "line.horizontal.3.decrease.circle") {
case .all: category = .release Section {
case .release: category = .beta Toggle("Installed Only", systemImage: "arrow.down.app", isOn: $isInstalledOnly) .labelStyle(.titleAndIcon)
case .beta: category = .all
} }
}) { .help("FilterInstalledDescription")
switch category {
case .all: Section {
Picker("Category", selection: $category) {
Label("All", systemImage: "line.horizontal.3.decrease.circle") Label("All", systemImage: "line.horizontal.3.decrease.circle")
case .release: .tag(XcodeListCategory.all)
Label("ReleaseOnly", systemImage: "line.horizontal.3.decrease.circle.fill") Label("ReleaseOnly", systemImage: "line.horizontal.3.decrease.circle.fill")
.labelStyle(.trailingIcon) .tag(XcodeListCategory.release)
.foregroundColor(.accentColor)
case .beta:
Label("BetaOnly", systemImage: "line.horizontal.3.decrease.circle.fill") Label("BetaOnly", systemImage: "line.horizontal.3.decrease.circle.fill")
.labelStyle(.trailingIcon) .tag(XcodeListCategory.beta)
.foregroundColor(.accentColor)
} }
} }
.help("FilterAvailableDescription") .help("FilterAvailableDescription")
.disabled(category.isManaged) .disabled(category.isManaged)
Button(action: { Section {
isInstalledOnly.toggle() Picker("Architecture", selection: $architectures) {
}) { Label("Universal", systemImage: "cpu.fill")
if isInstalledOnly { .tag(XcodeListArchitecture.universal)
Label("Filter", systemImage: "arrow.down.app.fill") Label("Apple Silicon", systemImage: "m4.button.horizontal")
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} else { .tag(XcodeListArchitecture.appleSilicon)
Label("Filter", systemImage: "arrow.down.app")
} }
.help("FilterArchitecturesDescription")
.disabled(architectures.isManaged)
} }
.help("FilterInstalledDescription") .labelStyle(.titleAndIcon)
}
.pickerStyle(.inline)
.symbolVariant(isFiltering ? .fill : .none)
} }
} }
} }
@ -65,13 +68,15 @@ extension View {
func mainToolbar( func mainToolbar(
category: Binding<XcodeListCategory>, category: Binding<XcodeListCategory>,
isInstalledOnly: Binding<Bool>, isInstalledOnly: Binding<Bool>,
isShowingInfoPane: Binding<Bool> isShowingInfoPane: Binding<Bool>,
architecture: Binding<XcodeListArchitecture>
) -> some View { ) -> some View {
modifier( modifier(
MainToolbarModifier( MainToolbarModifier(
category: category, category: category,
isInstalledOnly: isInstalledOnly, isInstalledOnly: isInstalledOnly,
isShowingInfoPane: isShowingInfoPane isShowingInfoPane: isShowingInfoPane,
architectures: architecture
) )
) )
} }

View file

@ -1,4 +1,5 @@
import Foundation import Foundation
import XcodesKit
enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible { enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible {
case all case all
@ -17,3 +18,19 @@ enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConverti
var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() } var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
} }
enum XcodeListArchitecture: String, CaseIterable, Identifiable, CustomStringConvertible {
case universal
case appleSilicon
var id: Self { self }
var description: String {
switch self {
case .universal: return localizeString("Universal")
case .appleSilicon: return localizeString("Apple Silicon")
}
}
var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
}

View file

@ -7,14 +7,16 @@ struct XcodeListView: View {
@Binding var selectedXcodeID: Xcode.ID? @Binding var selectedXcodeID: Xcode.ID?
private let searchText: String private let searchText: String
private let category: XcodeListCategory private let category: XcodeListCategory
private let architecture: XcodeListArchitecture
private let isInstalledOnly: Bool private let isInstalledOnly: Bool
@AppStorage(PreferenceKey.allowedMajorVersions.rawValue) private var allowedMajorVersions = Int.max @AppStorage(PreferenceKey.allowedMajorVersions.rawValue) private var allowedMajorVersions = Int.max
init(selectedXcodeID: Binding<Xcode.ID?>, searchText: String, category: XcodeListCategory, isInstalledOnly: Bool) { init(selectedXcodeID: Binding<Xcode.ID?>, searchText: String, category: XcodeListCategory, isInstalledOnly: Bool, architecture: XcodeListArchitecture) {
self._selectedXcodeID = selectedXcodeID self._selectedXcodeID = selectedXcodeID
self.searchText = searchText self.searchText = searchText
self.category = category self.category = category
self.isInstalledOnly = isInstalledOnly self.isInstalledOnly = isInstalledOnly
self.architecture = architecture
} }
var visibleXcodes: [Xcode] { var visibleXcodes: [Xcode] {
@ -28,6 +30,11 @@ struct XcodeListView: View {
xcodes = appState.allXcodes.filter { $0.version.isPrerelease } xcodes = appState.allXcodes.filter { $0.version.isPrerelease }
} }
if architecture == .appleSilicon {
xcodes = xcodes.filter { $0.architectures == [.arm64] }
}
let latestMajor = xcodes.sorted(\.version) let latestMajor = xcodes.sorted(\.version)
.filter { $0.version.isNotPrerelease } .filter { $0.version.isNotPrerelease }
.last? .last?
@ -64,6 +71,7 @@ struct XcodeListView: View {
PlatformsPocket() PlatformsPocket()
.padding(.horizontal) .padding(.horizontal)
.padding(.vertical, 8) .padding(.vertical, 8)
} }
} }
} }
@ -76,6 +84,20 @@ struct PlatformsPocket: View {
openWindow(id: "platforms") openWindow(id: "platforms")
} }
) { ) {
if #available(macOS 26.0, *) {
platformsLabel
.glassEffect(in: .rect(cornerRadius: 8, style: .continuous))
} else {
platformsLabel
.background(.quaternary.opacity(0.75))
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
}
}
.buttonStyle(.plain)
}
var platformsLabel: some View {
HStack(spacing: 5) { HStack(spacing: 5) {
Image(systemName: "square.3.layers.3d") Image(systemName: "square.3.layers.3d")
.font(.title3.weight(.medium)) .font(.title3.weight(.medium))
@ -85,21 +107,17 @@ struct PlatformsPocket: View {
.font(.body.weight(.medium)) .font(.body.weight(.medium))
.padding(.horizontal) .padding(.horizontal)
.padding(.vertical, 12) .padding(.vertical, 12)
.background(.quaternary.opacity(0.75))
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
}
.buttonStyle(.plain)
} }
} }
struct XcodeListView_Previews: PreviewProvider { struct XcodeListView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group { Group {
XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false) XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false, architecture: .appleSilicon)
.environmentObject({ () -> AppState in .environmentObject({ () -> AppState in
let a = AppState() let a = AppState()
a.allXcodes = [ a.allXcodes = [
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0+1234A")!), XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil), Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil), Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil), Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),

View file

@ -21,9 +21,17 @@ struct XcodeListViewRow: View {
.font(.subheadline) .font(.subheadline)
.foregroundColor(.secondary) .foregroundColor(.secondary)
.accessibility(label: Text("IdenticalBuilds")) .accessibility(label: Text("IdenticalBuilds"))
.accessibility(value: Text(xcode.identicalBuilds.map(\.appleDescription).joined(separator: ", "))) .accessibility(value: Text(xcode.identicalBuilds.map(\.version.appleDescription).joined(separator: ", ")))
.help("IdenticalBuilds.help") .help("IdenticalBuilds.help")
} }
if xcode.architectures?.isAppleSilicon ?? false {
Image(systemName: "m4.button.horizontal")
.font(.subheadline)
.foregroundColor(.secondary)
.accessibility(label: Text("Apple Silicon"))
.help("Apple Silicon")
}
} }
if case let .installed(path) = xcode.installState { if case let .installed(path) = xcode.installState {
@ -156,7 +164,7 @@ struct XcodeListViewRow_Previews: PreviewProvider {
) )
XcodeListViewRow( XcodeListViewRow(
xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
selected: false, selected: false,
appState: AppState() appState: AppState()
) )

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -1,4 +1,4 @@
{\rtf1\ansi\ansicpg1252\cocoartf2818 {\rtf1\ansi\ansicpg1252\cocoartf2865
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;} \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;}
{\colortbl;\red255\green255\blue255;} {\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;} {\*\expandedcolortbl;;}

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 KiB

View file

@ -0,0 +1,50 @@
{
"fill" : {
"automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000"
},
"groups" : [
{
"layers" : [
{
"glass" : false,
"hidden" : false,
"image-name-specializations" : [
{
"value" : "Light.png"
},
{
"appearance" : "dark",
"value" : "Dark.png"
},
{
"appearance" : "tinted",
"value" : "Mono.png"
}
],
"name" : "Dark",
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : false,
"translucency" : {
"enabled" : false,
"value" : 0.5
}
}
],
"supported-platforms" : {
"squares" : [
"macOS"
]
}
}

View file

@ -5,7 +5,7 @@ import PackageDescription
let package = Package( let package = Package(
name: "XcodesKit", name: "XcodesKit",
platforms: [.macOS(.v11)], platforms: [.macOS(.v13)],
products: [ products: [
// Products define the executables and libraries a package produces, and make them visible to other packages. // Products define the executables and libraries a package produces, and make them visible to other packages.
.library( .library(

View file

@ -37,8 +37,10 @@ public struct CoreSimulatorImage: Decodable, Identifiable, Equatable {
public struct CoreSimulatorRuntimeInfo: Decodable { public struct CoreSimulatorRuntimeInfo: Decodable {
public let build: String public let build: String
public let supportedArchitectures: [Architecture]?
public init(build: String) { public init(build: String, supportedArchitectures: [Architecture]? = nil) {
self.build = build self.build = build
self.supportedArchitectures = supportedArchitectures
} }
} }

View file

@ -12,6 +12,7 @@ public struct DownloadableRuntime: Codable, Identifiable, Hashable {
public let category: Category public let category: Category
public let simulatorVersion: SimulatorVersion public let simulatorVersion: SimulatorVersion
public let source: String? public let source: String?
public let architectures: [Architecture]?
public let dictionaryVersion: Int public let dictionaryVersion: Int
public let contentType: ContentType public let contentType: ContentType
public let platform: Platform public let platform: Platform
@ -49,10 +50,11 @@ public struct DownloadableRuntime: Codable, Identifiable, Hashable {
case name case name
case authentication case authentication
case sdkBuildUpdate case sdkBuildUpdate
case architectures
} }
var betaNumber: Int? { var betaNumber: Int? {
enum Regex { static let shared = try! NSRegularExpression(pattern: "b[0-9]+$") } enum Regex { static let shared = try! NSRegularExpression(pattern: "b[0-9]+") }
guard var foundString = Regex.shared.firstString(in: identifier) else { return nil } guard var foundString = Regex.shared.firstString(in: identifier) else { return nil }
foundString.removeFirst() foundString.removeFirst()
return Int(foundString)! return Int(foundString)!
@ -94,6 +96,7 @@ public struct SDKToSimulatorMapping: Codable {
public let sdkBuildUpdate: String public let sdkBuildUpdate: String
public let simulatorBuildUpdate: String public let simulatorBuildUpdate: String
public let sdkIdentifier: String public let sdkIdentifier: String
public let downloadableIdentifiers: [String]?
} }
extension DownloadableRuntime { extension DownloadableRuntime {
@ -167,6 +170,7 @@ public struct InstalledRuntime: Decodable {
let state: String let state: String
let version: String let version: String
let sizeBytes: Int? let sizeBytes: Int?
let supportedArchitectures: [Architecture]?
} }
extension InstalledRuntime { extension InstalledRuntime {

View file

@ -0,0 +1,71 @@
//
// Architecture.swift
// XcodesKit
//
// Created by Matt Kiazyk on 2025-08-23.
//
import Foundation
/// The name of an Architecture.
public enum Architecture: String, Codable, Equatable, Hashable, Identifiable, CaseIterable {
public var id: Self { self }
/// The Arm64 architecture (Apple Silicon)
case arm64 = "arm64"
/// The X86\_64 architecture (64-bit Intel)
case x86_64 = "x86_64"
public var displayString: String {
switch self {
case .arm64:
return "Apple Silicon"
case .x86_64:
return "Intel"
}
}
public var iconName: String {
switch self {
case .arm64:
return "m4.button.horizontal"
case .x86_64:
return "cpu.fill"
}
}
}
public enum ArchitectureVariant: String, Codable, Equatable, Hashable, Identifiable, CaseIterable {
public var id: Self { self }
case universal
case appleSilicon
public var displayString: String {
switch self {
case .appleSilicon:
return "Apple Silicon"
case .universal:
return "Universal"
}
}
public var iconName: String {
switch self {
case .appleSilicon:
return "m4.button.horizontal"
case .universal:
return "cpu.fill"
}
}
}
extension Array where Element == Architecture {
public var isAppleSilicon: Bool {
self == [.arm64]
}
public var isUniversal: Bool {
self.contains([.arm64, .x86_64])
}
}

View file

@ -0,0 +1,20 @@
//
// Checksums.swift
// xcodereleases
//
// Created by Xcode Releases on 9/17/20.
// Copyright © 2020 Xcode Releases. All rights reserved.
//
import Foundation
public struct Checksums: Codable {
public let sha1: String?
public init(sha1: String? = nil) {
self.sha1 = sha1
}
}

View file

@ -0,0 +1,33 @@
//
// Compiler.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct Compilers: Codable {
public let gcc: Array<XcodeVersion>?
public let llvm_gcc: Array<XcodeVersion>?
public let llvm: Array<XcodeVersion>?
public let clang: Array<XcodeVersion>?
public let swift: Array<XcodeVersion>?
public init(gcc: XcodeVersion? = nil, llvm_gcc: XcodeVersion? = nil, llvm: XcodeVersion? = nil, clang: XcodeVersion? = nil, swift: XcodeVersion? = nil) {
self.gcc = gcc.map { [$0] }
self.llvm_gcc = llvm_gcc.map { [$0] }
self.llvm = llvm.map { [$0] }
self.clang = clang.map { [$0] }
self.swift = swift.map { [$0] }
}
public init(gcc: Array<XcodeVersion>?, llvm_gcc: Array<XcodeVersion>?, llvm: Array<XcodeVersion>?, clang: Array<XcodeVersion>?, swift: Array<XcodeVersion>?) {
self.gcc = gcc?.isEmpty == true ? nil : gcc
self.llvm_gcc = llvm_gcc?.isEmpty == true ? nil : llvm_gcc
self.llvm = llvm?.isEmpty == true ? nil : llvm
self.clang = clang?.isEmpty == true ? nil : clang
self.swift = swift?.isEmpty == true ? nil : swift
}
}

View file

@ -0,0 +1,32 @@
//
// Link.swift
// xcodereleases
//
// Created by Xcode Releases on 4/5/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct Link: Codable {
public let url: URL
public let sizeMB: Int?
/// The platforms supported by this link, if applicable.
public var architectures: [Architecture]?
// public init(_ string: String, _ size: Int? = nil, _ architectures: [Architecture]? = nil) {
// self.url = URL(string: string)!
// self.sizeMB = size
// self.architectures = architectures
// }
}
public struct Links: Codable {
public let download: Link?
public let notes: Link?
public init(download: Link? = nil, notes: Link? = nil) {
self.download = download
self.notes = notes
}
}

View file

@ -0,0 +1,59 @@
//
// Release.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public enum Release: Codable {
public enum CodingKeys: String, CodingKey {
case gm, gmSeed, rc, beta, dp, release
}
public var isGM: Bool {
guard case .gm = self else { return false }
return true
}
case gm
case gmSeed(Int)
case rc(Int)
case beta(Int)
case dp(Int)
case release
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let _ = try container.decodeIfPresent(Bool.self, forKey: .gm) {
self = .gm
} else if let v = try container.decodeIfPresent(Int.self, forKey: .gmSeed) {
self = .gmSeed(v)
} else if let v = try container.decodeIfPresent(Int.self, forKey: .rc) {
self = .rc(v)
} else if let v = try container.decodeIfPresent(Int.self, forKey: .beta) {
self = .beta(v)
} else if let v = try container.decodeIfPresent(Int.self, forKey: .dp) {
self = .dp(v)
} else if let _ = try container.decodeIfPresent(Bool.self, forKey: .release) {
self = .release
} else {
fatalError("Unreachable")
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .gm: try container.encode(true, forKey: .gm)
case .gmSeed(let v): try container.encode(v, forKey: .gmSeed)
case .rc(let v): try container.encode(v, forKey: .rc)
case .beta(let v): try container.encode(v, forKey: .beta)
case .dp(let v): try container.encode(v, forKey: .dp)
case .release: try container.encode(true, forKey: .release)
}
}
}

View file

@ -0,0 +1,57 @@
//
// SDKs.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct SDKs: Codable {
public let macOS: Array<XcodeVersion>?
public let iOS: Array<XcodeVersion>?
public let watchOS: Array<XcodeVersion>?
public let tvOS: Array<XcodeVersion>?
public let visionOS: Array<XcodeVersion>?
public init(macOS: XcodeVersion? = nil, iOS: XcodeVersion? = nil, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS.map { [$0] }
self.iOS = iOS.map { [$0] }
self.watchOS = watchOS.map { [$0] }
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: XcodeVersion? = nil, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS.map { [$0] }
self.watchOS = watchOS.map { [$0] }
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS?.isEmpty == true ? nil : iOS
self.watchOS = watchOS.map { [$0] }
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: Array<XcodeVersion>?, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS?.isEmpty == true ? nil : iOS
self.watchOS = watchOS?.isEmpty == true ? nil : watchOS
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: Array<XcodeVersion>?, tvOS: Array<XcodeVersion>?, visionOS: Array<XcodeVersion>?) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS?.isEmpty == true ? nil : iOS
self.watchOS = watchOS?.isEmpty == true ? nil : watchOS
self.tvOS = tvOS?.isEmpty == true ? nil : tvOS
self.visionOS = visionOS?.isEmpty == true ? nil : visionOS
}
}

View file

@ -0,0 +1,35 @@
//
// Xcode.swift
// xcodereleases
//
// Created by Xcode Releases on 4/3/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct XcodeRelease: Codable {
public let name: String
public let version: XcodeVersion
public let date: YMD
public let requires: String
public let sdks: SDKs?
public let compilers: Compilers?
public let links: Links?
public let checksums: Checksums?
public var architectures: [Architecture]? {
return links.flatMap { $0.download?.architectures }
}
public init(name: String = "Xcode", version: XcodeVersion, date: (Int, Int, Int), requires: String, sdks: SDKs? = nil, compilers: Compilers? = nil, links: Links? = nil, checksums: Checksums? = nil) {
self.name = name
self.version = version;
self.date = YMD(date);
self.requires = requires;
self.sdks = sdks;
self.compilers = compilers
self.links = links
self.checksums = checksums
}
}

View file

@ -0,0 +1,24 @@
//
// Version.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public typealias V = XcodeVersion
public struct XcodeVersion: Codable {
public let number: String?
public let build: String?
public let release: Release
public init(_ build: String, _ number: String? = nil, _ release: Release = .release) {
self.number = number; self.build = build; self.release = release
}
public init(number: String, _ build: String? = nil, _ release: Release = .release) {
self.number = number; self.build = build; self.release = release
}
}

View file

@ -0,0 +1,23 @@
//
// YMD.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct YMD: Codable {
public let year: Int
public let month: Int
public let day: Int
public init(_ ymd: (Int, Int, Int)) {
self.year = ymd.0; self.month = ymd.1; self.day = ymd.2
}
public init(_ year: Int, _ month: Int, _ day: Int) {
self.year = year; self.month = month; self.day = day
}
}

View file

@ -6,10 +6,14 @@ public typealias ProcessOutput = (status: Int32, out: String, err: String)
extension Process { extension Process {
static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) async throws -> ProcessOutput { 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) return try run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
} }
static func run(_ executable: URL, workingDirectory: URL? = nil, input: String? = nil, _ arguments: [String]) async throws -> ProcessOutput { static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) throws -> ProcessOutput {
return try run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
}
static func run(_ executable: URL, workingDirectory: URL? = nil, input: String? = nil, _ arguments: [String]) throws -> ProcessOutput {
let process = Process() let process = Process()
process.currentDirectoryURL = workingDirectory ?? executable.deletingLastPathComponent() process.currentDirectoryURL = workingDirectory ?? executable.deletingLastPathComponent()

View file

@ -26,4 +26,8 @@ public struct XcodesShell {
public var deleteRuntime: (String) async throws -> ProcessOutput = { public var deleteRuntime: (String) async throws -> ProcessOutput = {
try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "delete", $0) try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "delete", $0)
} }
public var archs: (URL) throws -> ProcessOutput = {
try Process.run(Path.root.usr.bin.join("lipo"), "-archs", $0.path)
}
} }

View file

@ -149,7 +149,7 @@ class AppStateUpdateTests: XCTestCase {
) )
XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!]) XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!])
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[Version("12.4.0+12D4e")!, Version("12.4.0-RC+12D4e")!]]) XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[XcodeID(version: Version("12.4.0+12D4e")!), XcodeID(version: Version("12.4.0-RC+12D4e")!)]])
} }
func testIdenticalBuilds_DoNotMergeReleaseVersions() { func testIdenticalBuilds_DoNotMergeReleaseVersions() {
@ -234,7 +234,7 @@ class AppStateUpdateTests: XCTestCase {
) )
XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!]) XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0+12D4e")!])
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[Version("12.4.0+12D4e")!, Version("12.4.0-RC+12D4e")!]]) XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[XcodeID(version: Version("12.4.0+12D4e")!), XcodeID(version: Version("12.4.0-RC+12D4e")!)]])
} }
func testIdenticalBuilds_AppleDataSource_DoNotMergeVersionsWithoutBuildIdentifiers() { func testIdenticalBuilds_AppleDataSource_DoNotMergeVersionsWithoutBuildIdentifiers() {

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 22 KiB