Skip to content

Commit 38ac1bb

Browse files
authored
Create a GitHub Action for Notices Generation. (#9657)
* Create a GitHub Action for Notices Generation.
1 parent 1fc4e50 commit 38ac1bb

File tree

8 files changed

+347
-13
lines changed

8 files changed

+347
-13
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source "https://rubygems.org"
2+
3+
gem "cocoapods"
4+
gem "octokit", "~> 4.19"
5+
gem "xcodeproj", "~> 1.21"
6+
gem "plist"
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
CFPropertyList (3.0.5)
5+
rexml
6+
addressable (2.8.0)
7+
public_suffix (>= 2.0.2, < 5.0)
8+
atomos (0.1.3)
9+
claide (1.1.0)
10+
colored2 (3.1.2)
11+
faraday (1.1.0)
12+
multipart-post (>= 1.2, < 3)
13+
ruby2_keywords
14+
multipart-post (2.1.1)
15+
nanaimo (0.3.0)
16+
octokit (4.19.0)
17+
faraday (>= 0.9)
18+
sawyer (~> 0.8.0, >= 0.5.3)
19+
plist (3.6.0)
20+
public_suffix (4.0.6)
21+
rexml (3.2.5)
22+
ruby2_keywords (0.0.2)
23+
sawyer (0.8.2)
24+
addressable (>= 2.3.5)
25+
faraday (> 0.8, < 2.0)
26+
xcodeproj (1.21.0)
27+
CFPropertyList (>= 2.3.3, < 4.0)
28+
atomos (~> 0.1.3)
29+
claide (>= 1.0.2, < 2.0)
30+
colored2 (~> 3.1)
31+
nanaimo (~> 0.3.0)
32+
rexml (~> 3.2.4)
33+
34+
PLATFORMS
35+
ruby
36+
37+
DEPENDENCIES
38+
octokit (~> 4.19)
39+
plist
40+
xcodeproj (~> 1.21)
41+
42+
BUNDLED WITH
43+
2.3.11
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: 'Generate NOTICES'
2+
description: 'Generate a NOTICES containning SDK licenses.'
3+
inputs:
4+
pods:
5+
description: 'Targeted Pods for licences'
6+
required: true
7+
sources:
8+
description: 'Sources of PodSpecs'
9+
required: true
10+
default: 'https://cdn.cocoapods.org'
11+
min-ios-version:
12+
description: 'The minimum version of iOS'
13+
required: true
14+
default: '10.0'
15+
search_local_pod_version:
16+
description: 'Add pod version from local spec repo'
17+
required: true
18+
default: false
19+
type: boolean
20+
outputs:
21+
notices_contents:
22+
description: 'contents of notices'
23+
runs:
24+
using: 'composite'
25+
steps:
26+
- uses: ruby/setup-ruby@v1
27+
with:
28+
ruby-version: "2.7"
29+
- name: First step
30+
run: |
31+
cd "${{ github.action_path }}"
32+
bundle install
33+
if ${{ inputs.search_local_pod_version == 'true' }} ; then
34+
ruby app.rb --pods ${{ inputs.pods }} --sources ${{ inputs.sources }} --min_ios_version ${{ inputs.min-ios-version }} --search_local_pod_version
35+
else
36+
ruby app.rb --pods ${{ inputs.pods }} --sources ${{ inputs.sources }} --min_ios_version ${{ inputs.min-ios-version }}
37+
fi
38+
shell: bash
39+
- name: Upload artifacts
40+
uses: actions/upload-artifact@v3
41+
with:
42+
name: notices
43+
path: .github/actions/notices_generation/NOTICES
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 2022 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
require 'cocoapods'
18+
require 'digest'
19+
require 'octokit'
20+
require 'optparse'
21+
require 'plist'
22+
require 'tmpdir'
23+
require 'xcodeproj'
24+
25+
DEFAULT_TESTAPP_TARGET = "testApp"
26+
27+
# Default sources of min iOS version
28+
SOURCES=["https://cdn.cocoapods.org/"]
29+
MIN_IOS_VERSION="12.0"
30+
NOTICES_OUTPUT_PATH="./NOTICES"
31+
SEARCH_LOCAL_POD_VERSION=false
32+
33+
@options = {
34+
sources: SOURCES,
35+
min_ios_version: MIN_IOS_VERSION,
36+
output_path: NOTICES_OUTPUT_PATH,
37+
search_local_pod_version: false
38+
}
39+
begin
40+
OptionParser.new do |opts|
41+
opts.banner = "Usage: app.rb [options]"
42+
opts.on('-p', '--pods PODS', 'Pods seperated by space or comma.') { |v| @options[:pods] = v.split(/[ ,]/) }
43+
opts.on('-s', '--sources SOURCES', 'Sources of Pods') { |v| @options[:sources] = v.split(/[ ,]/) }
44+
opts.on('-m', '--min_ios_version MIN_IOS_VERSION', 'Minimum iOS version') { |v| @options[:min_ios_version] = v }
45+
opts.on('-n', '--output_path OUTPUT_PATH', 'The output path of NOTICES') { |v| @options[:output_path] = v }
46+
opts.on('-v', '--search-local-pod-version', 'Attach the latest pod version to a pod in Podfile') { |v| @options[:search_local_pod_version] = true }
47+
end.parse!
48+
49+
raise OptionParser::MissingArgument if @options[:pods].nil?
50+
rescue OptionParser::MissingArgument
51+
puts "Argument `--pods` should not be empty."
52+
raise
53+
end
54+
55+
PODS = @options[:pods]
56+
SOURCES = @options[:sources]
57+
MIN_IOS_VERSION = @options[:min_ios_version]
58+
NOTICES_OUTPUT_PATH = @options[:output_path]
59+
SEARCH_LOCAL_POD_VERSION = @options[:search_local_pod_version]
60+
61+
def create_podfile(path: , sources: , target: , pods: [], min_ios_version: , search_local_pod_version: )
62+
output = ""
63+
for source in sources do
64+
output += "source \'#{source}\'\n"
65+
end
66+
if search_local_pod_version
67+
for source in sources do
68+
if source == "https://cdn.cocoapods.org/"
69+
next
70+
end
71+
`pod repo add #{Digest::MD5.hexdigest source} #{source}`
72+
end
73+
end
74+
output += "use_frameworks! :linkage => :static\n"
75+
76+
output += "platform :ios, #{min_ios_version}\n"
77+
output += "target \'#{target}\' do\n"
78+
for pod in pods do
79+
if search_local_pod_version
80+
# `pod search` will search a pod locally and generate a corresonding pod
81+
# config in a Podfile with `grep`, e.g.
82+
# pod search Firebase | grep "pod.*" -m 1
83+
# will generate
84+
# pod 'Firebase', '~> 9.0.0'
85+
output += `pod search "#{pod}" | grep "pod.*" -m 1`
86+
else
87+
output += "pod \'#{pod}\'\n"
88+
end
89+
end
90+
output += "end\n"
91+
92+
# Remove default footers and headers generated by CocoaPods.
93+
output += "
94+
class ::Pod::Generator::Acknowledgements
95+
def header_text
96+
''
97+
end
98+
def header_title
99+
''
100+
end
101+
def footnote_text
102+
''
103+
end
104+
end
105+
"
106+
puts "------Podfile------\n#{output}\n-------------------\n"
107+
podfile = File.new("#{path}/Podfile", "w")
108+
podfile.puts(output)
109+
podfile.close
110+
end
111+
112+
def generate_notices_content(sources: SOURCES, pods: PODS, min_ios_version: MIN_IOS_VERSION)
113+
content = ""
114+
Dir.mktmpdir do |temp_dir|
115+
Dir.chdir(temp_dir) do
116+
project_path = "#{temp_dir}/barebone_app.xcodeproj"
117+
project_path = "barebone_app.xcodeproj"
118+
project = Xcodeproj::Project.new(project_path)
119+
project.new_target(:application, DEFAULT_TESTAPP_TARGET, :ios)
120+
project.save()
121+
create_podfile(path: temp_dir, sources: sources, target: DEFAULT_TESTAPP_TARGET,pods: pods, min_ios_version: min_ios_version, search_local_pod_version: SEARCH_LOCAL_POD_VERSION)
122+
pod_install_result = `pod install --allow-root`
123+
puts pod_install_result
124+
licences = Plist.parse_xml("Pods/Target Support Files/Pods-testApp/Pods-testApp-acknowledgements.plist")
125+
126+
existing_licences={}
127+
for licence in licences["PreferenceSpecifiers"] do
128+
if existing_licences.include?(licence["FooterText"])
129+
existing_licences.store(licence["FooterText"], existing_licences.fetch(licence["FooterText"])+"\n"+licence["Title"])
130+
next
131+
end
132+
existing_licences.store(licence["FooterText"], licence["Title"])
133+
end
134+
existing_licences.each{ |licence, title|
135+
content += "#{title}\n#{licence}\n"
136+
}
137+
end
138+
end
139+
return content.strip
140+
end
141+
142+
def main()
143+
content = generate_notices_content(sources: SOURCES, pods: PODS, min_ios_version: MIN_IOS_VERSION)
144+
notices = File.new(NOTICES_OUTPUT_PATH, "w")
145+
notices.puts(content)
146+
notices.close
147+
end
148+
149+
main()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: generate_notices
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/testing_actions.yml'
7+
- '.github/actions/notices_generation**'
8+
jobs:
9+
generate_a_notice:
10+
# Don't run on private repo.
11+
if: github.repository == 'Firebase/firebase-ios-sdk' && github.event_name != 'schedule'
12+
runs-on: macos-11
13+
name: Generate NOTICES
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: Get all pod names
17+
run: |
18+
cd "${GITHUB_WORKSPACE}/ReleaseTooling/"
19+
swift run manifest --pod-name-output-file-path ./output.txt
20+
PODS=`cat ./output.txt`
21+
echo "PODS=${PODS}" >> $GITHUB_ENV
22+
- name: Create a local specs repo
23+
run: |
24+
cd "${GITHUB_WORKSPACE}/ReleaseTooling/"
25+
swift run podspecs-tester --git-root "${GITHUB_WORKSPACE}"
26+
- name: Create a NOTICES file
27+
id: notices
28+
uses: ./.github/actions/notices_generation/
29+
with:
30+
pods: ${{ env.PODS }}
31+
sources: "https://github.com/firebase/SpecsTesting,https://github.com/firebase/SpecsStaging,https://cdn.cocoapods.org"
32+
min-ios-version: "13.0"
33+
search_local_pod_version: true

ReleaseTooling/Package.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ let package = Package(
2525
.executable(name: "firebase-releaser", targets: ["FirebaseReleaser"]),
2626
.executable(name: "zip-builder", targets: ["ZipBuilder"]),
2727
.executable(name: "podspecs-tester", targets: ["PodspecsTester"]),
28+
.executable(name: "manifest", targets: ["ManifestParser"]),
2829
],
2930
dependencies: [
3031
.package(url: "https://github.com/apple/swift-argument-parser", .exact("0.1.0")),
@@ -37,6 +38,10 @@ let package = Package(
3738
.target(
3839
name: "FirebaseManifest"
3940
),
41+
.target(
42+
name: "ManifestParser",
43+
dependencies: ["ArgumentParser", "FirebaseManifest", "Utils"]
44+
),
4045
.target(
4146
name: "FirebaseReleaser",
4247
dependencies: ["ArgumentParser", "FirebaseManifest", "Utils"]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2022 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 ArgumentParser
18+
import FirebaseManifest
19+
import Foundation
20+
import Utils
21+
22+
struct ManifestParser: ParsableCommand {
23+
/// Path of a text file for Firebase Pods' names.
24+
@Option(help: "Output path of a generated file with all Firebase Pods' names.",
25+
transform: URL.init(fileURLWithPath:))
26+
var podNameOutputFilePath: URL
27+
28+
func parsePodNames(_ manifest: Manifest) throws {
29+
var output: [String] = []
30+
for pod in manifest.pods {
31+
output.append(pod.name)
32+
}
33+
do {
34+
try output.joined(separator: ",")
35+
.write(to: podNameOutputFilePath, atomically: true,
36+
encoding: String.Encoding.utf8)
37+
print("\(output) is written in \n \(podNameOutputFilePath).")
38+
} catch {
39+
throw error
40+
}
41+
}
42+
43+
func run() throws {
44+
let manifest = FirebaseManifest.shared
45+
try parsePodNames(manifest)
46+
}
47+
}
48+
49+
// Start the parsing and run the tool.
50+
ManifestParser.main()

ReleaseTooling/Sources/PodspecsTester/main.swift

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct PodspecsTester: ParsableCommand {
2727

2828
/// A targeted testing pod, e.g. FirebaseAuth.podspec
2929
@Option(help: "A podspec that will be tested.")
30-
var podspec: String
30+
var podspec: String?
3131

3232
/// The root of the Firebase git repo.
3333
@Option(help: "Spec testing log dir", transform: URL.init(fileURLWithPath:))
@@ -114,20 +114,25 @@ struct PodspecsTester: ParsableCommand {
114114
return t
115115
}()
116116
timer.resume()
117-
let testingPod = podspec.components(separatedBy: ".")[0]
118-
for pod in manifest.pods {
119-
if testingPod == pod.name {
120-
var args: [String: String?] = [:]
121-
args["platforms"] = pod.platforms.joined(separator: ",")
122-
if pod.allowWarnings {
123-
args.updateValue(nil, forKey: "allow-warnings")
117+
if let podspec = podspec {
118+
let testingPod = podspec.components(separatedBy: ".")[0]
119+
for pod in manifest.pods {
120+
if testingPod == pod.name {
121+
var args: [String: String?] = [:]
122+
args["platforms"] = pod.platforms.joined(separator: ",")
123+
if pod.allowWarnings {
124+
args.updateValue(nil, forKey: "allow-warnings")
125+
}
126+
if skipTests {
127+
args.updateValue(nil, forKey: "skip-tests")
128+
}
129+
let code = specTest(spec: podspec, workingDir: gitRoot, args: args).code
130+
exitCode = code
124131
}
125-
if skipTests {
126-
args.updateValue(nil, forKey: "skip-tests")
127-
}
128-
let code = specTest(spec: podspec, workingDir: gitRoot, args: args).code
129-
exitCode = code
130132
}
133+
} else {
134+
print("A local podspec repo for \(gitRoot) is generated, but no " +
135+
"podspec testing will be run since `--podspec` is not specified.")
131136
}
132137
timer.cancel()
133138
let finishDate = Date()

0 commit comments

Comments
 (0)