xcocoapods 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6303 -0
  3. data/LICENSE +28 -0
  4. data/README.md +80 -0
  5. data/bin/pod +56 -0
  6. data/bin/sandbox-pod +168 -0
  7. data/lib/cocoapods.rb +73 -0
  8. data/lib/cocoapods/command.rb +175 -0
  9. data/lib/cocoapods/command/cache.rb +28 -0
  10. data/lib/cocoapods/command/cache/clean.rb +90 -0
  11. data/lib/cocoapods/command/cache/list.rb +69 -0
  12. data/lib/cocoapods/command/env.rb +66 -0
  13. data/lib/cocoapods/command/init.rb +128 -0
  14. data/lib/cocoapods/command/install.rb +45 -0
  15. data/lib/cocoapods/command/ipc.rb +19 -0
  16. data/lib/cocoapods/command/ipc/list.rb +40 -0
  17. data/lib/cocoapods/command/ipc/podfile.rb +31 -0
  18. data/lib/cocoapods/command/ipc/podfile_json.rb +30 -0
  19. data/lib/cocoapods/command/ipc/repl.rb +51 -0
  20. data/lib/cocoapods/command/ipc/spec.rb +29 -0
  21. data/lib/cocoapods/command/ipc/update_search_index.rb +24 -0
  22. data/lib/cocoapods/command/lib.rb +11 -0
  23. data/lib/cocoapods/command/lib/create.rb +105 -0
  24. data/lib/cocoapods/command/lib/lint.rb +121 -0
  25. data/lib/cocoapods/command/list.rb +39 -0
  26. data/lib/cocoapods/command/options/project_directory.rb +36 -0
  27. data/lib/cocoapods/command/options/repo_update.rb +34 -0
  28. data/lib/cocoapods/command/outdated.rb +140 -0
  29. data/lib/cocoapods/command/repo.rb +29 -0
  30. data/lib/cocoapods/command/repo/add.rb +103 -0
  31. data/lib/cocoapods/command/repo/lint.rb +82 -0
  32. data/lib/cocoapods/command/repo/list.rb +93 -0
  33. data/lib/cocoapods/command/repo/push.rb +281 -0
  34. data/lib/cocoapods/command/repo/remove.rb +36 -0
  35. data/lib/cocoapods/command/repo/update.rb +28 -0
  36. data/lib/cocoapods/command/setup.rb +103 -0
  37. data/lib/cocoapods/command/spec.rb +112 -0
  38. data/lib/cocoapods/command/spec/cat.rb +51 -0
  39. data/lib/cocoapods/command/spec/create.rb +283 -0
  40. data/lib/cocoapods/command/spec/edit.rb +87 -0
  41. data/lib/cocoapods/command/spec/env_spec.rb +53 -0
  42. data/lib/cocoapods/command/spec/lint.rb +137 -0
  43. data/lib/cocoapods/command/spec/which.rb +43 -0
  44. data/lib/cocoapods/command/update.rb +101 -0
  45. data/lib/cocoapods/config.rb +347 -0
  46. data/lib/cocoapods/core_overrides.rb +1 -0
  47. data/lib/cocoapods/downloader.rb +190 -0
  48. data/lib/cocoapods/downloader/cache.rb +233 -0
  49. data/lib/cocoapods/downloader/request.rb +86 -0
  50. data/lib/cocoapods/downloader/response.rb +16 -0
  51. data/lib/cocoapods/executable.rb +222 -0
  52. data/lib/cocoapods/external_sources.rb +57 -0
  53. data/lib/cocoapods/external_sources/abstract_external_source.rb +205 -0
  54. data/lib/cocoapods/external_sources/downloader_source.rb +30 -0
  55. data/lib/cocoapods/external_sources/path_source.rb +55 -0
  56. data/lib/cocoapods/external_sources/podspec_source.rb +54 -0
  57. data/lib/cocoapods/gem_version.rb +5 -0
  58. data/lib/cocoapods/generator/acknowledgements.rb +107 -0
  59. data/lib/cocoapods/generator/acknowledgements/markdown.rb +44 -0
  60. data/lib/cocoapods/generator/acknowledgements/plist.rb +94 -0
  61. data/lib/cocoapods/generator/app_target_helper.rb +244 -0
  62. data/lib/cocoapods/generator/bridge_support.rb +22 -0
  63. data/lib/cocoapods/generator/constant.rb +19 -0
  64. data/lib/cocoapods/generator/copy_resources_script.rb +230 -0
  65. data/lib/cocoapods/generator/dummy_source.rb +31 -0
  66. data/lib/cocoapods/generator/embed_frameworks_script.rb +215 -0
  67. data/lib/cocoapods/generator/header.rb +103 -0
  68. data/lib/cocoapods/generator/info_plist_file.rb +116 -0
  69. data/lib/cocoapods/generator/module_map.rb +99 -0
  70. data/lib/cocoapods/generator/prefix_header.rb +60 -0
  71. data/lib/cocoapods/generator/umbrella_header.rb +46 -0
  72. data/lib/cocoapods/hooks_manager.rb +132 -0
  73. data/lib/cocoapods/installer.rb +703 -0
  74. data/lib/cocoapods/installer/analyzer.rb +972 -0
  75. data/lib/cocoapods/installer/analyzer/analysis_result.rb +87 -0
  76. data/lib/cocoapods/installer/analyzer/locking_dependency_analyzer.rb +98 -0
  77. data/lib/cocoapods/installer/analyzer/pod_variant.rb +67 -0
  78. data/lib/cocoapods/installer/analyzer/pod_variant_set.rb +157 -0
  79. data/lib/cocoapods/installer/analyzer/podfile_dependency_cache.rb +54 -0
  80. data/lib/cocoapods/installer/analyzer/sandbox_analyzer.rb +240 -0
  81. data/lib/cocoapods/installer/analyzer/specs_state.rb +84 -0
  82. data/lib/cocoapods/installer/analyzer/target_inspection_result.rb +53 -0
  83. data/lib/cocoapods/installer/analyzer/target_inspector.rb +260 -0
  84. data/lib/cocoapods/installer/installation_options.rb +158 -0
  85. data/lib/cocoapods/installer/pod_source_installer.rb +202 -0
  86. data/lib/cocoapods/installer/pod_source_preparer.rb +77 -0
  87. data/lib/cocoapods/installer/podfile_validator.rb +139 -0
  88. data/lib/cocoapods/installer/post_install_hooks_context.rb +132 -0
  89. data/lib/cocoapods/installer/pre_install_hooks_context.rb +51 -0
  90. data/lib/cocoapods/installer/source_provider_hooks_context.rb +34 -0
  91. data/lib/cocoapods/installer/user_project_integrator.rb +250 -0
  92. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +463 -0
  93. data/lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +146 -0
  94. data/lib/cocoapods/installer/xcode.rb +8 -0
  95. data/lib/cocoapods/installer/xcode/pods_project_generator.rb +416 -0
  96. data/lib/cocoapods/installer/xcode/pods_project_generator/aggregate_target_installer.rb +181 -0
  97. data/lib/cocoapods/installer/xcode/pods_project_generator/app_host_installer.rb +84 -0
  98. data/lib/cocoapods/installer/xcode/pods_project_generator/file_references_installer.rb +334 -0
  99. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_installer.rb +777 -0
  100. data/lib/cocoapods/installer/xcode/pods_project_generator/pod_target_integrator.rb +116 -0
  101. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installation_result.rb +86 -0
  102. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installer.rb +256 -0
  103. data/lib/cocoapods/installer/xcode/pods_project_generator/target_installer_helper.rb +68 -0
  104. data/lib/cocoapods/installer/xcode/target_validator.rb +147 -0
  105. data/lib/cocoapods/open-uri.rb +33 -0
  106. data/lib/cocoapods/project.rb +414 -0
  107. data/lib/cocoapods/resolver.rb +585 -0
  108. data/lib/cocoapods/resolver/lazy_specification.rb +79 -0
  109. data/lib/cocoapods/sandbox.rb +404 -0
  110. data/lib/cocoapods/sandbox/file_accessor.rb +444 -0
  111. data/lib/cocoapods/sandbox/headers_store.rb +146 -0
  112. data/lib/cocoapods/sandbox/path_list.rb +220 -0
  113. data/lib/cocoapods/sandbox/pod_dir_cleaner.rb +85 -0
  114. data/lib/cocoapods/sandbox/podspec_finder.rb +23 -0
  115. data/lib/cocoapods/sources_manager.rb +157 -0
  116. data/lib/cocoapods/target.rb +261 -0
  117. data/lib/cocoapods/target/aggregate_target.rb +338 -0
  118. data/lib/cocoapods/target/build_settings.rb +1075 -0
  119. data/lib/cocoapods/target/pod_target.rb +559 -0
  120. data/lib/cocoapods/user_interface.rb +459 -0
  121. data/lib/cocoapods/user_interface/error_report.rb +187 -0
  122. data/lib/cocoapods/user_interface/inspector_reporter.rb +109 -0
  123. data/lib/cocoapods/validator.rb +981 -0
  124. metadata +533 -0
