Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make APPLICATION_EXTENSION_API_ONLY not break when performing a cached incremental install #9142

Merged
7 changes: 5 additions & 2 deletions CHANGELOG.md
Expand Up @@ -12,8 +12,11 @@ To install release candidates run `[sudo] gem install cocoapods --pre`

##### Bug Fixes

* None.

* Make `APPLICATION_EXTENSION_API_ONLY` build setting not break when performing a cached incremental install.
[Igor Makarov](https://github.com/igor-makarov)
[#8967](https://github.com/CocoaPods/CocoaPods/issues/8967)
[#9141](https://github.com/CocoaPods/CocoaPods/issues/9141)
[#9142](https://github.com/CocoaPods/CocoaPods/pull/9142)

## 1.8.0.beta.2 (2019-08-27)

Expand Down
12 changes: 12 additions & 0 deletions lib/cocoapods/installer/analyzer.rb
Expand Up @@ -438,6 +438,18 @@ def generate_targets(resolver_specs_by_target, target_inspections)
end
target.search_paths_aggregate_targets.concat(search_paths_aggregate_targets).freeze
end

aggregate_targets.each do |aggregate_target|
is_app_extension = !(aggregate_target.user_targets.map(&:symbol_type) &
[:app_extension, :watch_extension, :watch2_extension, :tv_extension, :messages_extension]).empty?
is_app_extension ||= aggregate_target.user_targets.any? { |ut| ut.common_resolved_build_setting('APPLICATION_EXTENSION_API_ONLY') == 'YES' }

next unless is_app_extension

aggregate_target.mark_application_extension_api_only
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the logic for determining is_app_extension live inside AggregateTarget. It looks like you're only using properties off aggregate_target. This allows you to remove mark_application_extension_api_only which is kinda ugly and get rid of the default false value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the logic for is_app_extension can be put in AggregateTarget, mark_application_extension_api_only cannot be done away with - PodTarget also needs to be marked and it has no idea who its parent is.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah bummer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a chat with @segiddins about it and it makes sense for the Analyzer to be the single one responsible for this weird flag.

aggregate_target.pod_targets.each(&:mark_application_extension_api_only)
end

if installation_options.integrate_targets?
# Copy embedded target pods that cannot have their pods embedded as frameworks to
# their host targets, and ensure we properly link library pods to their host targets
Expand Down
Expand Up @@ -41,10 +41,6 @@ def install!
aggregate_target = aggregate_target_installation_result.target
aggregate_native_target = aggregate_target_installation_result.native_target
project = aggregate_native_target.project
is_app_extension = !(aggregate_target.user_targets.map(&:symbol_type) &
[:app_extension, :watch_extension, :watch2_extension, :tv_extension, :messages_extension]).empty?
is_app_extension ||= aggregate_target.user_targets.any? { |ut| ut.common_resolved_build_setting('APPLICATION_EXTENSION_API_ONLY') == 'YES' }
configure_app_extension_api_only_to_native_target(aggregate_native_target) if is_app_extension
# Wire up dependencies that are part of inherit search paths for this aggregate target.
aggregate_target.search_paths_aggregate_targets.each do |search_paths_target|
aggregate_native_target.add_dependency(aggregate_target_installation_results[search_paths_target.name].native_target)
Expand All @@ -54,7 +50,6 @@ def install!
if pod_target_installation_result = pod_target_installation_results[pod_target.name]
pod_target_native_target = pod_target_installation_result.native_target
aggregate_native_target.add_dependency(pod_target_native_target)
configure_app_extension_api_only_to_native_target(pod_target_native_target) if is_app_extension
else
# Hit the cache
is_local = sandbox.local?(pod_target.pod_name)
Expand All @@ -65,17 +60,6 @@ def install!
end
end
end

private

# Sets the APPLICATION_EXTENSION_API_ONLY build setting to YES for all
# configurations of the given native target.
#
def configure_app_extension_api_only_to_native_target(native_target)
native_target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'YES'
end
end
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions lib/cocoapods/target.rb
Expand Up @@ -46,6 +46,10 @@ class Target
attr_reader :build_type
private :build_type

# @return [Boolean] whether the target can be linked to app extensions only.
#
attr_reader :application_extension_api_only

# Initialize a new target
#
# @param [Sandbox] sandbox @see #sandbox
Expand All @@ -63,6 +67,7 @@ def initialize(sandbox, host_requires_frameworks, user_build_configurations, arc
@platform = platform
@build_type = build_type

@application_extension_api_only = false
@build_settings = create_build_settings
end

Expand Down Expand Up @@ -302,6 +307,13 @@ def dummy_source_path
support_files_dir + "#{label}-dummy.m"
end

# mark the target as extension-only,
# translates to APPLICATION_EXTENSION_API_ONLY = YES in the build settings
#
def mark_application_extension_api_only
@application_extension_api_only = true
end

#-------------------------------------------------------------------------#

private
Expand Down
1 change: 1 addition & 0 deletions lib/cocoapods/target/aggregate_target.rb
Expand Up @@ -98,6 +98,7 @@ def merge_embedded_pod_targets(embedded_pod_targets_for_build_configuration)
AggregateTarget.new(sandbox, host_requires_frameworks, user_build_configurations, archs, platform,
target_definition, client_root, user_project, user_target_uuids, merged, :build_type => build_type).tap do |aggregate_target|
aggregate_target.search_paths_aggregate_targets.concat(search_paths_aggregate_targets).freeze
aggregate_target.mark_application_extension_api_only if application_extension_api_only
end
end

Expand Down
5 changes: 5 additions & 0 deletions lib/cocoapods/target/build_settings.rb
Expand Up @@ -856,6 +856,11 @@ def requires_objc_linker_flag?
target.configuration_build_dir(CONFIGURATION_BUILD_DIR_VARIABLE)
end

# @return [String]
define_build_settings_method :application_extension_api_only, :build_setting => true, :memoized => true do
target.application_extension_api_only ? 'YES' : nil
end

#-------------------------------------------------------------------------#

# @!group Target Properties
Expand Down
145 changes: 144 additions & 1 deletion spec/unit/installer/analyzer_spec.rb
@@ -1,7 +1,7 @@
require File.expand_path('../../../spec_helper', __FILE__)

module Pod
describe Installer::Analyzer do
describe Installer::Analyzer do # rubocop:disable Metrics/BlockLength
describe 'Analysis' do
before do
repos = [Source.new(fixture('spec-repos/test_repo')), TrunkSource.new(fixture('spec-repos/trunk'))]
Expand Down Expand Up @@ -2029,6 +2029,149 @@ module Pod
analyzer.analyze
end.message.should.match /Sample Extensions Project \(false\) and Today Extension \(true\) do not both set use_frameworks!\./
end

describe 'APPLICATION_EXTENSION_API_ONLY' do
it 'configures APPLICATION_EXTENSION_API_ONLY for app extension targets' do
@podfile.use_frameworks!
analyzer = Pod::Installer::Analyzer.new(config.sandbox, @podfile)
result = analyzer.analyze

result.targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['Pods-Sample Extensions Project', false], ['Pods-Today Extension', true]]
result.pod_targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['matryoshka-Bar', true], ['matryoshka-Bar-Foo', false], ['JSONKit', false], ['monkey', true]]
end

it 'configures APPLICATION_EXTENSION_API_ONLY for watch app extension targets' do
@user_project = Xcodeproj::Project.open(SpecHelper.create_sample_app_copy_from_fixture('Sample Extensions Project'))
targets = @user_project.targets
targets.delete(targets.find('Sample Extensions Project').first)
extension_target = targets.find('Today Extension').first
extension_target.product_type = 'com.apple.product-type.watchkit2-extension'
extension_target.name = 'watchOS Extension Target'
extension_target.symbol_type.should == :watch2_extension
@user_project.save
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this actually leave the file system dirty after this test runs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't - this is a temp copy (create_sample_app_copy_from_fixture).

project_path = @user_project.path
@podfile = Pod::Podfile.new do
source SpecHelper.test_repo_url
platform :watchos, '2.0'
project project_path

target 'watchOS Extension Target' do
use_frameworks!
pod 'monkey'
end
end

@podfile.use_frameworks!
analyzer = Pod::Installer::Analyzer.new(config.sandbox, @podfile)
result = analyzer.analyze

result.targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['Pods-watchOS Extension Target', true]]
result.pod_targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['monkey', true]]
end

it 'configures APPLICATION_EXTENSION_API_ONLY for TV app extension targets' do
@user_project = Xcodeproj::Project.open(SpecHelper.create_sample_app_copy_from_fixture('Sample Extensions Project'))
targets = @user_project.targets
targets.delete(targets.find('Sample Extensions Project').first)
extension_target = targets.find('Today Extension').first
extension_target.product_type = 'com.apple.product-type.tv-app-extension'
extension_target.name = 'tvOS Extension Target'
extension_target.symbol_type.should == :tv_extension
@user_project.save
project_path = @user_project.path
@podfile = Pod::Podfile.new do
source SpecHelper.test_repo_url
platform :tvos, '9.0'
project project_path

target 'tvOS Extension Target' do
use_frameworks!
pod 'monkey'
end
end

@podfile.use_frameworks!
analyzer = Pod::Installer::Analyzer.new(config.sandbox, @podfile)
result = analyzer.analyze

result.targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['Pods-tvOS Extension Target', true]]
result.pod_targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['monkey', true]]
end

