cocoapods-core 1.8.4 → 1.9.0.beta.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 306a8c96eeccd74ea026697148fe4b1cb29336e906a448f24ab7c1d254f0d9f8
4
- data.tar.gz: f5d4447b0d9016de3c8d122ad6f2472d41c3df2fad6a96a0d58d82d3f06b2c4e
3
+ metadata.gz: 767b9681ebb1b555c9750c0fb6ceafa498dd5193a5a509e7560e3c395ef7093e
4
+ data.tar.gz: ad41c838f0582b8ae978b180f35f2cfc32c321dcb47f8f8591abd3843f61598c
5
5
  SHA512:
6
- metadata.gz: d9c8236b2c02a23d7d70495bff802a2fe46c061a8ecc4a51fd012c7f717cdde113a227b60e1b4b857cd6f74b954b48d3fc11ec4a1cdfae62b8d80be0249711f0
7
- data.tar.gz: d8472bbcbcd8b00a673e7e42f61827d35b887f796b66b16e7ee922dfed4ab7b254e3176e09b27234bd8bf3ea4693c9d8823d752e6eff581941758abdf75b035f
6
+ metadata.gz: a6e61b8cd239ea526a301f6a340b043ecc4fa02be1e6985e728bbff0376aea1389f45e689b6bcef170300e80fdde978c48d20392fafa30d9902443e157b42ec9
7
+ data.tar.gz: 4eb1ba7d93173e723207bfdfb9488d5fda243cdf2c7cba14b76e96a7a46929747f80ed6d7fd5efd6d707faee99433b9846d5d567b290fa52899755e0d4196c93
@@ -32,6 +32,7 @@ module Pod
32
32
  autoload :Specification, 'cocoapods-core/specification'
33
33
  autoload :StandardError, 'cocoapods-core/standard_error'
34
34
  autoload :YAMLHelper, 'cocoapods-core/yaml_helper'
35
+ autoload :BuildType, 'cocoapods-core/build_type'
35
36
 
36
37
  # TODO: Fix
37
38
  #
@@ -0,0 +1,121 @@
1
+ module Pod
2
+ class BuildType
3
+ # @return [Array<Symbol>] known packaging options.
4
+ #
5
+ KNOWN_PACKAGING_OPTIONS = %i(library framework).freeze
6
+
7
+ # @return [Array<Symbol>] known linking options.
8
+ #
9
+ KNOWN_LINKAGE_OPTIONS = %i(static dynamic).freeze
10
+
11
+ # @return [Symbol] the packaging for this build type, one of #KNOWN_PACKAGING_OPTIONS
12
+ #
13
+ attr_reader :packaging
14
+
15
+ # @return [Symbol] the linkage for this build type, one of #KNOWN_LINKAGE_OPTIONS
16
+ #
17
+ attr_reader :linkage
18
+
19
+ attr_reader :hash
20
+
21
+ def initialize(linkage: :static, packaging: :library)
22
+ unless KNOWN_LINKAGE_OPTIONS.include?(linkage)
23
+ raise ArgumentError, "Invalid linkage option #{linkage.inspect}, valid options are #{KNOWN_LINKAGE_OPTIONS.inspect}"
24
+ end
25
+ unless KNOWN_PACKAGING_OPTIONS.include?(packaging)
26
+ raise ArgumentError, "Invalid packaging option #{packaging.inspect}, valid options are #{KNOWN_PACKAGING_OPTIONS.inspect}"
27
+ end
28
+ @packaging = packaging
29
+ @linkage = linkage
30
+ @hash = packaging.hash ^ linkage.hash
31
+ end
32
+
33
+ # @return [BuildType] the build type for a dynamic library
34
+ def self.dynamic_library
35
+ new(:linkage => :dynamic, :packaging => :library)
36
+ end
37
+
38
+ # @return [BuildType] the build type for a static library
39
+ #
40
+ def self.static_library
41
+ new(:linkage => :static, :packaging => :library)
42
+ end
43
+
44
+ # @return [BuildType] the build type for a dynamic framework
45
+ #
46
+ def self.dynamic_framework
47
+ new(:linkage => :dynamic, :packaging => :framework)
48
+ end
49
+
50
+ # @return [BuildType] the build type for a static framework
51
+ #
52
+ def self.static_framework
53
+ new(:linkage => :static, :packaging => :framework)
54
+ end
55
+
56
+ # @return [Boolean] whether the target is built dynamically
57
+ #
58
+ def dynamic?
59
+ linkage == :dynamic
60
+ end
61
+
62
+ # @return [Boolean] whether the target is built statically
63
+ #
64
+ def static?
65
+ linkage == :static
66
+ end
67
+
68
+ # @return [Boolean] whether the target is built as a framework
69
+ #
70
+ def framework?
71
+ packaging == :framework
72
+ end
73
+
74
+ # @return [Boolean] whether the target is built as a library
75
+ #
76
+ def library?
77
+ packaging == :library
78
+ end
79
+
80
+ # @return [Boolean] whether the target is built as a dynamic framework
81
+ #
82
+ def dynamic_framework?
83
+ dynamic? && framework?
84
+ end
85
+
86
+ # @return [Boolean] whether the target is built as a dynamic library
87
+ #
88
+ def dynamic_library?
89
+ dynamic? && library?
90
+ end
91
+
92
+ # @return [Boolean] whether the target is built as a static framework
93
+ #
94
+ def static_framework?
95
+ static? && framework?
96
+ end
97
+
98
+ # @return [Boolean] whether the target is built as a static library
99
+ #
100
+ def static_library?
101
+ static? && library?
102
+ end
103
+
104
+ def to_s
105
+ "#{linkage} #{packaging}"
106
+ end
107
+
108
+ def to_hash
109
+ { :linkage => linkage, :packaging => packaging }
110
+ end
111
+
112
+ def inspect
113
+ "#<#{self.class} linkage=#{linkage} packaging=#{packaging}>"
114
+ end
115
+
116
+ def ==(other)
117
+ linkage == other.linkage &&
118
+ packaging == other.packaging
119
+ end
120
+ end
121
+ end
@@ -1,13 +1,20 @@
1
1
  require 'cocoapods-core/source'
