-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Description
Expanding use_frameworks! DSL (specifying linkage style)
- Authors: Dimitris Koutsogiorgas
- Implementation: Expand
use_frameworks!DSL to accept linkage style. Core#581, Integrateuse_frameworks!linkage DSL. #9119 - Bugs: N/A
- Previous RFC: N/A
Introduction
CocoaPods today provides little space for pod consumers to customize how they want their pods to be linked. As of today, pod consumers are only able to customize different packaging (static library vs framework) via the use_frameworks! DSL. The linking style happens to be a side-effect of that decision without giving pod consumers a proper way to configure it.
This RFC aims to expand the use_frameworks! DSL in order to give pod consumers a little bit more control and allow them to specify the link style they want if they decide to integrate their pods using frameworks.
Motivation
Leveraging the .framework bundle structure has proven to be very useful. Using frameworks was also a hard requirement in the earlier days of Swift.
Here's the definition of a framework from Apple:
A framework is a hierarchical directory that encapsulates shared resources, such as a dynamic shared library, nib files, image files, localized strings, header files, and reference documentation in a single package. Multiple applications can use all of these resources simultaneously. The system loads them into memory as needed and shares the one copy of the resource among all applications whenever possible.
Up until recently, a .framework mostly contained a dynamic shared library which was loaded at runtime when the application launched. However, this could cause issues when the number of dynamic libraries grows high enough because the app would take longer to boot. In some extreme cases, the app would be killed off by the system if it took too long to load all dynamically shared libraries.
Since CocoaPods introduced the static_framework DSL, only pod authors could leverage the .framework structure but instead force static linkage. On the other hand, pod consumers still have no way of specifying that they want to use .framework packaging but instead statically link their pods into their app executable.
Giving this option to pod consumers would give them more control and allow them to leverage both the .framework structure but also avoid some of the aforementioned issues.
Proposed solution
Extend the existing use_frameworks! DSL in order to allow pod consumers to specify how they want linking to happen via a hash.
Detailed design
Core gem changes
Move BuildType class into core gem
The recently introduced BuildType class encapsulates all of packaging and linking concepts into one very simple and well tested class. This can be leveraged as part of this work in order for it to be used as an easy to understand model for storing all information related to a target definition's desired build type.
Thus, as part of this proposal, the BuildType class and its specs will move over to the CocoaPods/Core gem.
Expanding use_frameworks!
The current TargetDefinition use_frameworks! method is defined as such:
def use_frameworks!(flag = true)
set_hash_value('uses_frameworks', flag)
endIt stores the given flag as a Boolean in the target definition's hash.
The re-defined method will accept both a Boolean and a Hash and instead serialize an instance of BuildType class into the target definition's internal hash and will contain both the packaging and linkage information.
# Sets whether the target definition's pods should be built as frameworks.
#
# @param [Boolean, Hash] option
# Whether pods that are integrated in this target should be built as frameworks. If the option is a
# boolean then the value affects both packaging and linkage styles. If set to true, then dynamic frameworks
# are used and if it's set to false, then static libraries are used. If the option is a hash then
# `:framework` packaging is implied and the user configures the `:linkage` style to use.
#
# @return [void]
#
def use_frameworks!(option = true)
value = case option
when true, false
option ? BuildType.dynamic_framework : BuildType.static_library
when Hash
BuildType.new(:linkage => option.fetch(:linkage), :packaging => :framework)
else
raise ArgumentError, "Got `#{option.inspect}`, should be a boolean or hash."
end
set_hash_value('uses_frameworks', value.to_hash)
endAs you can see, the 'uses_frameworks' key stored in the target definition's hash will not change in order to maintain backwards compatibility for existing Podfile files that may have been serialized (for example, into yaml).
Furthermore, the default value of the option parameter will remain true also in order to maintain backwards compatibility with all of the existing configurations that currently specify use_frameworks! in their Podfile.
An additional method will be introduced in the TargetDefinition class that returns the (desired) build type it requires:
# The (desired) build type for the pods integrated in this target definition. Defaults to static libraries and can
# only be overridden through Pod::Podfile::DSL#use_frameworks!.
#
# @return [BuildType]
#
def build_type
value = get_hash_value('uses_frameworks', root? ? BuildType.static_library : parent.build_type)
case value
when true, false
value ? BuildType.dynamic_framework : BuildType.static_library
when Hash
BuildType.new(:linkage => value.fetch(:linkage), :packaging => value.fetch(:packaging))
when BuildType
value
else
raise ArgumentError, "Got `#{value.inspect}`, should be a boolean, hash or BuildType."
end
endFinally, the TargetDefinition#uses_frameworks? method will also need to be updated to work with the new hash that is now being stored:
-# @return [Bool] whether the target definition should build
-# a framework.
+# @return [Bool] whether the target definition pods should be built as frameworks.
#
def uses_frameworks?
if internal_hash['uses_frameworks'].nil?
root? ? false : parent.uses_frameworks?
else
- get_hash_value('uses_frameworks')
+ build_type.framework?
end
endWith the above changes the following examples are now possible:
pod 'AFNetworking', '~> 1.0'
target 'MyApp' do
use_frameworks!
end
target 'MyApp2' do
use_frameworks! :linkage => :dynamic
end
target 'MyApp3' do
use_frameworks! :linkage => :static
endThis concludes the changes required for the Core gem.
CocoaPods gem changes
As mentioned, most of the work for supporting the notion of different packaging or linking types has already been completed through the aforementioned BuildType class that was introduced in the 1.7 release. This also includes variant support and CocoaPods will automatically split a pod that is consumed across targets with different build types for both packaging and linking.
Update BuildType references
Since the BuildType class will be moved as part of this proposal, all references must be updated in the CocoaPods gem. This should be straightforward find/replace.
Remove host_requires_frameworks boolean flag
The existing host_requires_frameworks boolean flag found in Target is no longer needed and it is redundant given that the initializer requires a BuildType instance to be passed in.
As part of this proposal, the Target class and it's subclasses will need to be updated to remove host_requires_frameworks boolean flag and instead update all callers to rely on the BuildType attribute.
Additionally, all initializers will be updated to require a BuildType as it currently appears to be optional and can accept a nil value which should never be the case.
Update analyzer
The analyzer will need to be updated in order to take into consideration the (desired) BuildType specified in the TargetDefinition when constructing PodTarget instances.
A new method will be added that calculates the BuildType to use:
# Calculates and returns the BuildType to use for the given spec. If the spec specifies `static_framework` then
# it is honored as long as the host build type also requires its pods to be integrated as frameworks.
#
# @param [Specification] spec
# the spec to determine the BuildType for.
#
# @param [BuildType] host_build_type
# The desired build type by the host of this pod target. If the pod target does not specify explicitly a
# build type then the one from the host is used.
#
# @return [BuildType]
#
def determine_build_type(spec, host_build_type)
if host_build_type.framework?
root_spec = spec && spec.root
if root_spec && root_spec.static_framework
BuildType.static_framework
else
host_build_type
end
else
BuildType.static_library
end
endThe above method will be used by the analyzer when determining the BuildType to use for a PodTarget instance.
Backwards compatibility
Most of the concerns around backwards compatibility have already been covered in the 'Detailed Design' section especially regarding the serialization of the Podfile and ensuring that existing setups that use use_frameworks! continue to function after this proposal is implemented.
This proposal has no effect on podspec files and therefore it is considered a low-risk change.
Alternatives considered
An alternative was to introduce a new use_static_frameworks! DSL that would have the same effect. However, this was avoided due to the proliferation of DSL options in CocoaPods and would require additional linting in order to ensure it is mutually exclusive with the use_frameworks! option.
Another alternative as always is to do nothing in which case CocoaPods will remain very limited in its configuration options regarding linking and packaging during integration.