cocoapods-dykit 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pod/command.rb +2 -0
  3. data/lib/pod/command/dyinstall.rb +51 -0
  4. data/lib/pod/command/dyupdate.rb +106 -0
  5. data/lib/pod/command/fmwk.rb +4 -0
  6. data/lib/pod/command/lib/dylint.rb +1 -0
  7. data/lib/pod/gem_version.rb +1 -1
  8. data/lib/pod/installer.rb +715 -0
  9. data/lib/pod/installer/analyzer.rb +934 -0
  10. data/lib/pod/installer/analyzer/analysis_result.rb +57 -0
  11. data/lib/pod/installer/analyzer/locking_dependency_analyzer.rb +95 -0
  12. data/lib/pod/installer/analyzer/pod_variant.rb +68 -0
  13. data/lib/pod/installer/analyzer/pod_variant_set.rb +157 -0
  14. data/lib/pod/installer/analyzer/podfile_dependency_cache.rb +54 -0
  15. data/lib/pod/installer/analyzer/sandbox_analyzer.rb +251 -0
  16. data/lib/pod/installer/analyzer/specs_state.rb +84 -0
  17. data/lib/pod/installer/analyzer/target_inspection_result.rb +45 -0
  18. data/lib/pod/installer/analyzer/target_inspector.rb +254 -0
  19. data/lib/pod/installer/installation_options.rb +158 -0
  20. data/lib/pod/installer/pod_source_installer.rb +214 -0
  21. data/lib/pod/installer/pod_source_preparer.rb +77 -0
  22. data/lib/pod/installer/podfile_validator.rb +139 -0
  23. data/lib/pod/installer/post_install_hooks_context.rb +107 -0
  24. data/lib/pod/installer/pre_install_hooks_context.rb +42 -0
  25. data/lib/pod/installer/source_provider_hooks_context.rb +32 -0
  26. data/lib/pod/installer/user_project_integrator.rb +253 -0
  27. data/lib/pod/installer/user_project_integrator/target_integrator.rb +462 -0
  28. data/lib/pod/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +146 -0
  29. data/lib/pod/installer/xcode.rb +8 -0
  30. data/lib/pod/installer/xcode/pods_project_generator.rb +353 -0
  31. data/lib/pod/installer/xcode/pods_project_generator/aggregate_target_installer.rb +172 -0
  32. data/lib/pod/installer/xcode/pods_project_generator/file_references_installer.rb +367 -0
  33. data/lib/pod/installer/xcode/pods_project_generator/pod_target_installer.rb +718 -0
  34. data/lib/pod/installer/xcode/pods_project_generator/pod_target_integrator.rb +111 -0
  35. data/lib/pod/installer/xcode/pods_project_generator/target_installer.rb +265 -0
  36. data/lib/pod/installer/xcode/target_validator.rb +141 -0
  37. data/lib/pod/resolver.rb +632 -0
  38. metadata +34 -2