2
2
  require 'rest'
3
3
  require 'concurrent'
4
+ require 'typhoeus'
5
+ require 'netrc'
4
6
 
5
7
  module Pod
6
8
  # Subclass of Pod::Source to provide support for CDN-based Specs repositories
7
9
  #
8
10
  class CDNSource < Source
9
- MAX_CDN_NETWORK_THREADS = (ENV['MAX_CDN_NETWORK_THREADS'] || 50).to_i
11
+ include Concurrent
12
+
10
13
  MAX_NUMBER_OF_RETRIES = (ENV['COCOAPODS_CDN_MAX_NUMBER_OF_RETRIES'] || 5).to_i
14
+ # Single thread executor for all network activity.
15
+ HYDRA_EXECUTOR = Concurrent::SingleThreadExecutor.new
16
+
17
+ private_constant :HYDRA_EXECUTOR
11
18
 
12
19
  # @param [String] repo The name of the repository
13
20
  #
@@ -18,12 +25,6 @@ module Pod
18
25
  # after the source was initialized, is considered fresh enough.
19
26
  @startup_time = Time.new
20
27
 
21
- @executor = Concurrent::ThreadPoolExecutor.new(
22
- :min_threads => 5,
23
- :max_threads => MAX_CDN_NETWORK_THREADS,
24
- :max_queue => 0 # unbounded work queue
25
- )
26
-
27
28
  @version_arrays_by_fragment_by_name = {}
28
29
 
29
30
  super(repo)
@@ -58,14 +59,14 @@ module Pod
58
59
  def preheat_existing_files
59
60
  files_to_update = files_definitely_to_update + deprecated_local_podspecs - ['deprecated_podspecs.txt']
60
61
  debug "CDN: #{name} Going to update #{files_to_update.count} files"
61
- loaders = files_to_update.map do |file|
62
- Concurrent::Promises.future_on(@executor) do
63
- download_file(file)
64
- end
65
- end
66
62
 
67
- catching_concurrent_errors do
68
- Concurrent::Promises.zip(*loaders).wait!
63
+ concurrent_requests_catching_errors do
64
+ # Queue all tasks first
65
+ loaders = files_to_update.map do |file|
66
+ download_file_async(file)
67
+ end
68
+ # Block and wait for all to complete running on Hydra
69
+ Promises.zip_futures_on(HYDRA_EXECUTOR, *loaders).wait!
69
70
  end
70
71
  end
71
72
 
@@ -117,27 +118,30 @@ module Pod
117
118
 
118
119
  return nil if @version_arrays_by_fragment_by_name[fragment][name].nil?
119
120
 
120
- loaders = []
121
- @versions_by_name[name] ||= @version_arrays_by_fragment_by_name[fragment][name].map do |version|
122
- # Optimization: ensure all the podspec files at least exist. The correct one will get refreshed
123
- # in #specification_path regardless.
124
- podspec_version_path_relative = Pathname.new(version).join("#{name}.podspec.json")
125
- unless pod_path_actual.join(podspec_version_path_relative).exist?
126
- loaders << Concurrent::Promises.future_on(@executor) do
127
- download_file(pod_path_relative.join(podspec_version_path_relative).to_s)
121
+ concurrent_requests_catching_errors do
122
+ loaders = []
123
+
124
+ @versions_by_name[name] ||= @version_arrays_by_fragment_by_name[fragment][name].map do |version|
125
+ # Optimization: ensure all the podspec files at least exist. The correct one will get refreshed
126
+ # in #specification_path regardless.
127
+ podspec_version_path_relative = Pathname.new(version).join("#{name}.podspec.json")
128
+
129
+ unless pod_path_actual.join(podspec_version_path_relative).exist?
130
+ # Queue all podspec download tasks first
131
+ loaders << download_file_async(pod_path_relative.join(podspec_version_path_relative).to_s)
128
132
  end
