cocoapods 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +329 -0
  3. data/lib/cocoapods/command/init.rb +6 -6
  4. data/lib/cocoapods/command/ipc/list.rb +40 -0
  5. data/lib/cocoapods/command/ipc/podfile.rb +31 -0
  6. data/lib/cocoapods/command/ipc/repl.rb +51 -0
  7. data/lib/cocoapods/command/ipc/spec.rb +29 -0
  8. data/lib/cocoapods/command/ipc/update_search_index.rb +24 -0
  9. data/lib/cocoapods/command/ipc.rb +18 -0
  10. data/lib/cocoapods/command/lib/create.rb +105 -0
  11. data/lib/cocoapods/command/lib/lint.rb +111 -0
  12. data/lib/cocoapods/command/lib.rb +3 -207
  13. data/lib/cocoapods/command/repo/push.rb +44 -20
  14. data/lib/cocoapods/command/setup.rb +2 -1
  15. data/lib/cocoapods/command/spec/lint.rb +4 -0
  16. data/lib/cocoapods/command.rb +2 -1
  17. data/lib/cocoapods/config.rb +4 -1
  18. data/lib/cocoapods/downloader/cache.rb +1 -0
  19. data/lib/cocoapods/downloader.rb +20 -0
  20. data/lib/cocoapods/executable.rb +1 -1
  21. data/lib/cocoapods/gem_version.rb +1 -1
  22. data/lib/cocoapods/generator/acknowledgements/plist.rb +4 -1
  23. data/lib/cocoapods/generator/copy_resources_script.rb +4 -10
  24. data/lib/cocoapods/generator/header.rb +2 -1
  25. data/lib/cocoapods/generator/prefix_header.rb +0 -12
  26. data/lib/cocoapods/generator/xcconfig/aggregate_xcconfig.rb +38 -5
  27. data/lib/cocoapods/generator/xcconfig/xcconfig_helper.rb +5 -4
  28. data/lib/cocoapods/installer/analyzer/pod_variant_set.rb +5 -1
  29. data/lib/cocoapods/installer/analyzer/target_inspector.rb +24 -1
  30. data/lib/cocoapods/installer/analyzer.rb +161 -1
  31. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +29 -9
  32. data/lib/cocoapods/installer/xcode/pods_project_generator/aggregate_target_installer.rb +204 -0
  33. data/lib/cocoapods/installer/xcode/pods_project_generator/file_references_installer.rb +314 -0
  34. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_installer.rb +401 -0
  35. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installer.rb +214 -0
  36. data/lib/cocoapods/installer/xcode/pods_project_generator.rb +265 -0
  37. data/lib/cocoapods/installer/xcode.rb +7 -0
  38. data/lib/cocoapods/installer.rb +50 -214
  39. data/lib/cocoapods/resolver.rb +15 -9
  40. data/lib/cocoapods/sandbox/headers_store.rb +4 -10
  41. data/lib/cocoapods/sandbox/path_list.rb +20 -9
  42. data/lib/cocoapods/sources_manager.rb +7 -10
  43. data/lib/cocoapods/target/aggregate_target.rb +20 -0
  44. data/lib/cocoapods/target/pod_target.rb +37 -7
  45. data/lib/cocoapods/user_interface/error_report.rb +7 -0
  46. data/lib/cocoapods/user_interface/inspector_reporter.rb +109 -0
  47. data/lib/cocoapods/user_interface.rb +7 -5
  48. data/lib/cocoapods/validator.rb +59 -11
  49. metadata +112 -83
  50. data/lib/cocoapods/command/inter_process_communication.rb +0 -177
  51. data/lib/cocoapods/installer/file_references_installer.rb +0 -310
  52. data/lib/cocoapods/installer/migrator.rb +0 -86
  53. data/lib/cocoapods/installer/target_installer/aggregate_target_installer.rb +0 -191
  54. data/lib/cocoapods/installer/target_installer/pod_target_installer.rb +0 -368
  55. data/lib/cocoapods/installer/target_installer.rb +0 -210
@@ -73,9 +73,10 @@ module Pod
73
73
  def specs_by_target
