cocoapods 0.5.1 → 0.6.0.rc1
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.
- data/CHANGELOG.md +229 -2
- data/README.md +50 -20
- data/bin/pod +3 -2
- data/lib/cocoapods.rb +23 -9
- data/lib/cocoapods/command.rb +71 -30
- data/lib/cocoapods/command/error_report.rb +102 -0
- data/lib/cocoapods/command/install.rb +27 -19
- data/lib/cocoapods/command/list.rb +51 -8
- data/lib/cocoapods/command/presenter.rb +61 -0
- data/lib/cocoapods/command/presenter/cocoa_pod.rb +123 -0
- data/lib/cocoapods/command/push.rb +102 -0
- data/lib/cocoapods/command/repo.rb +70 -14
- data/lib/cocoapods/command/search.rb +7 -10
- data/lib/cocoapods/command/setup.rb +76 -15
- data/lib/cocoapods/command/spec.rb +581 -97
- data/lib/cocoapods/config.rb +23 -26
- data/lib/cocoapods/dependency.rb +86 -40
- data/lib/cocoapods/downloader.rb +30 -18
- data/lib/cocoapods/downloader/git.rb +125 -15
- data/lib/cocoapods/downloader/http.rb +73 -0
- data/lib/cocoapods/downloader/mercurial.rb +3 -9
- data/lib/cocoapods/downloader/subversion.rb +3 -9
- data/lib/cocoapods/executable.rb +26 -3
- data/lib/cocoapods/generator/acknowledgements.rb +37 -0
- data/lib/cocoapods/generator/acknowledgements/markdown.rb +38 -0
- data/lib/cocoapods/generator/acknowledgements/plist.rb +63 -0
- data/lib/cocoapods/generator/copy_resources_script.rb +8 -4
- data/lib/cocoapods/generator/documentation.rb +99 -0
- data/lib/cocoapods/generator/dummy_source.rb +14 -0
- data/lib/cocoapods/installer.rb +140 -109
- data/lib/cocoapods/installer/target_installer.rb +78 -83
- data/lib/cocoapods/installer/user_project_integrator.rb +162 -0
- data/lib/cocoapods/local_pod.rb +240 -0
- data/lib/cocoapods/platform.rb +41 -18
- data/lib/cocoapods/podfile.rb +234 -21
- data/lib/cocoapods/project.rb +67 -0
- data/lib/cocoapods/resolver.rb +62 -32
- data/lib/cocoapods/sandbox.rb +63 -0
- data/lib/cocoapods/source.rb +42 -20
- data/lib/cocoapods/specification.rb +294 -271
- data/lib/cocoapods/specification/set.rb +10 -28
- data/lib/cocoapods/specification/statistics.rb +112 -0
- metadata +124 -11
- data/lib/cocoapods/xcodeproj_pods.rb +0 -111
@@ -6,7 +6,7 @@ module Pod
|
|
6
6
|
def self.banner
|
7
7
|
%{Managing spec-repos:
|
8
8
|
|
9
|
-
$ pod repo add NAME URL
|
9
|
+
$ pod repo add NAME URL [BRANCH]
|
10
10
|
|
11
11
|
Clones `URL' in the local spec-repos directory at `~/.cocoapods'. The
|
12
12
|
remote can later be referred to by `NAME'.
|
@@ -14,11 +14,7 @@ module Pod
|
|
14
14
|
$ pod repo update NAME
|
15
15
|
|
16
16
|
Updates the local clone of the spec-repo `NAME'. If `NAME' is omitted
|
17
|
-
this will update all spec-repos in `~/.cocoapods'.
|
18
|
-
|
19
|
-
$ pod repo set-url NAME URL
|
20
|
-
|
21
|
-
Updates the remote `URL' of the spec-repo `NAME'.}
|
17
|
+
this will update all spec-repos in `~/.cocoapods'.}
|
22
18
|
end
|
23
19
|
|
24
20
|
extend Executable
|
@@ -26,10 +22,11 @@ module Pod
|
|
26
22
|
|
27
23
|
def initialize(argv)
|
28
24
|
case @action = argv.arguments[0]
|
29
|
-
when 'add'
|
25
|
+
when 'add'
|
30
26
|
unless (@name = argv.arguments[1]) && (@url = argv.arguments[2])
|
31
27
|
raise Informative, "#{@action == 'add' ? 'Adding' : 'Updating the remote of'} a repo needs a `name' and a `url'."
|
32
28
|
end
|
29
|
+
@branch = argv.arguments[3]
|
33
30
|
when 'update'
|
34
31
|
@name = argv.arguments[1]
|
35
32
|
else
|
@@ -46,24 +43,83 @@ module Pod
|
|
46
43
|
end
|
47
44
|
|
48
45
|
def add
|
49
|
-
|
46
|
+
print_subtitle "Cloning spec repo `#{@name}' from `#{@url}'#{" (branch `#{@branch}')" if @branch}"
|
50
47
|
config.repos_dir.mkpath
|
51
48
|
Dir.chdir(config.repos_dir) { git("clone '#{@url}' #{@name}") }
|
49
|
+
Dir.chdir(dir) { git("checkout #{@branch}") } if @branch
|
50
|
+
check_versions(dir)
|
52
51
|
end
|
53
52
|
|
54
53
|
def update
|
55
|
-
dirs = @name ? [dir] : config.repos_dir.children
|
54
|
+
dirs = @name ? [dir] : config.repos_dir.children.select {|c| c.directory?}
|
56
55
|
dirs.each do |dir|
|
57
|
-
|
58
|
-
Dir.chdir(dir)
|
56
|
+
print_subtitle "Updating spec repo `#{dir.basename}'"
|
57
|
+
Dir.chdir(dir) do
|
58
|
+
`git rev-parse >/dev/null 2>&1`
|
59
|
+
if $?.exitstatus.zero?
|
60
|
+
git("pull")
|
61
|
+
else
|
62
|
+
puts(" Not a git repository") if config.verbose?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
check_versions(dir)
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
69
|
+
def check_versions(dir)
|
70
|
+
versions = versions(dir)
|
71
|
+
unless is_compatilbe(versions)
|
72
|
+
min, max = versions['min'], versions['max']
|
73
|
+
version_msg = ( min == max ) ? min : "#{min} - #{max}"
|
74
|
+
raise Informative,
|
75
|
+
"\n[!] The `#{dir.basename.to_s}' repo requires CocoaPods #{version_msg}\n".red +
|
76
|
+
"Update Cocoapods, or checkout the appropriate tag in the repo.\n\n"
|
65
77
|
end
|
78
|
+
puts "\nCocoapods #{versions['last']} is available.\n".green if has_update(versions)
|
66
79
|
end
|
80
|
+
|
81
|
+
def self.compatible?(name)
|
82
|
+
dir = Config.instance.repos_dir + name
|
83
|
+
versions = versions(dir)
|
84
|
+
is_compatilbe(versions)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def versions(dir)
|
90
|
+
self.class.versions(dir)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.versions(dir)
|
94
|
+
require 'yaml'
|
95
|
+
yaml_file = dir + 'CocoaPods-version.yml'
|
96
|
+
yaml_file.exist? ? YAML.load_file(yaml_file) : {}
|
97
|
+
end
|
98
|
+
|
99
|
+
def is_compatilbe(versions)
|
100
|
+
self.class.is_compatilbe(versions)
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.is_compatilbe(versions)
|
104
|
+
min, max = versions['min'], versions['max']
|
105
|
+
supports_min = !min || bin_version >= Gem::Version.new(min)
|
106
|
+
supports_max = !max || bin_version <= Gem::Version.new(max)
|
107
|
+
supports_min && supports_max
|
108
|
+
end
|
109
|
+
|
110
|
+
def has_update(versions)
|
111
|
+
self.class.has_update(versions)
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.has_update(versions)
|
115
|
+
last = versions['last']
|
116
|
+
last && Gem::Version.new(last) > bin_version
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.bin_version
|
120
|
+
Gem::Version.new(VERSION)
|
121
|
+
end
|
122
|
+
|
67
123
|
end
|
68
124
|
end
|
69
125
|
end
|
@@ -12,23 +12,20 @@ module Pod
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.options
|
15
|
-
"
|
16
|
-
super
|
15
|
+
[["--full", "Search by name, summary, and description"]].concat(Presenter.options).concat(super)
|
17
16
|
end
|
18
17
|
|
19
18
|
def initialize(argv)
|
20
19
|
@full_text_search = argv.option('--full')
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
@presenter = Presenter.new(argv)
|
21
|
+
@query = argv.shift_argument
|
22
|
+
super unless argv.empty? && @query
|
24
23
|
end
|
25
24
|
|
26
25
|
def run
|
27
|
-
Source.search_by_name(@query.strip, @full_text_search)
|
28
|
-
|
29
|
-
|
30
|
-
puts
|
31
|
-
end
|
26
|
+
sets = Source.search_by_name(@query.strip, @full_text_search)
|
27
|
+
sets.each {|s| puts @presenter.describe(s)}
|
28
|
+
puts
|
32
29
|
end
|
33
30
|
end
|
34
31
|
end
|
@@ -2,7 +2,7 @@ module Pod
|
|
2
2
|
class Command
|
3
3
|
class Setup < Command
|
4
4
|
def self.banner
|
5
|
-
%{Setup CocoaPods environment:
|
5
|
+
%{Setup CocoaPods environment:
|
6
6
|
|
7
7
|
$ pod setup
|
8
8
|
|
@@ -14,36 +14,97 @@ module Pod
|
|
14
14
|
If the clone already exists, it will ensure that it is up-to-date.}
|
15
15
|
end
|
16
16
|
|
17
|
+
def self.options
|
18
|
+
[["--push", "Use this option to enable push access once granted"]].concat(super)
|
19
|
+
end
|
20
|
+
|
21
|
+
extend Executable
|
22
|
+
executable :git
|
23
|
+
|
17
24
|
def initialize(argv)
|
25
|
+
@push_option = argv.option('--push')
|
18
26
|
super unless argv.empty?
|
19
27
|
end
|
20
28
|
|
21
|
-
def
|
29
|
+
def dir
|
30
|
+
config.repos_dir + 'master'
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_only_url
|
22
34
|
'git://github.com/CocoaPods/Specs.git'
|
23
35
|
end
|
24
36
|
|
25
|
-
def
|
26
|
-
@
|
37
|
+
def read_write_url
|
38
|
+
'git@github.com:CocoaPods/Specs.git'
|
39
|
+
end
|
40
|
+
|
41
|
+
def url
|
42
|
+
if push?
|
43
|
+
read_write_url
|
44
|
+
else
|
45
|
+
read_only_url
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def origin_url_read_only?
|
50
|
+
read_master_repo_url.chomp == read_only_url
|
27
51
|
end
|
28
52
|
|
29
|
-
def
|
30
|
-
|
53
|
+
def origin_url_push?
|
54
|
+
read_master_repo_url.chomp == read_write_url
|
31
55
|
end
|
32
56
|
|
33
|
-
def
|
34
|
-
|
57
|
+
def push?
|
58
|
+
@push_option || (dir.exist? && origin_url_push?)
|
59
|
+
end
|
60
|
+
|
61
|
+
def read_master_repo_url
|
62
|
+
Dir.chdir(dir) do
|
63
|
+
origin_url = git('config --get remote.origin.url')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_master_repo_url
|
68
|
+
Dir.chdir(dir) do
|
69
|
+
git("remote set-url origin '#{url}'")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_master_repo
|
74
|
+
@command ||= Repo.new(ARGV.new(['add', 'master', url, '0.6'])).run
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_master_repo
|
78
|
+
Repo.new(ARGV.new(['update', 'master'])).run
|
79
|
+
end
|
80
|
+
|
81
|
+
#TODO: remove after rc
|
82
|
+
def set_master_repo_branch
|
83
|
+
Dir.chdir(dir) do
|
84
|
+
git("checkout 0.6")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def run_if_needed
|
89
|
+
run unless dir.exist? && Repo.compatible?('master')
|
35
90
|
end
|
36
91
|
|
37
92
|
def run
|
38
|
-
|
39
|
-
|
40
|
-
|
93
|
+
print_title "Setting up CocoaPods master repo"
|
94
|
+
if dir.exist?
|
95
|
+
set_master_repo_url
|
96
|
+
set_master_repo_branch
|
97
|
+
update_master_repo
|
41
98
|
else
|
42
|
-
|
99
|
+
add_master_repo
|
100
|
+
end
|
101
|
+
# Mainly so the specs run with submodule repos
|
102
|
+
if (dir + '.git/hooks').exist?
|
103
|
+
hook = dir + '.git/hooks/pre-commit'
|
104
|
+
hook.open('w') { |f| f << "#!/bin/sh\nrake lint" }
|
105
|
+
`chmod +x '#{hook}'`
|
43
106
|
end
|
44
|
-
|
45
|
-
hook.open('w') { |f| f << "#!/bin/sh\nrake lint" }
|
46
|
-
`chmod +x '#{hook}'`
|
107
|
+
print_subtitle "Setup completed (#{push? ? "push" : "read-only"} access)"
|
47
108
|
end
|
48
109
|
end
|
49
110
|
end
|
@@ -1,26 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Pod
|
2
4
|
class Command
|
3
5
|
class Spec < Command
|
4
6
|
def self.banner
|
5
|
-
%{Managing PodSpec files:
|
7
|
+
%{Managing PodSpec files:
|
6
8
|
|
7
|
-
$ pod spec create NAME
|
9
|
+
$ pod spec create [ NAME | https://github.com/USER/REPO ]
|
8
10
|
|
9
11
|
Creates a PodSpec, in the current working dir, called `NAME.podspec'.
|
12
|
+
If a GitHub url is passed the spec is prepopulated.
|
10
13
|
|
11
|
-
$ pod spec lint NAME.podspec
|
14
|
+
$ pod spec lint [ NAME.podspec | REPO ]
|
12
15
|
|
13
16
|
Validates `NAME.podspec'. In case `NAME.podspec' is omitted, it defaults
|
14
|
-
to `*.podspec' in the current working dir.
|
17
|
+
to `*.podspec' in the current working dir. If the name of a repo is
|
18
|
+
provided it validates all its specs.}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.options
|
22
|
+
[ ["--quick", "Lint skips checks that would require to donwload and build the spec"],
|
23
|
+
["--only-errors", "Lint validates even if warnings are present"],
|
24
|
+
["--no-clean", "Lint leaves the build directory intact for inspection"] ].concat(super)
|
15
25
|
end
|
16
26
|
|
17
27
|
def initialize(argv)
|
18
|
-
|
19
|
-
|
20
|
-
|
28
|
+
@action = argv.shift_argument
|
29
|
+
if @action == 'create'
|
30
|
+
@name_or_url = argv.shift_argument
|
31
|
+
@url = argv.shift_argument
|
32
|
+
super if @name_or_url.nil?
|
33
|
+
elsif @action == 'lint'
|
34
|
+
@quick = argv.option('--quick')
|
35
|
+
@only_errors = argv.option('--only-errors')
|
36
|
+
@no_clean = argv.option('--no-clean')
|
37
|
+
@repo_or_podspec = argv.shift_argument unless argv.empty?
|
38
|
+
super unless argv.size <= 1
|
39
|
+
else
|
21
40
|
super
|
22
41
|
end
|
23
|
-
|
42
|
+
super unless argv.empty?
|
24
43
|
end
|
25
44
|
|
26
45
|
def run
|
@@ -28,99 +47,564 @@ module Pod
|
|
28
47
|
end
|
29
48
|
|
30
49
|
def create
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# Remove all comments before submitting the spec.
|
39
|
-
#
|
40
|
-
Pod::Spec.new do |s|
|
41
|
-
s.name = '#{@name}'
|
42
|
-
s.version = '1.0.0'
|
43
|
-
s.license = 'MIT'
|
44
|
-
s.summary = 'A short description of #{@name}.'
|
45
|
-
s.homepage = 'http://EXAMPLE/#{@name}'
|
46
|
-
s.author = { '#{author}' => '#{email}' }
|
47
|
-
|
48
|
-
# Specify the location from where the source should be retreived.
|
49
|
-
#
|
50
|
-
s.source = { :git => 'http://EXAMPLE/#{@name}.git', :tag => '1.0.0' }
|
51
|
-
# s.source = { :svn => 'http://EXAMPLE/#{@name}/tags/1.0.0' }
|
52
|
-
# s.source = { :hg => 'http://EXAMPLE/#{@name}', :revision => '1.0.0' }
|
53
|
-
|
54
|
-
s.description = 'An optional longer description of #{@name}.'
|
55
|
-
|
56
|
-
# If this Pod runs only on iOS or OS X, then specify that with one of
|
57
|
-
# these, or none if it runs on both platforms.
|
58
|
-
#
|
59
|
-
# s.platform = :ios
|
60
|
-
# s.platform = :osx
|
61
|
-
|
62
|
-
# A list of file patterns which select the source files that should be
|
63
|
-
# added to the Pods project. If the pattern is a directory then the
|
64
|
-
# path will automatically have '*.{h,m,mm,c,cpp}' appended.
|
65
|
-
#
|
66
|
-
# Alternatively, you can use the FileList class for even more control
|
67
|
-
# over the selected files.
|
68
|
-
# (See http://rake.rubyforge.org/classes/Rake/FileList.html.)
|
69
|
-
#
|
70
|
-
s.source_files = 'Classes', 'Classes/**/*.{h,m}'
|
71
|
-
|
72
|
-
# A list of resources included with the Pod. These are copied into the
|
73
|
-
# target bundle with a build phase script.
|
74
|
-
#
|
75
|
-
# Also allows the use of the FileList class like `source_files does.
|
76
|
-
#
|
77
|
-
# s.resource = "icon.png"
|
78
|
-
# s.resources = "Resources/*.png"
|
79
|
-
|
80
|
-
# A list of paths to remove after installing the Pod without the
|
81
|
-
# `--no-clean' option. These can be examples, docs, and any other type
|
82
|
-
# of files that are not needed to build the Pod.
|
83
|
-
#
|
84
|
-
# *NOTE*: Never remove license and README files.
|
85
|
-
#
|
86
|
-
# Also allows the use of the FileList class like `source_files does.
|
87
|
-
#
|
88
|
-
# s.clean_path = "examples"
|
89
|
-
# s.clean_paths = "examples", "doc"
|
90
|
-
|
91
|
-
# Specify a list of frameworks that the application needs to link
|
92
|
-
# against for this Pod to work.
|
93
|
-
#
|
94
|
-
# s.framework = 'SomeFramework'
|
95
|
-
# s.frameworks = 'SomeFramework', 'AnotherFramework'
|
96
|
-
|
97
|
-
# Specify a list of libraries that the application needs to link
|
98
|
-
# against for this Pod to work.
|
99
|
-
#
|
100
|
-
# s.library = 'iconv'
|
101
|
-
# s.libraries = 'iconv', 'xml2'
|
102
|
-
|
103
|
-
# If this Pod uses ARC, specify it like so.
|
104
|
-
#
|
105
|
-
# s.requires_arc = true
|
106
|
-
|
107
|
-
# If you need to specify any other build settings, add them to the
|
108
|
-
# xcconfig hash.
|
109
|
-
#
|
110
|
-
# s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' }
|
111
|
-
|
112
|
-
# Finally, specify any Pods that this Pod depends on.
|
113
|
-
#
|
114
|
-
# s.dependency 'JSONKit', '~> 1.4'
|
50
|
+
if repo_id_match = (@url || @name_or_url).match(/github.com\/([^\/\.]*\/[^\/\.]*)\.*/)
|
51
|
+
# This is to make sure Faraday doesn't warn the user about the `system_timer` gem missing.
|
52
|
+
old_warn, $-w = $-w, nil
|
53
|
+
begin
|
54
|
+
require 'faraday'
|
55
|
+
ensure
|
56
|
+
$-w = old_warn
|
115
57
|
end
|
116
|
-
|
117
|
-
|
58
|
+
require 'octokit'
|
59
|
+
|
60
|
+
repo_id = repo_id_match[1]
|
61
|
+
data = github_data_for_template(repo_id)
|
62
|
+
data[:name] = @name_or_url if @url
|
63
|
+
puts semantic_versioning_notice(repo_id, data[:name]) if data[:version] == '0.0.1'
|
64
|
+
else
|
65
|
+
data = default_data_for_template(@name_or_url)
|
66
|
+
end
|
67
|
+
spec = spec_template(data)
|
68
|
+
(Pathname.pwd + "#{data[:name]}.podspec").open('w') { |f| f << spec }
|
69
|
+
puts "\nSpecification created at #{data[:name]}.podspec".green
|
118
70
|
end
|
119
71
|
|
120
72
|
def lint
|
121
|
-
|
122
|
-
|
123
|
-
|
73
|
+
puts
|
74
|
+
invalid_count = lint_podspecs
|
75
|
+
count = specs_to_lint.count
|
76
|
+
if invalid_count == 0
|
77
|
+
lint_passed_message = count == 1 ? "#{podspecs_to_lint.first.basename} passed validation" : "All the #{count} specs passed validation"
|
78
|
+
puts lint_passed_message.green << "\n\n" unless config.silent?
|
79
|
+
else
|
80
|
+
raise Informative, count == 1 ? "The spec did not pass validation" : "#{invalid_count} out of #{count} specs failed validation"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def lint_podspecs
|
87
|
+
invalid_count = 0
|
88
|
+
specs_to_lint.each do |spec|
|
89
|
+
# Show immediatly which pod is being processed.
|
90
|
+
print " -> #{spec}\r" unless config.silent? || is_repo?
|
91
|
+
$stdout.flush
|
92
|
+
|
93
|
+
linter = Linter.new(spec)
|
94
|
+
linter.lenient = @only_errors
|
95
|
+
linter.quick = @quick || is_repo?
|
96
|
+
linter.no_clean = @no_clean
|
97
|
+
invalid_count += 1 unless linter.lint
|
98
|
+
|
99
|
+
# This overwrites the previously printed text
|
100
|
+
puts " -> ".send(lint_result_color(linter)) << spec.to_s unless config.silent? || should_skip?(linter)
|
101
|
+
print_messages(spec, 'ERROR', linter.errors)
|
102
|
+
print_messages(spec, 'WARN', linter.warnings)
|
103
|
+
print_messages(spec, 'NOTE', linter.notes)
|
104
|
+
|
105
|
+
puts unless config.silent? || should_skip?(linter)
|
106
|
+
end
|
107
|
+
puts "Analyzed #{specs_to_lint.count} specs in #{podspecs_to_lint.count} podspecs files.\n\n" if is_repo? && !config.silent?
|
108
|
+
invalid_count
|
109
|
+
end
|
110
|
+
|
111
|
+
def lint_result_color(linter)
|
112
|
+
if linter.errors.empty? && linter.warnings.empty?
|
113
|
+
:green
|
114
|
+
elsif linter.errors.empty?
|
115
|
+
:yellow
|
116
|
+
else
|
117
|
+
:red
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def should_skip?(linter)
|
122
|
+
is_repo? && linter.errors.empty? && linter.warnings.empty? && linter.notes.empty?
|
123
|
+
end
|
124
|
+
|
125
|
+
def print_messages(spec, type, messages)
|
126
|
+
return if config.silent?
|
127
|
+
messages.each {|msg| puts " - #{type.ljust(5)} | #{msg}"}
|
128
|
+
end
|
129
|
+
|
130
|
+
def podspecs_to_lint
|
131
|
+
@podspecs_to_lint ||= begin
|
132
|
+
if (is_repo?)
|
133
|
+
files = (config.repos_dir + @repo_or_podspec).glob('**/*.podspec')
|
134
|
+
elsif @repo_or_podspec
|
135
|
+
files = [Pathname.new(@repo_or_podspec)]
|
136
|
+
raise Informative, "Unable to find a spec named #{@repo_or_podspec}" unless files[0].exist? && @repo_or_podspec.include?('.podspec')
|
137
|
+
else
|
138
|
+
files = Pathname.pwd.glob('*.podspec')
|
139
|
+
raise Informative, "No specs found in the current directory" if files.empty?
|
140
|
+
end
|
141
|
+
files
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def specs_to_lint
|
146
|
+
@specs_to_lint ||= begin
|
147
|
+
podspecs_to_lint.map do |podspec|
|
148
|
+
root_spec = Specification.from_file(podspec)
|
149
|
+
# TODO find a way to lint subspecs
|
150
|
+
# root_spec.preferred_dependency ? root_spec.subspec_dependencies : root_spec
|
151
|
+
end.flatten
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def is_repo?
|
156
|
+
@is_repo ||= @repo_or_podspec && (config.repos_dir + @repo_or_podspec).exist? && !@repo_or_podspec.include?('/')
|
157
|
+
end
|
158
|
+
|
159
|
+
# Linter class
|
160
|
+
#
|
161
|
+
class Linter
|
162
|
+
include Config::Mixin
|
163
|
+
|
164
|
+
# TODO: Add check to ensure that attributes inherited by subspecs are not duplicated ?
|
165
|
+
|
166
|
+
attr_accessor :quick, :lenient, :no_clean
|
167
|
+
attr_reader :spec, :file
|
168
|
+
attr_reader :errors, :warnings, :notes
|
169
|
+
|
170
|
+
def initialize(spec)
|
171
|
+
@spec = spec
|
172
|
+
@file = spec.defined_in_file.realpath
|
173
|
+
end
|
174
|
+
|
175
|
+
# Takes an array of podspec files and lints them all
|
176
|
+
#
|
177
|
+
# It returns true if the spec passed validation
|
178
|
+
#
|
179
|
+
def lint
|
180
|
+
@platform_errors, @platform_warnings, @platform_notes = {}, {}, {}
|
181
|
+
|
182
|
+
platforms = @spec.available_platforms
|
183
|
+
platforms.each do |platform|
|
184
|
+
@platform_errors[platform], @platform_warnings[platform], @platform_notes[platform] = [], [], []
|
185
|
+
|
186
|
+
@spec.activate_platform(platform)
|
187
|
+
@platform = platform
|
188
|
+
puts "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed if config.verbose? && !@quick
|
189
|
+
|
190
|
+
# Skip validation if there are errors in the podspec as it would result in a crash
|
191
|
+
if !podspec_errors.empty?
|
192
|
+
@platform_errors[platform] += podspec_errors
|
193
|
+
@platform_notes[platform] << "#{platform.name} [!] Fatal errors found skipping the rest of the validation"
|
194
|
+
else
|
195
|
+
@platform_warnings[platform] += podspec_warnings + deprecation_warnings
|
196
|
+
@platform_notes[platform] += podspec_notes
|
197
|
+
peform_extensive_analysis unless quick
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Get common messages
|
202
|
+
@errors = @platform_errors.values.reduce(:&) || []
|
203
|
+
@warnings = @platform_warnings.values.reduce(:&) || []
|
204
|
+
@notes = @platform_notes.values.reduce(:&) || []
|
205
|
+
|
206
|
+
platforms.each do |platform|
|
207
|
+
# Mark platform specific messages
|
208
|
+
@errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
|
209
|
+
@warnings += (@platform_warnings[platform] - @warnings).map {|m| "[#{platform}] #{m}"}
|
210
|
+
@notes += (@platform_notes[platform] - @notes).map {|m| "[#{platform}] #{m}"}
|
211
|
+
end
|
212
|
+
|
213
|
+
valid?
|
214
|
+
end
|
215
|
+
|
216
|
+
def valid?
|
217
|
+
lenient ? errors.empty? : ( errors.empty? && warnings.empty? )
|
218
|
+
end
|
219
|
+
|
220
|
+
# Performs platform specific analysis.
|
221
|
+
# It requires to download the source at each iteration
|
222
|
+
#
|
223
|
+
def peform_extensive_analysis
|
224
|
+
set_up_lint_environment
|
225
|
+
install_pod
|
226
|
+
puts "Building with xcodebuild.\n".yellow if config.verbose?
|
227
|
+
# treat xcodebuild warnings as notes because the spec maintainer might not be the author of the library
|
228
|
+
xcodebuild_output.each { |msg| ( msg.include?('error') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
|
229
|
+
@platform_errors[@platform] += file_patterns_errors
|
230
|
+
@platform_warnings[@platform] += file_patterns_warnings
|
231
|
+
tear_down_lint_environment
|
232
|
+
end
|
233
|
+
|
234
|
+
def install_pod
|
235
|
+
podfile = podfile_from_spec
|
236
|
+
config.verbose
|
237
|
+
installer = Installer.new(podfile)
|
238
|
+
installer.install!
|
239
|
+
@pod = installer.pods.find { |pod| pod.top_specification == @spec }
|
240
|
+
config.silent
|
241
|
+
end
|
242
|
+
|
243
|
+
def podfile_from_spec
|
244
|
+
name = spec.name
|
245
|
+
podspec = file.realpath.to_s
|
246
|
+
platform_sym = @platform.to_sym
|
247
|
+
podfile = Pod::Podfile.new do
|
248
|
+
platform(platform_sym)
|
249
|
+
dependency name, :podspec => podspec
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def set_up_lint_environment
|
254
|
+
tmp_dir.rmtree if tmp_dir.exist?
|
255
|
+
tmp_dir.mkpath
|
256
|
+
@original_config = Config.instance.clone
|
257
|
+
config.project_root = tmp_dir
|
258
|
+
config.project_pods_root = tmp_dir + 'Pods'
|
259
|
+
config.silent = !config.verbose
|
260
|
+
config.integrate_targets = false
|
261
|
+
config.generate_docs = false
|
262
|
+
end
|
263
|
+
|
264
|
+
def tear_down_lint_environment
|
265
|
+
tmp_dir.rmtree unless no_clean
|
266
|
+
Config.instance = @original_config
|
267
|
+
end
|
268
|
+
|
269
|
+
def tmp_dir
|
270
|
+
Pathname.new('/tmp/CocoaPods/Lint')
|
271
|
+
end
|
272
|
+
|
273
|
+
def pod_dir
|
274
|
+
tmp_dir + 'Pods' + spec.name
|
275
|
+
end
|
276
|
+
|
277
|
+
# @return [Array<String>] List of the fatal defects detected in a podspec
|
278
|
+
def podspec_errors
|
279
|
+
messages = []
|
280
|
+
messages << "The name of the spec should match the name of the file" unless names_match?
|
281
|
+
messages << "Unrecognized platfrom" unless platform_valid?
|
282
|
+
messages << "Missing name" unless spec.name
|
283
|
+
messages << "Missing version" unless spec.version
|
284
|
+
messages << "Missing summary" unless spec.summary
|
285
|
+
messages << "Missing homepage" unless spec.homepage
|
286
|
+
messages << "Missing author(s)" unless spec.authors
|
287
|
+
messages << "Missing source" unless spec.source
|
288
|
+
|
289
|
+
# attributes with multiplatform values
|
290
|
+
return messages unless platform_valid?
|
291
|
+
messages << "Missing source_files" if spec.source_files.empty? && spec.subspecs.empty? && spec.resources.empty?
|
292
|
+
messages += paths_starting_with_a_slash_errors
|
293
|
+
messages
|
294
|
+
end
|
295
|
+
|
296
|
+
def names_match?
|
297
|
+
return true unless spec.name
|
298
|
+
root_name = spec.name.match(/[^\/]*/)[0]
|
299
|
+
file.basename.to_s == root_name + '.podspec'
|
300
|
+
end
|
301
|
+
|
302
|
+
def platform_valid?
|
303
|
+
!spec.platform || [:ios, :osx].include?(spec.platform.name)
|
304
|
+
end
|
305
|
+
|
306
|
+
def paths_starting_with_a_slash_errors
|
307
|
+
messages = []
|
308
|
+
%w[source_files resources clean_paths].each do |accessor|
|
309
|
+
patterns = spec.send(accessor.to_sym)
|
310
|
+
# Some values are multiplaform
|
311
|
+
patterns = patterns.is_a?(Hash) ? patterns.values.flatten(1) : patterns
|
312
|
+
patterns.each do |pattern|
|
313
|
+
# Skip Filelist that would otherwise be resolved from the working directory resulting
|
314
|
+
# in a potentially very expensi operation
|
315
|
+
next if pattern.is_a?(FileList)
|
316
|
+
invalid = pattern.is_a?(Array) ? pattern.any? { |path| path.start_with?('/') } : pattern.start_with?('/')
|
317
|
+
if invalid
|
318
|
+
messages << "Paths cannot start with a slash (#{accessor})"
|
319
|
+
break
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
messages
|
324
|
+
end
|
325
|
+
|
326
|
+
# @return [Array<String>] List of the **non** fatal defects detected in a podspec
|
327
|
+
def podspec_warnings
|
328
|
+
license = @spec.license || {}
|
329
|
+
source = @spec.source || {}
|
330
|
+
text = @file.read
|
331
|
+
messages = []
|
332
|
+
messages << "Missing license type" unless license[:type]
|
333
|
+
messages << "Sample license type" if license[:type] && license[:type] =~ /\(example\)/
|
334
|
+
messages << "The summary is not meaningful" if spec.summary =~ /A short description of/
|
335
|
+
messages << "The description is not meaningful" if spec.description && spec.description =~ /An optional longer description of/
|
336
|
+
messages << "The summary should end with a dot" if @spec.summary !~ /.*\./
|
337
|
+
messages << "The description should end with a dot" if @spec.description !~ /.*\./ && @spec.description != @spec.summary
|
338
|
+
messages << "Git sources should specify either a tag or a commit" if source[:git] && !source[:commit] && !source[:tag]
|
339
|
+
messages << "Github repositories should end in `.git'" if github_source? && source[:git] !~ /.*\.git/
|
340
|
+
messages << "Github repositories should use `https' link" if github_source? && source[:git] !~ /https:\/\/github.com/
|
341
|
+
messages << "Comments must be deleted" if text =~ /^\w*#\n\w*#/ # allow a single line comment as it is generally used in subspecs
|
342
|
+
messages
|
343
|
+
end
|
344
|
+
|
345
|
+
def github_source?
|
346
|
+
@spec.source && @spec.source[:git] =~ /github.com/
|
347
|
+
end
|
348
|
+
|
349
|
+
# @return [Array<String>] List of the comments detected in the podspec
|
350
|
+
def podspec_notes
|
351
|
+
text = @file.read
|
352
|
+
deprecations = []
|
353
|
+
deprecations << "The `post_install' hook is reserved for edge cases" if text. =~ /post_install/
|
354
|
+
deprecations
|
355
|
+
end
|
356
|
+
|
357
|
+
# It reads a podspec file and checks for strings corresponding
|
358
|
+
# to features that are or will be deprecated
|
359
|
+
#
|
360
|
+
# @return [Array<String>]
|
361
|
+
#
|
362
|
+
def deprecation_warnings
|
363
|
+
text = @file.read
|
364
|
+
deprecations = []
|
365
|
+
deprecations << "`config.ios?' and `config.osx?' are deprecated" if text. =~ /config\..?os.?/
|
366
|
+
deprecations << "clean_paths are deprecated and ignored (use preserve_paths)" if text. =~ /clean_paths/
|
367
|
+
deprecations
|
368
|
+
end
|
369
|
+
|
370
|
+
# It creates a podfile in memory and builds a library containing
|
371
|
+
# the pod for all available platfroms with xcodebuild.
|
372
|
+
#
|
373
|
+
# @return [Array<String>]
|
374
|
+
#
|
375
|
+
def xcodebuild_output
|
376
|
+
return [] if `which xcodebuild`.strip.empty?
|
377
|
+
messages = []
|
378
|
+
output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
|
379
|
+
clean_output = process_xcode_build_output(output)
|
380
|
+
messages += clean_output
|
381
|
+
puts(output) if config.verbose?
|
382
|
+
messages
|
383
|
+
end
|
384
|
+
|
385
|
+
def process_xcode_build_output(output)
|
386
|
+
output_by_line = output.split("\n")
|
387
|
+
selected_lines = output_by_line.select do |l|
|
388
|
+
l.include?('error:') && (l !~ /errors? generated\./) \
|
389
|
+
|| l.include?('warning:') && (l !~ /warnings? generated\./)\
|
390
|
+
|| l.include?('note:')
|
391
|
+
end
|
392
|
+
selected_lines.map do |l|
|
393
|
+
new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
|
394
|
+
new.gsub!(/^ */,' ') # Remove indentation
|
395
|
+
"XCODEBUILD > " << new # Mark
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# It checks that every file pattern specified in a spec yields
|
400
|
+
# at least one file. It requires the pods to be alredy present
|
401
|
+
# in the current working directory under Pods/spec.name.
|
402
|
+
#
|
403
|
+
# @return [Array<String>]
|
404
|
+
#
|
405
|
+
def file_patterns_errors
|
406
|
+
messages = []
|
407
|
+
messages << "The sources did not match any file" if !@spec.source_files.empty? && @pod.source_files.empty?
|
408
|
+
messages << "The resources did not match any file" if !@spec.resources.empty? && @pod.resources.empty?
|
409
|
+
messages << "The preserve_paths did not match any file" if !@spec.preserve_paths.empty? && @pod.preserve_paths.empty?
|
410
|
+
messages << "The exclude_header_search_paths did not match any file" if !@spec.exclude_header_search_paths.empty? && @pod.headers_excluded_from_search_paths.empty?
|
411
|
+
messages
|
412
|
+
end
|
413
|
+
|
414
|
+
def file_patterns_warnings
|
415
|
+
messages = []
|
416
|
+
messages << "Unable to find a license file" unless @pod.license_file
|
417
|
+
messages
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
# Templates and github information retrival for spec create
|
422
|
+
|
423
|
+
def default_data_for_template(name)
|
424
|
+
data = {}
|
425
|
+
data[:name] = name
|
426
|
+
data[:version] = '0.0.1'
|
427
|
+
data[:summary] = "A short description of #{name}."
|
428
|
+
data[:homepage] = "http://EXAMPLE/#{name}"
|
429
|
+
data[:author_name] = `git config --get user.name`.strip
|
430
|
+
data[:author_email] = `git config --get user.email`.strip
|
431
|
+
data[:source_url] = "http://EXAMPLE/#{name}.git"
|
432
|
+
data[:ref_type] = ':tag'
|
433
|
+
data[:ref] = '0.0.1'
|
434
|
+
data
|
435
|
+
end
|
436
|
+
|
437
|
+
def github_data_for_template(repo_id)
|
438
|
+
repo = Octokit.repo(repo_id)
|
439
|
+
user = Octokit.user(repo['owner']['login'])
|
440
|
+
data = {}
|
441
|
+
|
442
|
+
data[:name] = repo['name']
|
443
|
+
data[:summary] = repo['description'].gsub(/["]/, '\"')
|
444
|
+
data[:homepage] = (repo['homepage'] && !repo['homepage'].empty? ) ? repo['homepage'] : repo['html_url']
|
445
|
+
data[:author_name] = user['name'] || user['login']
|
446
|
+
data[:author_email] = user['email'] || 'email@address.com'
|
447
|
+
data[:source_url] = repo['clone_url']
|
448
|
+
|
449
|
+
data.merge suggested_ref_and_version(repo)
|
450
|
+
end
|
451
|
+
|
452
|
+
def suggested_ref_and_version(repo)
|
453
|
+
tags = Octokit.tags(:username => repo['owner']['login'], :repo => repo['name']).map {|tag| tag["name"]}
|
454
|
+
versions_tags = {}
|
455
|
+
tags.each do |tag|
|
456
|
+
clean_tag = tag.gsub(/^v(er)? ?/,'')
|
457
|
+
versions_tags[Gem::Version.new(clean_tag)] = tag if Gem::Version.correct?(clean_tag)
|
458
|
+
end
|
459
|
+
version = versions_tags.keys.sort.last || '0.0.1'
|
460
|
+
data = {:version => version}
|
461
|
+
if version == '0.0.1'
|
462
|
+
branches = Octokit.branches(:username => repo['owner']['login'], :repo => repo['name'])
|
463
|
+
master_name = repo['master_branch'] || 'master'
|
464
|
+
master = branches.select {|branch| branch['name'] == master_name }.first
|
465
|
+
data[:ref_type] = ':commit'
|
466
|
+
data[:ref] = master['commit']['sha']
|
467
|
+
else
|
468
|
+
data[:ref_type] = ':tag'
|
469
|
+
data[:ref] = versions_tags[version]
|
470
|
+
end
|
471
|
+
data
|
472
|
+
end
|
473
|
+
|
474
|
+
def spec_template(data)
|
475
|
+
return <<-SPEC
|
476
|
+
#
|
477
|
+
# Be sure to run `pod spec lint #{data[:name]}.podspec' to ensure this is a
|
478
|
+
# valid spec.
|
479
|
+
#
|
480
|
+
# Remove all comments before submitting the spec. Optional attributes are commented.
|
481
|
+
#
|
482
|
+
# For details see: https://github.com/CocoaPods/CocoaPods/wiki/The-podspec-format
|
483
|
+
#
|
484
|
+
Pod::Spec.new do |s|
|
485
|
+
s.name = "#{data[:name]}"
|
486
|
+
s.version = "#{data[:version]}"
|
487
|
+
s.summary = "#{data[:summary]}"
|
488
|
+
# s.description = 'An optional longer description of #{data[:name]}.'
|
489
|
+
s.homepage = "#{data[:homepage]}"
|
490
|
+
|
491
|
+
# Specify the license type. CocoaPods detects automatically the license file if it is named
|
492
|
+
# `LICENSE*', however if the name is different, specify it.
|
493
|
+
# Only if no dedicated file is available include the full text of the license.
|
494
|
+
#
|
495
|
+
s.license = 'MIT (example)'
|
496
|
+
# s.license = { :type => 'MIT', :file => 'LICENSE', :text => 'Permission is hereby granted ...' }
|
497
|
+
|
498
|
+
# Specify the authors of the library, with email addresses. You can often find
|
499
|
+
# the email addresses of the authors by using the SCM log. E.g. $ git log
|
500
|
+
#
|
501
|
+
s.author = { "#{data[:author_name]}" => "#{data[:author_email]}" }
|
502
|
+
# s.authors = { "#{data[:author_name]}" => "#{data[:author_email]}", "other author" => "and email address" }
|
503
|
+
#
|
504
|
+
# If absolutely no email addresses are available, then you can use this form instead.
|
505
|
+
#
|
506
|
+
# s.author = '#{data[:author_name]}', 'other author'
|
507
|
+
|
508
|
+
# Specify the location from where the source should be retreived.
|
509
|
+
#
|
510
|
+
s.source = { :git => "#{data[:source_url]}", #{data[:ref_type]} => "#{data[:ref]}" }
|
511
|
+
# s.source = { :svn => 'http://EXAMPLE/#{data[:name]}/tags/1.0.0' }
|
512
|
+
# s.source = { :hg => 'http://EXAMPLE/#{data[:name]}', :revision => '1.0.0' }
|
513
|
+
|
514
|
+
# If this Pod runs only on iOS or OS X, then specify the platform and
|
515
|
+
# the deployment target.
|
516
|
+
#
|
517
|
+
# s.platform = :ios, '5.0'
|
518
|
+
# s.platform = :ios
|
519
|
+
|
520
|
+
# ――― MULTI-PLATFORM VALUES ――――――――――――――――――――――――――――――――――――――――――――――――― #
|
521
|
+
|
522
|
+
# If this Pod runs on both platforms, then specify the deployment
|
523
|
+
# targets.
|
524
|
+
#
|
525
|
+
# s.ios.deployment_target = '5.0'
|
526
|
+
# s.osx.deployment_target = '10.7'
|
527
|
+
|
528
|
+
# A list of file patterns which select the source files that should be
|
529
|
+
# added to the Pods project. If the pattern is a directory then the
|
530
|
+
# path will automatically have '*.{h,m,mm,c,cpp}' appended.
|
531
|
+
#
|
532
|
+
# Alternatively, you can use the FileList class for even more control
|
533
|
+
# over the selected files.
|
534
|
+
# (See http://rake.rubyforge.org/classes/Rake/FileList.html.)
|
535
|
+
#
|
536
|
+
s.source_files = 'Classes', 'Classes/**/*.{h,m}'
|
537
|
+
|
538
|
+
# A list of resources included with the Pod. These are copied into the
|
539
|
+
# target bundle with a build phase script.
|
540
|
+
#
|
541
|
+
# Also allows the use of the FileList class like `source_files does.
|
542
|
+
#
|
543
|
+
# s.resource = "icon.png"
|
544
|
+
# s.resources = "Resources/*.png"
|
545
|
+
|
546
|
+
# A list of paths to preserve after installing the Pod.
|
547
|
+
# CocoaPods cleans by default any file that is not used.
|
548
|
+
# Also allows the use of the FileList class like `source_files does.
|
549
|
+
#
|
550
|
+
# s.preserve_paths = "examples", "doc"
|
551
|
+
|
552
|
+
# Specify a list of frameworks that the application needs to link
|
553
|
+
# against for this Pod to work.
|
554
|
+
#
|
555
|
+
# s.framework = 'SomeFramework'
|
556
|
+
# s.frameworks = 'SomeFramework', 'AnotherFramework'
|
557
|
+
|
558
|
+
# Specify a list of libraries that the application needs to link
|
559
|
+
# against for this Pod to work.
|
560
|
+
#
|
561
|
+
# s.library = 'iconv'
|
562
|
+
# s.libraries = 'iconv', 'xml2'
|
563
|
+
|
564
|
+
# If this Pod uses ARC, specify it like so.
|
565
|
+
#
|
566
|
+
# s.requires_arc = true
|
567
|
+
|
568
|
+
# If you need to specify any other build settings, add them to the
|
569
|
+
# xcconfig hash.
|
570
|
+
#
|
571
|
+
# s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' }
|
572
|
+
|
573
|
+
# Finally, specify any Pods that this Pod depends on.
|
574
|
+
#
|
575
|
+
# s.dependency 'JSONKit', '~> 1.4'
|
576
|
+
end
|
577
|
+
SPEC
|
578
|
+
end
|
579
|
+
|
580
|
+
def semantic_versioning_notice(repo_id, repo)
|
581
|
+
return <<-EOS
|
582
|
+
|
583
|
+
#{'――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
|
584
|
+
|
585
|
+
I’ve recently added [#{repo}](https://github.com/CocoaPods/Specs/tree/master/#{repo}) to the [CocoaPods](https://github.com/CocoaPods/CocoaPods) package manager repo.
|
586
|
+
|
587
|
+
CocoaPods is a tool for managing dependencies for OSX and iOS Xcode projects and provides a central repository for iOS/OSX libraries. This makes adding libraries to a project and updating them extremely easy and it will help users to resolve dependencies of the libraries they use.
|
588
|
+
|
589
|
+
However, #{repo} doesn't have any version tags. I’ve added the current HEAD as version 0.0.1, but a version tag will make dependency resolution much easier.
|
590
|
+
|
591
|
+
[Semantic version](http://semver.org) tags (instead of plain commit hashes/revisions) allow for [resolution of cross-dependencies](https://github.com/CocoaPods/Specs/wiki/Cross-dependencies-resolution-example).
|
592
|
+
|
593
|
+
In case you didn’t know this yet; you can tag the current HEAD as, for instance, version 1.0.0, like so:
|
594
|
+
|
595
|
+
```
|
596
|
+
$ git tag -a 1.0.0 -m "Tag release 1.0.0"
|
597
|
+
$ git push --tags
|
598
|
+
```
|
599
|
+
|
600
|
+
#{'――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
|
601
|
+
|
602
|
+
#{'[!] This repo does not appear to have semantic version tags.'.yellow}
|
603
|
+
|
604
|
+
After commiting the specification, consider opening a ticket with the template displayed above:
|
605
|
+
- link: https://github.com/#{repo_id}/issues/new
|
606
|
+
- title: Please add semantic version tags
|
607
|
+
EOS
|
124
608
|
end
|
125
609
|
end
|
126
610
|
end
|