Compare commits
56 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a0d3353b9 | ||
|
|
da8d22c6d3 | ||
|
|
4f9b7f8f6a | ||
|
|
f34e291433 | ||
|
|
789eedcdb7 | ||
|
|
f9262d9a05 | ||
|
|
4776af75ee | ||
|
|
cab5533b49 | ||
|
|
fc50db1cee | ||
|
|
576585b7de | ||
|
|
6cf9647840 | ||
|
|
90dccd8180 | ||
|
|
372d5b1f4e | ||
|
|
4063796833 | ||
|
|
be2eea0532 | ||
|
|
a6dec75603 | ||
|
|
9527fb2961 | ||
|
|
084bd8f4a1 | ||
|
|
f93da1c502 | ||
|
|
79ef494504 | ||
|
|
cdf3675a5e | ||
|
|
5974a85800 | ||
|
|
4ccb6e7f06 | ||
|
|
22e4991f9a | ||
|
|
059c51b5dd | ||
|
|
00dcf3816e | ||
|
|
08738d6912 | ||
|
|
472e36ed0f | ||
|
|
bfb8c2cbb8 | ||
|
|
a7b3f78813 | ||
|
|
2e2b16e759 | ||
|
|
0bc8e42a9b | ||
|
|
ceae881d9a | ||
|
|
debc41f688 | ||
|
|
4b9d86b22e | ||
|
|
bbec397613 | ||
|
|
a434d26921 | ||
|
|
78615418b5 | ||
|
|
54cca28dbe | ||
|
|
10ce2b8511 | ||
|
|
14b2b3da10 | ||
|
|
f3f3bf0e4f | ||
|
|
3621601658 | ||
|
|
9630a68224 | ||
|
|
227f42af37 | ||
|
|
8fbf9eef8a | ||
|
|
b302365454 | ||
|
|
a61338804c | ||
|
|
259ad0789a | ||
|
|
cc03660576 | ||
|
|
a43bf63aab | ||
|
|
cfef2879b5 | ||
|
|
36424a78e0 | ||
|
|
3d9cf73fc1 | ||
|
|
2dc1bcdcbb | ||
|
|
afa6dff0b9 |
14
.github/workflows/appcast.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
4
.github/workflows/ci.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
After Width: | Height: | Size: 16 KiB |
BIN
IconMono.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
13
README.md
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
26
Scripts/fix_libfido2_framework.sh
Executable 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
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import struct XCModel.SDKs
|
|
||||||
import XcodesKit
|
import XcodesKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
Xcodes/Frontend/Common/TagView.swift
Normal 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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
6
Xcodes/Resources/Assets.xcassets/Icons/Contents.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;;}
|
||||||
|
|
|
||||||
BIN
Xcodes/Resources/XcodesIcon.icon/Assets/Dark.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
Xcodes/Resources/XcodesIcon.icon/Assets/Light.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
Xcodes/Resources/XcodesIcon.icon/Assets/Mono.png
Normal file
|
After Width: | Height: | Size: 716 KiB |
50
Xcodes/Resources/XcodesIcon.icon/icon.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 22 KiB |