129
- end
130
- begin
131
- Version.new(version) if version[0, 1] != '.'
132
- rescue ArgumentError
133
- raise Informative, 'An unexpected version directory ' \
134
- "`#{version}` was encountered for the " \
135
- "`#{pod_path_actual}` Pod in the `#{name}` repository."
136
- end
137
- end.compact.sort.reverse
138
133
 
139
- catching_concurrent_errors do
140
- Concurrent::Promises.zip(*loaders).wait!
134
+ begin
135
+ Version.new(version) if version[0, 1] != '.'
136
+ rescue ArgumentError
137
+ raise Informative, 'An unexpected version directory ' \
138
+ "`#{version}` was encountered for the " \
139
+ "`#{pod_path_actual}` Pod in the `#{name}` repository."
140
+ end
141
+ end.compact.sort.reverse
142
+
143
+ # Block and wait for all to complete running on Hydra
144
+ Promises.zip_futures_on(HYDRA_EXECUTOR, *loaders).wait!
141
145
  end
142
146
 
143
147
  @versions_by_name[name]
@@ -323,18 +327,25 @@ module Pod
323
327
  end
324
328
 
325
329
  def download_file(partial_url)
330
+ # Block the main thread waiting for Hydra to finish
331
+ #
332
+ # Used for single-file downloads
333
+ download_file_async(partial_url).wait!
334
+ end
335
+
336
+ def download_file_async(partial_url)
326
337
  file_remote_url = URI.encode(url + partial_url.to_s)
327
338
  path = repo + partial_url
328
339
 
329
340
  if File.exist?(path)
330
341
  if @startup_time < File.mtime(path)
331
342
  debug "CDN: #{name} Relative path: #{partial_url} modified during this run! Returning local"
332
- return partial_url
343
+ return Promises.fulfilled_future(partial_url, HYDRA_EXECUTOR)
333
344
  end
334
345
 
335
346
  unless @check_existing_files_for_update
336
347
  debug "CDN: #{name} Relative path: #{partial_url} exists! Returning local because checking is only perfomed in repo update"
337
- return partial_url
348
+ return Promises.fulfilled_future(partial_url, HYDRA_EXECUTOR)
338
349
  end
339
350
  end
340
351
 
@@ -345,46 +356,68 @@ module Pod
345
356
  etag = File.read(etag_path) if File.exist?(etag_path)
346
357
  debug "CDN: #{name} Relative path: #{partial_url}, has ETag? #{etag}" unless etag.nil?
347
358
 
348
- download_retrying_retryable_errors(partial_url, file_remote_url, etag)
359
+ download_and_save_with_retries_async(partial_url, file_remote_url, etag)
349
360
  end
350
361
 
351
- def download_retrying_retryable_errors(partial_url, file_remote_url, etag, retries = MAX_NUMBER_OF_RETRIES)
362
+ def download_and_save_with_retries_async(partial_url, file_remote_url, etag, retries = MAX_NUMBER_OF_RETRIES)
352
363
  path = repo + partial_url
353
364
  etag_path = path.sub_ext(path.extname + '.etag')
354
365
 
