├── .gitignore ├── README.md └── VerifyNoBS.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | .DS_Store 6 | wildcat_settings 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | IDEWorkspaceChecks.plist 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | ## Playgrounds 37 | timeline.xctimeline 38 | playground.xcworkspace 39 | 40 | # Swift Package Manager 41 | # 42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 43 | # Packages/ 44 | # Package.pins 45 | .build/ 46 | 47 | # CocoaPods 48 | # 49 | # We recommend against adding the Pods directory to your .gitignore. However 50 | # you should judge for yourself, the pros and cons are mentioned at: 51 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 52 | # 53 | # Pods/ 54 | 55 | # Carthage 56 | # 57 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 58 | # Carthage/Checkouts 59 | 60 | Carthage/Build 61 | 62 | # fastlane 63 | # 64 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 65 | # screenshots whenever they are needed. 66 | # For more information about the recommended setup visit: 67 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 68 | 69 | fastlane/report.xml 70 | fastlane/Preview.html 71 | fastlane/screenshots 72 | fastlane/test_output 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VerifyNoBS (Build Settings) 2 | 3 | This script is meant to be called from an Xcode run script build phase. 4 | It verifies there are no buildSettings embedded in the Xcode project as it is preferable to have build settings specified in .xcconfig files. 5 | 6 | ## How to use: 7 | Put this script in a folder called 'buildscripts' next to your xcode project. 8 | Then, add a "Run Script Build Phase" to one of your targets with this as the script 9 | 10 | xcrun -sdk macosx swift buildscripts/VerifyNoBS.swift --xcode ${PROJECT_DIR}/${PROJECT_NAME}.xcodeproj/project.pbxproj 11 | 12 | This will run the script and fail your build if any build settings are detected in your project file. 13 | The `--xcode` switch will output any errors in a format that is picked up by Xcode so you can directly click on the error and jump to the offending spot in the project file. 14 | 15 | When running it without the `--xcode` switch errors will simply be written to stderr. 16 | -------------------------------------------------------------------------------- /VerifyNoBS.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/swift 2 | 3 | // This script is meant to be called from an Xcode run script build phase 4 | // It verifies there are no buildSettings embedded in the Xcode project 5 | // as it is preferable to have build settings specified in .xcconfig files 6 | 7 | // How to use: 8 | // Put this script in a folder called 'buildscripts' next to your xcode project 9 | // Then, add a Run script build phase to one of your targets with this as the script 10 | // 11 | // xcrun -sdk macosx swift buildscripts/VerifyNoBS.swift --xcode ${PROJECT_DIR}/${PROJECT_NAME}.xcodeproj/project.pbxproj 12 | // 13 | 14 | import Darwin 15 | import Foundation 16 | 17 | /// A message with its file name and location 18 | struct LocatedMessage { 19 | let message: String 20 | let fileUrl: URL 21 | let line: Int 22 | } 23 | 24 | /// Utility to process the pbxproj file 25 | struct BuildSettingsVerifier { 26 | 27 | public enum ProcessXcodeprojResult { 28 | case foundBuildSettings([LocatedMessage]) 29 | case error(String) 30 | case success(String) 31 | } 32 | 33 | /// Mode to run the utility in. Mode defines the output format 34 | public enum Mode { 35 | /// Write errors to stderr 36 | case cmd 37 | /// Write errors to stdout in a format that is picked up by Xcode 38 | case xcode 39 | } 40 | 41 | /// The mode to run in 42 | let mode: Mode 43 | 44 | /// The absolute file URL to the pbxproj file 45 | let projUrl: URL 46 | 47 | init(mode: Mode, projUrl: URL) { 48 | self.mode = mode 49 | self.projUrl = projUrl 50 | } 51 | 52 | /// Reports an error either to stderr or to stdout, depending on the mode 53 | func reportError(message: String, fileUrl: URL? = nil, line: Int? = nil) { 54 | switch mode { 55 | case .cmd: 56 | let stderr = FileHandle.standardError 57 | if let data = "\(message)\n".data(using: String.Encoding.utf8, allowLossyConversion: false) { 58 | stderr.write(data) 59 | } else { 60 | print("There was an error. Could not convert error message to printable string") 61 | } 62 | case .xcode: 63 | var messageParts = [String]() 64 | 65 | if let fileUrl = fileUrl { 66 | messageParts.append("\(fileUrl.path):") 67 | } 68 | 69 | if let line = line { 70 | messageParts.append("\(line): ") 71 | } 72 | 73 | messageParts.append("error: \(message)") 74 | 75 | print(messageParts.joined()) 76 | } 77 | } 78 | 79 | /// Inspect the pbxproj file for non-empty buildSettings 80 | func processXcodeprojAt(url: URL) -> ProcessXcodeprojResult { 81 | let startTime = Date() 82 | guard let xcodeproj = try? String(contentsOf: url, encoding: String.Encoding.utf8) else { 83 | return .error("Failed to read xcodeproj contents from \(url)") 84 | } 85 | let lines = xcodeproj.components(separatedBy: CharacterSet.newlines) 86 | print("Found \(lines.count) lines") 87 | 88 | var locatedMessages: [LocatedMessage] = [] 89 | var inBuildSettingsBlock = false 90 | for (lineIndex, nthLine) in lines.enumerated() { 91 | if inBuildSettingsBlock { 92 | if nthLine.range(of: "\\u007d[:space:]*;", options: .regularExpression) != nil { 93 | inBuildSettingsBlock = false 94 | } else if nthLine.range(of: "CODE_SIGN_IDENTITY") != nil { 95 | 96 | } else { 97 | let message = mode == .cmd ? " \(nthLine)\n" : "Setting '\(nthLine.trimmingCharacters(in: .whitespacesAndNewlines))' should be in an xcconfig file" 98 | locatedMessages.append(LocatedMessage( 99 | message: message, 100 | fileUrl: url, 101 | line: lineIndex + 1 102 | )) 103 | } 104 | } else { 105 | if nthLine.range(of: "buildSettings[:space:]*=", options: .regularExpression) != nil { 106 | inBuildSettingsBlock = true 107 | } 108 | } 109 | } 110 | 111 | let timeInterval = Date().timeIntervalSince(startTime) 112 | print("Process took \(timeInterval) seconds") 113 | if locatedMessages.count > 0 { 114 | return .foundBuildSettings(locatedMessages) 115 | } 116 | return .success(":-)") 117 | } 118 | 119 | public func verify() -> Int32 { 120 | print("Verifying there are no build settings...") 121 | 122 | let result = processXcodeprojAt(url: projUrl) 123 | 124 | switch result { 125 | case .error(let str): 126 | reportError(message: "Error verifying build settings: \(str)") 127 | return EXIT_FAILURE 128 | case .foundBuildSettings(let locatedMessages): 129 | reportError(message: "Found build settings in project file") 130 | for msg in locatedMessages { 131 | reportError(message: msg.message, fileUrl: msg.fileUrl, line: msg.line) 132 | } 133 | return EXIT_FAILURE 134 | case .success: 135 | print("No build settings found in project file") 136 | return EXIT_SUCCESS 137 | } 138 | } 139 | } 140 | 141 | var commandLineArgs = CommandLine.arguments.dropFirst() 142 | //print("processArgs were \(commandLineArgs)") 143 | 144 | if commandLineArgs.count < 1 { 145 | print("Usage: \(#file) [--xcode] /path/to/Project.xcodeproj/project.pbxproj") 146 | exit(EXIT_FAILURE) 147 | } else { 148 | let xcodeProjFilePath = commandLineArgs.removeLast() 149 | let mode: BuildSettingsVerifier.Mode = commandLineArgs.count > 0 && commandLineArgs.last == "--xcode" ? .xcode : .cmd 150 | let myUrl = URL(fileURLWithPath: xcodeProjFilePath) 151 | let verifier = BuildSettingsVerifier(mode: mode, projUrl: myUrl) 152 | let exitCode = verifier.verify() 153 | 154 | exit(exitCode) 155 | } 156 | --------------------------------------------------------------------------------