ecb 0.0.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.
@@ -0,0 +1,38 @@
1
+
2
+ #
3
+ # Greg Seitz
4
+ #
5
+ # Copyright 2013, eBay Inc.
6
+ # All rights reserved.
7
+ # http://www.ebay.com
8
+ #
9
+
10
+ module Commands
11
+ class RandomBranch
12
+ # holds the options that were passed
13
+ # you can set any initial defaults here
14
+ def options
15
+ @options ||= {
16
+ }
17
+ end
18
+
19
+ # required options
20
+ def required_options
21
+ @required_options ||= Set.new [
22
+ ]
23
+ end
24
+
25
+ def register(opts, global_options)
26
+ opts.banner = "Usage: randombranch"
27
+ opts.description = "Creates a random name to be used with a build branch."
28
+
29
+ end
30
+
31
+ def run(global_options)
32
+ randomstring = SecureRandom.hex(4)
33
+ randomstring << "_buildbranch"
34
+ puts randomstring
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ #
2
+ # Greg Seitz
3
+ #
4
+ # Copyright 2013, eBay Inc.
5
+ # All rights reserved.
6
+ # http://www.ebay.com
7
+ #
8
+ module Commands
9
+ class RemoveMergedBranches
10
+
11
+ # holds the options that were passed
12
+ # you can set any initial defaults here
13
+ def options
14
+ @options ||= {
15
+ }
16
+ end
17
+
18
+ # required options
19
+ def required_options
20
+ @required_options ||= Set.new [
21
+ ]
22
+ end
23
+
24
+ def register(opts, global_options)
25
+ opts.banner = "Usage: status"
26
+ opts.description = "Shows the git status of all repos in the config."
27
+ opts.on('-n', "--dry-run", "Perform a dry run.") do |v|
28
+ options[:dry_run] = true
29
+ end
30
+
31
+ end
32
+
33
+ def run(global_options)
34
+
35
+ # see if we can open the config file - we append the .config suffix
36
+ # the file is expected to be in JSON format
37
+
38
+ # determine config_name by extracting parent of our directory
39
+ info = EbmSharedLib.get_config_from_top_dir
40
+ dry_run = !!options[:dry_run] ? "--dry-run" : ""
41
+
42
+ # Back up to version parent dir. This directory contains the top level repos.
43
+ # top_dir = File.expand_path("#{Dir.pwd}/..")
44
+ top_dir = Dir.pwd
45
+
46
+ repos = info[:repos]
47
+ repos.each do |repo|
48
+ if repo[:create_dev_branch]
49
+ repo_name = EbmSharedLib.get_repo_name(repo[:git_path])
50
+ repo_path = "#{top_dir}/#{repo_name}"
51
+ File.open("#{repo_name}/merged_branches.txt").each do |line|
52
+ cmd = "git push #{dry_run} origin :#{line}"
53
+ if EbmSharedLib::CL.do_cmd_result(cmd, repo_path) != 0
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,107 @@
1
+ #
2
+ # Greg Seitz
3
+ #
4
+ # Copyright 2013, eBay Inc.
5
+ # All rights reserved.
6
+ # http://www.ebay.com
7
+ #
8
+ module Commands
9
+ class Tag
10
+
11
+ # holds the options that were passed
12
+ # you can set any initial defaults here
13
+ def options
14
+ @options ||= {
15
+ }
16
+ end
17
+
18
+ # required options
19
+ def required_options
20
+ @required_options ||= Set.new [
21
+ :tag,
22
+ ]
23
+ end
24
+
25
+ def register(opts, global_options)
26
+ opts.banner = "Usage: tag [options]"
27
+ opts.description = "Apply or delete a tag for all taggable repos in a config"
28
+
29
+ opts.on('-t', "--tag name", "Required - Name of the tag.") do |v|
30
+ options[:tag] = v
31
+ end
32
+
33
+ opts.on('-d', "--delete", "When set causes tag to be deleted.") do |v|
34
+ options[:delete] = true
35
+ end
36
+
37
+ opts.on("--commit-and-push", "Commit any local changes and then push the remote - should only be used by the build system.") do |v|
38
+ options[:commit_and_push] = true
39
+ end
40
+
41
+ end
42
+
43
+ def run(global_options)
44
+
45
+ # see if we can open the config file - we append the .config suffix
46
+ # the file is expected to be in JSON format
47
+ tag = options[:tag]
48
+ delete_tag = !!options[:delete]
49
+ commit_and_push = !!options[:commit_and_push] # the !! forces conversion to a boolean
50
+
51
+ if (commit_and_push && delete_tag)
52
+ raise "You cannot use --commit-and-push with --delete."
53
+ end
54
+
55
+ info = EbmSharedLib.get_config_from_top_dir
56
+
57
+ # other than prepare, any commands that work across the repos expect you to start
58
+ # in the containing directory (i.e. if your config is named iphone_3.1, you are expected
59
+ # to be in that directory when you run the command).
60
+ top_dir = "#{Dir.pwd}"
61
+
62
+ repos = info[:repos]
63
+ repos.each do |repo|
64
+ if repo[:taggable]
65
+ repo_name = EbmSharedLib.get_repo_name(repo[:git_path])
66
+ repo_path = "#{top_dir}/#{repo_name}"
67
+ if delete_tag
68
+ cmd = "git tag -d #{tag}"
69
+ if EbmSharedLib::CL.do_cmd_result(cmd, repo_path) != 0
70
+ raise "Deleting tag failed for #{repo_name}."
71
+ end
72
+ cmd = "git push origin :refs/tags/#{tag}"
73
+ if EbmSharedLib::CL.do_cmd_result(cmd, repo_path) != 0
74
+ raise "Deleting tag failed failed for #{repo_name}."
75
+ end
76
+ else
77
+ if commit_and_push
78
+ # they want to commit whatever has changed and push to current remote
79
+ # first grab the current branch name
80
+ branch = EbmSharedLib.get_current_branch(repo, repo_path)
81
+
82
+ # we have the local branch so now commit and push to the remote branch
83
+ cmd = "git commit -am \"Committing changes via ebm tag --commit_and_push for tag #{tag}.\""
84
+ EbmSharedLib::CL.do_cmd_result(cmd, repo_path) # ignore any error
85
+
86
+ # now push what we just committed
87
+ cmd = "git push origin #{branch}"
88
+ if EbmSharedLib::CL.do_cmd_result(cmd, repo_path) != 0
89
+ raise "Push failed failed for #{repo_name}."
90
+ end
91
+ end
92
+ cmd = "git tag #{tag}"
93
+ if EbmSharedLib::CL.do_cmd_result(cmd, repo_path) != 0
94
+ raise "Tagging operation failed for #{repo_name}. Make sure you are in the top level #{config_name} directory."
95
+ end
96
+ cmd = "git push origin refs/tags/#{tag}"
97
+ if EbmSharedLib::CL.do_cmd_result(cmd, repo_path) != 0
98
+ raise "Tagging operation failed for #{repo_name}. Make sure you are in the top level #{config_name} directory."
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,62 @@
1
+ #
2
+ # Greg Seitz
3
+ #
4
+ # Copyright 2013, eBay Inc.
5
+ # All rights reserved.
6
+ # http://www.ebay.com
7
+ #
8
+
9
+ module Commands
10
+ class UpdatePlist
11
+
12
+ # holds the options that were passed
13
+ # you can set any initial defaults here
14
+ def options
15
+ @options ||= {
16
+ }
17
+ end
18
+
19
+ # required options
20
+ def required_options
21
+ @required_options ||= Set.new [
22
+ :plist,
23
+ ]
24
+ end
25
+
26
+ def register(opts, global_options)
27
+ opts.banner = "Usage: update_plist"
28
+ opts.description = "Updates the specified plist."
29
+ opts.on('-p', "--plist filename", "Required - Plist file to update") do |v|
30
+ options[:plist] = v
31
+ end
32
+ opts.on('-b', "--build number", "Optional - build number used to update plist") do |v|
33
+ options[:build] = v
34
+ end
35
+ opts.on('-i', "--identifier bundler identifier", "Optional - Bundle Name to be set") do |v|
36
+ options[:identifier] = v
37
+ end
38
+ opts.on('-v', "--verbose", "Display passed data") do |v|
39
+ options[:verbose] = true
40
+ end
41
+ end
42
+
43
+ def run(global_options)
44
+ build = options[:build]
45
+ plist = options[:plist]
46
+ identifier = options[:identifier]
47
+ verbose = options[:verbose]
48
+ puts ". plist....: " + plist if verbose
49
+ plist_data = Plist::parse_xml(plist)
50
+ if (build) then
51
+ buildindentifer = "_build" + build
52
+ plist_data["BuildIdentifier"] = buildindentifer
53
+ puts ". buildindentifer....: " + buildindentifer if verbose
54
+ end
55
+ if (identifier) then
56
+ plist_data["CFBundleIdentifier"] = identifier
57
+ puts ". Indentifer....: " + identifier if verbose
58
+ end
59
+ plist_data.save_plist(plist)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # Greg Seitz
3
+ #
4
+ # Copyright 2013, eBay Inc.
5
+ # All rights reserved.
6
+ # http://www.ebay.com
7
+ #
8
+ module Commands
9
+ class Version
10
+
11
+ # holds the options that were passed
12
+ # you can set any initial defaults here
13
+ def options
14
+ @options ||= {
15
+ }
16
+ end
17
+
18
+ # required options
19
+ def required_options
20
+ @required_options ||= Set.new [
21
+ ]
22
+ end
23
+
24
+ def register(opts, global_options)
25
+ opts.banner = "Usage: version"
26
+ opts.description = "Returns the version only."
27
+ end
28
+
29
+ def run(global_options)
30
+ puts "#{Info.version}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # Greg Seitz
3
+ #
4
+ # Copyright 2013, eBay Inc.
5
+ # All rights reserved.
6
+ # http://www.ebay.com
7
+ #
8
+ class Hash
9
+ # A method to recursively symbolize all keys in the Hash class
10
+ # lets you access keys via symbols such as :the_key instead of
11
+ # strings such as "the_key"
12
+ #
13
+ def recursively_symbolize_keys!
14
+ self.symbolize_keys!
15
+ self.values.each do |v|
16
+ if v.is_a? Hash
17
+ v.recursively_symbolize_keys!
18
+ elsif v.is_a? Array
19
+ v.each do |a|
20
+ a.recursively_symbolize_keys! if a.is_a? Hash
21
+ end
22
+ end
23
+ end
24
+ self
25
+ end
26
+
27
+ def symbolize_keys!
28
+ keys.each do |key|
29
+ self[(key.to_sym rescue key) || key] = delete(key)
30
+ end
31
+ self
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Greg Seitz
3
+ #
4
+ # Copyright 2013, eBay Inc.
5
+ # All rights reserved.
6
+ # http://www.ebay.com
7
+ #
8
+ module EbmSharedLib
9
+ # tracks the global and per command options but lets you
10
+ # fetch values without regard to which one. The command
11
+ # is checked before the global
12
+ class Options
13
+ def self.global_options=(options)
14
+ @@global_options = options
15
+ end
16
+
17
+ def self.cmd_options=(options)
18
+ @@cmd_options = options
19
+ end
20
+
21
+ def self.cmd_options
22
+ @@cmd_options ||= {}
23
+ end
24
+
25
+ def self.global_options
26
+ @@global_options ||= {}
27
+ end
28
+
29
+ def self.get(option)
30
+ v = cmd_options[option]
31
+ return v if !v.nil?
32
+
33
+ v = global_options[option]
34
+ return v
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,234 @@
1
+ #
2
+ # Greg Seitz
3
+ #
4
+ # Copyright 2013, eBay Inc.
5
+ # All rights reserved.
6
+ # http://www.ebay.com
7
+ #
8
+ module EbmSharedLib
9
+ ROOT_PATH = File.expand_path("~/.ebm")
10
+ CONFIG_DIR = "build_configs"
11
+ CONFIG_SUFFIX = ".config"
12
+ SETTINGS_FILE = ".ebm-settings.json"
13
+ REPO_COMMAND_DETAILS = "Remote git repository for initial configs file download [mobi,corp,stash,<git url>]"
14
+
15
+ # the key is the shortcut used for the config repos
16
+ CONFIG_REPOS = {
17
+ "stash" => "https://mobiebay.com/git/scm/ebmconfigs/build_configs.git",
18
+ "mobi" => "git@github.mobiebay.com:eBayMobile/build_configs.git",
19
+ }
20
+ CONFIG_REPOS["default"] = CONFIG_REPOS["mobi"]
21
+
22
+ class CL
23
+ # run a command line and echo to console
24
+ # returns 0 on no error, otherwise the error number
25
+ def self.do_cmd(cmd, dir=nil)
26
+ exit_code = 0
27
+
28
+ puts cmd
29
+ begin
30
+ if dir.nil? == false
31
+ Dir.chdir(dir){
32
+ Kernel.system(cmd)
33
+ }
34
+ else
35
+ Kernel.system(cmd)
36
+ end
37
+ rescue Exception => ex
38
+ exit_code = 100 #indicate an error, probably due to invalid dir
39
+ end
40
+
41
+ exit_code = $?.exitstatus if exit_code == 0
42
+
43
+ exit_code
44
+ end
45
+
46
+ # same as above but returns the result code
47
+ # 0 is success, anything else is an error code
48
+ def self.do_cmd_result(cmd, dir=nil)
49
+ do_cmd(cmd, dir)
50
+ end
51
+
52
+ # run a command and return the output string
53
+ def self.do_cmd_output(cmd, dir=nil)
54
+ exit_code = 0
55
+ result = ""
56
+
57
+ puts cmd
58
+ if dir.nil? == false
59
+ Dir.chdir(dir){
60
+ result = `#{cmd}`
61
+ }
62
+ else
63
+ result = `#{cmd}`
64
+ end
65
+
66
+ result
67
+ end
68
+
69
+ # run a command and don't consider it an error
70
+ # if output contains string
71
+ def self.do_cmd_ignore_str(cmd, ok_match, dir=nil)
72
+ msg = EbmSharedLib::CL.do_cmd_output(cmd, dir)
73
+ puts msg
74
+ exit_code = $?.exitstatus
75
+ if exit_code != 0
76
+ line = msg.split("\n").last # last line
77
+ # ugly hack to not show as error when nothing to commit
78
+ if !line.nil? && line.include?(ok_match)
79
+ exit_code = 999
80
+ end
81
+ end
82
+ exit_code
83
+ end
84
+
85
+ end
86
+
87
+ def self.get_config_repo_url(config_repo)
88
+ # convert to lowercase for comparison below if non nil
89
+ repo_alias = config_repo.nil? ? "default" : config_repo.downcase
90
+
91
+ # loop up matching repo
92
+ found_repo = CONFIG_REPOS[repo_alias]
93
+ # if no match use the passed in repo
94
+ config_repo = found_repo.nil? ? config_repo : found_repo
95
+ end
96
+
97
+ # compute a unique hash for this repo so
98
+ # we can store it in a subdir of configs
99
+ def self.repo_url_hash(repo_url)
100
+ Digest::SHA1.hexdigest(repo_url)
101
+ end
102
+
103
+ # return the base config path minus the git dir
104
+ # for the given url
105
+ def self.base_config_path(repo_url)
106
+ config_hash = repo_url_hash(repo_url)
107
+
108
+ "#{ROOT_PATH}/repo_configs/#{config_hash}"
109
+ end
110
+
111
+ # the full config path
112
+ def self.full_config_path(repo_url)
113
+ "#{base_config_path(repo_url)}/#{CONFIG_DIR}"
114
+ end
115
+
116
+ # takes the repo name (shortcut or full url)
117
+ # and fetches config into appropriate location
118
+ # returns the full repo_url
119
+ def self.prepare_config_repo(config_repo_url)
120
+ # get the full url
121
+ repo_url = EbmSharedLib.get_config_repo_url(config_repo_url)
122
+
123
+ # get the local config dir
124
+ base_config_path = base_config_path(repo_url)
125
+
126
+ # make the root if missing
127
+ FileUtils.mkpath(base_config_path)
128
+
129
+ # and the full config path
130
+ config_path = full_config_path(repo_url)
131
+
132
+ # try to pull, if it fails could be due to repo not cloned
133
+ cmd = "git pull"
134
+ if EbmSharedLib::CL.do_cmd_result(cmd, config_path) != 0
135
+ # pull failed, try to clone
136
+ cmd = "git clone #{repo_url} #{CONFIG_DIR}"
137
+ if EbmSharedLib::CL.do_cmd_result(cmd, base_config_path) != 0
138
+ raise "Unable to clone #{CONFIG_DIR} repo into #{base_config_path}"
139
+ end
140
+ end
141
+
142
+ repo_url
143
+ end
144
+
145
+ # read and parse a json file
146
+ def self.read_json_file(file_path, err_msg = nil)
147
+ begin
148
+ json = File.open(file_path, 'r') {|f| f.read }
149
+ info = JSON.parse(json)
150
+ info.recursively_symbolize_keys!
151
+ rescue
152
+ msg = err_msg.nil? ? "Error opening config file JSON: #{file_path}" : err_msg
153
+ raise msg
154
+ end
155
+ end
156
+
157
+ # write the map as json into the specified file
158
+ def self.write_json_file(map, file_path, err_msg = nil)
159
+ begin
160
+
161
+ json = JSON.pretty_generate(map)
162
+
163
+ File.open(file_path, 'w') { |file| file.write(json) }
164
+ rescue
165
+ msg = err_msg.nil? ? "Error creating JSON file: #{file_path}" : err_msg
166
+ raise msg
167
+ end
168
+ end
169
+
170
+ # write the prepared settings, expects us to pass
171
+ # dir to write into
172
+ def self.write_settings(map, dir, err_msg = nil)
173
+ settings_path = "#{dir}/#{SETTINGS_FILE}"
174
+ write_json_file(map, settings_path, err_msg)
175
+ end
176
+
177
+ # read and return the config info for this repo
178
+ def self.read_repo_config(repo_url, config_name, err_msg = nil)
179
+ config_file_path = "#{full_config_path(repo_url)}/configs/#{config_name}#{EbmSharedLib::CONFIG_SUFFIX}"
180
+
181
+ read_json_file(config_file_path, err_msg)
182
+ end
183
+
184
+ # read top level settings within a prepared dir
185
+ # lets us get to the appropriate config file
186
+ def self.read_settings(err_msg = nil)
187
+ settings_path = "#{Dir.pwd}/#{SETTINGS_FILE}"
188
+ settings = read_json_file(settings_path, err_msg)
189
+ end
190
+
191
+ # expects us to be in the top level dir that has the same name
192
+ # as the config. Such as /Users/gseitz/Develop/ebay/iphone_3.1
193
+ # will extract the config_name of iphone_3.1
194
+ def self.config_name_from_dir
195
+ config_name = "#{Dir.pwd}".split("/").last
196
+ end
197
+
198
+ def self.get_repo_name(git_path)
199
+ repo_name = git_path.split("/").last.split(".git")[0]
200
+ end
201
+
202
+ def self.printer
203
+ @printer ||= Printer.new
204
+ end
205
+
206
+ # get the current banch
207
+ def self.get_current_branch(repo, repo_path)
208
+ repo_name = EbmSharedLib.get_repo_name(repo[:git_path])
209
+
210
+ cmd = "git symbolic-ref HEAD"
211
+ result = EbmSharedLib::CL.do_cmd_output(cmd, repo_path)
212
+ if $?.exitstatus != 0
213
+ raise "Unable to get the current branch for #{repo_name}, you may be on a detached HEAD."
214
+ end
215
+ branch = result.rstrip.split("/").last
216
+ end
217
+
218
+ # read the repo_config file for the prepared directory by
219
+ # first fetching the settings from this dir
220
+ def self.get_config_from_top_dir(err_msg = nil)
221
+ begin
222
+ # for now we still operate without settings by using defaults
223
+ # this should be removed once everyone is on the new tool
224
+ settings = read_settings(".ebm-settings not found, make sure you are in the top level directory.")
225
+ repo_url = settings[:config_repo_url]
226
+ config_name = settings[:config_name]
227
+ rescue
228
+ # use defaults since failed to load settings
229
+ config_name = config_name_from_dir()
230
+ repo_url = get_config_repo_url(nil)
231
+ end
232
+ read_repo_config(repo_url, config_name, "Config not found, make sure you are in the top level directory.")
233
+ end
234
+ end