├── README.md ├── homebrew └── xcode-cli.rb └── src ├── commands ├── xcode-cleanup ├── xcode-list ├── xcode-nuke ├── xcode-open ├── xcode-play ├── xcode-switch └── xcode-version └── xcode /README.md: -------------------------------------------------------------------------------- 1 | # Xcode CLI 2 | 3 | Helper scripts for working with Xcode from the command line. 4 | 5 | These were originally extracted from [my dotfiles] in the hopes that making 6 | them easier to share will encourage external contributions. 7 | 8 | [my dotfiles]: https://github.com/gfontenot/dotfiles 9 | 10 | ## Installation 11 | 12 | Via [homebrew]: 13 | 14 | ``` 15 | $ brew install gfontenot/formulae/xcode-cli 16 | ``` 17 | 18 | [homebrew]: https://brew.sh 19 | 20 | ## Usage 21 | 22 | ``` 23 | # Open a directory with Xcode 24 | ❯ xcode open 25 | 26 | # Open a scratch playground with Xcode 27 | # Requires a playground to be located at ~/.scratch.playground 28 | ❯ xcode play 29 | 30 | # List installed Xcode versions 31 | ❯ xcode list 32 | 33 | # Switch the currently selected Xcode version using fzf 34 | ❯ xcode switch 35 | 36 | # Print the current Xcode version 37 | ❯ xcode version 38 | ``` 39 | 40 | See the `--help` flags for the individual commands for more options. 41 | 42 | ## Extending 43 | 44 | This tool follows the same pattern set by `git` and similar commands. It 45 | searches the `PATH` for commands prefixed with `xcode-` and adds them as 46 | subcommands. For example, if we added the following to our path as an 47 | executable named `xcode-foo`: 48 | 49 | ```sh 50 | #!/bin/sh 51 | 52 | echo "Hello World" 53 | ``` 54 | 55 | Then we could run `xcode foo`: 56 | 57 | ``` 58 | $ xcode foo 59 | Hello World 60 | ``` 61 | -------------------------------------------------------------------------------- /homebrew/xcode-cli.rb: -------------------------------------------------------------------------------- 1 | require "formula" 2 | 3 | class XcodeCli < Formula 4 | homepage "https://github.com/gfontenot/xcode-cli" 5 | url "https://github.com/gfontenot/xcode-cli/releases/download/__VERSION__/xcode-cli.tar.gz" 6 | sha256 "__SHA__" 7 | 8 | depends_on "fzf" 9 | 10 | def install 11 | bin.install "src/xcode" 12 | bin.install Dir["src/commands/*"] 13 | end 14 | 15 | test do 16 | system "#{bin}/xcode", "--help" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /src/commands/xcode-cleanup: -------------------------------------------------------------------------------- 1 | info(){ 2 | printf "%s\\n" "$1" 3 | } 4 | 5 | usage='xcode-cleanup 6 | Delete unavailable devices 7 | 8 | usage: xcode-cleanup [-h | --help] 9 | 10 | -h | --help Print this usage documentation and exit 11 | ' 12 | 13 | case $1 in 14 | --help|-h) 15 | info "$usage" 16 | exit 0 17 | ;; 18 | esac 19 | 20 | xcrun simctl delete unavailable 21 | -------------------------------------------------------------------------------- /src/commands/xcode-list: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | error(){ 4 | red="\e[1;31m" 5 | reset="\e[0m" 6 | printf "${red}%s${reset}\n" "$1" >&2 7 | } 8 | 9 | info(){ 10 | printf "%s\n" "$1" 11 | } 12 | 13 | usage='xcode-list 14 | List installed Xcode versions 15 | 16 | usage: xcode-list [-h | --help] [-f | --full-path] 17 | 18 | -h | --help Print this usage documentation and exit 19 | 20 | -f | --full-path Print the full path of the Xcode versions. By default, 21 | only the name is printed 22 | ' 23 | 24 | full_path=0 25 | 26 | case $1 in 27 | -f|--full-path) 28 | full_path=1 29 | shift 30 | ;; 31 | esac 32 | 33 | case $1 in 34 | --f|--full-path) 35 | error "Invalid configuration, can't specify multiple path options" 36 | exit 1 37 | ;; 38 | --help|-h) 39 | info "$usage" 40 | exit 0 41 | ;; 42 | esac 43 | 44 | find_xcodes() { 45 | find /Applications -maxdepth 1 -name "Xcode*.app" "$@" 46 | } 47 | 48 | if [ "$full_path" -eq "1" ]; then 49 | find_xcodes 50 | else 51 | find_xcodes -exec basename {} \; | sed 's/\.app//' 52 | fi 53 | -------------------------------------------------------------------------------- /src/commands/xcode-nuke: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | info(){ 4 | printf "%s\\n" "$1" 5 | } 6 | 7 | usage='xcode-nuke 8 | Delete derived data and spm caches. 9 | 10 | usage: xcode-nuke [-h | --help] [-n | --no-spm] 11 | 12 | -h | --help Print this usage documentation and exit 13 | -n | --no-spm Do not clear spm caches 14 | ' 15 | 16 | spm=1 17 | case $1 in 18 | --help|-h) 19 | info "$usage" 20 | exit 0 21 | ;; 22 | 23 | --no-spm|-n) 24 | spm=0 25 | shift 26 | ;; 27 | esac 28 | 29 | info "rm ~/Library/Developer/Xcode/DerivedData" 30 | rm -rf ~/Library/Developer/Xcode/DerivedData 31 | 32 | if [ "$spm" -eq 1 ]; then 33 | info "rm ~/Library/Caches/org.swift.swiftpm" 34 | rm -rf ~/Library/Caches/org.swift.swiftpm 35 | 36 | info "rm ~/Library/org.swift.swiftpm" 37 | rm -rf ~/library/org.swift.swiftpm 38 | fi 39 | -------------------------------------------------------------------------------- /src/commands/xcode-open: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | error(){ 4 | red="\e[1;31m" 5 | reset="\e[0m" 6 | printf "${red}%s${reset}\n" "$1" >&2 7 | } 8 | 9 | info(){ 10 | printf "%s\n" "$1" 11 | } 12 | 13 | usage='xcode-open 14 | Open a directory with Xcode 15 | 16 | usage: xcode-open [-h | --help] [--current | --beta] [PATH] 17 | 18 | -h | --help Print this usage documentation and exit 19 | 20 | --current DEFAULT - Use the currently selected Xcode version. 21 | 22 | --beta Use the newest beta version of Xcode. 23 | 24 | PATH The path to the directory containing the Xcode project. 25 | If not provided, will default to the current directory. 26 | ' 27 | 28 | app=$(xcode-version --path) 29 | 30 | case $1 in 31 | --current) 32 | shift 33 | ;; 34 | --beta) 35 | app=$(xcode-list | sort -r | grep -m 1 'beta') 36 | shift 37 | ;; 38 | esac 39 | 40 | case $1 in 41 | --current|--beta) 42 | error "Invalid configuration, can't specify multiple Xcode applications" 43 | exit 1 44 | ;; 45 | --help|-h) 46 | info "$usage" 47 | exit 0 48 | ;; 49 | esac 50 | 51 | target=${1:-"."} 52 | 53 | open -a "$app" "$target" 54 | -------------------------------------------------------------------------------- /src/commands/xcode-play: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | error(){ 4 | red="\e[1;31m" 5 | reset="\e[0m" 6 | printf "${red}%s${reset}\n" "$1" >&2 7 | } 8 | 9 | info(){ 10 | printf "%s\n" "$1" 11 | } 12 | 13 | usage='xcode-play 14 | Open a scratch playground with Xcode 15 | Requires a playground to be located at ~/.scratch.playground 16 | 17 | usage: xcode-play [-h | --help] [--current | --beta] 18 | 19 | -h | --help Print this usage documentation and exit 20 | 21 | --current DEFAULT - Use the currently selected Xcode version. 22 | 23 | --beta Use the newest beta version of Xcode. 24 | ' 25 | 26 | version="--current" 27 | 28 | case $1 in 29 | --current) 30 | shift 31 | ;; 32 | --beta) 33 | version="--beta" 34 | shift 35 | ;; 36 | esac 37 | 38 | case $1 in 39 | --current|--beta) 40 | error "Invalid configuration, can't specify multiple Xcode applications" 41 | exit 1 42 | ;; 43 | --help|-h) 44 | info "$usage" 45 | exit 0 46 | ;; 47 | esac 48 | 49 | xcode-open "$version" "$HOME/.scratchpad.playground" 50 | -------------------------------------------------------------------------------- /src/commands/xcode-switch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | error(){ 4 | red="\e[1;31m" 5 | reset="\e[0m" 6 | printf "${red}%s${reset}\n" "$1" >&2 7 | } 8 | 9 | info(){ 10 | printf "%s\n" "$1" 11 | } 12 | 13 | usage='xcode-switch 14 | Switch the currently selected Xcode version 15 | 16 | usage: xcode-switch [-h | --help] [VERSION] 17 | 18 | -h | --help Print this usage documentation and exit 19 | 20 | VERSION The optional initial version to search for. Note that 21 | if there is no Xcode version matching this query, fzf 22 | will be displayed in an empty state. By default this is 23 | not set, and fzf will automatically select the version 24 | if there is only one other Xcode version installed. 25 | ' 26 | 27 | case $1 in 28 | --help|-h) 29 | info "$usage" 30 | exit 0 31 | ;; 32 | esac 33 | 34 | cmd_prefix="" 35 | if [ "$(id -u)" != 0 ]; then 36 | cmd_prefix="sudo" 37 | fi 38 | 39 | current_app=$(xcode-version --path) 40 | 41 | if other_apps=$(xcode-list --full-path | grep --invert-match "$current_app"); then 42 | echo "$other_apps" \ 43 | | fzf --query="$1" --select-1 --print0 \ 44 | | xargs -0 $cmd_prefix xcode-select --switch 45 | else 46 | error "No other Xcode versions found to switch to" 47 | fi 48 | 49 | info "Current Xcode is now '$(xcode-version --path)'" 50 | -------------------------------------------------------------------------------- /src/commands/xcode-version: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | error(){ 4 | red="\e[1;31m" 5 | reset="\e[0m" 6 | printf "${red}%s${reset}\n" "$1" >&2 7 | } 8 | 9 | info(){ 10 | printf "%s\n" "$1" 11 | } 12 | 13 | usage='xcode-version 14 | Print the current Xcode version 15 | 16 | usage: xcode-version [-h | --help] [[-n | --number] | [-p | --path]] 17 | 18 | -h | --help Print this usage documentation and exit 19 | 20 | -n | --number DEFAULT - Print the version number of the currently 21 | selected Xcode. 22 | 23 | -p | --path Print the path of the currently selected Xcode. 24 | ' 25 | 26 | print_path=0 27 | 28 | case $1 in 29 | -n|--number) 30 | shift 31 | ;; 32 | -p|--path) 33 | print_path=1 34 | shift 35 | ;; 36 | esac 37 | 38 | case $1 in 39 | -n|--number|-p|--path) 40 | error "Invalid configuration, can't specify multiple version types" 41 | exit 1 42 | ;; 43 | --help|-h) 44 | info "$usage" 45 | exit 0 46 | ;; 47 | esac 48 | 49 | if [ "$print_path" -eq "1" ]; then 50 | xcode-select --print-path | cut -d'/' -f-3 51 | else 52 | xcodebuild -version | grep "Xcode" 53 | fi 54 | -------------------------------------------------------------------------------- /src/xcode: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | error(){ 4 | red="\e[1;31m" 5 | reset="\e[0m" 6 | printf "${red}%s${reset}\n" "$1" >&2 7 | } 8 | 9 | info(){ 10 | printf "%s\n" "$1" 11 | } 12 | 13 | available_commands=$( \ 14 | echo "$PATH" \ 15 | | tr ":" "\n" \ 16 | | xargs -I {} sh -c 'test -d "$@" && echo "$@"' test {} \ 17 | | xargs -I {} find {} -maxdepth 1 -perm +111 -name "xcode-*" \ 18 | | xargs basename \ 19 | ) 20 | 21 | usage='xcode 22 | Perform Xcode actions from the command line 23 | 24 | usage: xcode [-h | --help] [COMMAND] 25 | 26 | -h | --help Print this usage documentation and exit 27 | 28 | COMMAND The command to run. This defaults to open. 29 | Run xcode COMMAND --help for help with a specific 30 | command. 31 | ' 32 | 33 | case $1 in 34 | --help|-h) 35 | info "$usage" 36 | info "Available commands:" 37 | info "$(echo "$available_commands" | sed 's/xcode-//')" 38 | exit 0 39 | ;; 40 | esac 41 | 42 | for cmd in $available_commands; do 43 | if [ "$cmd" = "xcode-$1" ]; then 44 | command=$cmd 45 | shift 46 | continue 47 | fi 48 | done 49 | 50 | if [ -z "$command" ]; then 51 | error "Unknown command: $1" 52 | else 53 | $command "$@" 54 | fi 55 | --------------------------------------------------------------------------------