Skip to content

Commit

Permalink
One version release process (#6724)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulb777 authored Oct 15, 2020
1 parent 6cfc7a2 commit dd2e23e
Show file tree
Hide file tree
Showing 16 changed files with 456 additions and 73 deletions.
16 changes: 11 additions & 5 deletions ZipBuilder/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import PackageDescription
let package = Package(
name: "ZipBuilder",
products: [
.executable(name: "firebase-pod-updater", targets: ["firebase-pod-updater"]),
.executable(name: "firebase-releaser", targets: ["FirebaseReleaser"]),
.executable(name: "ReleasePackager", targets: ["ZipBuilder"]),
],
dependencies: [
Expand All @@ -33,17 +33,23 @@ let package = Package(
],
targets: [
.target(
name: "firebase-pod-updater",
dependencies: ["ArgumentParser", "ManifestReader"]
name: "ZipBuilder",
dependencies: ["ArgumentParser", "ManifestReader", "Utils"]
),
.target(
name: "ZipBuilder",
dependencies: ["ArgumentParser", "ManifestReader"]
name: "FirebaseManifest"
),
.target(
name: "FirebaseReleaser",
dependencies: ["ArgumentParser", "FirebaseManifest", "Utils"]
),
.target(
name: "ManifestReader",
dependencies: ["SwiftProtobuf"]
),
.target(
name: "Utils"
),
.target(
name: "oss-manifest-generator",
dependencies: ["ArgumentParser", "ManifestReader"]
Expand Down
60 changes: 60 additions & 0 deletions ZipBuilder/Sources/FirebaseManifest/FirebaseManifest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2020 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

/// The manifest contents for a release.
/// Version should be updated every release.
/// The version and releasing fields of the non-Firebase pods should be reviewed every release.
public let shared = Manifest(
version: "7.0.0",
pods: [
Pod("GoogleUtilities", isFirebase: false, podVersion: "7.0.0", releasing: false),
Pod("GoogleDataTransport", isFirebase: false, podVersion: "8.0.0", releasing: true),

Pod("FirebaseCoreDiagnostics"),
Pod("FirebaseCore"),
Pod("FirebaseInstallations"),
Pod("FirebaseInstanceID"),
Pod("GoogleAppMeasurement", isClosedSource: true),
Pod("FirebaseAnalytics", isClosedSource: true),
Pod("FirebaseABTesting"),
Pod("FirebaseAppDistribution", isBeta: true),
Pod("FirebaseAuth"),
Pod("FirebaseCrashlytics"),
Pod("FirebaseDatabase"),
Pod("FirebaseDynamicLinks"),
Pod("FirebaseFirestore", allowWarnings: true),
Pod("FirebaseFirestoreSwift", isBeta: true),
Pod("FirebaseFunctions"),
Pod("FirebaseInAppMessaging", isBeta: true),
Pod("FirebaseMessaging"),
Pod("FirebasePerformance", isClosedSource: true),
Pod("FirebaseRemoteConfig"),
Pod("FirebaseStorage"),
Pod("FirebaseStorageSwift", isBeta: true),
Pod("FirebaseMLCommon", isClosedSource: true, isBeta: true),
Pod("FirebaseMLModelInterpreter", isClosedSource: true, isBeta: true),
Pod("FirebaseMLVision", isClosedSource: true, isBeta: true),
Pod("Firebase", allowWarnings: true),
]
)

/// Manifest describing the contents of a Firebase release.
public struct Manifest {
public let version: String
public let pods: [Pod]
}
57 changes: 57 additions & 0 deletions ZipBuilder/Sources/FirebaseManifest/Pod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2020 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

/// Struct describing Firebase pods to release.
public struct Pod {
public let name: String
public let isClosedSource: Bool
public let isBeta: Bool
public let isFirebase: Bool
public let allowWarnings: Bool // Allow validation warnings. Ideally these should all be false
public let podVersion: String? // Non-Firebase pods have their own version
public let releasing: Bool // Non-Firebase pods may not release

init(_ name: String,
isClosedSource: Bool = false,
isBeta: Bool = false,
isFirebase: Bool = true,
allowWarnings: Bool = false,
podVersion: String? = nil,
releasing: Bool = true) {
self.name = name
self.isClosedSource = isClosedSource
self.isBeta = isBeta
self.isFirebase = isFirebase
self.allowWarnings = allowWarnings
self.podVersion = podVersion
self.releasing = releasing
}

public func podspecName() -> String {
return isClosedSource ? "\(name).podspec.json" : "\(name).podspec"
}

/// Closed source pods do not validate on Xcode 12 until they support the ARM simulator slice.
public func skipImportValidation() -> String {
if isClosedSource || name == "Firebase" {
return "-skip-import-validation"
} else {
return ""
}
}
}
135 changes: 135 additions & 0 deletions ZipBuilder/Sources/FirebaseReleaser/InitializeRelease.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2020 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

struct InitializeRelease {
static func setupRepo(gitRoot: URL) -> String {
let manifest = FirebaseManifest.shared
let branch = createReleaseBranch(path: gitRoot, version: manifest.version)
updatePodspecs(path: gitRoot, manifest: manifest)
updatePodfiles(path: gitRoot, version: manifest.version)
return branch
}

/// The branch is based on the minor version to represent this is the branch for subsequent
/// patches.
private static func createReleaseBranch(path: URL, version: String) -> String {
let versionParts = version.split(separator: ".")
let minorVersion = "\(versionParts[0]).\(versionParts[1])"
let branch = "release-\(minorVersion)"
Shell.executeCommand("git checkout master", workingDir: path)
Shell.executeCommand("git pull", workingDir: path)
Shell.executeCommand("git checkout -b \(branch)", workingDir: path)
return branch
}

/// Update the podspec versions.
private static func updatePodspecs(path: URL, manifest: FirebaseManifest.Manifest) {
for pod in manifest.pods {
if !pod.isClosedSource {
if pod.name == "Firebase" {
updateFirebasePodspec(path: path, manifest: manifest)
} else {
let version = pod.podVersion ??
(pod.isBeta ? manifest.version + "-beta" : manifest.version)

// Patch the new version to the podspec's version attribute.
Shell.executeCommand("sed -i.bak -e \"s/\\(\\.version.*=[[:space:]]*'\\).*'/\\1" +
"\(version)'/\" \(pod.name).podspec", workingDir: path)
}
}
}
}

// This function patches the versions in the Firebase.podspec. It uses Swift instead of sed
// like the other version patching.
// TODO: Choose one or the other mechanism.
// TODO: If we keep Swift, consider using Scanner.
private static func updateFirebasePodspec(path: URL, manifest: FirebaseManifest.Manifest) {
let podspecFile = path.appendingPathComponent("Firebase.podspec")
var contents = ""
do {
contents = try String(contentsOfFile: podspecFile.path, encoding: .utf8)
} catch {
fatalError("Could not read Firebase podspec. \(error)")
}
let firebaseVersion = manifest.version
for firebasePod in manifest.pods {
if !firebasePod.isFirebase {
continue
}
let pod = firebasePod.name
let version = firebasePod.isBeta ? firebaseVersion + "-beta" : firebaseVersion
if pod == "Firebase" {
// TODO: This then block is redundant with the updatePodspecs function above and is left
// until we decide to go with Swift or sed.
// Replace version in string like s.version = '6.9.0'
guard let range = contents.range(of: "s.version") else {
fatalError("Could not find version of Firebase pod in podspec at \(podspecFile)")
}
var versionStartIndex = contents.index(range.upperBound, offsetBy: 1)
while contents[versionStartIndex] != "'" {
versionStartIndex = contents.index(versionStartIndex, offsetBy: 1)
}
var versionEndIndex = contents.index(versionStartIndex, offsetBy: 1)
while contents[versionEndIndex] != "'" {
versionEndIndex = contents.index(versionEndIndex, offsetBy: 1)
}
contents.removeSubrange(versionStartIndex ... versionEndIndex)
contents.insert(contentsOf: "'" + version + "'", at: versionStartIndex)
} else {
// Replace version in string like ss.dependency 'FirebaseCore', '6.3.0'
guard let range = contents.range(of: pod) else {
// This pod is not a top-level Firebase pod dependency.
continue
}
var versionStartIndex = contents.index(range.upperBound, offsetBy: 2)
while !contents[versionStartIndex].isWholeNumber {
versionStartIndex = contents.index(versionStartIndex, offsetBy: 1)
}
var versionEndIndex = contents.index(versionStartIndex, offsetBy: 1)
while contents[versionEndIndex] != "'" {
versionEndIndex = contents.index(versionEndIndex, offsetBy: 1)
}
contents.removeSubrange(versionStartIndex ... versionEndIndex)
contents.insert(contentsOf: version + "'", at: versionStartIndex)
}
}
do {
try contents.write(to: podspecFile, atomically: false, encoding: .utf8)
} catch {
fatalError("Failed to write \(podspecFile.path). \(error)")
}
}

private static func updatePodfiles(path: URL, version: String) {
// Update the Podfiles across the repo.
let firestorePodfile = path.appendingPathComponent("Firestore")
.appendingPathComponent("Example")
let collisionPodfile = path.appendingPathComponent("SymbolCollisionTest")
let sedCommand = "sed -i.bak -e \"s#\\(pod " +
"'Firebase/CoreOnly',[[:space:]]*'\\).*'#\\1\(version)'#\" Podfile"
Shell.executeCommand(sedCommand, workingDir: firestorePodfile)

let sedCommand2 = "sed -i.bak -e \"s#\\(pod " +
"'Firebase',[[:space:]]*'\\).*'#\\1\(version)'#\" Podfile"
Shell.executeCommand(sedCommand2, workingDir: collisionPodfile)
}
}
55 changes: 55 additions & 0 deletions ZipBuilder/Sources/FirebaseReleaser/Push.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2020 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

enum Push {
static func pushPodsToCPDC(gitRoot: URL) {
let cpdcLocation = findCpdc(gitRoot: gitRoot)
let manifest = FirebaseManifest.shared

for pod in manifest.pods {
if !pod.releasing {
continue
}
let warningsOK = pod.allowWarnings ? " --allow-warnings" : ""

Shell.executeCommand("pod repo push --skip-tests --use-json \(warningsOK) \(cpdcLocation) " +
pod.skipImportValidation() + " \(pod.podspecName()) " +
"--sources=sso://cpdc-internal/firebase.git,https://cdn.cocoapods.org",
workingDir: gitRoot)
}
}

private static func findCpdc(gitRoot: URL) -> String {
let command = "pod repo list | grep -B2 sso://cpdc-internal/firebase | head -1"
let result = Shell.executeCommandFromScript(command, workingDir: gitRoot)
switch result {
case let .error(code, output):
fatalError("""
`pod --version` failed with exit code \(code)
Output from `pod repo list`:
\(output)
""")
case let .success(output):
print(output)
return output.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
}
47 changes: 47 additions & 0 deletions ZipBuilder/Sources/FirebaseReleaser/Tags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2020 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

enum Tags {
static func create(gitRoot: URL) {
let manifest = FirebaseManifest.shared
createTag(gitRoot: gitRoot, tag: "CocoaPods-\(manifest.version)")
createTag(gitRoot: gitRoot, tag: "CocoaPods-\(manifest.version)-beta")

for pod in manifest.pods {
if pod.isFirebase {
continue
}
if !pod.name.starts(with: "Google") {
fatalError("Unrecognized Other Pod: \(pod.name). Only Google prefix is recognized")
}
guard let version = pod.podVersion else {
fatalError("Non-Firebase pod \(pod.name) is missing a version")
}
let tag = pod.name.replacingOccurrences(of: "Google", with: "") + "-" + version
createTag(gitRoot: gitRoot, tag: tag)
}
}

private static func createTag(gitRoot: URL, tag: String) {
Shell.executeCommand("git tag \(tag)", workingDir: gitRoot)
Shell.executeCommand("git push origin \(tag)", workingDir: gitRoot)
}
}
Loading

0 comments on commit dd2e23e

Please sign in to comment.