74
74
  @specs_by_target ||= {}.tap do |specs_by_target|
75
75
  podfile.target_definition_list.each do |target|
76
+ dependencies = {}
76
77
  specs = target.dependencies.map(&:name).flat_map do |name|
77
78
  node = @activated.vertex_named(name)
78
- valid_dependencies_for_target_from_node(target, node) << node
79
+ valid_dependencies_for_target_from_node(target, dependencies, node) << node
79
80
  end
80
81
 
81
82
  specs_by_target[target] = specs.
@@ -389,7 +390,6 @@ module Pod
389
390
  error.conflicts.each do |name, conflict|
390
391
  local_pod_parent = conflict.requirement_trees.flatten.reverse.find(&:local?)
391
392
  lockfile_reqs = conflict.requirements[name_for_locking_dependency_source]
392
-
393
393
  if lockfile_reqs && lockfile_reqs.last && lockfile_reqs.last.prerelease? && !conflict.existing
394
394
  message = 'Due to the previous naïve CocoaPods resolver, ' \
395
395
  "you were using a pre-release version of `#{name}`, " \
@@ -398,7 +398,7 @@ module Pod
398
398
  'version requirement to your Podfile ' \
399
399
  "(e.g. `pod '#{name}', '#{lockfile_reqs.map(&:requirement).join("', '")}'`) " \
400
400
  "or revert to a stable version by running `pod update #{name}`."
401
- elsif local_pod_parent && !specifications_for_dependency(conflict.requirement).empty?
401
+ elsif local_pod_parent && !specifications_for_dependency(conflict.requirement).empty? && !conflict.possibility
402
402
  # Conflict was caused by a requirement from a local dependency.
403
403
  # Tell user to use `pod update`.
404
404
  message << "\n\nIt seems like you've changed the constraints of dependency `#{name}` " \
@@ -472,13 +472,19 @@ module Pod
472
472
  # An array of target-appropriate nodes whose `payload`s are
473
473
  # dependencies for `target`.
474
474
  #
475
- def valid_dependencies_for_target_from_node(target, node)
476
- validate_platform(node.payload, target)
477
- dependency_nodes = node.outgoing_edges.select do |edge|
478
- edge_is_valid_for_target?(edge, target)
479
- end.map(&:destination)
475
+ def valid_dependencies_for_target_from_node(target, dependencies, node)
476
+ dependencies[node.name] ||= begin
477
+ validate_platform(node.payload, target)
478
+ dependency_nodes = node.outgoing_edges.select do |edge|
479
+ edge_is_valid_for_target?(edge, target)
480
+ end.map(&:destination)
481
+
482
+ dependency_nodes + dependency_nodes.flat_map do |item|
483
+ node_result = valid_dependencies_for_target_from_node(target, dependencies, item)
480
484
 
481
- dependency_nodes + dependency_nodes.flat_map { |n| valid_dependencies_for_target_from_node(target, n) }
485
+ node_result
486
+ end
487
+ end
482
488
  end
483
489
 
484
490
  # Whether the given `edge` should be followed to find dependencies for the
@@ -72,11 +72,11 @@ module Pod
72
72
  #
73
73
  def add_files(namespace, relative_header_paths)
74
74
  relative_header_paths.map do |relative_header_path|
75
- add_file(namespace, relative_header_path, relative_header_path.basename)
75
+ add_file(namespace, relative_header_path)
76
76
  end
77
77
  end
78
78
 
79
- # Adds a header to the directory under different name.
79
+ # Adds a header to the directory.
80
80
  #
81
81
  # @param [Pathname] namespace
82
82
  # the path where the header file should be stored relative to the
@@ -86,23 +86,17 @@ module Pod
86
86
  # the path of the header file relative to the Pods project
87
87
  # (`PODS_ROOT` variable of the xcconfigs).
88
88
  #
89
- # @param [String] final_name
90
- # the name under which the file should be available in the
91
- # headers directory.
92
- #
93
89
  # @note This method does _not_ add the file to the search paths.
94
90
  #
95
91
  # @return [Pathname]
96
92
  #
97
- def add_file(namespace, relative_header_path, final_name)
93
+ def add_file(namespace, relative_header_path)
98
94
  namespaced_path = root + namespace
