├── .gitignore ├── LICENSE ├── README.MD └── makeXcodePluginsWork /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Nick Brook 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ## What is this? 2 | 3 | This is a bash script that can unsign the Xcode binary and update the Xcode compatibility UUID in plugins. 4 | 5 | Xcode disabled plugins in Xcode 8 in favour of its extensions API. Unfortunately this is very limited at this point. Unsigning Xcode allows plugins to run again. 6 | 7 | Also, each version of Xcode has it's own UUID which needs to be listed inside the plugin to ensure compatibility. It seems lots of plugins work just fine with Xcode 8, but authors are unlikely to update them with the new UUID as Xcode stops them loading. This script can also add in that UUID. 8 | 9 | *Use at your own risk.* Although a copy of the binary is left inside your Xcode.app at `Xcode.app/Contents/MacOS/Xcode.signed`, you are using this at your own risk. I've tested it, it works for me. Your results may vary. 10 | 11 | Uses the unsign project at [https://github.com/steakknife/unsign](https://github.com/steakknife/unsign) 12 | 13 | This doesn't fix the problem long term! If you like plugins, Be sure to file a radar at [https://bugreport.apple.com/](https://bugreport.apple.com/) detailing which ones, and hopefully Apple will add the necessary APIs to its extension capability in future. 14 | 15 | ## Usage 16 | 17 | ``` 18 | $ curl -O https://raw.githubusercontent.com/nrbrook/MakeXcodePluginsWork/master/makeXcodePluginsWork 19 | $ chmod +x makeXcodePluginsWork 20 | $ ./makeXcodePluginsWork 21 | ``` 22 | -------------------------------------------------------------------------------- /makeXcodePluginsWork: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | xcodePath="/Applications/Xcode.app" 4 | binaryPath="$xcodePath/Contents/MacOS/Xcode" 5 | pluginsDir="$HOME/Library/Application Support/Developer/Shared/Xcode/Plug-ins" 6 | altPluginsDir="$HOME/Library/Developer/Xcode/Plug-ins" 7 | tmpRepoPath="/tmp/unsign-updatePlugins" 8 | 9 | bold=$(tput bold) 10 | normal=$(tput sgr0) 11 | 12 | if pgrep "Xcode" > /dev/null 13 | then 14 | printf "${bold}Xcode is Running, please quit now...${normal}\n" 15 | while pgrep "Xcode" > /dev/null 16 | do 17 | sleep 1 18 | done 19 | fi 20 | 21 | function signedState { 22 | if [ -f "$binaryPath.unsigned" ]; then 23 | return 1 # binary is currently signed 24 | elif [ -f "$binaryPath.signed" ]; then 25 | return 2 # binary is currently unsigned 26 | fi 27 | return 0 # binary is currently signed 28 | } 29 | 30 | function restoreUnsigned { 31 | sudo mv "$binaryPath" "$binaryPath.signed" 32 | sudo mv "$binaryPath.unsigned" "$binaryPath" 33 | printf "Restored unsigned binary\n" 34 | } 35 | 36 | function restoreSigned { 37 | sudo mv "$binaryPath" "$binaryPath.unsigned" 38 | sudo mv "$binaryPath.signed" "$binaryPath" 39 | printf "Restored signed binary\n" 40 | } 41 | 42 | function unsign { 43 | rm -rf "$tmpRepoPath" 44 | mkdir "$tmpRepoPath" 45 | cd $tmpRepoPath 46 | printf "Cloning unsign repository\n" 47 | git clone git@github.com:steakknife/unsign.git 48 | cd unsign 49 | make 50 | 51 | printf "Unsigning (admin permissions required)\n" 52 | sudo ./unsign "$binaryPath" 53 | status=$? 54 | if [ $status -eq 0 ]; then 55 | sudo mv "$binaryPath" "$binaryPath.signed" 56 | sudo mv "$binaryPath.unsigned" "$binaryPath" 57 | printf "Done!\n" 58 | else 59 | printf "Failed.\n" 60 | fi 61 | rm -rf "$tmpRepoPath" 62 | } 63 | 64 | function resetWarning { 65 | version=$(defaults read /Applications/Xcode.app/Contents/version CFBundleShortVersionString) 66 | defaults delete com.apple.dt.Xcode "DVTPlugInManagerNonApplePlugIns-Xcode-$version" > /dev/null 2>&1 67 | printf "\nReset!\n" 68 | } 69 | 70 | UUID=$(defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID) 71 | 72 | function containsElement { 73 | local e 74 | for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done 75 | return 1 76 | } 77 | 78 | function checkIfLatestUUID { 79 | existingUUIDs=($(defaults read "$1/Contents/Info" DVTPlugInCompatibilityUUIDs | sed "s/[,()\"]*//g")) 80 | containsElement "$UUID" "${existingUUIDs[@]}" 81 | return $? 82 | } 83 | 84 | function upgradePlugin { 85 | checkIfLatestUUID "$1" 86 | if [ $? -eq 0 ]; then 87 | return 1 88 | fi 89 | defaults write "$1/Contents/Info" DVTPlugInCompatibilityUUIDs -array-add $UUID 90 | return 0 91 | } 92 | 93 | function upgradePlugins { 94 | found=0 95 | 96 | function findPlugins { 97 | plugins=() 98 | while read -d '' -r; do 99 | ((found++)) 100 | checkIfLatestUUID "$REPLY" 101 | if [ $? -eq 1 ]; then 102 | plugins+=( "$REPLY" ) 103 | fi 104 | done < <(find "$1" -name "*.xcplugin" -maxdepth 1 -print0 2>/dev/null) 105 | } 106 | 107 | findPlugins "$pluginsDir" 108 | options=("${plugins[@]}") 109 | findPlugins "$altPluginsDir" 110 | options=( "${options[@]}" "${plugins[@]}" ) 111 | 112 | printf "\n" 113 | 114 | if [ ${#options[@]} -eq 0 ]; then 115 | printf "No plugins need upgrading! $found plugins found already have latest UUID.\n" 116 | break 117 | else 118 | ((upgraded=$found-${#options[@]})) 119 | if [ $upgraded -gt 0 ]; then 120 | printf "$upgraded plugins already have latest UUID.\n" 121 | fi 122 | fi 123 | 124 | 125 | 126 | if [ $1 == "update" ]; then 127 | printf "\n${bold}" 128 | PS3="${bold}Please select a plugin to update: ${normal}" 129 | select opt in "${options[@]}" "Back" "Quit" ; do 130 | if (( REPLY == 1 + ${#options[@]} )) ; then 131 | break 132 | 133 | elif (( REPLY == 2 + ${#options[@]} )) ; then 134 | exit 135 | 136 | elif (( REPLY > 0 && REPLY <= ${#options[@]} )) ; then 137 | upgradePlugin "$opt" 138 | if [ $? -eq 0 ]; then 139 | printf "Upgraded $opt" 140 | else 141 | printf "Already upgraded this plugin" 142 | fi 143 | else 144 | printf "Invalid option. Try another one." 145 | fi 146 | printf "\n" 147 | done 148 | else 149 | for i in "${options[@]}"; do 150 | upgradePlugin "$i" 151 | if [ $? -eq 0 ]; then 152 | printf "Upgraded $i" 153 | fi 154 | done 155 | fi 156 | } 157 | 158 | while true 159 | do 160 | 161 | declare -a mainOptTests=("all" "unsign" "restoreUnsigned" "restoreSigned" "resetWarning" "update" "updateAll" "quit") 162 | declare -a mainOpts=("Just make it work! (unsign, reset warning, update plugins)" "Unsign Xcode" "Restore unsigned Xcode" "Restore signed Xcode" "Reset 'Load Bundles' Xcode warning" "Update specific plugins" "Update all plugins" "Quit") 163 | 164 | signedState 165 | SS=$? 166 | case $SS in 167 | 0) # fresh xcode, currently signed, remove both 'restore' options 168 | unset mainOpts[2] 169 | unset mainOptTests[2] 170 | unset mainOpts[3] 171 | unset mainOptTests[3] 172 | ;; 173 | 1) # unsigned binary present, currently signed remove 'restore signed' 174 | unset mainOptTests[3] 175 | unset mainOpts[3] 176 | ;; 177 | 2) # signed binary present, currently unsigned, remove 'unsign' and 'restore unsigned' 178 | unset mainOpts[1] 179 | unset mainOptTests[1] 180 | unset mainOpts[2] 181 | unset mainOptTests[2] 182 | ;; 183 | esac 184 | 185 | mainOptTests=( "${mainOptTests[@]}" ) 186 | mainOpts=( "${mainOpts[@]}" ) 187 | 188 | printf "\n${bold}" 189 | PS3="${bold}Which do you want to do?: ${normal}" 190 | select opt in "${mainOpts[@]}" ; do 191 | printf "${normal}" 192 | testRes=${mainOptTests[REPLY-1]} 193 | case $testRes in 194 | "all" ) 195 | signedState 196 | case $SS in 197 | 0) 198 | unsign 199 | ;; 200 | 1) 201 | restoreUnsigned 202 | ;; 203 | esac 204 | resetWarning 205 | upgradePlugins "updateAll" 206 | break 207 | ;; 208 | "unsign" ) 209 | unsign 210 | break 211 | ;; 212 | "restoreUnsigned" ) 213 | restoreUnsigned 214 | break 215 | ;; 216 | "restoreSigned" ) 217 | restoreSigned 218 | break 219 | ;; 220 | "resetWarning" ) 221 | resetWarning 222 | break 223 | ;; 224 | "update" | "updateAll" ) 225 | upgradePlugins $testRes 226 | break 227 | ;; 228 | "quit" ) 229 | exit 230 | ;; 231 | esac 232 | done 233 | done 234 | 235 | 236 | --------------------------------------------------------------------------------