@@ -0,0 +1,84 @@
1
+ require 'set'
2
+
3
+ module Pod
4
+ class DyInstaller
5
+ class Analyzer
6
+ # This class represents the state of a collection of Pods.
7
+ #
8
+ # @note The names of the pods stored by this class are always the **root**
9
+ # name of the specification.
10
+ #
11
+ # @note The motivation for this class is to ensure that the names of the
12
+ # subspecs are added instead of the name of the Pods.
13
+ #
14
+ class SpecsState
15
+ # @return [Set<String>] the names of the pods that were added.
16
+ #
17
+ attr_reader :added
18
+
19
+ # @return [Set<String>] the names of the pods that were changed.
20
+ #
21
+ attr_reader :changed
22
+
23
+ # @return [Set<String>] the names of the pods that were deleted.
24
+ #
25
+ attr_reader :deleted
26
+
27
+ # @return [Set<String>] the names of the pods that were unchanged.
28
+ #
29
+ attr_reader :unchanged
30
+
31
+ # Initialize a new instance
32
+ #
33
+ # @param [Hash{Symbol=>String}] pods_by_state
34
+ # The name of the pods grouped by their state
35
+ # (`:added`, `:removed`, `:changed` or `:unchanged`).
36
+ #
37
+ def initialize(pods_by_state = nil)
38
+ @added = Set.new
39
+ @deleted = Set.new
40
+ @changed = Set.new
41
+ @unchanged = Set.new
42
+
43
+ if pods_by_state
44
+ {
45
+ :added => :added,
46
+ :changed => :changed,
47
+ :removed => :deleted,
48
+ :unchanged => :unchanged,
49
+ }.each do |state, spec_state|
50
+ Array(pods_by_state[state]).each do |name|
51
+ add_name(name, spec_state)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # Displays the state of each pod.
58
+ #
59
+ # @return [void]
60
+ #
61
+ def print
62
+ added .sort.each { |pod| UI.message('A'.green + " #{pod}", '', 2) }
63
+ deleted .sort.each { |pod| UI.message('R'.red + " #{pod}", '', 2) }
64
+ changed .sort.each { |pod| UI.message('M'.yellow + " #{pod}", '', 2) }
65
+ unchanged.sort.each { |pod| UI.message('-' + " #{pod}", '', 2) }
66
+ end
67
+
68
+ # Adds the name of a Pod to the give state.
69
+ #
70
+ # @param [String] name
71
+ # the name of the Pod.
72
+ #
73
+ # @param [Symbol] state
74
+ # the state of the Pod.
75
+ #
76
+ # @return [void]
77
+ #
78
+ def add_name(name, state)
79
+ send(state) << Specification.root_name(name)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,45 @@
1
+ module Pod
2
+ class DyInstaller
3
+ class Analyzer
4
+ class TargetInspectionResult
5
+ # @return [TargetDefinition] the target definition, whose project was
6
+ # inspected
7
+ #
8
+ attr_accessor :target_definition
9
+
10
+ # @return [Pathname] the path of the user project that the
11
+ # #target_definition should integrate
12
+ #
13
+ attr_accessor :project_path
14
+
15
+ # @return [Array<String>] the uuid of the user's targets
16
+ #
17
+ attr_accessor :project_target_uuids
18
+
19
+ # @return [Hash{String=>Symbol}] A hash representing the user build
20
+ # configurations where each key corresponds to the name of a
21
+ # configuration and its value to its type (`:debug` or
22
+ # `:release`).
23
+ #
24
+ attr_accessor :build_configurations
25
+
26
+ # @return [Platform] the platform of the user targets
27
+ #
28
+ attr_accessor :platform
29
+
30
+ # @return [Array<String>] the architectures used by user's targets
31
+ #
32
+ attr_accessor :archs
33
+
34
+ # @return [Bool] whether frameworks are recommended for the integration
35
+ # due to the presence of Swift source in the user's targets
36
+ #
37
+ attr_accessor :recommends_frameworks
38
+
39
+ # @return [Xcodeproj::Project] the user's Xcode project
40
+ #
41
+ attr_accessor :project
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,254 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+
3
+ module Pod
4
+ class DyInstaller
5
+ class Analyzer
6
+ class TargetInspector
7
+ PLATFORM_INFO_URL = 'https://guides.cocoapods.org/syntax/podfile.html#platform'.freeze
8
+
9
+ # @return [TargetDefinition] the target definition to inspect
10
+ #
11
+ attr_reader :target_definition
12
+
13
+ # @return [Pathname] the root of the CocoaPods installation where the
14
+ # Podfile is located
15
+ #
16
+ attr_reader :installation_root
17
+
18
+ # Initialize a new instance
19
+ #
20
+ # @param [TargetDefinition] target_definition
21
+ # @see #target_definition
22
+ #
23
+ # @param [Pathname] installation_root
24
+ # @see #installation_root
25
+ #
26
+ def initialize(target_definition, installation_root)
27
+ @target_definition = target_definition
28
+ @installation_root = installation_root
29
+ end
30
+
31
+ # Inspect the #target_definition
32
+ #
33
+ # @raise If no `user_project` is set
34
+ #
35
+ # @return [TargetInspectionResult]
36
+ #
37
+ def compute_results(user_project)
38
+ raise ArgumentError, 'Cannot compute results without a user project set' unless user_project
39
+
40
+ targets = compute_targets(user_project)
41
+
42
+ result = TargetInspectionResult.new
43
+ result.target_definition = target_definition
44
+ result.project_path = user_project.path
45
+ result.project_target_uuids = targets.map(&:uuid)
46
+ result.build_configurations = compute_build_configurations(targets)
47
+ result.platform = compute_platform(targets)
48
+ result.archs = compute_archs(targets)
49
+ result.project = user_project
50
+ result.target_definition.swift_version = compute_swift_version_from_targets(targets)
51
+ result
52
+ end
53
+
54
+ # Returns the path of the user project that the #target_definition
55
+ # should integrate.
56
+ #
57
+ # @raise If the project is implicit and there are multiple projects.
58
+ #
59
+ # @raise If the path doesn't exits.
60
+ #
61
+ # @return [Pathname] the path of the user project.
62
+ #
63
+ def compute_project_path
64
+ if target_definition.user_project_path
65
+ path = installation_root + target_definition.user_project_path
66
+ path = "#{path}.xcodeproj" unless File.extname(path) == '.xcodeproj'
67
+ path = Pathname.new(path)
68
+ unless path.exist?
69
+ raise Informative, 'Unable to find the Xcode project ' \
70
+ "`#{path}` for the target `#{target_definition.label}`."
71
+ end
72
+ else
73
+ xcodeprojs = installation_root.children.select { |e| e.fnmatch('*.xcodeproj') }
74
+ if xcodeprojs.size == 1
75
+ path = xcodeprojs.first
76
+ else
77
+ raise Informative, 'Could not automatically select an Xcode project. ' \
78
+ "Specify one in your Podfile like so:\n\n" \
79
+ " project 'path/to/Project.xcodeproj'\n"
80
+ end
81
+ end
82
+ path
83
+ end
84
+
85
+ #-----------------------------------------------------------------------#
86
+
87
+ private
88
+
89
+ # Returns a list of the targets from the project of #target_definition
90
+ # that needs to be integrated.
91
+ #
92
+ # @note The method first looks if there is a target specified with
93
+ # the `link_with` option of the {TargetDefinition}. Otherwise
94
+ # it looks for the target that has the same name of the target
95
+ # definition. Finally if no target was found the first
96
+ # encountered target is returned (it is assumed to be the one
97
+ # to integrate in simple projects).
98
+ #
99
+ # @param [Xcodeproj::Project] user_project
100
+ # the user project
101
+ #
102
+ # @return [Array<PBXNativeTarget>]
103
+ #
104
+ def compute_targets(user_project)
105
+ native_targets = user_project.native_targets
106
+ target = native_targets.find { |t| t.name == target_definition.name.to_s }
107
+ unless target
108
+ found = native_targets.map { |t| "`#{t.name}`" }.to_sentence
109
+ raise Informative, "Unable to find a target named `#{target_definition.name}`, did find #{found}."
110
+ end
111
+ [target]
112
+ end
113
+
114
+ # @param [Array<PBXNativeTarget] user_targets the user's targets of the project of
115
+ # #target_definition which needs to be integrated
116
+ #
117
+ # @return [Hash{String=>Symbol}] A hash representing the user build
118
+ # configurations where each key corresponds to the name of a
119
+ # configuration and its value to its type (`:debug` or `:release`).
120
+ #
121
+ def compute_build_configurations(user_targets)
122
+ if user_targets
123
+ user_targets.flat_map { |t| t.build_configurations.map(&:name) }.each_with_object({}) do |name, hash|
124
+ hash[name] = name == 'Debug' ? :debug : :release
125
+ end.merge(target_definition.build_configurations || {})
126
+ else
127
+ target_definition.build_configurations || {}
128
+ end
129
+ end
130
+
131
+ # @param [Array<PBXNativeTarget] user_targets the user's targets of the project of
132
+ # #target_definition which needs to be integrated
133
+ #
134
+ # @return [Platform] The platform of the user's targets
135
+ #
136
+ # @note This resolves to the lowest deployment target across the user
137
+ # targets.
138
+ #
139
+ # @todo Is assigning the platform to the target definition the best way
140
+ # to go?
141
+ #
142
+ def compute_platform(user_targets)
143
+ return target_definition.platform if target_definition.platform
144
+ name = nil
145
+ deployment_target = nil
146
+
147
+ user_targets.each do |target|
148
+ name ||= target.platform_name
149
+ raise Informative, 'Targets with different platforms' unless name == target.platform_name
150
+ if !deployment_target || deployment_target > Version.new(target.deployment_target)
151
+ deployment_target = Version.new(target.deployment_target)
152
+ end
153
+ end
154
+
155
+ unless name
156
+ raise Informative,
157
+ "Unable to determine the platform for the `#{target_definition.name}` target."
158
+ end
159
+
160
+ UI.warn "Automatically assigning platform `#{name}` with version `#{deployment_target}` " \
161
+ "on target `#{target_definition.name}` because no platform was specified. " \
162
+ "Please specify a platform for this target in your Podfile. See `#{PLATFORM_INFO_URL}`."
163
+
164
+ target_definition.set_platform(name, deployment_target)
165
+ Platform.new(name, deployment_target)
166
+ end
167
+
168
+ # Computes the architectures relevant for the user's targets.
169
+ #
170
+ # @param [Array<PBXNativeTarget] user_targets the user's targets of the project of
171
+ # #target_definition which needs to be integrated
172
+ #
173
+ # @return [Array<String>]
174
+ #
175
+ def compute_archs(user_targets)
176
+ user_targets.flat_map do |target|
177
+ Array(target.common_resolved_build_setting('ARCHS'))
178
+ end.compact.uniq.sort
179
+ end
180
+
181
+ # Checks if any of the targets for the {TargetDefinition} computed before
182
+ # by #compute_user_project_targets is recommended to be build as a framework
183
+ # due the presence of Swift source code in any of the source build phases.
184
+ #
185
+ # @param [TargetDefinition] target_definition
186
+ # the target definition
187
+ #
188
+ # @param [Array<PBXNativeTarget>] native_targets
189
+ # the targets which are checked for presence of Swift source code
190
+ #
191
+ # @return [Boolean] Whether the user project targets to integrate into
192
+ # uses Swift
193
+ #
194
+ def compute_recommends_frameworks(target_definition, native_targets)
195
+ file_predicate = nil
196
+ file_predicate = proc do |file_ref|
197
+ if file_ref.respond_to?(:last_known_file_type)
198
+ file_ref.last_known_file_type == 'sourcecode.swift'
199
+ elsif file_ref.respond_to?(:files)
200
+ file_ref.files.any?(&file_predicate)
201
+ else
202
+ false
203
+ end
204
+ end
205
+ target_definition.platform.supports_dynamic_frameworks? || native_targets.any? do |target|
206
+ target.source_build_phase.files.any? do |build_file|
207
+ file_predicate.call(build_file.file_ref)
208
+ end
209
+ end
210
+ end
211
+
212
+ # Compute the Swift version for the target build configurations. If more
213
+ # than one Swift version is defined for a given target, then it will raise.
214
+ #
215
+ # @param [Array<PBXNativeTarget>] targets
216
+ # the targets that are checked for Swift versions.
217
+ #
218
+ # @return [String] the targets Swift version or nil
219
+ #
220
+ def compute_swift_version_from_targets(targets)
221
+ versions_to_targets = targets.inject({}) do |memo, target|
222
+ versions = target.resolved_build_setting('SWIFT_VERSION').values
223
+ versions.each do |version|
224
+ memo[version] = [] if memo[version].nil?
225
+ memo[version] << target.name unless memo[version].include? target.name
226
+ end
227
+ memo
228
+ end
229
+
230
+ case versions_to_targets.count
231
+ when 0
232
+ nil
233
+ when 1
234
+ versions_to_targets.keys.first
235
+ else
236
+ target_version_pairs = versions_to_targets.map do |version_names, target_names|
237
+ target_names.map { |target_name| [target_name, version_names] }
238
+ end
239
+
240
+ sorted_pairs = target_version_pairs.flat_map { |i| i }.sort_by do |target_name, version_name|
241
+ "#{target_name} #{version_name}"
242
+ end
243
+
244
+ formatted_output = sorted_pairs.map do |target, version_name|
245
+ "#{target}: Swift #{version_name}"
246
+ end.join("\n")
247
+
248
+ raise Informative, "There may only be up to 1 unique SWIFT_VERSION per target. Found target(s) with multiple Swift versions:\n#{formatted_output}"
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,158 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module Pod
4
+ class DyInstaller
5
+ # Represents the installation options the user can customize via a
6
+ # `Podfile`.
7
+ #
8
+ class InstallationOptions
9
+ # Parses installation options from a podfile.
10
+ #
11
+ # @param [Podfile] podfile the podfile to parse installation options
12
+ # from.
13
+ #
14
+ # @raise [Informative] if `podfile` does not specify a `CocoaPods`
15
+ # install.
16
+ #
17
+ # @return [Self]
18
+ #
19
+ def self.from_podfile(podfile)
20
+ name, options = podfile.installation_method
21
+ unless name.downcase == 'cocoapods'
22
+ raise Informative, "Currently need to specify a `cocoapods` install, you chose `#{name}`."
23
+ end
24
+ new(options)
25
+ end
26
+
27
+ # Defines a new installation option.
28
+ #
29
+ # @param [#to_s] name the name of the option.
30
+ #
31
+ # @param default the default value for the option.
32
+ #
33
+ # @param [Boolean] boolean whether the option has a boolean value.
34
+ #
35
+ # @return [void]
36
+ #
37
+ # @!macro [attach] option
38
+ #
39
+ # @note this option defaults to $2.
40
+ #
41
+ # @return the $1 $0 for installation.
42
+ #
43
+ def self.option(name, default, boolean: true)
44
+ name = name.to_s
45
+ raise ArgumentError, "The `#{name}` option is already defined" if defaults.key?(name)
46
+ defaults[name] = default
47
+ attr_accessor name
48
+ alias_method "#{name}?", name if boolean
49
+ end
50
+
51
+ # @return [Hash<Symbol,Object>] all known installation options and their
52
+ # default values.
53
+ #
54
+ def self.defaults
55
+ @defaults ||= {}
56
+ end
57
+
58
+ # @return [Array<Symbol>] the names of all known installation options.
59
+ #
60
+ def self.all_options
61
+ defaults.keys
62
+ end
63
+
64
+ # Initializes the installation options with a hash of options from a
65
+ # Podfile.
66
+ #
67
+ # @param [Hash] options the options to parse.
68
+ #
69
+ # @raise [Informative] if `options` contains any unknown keys.
70
+ #
71
+ def initialize(options = {})
72
+ options = ActiveSupport::HashWithIndifferentAccess.new(options)
73
+ unknown_keys = options.keys - self.class.all_options.map(&:to_s)
74
+ raise Informative, "Unknown installation options: #{unknown_keys.to_sentence}." unless unknown_keys.empty?
75
+ self.class.defaults.each do |key, default|
76
+ value = options.fetch(key, default)
77
+ send("#{key}=", value)
78
+ end
79
+ end
80
+
81
+ # @param [Boolean] include_defaults whether values that match the default
82
+ # for their option should be included. Defaults to `true`.
83
+ #
84
+ # @return [Hash] the options, keyed by option name.
85
+ #
86
+ def to_h(include_defaults: true)
87
+ self.class.defaults.reduce(ActiveSupport::HashWithIndifferentAccess.new) do |hash, (option, default)|
88
+ value = send(option)
89
+ hash[option] = value if include_defaults || value != default
90
+ hash
91
+ end
92
+ end
93
+
94
+ def ==(other)
95
+ other.is_a?(self.class) && to_h == other.to_h
96
+ end
97
+
98
+ alias_method :eql, :==
99
+
100
+ def hash
101
+ to_h.hash
102
+ end
103
+
104
+ option :clean, true
105
+ option :deduplicate_targets, true
106
+ option :deterministic_uuids, true
107
+ option :integrate_targets, true
108
+ option :lock_pod_sources, true
109
+ option :warn_for_multiple_pod_sources, true
110
+ option :share_schemes_for_development_pods, false
111
+
112
+ module Mixin
113
+ module ClassMethods
114
+ # Delegates the creation of {#installation_options} to the `Podfile`
115
+ # returned by the given block.
116
+ #
117
+ # @param blk a block that returns the `Podfile` to create
118
+ # installation options from.
119
+ #
120
+ # @return [Void]
121
+ #
122
+ def delegate_installation_options(&blk)
123
+ define_method(:installation_options) do
124
+ @installation_options ||= InstallationOptions.from_podfile(instance_eval(&blk))
125
+ end
126
+ end
127
+
128
+ # Delegates the installation options attributes directly to
129
+ # {#installation_options}.
130
+ #
131
+ # @return [Void]
132
+ #
133
+ def delegate_installation_option_attributes!
134
+ define_method(:respond_to_missing?) do |name, *args|
135
+ installation_options.respond_to?(name, *args) || super
136
+ end
137
+
138
+ define_method(:method_missing) do |name, *args, &blk|
139
+ if installation_options.respond_to?(name)
140
+ installation_options.send(name, *args, &blk)
141
+ else
142
+ super
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ # @return [InstallationOptions] The installation options.
149
+ #
150
+ attr_accessor :installation_options
151
+
152
+ def self.included(mod)
153
+ mod.extend(ClassMethods)
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end