355
- response = download_retrying_connection_errors(partial_url, file_remote_url, etag, retries)
356
-
357
- case response.status_code
358
- when 301
359
- redirect_location = response.headers['location'].first
360
- debug "CDN: #{name} Redirecting from #{file_remote_url} to #{redirect_location}"
361
- download_retrying_retryable_errors(partial_url, redirect_location, etag)
362
- when 304
363
- debug "CDN: #{name} Relative path not modified: #{partial_url}"
364
- # We need to update the file modification date, as it is later used for freshness
365
- # optimization. See #initialize for more information.
366
- FileUtils.touch path
367
- partial_url
368
- when 200
369
- File.open(path, 'w') { |f| f.write(response.body) }
370
-
371
- etag_new = response.headers['etag'].first if response.headers.include?('etag')
372
- debug "CDN: #{name} Relative path downloaded: #{partial_url}, save ETag: #{etag_new}"
373
- File.open(etag_path, 'w') { |f| f.write(etag_new) } unless etag_new.nil?
374
- partial_url
375
- when 404
376
- debug "CDN: #{name} Relative path couldn't be downloaded: #{partial_url} Response: #{response.status_code}"
377
- nil
378
- when 502, 503, 504
379
- if retries <= 1
380
- raise Informative, "CDN: #{name} URL couldn't be downloaded: #{file_remote_url} Response: #{response.status_code}"
366
+ download_task = download_typhoeus_impl_async(file_remote_url, etag).then do |response|
367
+ case response.response_code
368
+ when 301
369
+ redirect_location = response.headers['location']
370
+ debug "CDN: #{name} Redirecting from #{file_remote_url} to #{redirect_location}"
371
+ download_and_save_with_retries_async(partial_url, redirect_location, etag)
372
+ when 304
373
+ debug "CDN: #{name} Relative path not modified: #{partial_url}"
374
+ # We need to update the file modification date, as it is later used for freshness
375
+ # optimization. See #initialize for more information.
376
+ FileUtils.touch path
377
+ partial_url
378
+ when 200
379
+ File.open(path, 'w') { |f| f.write(response.response_body) }
380
+
381
+ etag_new = response.headers['etag']
382
+ debug "CDN: #{name} Relative path downloaded: #{partial_url}, save ETag: #{etag_new}"
383
+ File.open(etag_path, 'w') { |f| f.write(etag_new) } unless etag_new.nil?
384
+ partial_url
385
+ when 404
386
+ debug "CDN: #{name} Relative path couldn't be downloaded: #{partial_url} Response: #{response.response_code}"
387
+ nil
388
+ when 502, 503, 504
389
+ # Retryable HTTP errors, usually related to server overloading
390
+ if retries <= 1
391
+ raise Informative, "CDN: #{name} URL couldn't be downloaded: #{file_remote_url} Response: #{response.response_code} #{response.response_body}"
392
+ else
393
+ debug "CDN: #{name} URL couldn't be downloaded: #{file_remote_url} Response: #{response.response_code} #{response.response_body}, retries: #{retries - 1}"
394
+ exponential_backoff_async(retries).then do
395
+ download_and_save_with_retries_async(partial_url, file_remote_url, etag, retries - 1)
396
+ end
397
+ end
398
+ when 0
399
+ # Non-HTTP errors, usually network layer
400
+ if retries <= 1
401
+ raise Informative, "CDN: #{name} URL couldn't be downloaded: #{file_remote_url} Response: #{response.return_message}"
402
+ else
403
+ debug "CDN: #{name} URL couldn't be downloaded: #{file_remote_url} Response: #{response.return_message}, retries: #{retries - 1}"
404
+ exponential_backoff_async(retries).then do
405
+ download_and_save_with_retries_async(partial_url, file_remote_url, etag, retries - 1)
406
+ end
407
+ end
381
408
  else
382
- sleep_for(backoff_time(retries))
383
- download_retrying_retryable_errors(partial_url, file_remote_url, etag, retries - 1)
409
+ raise Informative, "CDN: #{name} URL couldn't be downloaded: #{file_remote_url} Response: #{response.response_code} #{response.response_body}"
384
410
  end
385
- else
386
- raise Informative, "CDN: #{name} URL couldn't be downloaded: #{file_remote_url} Response: #{response.status_code}"
387
411
  end
412
+
413
+ # Calling `Future#run` flattens the chained futures created by retries or redirects
414
+ #
415
+ # Does not, in fact, run the task - that is already happening in Hydra at this point
416
+ download_task.run
417
+ end
418
+
419
+ def exponential_backoff_async(retries)
420
+ sleep_async(backoff_time(retries))
388
421
  end
389
422
 
390
423
  def backoff_time(retries)
@@ -392,19 +425,38 @@ module Pod
392
425
  4 * 2**current_retry
393
426
  end
394
427
 
395
- def sleep_for(seconds)
396
- sleep(seconds)
397
- end
428
+ def sleep_async(seconds)
429
+ # Async sleep to avoid blocking either the main or the Hydra thread
430
+ Promises.schedule_on(HYDRA_EXECUTOR, seconds)
431
+ end
432
+
433
+ def download_typhoeus_impl_async(file_remote_url, etag)
434
+ # Create a prefereably HTTP/2 request - the protocol is ultimately responsible for picking
435
+ # the maximum supported protocol
436
+ # When debugging with proxy, use the following extra options:
437
+ # :proxy => 'http://localhost:8888',
438
+ # :ssl_verifypeer => false,
439
+ # :ssl_verifyhost => 0,
440
+ request = Typhoeus::Request.new(
441
+ file_remote_url,
442
+ :method => :get,
443
+ :http_version => :httpv2_0,
444
+ :timeout => 10,
445
+ :connecttimeout => 10,
446
+ :accept_encoding => 'gzip',
447
+ :netrc => :optional,
448
+ :netrc_file => Netrc.default_path,
449
+ :headers => etag.nil? ? {} : { 'If-None-Match' => etag },
450
+ )
398
451
 
399
- def download_retrying_connection_errors(partial_url, file_remote_url, etag, retries)
400
- etag.nil? ? REST.get(file_remote_url) : REST.get(file_remote_url, 'If-None-Match' => etag)
401
- rescue REST::Error => e
402
- if retries <= 1
403
- raise Informative, "CDN: #{name} URL couldn't be downloaded: #{file_remote_url}, error: #{e}"
404
- else
405
- debug "CDN: #{name} Relative path: #{partial_url} error: #{e} - retrying"
406
- download_retrying_connection_errors(partial_url, file_remote_url, etag, retries - 1)
452
+ future = Promises.resolvable_future_on(HYDRA_EXECUTOR)
453
+ queue_request(request)
454
+ request.on_complete do |response|
455
+ future.fulfill(response)
407
456
  end