it 'configures APPLICATION_EXTENSION_API_ONLY for messages extension targets' do
@user_project = Xcodeproj::Project.open(SpecHelper.create_sample_app_copy_from_fixture('Sample Extensions Project'))
targets = @user_project.targets
app_target = targets.find { |t| t.name == 'Sample Extensions Project' }
app_target.product_type = 'com.apple.product-type.application.messages'
extension_target = targets.find { |t| t.name == 'Today Extension' }
extension_target.product_type = 'com.apple.product-type.app-extension.messages'
extension_target.name = 'Messages Extension Target'
extension_target.symbol_type.should == :messages_extension
@user_project.save
project_path = @user_project.path
@podfile = Pod::Podfile.new do
source SpecHelper.test_repo_url
platform :ios, '8.0'
project project_path

target 'Sample Extensions Project' do
pod 'JSONKit', '1.4'
end

target 'Messages Extension Target' do
use_frameworks!
pod 'monkey'
end
end

@podfile.use_frameworks!
analyzer = Pod::Installer::Analyzer.new(config.sandbox, @podfile)
result = analyzer.analyze

result.targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['Pods-Sample Extensions Project', false], ['Pods-Messages Extension Target', true]]
result.pod_targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['JSONKit', false], ['monkey', true]]
end

