Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One version release process #6724

Merged
merged 10 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: IMO the manifest declaration should be in a separate file to reduce the noise when reading the manifest.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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" +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would appreciate a brief command on what this command is doing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"\(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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: I would move content of if-else block to separate methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is mostly unchanged from the old Firebase podspec updater. See deletion below.

I added some comments about consolidating to either always using more compact sed commands or potentially more readable Swift.

Deferring for now.

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] != "'" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: Scanner API like this may be a bit more expressive for such cases than enumerating the string directly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added TODO.

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