Setting the file. One moment. Main · Xcode Project Setup · firebase/agent-skills · Skills DocsRaw file
scripts/xcode_spm_setup/Sources/main.swift
Swift·236 lines·9 KB
first
?
.buildConfigurationList
?
.buildConfigurations
??
[]
10 let buildConfigs = targetConfigs + rootConfigs
11 var hasExplicitSetting = false
12 for configuration in buildConfigs {
13 if let userSandbox =
14 configuration.buildSettings["ENABLE_USER_SCRIPT_SANDBOXING"] as? String {
15 hasExplicitSetting = true
16 if userSandbox.uppercased() == "YES" {
17 return true
18 }
19 }
20 }
21
22 // If the value is absent, assume it is the default "YES"
23 return !hasExplicitSetting
24}
25
26func hasCrashlyticsRunScriptBuildPhase(project: PBXProj) -> Bool {
27 guard let nativeTargets = project.nativeTargets.first else {
28 return false
29 }
30
31 for phase in nativeTargets.buildPhases {
32 if phase.buildPhase == BuildPhase.runScript, let scriptPhase = phase as? PBXShellScriptBuildPhase {
33 if let script = scriptPhase.shellScript, script.contains("Crashlytics") {
34 return true
35 }
36 }
37 }
38
39 return false
40}
41
42func addCrashlyticsRunScriptBuildPhase(project: PBXProj) {
43 guard let nativeTarget = project.nativeTargets.first else {
44 print("Error: couldn't add the Crashlytics Run Script Build phase automatically, please add it manually")
45 return
46 }
47
48 var inputPaths = [
49 "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}",
50 "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}",
51 "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist",
52 "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist",
53 "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)"
54 ]
55
56 if isUserScriptSandboxingEnabled(project: project) {
57 inputPaths.append("${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}.debug.dylib")
58 }
59
60 let phase = PBXShellScriptBuildPhase(
61 files: [],
62 inputPaths: inputPaths,
63 outputPaths: [],
64 shellPath: "/bin/sh",
65 shellScript: "\"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\n",
66 runOnlyForDeploymentPostprocessing: false
67 )
68
69 project.add(object: phase)
70 nativeTarget.buildPhases.append(phase)
71}
72
73func setDwarfWithDsymDebugInformationFormat(project: PBXProj) {
74 let targetConfigs = project.nativeTargets.flatMap {
75 $0.buildConfigurationList?.buildConfigurations ?? []
76 }
77 let rootConfigs =
78 project.projects.first?.buildConfigurationList?.buildConfigurations ?? []
79 for configuration in targetConfigs + rootConfigs {
80 configuration.buildSettings["DEBUG_INFORMATION_FORMAT"] = "dwarf-with-dsym"
81 }
82}
83
84func main() {
85 let args = CommandLine.arguments
86 guard args.count >= 5 else {
87 print("Usage: swift run --package-path <path> xcode_spm_setup <Path/To/Project.xcodeproj> <RepoURL> <VersionRequirement> [--plist <Path/To/Plist>] <Product1> [Product2 ...]")
88 exit(1)
89 }
90
91 var arguments = args
92 _ = arguments.removeFirst() // executable name
93 let projectPath = Path(arguments.removeFirst())
94 let repoURL = arguments.removeFirst()
95 let versionRequirementString = arguments.removeFirst()
96
97 var plistPath: Path? = nil
98 if let plistIndex = arguments.firstIndex(of: "--plist"), plistIndex + 1 < arguments.count {
99 plistPath = Path(arguments[plistIndex + 1])
100 arguments.remove(at: plistIndex + 1)
101 arguments.remove(at: plistIndex)
102 }
103
104 let products = arguments
105
106 guard !products.isEmpty else {
107 print("Error: No products specified to link.")
108 exit(1)
109 }
110
111 do {
112 let xcodeproj = try XcodeProj(path: projectPath)
113 let pbxproj = xcodeproj.pbxproj
114
115 guard let rootObject = try pbxproj.rootProject() else {
116 print("Error: Could not find root project")
117 exit(1)
118 }
119
120 guard let target = pbxproj.nativeTargets.first else {
121 print("Error: No native targets found")
122 exit(1)
123 }
124
125 // 1. Add Plist to the project (Optional)
126 if let plistPath = plistPath {
127 print("Adding \(plistPath.lastComponent) to project...")
128 let mainGroup = rootObject.mainGroup
129
130 let appName = target.name
131 let groupToAddTo = mainGroup?.children.first(where: { $0.path == appName }) as? PBXGroup ?? mainGroup
132
133 // Only add if it doesn't already exist
134 if groupToAddTo?.children.contains(where: { $0.path == plistPath.lastComponent || $0.name == plistPath.lastComponent }) == false {
135 let fileRef = try groupToAddTo?.addFile(at: plistPath, sourceRoot: projectPath.parent())
136
137 if let fileRef = fileRef, let buildPhase = target.buildPhases.first(where: { $0.buildPhase == .resources }) as? PBXResourcesBuildPhase {
138 _ = try buildPhase.add(file: fileRef)
139 print("Successfully added \(plistPath.lastComponent) to resources build phase.")
140 }
141 } else {
142 print("\(plistPath.lastComponent) already exists in project.")
143 }
144 }
145
146 // 2. Add Swift Package Dependency
147 print("Adding Swift Package Dependency: \(repoURL)")
148
149 // Check if package already exists
150 let packageRef: XCRemoteSwiftPackageReference
151 if let existingPkg = rootObject.remotePackages.first(where: { $0.repositoryURL == repoURL }) {
152 packageRef = existingPkg
153 print("Package already present.")
154 } else {
155 packageRef = try rootObject.addSwiftPackage(
156 repositoryURL: repoURL,
157 productName: products.first!,
158 versionRequirement: .upToNextMajorVersion(versionRequirementString),
159 targetName: target.name
160 )
161 }
162
163 // 3. Link requested products
164 print("Linking products: \(products.joined(separator: ", "))")
165 var frameworksBuildPhase = target.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase }.first
166 if frameworksBuildPhase == nil {
167 let newPhase = PBXFrameworksBuildPhase()
168 pbxproj.add(object: newPhase)
169 target.buildPhases.append(newPhase)
170 frameworksBuildPhase = newPhase
171 }
172
173 for product in products {
174 // Check if product is already linked
175 if target.packageProductDependencies?.contains(where: { $0.productName == product }) == true {
176 print("Product \(product) is already linked.")
177 continue
178 }
179
180 let dependency = XCSwiftPackageProductDependency(productName: product, package: packageRef)
181 pbxproj.add(object: dependency)
182
183 if target.packageProductDependencies == nil { target.packageProductDependencies = [] }
184 target.packageProductDependencies?.append(dependency)
185
186 let buildFile = PBXBuildFile(product: dependency)
187 pbxproj.add(object: buildFile)
188
189 if frameworksBuildPhase?.files == nil { frameworksBuildPhase?.files = [] }
190 frameworksBuildPhase?.files?.append(buildFile)
191 }
192
193 // 4. Add -ObjC linker flag if adding Firebase
194 if products.contains(where: { $0.contains("Firebase") }) {
195 print("Adding -ObjC to OTHER_LDFLAGS...")
196 for configuration in target.buildConfigurationList?.buildConfigurations ?? [] {
197 var otherLdFlags: [String] = []
198 if let current = configuration.buildSettings["OTHER_LDFLAGS"] {
199 if let currentArray = current as? [String] {
200 otherLdFlags = currentArray
201 } else if let currentString = current as? String {
202 otherLdFlags = [currentString]
203 }
204 }
205
206 if !otherLdFlags.contains("-ObjC") {
207 otherLdFlags.append("-ObjC")
208 configuration.buildSettings["OTHER_LDFLAGS"] = otherLdFlags
209 print("Updated OTHER_LDFLAGS for configuration: \(configuration.name)")
210 }
211 }
212 }
213
214 if products.contains(where: { $0.contains("FirebaseCrashlytics")}) {
215 print("Setting the debug format to DWARF with dSYMs")
216 setDwarfWithDsymDebugInformationFormat(project: pbxproj)
217
218 print("Adding the Crashlytics Run Script Build phase")
219 if !hasCrashlyticsRunScriptBuildPhase(project: pbxproj) {
220 addCrashlyticsRunScriptBuildPhase(project: pbxproj)
221 } else {
222 print("Crashlytics Run Script Build phase already exists")
223 }
224 }
225
226 // Write changes
227 try xcodeproj.write(path: projectPath)
228 print("Successfully updated Xcode project!")
229
230 } catch {
231 print("Error: \(error)")
232 exit(1)
233 }
234}
235
236main()