From 2b6fb96d901cd2284cb5ab3eb44933ea4a3a69c8 Mon Sep 17 00:00:00 2001 From: Kyle Hammond Date: Thu, 6 Oct 2022 14:27:23 -0500 Subject: [PATCH 1/3] Handle two edge cases of Podfiles and pods. Having a Podfile that includes nothing no longer causes a crash. Including a pod that has a platform-specific dependent pod that is not actually used no longer causes a crash. Signed-off-by: Kyle Hammond --- lib/cyclonedx/cocoapods/podfile_analyzer.rb | 8 ++-- .../cocoapods/podfile_analyzer_spec.rb | 40 ++++++++++++++++--- spec/fixtures/EmptyPodfile/Podfile | 6 +++ spec/fixtures/EmptyPodfile/Podfile.lock | 3 ++ spec/fixtures/RestrictedPod/Podfile | 7 ++++ spec/fixtures/RestrictedPod/Podfile.lock | 17 ++++++++ 6 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 spec/fixtures/EmptyPodfile/Podfile create mode 100644 spec/fixtures/EmptyPodfile/Podfile.lock create mode 100644 spec/fixtures/RestrictedPod/Podfile create mode 100644 spec/fixtures/RestrictedPod/Podfile.lock diff --git a/lib/cyclonedx/cocoapods/podfile_analyzer.rb b/lib/cyclonedx/cocoapods/podfile_analyzer.rb index 250efe3..de066eb 100644 --- a/lib/cyclonedx/cocoapods/podfile_analyzer.rb +++ b/lib/cyclonedx/cocoapods/podfile_analyzer.rb @@ -88,7 +88,7 @@ def simple_hash_of_lockfile_pods(lockfile) pods_hash = { } pods_used = lockfile.internal_data['PODS'] - pods_used.each { |pod| + pods_used&.each { |pod| if pod.is_a?(String) # Pods stored as String have no dependencies pod_name = pod.split.first @@ -109,11 +109,13 @@ def append_all_pod_dependencies(pods_used, pods_cache) original_number = 0 # 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 the edge case of having a Podfile with no pods used. + # 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 { |pod_name| - result.push(*pods_cache[pod_name]) unless pods_cache[pod_name].empty? + result.push(*pods_cache[pod_name]) unless !pods_cache.key?(pod_name) || pods_cache[pod_name].empty? } result = result.uniq pods_used = result diff --git a/spec/cyclonedx/cocoapods/podfile_analyzer_spec.rb b/spec/cyclonedx/cocoapods/podfile_analyzer_spec.rb index 68c5045..656580a 100644 --- a/spec/cyclonedx/cocoapods/podfile_analyzer_spec.rb +++ b/spec/cyclonedx/cocoapods/podfile_analyzer_spec.rb @@ -22,7 +22,9 @@ RSpec.describe CycloneDX::CocoaPods::PodfileAnalyzer do let(:fixtures) { Pathname.new(File.expand_path('../../../fixtures/', __FILE__)) } + let(:empty_podfile) { 'EmptyPodfile/Podfile' } let(:simple_pod) { 'SimplePod/Podfile' } + let(:restricted_pod) { 'RestrictedPod/Podfile' } let(:tests_pod) { 'TestingPod/Podfile' } ::Pod::Config.instance.installation_root = File.expand_path('../../../fixtures/', __FILE__) + '/' @@ -34,20 +36,48 @@ context 'parsing pods' do context 'when created with standard parameters' do + it 'should handle no pods correctly' do + analyzer = ::CycloneDX::CocoaPods::PodfileAnalyzer.new(logger: @logger) + + pod_file = ::Pod::Podfile.from_file(fixtures + empty_podfile) + expect(pod_file).not_to be_nil + lock_file = ::Pod::Lockfile.from_file(fixtures + (empty_podfile + '.lock')) + expect(lock_file).not_to be_nil + + included_pods = analyzer.parse_pods(pod_file, lock_file) + + pod_names = included_pods.map(&:name) + expect(pod_names).to eq([]) + end + it 'should find all simple pods' do analyzer = ::CycloneDX::CocoaPods::PodfileAnalyzer.new(logger: @logger) - simple_pod_file = ::Pod::Podfile.from_file(fixtures + simple_pod) - expect(simple_pod_file).not_to be_nil - simple_lock_file = ::Pod::Lockfile.from_file(fixtures + (simple_pod + '.lock')) - expect(simple_lock_file).not_to be_nil + pod_file = ::Pod::Podfile.from_file(fixtures + simple_pod) + expect(pod_file).not_to be_nil + lock_file = ::Pod::Lockfile.from_file(fixtures + (simple_pod + '.lock')) + expect(lock_file).not_to be_nil - included_pods = analyzer.parse_pods(simple_pod_file, simple_lock_file) + included_pods = analyzer.parse_pods(pod_file, lock_file) pod_names = included_pods.map(&:name) expect(pod_names).to eq(['Alamofire', 'MSAL', 'MSAL/app-lib']) end + it 'should find all pods actually used' do + analyzer = ::CycloneDX::CocoaPods::PodfileAnalyzer.new(logger: @logger) + + pod_file = ::Pod::Podfile.from_file(fixtures + restricted_pod) + expect(pod_file).not_to be_nil + lock_file = ::Pod::Lockfile.from_file(fixtures + (restricted_pod + '.lock')) + expect(lock_file).not_to be_nil + + included_pods = analyzer.parse_pods(pod_file, lock_file) + + pod_names = included_pods.map(&:name) + expect(pod_names).to eq(['EFQRCode']) + end + it 'should find all pods' do analyzer = ::CycloneDX::CocoaPods::PodfileAnalyzer.new(logger: @logger) diff --git a/spec/fixtures/EmptyPodfile/Podfile b/spec/fixtures/EmptyPodfile/Podfile new file mode 100644 index 0000000..3ed1842 --- /dev/null +++ b/spec/fixtures/EmptyPodfile/Podfile @@ -0,0 +1,6 @@ +project 'SampleProject.xcodeproj' +platform :ios, '16.0' + +target 'SampleProject' do + # No pods specified! +end diff --git a/spec/fixtures/EmptyPodfile/Podfile.lock b/spec/fixtures/EmptyPodfile/Podfile.lock new file mode 100644 index 0000000..d4d1cb1 --- /dev/null +++ b/spec/fixtures/EmptyPodfile/Podfile.lock @@ -0,0 +1,3 @@ +PODFILE CHECKSUM: f88bba0bd98b5e52e9e9d8ae347b4c8274976c54 + +COCOAPODS: 1.11.3 diff --git a/spec/fixtures/RestrictedPod/Podfile b/spec/fixtures/RestrictedPod/Podfile new file mode 100644 index 0000000..c82d442 --- /dev/null +++ b/spec/fixtures/RestrictedPod/Podfile @@ -0,0 +1,7 @@ +project 'SampleProject.xcodeproj' +platform :ios, '13.0' + +target 'SampleProject' do + # EFQRCode has a dependency only for watchOS so it's not actually included in this iOS project + pod 'EFQRCode' +end diff --git a/spec/fixtures/RestrictedPod/Podfile.lock b/spec/fixtures/RestrictedPod/Podfile.lock new file mode 100644 index 0000000..c555827 --- /dev/null +++ b/spec/fixtures/RestrictedPod/Podfile.lock @@ -0,0 +1,17 @@ +PODS: + - EFQRCode (6.2.1): + - swift_qrcodejs (~> 2.2.2) + +DEPENDENCIES: + - EFQRCode + +SPEC REPOS: + https://github.com/CocoaPods/Specs.git: + - EFQRCode + +SPEC CHECKSUMS: + EFQRCode: a4d39ec3466b68dffa71de3b5caef7c9ceefdc53 + +PODFILE CHECKSUM: 3e93ab9c0580591da64eca6661a70cd9abb23c67 + +COCOAPODS: 1.10.2 From 5bba49221d9adf0d641ae25afbcb927714b4ae1c Mon Sep 17 00:00:00 2001 From: Kyle Hammond Date: Thu, 6 Oct 2022 15:22:02 -0500 Subject: [PATCH 2/3] Update error messaging when analyzing pods from spec repositories. Fixes #48. Signed-off-by: Kyle Hammond --- lib/cyclonedx/cocoapods/pod_attributes.rb | 6 +++--- spec/cyclonedx/cocoapods/pod_attributes_spec.rb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/cyclonedx/cocoapods/pod_attributes.rb b/lib/cyclonedx/cocoapods/pod_attributes.rb index 1d2f18b..66a0a23 100644 --- a/lib/cyclonedx/cocoapods/pod_attributes.rb +++ b/lib/cyclonedx/cocoapods/pod_attributes.rb @@ -33,11 +33,11 @@ def self.searchable_source(url:, source_manager:) def attributes_for(pod:) specification_sets = @source_manager.search_by_name("^#{Regexp.escape(pod.root_name)}$") - raise SearchError, "No pod found named #{pod.name}" if specification_sets.length == 0 - raise SearchError, "More than one pod found named #{pod.name}" if specification_sets.length > 1 + raise SearchError, "No pod found named #{pod.name}; run 'pod repo update' and try again" if specification_sets.length == 0 + raise SearchError, "More than one pod found named #{pod.name}; a pod in a private spec repo should not have the same name as a public pod" if specification_sets.length > 1 paths = specification_sets[0].specification_paths_for_version(pod.version) - raise SearchError, "Version #{pod.version} not found for pod #{pod.name}" if paths.length == 0 + raise SearchError, "Version #{pod.version} not found for pod #{pod.name}; run 'pod repo update' and try again" if paths.length == 0 ::Pod::Specification.from_file(paths[0]).attributes_hash end diff --git a/spec/cyclonedx/cocoapods/pod_attributes_spec.rb b/spec/cyclonedx/cocoapods/pod_attributes_spec.rb index 657ddcf..d629938 100644 --- a/spec/cyclonedx/cocoapods/pod_attributes_spec.rb +++ b/spec/cyclonedx/cocoapods/pod_attributes_spec.rb @@ -64,7 +64,7 @@ it 'should raise an error' do expect { @source.attributes_for(pod: pod) - }.to raise_error(CycloneDX::CocoaPods::SearchError, "No pod found named #{pod.name}") + }.to raise_error(CycloneDX::CocoaPods::SearchError, "No pod found named #{pod.name}; run 'pod repo update' and try again") end end @@ -76,7 +76,7 @@ it 'should raise an error' do expect { @source.attributes_for(pod: pod) - }.to raise_error(CycloneDX::CocoaPods::SearchError, "More than one pod found named #{pod.name}") + }.to raise_error(CycloneDX::CocoaPods::SearchError, "More than one pod found named #{pod.name}; a pod in a private spec repo should not have the same name as a public pod") end end @@ -89,7 +89,7 @@ it 'should raise an error' do expect { @source.attributes_for(pod: pod) - }.to raise_error(CycloneDX::CocoaPods::SearchError, "Version #{pod.version} not found for pod #{pod.name}") + }.to raise_error(CycloneDX::CocoaPods::SearchError, "Version #{pod.version} not found for pod #{pod.name}; run 'pod repo update' and try again") end end From a6e852988afc4a41accc7dc3740247ad915734c0 Mon Sep 17 00:00:00 2001 From: Kyle Hammond Date: Thu, 6 Oct 2022 15:24:11 -0500 Subject: [PATCH 3/3] Update version and CHANGELOG. Signed-off-by: Kyle Hammond --- CHANGELOG.md | 10 ++++++++++ lib/cyclonedx/cocoapods/version.rb | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a2322..28c6fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,18 @@ 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.1.1] + +### Changed +- Better error messaging when a problem is encountered while gathering pod information ([Issue #48](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/48)) [@macblazer](https://github.com/macblazer). + +### Fixed +- Including a pod that has a platform-specific dependency for an unused platform no longer causes a crash ([Issue #46](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/46)) [@macblazer](https://github.com/macblazer). +- Analyzing a Podfile that has no pods defined in it no longer causes a crash [@macblazer](https://github.com/macblazer). + ## [1.1.0] +### Added - Can now eliminate Podfile targets that include "test" in their name ([Issue #43](https://github.com/CycloneDX/cyclonedx-cocoapods/issues/43)) [@macblazer](https://github.com/macblazer). ## [1.0.0] diff --git a/lib/cyclonedx/cocoapods/version.rb b/lib/cyclonedx/cocoapods/version.rb index 4e3e3f6..a5eb163 100644 --- a/lib/cyclonedx/cocoapods/version.rb +++ b/lib/cyclonedx/cocoapods/version.rb @@ -20,7 +20,7 @@ module CycloneDX module CocoaPods - VERSION = '1.1.0' + VERSION = '1.1.1' DEPENDENCIES = { cocoapods: '~> 1.10.1', nokogiri: '~> 1.11.2'