diff --git a/CHANGELOG.md b/CHANGELOG.md index e9140abad..ddba14f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ _None_ ### Internal Changes - Updates `activesupport` to `6.1.7.1`, addressing [a security issue](https://github.com/advisories/GHSA-j6gc-792m-qgm2). This is a major version change, but as the dependency is internal-only, it shouldn't be a breaking change for clients. [#441] +- Add the explicit dependency to `xcodeproj (~> 1.22)`, used in this case to replace the previous manual parsing of `.xcconfig` files. [#451] ## 6.3.0 diff --git a/Gemfile.lock b/Gemfile.lock index e25104bc0..df251f5e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,6 +16,7 @@ PATH progress_bar (~> 1.3) rake (>= 12.3, < 14.0) rake-compiler (~> 1.0) + xcodeproj (~> 1.22) GEM remote: https://rubygems.org/ diff --git a/fastlane-plugin-wpmreleasetoolkit.gemspec b/fastlane-plugin-wpmreleasetoolkit.gemspec index 3266d5cbd..3e67c9591 100644 --- a/fastlane-plugin-wpmreleasetoolkit.gemspec +++ b/fastlane-plugin-wpmreleasetoolkit.gemspec @@ -37,6 +37,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'progress_bar', '~> 1.3' spec.add_dependency 'rake', '>= 12.3', '< 14.0' spec.add_dependency 'rake-compiler', '~> 1.0' + spec.add_dependency 'xcodeproj', '~> 1.22' # `google-cloud-storage` is required by fastlane, but we pin it in case it's not in the future spec.add_dependency 'google-cloud-storage', '~> 1.31' diff --git a/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_version_helper.rb b/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_version_helper.rb index dd98e2962..8866e111b 100644 --- a/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_version_helper.rb +++ b/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_version_helper.rb @@ -1,3 +1,5 @@ +require 'xcodeproj' + module Fastlane module Helper module Ios @@ -23,6 +25,9 @@ module VersionHelper # def self.get_xcconfig_public_version(xcconfig_file:) version = read_long_version_from_config_file(xcconfig_file) + + UI.user_error!(".xcconfig file doesn't have a version configured") if version.nil? + vp = get_version_parts(version) return "#{vp[MAJOR_NUMBER]}.#{vp[MINOR_NUMBER]}" unless is_hotfix?(version) @@ -306,19 +311,15 @@ def self.read_build_number_from_config_file(filePath) # Read the value of a given key from an `.xcconfig` file. # # @param [String] key The xcconfig key to get the value for - # @param [String] filePath The path to the `.xcconfig` file to read the value from + # @param [String] file_path The path to the `.xcconfig` file to read the value from # # @return [String] The value for the given key, or `nil` if the key was not found. # - def self.read_from_config_file(key, filePath) - File.open(filePath, 'r') do |f| - f.each_line do |line| - line = line.strip() - return line.split('=')[1] if line.start_with?("#{key}=") - end - end + def self.read_from_config_file(key, file_path) + UI.user_error!(".xcconfig file #{file_path} not found") unless File.exist?(file_path) - return nil + config = Xcodeproj::Config.new(file_path) + config.attributes[key] end # Ensure that the version provided is only composed of number parts and return the validated string diff --git a/spec/ios_get_app_version_spec.rb b/spec/ios_get_app_version_spec.rb index 2e4334d74..0355fd175 100644 --- a/spec/ios_get_app_version_spec.rb +++ b/spec/ios_get_app_version_spec.rb @@ -3,39 +3,85 @@ describe Fastlane::Actions::IosGetAppVersionAction do describe 'getting the public app version from the provided .xcconfig file' do it 'parses the xcconfig file format correctly and gets the public version' do + xcconfig_mock_content = <<~CONTENT + // a comment + VERSION_SHORT = 6 + VERSION_LONG = 6.30.0 + CONTENT + + expect_version(xcconfig_mock_content: xcconfig_mock_content, expected_version: '6.30') + end + + it 'parses the xcconfig file format correctly and gets the public hotfix version' do + xcconfig_mock_content = <<~CONTENT + VERSION_SHORT = 6 + // a comment + VERSION_LONG = 6.30.1 + CONTENT + + expect_version(xcconfig_mock_content: xcconfig_mock_content, expected_version: '6.30.1') + end + + it 'parses the xcconfig with keys without spacing and gets the public version' do xcconfig_mock_content = <<~CONTENT // a comment VERSION_SHORT=6 VERSION_LONG=6.30.0 CONTENT - allow(File).to receive(:exist?).and_return(true) - expect_version(xcconfig_mock_content: xcconfig_mock_content, expected_version: '6.30') end - it 'parses the xcconfig file format correctly and gets the public hotfix version' do + it 'parses the xcconfig with keys without spacing and gets the public hotfix version' do xcconfig_mock_content = <<~CONTENT VERSION_SHORT=6 // a comment VERSION_LONG=6.30.1 CONTENT - allow(File).to receive(:exist?).and_return(true) - expect_version(xcconfig_mock_content: xcconfig_mock_content, expected_version: '6.30.1') end - def expect_version(xcconfig_mock_content:, expected_version:) - xcconfig_mock_file_path = File.join('mock', 'file', 'path') + it 'fails to extract the version from an xcconfig file with an invalid format' do + xcconfig_mock_content = <<~CONTENT + VERSION_SHORT = 6 + VERSION_LONG 6.30.1 + CONTENT + + expect do + expect_version(xcconfig_mock_content: xcconfig_mock_content, expected_version: 'n/a') + end.to raise_error(FastlaneCore::Interface::FastlaneError) + end + + it 'throws an error when the file is not found' do + file_path = 'file/not/found' - allow(File).to receive(:open).with(xcconfig_mock_file_path, 'r').and_yield(StringIO.new(xcconfig_mock_content)) + expect do + run_described_fastlane_action( + public_version_xcconfig_file: file_path + ) + end.to raise_error(FastlaneCore::Interface::FastlaneError) + end + + it "throws an error when there isn't a version configured in the .xcconfig file" do + xcconfig_mock_content = <<~CONTENT + VERSION_SHORT = 6 + // a comment + CONTENT + + expect do + expect_version(xcconfig_mock_content: xcconfig_mock_content, expected_version: 'n/a') + end.to raise_error(FastlaneCore::Interface::FastlaneError) + end - version_result = run_described_fastlane_action( - public_version_xcconfig_file: xcconfig_mock_file_path - ) + def expect_version(xcconfig_mock_content:, expected_version:) + with_tmp_file(named: 'mock_xcconfig.xcconfig', content: xcconfig_mock_content) do |tmp_file_path| + version_result = run_described_fastlane_action( + public_version_xcconfig_file: tmp_file_path + ) - expect(version_result).to eq(expected_version) + expect(version_result).to eq(expected_version) + end end end end