it 'configures APPLICATION_EXTENSION_API_ONLY when build setting is set in user target' do
@user_project = Xcodeproj::Project.open(SpecHelper.create_sample_app_copy_from_fixture('Sample Extensions Project'))
targets = @user_project.targets
app_target = targets.find { |t| t.name == 'Sample Extensions Project' }
app_target.build_configurations.each { |c| c.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'YES' }
@user_project.save
project_path = @user_project.path
@podfile = Pod::Podfile.new do
source SpecHelper.test_repo_url
platform :ios, '8.0'
project project_path

target 'Sample Extensions Project' do
pod 'JSONKit', '1.4'
end

target 'Today Extension' do
use_frameworks!
pod 'monkey'
end
end

@podfile.use_frameworks!
analyzer = Pod::Installer::Analyzer.new(config.sandbox, @podfile)
result = analyzer.analyze

result.targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['Pods-Sample Extensions Project', true], ['Pods-Today Extension', true]]
result.pod_targets.map { |t| [t.name, t.application_extension_api_only] }.
should == [['JSONKit', true], ['monkey', true]]
end
end
end

#-------------------------------------------------------------------------#
Expand Down
55 changes: 0 additions & 55 deletions spec/unit/installer/xcode/multi_pods_project_generator_spec.rb
Expand Up @@ -537,68 +537,13 @@ class Xcode
}
end