@@ -0,0 +1,28 @@
1
+ require 'cocoapods/downloader'
2
+ require 'cocoapods/command/cache/list'
3
+ require 'cocoapods/command/cache/clean'
4
+
5
+ module Pod
6
+ class Command
7
+ class Cache < Command
8
+ self.abstract_command = true
9
+ self.summary = 'Manipulate the CocoaPods cache'
10
+
11
+ self.description = <<-DESC
12
+ Manipulate the download cache for pods, like printing the cache content
13
+ or cleaning the pods cache.
14
+ DESC
15
+
16
+ def initialize(argv)
17
+ @cache = Downloader::Cache.new(Config.instance.cache_root + 'Pods')
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def pod_type(pod_cache_descriptor)
24
+ pod_cache_descriptor[:release] ? 'Release' : 'External'
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,90 @@
1
+ module Pod
2
+ class Command
3
+ class Cache < Command
4
+ class Clean < Cache
5
+ self.summary = 'Remove the cache for pods'
6
+
7
+ self.description = <<-DESC
8
+ Remove the cache for a given pod, or clear the cache completely.
9
+
10
+ If there is multiple cache for various versions of the requested pod,
11
+ you will be asked which one to clean. Use `--all` to clean them all.
12
+
13
+ If you don't give a pod `NAME`, you need to specify the `--all`
14
+ flag (this is to avoid cleaning all the cache by mistake).
15
+ DESC
16
+
17
+ self.arguments = [
18
+ CLAide::Argument.new('NAME', false),
19
+ ]
20
+
21
+ def self.options
22
+ [[
23
+ '--all', 'Remove all the cached pods without asking'
24
+ ]].concat(super)
25
+ end
26
+
27
+ def initialize(argv)
28
+ @pod_name = argv.shift_argument
29
+ @wipe_all = argv.flag?('all')
30
+ super
31
+ end
32
+
33
+ def run
34
+ if @pod_name.nil?
35
+ # Note: at that point, @wipe_all is always true (thanks to `validate!`)
36
+ # Remove all
37
+ clear_cache
38
+ else
39
+ # Remove only cache for this pod
40
+ cache_descriptors = @cache.cache_descriptors_per_pod[@pod_name]
41
+ if cache_descriptors.nil?
42
+ UI.notice("No cache for pod named #{@pod_name} found")
43
+ elsif cache_descriptors.count > 1 && !@wipe_all
44
+ # Ask which to remove
45
+ choices = cache_descriptors.map { |c| "#{@pod_name} v#{c[:version]} (#{pod_type(c)})" }
46
+ index = UI.choose_from_array(choices, 'Which pod cache do you want to remove?')
47
+ remove_caches([cache_descriptors[index]])
48
+ else
49
+ # Remove all found cache of this pod
50
+ remove_caches(cache_descriptors)
51
+ end
52
+ end
53
+ end
54
+
55
+ def validate!
56
+ super
57
+ if @pod_name.nil? && !@wipe_all
58
+ # Security measure, to avoid removing the pod cache too agressively by mistake
59
+ help! 'You should either specify a pod name or use the --all flag'
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # Removes the specified cache
66
+ #
67
+ # @param [Array<Hash>] cache_descriptors
68
+ # An array of caches to remove, each specified with the same
69
+ # hash as cache_descriptors_per_pod especially :spec_file and :slug
70
+ #
71
+ def remove_caches(cache_descriptors)
72
+ cache_descriptors.each do |desc|
73
+ UI.message("Removing spec #{desc[:spec_file]} (v#{desc[:version]})") do
74
+ FileUtils.rm(desc[:spec_file])
75
+ end
76
+ UI.message("Removing cache #{desc[:slug]}") do
77
+ FileUtils.rm_rf(desc[:slug])
78
+ end
79
+ end
80
+ end
81
+
82
+ def clear_cache
83
+ UI.message("Removing the whole cache dir #{@cache.root}") do
84
+ FileUtils.rm_rf(@cache.root)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,69 @@
1
+ module Pod
2
+ class Command
3
+ class Cache < Command
4
+ class List < Cache
5
+ self.summary = 'List the paths of pod caches for each known pod'
6
+
7
+ self.description = <<-DESC
8
+ Shows the content of the pods cache as a YAML tree output, organized by pod.
9
+ If `NAME` is given, only the caches for that pod will be included in the output.
10
+ DESC
11
+
12
+ self.arguments = [
13
+ CLAide::Argument.new('NAME', false),
14
+ ]
15
+
16
+ def self.options
17
+ [[
18
+ '--short', 'Only print the path relative to the cache root'
19
+ ]].concat(super)
20
+ end
21
+
22
+ def initialize(argv)
23
+ @pod_name = argv.shift_argument
24
+ @short_output = argv.flag?('short')
25
+ super
26
+ end
27
+
28
+ def run
29
+ UI.puts("$CACHE_ROOT: #{@cache.root}") if @short_output
30
+ if @pod_name.nil? # Print all
31
+ @cache.cache_descriptors_per_pod.each do |pod_name, cache_descriptors|
32
+ print_pod_cache_infos(pod_name, cache_descriptors)
33
+ end
34
+ else # Print only for the requested pod
35
+ cache_descriptors = @cache.cache_descriptors_per_pod[@pod_name]
36
+ if cache_descriptors.nil?
37
+ UI.notice("No cache for pod named #{@pod_name} found")
38
+ else
39
+ print_pod_cache_infos(@pod_name, cache_descriptors)
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Prints the list of specs & pod cache dirs for a single pod name.
47
+ #
48
+ # This output is valid YAML so it can be parsed with 3rd party tools
49
+ #
50
+ # @param [Array<Hash>] cache_descriptors
51
+ # The various infos about a pod cache. Keys are
52
+ # :spec_file, :version, :release and :slug
53
+ #
54
+ def print_pod_cache_infos(pod_name, cache_descriptors)
55
+ UI.puts "#{pod_name}:"
56
+ cache_descriptors.each do |desc|
57
+ if @short_output
58
+ [:spec_file, :slug].each { |k| desc[k] = desc[k].relative_path_from(@cache.root) }
59
+ end
60
+ UI.puts(" - Version: #{desc[:version]}")
61
+ UI.puts(" Type: #{pod_type(desc)}")
62
+ UI.puts(" Spec: #{desc[:spec_file]}")
63
+ UI.puts(" Pod: #{desc[:slug]}")
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,66 @@
1
+ require 'cocoapods/user_interface/error_report'
2
+
3
+ module Pod
4
+ class Command
5
+ class Env < Command
6
+ self.summary = 'Display pod environment'
7
+ self.description = 'Display pod environment.'
8
+
9
+ def self.options
10
+ options = []
11
+ options.concat(super.reject { |option, _| option == '--silent' })
12
+ end
13
+
14
+ def initialize(argv)
15
+ super
16
+ config.silent = false
17
+ end
18
+
19
+ def run
20
+ UI.puts report
21
+ end
22
+
23
+ def report
24
+ <<-EOS
25
+
26
+ #{stack}
27
+ #{executable_path}
28
+ ### Plugins
29
+
30
+ ```
31
+ #{plugins_string}
32
+ ```
33
+ #{markdown_podfile}
34
+ EOS
35
+ end
36
+
37
+ def stack
38
+ UI::ErrorReport.stack
39
+ end
40
+
41
+ def markdown_podfile
42
+ UI::ErrorReport.markdown_podfile
43
+ end
44
+
45
+ def plugins_string
46
+ UI::ErrorReport.plugins_string
47
+ end
48
+
49
+ private
50
+
51
+ def executable_path
52
+ <<-EOS
53
+ ### Installation Source
54
+
55
+ ```
56
+ Executable Path: #{actual_path}
57
+ ```
58
+ EOS
59
+ end
60
+
61
+ def actual_path
62
+ $PROGRAM_NAME
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,128 @@
1
+ require 'xcodeproj'
2
+ require 'active_support/core_ext/string/strip'
3
+
4
+ module Pod
5
+ class Command
6
+ class Init < Command
7
+ self.summary = 'Generate a Podfile for the current directory'
8
+ self.description = <<-DESC
9
+ Creates a Podfile for the current directory if none currently exists. If
10
+ an `XCODEPROJ` project file is specified or if there is only a single
11
+ project file in the current directory, targets will be automatically
12
+ generated based on targets defined in the project.
13
+
14
+ It is possible to specify a list of dependencies which will be used by
15
+ the template in the `Podfile.default` (normal targets) `Podfile.test`
16
+ (test targets) files which should be stored in the
17
+ `#{Config.instance.templates_dir}` folder.
18
+ DESC
19
+ self.arguments = [
20
+ CLAide::Argument.new('XCODEPROJ', :false),
21
+ ]
22
+
23
+ def initialize(argv)
24
+ @podfile_path = Pathname.pwd + 'Podfile'
25
+ @project_path = argv.shift_argument
26
+ @project_paths = Pathname.pwd.children.select { |pn| pn.extname == '.xcodeproj' }
27
+ super
28
+ end
29
+
30
+ def validate!
31
+ super
32
+ raise Informative, 'Existing Podfile found in directory' unless config.podfile_path_in_dir(Pathname.pwd).nil?
33
+ if @project_path
34
+ help! "Xcode project at #{@project_path} does not exist" unless File.exist? @project_path
35
+ project_path = @project_path
36
+ else
37
+ raise Informative, 'No Xcode project found, please specify one' unless @project_paths.length > 0
38
+ raise Informative, 'Multiple Xcode projects found, please specify one' unless @project_paths.length == 1
39
+ project_path = @project_paths.first
40
+ end
41
+ @xcode_project = Xcodeproj::Project.open(project_path)
42
+ end
43
+
44
+ def run
45
+ @podfile_path.open('w') { |f| f << podfile_template(@xcode_project) }
46
+ end
47
+
48
+ private
49
+
50
+ # @param [Xcodeproj::Project] project
51
+ # The xcode project to generate a podfile for.
52
+ #
53
+ # @return [String] the text of the Podfile for the provided project
54
+ #
55
+ def podfile_template(project)
56
+ podfile = ''
57
+ podfile << "project '#{@project_path}'\n\n" if @project_path
58
+ podfile << <<-PLATFORM.strip_heredoc
59
+ # Uncomment the next line to define a global platform for your project
60
+ # platform :ios, '9.0'
61
+ PLATFORM
62
+
63
+ # Split out the targets into app and test targets
64
+ test_targets, app_targets = project.native_targets.
65
+ sort_by { |t| t.name.downcase }.
66
+ partition(&:test_target_type?)
67
+
68
+ app_targets.each do |app_target|
69
+ test_targets_for_app = test_targets.select do |target|
70
+ target.name.downcase.start_with?(app_target.name.downcase)
71
+ end
72
+ podfile << target_module(app_target, test_targets_for_app)
73
+ end
74
+
75
+ podfile
76
+ end
77
+
78
+ # @param [[Xcodeproj::PBXTarget]] targets
79
+ # An array which always has a target as its first item
80
+ # and may optionally contain related test targets
81
+ #
82
+ # @return [String] the text for the target module
83
+ #
84
+ def target_module(app, tests)
85
+ target_module = "\ntarget '#{app.name.gsub(/'/, "\\\\\'")}' do\n"
86
+
87
+ target_module << if app.resolved_build_setting('SWIFT_OPTIMIZATION_LEVEL').values.any?
88
+ <<-RUBY
89
+ # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
90
+ use_frameworks!
91
+
92
+ RUBY
93
+ else
94
+ <<-RUBY
95
+ # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
96
+ # use_frameworks!
97
+
98
+ RUBY
99
+ end
100
+
101
+ target_module << template_contents(config.default_podfile_path, ' ', "Pods for #{app.name}\n")
102
+
103
+ tests.each do |test|
104
+ target_module << "\n target '#{test.name.gsub(/'/, "\\\\\'")}' do\n"
105
+ target_module << " inherit! :search_paths\n"
106
+ target_module << template_contents(config.default_test_podfile_path, ' ', 'Pods for testing')
107
+ target_module << "\n end\n"
108
+ end
109
+
110
+ target_module << "\nend\n"
111
+ end
112
+
113
+ # @param [[Xcodeproj::PBXTarget]] targets
114
+ # An array which always has a target as its first item
115
+ # and may optionally contain a second target as its test target
116
+ #
117
+ # @return [String] the text for the target module
118
+ #
119
+ def template_contents(path, prefix, fallback)
120
+ if path.exist?
121
+ path.read.chomp.lines.map { |line| "#{prefix}#{line}" }.join("\n")
122
+ else
123
+ "#{prefix}# #{fallback}"
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,45 @@
1
+ module Pod
2
+ class Command
3
+ class Install < Command
4
+ include RepoUpdate
5
+ include ProjectDirectory
6
+
7
+ self.summary = 'Install project dependencies according to versions from a Podfile.lock'
8
+
9
+ self.description = <<-DESC
10
+ Downloads all dependencies defined in `Podfile` and creates an Xcode
11
+ Pods library project in `./Pods`.
12
+
13
+ The Xcode project file should be specified in your `Podfile` like this:
14
+
15
+ project 'path/to/XcodeProject.xcodeproj'
16
+
17
+ If no project is specified, then a search for an Xcode project will
18
+ be made. If more than one Xcode project is found, the command will
19
+ raise an error.
20
+
21
+ This will configure the project to reference the Pods static library,
22
+ add a build configuration file, and add a post build script to copy
23
+ Pod resources.
24
+
25
+ This may return one of several error codes if it encounters problems.
26
+ * `1` Generic error code
27
+ * `31` Spec not found (i.e out-of-date source repos, mistyped Pod name etc...)
28
+ DESC
29
+
30
+ def self.options
31
+ [
32
+ ['--repo-update', 'Force running `pod repo update` before install'],
33
+ ].concat(super).reject { |(name, _)| name == '--no-repo-update' }
34
+ end
35
+
36
+ def run
37
+ verify_podfile_exists!
38
+ installer = installer_for_config
39
+ installer.repo_update = repo_update?(:default => false)
40
+ installer.update = false
41
+ installer.install!
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ require 'cocoapods/command/ipc/list'
2
+ require 'cocoapods/command/ipc/podfile'
3
+ require 'cocoapods/command/ipc/podfile_json'
4
+ require 'cocoapods/command/ipc/repl'
5
+ require 'cocoapods/command/ipc/spec'
6
+ require 'cocoapods/command/ipc/update_search_index'
7
+
8
+ module Pod
9
+ class Command
10
+ class IPC < Command
11
+ self.abstract_command = true
12
+ self.summary = 'Inter-process communication'
13
+
14
+ def output_pipe
15
+ STDOUT
16
+ end
17
+ end
18
+ end
19
+ end