457
+
458
+ # This `Future` should never reject, network errors are exposed on `Typhoeus::Response`
459
+ future
408
460
  end
409
461
 
410
462
  def debug(message)
@@ -415,11 +467,27 @@ module Pod
415
467
  end
416
468
  end
417
469
 
418
- def catching_concurrent_errors
470
+ def concurrent_requests_catching_errors
419
471
  yield
420
- rescue Concurrent::MultipleErrors => e
472
+ rescue MultipleErrors => e
473
+ # aggregated error message from `Concurrent`
421
474
  errors = e.errors
422
475
  raise Informative, "CDN: #{name} Repo update failed - #{e.errors.size} error(s):\n#{errors.join("\n")}"
423
476
  end
477
+
478
+ def queue_request(request)
479
+ @hydra ||= Typhoeus::Hydra.new
480
+
481
+ # Queue the request into the Hydra (libcurl reactor).
482
+ @hydra.queue(request)
483
+
484
+ # Cycle the reactor on a separate thread
485
+ #
486
+ # The way it works is that if more requests are queued while Hydra is in the `#run`
487
+ # method, it will keep executing them
488
+ #
489
+ # The upcoming calls to `#run` will simply run empty.
490
+ HYDRA_EXECUTOR.post(@hydra, &:run)
491
+ end
424
492
  end
425
493
  end
@@ -1,5 +1,5 @@
1
1
  module Pod
2
2
  # The version of the cocoapods-core.
3
3
  #
4
- CORE_VERSION = '1.8.4'.freeze unless defined? Pod::CORE_VERSION
4
+ CORE_VERSION = '1.9.0.beta.1'.freeze unless defined? Pod::CORE_VERSION
5
5
  end
@@ -45,6 +45,10 @@ module Pod
45
45
  @symbolic_name = input.name
46
46
  @deployment_target = input.deployment_target
47
47
  else
48
+ # Allow `Platform.new('macos')` to be equivalent to `Platform.macos`
49
+ if input == 'macos'
50
+ input = 'osx'
51
+ end
48
52
  @symbolic_name = input.to_sym
49
53
  target = target[:deployment_target] if target.is_a?(Hash)
50
54
  @deployment_target = Version.create(target)
@@ -422,6 +422,9 @@ module Pod
422
422
  # specifies the position of which this script phase should be executed. The currently supported values are:
423
423
  # `:before_compile`, `:after_compile` and `:any` which is the default.
424
424
  #
425
+ # @option options [String] :dependency_file
426
+ # specifies the dependency file to use for this script phase.
427
+ #
425
428
  # @return [void]
426
429
  #
427
430
  def script_phase(options)
@@ -677,14 +680,39 @@ module Pod
677
680
  current_target_definition.use_modular_headers_for_all_pods = true
678
681
  end
679
682
 
680
- # Use frameworks instead of static libraries for Pods.
683
+ # Use frameworks instead of static libraries for Pods. When using frameworks, you may also specify the `:linkage`
684
+ # style to use, either `:static` or `:dynamic`.
681
685
  #
682
686
  # ------
683
687
  #
684
688
  # This attribute is inherited by child target definitions.
685
689
  #
686
- def use_frameworks!(flag = true)
687
- current_target_definition.use_frameworks!(flag)
690
+ # @param [Boolean, Hash] option
691
+ # The option to use for configuring packaging and linkage style.
692
+ #
693
+ # @example
694
+ #
695
+ # target 'MyApp' do
696
+ # use_frameworks!
697
+ # pod 'AFNetworking', '~> 1.0'
698
+ # end
699
+ #
700
+ # @example
701
+ #
702
+ # target 'MyApp' do
703
+ # use_frameworks! :linkage => :dynamic
704
+ # pod 'AFNetworking', '~> 1.0'
705
+ # end
706
+ #
707
+ # target 'ZipApp' do
708
+ # use_frameworks! :linkage => :static
709
+ # pod 'SSZipArchive'
710
+ # end
711
+ #
712
+ # @return [void]
713
+ #
714
+ def use_frameworks!(option = true)
715
+ current_target_definition.use_frameworks!(option)
688
716
  end
689
717
 
690
718
  # Specifies the Swift version requirements this target definition supports.
@@ -365,25 +365,54 @@ module Pod
365
365
 
366
366
  #--------------------------------------#
367
367
 
