Skip to content

Commit dd2e23e

Browse files
authored
One version release process (#6724)
1 parent 6cfc7a2 commit dd2e23e

16 files changed

+456
-73
lines changed

ZipBuilder/Package.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import PackageDescription
2222
let package = Package(
2323
name: "ZipBuilder",
2424
products: [
25-
.executable(name: "firebase-pod-updater", targets: ["firebase-pod-updater"]),
25+
.executable(name: "firebase-releaser", targets: ["FirebaseReleaser"]),
2626
.executable(name: "ReleasePackager", targets: ["ZipBuilder"]),
2727
],
2828
dependencies: [
@@ -33,17 +33,23 @@ let package = Package(
3333
],
3434
targets: [
3535
.target(
36-
name: "firebase-pod-updater",
37-
dependencies: ["ArgumentParser", "ManifestReader"]
36+
name: "ZipBuilder",
37+
dependencies: ["ArgumentParser", "ManifestReader", "Utils"]
3838
),
3939
.target(
40-
name: "ZipBuilder",
41-
dependencies: ["ArgumentParser", "ManifestReader"]
40+
name: "FirebaseManifest"
41+
),
42+
.target(
43+
name: "FirebaseReleaser",
44+
dependencies: ["ArgumentParser", "FirebaseManifest", "Utils"]
4245
),
4346
.target(
4447
name: "ManifestReader",
4548
dependencies: ["SwiftProtobuf"]
4649
),
50+
.target(
51+
name: "Utils"
52+
),
4753
.target(
4854
name: "oss-manifest-generator",
4955
dependencies: ["ArgumentParser", "ManifestReader"]
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
19+
/// The manifest contents for a release.
20+
/// Version should be updated every release.
21+
/// The version and releasing fields of the non-Firebase pods should be reviewed every release.
22+
public let shared = Manifest(
23+
version: "7.0.0",
24+
pods: [
25+
Pod("GoogleUtilities", isFirebase: false, podVersion: "7.0.0", releasing: false),
26+
Pod("GoogleDataTransport", isFirebase: false, podVersion: "8.0.0", releasing: true),
27+
28+
Pod("FirebaseCoreDiagnostics"),
29+
Pod("FirebaseCore"),
30+
Pod("FirebaseInstallations"),
31+
Pod("FirebaseInstanceID"),
32+
Pod("GoogleAppMeasurement", isClosedSource: true),
33+
Pod("FirebaseAnalytics", isClosedSource: true),
34+
Pod("FirebaseABTesting"),
35+
Pod("FirebaseAppDistribution", isBeta: true),
36+
Pod("FirebaseAuth"),
37+
Pod("FirebaseCrashlytics"),
38+
Pod("FirebaseDatabase"),
39+
Pod("FirebaseDynamicLinks"),
40+
Pod("FirebaseFirestore", allowWarnings: true),
41+
Pod("FirebaseFirestoreSwift", isBeta: true),
42+
Pod("FirebaseFunctions"),
43+
Pod("FirebaseInAppMessaging", isBeta: true),
44+
Pod("FirebaseMessaging"),
45+
Pod("FirebasePerformance", isClosedSource: true),
46+
Pod("FirebaseRemoteConfig"),
47+
Pod("FirebaseStorage"),
48+
Pod("FirebaseStorageSwift", isBeta: true),
49+
Pod("FirebaseMLCommon", isClosedSource: true, isBeta: true),
50+
Pod("FirebaseMLModelInterpreter", isClosedSource: true, isBeta: true),
51+
Pod("FirebaseMLVision", isClosedSource: true, isBeta: true),
52+
Pod("Firebase", allowWarnings: true),
53+
]
54+
)
55+
56+
/// Manifest describing the contents of a Firebase release.
57+
public struct Manifest {
58+
public let version: String
59+
public let pods: [Pod]
60+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
19+
/// Struct describing Firebase pods to release.
20+
public struct Pod {
21+
public let name: String
22+
public let isClosedSource: Bool
23+
public let isBeta: Bool
24+
public let isFirebase: Bool
25+
public let allowWarnings: Bool // Allow validation warnings. Ideally these should all be false
26+
public let podVersion: String? // Non-Firebase pods have their own version
27+
public let releasing: Bool // Non-Firebase pods may not release
28+
29+
init(_ name: String,
30+
isClosedSource: Bool = false,
31+
isBeta: Bool = false,
32+
isFirebase: Bool = true,
33+
allowWarnings: Bool = false,
34+
podVersion: String? = nil,
35+
releasing: Bool = true) {
36+
self.name = name
37+
self.isClosedSource = isClosedSource
38+
self.isBeta = isBeta
39+
self.isFirebase = isFirebase
40+
self.allowWarnings = allowWarnings
41+
self.podVersion = podVersion
42+
self.releasing = releasing
43+
}
44+
45+
public func podspecName() -> String {
46+
return isClosedSource ? "\(name).podspec.json" : "\(name).podspec"
47+
}
48+
49+
/// Closed source pods do not validate on Xcode 12 until they support the ARM simulator slice.
50+
public func skipImportValidation() -> String {
51+
if isClosedSource || name == "Firebase" {
52+
return "-skip-import-validation"
53+
} else {
54+
return ""
55+
}
56+
}
57+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
19+
import FirebaseManifest
20+
import Utils
21+
22+
struct InitializeRelease {
23+
static func setupRepo(gitRoot: URL) -> String {
24+
let manifest = FirebaseManifest.shared
25+
let branch = createReleaseBranch(path: gitRoot, version: manifest.version)
26+
updatePodspecs(path: gitRoot, manifest: manifest)
27+
updatePodfiles(path: gitRoot, version: manifest.version)
28+
return branch
29+
}
30+
31+
/// The branch is based on the minor version to represent this is the branch for subsequent
32+
/// patches.
33+
private static func createReleaseBranch(path: URL, version: String) -> String {
34+
let versionParts = version.split(separator: ".")
35+
let minorVersion = "\(versionParts[0]).\(versionParts[1])"
36+
let branch = "release-\(minorVersion)"
37+
Shell.executeCommand("git checkout master", workingDir: path)
38+
Shell.executeCommand("git pull", workingDir: path)
39+
Shell.executeCommand("git checkout -b \(branch)", workingDir: path)
40+
return branch
41+
}
42+
43+
/// Update the podspec versions.
44+
private static func updatePodspecs(path: URL, manifest: FirebaseManifest.Manifest) {
45+
for pod in manifest.pods {
46+
if !pod.isClosedSource {
47+
if pod.name == "Firebase" {
48+
updateFirebasePodspec(path: path, manifest: manifest)
49+
} else {
50+
let version = pod.podVersion ??
51+
(pod.isBeta ? manifest.version + "-beta" : manifest.version)
52+
53+
// Patch the new version to the podspec's version attribute.
54+
Shell.executeCommand("sed -i.bak -e \"s/\\(\\.version.*=[[:space:]]*'\\).*'/\\1" +
55+
"\(version)'/\" \(pod.name).podspec", workingDir: path)
56+
}
57+
}
58+
}
59+
}
60+
61+
// This function patches the versions in the Firebase.podspec. It uses Swift instead of sed
62+
// like the other version patching.
63+
// TODO: Choose one or the other mechanism.
64+
// TODO: If we keep Swift, consider using Scanner.
65+
private static func updateFirebasePodspec(path: URL, manifest: FirebaseManifest.Manifest) {
66+
let podspecFile = path.appendingPathComponent("Firebase.podspec")
67+
var contents = ""
68+
do {
69+
contents = try String(contentsOfFile: podspecFile.path, encoding: .utf8)
70+
} catch {
71+
fatalError("Could not read Firebase podspec. \(error)")
72+
}
73+
let firebaseVersion = manifest.version
74+
for firebasePod in manifest.pods {
75+
if !firebasePod.isFirebase {
76+
continue
77+
}
78+
let pod = firebasePod.name
79+
let version = firebasePod.isBeta ? firebaseVersion + "-beta" : firebaseVersion
80+
if pod == "Firebase" {
81+
// TODO: This then block is redundant with the updatePodspecs function above and is left
82+
// until we decide to go with Swift or sed.
83+
// Replace version in string like s.version = '6.9.0'
84+
guard let range = contents.range(of: "s.version") else {
85+
fatalError("Could not find version of Firebase pod in podspec at \(podspecFile)")
86+
}
87+
var versionStartIndex = contents.index(range.upperBound, offsetBy: 1)
88+
while contents[versionStartIndex] != "'" {
89+
versionStartIndex = contents.index(versionStartIndex, offsetBy: 1)
90+
}
91+
var versionEndIndex = contents.index(versionStartIndex, offsetBy: 1)
92+
while contents[versionEndIndex] != "'" {
93+
versionEndIndex = contents.index(versionEndIndex, offsetBy: 1)
94+
}
95+
contents.removeSubrange(versionStartIndex ... versionEndIndex)
96+
contents.insert(contentsOf: "'" + version + "'", at: versionStartIndex)
97+
} else {
98+
// Replace version in string like ss.dependency 'FirebaseCore', '6.3.0'
99+
guard let range = contents.range(of: pod) else {
100+
// This pod is not a top-level Firebase pod dependency.
101+
continue
102+
}
103+
var versionStartIndex = contents.index(range.upperBound, offsetBy: 2)
104+
while !contents[versionStartIndex].isWholeNumber {
105+
versionStartIndex = contents.index(versionStartIndex, offsetBy: 1)
106+
}
107+
var versionEndIndex = contents.index(versionStartIndex, offsetBy: 1)
108+
while contents[versionEndIndex] != "'" {
109+
versionEndIndex = contents.index(versionEndIndex, offsetBy: 1)
110+
}
111+
contents.removeSubrange(versionStartIndex ... versionEndIndex)
112+
contents.insert(contentsOf: version + "'", at: versionStartIndex)
113+
}
114+
}
115+
do {
116+
try contents.write(to: podspecFile, atomically: false, encoding: .utf8)
117+
} catch {
118+
fatalError("Failed to write \(podspecFile.path). \(error)")
119+
}
120+
}
121+
122+
private static func updatePodfiles(path: URL, version: String) {
123+
// Update the Podfiles across the repo.
124+
let firestorePodfile = path.appendingPathComponent("Firestore")
125+
.appendingPathComponent("Example")
126+
let collisionPodfile = path.appendingPathComponent("SymbolCollisionTest")
127+
let sedCommand = "sed -i.bak -e \"s#\\(pod " +
128+
"'Firebase/CoreOnly',[[:space:]]*'\\).*'#\\1\(version)'#\" Podfile"
129+
Shell.executeCommand(sedCommand, workingDir: firestorePodfile)
130+
131+
let sedCommand2 = "sed -i.bak -e \"s#\\(pod " +
132+
"'Firebase',[[:space:]]*'\\).*'#\\1\(version)'#\" Podfile"
133+
Shell.executeCommand(sedCommand2, workingDir: collisionPodfile)
134+
}
135+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
19+
import FirebaseManifest
20+
import Utils
21+
22+
enum Push {
23+
static func pushPodsToCPDC(gitRoot: URL) {
24+
let cpdcLocation = findCpdc(gitRoot: gitRoot)
25+
let manifest = FirebaseManifest.shared
26+
27+
for pod in manifest.pods {
28+
if !pod.releasing {
29+
continue
30+
}
31+
let warningsOK = pod.allowWarnings ? " --allow-warnings" : ""
32+
33+
Shell.executeCommand("pod repo push --skip-tests --use-json \(warningsOK) \(cpdcLocation) " +
34+
pod.skipImportValidation() + " \(pod.podspecName()) " +
35+
"--sources=sso://cpdc-internal/firebase.git,https://cdn.cocoapods.org",
36+
workingDir: gitRoot)
37+
}
38+
}
39+
40+
private static func findCpdc(gitRoot: URL) -> String {
41+
let command = "pod repo list | grep -B2 sso://cpdc-internal/firebase | head -1"
42+
let result = Shell.executeCommandFromScript(command, workingDir: gitRoot)
43+
switch result {
44+
case let .error(code, output):
45+
fatalError("""
46+
`pod --version` failed with exit code \(code)
47+
Output from `pod repo list`:
48+
\(output)
49+
""")
50+
case let .success(output):
51+
print(output)
52+
return output.trimmingCharacters(in: .whitespacesAndNewlines)
53+
}
54+
}
55+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
19+
import FirebaseManifest
20+
import Utils
21+
22+
enum Tags {
23+
static func create(gitRoot: URL) {
24+
let manifest = FirebaseManifest.shared
25+
createTag(gitRoot: gitRoot, tag: "CocoaPods-\(manifest.version)")
26+
createTag(gitRoot: gitRoot, tag: "CocoaPods-\(manifest.version)-beta")
27+
28+
for pod in manifest.pods {
29+
if pod.isFirebase {
30+
continue
31+
}
32+
if !pod.name.starts(with: "Google") {
33+
fatalError("Unrecognized Other Pod: \(pod.name). Only Google prefix is recognized")
34+
}
35+
guard let version = pod.podVersion else {
36+
fatalError("Non-Firebase pod \(pod.name) is missing a version")
37+
}
38+
let tag = pod.name.replacingOccurrences(of: "Google", with: "") + "-" + version
39+
createTag(gitRoot: gitRoot, tag: tag)
40+
}
41+
}
42+
43+
private static func createTag(gitRoot: URL, tag: String) {
44+
Shell.executeCommand("git tag \(tag)", workingDir: gitRoot)
45+
Shell.executeCommand("git push origin \(tag)", workingDir: gitRoot)
46+
}
47+
}

0 commit comments

Comments
 (0)