99
95
  namespaced_path.mkpath unless File.exist?(namespaced_path)
100
96
 
101
97
  absolute_source = (sandbox.root + relative_header_path)
102
98
  source = absolute_source.relative_path_from(namespaced_path)
103
- Dir.chdir(namespaced_path) do
104
- FileUtils.ln_sf(source, final_name)
105
- end
99
+ FileUtils.ln_sf(source, namespaced_path)
106
100
  namespaced_path + relative_header_path.basename
107
101
  end
108
102
 
@@ -1,3 +1,5 @@
1
+ require 'active_support/multibyte/unicode'
2
+
1
3
  module Pod
2
4
  class Sandbox
3
5
  # The PathList class is designed to perform multiple glob matches against
@@ -20,7 +22,8 @@ module Pod
20
22
  # @param [Pathname] root The root of the PathList.
21
23
  #
22
24
  def initialize(root)
23
- @root = root
25
+ root_dir = ActiveSupport::Multibyte::Unicode.normalize(root.to_s)
26
+ @root = Pathname.new(root_dir)
24
27
  @glob_cache = {}
25
28
  end
26
29
 
@@ -47,15 +50,23 @@ module Pod
47
50
  unless root.exist?
48
51
  raise Informative, "Attempt to read non existent folder `#{root}`."
49
52
  end
50
- root_length = root.to_s.length + 1
51
53
  escaped_root = escape_path_for_glob(root)
52
- paths = Dir.glob(escaped_root + '**/*', File::FNM_DOTMATCH).sort_by(&:upcase)
53
- absolute_dirs = paths.select { |path| File.directory?(path) }
54
- relative_dirs = absolute_dirs.map { |p| p[root_length..-1] }
55
- absolute_paths = paths.reject { |p| p == "#{root}/." || p == "#{root}/.." }
56
- relative_paths = absolute_paths.map { |p| p[root_length..-1] }
57
- @files = relative_paths - relative_dirs
58
- @dirs = relative_dirs.map { |d| d.gsub(/\/\.\.?$/, '') }.reject { |d| d == '.' || d == '..' } .uniq
54
+
55
+ absolute_paths = Pathname.glob(escaped_root + '**/*', File::FNM_DOTMATCH).lazy
56
+ dirs_and_files = absolute_paths.reject { |path| path.basename.to_s =~ /^\.\.?$/ }
57
+ dirs, files = dirs_and_files.partition { |path| File.directory?(path) }
58
+
59
+ root_length = root.cleanpath.to_s.length + File::SEPARATOR.length
60
+ sorted_relative_paths_from_full_paths = lambda do |paths|
61
+ relative_paths = paths.lazy.map do |path|
62
+ path_string = path.to_s
63
+ path_string.slice(root_length, path_string.length - root_length)
64
+ end
65
+ relative_paths.sort_by(&:upcase)
66
+ end
67
+
68
+ @dirs = sorted_relative_paths_from_full_paths.call(dirs)
69
+ @files = sorted_relative_paths_from_full_paths.call(files)
59
70
  @glob_cache = {}
60
71
  end
61
72
 
@@ -1,4 +1,5 @@
1
1
  require 'cocoapods-core/source'
2
+ require 'set'
2
3
 
3
4
  module Pod
4
5
  class Source
@@ -102,24 +103,22 @@ module Pod
102
103
  end
103
104
 
104
105
  def update_git_repo(show_output = false)
105
- ensure_in_repo!
106
106
  Config.instance.with_changes(:verbose => show_output) do
107
- git!(%w(pull --ff-only))
107
+ git!(['-C', repo, 'pull', '--ff-only'])
108
108
  end
109
109
  rescue
110
- UI.warn 'CocoaPods was not able to update the ' \
111
- "`#{name}` repo. If this is an unexpected issue " \
112
- 'and persists you can inspect it running ' \
113
- '`pod repo update --verbose`'
110
+ raise Informative, 'CocoaPods was not able to update the ' \
111
+ "`#{name}` repo. If this is an unexpected issue " \
112
+ 'and persists you can inspect it running ' \
113
+ '`pod repo update --verbose`'
114
114
  end