368
- # Sets whether the target definition should build a framework.
368
+ # The (desired) build type for the pods integrated in this target definition. Defaults to static libraries and can
369
+ # only be overridden through Pod::Podfile::DSL#use_frameworks!.
370
+ #
371
+ # @return [BuildType]
372
+ #
373
+ def build_type
374
+ value = get_hash_value('uses_frameworks', root? ? BuildType.static_library : parent.build_type)
375
+ case value
376
+ when true, false
377
+ value ? BuildType.dynamic_framework : BuildType.static_library
378
+ when Hash
379
+ BuildType.new(:linkage => value.fetch(:linkage), :packaging => value.fetch(:packaging))
380
+ when BuildType
381
+ value
382
+ else
383
+ raise ArgumentError, "Got `#{value.inspect}`, should be a boolean, hash or BuildType."
384
+ end
385
+ end
386
+
387
+ # Sets whether the target definition's pods should be built as frameworks.
369
388
  #
370
- # @param [Bool] flag
371
- # Whether a framework should be built.
389
+ # @param [Boolean, Hash] option
390
+ # Whether pods that are integrated in this target should be built as frameworks. If the option is a
391
+ # boolean then the value affects both packaging and linkage styles. If set to true, then dynamic frameworks
392
+ # are used and if it's set to false, then static libraries are used. If the option is a hash then
393
+ # `:framework` packaging is implied and the user configures the `:linkage` style to use.
372
394
  #
373
395
  # @return [void]
374
396
  #
375
- def use_frameworks!(flag = true)
376
- set_hash_value('uses_frameworks', flag)
397
+ def use_frameworks!(option = true)
398
+ value = case option
399
+ when true, false
400
+ option ? BuildType.dynamic_framework : BuildType.static_library
401
+ when Hash
402
+ BuildType.new(:linkage => option.fetch(:linkage), :packaging => :framework)
403
+ else
404
+ raise ArgumentError, "Got `#{option.inspect}`, should be a boolean or hash."
405
+ end
406
+ set_hash_value('uses_frameworks', value.to_hash)
377
407
  end
378
408
 
379
- # @return [Bool] whether the target definition should build
380
- # a framework.
409
+ # @return [Bool] whether the target definition pods should be built as frameworks.
381
410
  #
382
411
  def uses_frameworks?
383
412
  if internal_hash['uses_frameworks'].nil?
384
413
  root? ? false : parent.uses_frameworks?
385
414
  else
386
- get_hash_value('uses_frameworks')
415
+ build_type.framework?
387
416
  end
388
417
  end
389
418
 
@@ -737,7 +766,8 @@ module Pod
737
766
  # @param [Hash] options
738
767
  # The options to use for this script phase. The required keys
739
768
  # are: `:name`, `:script`, while the optional keys are:
740
- # `:shell_path`, `:input_files`, `:output_files`, `:show_env_vars_in_log` and `:execution_position`.
769
+ # `:shell_path`, `:input_files`, `:output_files`, `:show_env_vars_in_log`, `:execution_position` and
770
+ # `:dependency_file`.
741
771
  #
742
772
  # @return [void]
743
773
  #
@@ -346,11 +346,17 @@ module Pod
346
346
  end
347
347
  end
348
348
 
349
- # @return [Array<String>] the name of the default subspecs if provided.
349
+ # @return [Array<String>, Symbol] the name(s) of the default subspecs if provided or :none for no default subspecs.
350
350
  #
351
351
  def default_subspecs
352
352
  # TODO: remove singular form and update the JSON specs.
353
- Array(attributes_hash['default_subspecs'] || attributes_hash['default_subspec'])
353
+ value = Array(attributes_hash['default_subspecs'] || attributes_hash['default_subspec'])
354
+ first = value.first
355
+ if first == :none || first == 'none'
356
+ first.to_sym
357
+ else
358
+ value
359
+ end
354
360
  end
355
361
 
356
362
  # Returns the dependencies on subspecs.
@@ -367,6 +373,8 @@ module Pod
367
373
  def subspec_dependencies(platform = nil)
368
374
  specs = if default_subspecs.empty?
369
375
  subspecs.compact.reject(&:non_library_specification?)
376
+ elsif default_subspecs == :none
377
+ []
370
378
  else
371
379
  default_subspecs.map do |subspec_name|
372
380
  root.subspec_by_name("#{name}/#{subspec_name}")
@@ -403,10 +411,29 @@ module Pod
403
411
  dependencies(platform) + subspec_dependencies(platform)
404
412
  end
405
413
 
414
+ # Returns whether a dependency is whitelisted for the given configuration.
415
+ #
416
+ # @param [Pod::Dependency] dependency
417
+ # the dependency verify.
418
+ #
419
+ # @param [Symbol, String] configuration
420
+ # the configuration to check against.
421
+ #
422
+ # @return [Bool] whether the dependency is whitelisted or not.
423
+ #
424
+ def dependency_whitelisted_for_configuration?(dependency, configuration)
425
+ inherited = -> { root? ? true : parent.dependency_whitelisted_for_configuration?(dependency, configuration) }
426
+
427
+ return inherited[] unless configuration_whitelist = attributes_hash['configuration_pod_whitelist']
428
+ return inherited[] unless whitelist_for_pod = configuration_whitelist[dependency.name]
429
+
430
+ whitelist_for_pod.include?(configuration.to_s.downcase)
431
+ end
432
+
406
433
  # Returns a consumer to access the multi-platform attributes.
