ソースを参照

Create a GitHub Action for Notices Generation. (#9657)

* Create a GitHub Action for Notices Generation.
Gran 3 年 前
コミット
38ac1bb382

+ 6 - 0
.github/actions/notices_generation/Gemfile

@@ -0,0 +1,6 @@
+source "https://rubygems.org"
+
+gem "cocoapods"
+gem "octokit", "~> 4.19"
+gem "xcodeproj", "~> 1.21"
+gem "plist"

+ 43 - 0
.github/actions/notices_generation/Gemfile.lock

@@ -0,0 +1,43 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    CFPropertyList (3.0.5)
+      rexml
+    addressable (2.8.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    atomos (0.1.3)
+    claide (1.1.0)
+    colored2 (3.1.2)
+    faraday (1.1.0)
+      multipart-post (>= 1.2, < 3)
+      ruby2_keywords
+    multipart-post (2.1.1)
+    nanaimo (0.3.0)
+    octokit (4.19.0)
+      faraday (>= 0.9)
+      sawyer (~> 0.8.0, >= 0.5.3)
+    plist (3.6.0)
+    public_suffix (4.0.6)
+    rexml (3.2.5)
+    ruby2_keywords (0.0.2)
+    sawyer (0.8.2)
+      addressable (>= 2.3.5)
+      faraday (> 0.8, < 2.0)
+    xcodeproj (1.21.0)
+      CFPropertyList (>= 2.3.3, < 4.0)
+      atomos (~> 0.1.3)
+      claide (>= 1.0.2, < 2.0)
+      colored2 (~> 3.1)
+      nanaimo (~> 0.3.0)
+      rexml (~> 3.2.4)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  octokit (~> 4.19)
+  plist
+  xcodeproj (~> 1.21)
+
+BUNDLED WITH
+   2.3.11

+ 43 - 0
.github/actions/notices_generation/action.yml

@@ -0,0 +1,43 @@
+name: 'Generate NOTICES'
+description: 'Generate a NOTICES containning SDK licenses.'
+inputs:
+  pods:
+    description: 'Targeted Pods for licences'
+    required: true
+  sources:
+    description: 'Sources of PodSpecs'
+    required: true
+    default: 'https://cdn.cocoapods.org'
+  min-ios-version:
+    description: 'The minimum version of iOS'
+    required: true
+    default: '10.0'
+  search_local_pod_version:
+    description: 'Add pod version from local spec repo'
+    required: true
+    default: false
+    type: boolean
+outputs:
+  notices_contents:
+    description: 'contents of notices'
+runs:
+  using: 'composite'
+  steps:
+  - uses: ruby/setup-ruby@v1
+    with:
+      ruby-version: "2.7"
+  - name: First step
+    run: |
+      cd "${{ github.action_path }}"
+      bundle install
+      if ${{ inputs.search_local_pod_version == 'true' }} ; then
+        ruby app.rb --pods ${{ inputs.pods }} --sources ${{ inputs.sources }} --min_ios_version ${{ inputs.min-ios-version }} --search_local_pod_version
+      else
+        ruby app.rb --pods ${{ inputs.pods }} --sources ${{ inputs.sources }} --min_ios_version ${{ inputs.min-ios-version }}
+      fi
+    shell: bash
+  - name: Upload artifacts
+    uses: actions/upload-artifact@v3
+    with:
+      name: notices
+      path: .github/actions/notices_generation/NOTICES

+ 149 - 0
.github/actions/notices_generation/app.rb

@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+# Copyright 2022 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.
+
+require 'cocoapods'
+require 'digest'
+require 'octokit'
+require 'optparse'
+require 'plist'
+require 'tmpdir'
+require 'xcodeproj'
+
+DEFAULT_TESTAPP_TARGET = "testApp"
+
+# Default sources of min iOS version
+SOURCES=["https://cdn.cocoapods.org/"]
+MIN_IOS_VERSION="12.0"
+NOTICES_OUTPUT_PATH="./NOTICES"
+SEARCH_LOCAL_POD_VERSION=false
+
+@options = {
+    sources: SOURCES,
+    min_ios_version: MIN_IOS_VERSION,
+    output_path: NOTICES_OUTPUT_PATH,
+    search_local_pod_version: false
+}
+begin
+  OptionParser.new do |opts|
+    opts.banner = "Usage: app.rb [options]"
+    opts.on('-p', '--pods PODS', 'Pods seperated by space or comma.') { |v| @options[:pods] = v.split(/[ ,]/) }
+    opts.on('-s', '--sources SOURCES', 'Sources of Pods') { |v| @options[:sources] = v.split(/[ ,]/) }
+    opts.on('-m', '--min_ios_version MIN_IOS_VERSION', 'Minimum iOS version') { |v| @options[:min_ios_version] = v }
+    opts.on('-n', '--output_path OUTPUT_PATH', 'The output path of NOTICES') { |v| @options[:output_path] = v }
+    opts.on('-v', '--search-local-pod-version', 'Attach the latest pod version to a pod in Podfile') { |v| @options[:search_local_pod_version] = true }
+  end.parse!
+
+  raise OptionParser::MissingArgument if @options[:pods].nil?
+rescue OptionParser::MissingArgument
+  puts "Argument `--pods` should not be empty."
+  raise
+end
+
+PODS = @options[:pods]
+SOURCES = @options[:sources]
+MIN_IOS_VERSION = @options[:min_ios_version]
+NOTICES_OUTPUT_PATH = @options[:output_path]
+SEARCH_LOCAL_POD_VERSION = @options[:search_local_pod_version]
+
+def create_podfile(path: , sources: , target: , pods: [], min_ios_version: , search_local_pod_version: )
+  output = ""
+  for source in sources do
+    output += "source \'#{source}\'\n"
+  end
+  if search_local_pod_version
+    for source in sources do
+      if source == "https://cdn.cocoapods.org/"
+        next
+      end
+      `pod repo add #{Digest::MD5.hexdigest source} #{source}`
+    end
+  end
+  output += "use_frameworks! :linkage => :static\n"
+
+  output += "platform :ios, #{min_ios_version}\n"
+  output += "target \'#{target}\' do\n"
+  for pod in pods do
+    if search_local_pod_version
+      # `pod search` will search a pod locally and generate a corresonding pod
+      # config in a Podfile with `grep`, e.g.
+      # pod search Firebase | grep "pod.*" -m 1
+      # will generate
+      # pod 'Firebase', '~> 9.0.0'
+      output += `pod search "#{pod}" | grep "pod.*" -m 1`
+    else
+      output += "pod \'#{pod}\'\n"
+    end
+  end
+  output += "end\n"
+
+# Remove default footers and headers generated by CocoaPods.
+  output += "
+    class ::Pod::Generator::Acknowledgements
+      def header_text
+    ''
+      end
+      def header_title
+     ''
+      end
+      def footnote_text
+      ''
+      end
+    end
+  "
+  puts "------Podfile------\n#{output}\n-------------------\n"
+  podfile = File.new("#{path}/Podfile", "w")
+  podfile.puts(output)
+  podfile.close
+end
+
+def generate_notices_content(sources: SOURCES, pods: PODS, min_ios_version: MIN_IOS_VERSION)
+  content = ""
+  Dir.mktmpdir do |temp_dir|
+    Dir.chdir(temp_dir) do
+      project_path = "#{temp_dir}/barebone_app.xcodeproj"
+      project_path = "barebone_app.xcodeproj"
+      project = Xcodeproj::Project.new(project_path)
+      project.new_target(:application, DEFAULT_TESTAPP_TARGET, :ios)
+      project.save()
+      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)
+      pod_install_result = `pod install --allow-root`
+      puts pod_install_result
+      licences = Plist.parse_xml("Pods/Target Support Files/Pods-testApp/Pods-testApp-acknowledgements.plist")
+
+      existing_licences={}
+      for licence in licences["PreferenceSpecifiers"] do
+        if existing_licences.include?(licence["FooterText"])
+          existing_licences.store(licence["FooterText"], existing_licences.fetch(licence["FooterText"])+"\n"+licence["Title"])
+          next
+        end
+        existing_licences.store(licence["FooterText"], licence["Title"])
+      end
+      existing_licences.each{ |licence, title|
+        content += "#{title}\n#{licence}\n"
+      }
+    end
+  end
+  return content.strip
+end
+
+def main()
+  content = generate_notices_content(sources: SOURCES, pods: PODS, min_ios_version: MIN_IOS_VERSION)
+  notices = File.new(NOTICES_OUTPUT_PATH, "w")
+  notices.puts(content)
+  notices.close
+end
+
+main()

+ 33 - 0
.github/workflows/notice_generation.yml

@@ -0,0 +1,33 @@
+name: generate_notices
+
+on:
+  pull_request:
+    paths:
+    - '.github/workflows/testing_actions.yml'
+    - '.github/actions/notices_generation**'
+jobs:
+  generate_a_notice:
+    # Don't run on private repo.
+    if: github.repository == 'Firebase/firebase-ios-sdk' && github.event_name != 'schedule'
+    runs-on: macos-11
+    name: Generate NOTICES
+    steps:
+    - uses: actions/checkout@v2
+    - name: Get all pod names
+      run: |
+        cd "${GITHUB_WORKSPACE}/ReleaseTooling/"
+        swift run manifest --pod-name-output-file-path ./output.txt
+        PODS=`cat ./output.txt`
+        echo "PODS=${PODS}" >> $GITHUB_ENV
+    - name: Create a local specs repo
+      run: |
+        cd "${GITHUB_WORKSPACE}/ReleaseTooling/"
+        swift run podspecs-tester --git-root "${GITHUB_WORKSPACE}"
+    - name: Create a NOTICES file
+      id: notices
+      uses: ./.github/actions/notices_generation/
+      with:
+        pods: ${{ env.PODS }}
+        sources: "https://github.com/firebase/SpecsTesting,https://github.com/firebase/SpecsStaging,https://cdn.cocoapods.org"
+        min-ios-version: "13.0"
+        search_local_pod_version: true

+ 5 - 0
ReleaseTooling/Package.swift

@@ -25,6 +25,7 @@ let package = Package(
     .executable(name: "firebase-releaser", targets: ["FirebaseReleaser"]),
     .executable(name: "zip-builder", targets: ["ZipBuilder"]),
     .executable(name: "podspecs-tester", targets: ["PodspecsTester"]),
+    .executable(name: "manifest", targets: ["ManifestParser"]),
   ],
   dependencies: [
     .package(url: "https://github.com/apple/swift-argument-parser", .exact("0.1.0")),
@@ -37,6 +38,10 @@ let package = Package(
     .target(
       name: "FirebaseManifest"
     ),
+    .target(
+      name: "ManifestParser",
+      dependencies: ["ArgumentParser", "FirebaseManifest", "Utils"]
+    ),
     .target(
       name: "FirebaseReleaser",
       dependencies: ["ArgumentParser", "FirebaseManifest", "Utils"]

+ 50 - 0
ReleaseTooling/Sources/ManifestParser/main.swift

@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 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 ArgumentParser
+import FirebaseManifest
+import Foundation
+import Utils
+
+struct ManifestParser: ParsableCommand {
+  /// Path of a text file for Firebase Pods' names.
+  @Option(help: "Output path of a generated file with all Firebase Pods' names.",
+          transform: URL.init(fileURLWithPath:))
+  var podNameOutputFilePath: URL
+
+  func parsePodNames(_ manifest: Manifest) throws {
+    var output: [String] = []
+    for pod in manifest.pods {
+      output.append(pod.name)
+    }
+    do {
+      try output.joined(separator: ",")
+        .write(to: podNameOutputFilePath, atomically: true,
+               encoding: String.Encoding.utf8)
+      print("\(output) is written in \n \(podNameOutputFilePath).")
+    } catch {
+      throw error
+    }
+  }
+
+  func run() throws {
+    let manifest = FirebaseManifest.shared
+    try parsePodNames(manifest)
+  }
+}
+
+// Start the parsing and run the tool.
+ManifestParser.main()

+ 18 - 13
ReleaseTooling/Sources/PodspecsTester/main.swift

@@ -27,7 +27,7 @@ struct PodspecsTester: ParsableCommand {
 
   /// A targeted testing pod, e.g. FirebaseAuth.podspec
   @Option(help: "A podspec that will be tested.")
-  var podspec: String
+  var podspec: String?
 
   /// The root of the Firebase git repo.
   @Option(help: "Spec testing log dir", transform: URL.init(fileURLWithPath:))
@@ -114,20 +114,25 @@ struct PodspecsTester: ParsableCommand {
       return t
     }()
     timer.resume()
-    let testingPod = podspec.components(separatedBy: ".")[0]
-    for pod in manifest.pods {
-      if testingPod == pod.name {
-        var args: [String: String?] = [:]
-        args["platforms"] = pod.platforms.joined(separator: ",")
-        if pod.allowWarnings {
-          args.updateValue(nil, forKey: "allow-warnings")
+    if let podspec = podspec {
+      let testingPod = podspec.components(separatedBy: ".")[0]
+      for pod in manifest.pods {
+        if testingPod == pod.name {
+          var args: [String: String?] = [:]
+          args["platforms"] = pod.platforms.joined(separator: ",")
+          if pod.allowWarnings {
+            args.updateValue(nil, forKey: "allow-warnings")
+          }
+          if skipTests {
+            args.updateValue(nil, forKey: "skip-tests")
+          }
+          let code = specTest(spec: podspec, workingDir: gitRoot, args: args).code
+          exitCode = code
         }
-        if skipTests {
-          args.updateValue(nil, forKey: "skip-tests")
-        }
-        let code = specTest(spec: podspec, workingDir: gitRoot, args: args).code
-        exitCode = code
       }
+    } else {
+      print("A local podspec repo for \(gitRoot) is generated, but no " +
+        "podspec testing will be run since `--podspec` is not specified.")
     }
     timer.cancel()
     let finishDate = Date()