115
115
  end
116
116
 
117
117
  class MasterSource
118
118
  def update_git_repo(show_output = false)
119
- ensure_in_repo!
120
119
  if repo.join('.git', 'shallow').file?
121
120
  UI.info "Performing a deep fetch of the `#{name}` specs repo to improve future performance" do
122
- git!(%w(fetch --unshallow))
121
+ git!(['-C', repo, 'fetch', '--unshallow'])
123
122
  end
124
123
  end
125
124
  super
@@ -141,8 +140,6 @@ module Pod
141
140
  "CocoaPods #{latest_cocoapods_version} is available.".green,
142
141
  "To update use: `#{install_message}`".green,
143
142
  ("[!] This is a test version we'd love you to try.".yellow if rc),
144
- ("Until we reach version 1.0 the features of CocoaPods can and will change.\n" \
145
- 'We strongly recommend that you use the latest version at all times.'.yellow unless rc),
146
143
  '',
147
144
  'For more information, see https://blog.cocoapods.org ' \
148
145
  'and the CHANGELOG for this version at ' \
@@ -7,6 +7,10 @@ module Pod
7
7
  # generated this target.
8
8
  attr_reader :target_definition
9
9
 
10
+ # Product types where the product's frameworks must be embedded in a host target
11
+ #
12
+ EMBED_FRAMEWORKS_IN_HOST_TARGET_TYPES = [:app_extension, :framework, :messages_extension, :watch_extension, :xpc_service].freeze
13
+
10
14
  # Initialize a new instance
11
15
  #
12
16
  # @param [TargetDefinition] target_definition @see target_definition
@@ -23,6 +27,22 @@ module Pod
23
27
  @xcconfigs = {}
24
28
  end
25
29
 
30
+ # @return [Boolean] True if the user_target's pods are
31
+ # for an extension and must be embedded in a host,
32
+ # target, otherwise false.
33
+ #
34
+ def requires_host_target?
35
+ # If we don't have a user_project, then we can't
36
+ # glean any info about how this target is going to
37
+ # be integrated, so return false since we can't know
38
+ # for sure that this target refers to an extension
39
+ # target that would require a host target
40
+ return false if user_project.nil?
41
+ symbol_types = user_targets.map(&:symbol_type).uniq
42
+ raise ArgumentError, "Expected single kind of user_target for #{name}. Found #{symbol_types.join(', ')}." unless symbol_types.count == 1
43
+ EMBED_FRAMEWORKS_IN_HOST_TARGET_TYPES.include?(symbol_types[0])
44
+ end
45
+
26
46
  # @return [String] the label for the target.
27
47
  #
28
48
  def label
@@ -47,6 +47,7 @@ module Pod
47
47
  @file_accessors = []
48
48
  @resource_bundle_targets = []
49
49
  @dependent_targets = []
50
+ @build_config_cache = {}
50
51
  end
51
52
 
52
53
  # @param [Hash{Array => PodTarget}] cache
@@ -65,6 +66,7 @@ module Pod
65
66
  target.native_target = native_target
66
67
  target.archs = archs
67
68
  target.dependent_targets = dependent_targets.flat_map { |pt| pt.scoped(cache) }.select { |pt| pt.target_definitions == [target_definition] }
69
+ target.host_requires_frameworks = host_requires_frameworks
68
70
  cache[cache_key] = target
69
71
  end
70
72
  end
@@ -80,6 +82,12 @@ module Pod
80
82
  end
81
83
  end
82
84
 
85
+ # @return [String] the Swift version for the target.
86
+ #
87
+ def swift_version
88
+ target_definitions.map(&:swift_version).compact.uniq.first
89
+ end
90
+
83
91
  # @note The deployment target for the pod target is the maximum of all
84
92
  # the deployment targets for the current platform of the target
85
93
  # (or the minimum required to support the current installation
@@ -134,9 +142,12 @@ module Pod
134
142
  # A target should not be build if it has no source files.
135
143
  #
136
144
  def should_build?