407
434
  #
408
435
  # @param [String, Symbol, Platform] platform
409
- # he platform of the consumer
436
+ # the platform of the consumer
410
437
  #
411
438
  # @return [Specification::Consumer] the consumer for the given platform
412
439
  #
@@ -653,6 +653,12 @@ module Pod
653
653
  # spec.dependency 'AFNetworking', '~> 1.0'
654
654
  #
655
655
  # @example
656
+ # spec.dependency 'AFNetworking', '~> 1.0', :configurations => ['Debug']
657
+ #
658
+ # @example
659
+ # spec.dependency 'AFNetworking', '~> 1.0', :configurations => :debug
660
+ #
661
+ # @example
656
662
  # spec.dependency 'RestKit/CoreData', '~> 0.20.0'
657
663
  #
658
664
  # @example
@@ -676,16 +682,22 @@ module Pod
676
682
  end
677
683
  end
678
684
  end
679
- unless version_requirements.all? { |req| req.is_a?(String) }
680
- version_requirements.each do |requirement|
681
- if requirement.is_a?(Hash)
682
- if !requirement[:path].nil?
683
- raise Informative, 'Podspecs cannot specify the source of dependencies. The `:path` option is not supported.'\
684
- ' `:path` can be used in the Podfile instead to override global dependencies.'
685
- elsif !requirement[:git].nil?
686
- raise Informative, 'Podspecs cannot specify the source of dependencies. The `:git` option is not supported.'\
687
- ' `:git` can be used in the Podfile instead to override global dependencies.'
688
- end
685
+
686
+ configurations_option = version_requirements.find { |option| option.is_a?(Hash) && option.key?(:configurations) }
687
+ whitelisted_configurations = if configurations_option
688
+ version_requirements.delete(configurations_option)
689
+ Array(configurations_option.delete(:configurations)).map { |c| c.to_s.downcase }
690
+ end
691
+
692
+ dependency_options = version_requirements.reject { |req| req.is_a?(String) }
693
+ dependency_options.each do |dependency_option|
694
+ if dependency_option.is_a?(Hash)
695
+ if !dependency_option[:path].nil?
696
+ raise Informative, 'Podspecs cannot specify the source of dependencies. The `:path` option is not supported.'\
697
+ ' `:path` can be used in the Podfile instead to override global dependencies.'
698
+ elsif !dependency_option[:git].nil?
699
+ raise Informative, 'Podspecs cannot specify the source of dependencies. The `:git` option is not supported.'\
700
+ ' `:git` can be used in the Podfile instead to override global dependencies.'
689
701
  end
690
702
  end
691
703
 
@@ -694,6 +706,15 @@ module Pod
694
706
 
695
707
  attributes_hash['dependencies'] ||= {}
696
708
  attributes_hash['dependencies'][name] = version_requirements
709
+
710
+ unless whitelisted_configurations.nil?
711
+ if (extras = whitelisted_configurations - %w(debug release)) && !extras.empty?
712
+ raise Informative, "Only `Debug` & `Release` are allowed under configurations for dependency on `#{name}`. " \
713
+ "Found #{extras.map { |configuration| "`#{configuration}`" }.to_sentence}."
714
+ end
715
+ attributes_hash['configuration_pod_whitelist'] ||= {}
716
+ attributes_hash['configuration_pod_whitelist'][name] = whitelisted_configurations
717
+ end
697
718
  end
698
719
 
699
720
  def dependency=(args)
@@ -1024,7 +1045,7 @@ module Pod
1024
1045
  SCRIPT_PHASE_REQUIRED_KEYS = [:name, :script].freeze
1025
1046
 
1026
1047
  SCRIPT_PHASE_OPTIONAL_KEYS = [:shell_path, :input_files, :output_files, :input_file_lists, :output_file_lists,
1027
- :show_env_vars_in_log, :execution_position].freeze
1048
+ :show_env_vars_in_log, :execution_position, :dependency_file].freeze
1028
1049
 
1029
1050
  EXECUTION_POSITION_KEYS = [:before_compile, :after_compile, :any].freeze
1030
1051
 
@@ -1575,7 +1596,7 @@ module Pod
1575
1596
  :types => [String],
1576
1597
  :spec_types => [:test]
1577
1598
 
1578
- SCHEME_KEYS = [:launch_arguments, :environment_variables].freeze
1599
+ SCHEME_KEYS = [:launch_arguments, :environment_variables, :code_coverage].freeze
1579
1600
 
1580
1601
  # @!method scheme=(flag)
1581
1602
  #
@@ -1648,9 +1669,12 @@ module Pod
1648
1669
  # @!method default_subspecs=(subspec_array)
1649
1670
  #
1650
1671
  # An array of subspecs names that should be used as preferred dependency.
