| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import Foundation
- import FirebaseManifest
- import Utils
- private enum Constants {}
- extension Constants {
- static let localSpecRepoName = "specstesting"
- static let specRepo = "https://github.com/firebase/SpecsTesting"
- static let sdkRepo = "https://github.com/firebase/firebase-ios-sdk"
- static let testingTagPrefix = "testing-"
- static let cocoapodsDir =
- "\(ProcessInfo.processInfo.environment["HOME"]!)/.cocoapods/repos/\(localSpecRepoName)"
- static let versionFetchPatterns = [
- "json": "\"version\"[[:space:]]*:[[:space:]]*\"(.*)\"",
- "podspec": "\\.version[[:space:]]*=[[:space:]]*\'([^~=><].*)\'",
- ]
- }
- enum InitializeSpecTesting {
- enum VersionFetchError: Error {
- case noMatchesCaught
- case multipleMatches
- case noSubgroupCaught
- }
- static func setupRepo(sdkRepoURL: URL) {
- let manifest = FirebaseManifest.shared
- addSpecRepo(repoURL: Constants.specRepo)
- addTestingTag(path: sdkRepoURL, manifest: manifest)
- updatePodspecs(path: sdkRepoURL, manifest: manifest)
- copyPodspecs(from: sdkRepoURL, manifest: manifest)
- }
- // The SpecsTesting repo will be added to `${HOME}/.cocoapods/`, and all
- // podspecs under this dir will be the source of the specs testing.
- private static func addSpecRepo(repoURL: String,
- podRepoName: String = Constants.localSpecRepoName) {
- let result = Shell.executeCommandFromScript("pod repo remove \(podRepoName)")
- switch result {
- case let .error(_, output):
- print("\(podRepoName) was not properly removed. \(podRepoName) probably" +
- "does not exist in local.\n \(output)")
- case .success:
- print("\(podRepoName) was removed.")
- }
- Shell.executeCommand("pod repo add \(podRepoName) \(repoURL)")
- }
- // Add a testing tag to the head of the branch.
- private static func addTestingTag(path sdkRepoPath: URL, manifest: FirebaseManifest.Manifest) {
- // Pods could have different versions, like `8.11.0` and `8.11.0-beta`.
- // These versions should be part of tags, so a warning from `pod spec lint`
- // could be avoided.
- // ```
- // The version should be included in the Git tag.
- // ```
- // The tag should include `s.version`, e.g.
- // If "s.version = '8.11.0-beta'", the tag should include 8.11.0-beta to
- // avoid trigerring the warning.
- for pod in manifest.pods {
- let testingTag = Constants.testingTagPrefix + manifest.versionString(pod)
- // Add or update the testing tag to the local sdk repo.
- Shell.executeCommand("git tag -af \(testingTag) -m 'spectesting'", workingDir: sdkRepoPath)
- }
- }
- // Update the podspec source.
- private static func updatePodspecs(path: URL, manifest: FirebaseManifest.Manifest) {
- for pod in manifest.pods {
- let version = manifest.versionString(pod)
- if !pod.isClosedSource {
- // Replace git and tag in the source of a podspec.
- // Before:
- // s.source = {
- // :git => 'https://github.com/firebase/firebase-ios-sdk.git',
- // :tag => 'CocoaPods-' + s.version.to_s
- // }
- // After `sed`:
- // s.source = {
- // :git => '\(path.path)',
- // :tag => 'testing-\(version)',
- // }
- Shell.executeCommand(
- "sed -i.bak -e \"s|\\(.*\\:git =>[[:space:]]*\\).*|\\1'\(path.path)',| ; " +
- "s|\\(.*\\:tag =>[[:space:]]*\\).*|\\1'\(Constants.testingTagPrefix + version)',|\" \(pod.name).podspec",
- workingDir: path
- )
- }
- }
- }
- // Copy updated specs to the `${HOME}/.cocoapods/` dir.
- private static func copyPodspecs(from specsDir: URL, manifest: FirebaseManifest.Manifest) {
- let path = specsDir.appendingPathComponent("*.podspec").path
- let paths = Shell.executeCommandFromScript("ls \(path)", outputToConsole: false)
- var candidateSpecs: [String]?
- switch paths {
- case let .error(_, output):
- print("specs are not properly read, \(output)")
- case let .success(output):
- candidateSpecs = output.trimmingCharacters(in: .whitespacesAndNewlines)
- .components(separatedBy: "\n")
- }
- guard let specs = candidateSpecs else {
- print("There are no files ending with `podspec` detected.")
- return
- }
- for spec in specs {
- let specInfo = fetchPodVersion(from: URL(fileURLWithPath: spec))
- // Create directories `${HOME}/.cocoapods/${Pod}/${version}`
- let podDirURL = createPodDirctory(
- specRepoPath: Constants.cocoapodsDir,
- podName: specInfo.name,
- version: specInfo.version
- )
- // Copy updated podspecs to directories `${HOME}/.cocoapods/${Pod}/${version}`
- Shell.executeCommand("cp -rf \(spec) \(podDirURL)")
- }
- }
- private static func fetchPodVersion(from path: URL) -> (name: String, version: String) {
- var contents = ""
- var podName = ""
- var version = ""
- do {
- contents = try String(contentsOfFile: path.path, encoding: .utf8)
- } catch {
- fatalError("Could not read the podspec. \(error)")
- }
- // Closed source podspecs, e.g. `GoogleAppMeasurement.podspec`.
- if path.pathExtension == "json" {
- // Remove both extenstions of `podspec` and `json`.
- podName = path.deletingPathExtension().deletingPathExtension().lastPathComponent
- } else if path.pathExtension == "podspec" {
- podName = path.deletingPathExtension().lastPathComponent
- }
- guard let versionPattern = Constants.versionFetchPatterns[path.pathExtension] else {
- fatalError("Regex pattern for \(path.pathExtension) is not found.")
- }
- do {
- version = try matchVersion(from: contents, withPattern: versionPattern)
- } catch VersionFetchError.noMatchesCaught {
- fatalError(
- "Podspec from '\(path.path)' cannot find a version with the following regex\n\(versionPattern)"
- )
- } catch VersionFetchError.noSubgroupCaught {
- fatalError(
- "A subgroup of version from Podspec, '\(path.path)', is not caught from the pattern\n\(versionPattern)"
- )
- } catch VersionFetchError.multipleMatches {
- print("found multiple version matches from \(path.path).")
- fatalError(
- "There should have only one version matching the regex pattern, please update the pattern\n\(versionPattern)"
- )
- } catch {
- fatalError("Version is not caught properly. \(error)")
- }
- return (podName, version)
- }
- private static func matchVersion(from content: String,
- withPattern regex: String) throws -> String {
- let versionMatches = try content.match(regex: regex)
- if versionMatches.isEmpty {
- throw VersionFetchError.noMatchesCaught
- }
- // One subgroup in the regex should be for the version
- else if versionMatches[0].count < 2 {
- throw VersionFetchError.noSubgroupCaught
- }
- // There are more than one string matching the regex. There should be only
- // one version matching the regex.
- else if versionMatches.count > 1 {
- print(versionMatches)
- throw VersionFetchError.multipleMatches
- }
- return versionMatches[0][1]
- }
- private static func createPodDirctory(specRepoPath: String, podName: String,
- version: String) -> URL {
- guard let specRepoURL = URL(string: specRepoPath) else {
- fatalError("\(specRepoPath) does not exist.")
- }
- let podDirPath = specRepoURL.appendingPathComponent(podName).appendingPathComponent(version)
- if !FileManager.default.fileExists(atPath: podDirPath.absoluteString) {
- do {
- print("create path: \(podDirPath.absoluteString)")
- try FileManager.default.createDirectory(atPath: podDirPath.absoluteString,
- withIntermediateDirectories: true,
- attributes: nil)
- } catch {
- print(error.localizedDescription)
- }
- }
- return podDirPath
- }
- }
- extension String: Error {
- /// Returns an array of matching groups, which contains matched string and
- /// subgroups.
- ///
- /// - Parameters:
- /// - regex: A string of regex.
- /// - Returns: An array of array containing each match and its subgroups.
- func match(regex: String) throws -> [[String]] {
- do {
- let regex = try NSRegularExpression(pattern: regex, options: [])
- let nsString = self as NSString
- let results = regex.matches(
- in: self,
- options: [],
- range: NSMakeRange(0, nsString.length)
- )
- return results.map { result in
- (0 ..< result.numberOfRanges).map {
- nsString.substring(with: result.range(at: $0))
- }
- }
- } catch {
- fatalError("regex is invalid\n\(error.localizedDescription)")
- }
- }
- }
|