137
- source_files = file_accessors.flat_map(&:source_files)
138
- source_files -= file_accessors.flat_map(&:headers)
139
- !source_files.empty?
145
+ return @should_build if defined? @should_build
146
+ @should_build = begin
147
+ source_files = file_accessors.flat_map(&:source_files)
148
+ source_files -= file_accessors.flat_map(&:headers)
149
+ !source_files.empty?
150
+ end
140
151
  end
141
152
 
142
153
  # @return [Array<Specification::Consumer>] the specification consumers for
@@ -149,8 +160,11 @@ module Pod
149
160
  # @return [Boolean] Whether the target uses Swift code
150
161
  #
151
162
  def uses_swift?
152
- file_accessors.any? do |file_accessor|
153
- file_accessor.source_files.any? { |sf| sf.extname == '.swift' }
163
+ return @uses_swift if defined? @uses_swift
164
+ @uses_swift = begin
165
+ file_accessors.any? do |file_accessor|
166
+ file_accessor.source_files.any? { |sf| sf.extname == '.swift' }
167
+ end
154
168
  end
155
169
  end
156
170
 
@@ -188,8 +202,14 @@ module Pod
188
202
  # dependency upon.
189
203
  #
190
204
  def recursive_dependent_targets
191
- targets = dependent_targets + dependent_targets.flat_map(&:recursive_dependent_targets)
192
- targets.uniq!
205
+ targets = dependent_targets.clone
206
+
207
+ targets.each do |target|
208
+ target.dependent_targets.each do |t|
209
+ targets.push(t) unless t == self || targets.include?(t)
210
+ end
211
+ end
212
+
193
213
  targets
194
214
  end
195
215
 
@@ -203,13 +223,20 @@ module Pod
203
223
  # The name of the build configuration.
204
224
  #
205
225
  def include_in_build_config?(target_definition, configuration_name)
226
+ key = [target_definition.label, configuration_name]
227
+ if @build_config_cache.key?(key)
228
+ return @build_config_cache[key]
229
+ end
230
+
206
231
  whitelists = target_definition_dependencies(target_definition).map do |dependency|
207
232
  target_definition.pod_whitelisted_for_configuration?(dependency.name, configuration_name)
208
233
  end.uniq
209
234
 
210
235
  if whitelists.empty?
236
+ @build_config_cache[key] = true
211
237
  return true
212
238
  elsif whitelists.count == 1
239
+ @build_config_cache[key] = whitelists.first
213
240
  whitelists.first
214
241
  else
215
242
  raise Informative, "The subspecs of `#{pod_name}` are linked to " \
@@ -224,13 +251,16 @@ module Pod
224
251
  # @return [Bool]
225
252
  #
226
253
  def inhibit_warnings?
254
+ return @inhibit_warnings if defined? @inhibit_warnings
227
255
  whitelists = target_definitions.map do |target_definition|
228
256
  target_definition.inhibits_warnings_for_pod?(root_spec.name)
229
257
  end.uniq
230
258
 
231
259
  if whitelists.empty?
260
+ @inhibit_warnings = false
232
261
  return false
233
262
  elsif whitelists.count == 1
263
+ @inhibit_warnings = whitelists.first
234
264
  whitelists.first
235
265
  else
236
266
  UI.warn "The pod `#{pod_name}` is linked to different targets " \
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'rbconfig'
4
4
  require 'cgi'
5
+ require 'gh_inspector'
5
6
 
6
7
  module Pod
7
8
  module UserInterface
@@ -112,6 +113,12 @@ EOS
112
113
  EOS
113
114
  end
114
115
 
116
+ def search_for_exceptions(exception)
117
+ inspector = GhInspector::Inspector.new 'cocoapods', 'cocoapods'
118
+ message_delegate = UserInterface::InspectorReporter.new
119
+ inspector.search_exception exception, message_delegate
120
+ end
121
+
115
122
  private
116
123
 
