cocoapods-dykit 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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