Skip to content

Commit

Permalink
Merge pull request #79 from macblazer/78-sbom-generation-is-taking-ex…
Browse files Browse the repository at this point in the history
…tremely-long

Improve performance of analysis with large number of pods
  • Loading branch information
macblazer authored Nov 18, 2024
2 parents e11d412 + df394a6 commit 56a9a37
Show file tree
Hide file tree
Showing 10 changed files with 2,383 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ inherit_mode:
- Exclude

AllCops:
TargetRubyVersion: 2.4.0
TargetRubyVersion: 2.6.3
NewCops: enable
# Completely ignore test fixture files
Exclude:
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.4.1]

### Changed
- Minimum Ruby version is now v2.6.3 so the [Array.union](https://apidock.com/ruby/v2_6_3/Array/union) function can be used.

### Fixed
- Improved performance when analyzing a Podfile with many pods. ([Issue #78](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/78)) [@macblazer](https://github.com/macblazer).

## [1.4.0]

### Added
Expand Down
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@

source 'https://rubygems.org'
gemspec

gem 'equivalent-xml', '~> 0.6.0'
gem 'rake', '~> 13.0'
gem 'rspec', '~> 3.0'
6 changes: 1 addition & 5 deletions cyclonedx-cocoapods.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
'generates CycloneDX BOMs from CocoaPods projects.'
spec.homepage = 'https://github.com/CycloneDX/cyclonedx-cocoapods'
spec.license = 'Apache-2.0'
spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
spec.required_ruby_version = Gem::Requirement.new('>= 2.6.3')

spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = 'https://github.com/CycloneDX/cyclonedx-cocoapods.git'
Expand All @@ -28,8 +28,4 @@ Gem::Specification.new do |spec|

spec.add_dependency 'cocoapods', ['>= 1.10.1', '< 2.0']
spec.add_dependency 'nokogiri', ['>= 1.11.2', '< 2.0']

spec.add_development_dependency 'equivalent-xml', '~> 0.6.0'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rspec', '~> 3.0'
end
10 changes: 4 additions & 6 deletions lib/cyclonedx/cocoapods/cli_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,10 @@ def parse_options
parsed_options[:name] = name
end
options.on('-v', '--version version', 'Version of the component for which the BOM is generated') do |version|
begin
Gem::Version.new(version)
parsed_options[:version] = version
rescue StandardError => e
raise OptionParser::InvalidArgument, e.message
end
Gem::Version.new(version)
parsed_options[:version] = version
rescue StandardError => e
raise OptionParser::InvalidArgument, e.message
end
options.on('-t', '--type type',
'Type of the component for which the BOM is generated ' \
Expand Down
29 changes: 20 additions & 9 deletions lib/cyclonedx/cocoapods/podfile_analyzer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,28 +184,39 @@ def map_single_pod(pod, pods_hash)
end
end

def append_all_pod_dependencies(pods_used, pods_cache)
result = pods_used
# Calculate simple array of all used pods plus their direct dependencies
#
# @param [Array<String>] top_level_pods List of pod names that are directly imported by the Podfile
# @param [Hash<String,Array<String>>] pods_cache Dependency information directly from the Podfile.lock;
# keys are string pod names, values are list of direct dependencies of the given pod.
# @return [Array<String>, Hash<String,Array<String>>] First element is list of all used pod names.
# Second element is a hash: keys are string pod names, values are the direct dependencies of that pod.
def append_all_pod_dependencies(top_level_pods, pods_cache)
result = top_level_pods
original_number = 0
dependencies_hash = {}

# Loop adding pod dependencies until we are not adding any more dependencies to the result
# Loop adding pod dependencies until we are not adding any more dependencies to the result.
# This brings in all the transitive dependencies of every top level pod.
# Note this also handles two edge cases:
# 1. Having a Podfile with no pods used.
# 2. Having a pod that has a platform-specific dependency that is unused for this Podfile.
while result.length != original_number
original_number = result.length

pods_used.each do |pod_name|
top_level_pods.each do |pod_name|
if pods_cache.key?(pod_name)
result.push(*pods_cache[pod_name])
dependencies_hash[pod_name] = pods_cache[pod_name].empty? ? [] : pods_cache[pod_name]
# Append all of the dependencies of this pod into the main list, if they aren't already in the list
result = result.union(pods_cache[pod_name])
end
end

result = result.uniq
pods_used = result
top_level_pods = result
end

# Now that we have the simple list of all unique pods, grab their direct dependencies
dependencies_hash = {}
result.each do |pod_name|
dependencies_hash[pod_name] = pods_cache.key?(pod_name) ? pods_cache[pod_name] : []
end

[result, dependencies_hash]
Expand Down
2 changes: 1 addition & 1 deletion lib/cyclonedx/cocoapods/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@

module CycloneDX
module CocoaPods
VERSION = '1.4.0'
VERSION = '1.4.1'
end
end
30 changes: 30 additions & 0 deletions spec/cyclonedx/cocoapods/podfile_analyzer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
RSpec.describe CycloneDX::CocoaPods::PodfileAnalyzer do
let(:fixtures) { Pathname.new(File.expand_path('../../fixtures', __dir__)) }
let(:empty_podfile) { 'EmptyPodfile/Podfile' }
let(:large_podfile) { 'LargePodfile/Podfile' }
let(:simple_pod) { 'SimplePod/Podfile' }
let(:restricted_pod) { 'RestrictedPod/Podfile' }
let(:tests_pod) { 'TestingPod/Podfile' }
Expand Down Expand Up @@ -103,6 +104,35 @@
expect(pod_names.length).to eq(dependencies.length)
end

it 'should load large podfiles quickly' do
analyzer = CycloneDX::CocoaPods::PodfileAnalyzer.new(logger: @logger)

pod_file = Pod::Podfile.from_file(fixtures + large_podfile)
expect(pod_file).not_to be_nil
lock_file = Pod::Lockfile.from_file(fixtures + "#{large_podfile}.lock")
expect(lock_file).not_to be_nil

included_pods, dependencies = analyzer.parse_pods(pod_file, lock_file)

# Only 104 pods listed in the Podfile, but there are 187 pods counting all 104 plus dependencies.
expect(included_pods.count).to eq(187)
# There are 187 pods here! We only verify some of them.
pod_names = included_pods.map(&:name)
expect(pod_names.first(6)).to eq(['boost', 'DoubleConversion', 'Dynatrace',
'Dynatrace/xcframework', 'EXApplication', 'EXConstants'])
expect(pod_names.last(5)).to eq(['VisionCameraOcr', 'Yoga', 'ZXingObjC/Core',
'ZXingObjC/OneD', 'ZXingObjC/PDF417'])
# rubocop:disable Layout/LineLength
expect(dependencies.first).to eq([
'pkg:cocoapods/[email protected]?download_url=..%2Fnode_modules%2Freact-native%2Fthird-party-podspecs%2Fboost.podspec',
[]
])
# rubocop:enable Layout/LineLength

# Each of the pods should have an entry in the dependencies hash
expect(pod_names.length).to eq(dependencies.length)
end

it 'should find all simple pods' do
analyzer = CycloneDX::CocoaPods::PodfileAnalyzer.new(logger: @logger)

Expand Down
109 changes: 109 additions & 0 deletions spec/fixtures/LargePodfile/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
project 'SampleProject.xcodeproj'
platform :osx, '11.0'

target 'SampleProject' do
pod 'Alamofire'
pod 'boost'
pod 'DoubleConversion'
pod 'EXApplication'
pod 'EXConstants'
pod 'EXJSONUtils'
pod 'EXManifests'
pod 'Expo'
pod 'expo-dev-client'
pod 'expo-dev-launcher'
pod 'expo-dev-menu'
pod 'expo-dev-menu-interface'
pod 'ExpoAsset'
pod 'ExpoCamera'
pod 'ExpoCellular'
pod 'ExpoClipboard'
pod 'ExpoCrypto'
pod 'ExpoDevice'
pod 'ExpoFileSystem'
pod 'ExpoFont'
pod 'ExpoHead'
pod 'ExpoKeepAwake'
pod 'ExpoLocalAuthentication'
pod 'ExpoModulesCore'
pod 'ExpoNetwork'
pod 'ExpoScreenOrientation'
pod 'ExpoSecureStore'
pod 'ExpoWebBrowser'
pod 'EXSplashScreen'
pod 'EXUpdatesInterface'
pod 'FBLazyVector'
pod 'fmt'
pod 'glog'
pod 'hermes-engine'
pod 'jail-monkey'
pod 'RCT-Folly'
pod 'RCT-Folly/Fabric'
pod 'RCTDeprecation'
pod 'RCTRequired'
pod 'RCTTypeSafety'
pod 'React'
pod 'React-callinvoker'
pod 'React-Codegen'
pod 'React-Core'
pod 'React-Core/RCTWebSocket'
pod 'React-CoreModules'
pod 'React-cxxreact'
pod 'React-debug'
pod 'React-Fabric'
pod 'React-FabricImage'
pod 'React-featureflags'
pod 'React-graphics'
pod 'React-hermes'
pod 'React-ImageManager'
pod 'React-jserrorhandler'
pod 'React-jsi'
pod 'React-jsiexecutor'
pod 'React-jsinspector'
pod 'React-jsitracing'
pod 'React-logger'
pod 'React-Mapbuffer'
pod 'react-native-dynatrace'
pod 'react-native-keyboard-controller'
pod 'react-native-netinfo'
pod 'react-native-piwik-pro-sdk'
pod 'react-native-safe-area-context'
pod 'react-native-simple-crypto'
pod 'react-native-webview'
pod 'react-native-worklets-core'
pod 'React-nativeconfig'
pod 'React-NativeModulesApple'
pod 'React-perflogger'
pod 'React-RCTActionSheet'
pod 'React-RCTAnimation'
pod 'React-RCTAppDelegate'
pod 'React-RCTBlob'
pod 'React-RCTFabric'
pod 'React-RCTImage'
pod 'React-RCTLinking'
pod 'React-RCTNetwork'
pod 'React-RCTSettings'
pod 'React-RCTText'
pod 'React-RCTVibration'
pod 'React-rendererdebug'
pod 'React-rncore'
pod 'React-RuntimeApple'
pod 'React-RuntimeCore'
pod 'React-runtimeexecutor'
pod 'React-RuntimeHermes'
pod 'React-runtimescheduler'
pod 'React-utils'
pod 'ReactCommon/turbomodule/core'
pod 'RNCAsyncStorage'
pod 'RNCMaskedView'
pod 'RNCPicker'
pod 'RNDateTimePicker'
pod 'RNGestureHandler'
pod 'RNReanimated'
pod 'RNScreens'
pod 'RNSVG'
pod 'ssl-pinning'
pod 'VisionCamera'
pod 'VisionCameraOcr'
pod 'Yoga'
end
Loading

0 comments on commit 56a9a37

Please sign in to comment.