117
124
  def `(other)
@@ -0,0 +1,109 @@
1
+ require 'uri'
2
+
3
+ module Pod
4
+ module UserInterface
5
+ # Redirects GH-issues delegate callbacks to CocoaPods UI methods.
6
+ #
7
+ class InspectorReporter
8
+ # Called just as the investigation has begun.
9
+ # Lets the user know that it's looking for an issue.
10
+ #
11
+ # @param [query] String unused
12
+ #
13
+ # @param [GhInspector::Inspector] inspector
14
+ # The current inspector
15
+ #
16
+ # @return [void]
17
+ #
18
+ def inspector_started_query(_, inspector)
19
+ UI.puts "Looking for related issues on #{inspector.repo_owner}/#{inspector.repo_name}..."
20
+ end
21
+
22
+ # Called once the inspector has recieved a report with more than one issue,
23
+ # showing the top 3 issues, and offering a link to see more.
24
+ #
25
+ # @param [GhInspector::InspectionReport] report
26
+ # Report a list of the issues
27
+ #
28
+ # @param [GhInspector::Inspector] inspector
29
+ # The current inspector
30
+ #
31
+ # @return [void]
32
+ #
33
+ def inspector_successfully_recieved_report(report, _)
34
+ report.issues[0..2].each { |issue| print_issue_full(issue) }
35
+
36
+ if report.issues.count > 3
37
+ UI.puts "and #{report.total_results - 3} more at:"
38
+ UI.puts report.url
39
+ end
40
+ end
41
+
42
+ # Called once the report has been recieved, but when there are no issues found.
43
+ #
44
+ # @param [GhInspector::InspectionReport] report
45
+ # An empty report
46
+ #
47
+ # @param [GhInspector::Inspector] inspector
48
+ # The current inspector
49
+ #
50
+ # @return [void]
51
+ #
52
+ def inspector_recieved_empty_report(_, inspector)
53
+ UI.puts 'Found no similar issues. To create a new issue, please visit:'
54
+ UI.puts "https://github.com/#{inspector.repo_owner}/#{inspector.repo_name}/issues/new"
55
+ end
56
+
57
+ # Called when there have been networking issues in creating the report.
58
+ #
59
+ # @param [Error] error
60
+ # The error returned during networking
61
+ #
62
+ # @param [String] query
63
+ # The original search query
64
+ #
65
+ # @param [GhInspector::Inspector] inspector
66
+ # The current inspector
67
+ #
68
+ # @return [void]
69
+ #
70
+ def inspector_could_not_create_report(error, query, inspector)
71
+ safe_query = URI.escape query
72
+ UI.puts 'Could not access the GitHub API, you may have better luck via the website.'
73
+ UI.puts "https://github.com/#{inspector.repo_owner}/#{inspector.repo_name}/search?q=#{safe_query}&type=Issues&utf8=✓"
74
+ UI.puts "Error: #{error.name}"
75
+ end
76
+
77
+ private
78
+
79
+ def print_issue_full(issue)
80
+ safe_url = URI.escape issue.html_url
81
+ UI.puts " - #{issue.title}"
82
+ UI.puts " #{safe_url} [#{issue.state}] [#{issue.comments} comment#{issue.comments == 1 ? '' : 's'}]"
83
+ UI.puts " #{pretty_date(issue.updated_at)}"
84
+ UI.puts ''
85
+ end
86
+
87
+ # Taken from http://stackoverflow.com/questions/195740/how-do-you-do-relative-time-in-rails
88
+ def pretty_date(date_string)
89
+ date = Time.parse(date_string)
90
+ a = (Time.now - date).to_i
91
+
92
+ case a
93
+ when 0 then 'just now'
94
+ when 1 then 'a second ago'
95
+ when 2..59 then a.to_s + ' seconds ago'
96
+ when 60..119 then 'a minute ago' # 120 = 2 minutes
97
+ when 120..3540 then (a / 60).to_i.to_s + ' minutes ago'
98
+ when 3541..7100 then 'an hour ago' # 3600 = 1 hour
99
+ when 7101..82_800 then ((a + 99) / 3600).to_i.to_s + ' hours ago'
100
+ when 82_801..172_000 then 'a day ago' # 86400 = 1 day
101
+ when 172_001..518_400 then ((a + 800) / (60 * 60 * 24)).to_i.to_s + ' days ago'
102
+ when 518_400..1_036_800 then 'a week ago'
103
+ when 1_036_801..4_147_204 then ((a + 180_000) / (60 * 60 * 24 * 7)).to_i.to_s + ' weeks ago'
104
+ else date.strftime('%d %b %Y')
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,4 +1,5 @@
1
1
  require 'cocoapods/user_interface/error_report'
2
+ require 'cocoapods/user_interface/inspector_reporter'
2
3
 
3
4
  module Pod
4
5
  # Provides support for UI output. It provides support for nested sections of
@@ -7,7 +8,7 @@ module Pod
7
8
  module UserInterface
8
9
  require 'colored'
9
10
 
10
- @title_colors = %w( yellow green )
11
+ @title_colors = %w( yellow green )
11
12
  @title_level = 0
12
13
  @indentation_level = 2
13
14
  @treat_titles_as_messages = false
@@ -117,9 +118,6 @@ module Pod
117
118
  self.title_level -= 1
118
119
  end
119
120
 
120
- # def title(title, verbose_prefix = '', relative_indentation = 2)
121
- # end
122
-
123
121
  # Prints a verbose message taking an optional verbose prefix and
124
122
  # a relative indentation valid for the UI action in the passed
125
123
  # block.
@@ -187,7 +185,11 @@ module Pod
187
185
  if pathname
188
186
  from_path = config.podfile_path.dirname if config.podfile_path
189
187
  from_path ||= Pathname.pwd
190
- path = Pathname(pathname).relative_path_from(from_path)
188
+ path = begin
189
+ Pathname(pathname).relative_path_from(from_path)
190
+ rescue
191
+ pathname
192
+ end
191
193
  "`#{path}`"
