ecb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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