1651
- # If not specified a specifications requires all its subspecs as
1672
+ # If not specified, a specification requires all of its subspecs as
1652
1673
  # dependencies.
1653
1674
  #
1675
+ # You may use the value `:none` to specify that none of the subspecs are
1676
+ # required to compile this pod and that all subspecs are optional.
1677
+ #
1654
1678
  # ---
1655
1679
  #
1656
1680
  # A Pod should make available the full library by default. Users can
@@ -1666,17 +1690,21 @@ module Pod
1666
1690
  # spec.default_subspec = 'Core'
1667
1691
  #
1668
1692
  # @example
1693
+ #
1669
1694
  # spec.default_subspecs = 'Core', 'UI'
1670
1695
  #
1671
- # @param [Array<String>] subspec_names
1696
+ # @example
1697
+ #
1698
+ # spec.default_subspecs = :none
1699
+ #
1700
+ # @param [Array<String>, String, Symbol] subspec_names
1672
1701
  # An array of subspec names that should be inherited as
1673
1702
  # dependency.
1674
1703
  #
1675
- attribute :default_subspecs,
1676
- :container => Array,
1677
- :singularize => true,
1678
- :multi_platform => false,
1679
- :root_only => true
1704
+ root_attribute :default_subspecs,
1705
+ :container => Array,
1706
+ :types => [Array, String, Symbol],
1707
+ :singularize => true
1680
1708
 
1681
1709
  #-----------------------------------------------------------------------#
1682
1710
 
@@ -17,7 +17,7 @@ module Pod
17
17
  # multi-platform, they don't have inheritance, and they never have a
18
18
  # default value.
19
19
  #
20
- # @param [String] name
20
+ # @param [Symbol, String] name
21
21
  # The name of the attribute.
22
22
  #
23
23
  # @param [Hash] options
@@ -36,7 +36,7 @@ module Pod
36
36
  # Regular attributes in general support inheritance and multi-platform
37
37
  # values, so resolving them for a given specification is not trivial.
38
38
  #
39
- # @param [String] name
39
+ # @param [Symbol, String] name
40
40
  # The name of the attribute.
41
41
  #
42
42
  # @param [Hash] options
@@ -450,6 +450,9 @@ module Pod
450
450
  if s.key?(:environment_variables) && !s[:environment_variables].is_a?(Hash)
451
451
  results.add_error('scheme', 'Expected a hash for key `environment_variables`.')
452
452
  end
453
+ if s.key?(:code_coverage) && ![true, false].include?(s[:code_coverage])
454
+ results.add_error('scheme', 'Expected a boolean for key `code_coverage`.')
455
+ end
453
456
  end
454
457
  end
455
458
 
@@ -28,6 +28,14 @@ module Pod
28
28
 
29
29
  attr_reader :results
30
30
 
31
+ # @return [Array<String>] Keys that are valid but have been deprecated.
32
+ #
33
+ DEPRECATED_KEYS = ['swift_version'].freeze
34
+
35
+ # @return [Array<String>] Keys that are only used for internal purposes.
36
+ #
37
+ INTERNAL_KEYS = ['configuration_pod_whitelist'].freeze
38
+
31
39
  # Checks the attributes hash for any unknown key which might be the
32
40
  # result of a misspelling in a JSON file.
33
41
  #
@@ -42,8 +50,7 @@ module Pod
42
50
  def check_attributes
43
51
  attributes_keys = Pod::Specification::DSL.attributes.keys.map(&:to_s)
44
52
  platform_keys = Specification::DSL::PLATFORMS.map(&:to_s)
45
- deprecated_keys = ['swift_version']
46
- valid_keys = attributes_keys + platform_keys + deprecated_keys
53
+ valid_keys = attributes_keys + platform_keys + DEPRECATED_KEYS + INTERNAL_KEYS
47
54
  attributes_hash = consumer.spec.attributes_hash
48
55
  keys = attributes_hash.keys
49
56
  Specification::DSL::PLATFORMS.each do |platform|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocoapods-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.4
4
+ version: 1.9.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Duran
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-10-16 00:00:00.000000000 Z
12
+ date: 2019-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -87,6 +87,34 @@ dependencies:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: '1.1'
90
+ - !ruby/object:Gem::Dependency
91
+ name: typhoeus
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.0'
97
+ type: :runtime
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ - !ruby/object:Gem::Dependency
105
+ name: netrc
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.11'
111
+ type: :runtime
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.11'
90
118
  - !ruby/object:Gem::Dependency
91
119
  name: bacon
92
120
  requirement: !ruby/object:Gem::Requirement
@@ -115,6 +143,7 @@ files:
115
143
  - LICENSE
116
144
  - README.md
117
145
  - lib/cocoapods-core.rb
146
+ - lib/cocoapods-core/build_type.rb
118
147
  - lib/cocoapods-core/cdn_source.rb
119
148
  - lib/cocoapods-core/core_ui.rb
120
149
  - lib/cocoapods-core/dependency.rb