192
194
  else
193
195
  ''
@@ -1,8 +1,6 @@
1
1
  require 'active_support/core_ext/array'
2
2
  require 'active_support/core_ext/string/inflections'
3
3
 
4
- autoload :Fourflusher, 'fourflusher'
5
-
6
4
  module Pod
7
5
  # Validates a Specification.
8
6
  #
@@ -144,7 +142,15 @@ module Pod
144
142
  reasons << 'all results apply only to public specs, but you can use ' \
145
143
  '`--private` to ignore them if linting the specification for a private pod'
146
144
  end
147
- reasons.to_sentence
145
+ if dot_swift_version.nil?
146
+ reasons.to_sentence + ".\n[!] The validator for Swift projects uses " \
147
+ 'Swift 3.0 by default, if you are using a different version of ' \
148
+ 'swift you can use a `.swift-version` file to set the version for ' \
149
+ "your Pod. For example to use Swift 2.3, run: \n" \
150
+ ' `echo "2.3" > .swift-version`'
151
+ else
152
+ reasons.to_sentence
153
+ end
148
154
  end
149
155
 
150
156
  #-------------------------------------------------------------------------#
@@ -239,6 +245,30 @@ module Pod
239
245
  Pathname(Dir.tmpdir) + 'CocoaPods/Lint'
240
246
  end
241
247
 
248
+ # @return [String] the SWIFT_VERSION to use for validation.
249
+ #
250
+ def swift_version
251
+ @swift_version ||= dot_swift_version || '3.0'
252
+ end
253
+
254
+ # Set the SWIFT_VERSION that should be used to validate the pod.
255
+ #
256
+ attr_writer :swift_version
257
+
258
+ # @return [String] the SWIFT_VERSION in the .swift-version file or nil.
259
+ #
260
+ def dot_swift_version
261
+ swift_version_path = file.dirname + '.swift-version'
262
+ swift_version_path.read if swift_version_path.exist?
263
+ end
264
+
265
+ # @return [String] A string representing the Swift version used during linting
266
+ # or nil, if Swift was not used.
267
+ #
268
+ def used_swift_version
269
+ swift_version if @installer.pod_targets.any?(&:uses_swift?)
270
+ end
271
+
242
272
  #-------------------------------------------------------------------------#
243
273
 
244
274
  private
@@ -388,10 +418,26 @@ module Pod
388
418
 
389
419
  source_file = write_app_import_source_file(pod_target)
390
420
  source_file_ref = app_project.new_group('App', 'App').new_file(source_file)
