├── .gitignore ├── LICENSE ├── README.md ├── Sample.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Sample ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── SampleTests ├── Info.plist └── SampleTests.m ├── SampleUITests ├── Info.plist └── SampleUITests.m ├── debug.json ├── mod_pbxproj.py ├── release.json └── xcode_auto_configurator.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | 28 | *.pyc 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jiakai Lian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XcodeProjectAutoConfigurator 2 | 3 | ---------- 4 | A simple Python script used to automatically configure the project.pbxproj file. 5 | This script reads json config files and then apply these configurations to the project targets. 6 | 7 | ## How To Use the project ## 8 | 9 | Clone this git repository or simply download **mod_pbxproj.py** and **xcode_auto_configurator.py**. Please download **debug.json** and **release.json** if you want. These two files are my own preferences for iOS projects. 10 | 11 | **Command:** 12 | ``` 13 | python xocde_auto_configurator.py pbxprojPath [*.json...] 14 | ``` 15 | **Apply default configurations to the Sample project:** 16 | 17 | ``` 18 | python xcode_auto_configurator.py Sample.xcodeproj/project.pbxproj 19 | ``` 20 | 21 | This command applies debug.json and release.json to the project's debug and release targets. 22 | 23 | You also can explicitly indicate json files. 24 | 25 | ``` 26 | python xcode_auto_configurator.py Sample.xcodeproj/project.pbxproj debug.json release.json 27 | ``` 28 | 29 | 30 | **Auto Backup:** 31 | 32 | If anything wrong, you always can find your backups inside the xcodeproj folder. 33 | 34 | **References:** 35 | 36 | [mod-pbxproj](https://github.com/kronenthaler/mod-pbxproj) 37 | -------------------------------------------------------------------------------- /Sample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3E5A541A1BB425C500AFC405 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E5A54191BB425C500AFC405 /* main.m */; }; 11 | 3E5A541D1BB425C500AFC405 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E5A541C1BB425C500AFC405 /* AppDelegate.m */; }; 12 | 3E5A54201BB425C500AFC405 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E5A541F1BB425C500AFC405 /* ViewController.m */; }; 13 | 3E5A54231BB425C500AFC405 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3E5A54211BB425C500AFC405 /* Main.storyboard */; }; 14 | 3E5A54251BB425C500AFC405 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3E5A54241BB425C500AFC405 /* Assets.xcassets */; }; 15 | 3E5A54281BB425C500AFC405 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3E5A54261BB425C500AFC405 /* LaunchScreen.storyboard */; }; 16 | 3E5A54331BB425C500AFC405 /* SampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E5A54321BB425C500AFC405 /* SampleTests.m */; }; 17 | 3E5A543E1BB425C500AFC405 /* SampleUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E5A543D1BB425C500AFC405 /* SampleUITests.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 3E5A542F1BB425C500AFC405 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 3E5A540D1BB425C500AFC405 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 3E5A54141BB425C500AFC405; 26 | remoteInfo = Sample; 27 | }; 28 | 3E5A543A1BB425C500AFC405 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 3E5A540D1BB425C500AFC405 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 3E5A54141BB425C500AFC405; 33 | remoteInfo = Sample; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 3E5A54151BB425C500AFC405 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 3E5A54191BB425C500AFC405 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 40 | 3E5A541B1BB425C500AFC405 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 41 | 3E5A541C1BB425C500AFC405 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 42 | 3E5A541E1BB425C500AFC405 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 43 | 3E5A541F1BB425C500AFC405 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 44 | 3E5A54221BB425C500AFC405 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 3E5A54241BB425C500AFC405 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 3E5A54271BB425C500AFC405 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 3E5A54291BB425C500AFC405 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 3E5A542E1BB425C500AFC405 /* SampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 3E5A54321BB425C500AFC405 /* SampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleTests.m; sourceTree = ""; }; 50 | 3E5A54341BB425C500AFC405 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 3E5A54391BB425C500AFC405 /* SampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 3E5A543D1BB425C500AFC405 /* SampleUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleUITests.m; sourceTree = ""; }; 53 | 3E5A543F1BB425C500AFC405 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 3E5A54121BB425C500AFC405 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | 3E5A542B1BB425C500AFC405 /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | 3E5A54361BB425C500AFC405 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | 3E5A540C1BB425C500AFC405 = { 82 | isa = PBXGroup; 83 | children = ( 84 | 3E5A54171BB425C500AFC405 /* Sample */, 85 | 3E5A54311BB425C500AFC405 /* SampleTests */, 86 | 3E5A543C1BB425C500AFC405 /* SampleUITests */, 87 | 3E5A54161BB425C500AFC405 /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | 3E5A54161BB425C500AFC405 /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 3E5A54151BB425C500AFC405 /* Sample.app */, 95 | 3E5A542E1BB425C500AFC405 /* SampleTests.xctest */, 96 | 3E5A54391BB425C500AFC405 /* SampleUITests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 3E5A54171BB425C500AFC405 /* Sample */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 3E5A541B1BB425C500AFC405 /* AppDelegate.h */, 105 | 3E5A541C1BB425C500AFC405 /* AppDelegate.m */, 106 | 3E5A541E1BB425C500AFC405 /* ViewController.h */, 107 | 3E5A541F1BB425C500AFC405 /* ViewController.m */, 108 | 3E5A54211BB425C500AFC405 /* Main.storyboard */, 109 | 3E5A54241BB425C500AFC405 /* Assets.xcassets */, 110 | 3E5A54261BB425C500AFC405 /* LaunchScreen.storyboard */, 111 | 3E5A54291BB425C500AFC405 /* Info.plist */, 112 | 3E5A54181BB425C500AFC405 /* Supporting Files */, 113 | ); 114 | path = Sample; 115 | sourceTree = ""; 116 | }; 117 | 3E5A54181BB425C500AFC405 /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 3E5A54191BB425C500AFC405 /* main.m */, 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | 3E5A54311BB425C500AFC405 /* SampleTests */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 3E5A54321BB425C500AFC405 /* SampleTests.m */, 129 | 3E5A54341BB425C500AFC405 /* Info.plist */, 130 | ); 131 | path = SampleTests; 132 | sourceTree = ""; 133 | }; 134 | 3E5A543C1BB425C500AFC405 /* SampleUITests */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 3E5A543D1BB425C500AFC405 /* SampleUITests.m */, 138 | 3E5A543F1BB425C500AFC405 /* Info.plist */, 139 | ); 140 | path = SampleUITests; 141 | sourceTree = ""; 142 | }; 143 | /* End PBXGroup section */ 144 | 145 | /* Begin PBXNativeTarget section */ 146 | 3E5A54141BB425C500AFC405 /* Sample */ = { 147 | isa = PBXNativeTarget; 148 | buildConfigurationList = 3E5A54421BB425C500AFC405 /* Build configuration list for PBXNativeTarget "Sample" */; 149 | buildPhases = ( 150 | 3E5A54111BB425C500AFC405 /* Sources */, 151 | 3E5A54121BB425C500AFC405 /* Frameworks */, 152 | 3E5A54131BB425C500AFC405 /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = Sample; 159 | productName = Sample; 160 | productReference = 3E5A54151BB425C500AFC405 /* Sample.app */; 161 | productType = "com.apple.product-type.application"; 162 | }; 163 | 3E5A542D1BB425C500AFC405 /* SampleTests */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 3E5A54451BB425C500AFC405 /* Build configuration list for PBXNativeTarget "SampleTests" */; 166 | buildPhases = ( 167 | 3E5A542A1BB425C500AFC405 /* Sources */, 168 | 3E5A542B1BB425C500AFC405 /* Frameworks */, 169 | 3E5A542C1BB425C500AFC405 /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | 3E5A54301BB425C500AFC405 /* PBXTargetDependency */, 175 | ); 176 | name = SampleTests; 177 | productName = SampleTests; 178 | productReference = 3E5A542E1BB425C500AFC405 /* SampleTests.xctest */; 179 | productType = "com.apple.product-type.bundle.unit-test"; 180 | }; 181 | 3E5A54381BB425C500AFC405 /* SampleUITests */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 3E5A54481BB425C500AFC405 /* Build configuration list for PBXNativeTarget "SampleUITests" */; 184 | buildPhases = ( 185 | 3E5A54351BB425C500AFC405 /* Sources */, 186 | 3E5A54361BB425C500AFC405 /* Frameworks */, 187 | 3E5A54371BB425C500AFC405 /* Resources */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | 3E5A543B1BB425C500AFC405 /* PBXTargetDependency */, 193 | ); 194 | name = SampleUITests; 195 | productName = SampleUITests; 196 | productReference = 3E5A54391BB425C500AFC405 /* SampleUITests.xctest */; 197 | productType = "com.apple.product-type.bundle.ui-testing"; 198 | }; 199 | /* End PBXNativeTarget section */ 200 | 201 | /* Begin PBXProject section */ 202 | 3E5A540D1BB425C500AFC405 /* Project object */ = { 203 | isa = PBXProject; 204 | attributes = { 205 | LastUpgradeCheck = 0700; 206 | ORGANIZATIONNAME = jiakai; 207 | TargetAttributes = { 208 | 3E5A54141BB425C500AFC405 = { 209 | CreatedOnToolsVersion = 7.0; 210 | }; 211 | 3E5A542D1BB425C500AFC405 = { 212 | CreatedOnToolsVersion = 7.0; 213 | TestTargetID = 3E5A54141BB425C500AFC405; 214 | }; 215 | 3E5A54381BB425C500AFC405 = { 216 | CreatedOnToolsVersion = 7.0; 217 | TestTargetID = 3E5A54141BB425C500AFC405; 218 | }; 219 | }; 220 | }; 221 | buildConfigurationList = 3E5A54101BB425C500AFC405 /* Build configuration list for PBXProject "Sample" */; 222 | compatibilityVersion = "Xcode 3.2"; 223 | developmentRegion = English; 224 | hasScannedForEncodings = 0; 225 | knownRegions = ( 226 | en, 227 | Base, 228 | ); 229 | mainGroup = 3E5A540C1BB425C500AFC405; 230 | productRefGroup = 3E5A54161BB425C500AFC405 /* Products */; 231 | projectDirPath = ""; 232 | projectRoot = ""; 233 | targets = ( 234 | 3E5A54141BB425C500AFC405 /* Sample */, 235 | 3E5A542D1BB425C500AFC405 /* SampleTests */, 236 | 3E5A54381BB425C500AFC405 /* SampleUITests */, 237 | ); 238 | }; 239 | /* End PBXProject section */ 240 | 241 | /* Begin PBXResourcesBuildPhase section */ 242 | 3E5A54131BB425C500AFC405 /* Resources */ = { 243 | isa = PBXResourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 3E5A54281BB425C500AFC405 /* LaunchScreen.storyboard in Resources */, 247 | 3E5A54251BB425C500AFC405 /* Assets.xcassets in Resources */, 248 | 3E5A54231BB425C500AFC405 /* Main.storyboard in Resources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | 3E5A542C1BB425C500AFC405 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 3E5A54371BB425C500AFC405 /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXResourcesBuildPhase section */ 267 | 268 | /* Begin PBXSourcesBuildPhase section */ 269 | 3E5A54111BB425C500AFC405 /* Sources */ = { 270 | isa = PBXSourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | 3E5A54201BB425C500AFC405 /* ViewController.m in Sources */, 274 | 3E5A541D1BB425C500AFC405 /* AppDelegate.m in Sources */, 275 | 3E5A541A1BB425C500AFC405 /* main.m in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | 3E5A542A1BB425C500AFC405 /* Sources */ = { 280 | isa = PBXSourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | 3E5A54331BB425C500AFC405 /* SampleTests.m in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | 3E5A54351BB425C500AFC405 /* Sources */ = { 288 | isa = PBXSourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | 3E5A543E1BB425C500AFC405 /* SampleUITests.m in Sources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXSourcesBuildPhase section */ 296 | 297 | /* Begin PBXTargetDependency section */ 298 | 3E5A54301BB425C500AFC405 /* PBXTargetDependency */ = { 299 | isa = PBXTargetDependency; 300 | target = 3E5A54141BB425C500AFC405 /* Sample */; 301 | targetProxy = 3E5A542F1BB425C500AFC405 /* PBXContainerItemProxy */; 302 | }; 303 | 3E5A543B1BB425C500AFC405 /* PBXTargetDependency */ = { 304 | isa = PBXTargetDependency; 305 | target = 3E5A54141BB425C500AFC405 /* Sample */; 306 | targetProxy = 3E5A543A1BB425C500AFC405 /* PBXContainerItemProxy */; 307 | }; 308 | /* End PBXTargetDependency section */ 309 | 310 | /* Begin PBXVariantGroup section */ 311 | 3E5A54211BB425C500AFC405 /* Main.storyboard */ = { 312 | isa = PBXVariantGroup; 313 | children = ( 314 | 3E5A54221BB425C500AFC405 /* Base */, 315 | ); 316 | name = Main.storyboard; 317 | sourceTree = ""; 318 | }; 319 | 3E5A54261BB425C500AFC405 /* LaunchScreen.storyboard */ = { 320 | isa = PBXVariantGroup; 321 | children = ( 322 | 3E5A54271BB425C500AFC405 /* Base */, 323 | ); 324 | name = LaunchScreen.storyboard; 325 | sourceTree = ""; 326 | }; 327 | /* End PBXVariantGroup section */ 328 | 329 | /* Begin XCBuildConfiguration section */ 330 | 3E5A54401BB425C500AFC405 /* Debug */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 335 | CLANG_CXX_LIBRARY = "libc++"; 336 | CLANG_ENABLE_MODULES = YES; 337 | CLANG_ENABLE_OBJC_ARC = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_CONSTANT_CONVERSION = YES; 340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 341 | CLANG_WARN_EMPTY_BODY = YES; 342 | CLANG_WARN_ENUM_CONVERSION = YES; 343 | CLANG_WARN_INT_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_UNREACHABLE_CODE = YES; 346 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 347 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 348 | COPY_PHASE_STRIP = NO; 349 | DEBUG_INFORMATION_FORMAT = dwarf; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | ENABLE_TESTABILITY = YES; 352 | GCC_C_LANGUAGE_STANDARD = gnu99; 353 | GCC_DYNAMIC_NO_PIC = NO; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_OPTIMIZATION_LEVEL = 0; 356 | GCC_PREPROCESSOR_DEFINITIONS = ( 357 | "DEBUG=1", 358 | "$(inherited)", 359 | ); 360 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 361 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 362 | GCC_WARN_UNDECLARED_SELECTOR = YES; 363 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 364 | GCC_WARN_UNUSED_FUNCTION = YES; 365 | GCC_WARN_UNUSED_VARIABLE = YES; 366 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 367 | MTL_ENABLE_DEBUG_INFO = YES; 368 | ONLY_ACTIVE_ARCH = YES; 369 | SDKROOT = iphoneos; 370 | TARGETED_DEVICE_FAMILY = "1,2"; 371 | }; 372 | name = Debug; 373 | }; 374 | 3E5A54411BB425C500AFC405 /* Release */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ALWAYS_SEARCH_USER_PATHS = NO; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 379 | CLANG_CXX_LIBRARY = "libc++"; 380 | CLANG_ENABLE_MODULES = YES; 381 | CLANG_ENABLE_OBJC_ARC = YES; 382 | CLANG_WARN_BOOL_CONVERSION = YES; 383 | CLANG_WARN_CONSTANT_CONVERSION = YES; 384 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 385 | CLANG_WARN_EMPTY_BODY = YES; 386 | CLANG_WARN_ENUM_CONVERSION = YES; 387 | CLANG_WARN_INT_CONVERSION = YES; 388 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 389 | CLANG_WARN_UNREACHABLE_CODE = YES; 390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 391 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 392 | COPY_PHASE_STRIP = NO; 393 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 394 | ENABLE_NS_ASSERTIONS = NO; 395 | ENABLE_STRICT_OBJC_MSGSEND = YES; 396 | GCC_C_LANGUAGE_STANDARD = gnu99; 397 | GCC_NO_COMMON_BLOCKS = YES; 398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 400 | GCC_WARN_UNDECLARED_SELECTOR = YES; 401 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 402 | GCC_WARN_UNUSED_FUNCTION = YES; 403 | GCC_WARN_UNUSED_VARIABLE = YES; 404 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 405 | MTL_ENABLE_DEBUG_INFO = NO; 406 | SDKROOT = iphoneos; 407 | TARGETED_DEVICE_FAMILY = "1,2"; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 3E5A54431BB425C500AFC405 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | INFOPLIST_FILE = Sample/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.jiakai.Sample; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | }; 421 | name = Debug; 422 | }; 423 | 3E5A54441BB425C500AFC405 /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 427 | INFOPLIST_FILE = Sample/Info.plist; 428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 429 | PRODUCT_BUNDLE_IDENTIFIER = com.jiakai.Sample; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | }; 432 | name = Release; 433 | }; 434 | 3E5A54461BB425C500AFC405 /* Debug */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | BUNDLE_LOADER = "$(TEST_HOST)"; 438 | INFOPLIST_FILE = SampleTests/Info.plist; 439 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 440 | PRODUCT_BUNDLE_IDENTIFIER = com.jiakai.SampleTests; 441 | PRODUCT_NAME = "$(TARGET_NAME)"; 442 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Sample.app/Sample"; 443 | }; 444 | name = Debug; 445 | }; 446 | 3E5A54471BB425C500AFC405 /* Release */ = { 447 | isa = XCBuildConfiguration; 448 | buildSettings = { 449 | BUNDLE_LOADER = "$(TEST_HOST)"; 450 | INFOPLIST_FILE = SampleTests/Info.plist; 451 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 452 | PRODUCT_BUNDLE_IDENTIFIER = com.jiakai.SampleTests; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Sample.app/Sample"; 455 | }; 456 | name = Release; 457 | }; 458 | 3E5A54491BB425C500AFC405 /* Debug */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | INFOPLIST_FILE = SampleUITests/Info.plist; 462 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 463 | PRODUCT_BUNDLE_IDENTIFIER = com.jiakai.SampleUITests; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | TEST_TARGET_NAME = Sample; 466 | USES_XCTRUNNER = YES; 467 | }; 468 | name = Debug; 469 | }; 470 | 3E5A544A1BB425C500AFC405 /* Release */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | INFOPLIST_FILE = SampleUITests/Info.plist; 474 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 475 | PRODUCT_BUNDLE_IDENTIFIER = com.jiakai.SampleUITests; 476 | PRODUCT_NAME = "$(TARGET_NAME)"; 477 | TEST_TARGET_NAME = Sample; 478 | USES_XCTRUNNER = YES; 479 | }; 480 | name = Release; 481 | }; 482 | /* End XCBuildConfiguration section */ 483 | 484 | /* Begin XCConfigurationList section */ 485 | 3E5A54101BB425C500AFC405 /* Build configuration list for PBXProject "Sample" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 3E5A54401BB425C500AFC405 /* Debug */, 489 | 3E5A54411BB425C500AFC405 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | 3E5A54421BB425C500AFC405 /* Build configuration list for PBXNativeTarget "Sample" */ = { 495 | isa = XCConfigurationList; 496 | buildConfigurations = ( 497 | 3E5A54431BB425C500AFC405 /* Debug */, 498 | 3E5A54441BB425C500AFC405 /* Release */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | }; 502 | 3E5A54451BB425C500AFC405 /* Build configuration list for PBXNativeTarget "SampleTests" */ = { 503 | isa = XCConfigurationList; 504 | buildConfigurations = ( 505 | 3E5A54461BB425C500AFC405 /* Debug */, 506 | 3E5A54471BB425C500AFC405 /* Release */, 507 | ); 508 | defaultConfigurationIsVisible = 0; 509 | }; 510 | 3E5A54481BB425C500AFC405 /* Build configuration list for PBXNativeTarget "SampleUITests" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | 3E5A54491BB425C500AFC405 /* Debug */, 514 | 3E5A544A1BB425C500AFC405 /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | }; 518 | /* End XCConfigurationList section */ 519 | }; 520 | rootObject = 3E5A540D1BB425C500AFC405 /* Project object */; 521 | } 522 | -------------------------------------------------------------------------------- /Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Sample 4 | // 5 | // Created by jiakai lian on 24/09/2015. 6 | // Copyright © 2015 jiakai. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Sample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Sample 4 | // 5 | // Created by jiakai lian on 24/09/2015. 6 | // Copyright © 2015 jiakai. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Sample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Sample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Sample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Sample 4 | // 5 | // Created by jiakai lian on 24/09/2015. 6 | // Copyright © 2015 jiakai. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Sample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Sample 4 | // 5 | // Created by jiakai lian on 24/09/2015. 6 | // Copyright © 2015 jiakai. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | } 21 | 22 | - (void)didReceiveMemoryWarning { 23 | [super didReceiveMemoryWarning]; 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Sample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample 4 | // 5 | // Created by jiakai lian on 24/09/2015. 6 | // Copyright © 2015 jiakai. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SampleTests/SampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SampleTests.m 3 | // SampleTests 4 | // 5 | // Created by jiakai lian on 24/09/2015. 6 | // Copyright © 2015 jiakai. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SampleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation SampleTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /SampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SampleUITests/SampleUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SampleUITests.m 3 | // SampleUITests 4 | // 5 | // Created by jiakai lian on 24/09/2015. 6 | // Copyright © 2015 jiakai. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SampleUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation SampleUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | } 27 | 28 | - (void)tearDown { 29 | // Put teardown code here. This method is called after the invocation of each test method in the class. 30 | [super tearDown]; 31 | } 32 | 33 | - (void)testExample { 34 | // Use recording to get started writing UI tests. 35 | // Use XCTAssert and related functions to verify your tests produce the correct results. 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "WARNING_CFLAGS":"-Wall -Wextra -Wno-unused-parameter -Wno-unused-variable", 3 | "CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER":"YES", 4 | "CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND":"YES", 5 | "CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY":"YES", 6 | "RUN_CLANG_STATIC_ANALYZER":"YES", 7 | "GCC_TREAT_WARNINGS_AS_ERRORS":"NO" 8 | } -------------------------------------------------------------------------------- /mod_pbxproj.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Calvin Rien 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # A pbxproj file is an OpenStep format plist 16 | # {} represents dictionary of key=value pairs delimited by ; 17 | # () represents list of values delimited by , 18 | # file starts with a comment specifying the character type 19 | # // !$*UTF8*$! 20 | 21 | # when adding a file to a project, create the PBXFileReference 22 | # add the PBXFileReference's guid to a group 23 | # create a PBXBuildFile with the PBXFileReference's guid 24 | # add the PBXBuildFile to the appropriate build phase 25 | 26 | # when adding a header search path add 27 | # HEADER_SEARCH_PATHS = "path/**"; 28 | # to each XCBuildConfiguration object 29 | 30 | # Xcode4 will read either a OpenStep or XML plist. 31 | # this script uses `plutil` to validate, read and write 32 | # the pbxproj file. Plutil is available in OS X 10.2 and higher 33 | # Plutil can't write OpenStep plists, so I save as XML 34 | 35 | import datetime 36 | import json 37 | import ntpath 38 | import os 39 | import plistlib 40 | import re 41 | import shutil 42 | import subprocess 43 | import uuid 44 | 45 | from UserDict import IterableUserDict 46 | from UserList import UserList 47 | 48 | regex = '[a-zA-Z0-9\\._/-]*' 49 | 50 | 51 | class PBXEncoder(json.JSONEncoder): 52 | def default(self, obj): 53 | """Tests the input object, obj, to encode as JSON.""" 54 | 55 | if isinstance(obj, (PBXList, PBXDict)): 56 | return obj.data 57 | 58 | return json.JSONEncoder.default(self, obj) 59 | 60 | 61 | class PBXDict(IterableUserDict): 62 | def __init__(self, d=None): 63 | if d: 64 | d = dict([(PBXType.Convert(k), PBXType.Convert(v)) for k, v in d.items()]) 65 | 66 | IterableUserDict.__init__(self, d) 67 | 68 | def __setitem__(self, key, value): 69 | IterableUserDict.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value)) 70 | 71 | def remove(self, key): 72 | self.data.pop(PBXType.Convert(key), None) 73 | 74 | 75 | class PBXList(UserList): 76 | def __init__(self, l=None): 77 | if isinstance(l, basestring): 78 | UserList.__init__(self) 79 | self.add(l) 80 | return 81 | elif l: 82 | l = [PBXType.Convert(v) for v in l] 83 | 84 | UserList.__init__(self, l) 85 | 86 | def add(self, value): 87 | value = PBXType.Convert(value) 88 | 89 | if value in self.data: 90 | return False 91 | 92 | self.data.append(value) 93 | return True 94 | 95 | def remove(self, value): 96 | value = PBXType.Convert(value) 97 | 98 | if value in self.data: 99 | self.data.remove(value) 100 | return True 101 | return False 102 | 103 | def __setitem__(self, key, value): 104 | UserList.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value)) 105 | 106 | 107 | class PBXType(PBXDict): 108 | def __init__(self, d=None): 109 | PBXDict.__init__(self, d) 110 | 111 | if 'isa' not in self: 112 | self['isa'] = self.__class__.__name__ 113 | self.id = None 114 | 115 | @staticmethod 116 | def Convert(o): 117 | if isinstance(o, list): 118 | return PBXList(o) 119 | elif isinstance(o, dict): 120 | isa = o.get('isa') 121 | 122 | if not isa: 123 | return PBXDict(o) 124 | 125 | cls = globals().get(isa) 126 | 127 | if cls and issubclass(cls, PBXType): 128 | return cls(o) 129 | 130 | print 'warning: unknown PBX type: %s' % isa 131 | return PBXDict(o) 132 | else: 133 | return o 134 | 135 | @staticmethod 136 | def IsGuid(o): 137 | return re.match('^[A-F0-9]{24}$', str(o)) 138 | 139 | @classmethod 140 | def GenerateId(cls): 141 | return ''.join(str(uuid.uuid4()).upper().split('-')[1:]) 142 | 143 | @classmethod 144 | def Create(cls, *args, **kwargs): 145 | return cls(*args, **kwargs) 146 | 147 | 148 | class PBXFileReference(PBXType): 149 | def __init__(self, d=None): 150 | PBXType.__init__(self, d) 151 | self.build_phase = None 152 | 153 | types = { 154 | '.a': ('archive.ar', 'PBXFrameworksBuildPhase'), 155 | '.app': ('wrapper.application', None), 156 | '.s': ('sourcecode.asm', 'PBXSourcesBuildPhase'), 157 | '.c': ('sourcecode.c.c', 'PBXSourcesBuildPhase'), 158 | '.cpp': ('sourcecode.cpp.cpp', 'PBXSourcesBuildPhase'), 159 | '.framework': ('wrapper.framework', 'PBXFrameworksBuildPhase'), 160 | '.h': ('sourcecode.c.h', None), 161 | '.hpp': ('sourcecode.c.h', None), 162 | '.swift': ('sourcecode.swift', 'PBXSourcesBuildPhase'), 163 | '.icns': ('image.icns', 'PBXResourcesBuildPhase'), 164 | '.m': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'), 165 | '.j': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'), 166 | '.mm': ('sourcecode.cpp.objcpp', 'PBXSourcesBuildPhase'), 167 | '.nib': ('wrapper.nib', 'PBXResourcesBuildPhase'), 168 | '.plist': ('text.plist.xml', 'PBXResourcesBuildPhase'), 169 | '.json': ('text.json', 'PBXResourcesBuildPhase'), 170 | '.png': ('image.png', 'PBXResourcesBuildPhase'), 171 | '.rtf': ('text.rtf', 'PBXResourcesBuildPhase'), 172 | '.tiff': ('image.tiff', 'PBXResourcesBuildPhase'), 173 | '.txt': ('text', 'PBXResourcesBuildPhase'), 174 | '.xcodeproj': ('wrapper.pb-project', None), 175 | '.xib': ('file.xib', 'PBXResourcesBuildPhase'), 176 | '.strings': ('text.plist.strings', 'PBXResourcesBuildPhase'), 177 | '.bundle': ('wrapper.plug-in', 'PBXResourcesBuildPhase'), 178 | '.dylib': ('compiled.mach-o.dylib', 'PBXFrameworksBuildPhase'), 179 | '.xcdatamodeld': ('wrapper.xcdatamodel', 'PBXSourcesBuildPhase'), 180 | '.xcassets': ('folder.assetcatalog', 'PBXResourcesBuildPhase') 181 | } 182 | 183 | trees = [ 184 | '', 185 | '', 186 | 'BUILT_PRODUCTS_DIR', 187 | 'DEVELOPER_DIR', 188 | 'SDKROOT', 189 | 'SOURCE_ROOT', 190 | ] 191 | 192 | def guess_file_type(self, ignore_unknown_type=False): 193 | self.remove('explicitFileType') 194 | self.remove('lastKnownFileType') 195 | 196 | 197 | ext = os.path.splitext(self.get('name', ''))[1] 198 | if os.path.isdir(self.get('path')) and ext not in XcodeProject.special_folders: 199 | f_type = 'folder' 200 | build_phase = None 201 | ext = '' 202 | else: 203 | f_type, build_phase = PBXFileReference.types.get(ext, ('?', 'PBXResourcesBuildPhase')) 204 | 205 | self['lastKnownFileType'] = f_type 206 | self.build_phase = build_phase 207 | 208 | if f_type == '?' and not ignore_unknown_type: 209 | print 'unknown file extension: %s' % ext 210 | print 'please add extension and Xcode type to PBXFileReference.types' 211 | 212 | return f_type 213 | 214 | def set_file_type(self, ft): 215 | self.remove('explicitFileType') 216 | self.remove('lastKnownFileType') 217 | 218 | self['explicitFileType'] = ft 219 | 220 | @classmethod 221 | def Create(cls, os_path, tree='SOURCE_ROOT', ignore_unknown_type=False): 222 | if tree not in cls.trees: 223 | print 'Not a valid sourceTree type: %s' % tree 224 | return None 225 | 226 | fr = cls() 227 | fr.id = cls.GenerateId() 228 | fr['path'] = os_path 229 | fr['name'] = os.path.split(os_path)[1] 230 | fr['sourceTree'] = '' if os.path.isabs(os_path) else tree 231 | fr.guess_file_type(ignore_unknown_type=ignore_unknown_type) 232 | 233 | return fr 234 | 235 | 236 | class PBXBuildFile(PBXType): 237 | def set_weak_link(self, weak=False): 238 | k_settings = 'settings' 239 | k_attributes = 'ATTRIBUTES' 240 | 241 | s = self.get(k_settings) 242 | 243 | if not s: 244 | if weak: 245 | self[k_settings] = PBXDict({k_attributes: PBXList(['Weak'])}) 246 | 247 | return True 248 | 249 | atr = s.get(k_attributes) 250 | 251 | if not atr: 252 | if weak: 253 | atr = PBXList() 254 | else: 255 | return False 256 | 257 | if weak: 258 | atr.add('Weak') 259 | else: 260 | atr.remove('Weak') 261 | 262 | self[k_settings][k_attributes] = atr 263 | 264 | return True 265 | 266 | def add_compiler_flag(self, flag): 267 | k_settings = 'settings' 268 | k_attributes = 'COMPILER_FLAGS' 269 | 270 | if k_settings not in self: 271 | self[k_settings] = PBXDict() 272 | 273 | if k_attributes not in self[k_settings]: 274 | self[k_settings][k_attributes] = flag 275 | return True 276 | 277 | flags = self[k_settings][k_attributes].split(' ') 278 | 279 | if flag in flags: 280 | return False 281 | 282 | flags.append(flag) 283 | 284 | self[k_settings][k_attributes] = ' '.join(flags) 285 | 286 | @classmethod 287 | def Create(cls, file_ref, weak=False): 288 | if isinstance(file_ref, PBXFileReference): 289 | file_ref = file_ref.id 290 | 291 | bf = cls() 292 | bf.id = cls.GenerateId() 293 | bf['fileRef'] = file_ref 294 | 295 | if weak: 296 | bf.set_weak_link(True) 297 | 298 | return bf 299 | 300 | 301 | class PBXGroup(PBXType): 302 | def add_child(self, ref): 303 | if not isinstance(ref, PBXDict): 304 | return None 305 | 306 | isa = ref.get('isa') 307 | 308 | if isa != 'PBXFileReference' and isa != 'PBXGroup': 309 | return None 310 | 311 | if 'children' not in self: 312 | self['children'] = PBXList() 313 | 314 | self['children'].add(ref.id) 315 | 316 | return ref.id 317 | 318 | def remove_child(self, id): 319 | if 'children' not in self: 320 | self['children'] = PBXList() 321 | return 322 | 323 | if not PBXType.IsGuid(id): 324 | id = id.id 325 | 326 | self['children'].remove(id) 327 | 328 | def has_child(self, id): 329 | if 'children' not in self: 330 | self['children'] = PBXList() 331 | return False 332 | 333 | if not PBXType.IsGuid(id): 334 | id = id.id 335 | 336 | return id in self['children'] 337 | 338 | def get_name(self): 339 | path_name = os.path.split(self.get('path', ''))[1] 340 | return self.get('name', path_name) 341 | 342 | @classmethod 343 | def Create(cls, name, path=None, tree='SOURCE_ROOT'): 344 | grp = cls() 345 | grp.id = cls.GenerateId() 346 | grp['name'] = name 347 | grp['children'] = PBXList() 348 | 349 | if path: 350 | grp['path'] = path 351 | grp['sourceTree'] = tree 352 | else: 353 | grp['sourceTree'] = '' 354 | 355 | return grp 356 | 357 | 358 | class PBXNativeTarget(PBXType): 359 | pass 360 | 361 | 362 | class PBXProject(PBXType): 363 | pass 364 | 365 | 366 | class PBXContainerItemProxy(PBXType): 367 | pass 368 | 369 | 370 | class PBXReferenceProxy(PBXType): 371 | pass 372 | 373 | 374 | class PBXVariantGroup(PBXType): 375 | pass 376 | 377 | 378 | class PBXTargetDependency(PBXType): 379 | pass 380 | 381 | 382 | class PBXAggregateTarget(PBXType): 383 | pass 384 | 385 | 386 | class PBXHeadersBuildPhase(PBXType): 387 | pass 388 | 389 | class XCVersionGroup(PBXType): 390 | pass 391 | 392 | class PBXBuildPhase(PBXType): 393 | def add_build_file(self, bf): 394 | if bf.get('isa') != 'PBXBuildFile': 395 | return False 396 | 397 | if 'files' not in self: 398 | self['files'] = PBXList() 399 | 400 | self['files'].add(bf.id) 401 | 402 | return True 403 | 404 | def remove_build_file(self, id): 405 | if 'files' not in self: 406 | self['files'] = PBXList() 407 | return 408 | 409 | self['files'].remove(id) 410 | 411 | def has_build_file(self, id): 412 | if 'files' not in self: 413 | self['files'] = PBXList() 414 | return False 415 | 416 | if not PBXType.IsGuid(id): 417 | id = id.id 418 | 419 | return id in self['files'] 420 | 421 | 422 | class PBXFrameworksBuildPhase(PBXBuildPhase): 423 | pass 424 | 425 | 426 | class PBXResourcesBuildPhase(PBXBuildPhase): 427 | pass 428 | 429 | 430 | class PBXShellScriptBuildPhase(PBXBuildPhase): 431 | @classmethod 432 | def Create(cls, script, shell="/bin/sh", files=[], input_paths=[], output_paths=[], show_in_log = '0'): 433 | bf = cls() 434 | bf.id = cls.GenerateId() 435 | bf['files'] = files 436 | bf['inputPaths'] = input_paths 437 | bf['outputPaths'] = output_paths 438 | bf['runOnlyForDeploymentPostprocessing'] = '0'; 439 | bf['shellPath'] = shell 440 | bf['shellScript'] = script 441 | bf['showEnvVarsInLog'] = show_in_log 442 | 443 | return bf 444 | 445 | 446 | class PBXSourcesBuildPhase(PBXBuildPhase): 447 | pass 448 | 449 | 450 | class PBXCopyFilesBuildPhase(PBXBuildPhase): 451 | pass 452 | 453 | 454 | class XCBuildConfiguration(PBXType): 455 | def add_search_paths(self, paths, base, key, recursive=True, escape=True): 456 | modified = False 457 | 458 | if not isinstance(paths, list): 459 | paths = [paths] 460 | 461 | if base not in self: 462 | self[base] = PBXDict() 463 | 464 | for path in paths: 465 | if recursive and not path.endswith('/**'): 466 | path = os.path.join(path, '**') 467 | 468 | if key not in self[base]: 469 | self[base][key] = PBXList() 470 | elif isinstance(self[base][key], basestring): 471 | self[base][key] = PBXList(self[base][key]) 472 | 473 | if path == '$(inherited)': 474 | escape = False 475 | 476 | if escape: 477 | if self[base][key].add('"%s"' % path): # '\\"%s\\"' % path 478 | modified = True 479 | else: 480 | if self[base][key].add(path): # '\\"%s\\"' % path 481 | modified = True 482 | 483 | return modified 484 | 485 | def add_header_search_paths(self, paths, recursive=True): 486 | return self.add_search_paths(paths, 'buildSettings', 'HEADER_SEARCH_PATHS', recursive=recursive) 487 | 488 | def add_library_search_paths(self, paths, recursive=True): 489 | return self.add_search_paths(paths, 'buildSettings', 'LIBRARY_SEARCH_PATHS', recursive=recursive) 490 | 491 | def add_framework_search_paths(self, paths, recursive=True): 492 | return self.add_search_paths(paths, 'buildSettings', 'FRAMEWORK_SEARCH_PATHS', recursive=recursive) 493 | 494 | def add_other_cflags(self, flags): 495 | return self.add_flag('OTHER_CFLAGS', flags) 496 | 497 | def add_other_ldflags(self, flags): 498 | return self.add_flag('OTHER_LDFLAGS', flags) 499 | 500 | def add_flag(self, key, flags): 501 | modified = False 502 | base = 'buildSettings' 503 | 504 | if isinstance(flags, basestring): 505 | flags = PBXList(flags) 506 | 507 | if base not in self: 508 | self[base] = PBXDict() 509 | 510 | for flag in flags: 511 | if key not in self[base]: 512 | self[base][key] = PBXList() 513 | elif isinstance(self[base][key], basestring): 514 | self[base][key] = PBXList(self[base][key]) 515 | 516 | if self[base][key].add(flag): 517 | self[base][key] = [e for e in self[base][key] if e] 518 | modified = True 519 | 520 | return modified 521 | 522 | def remove_flag(self, key, flags): 523 | modified = False 524 | base = 'buildSettings' 525 | 526 | if isinstance(flags, basestring): 527 | flags = PBXList(flags) 528 | 529 | if base in self: # there are flags, so we can "remove" something 530 | for flag in flags: 531 | if key not in self[base]: 532 | return False 533 | elif isinstance(self[base][key], basestring): 534 | self[base][key] = PBXList(self[base][key]) 535 | 536 | if self[base][key].remove(flag): 537 | self[base][key] = [e for e in self[base][key] if e] 538 | modified = True 539 | 540 | if len(self[base][key]) == 0: 541 | self[base].pop(key, None) 542 | 543 | return modified 544 | 545 | def remove_other_ldflags(self, flags): 546 | return self.remove_flag('OTHER_LD_FLAGS', flags) 547 | 548 | class XCConfigurationList(PBXType): 549 | pass 550 | 551 | 552 | class XcodeProject(PBXDict): 553 | plutil_path = 'plutil' 554 | special_folders = ['.bundle', '.framework', '.xcodeproj', '.xcassets', '.xcdatamodeld'] 555 | 556 | def __init__(self, d=None, path=None): 557 | if not path: 558 | path = os.path.join(os.getcwd(), 'project.pbxproj') 559 | 560 | self.pbxproj_path = os.path.abspath(path) 561 | self.source_root = os.path.abspath(os.path.join(os.path.split(path)[0], '..')) 562 | 563 | IterableUserDict.__init__(self, d) 564 | 565 | self.data = PBXDict(self.data) 566 | self.objects = self.get('objects') 567 | self.modified = False 568 | 569 | root_id = self.get('rootObject') 570 | 571 | if root_id: 572 | self.root_object = self.objects[root_id] 573 | root_group_id = self.root_object.get('mainGroup') 574 | self.root_group = self.objects[root_group_id] 575 | else: 576 | print "error: project has no root object" 577 | self.root_object = None 578 | self.root_group = None 579 | 580 | for k, v in self.objects.iteritems(): 581 | v.id = k 582 | 583 | def add_other_cflags(self, flags): 584 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 585 | 586 | for b in build_configs: 587 | if b.add_other_cflags(flags): 588 | self.modified = True 589 | 590 | def add_other_ldflags(self, flags): 591 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 592 | 593 | for b in build_configs: 594 | if b.add_other_ldflags(flags): 595 | self.modified = True 596 | 597 | def remove_other_ldflags(self, flags): 598 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 599 | 600 | for b in build_configs: 601 | if b.remove_other_ldflags(flags): 602 | self.modified = True 603 | 604 | def add_header_search_paths(self, paths, recursive=True): 605 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 606 | 607 | for b in build_configs: 608 | if b.add_header_search_paths(paths, recursive): 609 | self.modified = True 610 | 611 | def add_framework_search_paths(self, paths, recursive=True): 612 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 613 | 614 | for b in build_configs: 615 | if b.add_framework_search_paths(paths, recursive): 616 | self.modified = True 617 | 618 | def add_library_search_paths(self, paths, recursive=True): 619 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 620 | 621 | for b in build_configs: 622 | if b.add_library_search_paths(paths, recursive): 623 | self.modified = True 624 | 625 | def add_flags(self, pairs, configuration='All'): 626 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 627 | 628 | # iterate over all the pairs of configurations 629 | for b in build_configs: 630 | if configuration != "All" and b.get('name') != configuration : 631 | continue 632 | 633 | for k in pairs: 634 | if b.add_flag(k, pairs[k]): 635 | self.modified = True 636 | 637 | def remove_flags(self, pairs, configuration='All'): 638 | build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration'] 639 | 640 | # iterate over all the pairs of configurations 641 | for b in build_configs: 642 | if configuration != "All" and b.get('name') != configuration : 643 | continue 644 | for k in pairs: 645 | if b.remove_flag(k, pairs[k]): 646 | self.modified = True 647 | 648 | def get_obj(self, id): 649 | return self.objects.get(id) 650 | 651 | def get_ids(self): 652 | return self.objects.keys() 653 | 654 | def get_files_by_os_path(self, os_path, tree='SOURCE_ROOT'): 655 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference' 656 | and f.get('path') == os_path 657 | and f.get('sourceTree') == tree] 658 | 659 | return files 660 | 661 | def get_files_by_name(self, name, parent=None): 662 | if parent: 663 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference' 664 | and f.get('name') == name 665 | and parent.has_child(f)] 666 | else: 667 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference' 668 | and f.get('name') == name] 669 | 670 | return files 671 | 672 | def get_build_files(self, id): 673 | files = [f for f in self.objects.values() if f.get('isa') == 'PBXBuildFile' 674 | and f.get('fileRef') == id] 675 | 676 | return files 677 | 678 | def get_groups_by_name(self, name, parent=None): 679 | if parent: 680 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup' 681 | and g.get_name() == name 682 | and parent.has_child(g)] 683 | else: 684 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup' 685 | and g.get_name() == name] 686 | 687 | return groups 688 | 689 | def get_or_create_group(self, name, path=None, parent=None): 690 | if not name: 691 | return None 692 | 693 | if not parent: 694 | parent = self.root_group 695 | elif not isinstance(parent, PBXGroup): 696 | # assume it's an id 697 | parent = self.objects.get(parent, self.root_group) 698 | 699 | groups = self.get_groups_by_name(name) 700 | 701 | for grp in groups: 702 | if parent.has_child(grp.id): 703 | return grp 704 | 705 | grp = PBXGroup.Create(name, path) 706 | parent.add_child(grp) 707 | 708 | self.objects[grp.id] = grp 709 | 710 | self.modified = True 711 | 712 | return grp 713 | 714 | def get_groups_by_os_path(self, path): 715 | path = os.path.abspath(path) 716 | 717 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup' 718 | and os.path.abspath(g.get('path', '/dev/null')) == path] 719 | 720 | return groups 721 | 722 | def get_build_phases(self, phase_name): 723 | phases = [p for p in self.objects.values() if p.get('isa') == phase_name] 724 | 725 | return phases 726 | 727 | def get_relative_path(self, os_path): 728 | return os.path.relpath(os_path, self.source_root) 729 | 730 | def verify_files(self, file_list, parent=None): 731 | # returns list of files not in the current project. 732 | if not file_list: 733 | return [] 734 | 735 | if parent: 736 | exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list and parent.has_child(f)] 737 | else: 738 | exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list] 739 | 740 | return set(file_list).difference(exists_list) 741 | 742 | def add_run_script(self, target, script=None, insert_before_compile=False): 743 | result = [] 744 | targets = [t for t in self.get_build_phases('PBXNativeTarget') + self.get_build_phases('PBXAggregateTarget') if t.get('name') == target] 745 | if len(targets) != 0 : 746 | script_phase = PBXShellScriptBuildPhase.Create(script) 747 | for t in targets: 748 | skip = False 749 | for buildPhase in t['buildPhases']: 750 | if self.objects[buildPhase].get('isa') == 'PBXShellScriptBuildPhase' and self.objects[buildPhase].get('shellScript') == script: 751 | skip = True 752 | 753 | if not skip: 754 | if insert_before_compile: 755 | t['buildPhases'].insert(0, script_phase.id) 756 | else: 757 | t['buildPhases'].add(script_phase.id) 758 | self.objects[script_phase.id] = script_phase 759 | result.append(script_phase) 760 | 761 | return result 762 | 763 | def add_run_script_all_targets(self, script=None): 764 | result = [] 765 | targets = self.get_build_phases('PBXNativeTarget') + self.get_build_phases('PBXAggregateTarget') 766 | if len(targets) != 0 : 767 | script_phase = PBXShellScriptBuildPhase.Create(script) 768 | for t in targets: 769 | skip = False 770 | for buildPhase in t['buildPhases']: 771 | if self.objects[buildPhase].get('isa') == 'PBXShellScriptBuildPhase' and self.objects[buildPhase].get('shellScript') == script: 772 | skip = True 773 | 774 | if not skip: 775 | t['buildPhases'].add(script_phase.id) 776 | self.objects[script_phase.id] = script_phase 777 | result.append(script_phase) 778 | 779 | return result 780 | 781 | def add_folder(self, os_path, parent=None, excludes=None, recursive=True, create_build_files=True): 782 | if not os.path.isdir(os_path): 783 | return [] 784 | 785 | if not excludes: 786 | excludes = [] 787 | 788 | results = [] 789 | 790 | if not parent: 791 | parent = self.root_group 792 | elif not isinstance(parent, PBXGroup): 793 | # assume it's an id 794 | parent = self.objects.get(parent, self.root_group) 795 | 796 | path_dict = {os.path.split(os_path)[0]: parent} 797 | special_list = [] 798 | 799 | for (grp_path, subdirs, files) in os.walk(os_path): 800 | parent_folder, folder_name = os.path.split(grp_path) 801 | parent = path_dict.get(parent_folder, parent) 802 | 803 | if [sp for sp in special_list if parent_folder.startswith(sp)]: 804 | continue 805 | 806 | if folder_name.startswith('.'): 807 | special_list.append(grp_path) 808 | continue 809 | 810 | if os.path.splitext(grp_path)[1] in XcodeProject.special_folders: 811 | # if this file has a special extension (bundle or framework mainly) treat it as a file 812 | special_list.append(grp_path) 813 | new_files = self.verify_files([folder_name], parent=parent) 814 | 815 | # Ignore this file if it is in excludes 816 | if new_files and not [m for m in excludes if re.match(m, grp_path)]: 817 | results.extend(self.add_file(grp_path, parent, create_build_files=create_build_files)) 818 | 819 | continue 820 | 821 | # create group 822 | grp = self.get_or_create_group(folder_name, path=self.get_relative_path(grp_path), parent=parent) 823 | path_dict[grp_path] = grp 824 | 825 | results.append(grp) 826 | 827 | file_dict = {} 828 | 829 | for f in files: 830 | if f[0] == '.' or [m for m in excludes if re.match(m, f)]: 831 | continue 832 | 833 | kwds = { 834 | 'create_build_files': create_build_files, 835 | 'parent': grp, 836 | 'name': f 837 | } 838 | 839 | f_path = os.path.join(grp_path, f) 840 | file_dict[f_path] = kwds 841 | 842 | new_files = self.verify_files([n.get('name') for n in file_dict.values()], parent=grp) 843 | add_files = [(k, v) for k, v in file_dict.items() if v.get('name') in new_files] 844 | 845 | for path, kwds in add_files: 846 | kwds.pop('name', None) 847 | self.add_file(path, **kwds) 848 | 849 | if not recursive: 850 | break 851 | 852 | for r in results: 853 | self.objects[r.id] = r 854 | 855 | return results 856 | 857 | def path_leaf(self, path): 858 | head, tail = ntpath.split(path) 859 | return tail or ntpath.basename(head) 860 | 861 | def add_file_if_doesnt_exist(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=False): 862 | for obj in self.objects.values(): 863 | if 'path' in obj: 864 | if self.path_leaf(f_path) == self.path_leaf(obj.get('path')): 865 | return [] 866 | 867 | return self.add_file(f_path, parent, tree, create_build_files, weak, ignore_unknown_type=ignore_unknown_type) 868 | 869 | def add_file(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=False): 870 | results = [] 871 | abs_path = '' 872 | 873 | if os.path.isabs(f_path): 874 | abs_path = f_path 875 | 876 | if not os.path.exists(f_path): 877 | return results 878 | elif tree == 'SOURCE_ROOT': 879 | f_path = os.path.relpath(f_path, self.source_root) 880 | else: 881 | tree = '' 882 | 883 | if not parent: 884 | parent = self.root_group 885 | elif not isinstance(parent, PBXGroup): 886 | # assume it's an id 887 | parent = self.objects.get(parent, self.root_group) 888 | 889 | file_ref = PBXFileReference.Create(f_path, tree, ignore_unknown_type=ignore_unknown_type) 890 | parent.add_child(file_ref) 891 | results.append(file_ref) 892 | 893 | # create a build file for the file ref 894 | if file_ref.build_phase and create_build_files: 895 | phases = self.get_build_phases(file_ref.build_phase) 896 | 897 | for phase in phases: 898 | build_file = PBXBuildFile.Create(file_ref, weak=weak) 899 | 900 | phase.add_build_file(build_file) 901 | results.append(build_file) 902 | 903 | if abs_path and tree == 'SOURCE_ROOT' \ 904 | and os.path.isfile(abs_path) \ 905 | and file_ref.build_phase == 'PBXFrameworksBuildPhase': 906 | library_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0]) 907 | self.add_library_search_paths([library_path], recursive=False) 908 | 909 | if abs_path and tree == 'SOURCE_ROOT' \ 910 | and not os.path.isfile(abs_path) \ 911 | and file_ref.build_phase == 'PBXFrameworksBuildPhase': 912 | framework_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0]) 913 | self.add_framework_search_paths([framework_path, '$(inherited)'], recursive=False) 914 | 915 | for r in results: 916 | self.objects[r.id] = r 917 | 918 | if results: 919 | self.modified = True 920 | 921 | return results 922 | 923 | def check_and_repair_framework(self, base): 924 | name = os.path.basename(base) 925 | 926 | if ".framework" in name: 927 | basename = name[:-len(".framework")] 928 | 929 | finalHeaders = os.path.join(base, "Headers") 930 | finalCurrent = os.path.join(base, "Versions/Current") 931 | finalLib = os.path.join(base, basename) 932 | srcHeaders = "Versions/A/Headers" 933 | srcCurrent = "A" 934 | srcLib = "Versions/A/" + basename 935 | 936 | if not os.path.exists(finalHeaders): 937 | os.symlink(srcHeaders, finalHeaders) 938 | if not os.path.exists(finalCurrent): 939 | os.symlink(srcCurrent, finalCurrent) 940 | if not os.path.exists(finalLib): 941 | os.symlink(srcLib, finalLib) 942 | 943 | 944 | def get_file_id_by_path(self, f_path): 945 | for k, v in self.objects.iteritems(): 946 | if str(v.get('path')) == f_path: 947 | return k 948 | return 0 949 | 950 | 951 | def remove_file_by_path(self, f_path, recursive=True): 952 | id = self.get_file_id_by_path(f_path) 953 | if id != 0: 954 | self.remove_file(id, recursive=recursive) 955 | return 956 | 957 | 958 | def remove_file(self, id, recursive=True): 959 | if not PBXType.IsGuid(id): 960 | id = id.id 961 | 962 | if id in self.objects: 963 | self.objects.remove(id) 964 | # Remove from PBXResourcesBuildPhase and PBXSourcesBuildPhase if necessary 965 | buildFiles = [f for f in self.objects.values() if f.get('isa') == 'PBXBuildFile'] 966 | for buildFile in buildFiles: 967 | if id == buildFile.get('fileRef'): 968 | key = buildFile.id 969 | PBXRBP = [f for f in self.objects.values() if f.get('isa') == 'PBXResourcesBuildPhase'] 970 | PBXSBP = [f for f in self.objects.values() if f.get('isa') == 'PBXSourcesBuildPhase'] 971 | self.objects.remove(key) 972 | if len(PBXSBP) and PBXSBP[0].has_build_file(key): 973 | PBXSBP[0].remove_build_file(key) 974 | if len(PBXRBP) and PBXRBP[0].has_build_file(key): 975 | PBXRBP[0].remove_build_file(key) 976 | if recursive: 977 | groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'] 978 | 979 | for group in groups: 980 | if id in group['children']: 981 | group.remove_child(id) 982 | 983 | self.modified = True 984 | 985 | def remove_group(self, id, recursive = True): 986 | if not PBXType.IsGuid(id): 987 | id = id.id 988 | name = self.objects.get(id).get('path') 989 | children = self.objects.get(id).get('children') 990 | if name is None: 991 | name = id 992 | if id in self.objects: 993 | if recursive: 994 | for childKey in children: 995 | childValue = self.objects.get(childKey) 996 | if childValue.get('isa') == 'PBXGroup': 997 | self.remove_group(childKey, True) 998 | else: 999 | self.remove_file(childKey, False) 1000 | 1001 | self.objects.remove(id); 1002 | 1003 | def remove_group_by_name(self, name, recursive = True): 1004 | groups = self.get_groups_by_name(name) 1005 | if len(groups): 1006 | for group in groups: 1007 | self.remove_group(group.id, recursive) 1008 | 1009 | def move_file(self, id, dest_grp=None): 1010 | pass 1011 | 1012 | def apply_patch(self, patch_path, xcode_path): 1013 | if not os.path.isfile(patch_path) or not os.path.isdir(xcode_path): 1014 | print 'ERROR: couldn\'t apply "%s" to "%s"' % (patch_path, xcode_path) 1015 | return 1016 | 1017 | print 'applying "%s" to "%s"' % (patch_path, xcode_path) 1018 | 1019 | return subprocess.call(['patch', '-p1', '--forward', '--directory=%s' % xcode_path, '--input=%s' % patch_path]) 1020 | 1021 | def apply_mods(self, mod_dict, default_path=None): 1022 | if not default_path: 1023 | default_path = os.getcwd() 1024 | 1025 | keys = mod_dict.keys() 1026 | 1027 | for k in keys: 1028 | v = mod_dict.pop(k) 1029 | mod_dict[k.lower()] = v 1030 | 1031 | parent = mod_dict.pop('group', None) 1032 | 1033 | if parent: 1034 | parent = self.get_or_create_group(parent) 1035 | 1036 | excludes = mod_dict.pop('excludes', []) 1037 | 1038 | if excludes: 1039 | excludes = [re.compile(e) for e in excludes] 1040 | 1041 | compiler_flags = mod_dict.pop('compiler_flags', {}) 1042 | 1043 | for k, v in mod_dict.items(): 1044 | if k == 'patches': 1045 | for p in v: 1046 | if not os.path.isabs(p): 1047 | p = os.path.join(default_path, p) 1048 | 1049 | self.apply_patch(p, self.source_root) 1050 | elif k == 'folders': 1051 | # get and compile excludes list 1052 | # do each folder individually 1053 | for folder in v: 1054 | kwds = {} 1055 | 1056 | # if path contains ':' remove it and set recursive to False 1057 | if ':' in folder: 1058 | args = folder.split(':') 1059 | kwds['recursive'] = False 1060 | folder = args.pop(0) 1061 | 1062 | if os.path.isabs(folder) and os.path.isdir(folder): 1063 | pass 1064 | else: 1065 | folder = os.path.join(default_path, folder) 1066 | if not os.path.isdir(folder): 1067 | continue 1068 | 1069 | if parent: 1070 | kwds['parent'] = parent 1071 | 1072 | if excludes: 1073 | kwds['excludes'] = excludes 1074 | 1075 | self.add_folder(folder, **kwds) 1076 | elif k == 'headerpaths' or k == 'librarypaths': 1077 | paths = [] 1078 | 1079 | for p in v: 1080 | if p.endswith('/**'): 1081 | p = os.path.split(p)[0] 1082 | 1083 | if not os.path.isabs(p): 1084 | p = os.path.join(default_path, p) 1085 | 1086 | if not os.path.exists(p): 1087 | continue 1088 | 1089 | p = self.get_relative_path(p) 1090 | paths.append(os.path.join('$(SRCROOT)', p, "**")) 1091 | 1092 | if k == 'headerpaths': 1093 | self.add_header_search_paths(paths) 1094 | else: 1095 | self.add_library_search_paths(paths) 1096 | elif k == 'other_cflags': 1097 | self.add_other_cflags(v) 1098 | elif k == 'other_ldflags': 1099 | self.add_other_ldflags(v) 1100 | elif k == 'libs' or k == 'frameworks' or k == 'files': 1101 | paths = {} 1102 | 1103 | for p in v: 1104 | kwds = {} 1105 | 1106 | if ':' in p: 1107 | args = p.split(':') 1108 | p = args.pop(0) 1109 | 1110 | if 'weak' in args: 1111 | kwds['weak'] = True 1112 | 1113 | file_path = os.path.join(default_path, p) 1114 | search_path, file_name = os.path.split(file_path) 1115 | 1116 | if [m for m in excludes if re.match(m, file_name)]: 1117 | continue 1118 | 1119 | try: 1120 | expr = re.compile(file_name) 1121 | except re.error: 1122 | expr = None 1123 | 1124 | if expr and os.path.isdir(search_path): 1125 | file_list = os.listdir(search_path) 1126 | 1127 | for f in file_list: 1128 | if [m for m in excludes if re.match(m, f)]: 1129 | continue 1130 | 1131 | if re.search(expr, f): 1132 | kwds['name'] = f 1133 | paths[os.path.join(search_path, f)] = kwds 1134 | p = None 1135 | 1136 | if k == 'libs': 1137 | kwds['parent'] = self.get_or_create_group('Libraries', parent=parent) 1138 | elif k == 'frameworks': 1139 | kwds['parent'] = self.get_or_create_group('Frameworks', parent=parent) 1140 | 1141 | if p: 1142 | kwds['name'] = file_name 1143 | 1144 | if k == 'libs': 1145 | p = os.path.join('usr', 'lib', p) 1146 | kwds['tree'] = 'SDKROOT' 1147 | elif k == 'frameworks': 1148 | p = os.path.join('System', 'Library', 'Frameworks', p) 1149 | kwds['tree'] = 'SDKROOT' 1150 | elif k == 'files' and not os.path.exists(file_path): 1151 | # don't add non-existent files to the project. 1152 | continue 1153 | 1154 | paths[p] = kwds 1155 | 1156 | new_files = self.verify_files([n.get('name') for n in paths.values()]) 1157 | add_files = [(k, v) for k, v in paths.items() if v.get('name') in new_files] 1158 | 1159 | for path, kwds in add_files: 1160 | kwds.pop('name', None) 1161 | 1162 | if 'parent' not in kwds and parent: 1163 | kwds['parent'] = parent 1164 | 1165 | self.add_file(path, **kwds) 1166 | 1167 | if compiler_flags: 1168 | for k, v in compiler_flags.items(): 1169 | filerefs = [] 1170 | 1171 | for f in v: 1172 | filerefs.extend([fr.id for fr in self.objects.values() if fr.get('isa') == 'PBXFileReference' 1173 | and fr.get('name') == f]) 1174 | 1175 | buildfiles = [bf for bf in self.objects.values() if bf.get('isa') == 'PBXBuildFile' 1176 | and bf.get('fileRef') in filerefs] 1177 | 1178 | for bf in buildfiles: 1179 | if bf.add_compiler_flag(k): 1180 | self.modified = True 1181 | 1182 | def backup(self, file_name=None, backup_name=None): 1183 | if not file_name: 1184 | file_name = self.pbxproj_path 1185 | 1186 | if not backup_name: 1187 | backup_name = "%s.%s.backup" % (file_name, datetime.datetime.now().strftime('%d%m%y-%H%M%S')) 1188 | 1189 | shutil.copy2(file_name, backup_name) 1190 | return backup_name 1191 | 1192 | def save(self, file_name=None, old_format=False, sort=False): 1193 | if old_format : 1194 | self.save_format_xml(file_name) 1195 | else: 1196 | self.save_new_format(file_name, sort) 1197 | 1198 | def save_format_xml(self, file_name=None): 1199 | """Saves in old (xml) format""" 1200 | if not file_name: 1201 | file_name = self.pbxproj_path 1202 | 1203 | # This code is adapted from plistlib.writePlist 1204 | with open(file_name, "w") as f: 1205 | writer = PBXWriter(f) 1206 | writer.writeln("") 1207 | writer.writeValue(self.data) 1208 | writer.writeln("") 1209 | 1210 | def save_new_format(self, file_name=None, sort=False): 1211 | """Save in Xcode 3.2 compatible (new) format""" 1212 | if not file_name: 1213 | file_name = self.pbxproj_path 1214 | 1215 | # process to get the section's info and names 1216 | objs = self.data.get('objects') 1217 | sections = dict() 1218 | uuids = dict() 1219 | 1220 | for key in objs: 1221 | l = list() 1222 | 1223 | if objs.get(key).get('isa') in sections: 1224 | l = sections.get(objs.get(key).get('isa')) 1225 | 1226 | l.append(tuple([key, objs.get(key)])) 1227 | sections[objs.get(key).get('isa')] = l 1228 | 1229 | if 'name' in objs.get(key): 1230 | uuids[key] = objs.get(key).get('name') 1231 | elif 'path' in objs.get(key): 1232 | uuids[key] = objs.get(key).get('path') 1233 | else: 1234 | if objs.get(key).get('isa') == 'PBXProject': 1235 | uuids[objs.get(key).get('buildConfigurationList')] = 'Build configuration list for PBXProject "Unity-iPhone"' 1236 | elif objs.get(key).get('isa')[0:3] == 'PBX': 1237 | uuids[key] = objs.get(key).get('isa')[3:-10] 1238 | else: 1239 | uuids[key] = 'Build configuration list for PBXNativeTarget "TARGET_NAME"' 1240 | 1241 | ro = self.data.get('rootObject') 1242 | uuids[ro] = 'Project object' 1243 | 1244 | for key in objs: 1245 | # transitive references (used in the BuildFile section) 1246 | if 'fileRef' in objs.get(key) and objs.get(key).get('fileRef') in uuids: 1247 | uuids[key] = uuids[objs.get(key).get('fileRef')] 1248 | 1249 | # transitive reference to the target name (used in the Native target section) 1250 | if objs.get(key).get('isa') == 'PBXNativeTarget': 1251 | uuids[objs.get(key).get('buildConfigurationList')] = uuids[objs.get(key).get('buildConfigurationList')].replace('TARGET_NAME', uuids[key]) 1252 | 1253 | self.uuids = uuids 1254 | self.sections = sections 1255 | 1256 | out = open(file_name, 'w') 1257 | out.write('// !$*UTF8*$!\n') 1258 | self._printNewXCodeFormat(out, self.data, '', enters=True, sort=sort) 1259 | out.close() 1260 | 1261 | @classmethod 1262 | def addslashes(cls, s): 1263 | d = {'"': '\\"', "'": "\\'", "\0": "\\\0", "\\": "\\\\", "\n":"\\n"} 1264 | return ''.join(d.get(c, c) for c in s) 1265 | 1266 | def _printNewXCodeFormat(self, out, root, deep, enters=True, sort=False): 1267 | if isinstance(root, IterableUserDict): 1268 | out.write('{') 1269 | 1270 | if enters: 1271 | out.write('\n') 1272 | 1273 | isa = root.pop('isa', '') 1274 | 1275 | if isa != '': # keep the isa in the first spot 1276 | if enters: 1277 | out.write('\t' + deep) 1278 | 1279 | out.write('isa = ') 1280 | self._printNewXCodeFormat(out, isa, '\t' + deep, enters=enters) 1281 | out.write(';') 1282 | 1283 | if enters: 1284 | out.write('\n') 1285 | else: 1286 | out.write(' ') 1287 | 1288 | for key in sorted(root.iterkeys()): # keep the same order as Apple. 1289 | if enters: 1290 | out.write('\t' + deep) 1291 | 1292 | if re.match(regex, key).group(0) == key: 1293 | out.write(key.encode("utf-8") + ' = ') 1294 | else: 1295 | out.write('"' + key.encode("utf-8") + '" = ') 1296 | 1297 | if key == 'objects': 1298 | out.write('{') # open the objects section 1299 | 1300 | if enters: 1301 | out.write('\n') 1302 | #root.remove('objects') # remove it to avoid problems 1303 | 1304 | sections = [ 1305 | ('PBXBuildFile', False), 1306 | ('PBXCopyFilesBuildPhase', True), 1307 | ('PBXFileReference', False), 1308 | ('PBXFrameworksBuildPhase', True), 1309 | ('PBXGroup', True), 1310 | ('PBXAggregateTarget', True), 1311 | ('PBXNativeTarget', True), 1312 | ('PBXProject', True), 1313 | ('PBXResourcesBuildPhase', True), 1314 | ('PBXShellScriptBuildPhase', True), 1315 | ('PBXSourcesBuildPhase', True), 1316 | ('XCBuildConfiguration', True), 1317 | ('XCConfigurationList', True), 1318 | ('PBXTargetDependency', True), 1319 | ('PBXVariantGroup', True), 1320 | ('PBXReferenceProxy', True), 1321 | ('PBXContainerItemProxy', True), 1322 | ('XCVersionGroup', True)] 1323 | 1324 | for section in sections: # iterate over the sections 1325 | if self.sections.get(section[0]) is None: 1326 | continue 1327 | 1328 | out.write('\n/* Begin %s section */' % section[0].encode("utf-8")) 1329 | self.sections.get(section[0]).sort(cmp=lambda x, y: cmp(x[0], y[0])) 1330 | 1331 | if sort and section[0] == 'PBXGroup': 1332 | for entry in self.sections.get(section[0]): 1333 | entry[1]['children'] = sorted(entry[1]['children'], 1334 | key=lambda x: self.uuids[x].encode("utf-8")) 1335 | 1336 | for pair in self.sections.get(section[0]): 1337 | key = pair[0] 1338 | value = pair[1] 1339 | out.write('\n') 1340 | 1341 | if enters: 1342 | out.write('\t\t' + deep) 1343 | 1344 | out.write(key.encode("utf-8")) 1345 | 1346 | if key in self.uuids: 1347 | out.write(" /* " + self.uuids[key].encode("utf-8") + " */") 1348 | 1349 | out.write(" = ") 1350 | self._printNewXCodeFormat(out, value, '\t\t' + deep, enters=section[1]) 1351 | out.write(';') 1352 | 1353 | out.write('\n/* End %s section */\n' % section[0].encode("utf-8")) 1354 | 1355 | out.write(deep + '\t}') # close of the objects section 1356 | else: 1357 | self._printNewXCodeFormat(out, root[key], '\t' + deep, enters=enters) 1358 | 1359 | out.write(';') 1360 | 1361 | if enters: 1362 | out.write('\n') 1363 | else: 1364 | out.write(' ') 1365 | 1366 | root['isa'] = isa # restore the isa for further calls 1367 | 1368 | if enters: 1369 | out.write(deep) 1370 | 1371 | out.write('}') 1372 | 1373 | elif isinstance(root, UserList): 1374 | out.write('(') 1375 | 1376 | if enters: 1377 | out.write('\n') 1378 | 1379 | for value in root: 1380 | if enters: 1381 | out.write('\t' + deep) 1382 | 1383 | self._printNewXCodeFormat(out, value, '\t' + deep, enters=enters) 1384 | out.write(',') 1385 | 1386 | if enters: 1387 | out.write('\n') 1388 | 1389 | if enters: 1390 | out.write(deep) 1391 | 1392 | out.write(')') 1393 | 1394 | else: 1395 | if len(root) > 0 and re.match(regex, root).group(0) == root: 1396 | out.write(root.encode("utf-8")) 1397 | else: 1398 | out.write('"' + XcodeProject.addslashes(root.encode("utf-8")) + '"') 1399 | 1400 | if root in self.uuids: 1401 | out.write(" /* " + self.uuids[root].encode("utf-8") + " */") 1402 | 1403 | @classmethod 1404 | def Load(cls, path, pure_python=False): 1405 | if pure_python: 1406 | import openstep_parser as osp 1407 | tree = osp.OpenStepDecoder.ParseFromFile(open(path, 'r')) 1408 | else: 1409 | cls.plutil_path = os.path.join(os.path.split(__file__)[0], 'plutil') 1410 | 1411 | if not os.path.isfile(XcodeProject.plutil_path): 1412 | cls.plutil_path = 'plutil' 1413 | 1414 | # load project by converting to xml and then convert that using plistlib 1415 | p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', '-', path], stdout=subprocess.PIPE) 1416 | stdout, stderr = p.communicate() 1417 | 1418 | # If the plist was malformed, return code will be non-zero 1419 | if p.returncode != 0: 1420 | print stdout 1421 | return None 1422 | 1423 | tree = plistlib.readPlistFromString(stdout) 1424 | return XcodeProject(tree, path) 1425 | 1426 | @classmethod 1427 | def LoadFromXML(cls, path): 1428 | tree = plistlib.readPlist(path) 1429 | return XcodeProject(tree, path) 1430 | 1431 | 1432 | # The code below was adapted from plistlib.py. 1433 | 1434 | class PBXWriter(plistlib.PlistWriter): 1435 | def writeValue(self, value): 1436 | if isinstance(value, (PBXList, PBXDict)): 1437 | plistlib.PlistWriter.writeValue(self, value.data) 1438 | else: 1439 | plistlib.PlistWriter.writeValue(self, value) 1440 | 1441 | def simpleElement(self, element, value=None): 1442 | """ 1443 | We have to override this method to deal with Unicode text correctly. 1444 | Non-ascii characters have to get encoded as character references. 1445 | """ 1446 | if value is not None: 1447 | value = _escapeAndEncode(value) 1448 | self.writeln("<%s>%s" % (element, value, element)) 1449 | else: 1450 | self.writeln("<%s/>" % element) 1451 | 1452 | 1453 | # Regex to find any control chars, except for \t \n and \r 1454 | _controlCharPat = re.compile( 1455 | r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f" 1456 | r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]") 1457 | 1458 | 1459 | def _escapeAndEncode(text): 1460 | m = _controlCharPat.search(text) 1461 | if m is not None: 1462 | raise ValueError("strings can't contains control characters; " 1463 | "use plistlib.Data instead") 1464 | text = text.replace("\r\n", "\n") # convert DOS line endings 1465 | text = text.replace("\r", "\n") # convert Mac line endings 1466 | text = text.replace("&", "&") # escape '&' 1467 | text = text.replace("<", "<") # escape '<' 1468 | text = text.replace(">", ">") # escape '>' 1469 | return text.encode("ascii", "xmlcharrefreplace") # encode as ascii with xml character references 1470 | -------------------------------------------------------------------------------- /release.json: -------------------------------------------------------------------------------- 1 | { 2 | "WARNING_CFLAGS":"-Wall -Wextra -Wno-unused-parameter -Wno-unused-variable", 3 | "CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER":"YES", 4 | "CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND":"YES", 5 | "CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY":"YES", 6 | "RUN_CLANG_STATIC_ANALYZER":"YES", 7 | "GCC_TREAT_WARNINGS_AS_ERRORS":"YES", 8 | "CLANG_STATIC_ANALYZER_MODE":"deep" 9 | } -------------------------------------------------------------------------------- /xcode_auto_configurator.py: -------------------------------------------------------------------------------- 1 | #The MIT License (MIT) 2 | # 3 | #Copyright (c) 2015 Jiakai Lian 4 | # 5 | #Permission is hereby granted, free of charge, to any person obtaining a copy 6 | #of this software and associated documentation files (the "Software"), to deal 7 | #in the Software without restriction, including without limitation the rights 8 | #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | #copies of the Software, and to permit persons to whom the Software is 10 | #furnished to do so, subject to the following conditions: 11 | # 12 | #The above copyright notice and this permission notice shall be included in all 13 | #copies or substantial portions of the Software. 14 | # 15 | #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | #SOFTWARE. 22 | 23 | #!/usr/bin/python 24 | 25 | from mod_pbxproj import XcodeProject 26 | import sys 27 | import json 28 | 29 | #print 'Number of arguments:', len(sys.argv), 'arguments.' 30 | #print 'Argument List:', str(sys.argv) 31 | 32 | class Configuration: 33 | def __init__(self,jsonFileName): 34 | self.jsonFileName = jsonFileName 35 | #find config name 36 | self.name = jsonFileName.split(".")[0].lower() 37 | 38 | #load json data 39 | with open(jsonFileName) as data_file: 40 | self.jsonContent = json.load(data_file) 41 | 42 | 43 | if len(sys.argv) < 2: 44 | raise Exception("need project.pbxproj file path") 45 | 46 | 47 | #read the file path 48 | filePath = sys.argv[1] 49 | 50 | if len(sys.argv) > 2: 51 | jsonFiles = list(sys.argv) 52 | del jsonFiles[0:2] 53 | else: 54 | jsonFiles = ["debug.json","release.json"] 55 | 56 | print jsonFiles 57 | 58 | #create configuration objects 59 | dictOfConfig = dict(); 60 | for file in jsonFiles: 61 | config = Configuration(file) 62 | dictOfConfig[config.name] = config 63 | 64 | #load project file and create a backup 65 | project = XcodeProject.Load(filePath) 66 | project.backup() 67 | 68 | rootObject = project["rootObject"] 69 | projectObject = project["objects"][rootObject]["buildConfigurationList"] 70 | 71 | for id in project["objects"][projectObject]["buildConfigurations"]: 72 | name = project["objects"][id]["name"].lower() 73 | 74 | #if this configuration need to be changed 75 | if dictOfConfig[name] is not None: 76 | entry = project["objects"][id]["buildSettings"] 77 | #for each setting in the json, apply to the target entry 78 | for key in dictOfConfig[name].jsonContent: 79 | entry[key] = dictOfConfig[name].jsonContent[key] 80 | 81 | project.save() 82 | 83 | print "Auto Configuration Complete" --------------------------------------------------------------------------------