it 'configures APPLICATION_EXTENSION_API_ONLY for pod targets of an aggregate target' do
user_target = stub('SampleApp-iOS-User-Target', :symbol_type => :app_extension)
@ios_target.stubs(:user_targets).returns([user_target])
pod_generator_result = @generator.generate!
projects_by_pod_targets = pod_generator_result.projects_by_pod_targets
pod_generator_result.project.targets.find { |t| t.name == 'Pods-SampleApp-iOS' }.dependencies.each do |dependency|
project = projects_by_pod_targets.find { |_, pod_targets| pod_targets.find { |t| t.label == dependency.name } }.first
build_settings = project.targets.find { |t| t.name == dependency.name }.build_configurations.map(&:build_settings)
build_settings.each do |build_setting|
build_setting['APPLICATION_EXTENSION_API_ONLY'].should == 'YES'
end
end
end

it 'configures APPLICATION_EXTENSION_API_ONLY for app extension targets' do
user_target = stub('SampleApp-iOS-User-Target', :symbol_type => :app_extension)
@ios_target.stubs(:user_targets).returns([user_target])
pod_generator_result = @generator.generate!
build_settings = pod_generator_result.project.targets.find { |t| t.name == 'Pods-SampleApp-iOS' }.build_configurations.map(&:build_settings)
build_settings.each do |build_setting|
build_setting['APPLICATION_EXTENSION_API_ONLY'].should == 'YES'
end
end

it 'configures APPLICATION_EXTENSION_API_ONLY for watch2 extension targets' do
user_target = stub('SampleApp-iOS-User-Target', :symbol_type => :watch2_extension)
@ios_target.stubs(:user_targets).returns([user_target])
pod_generator_result = @generator.generate!
build_settings = pod_generator_result.project.targets.find { |t| t.name == 'Pods-SampleApp-iOS' }.build_configurations.map(&:build_settings)
build_settings.each do |build_setting|
build_setting['APPLICATION_EXTENSION_API_ONLY'].should == 'YES'
end
end

it 'configures APPLICATION_EXTENSION_API_ONLY for tvOS extension targets' do
user_target = stub('SampleApp-iOS-User-Target', :symbol_type => :tv_extension)
@ios_target.stubs(:user_targets).returns([user_target])
pod_generator_result = @generator.generate!
build_settings = pod_generator_result.project.targets.find { |t| t.name == 'Pods-SampleApp-iOS' }.build_configurations.map(&:build_settings)
build_settings.each do |build_setting|
build_setting['APPLICATION_EXTENSION_API_ONLY'].should == 'YES'
end
end

it 'configures APPLICATION_EXTENSION_API_ONLY for Messages extension targets' do
user_target = stub('SampleApp-iOS-User-Target', :symbol_type => :messages_extension)
@ios_target.stubs(:user_targets).returns([user_target])
pod_generator_result = @generator.generate!
build_settings = pod_generator_result.project.targets.find { |t| t.name == 'Pods-SampleApp-iOS' }.build_configurations.map(&:build_settings)
build_settings.each do |build_setting|
build_setting['APPLICATION_EXTENSION_API_ONLY'].should == 'YES'
end
end

it "uses the user project's object version for the all projects" do
tmp_directory = Pathname(Dir.tmpdir) + 'CocoaPods'
FileUtils.mkdir_p(tmp_directory)
proj = Xcodeproj::Project.new(tmp_directory + 'Yolo.xcodeproj', false, 51)
proj.save

user_target = stub('SampleApp-iOS-User-Target', :symbol_type => :application)
user_target.expects(:common_resolved_build_setting).with('APPLICATION_EXTENSION_API_ONLY').returns('NO')

target = AggregateTarget.new(config.sandbox, false,
{ 'App Store' => :release, 'Debug' => :debug, 'Release' => :release, 'Test' => :debug },
Expand Down