391
- app_project.targets.first.add_file_references([source_file_ref])
421
+ app_target = app_project.targets.first
422
+ app_target.add_file_references([source_file_ref])
423
+ add_swift_version(app_target)
424
+ add_xctest(app_target) if @installer.pod_targets.any? { |pt| pt.spec_consumers.any? { |c| c.frameworks.include?('XCTest') } }
392
425
  app_project.save
393
426
  end
394
427
 
428
+ def add_swift_version(app_target)
429
+ app_target.build_configurations.each do |configuration|
430
+ configuration.build_settings['SWIFT_VERSION'] = swift_version
431
+ end
432
+ end
433
+
434
+ def add_xctest(app_target)
435
+ app_target.build_configurations.each do |configuration|
436
+ search_paths = configuration.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= '$(inherited)'
437
+ search_paths << ' "$(PLATFORM_DIR)/Developer/Library/Frameworks"'
438
+ end
439
+ end
440
+
395
441
  def write_app_import_source_file(pod_target)
396
442
  language = pod_target.uses_swift? ? :swift : :objc
397
443
 
@@ -427,7 +473,7 @@ module Pod
427
473
  # for all available platforms with xcodebuild.
428
474
  #
429
475
  def install_pod
430
- %i(verify_no_duplicate_framework_names
476
+ %i(verify_no_duplicate_framework_and_library_names
431
477
  verify_no_static_framework_transitive_dependencies
432
478
  verify_framework_usage generate_pods_project integrate_user_project
433
479
  perform_post_install_actions).each { |m| @installer.send(m) }
@@ -438,6 +484,7 @@ module Pod
438
484
  next unless native_target = pod_target.native_target
439
485
  native_target.build_configuration_list.build_configurations.each do |build_configuration|
440
486
  (build_configuration.build_settings['OTHER_CFLAGS'] ||= '$(inherited)') << ' -Wincomplete-umbrella'
487
+ build_configuration.build_settings['SWIFT_VERSION'] = swift_version if pod_target.uses_swift?
441
488
  end
442
489
  end
443
490
  if target.pod_targets.any?(&:uses_swift?) && consumer.platform_name == :ios &&
@@ -575,7 +622,7 @@ module Pod
575
622
  end
576
623
  non_source_files = header_files - file_accessor.source_files
577
624
  unless non_source_files.empty?
578
- error(attr_name, 'The pattern includes header files that are not listed' \
625
+ error(attr_name, 'The pattern includes header files that are not listed ' \
579
626
  "in source_files (#{non_source_files.join(', ')}).")
580
627
  end
581
628
  end
@@ -710,20 +757,21 @@ module Pod
710
757
  # returns its output (both STDOUT and STDERR).
711
758
  #
712
759
  def xcodebuild
713
- command = %w(clean build -workspace App.xcworkspace -scheme App -configuration Release)
760
+ require 'fourflusher'
761
+ command = ['clean', 'build', '-workspace', File.join(validation_dir, 'App.xcworkspace'), '-scheme', 'App', '-configuration', 'Release']
714
762
  case consumer.platform_name
715
763
  when :ios
716
764
  command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator)
717
- command += Fourflusher::SimControl.new.destination('iPhone 4s', deployment_target)
765
+ command += Fourflusher::SimControl.new.destination(:oldest, 'iOS', deployment_target)
718
766
  when :watchos
719
767
  command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator)
720
- command += Fourflusher::SimControl.new.destination('Apple Watch - 38mm', deployment_target)
768
+ command += Fourflusher::SimControl.new.destination(:oldest, 'watchOS', deployment_target)
721
769
  when :tvos
722
770
  command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator)
723
- command += Fourflusher::SimControl.new.destination('Apple TV 1080p', deployment_target)
771
+ command += Fourflusher::SimControl.new.destination(:oldest, 'tvOS', deployment_target)
724
772
  end
725
773
 
726
- output, status = Dir.chdir(validation_dir) { _xcodebuild(command) }
774
+ output, status = _xcodebuild(command)
727
775
 
728
776
  unless status.success?
729
777
  message = 'Returned an unsuccessful exit code.'