OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 | 2 |
3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
| 7 """CBuildbot is wrapper around the build process used by the pre-flight queue""" |
| 8 |
7 import errno | 9 import errno |
8 import optparse | 10 import optparse |
9 import os | 11 import os |
| 12 import re |
10 import shutil | 13 import shutil |
11 import subprocess | 14 import subprocess |
12 import sys | 15 import sys |
13 | 16 |
14 from cbuildbot_config import config | 17 from cbuildbot_config import config |
15 | 18 |
16 _DEFAULT_RETRIES = 3 | 19 _DEFAULT_RETRIES = 3 |
17 | 20 |
18 # Utility functions | 21 # ======================== Utility functions ================================ |
19 | 22 |
20 def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None, | 23 def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None, |
21 exit_code=False, redirect_stdout=False, redirect_stderr=False, | 24 exit_code=False, redirect_stdout=False, redirect_stderr=False, |
22 cwd=None, input=None): | 25 cwd=None, input=None, enter_chroot=False): |
| 26 """Runs a shell command. |
| 27 |
| 28 Keyword arguments: |
| 29 print_cmd -- prints the command before running it. |
| 30 error_ok -- does not raise an exception on error. |
| 31 error_message -- prints out this message when an error occurrs. |
| 32 exit_code -- returns the return code of the shell command. |
| 33 redirect_stdout -- returns the stdout. |
| 34 redirect_stderr -- holds stderr output until input is communicated. |
| 35 cwd -- the working directory to run this cmd. |
| 36 input -- input to pipe into this command through stdin. |
| 37 enter_chroot -- this command should be run from within the chroot. |
| 38 |
| 39 """ |
| 40 # Set default for variables. |
| 41 stdout = None |
| 42 stderr = None |
| 43 stdin = None |
| 44 |
| 45 # Modify defaults based on parameters. |
| 46 if redirect_stdout: stdout = subprocess.PIPE |
| 47 if redirect_stderr: stderr = subprocess.PIPE |
| 48 if input: stdin = subprocess.PIPE |
| 49 if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd |
| 50 |
23 # Print out the command before running. | 51 # Print out the command before running. |
24 if print_cmd: | 52 if print_cmd: |
25 print >> sys.stderr, "CBUILDBOT -- RunCommand:", ' '.join(cmd) | 53 print >> sys.stderr, 'CBUILDBOT -- RunCommand: ', ' '.join(cmd) |
26 if redirect_stdout: | 54 |
27 stdout = subprocess.PIPE | |
28 else: | |
29 stdout = None | |
30 if redirect_stderr: | |
31 stderr = subprocess.PIPE | |
32 else: | |
33 stderr = None | |
34 if input: | |
35 stdin = subprocess.PIPE | |
36 else: | |
37 stdin = None | |
38 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, | 55 proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, |
39 stdout=stdout, stderr=stderr) | 56 stdout=stdout, stderr=stderr) |
40 (output, error) = proc.communicate(input) | 57 (output, error) = proc.communicate(input) |
41 if exit_code: | 58 if exit_code: |
42 return proc.returncode | 59 return proc.returncode |
43 if not error_ok and proc.returncode != 0: | 60 if not error_ok and proc.returncode != 0: |
44 raise Exception('Command "%s" failed.\n' % (' '.join(cmd)) + | 61 raise Exception('Command "%s" failed.\n' % (' '.join(cmd)) + |
45 (error_message or error or output or '')) | 62 (error_message or error or output or '')) |
46 return output | 63 return output |
47 | 64 |
| 65 |
48 def MakeDir(path, parents=False): | 66 def MakeDir(path, parents=False): |
| 67 """Basic wrapper around os.mkdirs. |
| 68 |
| 69 Keyword arguments: |
| 70 path -- Path to create. |
| 71 parents -- Follow mkdir -p logic. |
| 72 |
| 73 """ |
49 try: | 74 try: |
50 os.makedirs(path) | 75 os.makedirs(path) |
51 except OSError, e: | 76 except OSError, e: |
52 if e.errno == errno.EEXIST and parents: | 77 if e.errno == errno.EEXIST and parents: |
53 pass | 78 pass |
54 else: | 79 else: |
55 raise | 80 raise |
56 | 81 |
57 def RepoSync(buildroot, rw_checkout, retries=_DEFAULT_RETRIES): | 82 |
| 83 def RepoSync(buildroot, rw_checkout=False, retries=_DEFAULT_RETRIES): |
| 84 """Uses repo to checkout the source code. |
| 85 |
| 86 Keyword arguments: |
| 87 rw_checkout -- Reconfigure repo after sync'ing to read-write. |
| 88 retries -- Number of retries to try before failing on the sync. |
| 89 |
| 90 """ |
58 while retries > 0: | 91 while retries > 0: |
59 try: | 92 try: |
60 RunCommand(['repo', 'sync'], cwd=buildroot) | 93 RunCommand(['repo', 'sync'], cwd=buildroot) |
61 if rw_checkout: | 94 if rw_checkout: |
62 # Always re-run in case of new git repos or repo sync | 95 # Always re-run in case of new git repos or repo sync |
63 # failed in a previous run because of a forced Stop Build. | 96 # failed in a previous run because of a forced Stop Build. |
64 RunCommand(['repo', 'forall', '-c', 'git', 'config', | 97 RunCommand(['repo', 'forall', '-c', 'git', 'config', |
65 'url.ssh://git@gitrw.chromium.org:9222.pushinsteadof', | 98 'url.ssh://git@gitrw.chromium.org:9222.pushinsteadof', |
66 'http://src.chromium.org/git'], cwd=buildroot) | 99 'http://src.chromium.org/git'], cwd=buildroot) |
67 retries = 0 | 100 retries = 0 |
68 except: | 101 except: |
69 retries -= 1 | 102 retries -= 1 |
70 if retries > 0: | 103 if retries > 0: |
71 print >> sys.stderr, 'CBUILDBOT -- Repo Sync Failed, retrying' | 104 print >> sys.stderr, 'CBUILDBOT -- Repo Sync Failed, retrying' |
72 else: | 105 else: |
73 print >> sys.stderr, 'CBUILDBOT -- Retries exhausted' | 106 print >> sys.stderr, 'CBUILDBOT -- Retries exhausted' |
74 raise | 107 raise |
75 | 108 |
76 # Main functions | 109 # =========================== Command Helpers ================================= |
| 110 |
| 111 def _GetAllGitRepos(buildroot, debug=False): |
| 112 """Returns a list of tuples containing [git_repo, src_path].""" |
| 113 manifest_tuples = [] |
| 114 # Gets all the git repos from a full repo manifest. |
| 115 repo_cmd = "repo manifest -o -".split() |
| 116 output = RunCommand(repo_cmd, cwd=buildroot, redirect_stdout=True, |
| 117 redirect_stderr=True, print_cmd=debug) |
| 118 |
| 119 # Extract all lines containg a project. |
| 120 extract_cmd = ["grep", "project name="] |
| 121 output = RunCommand(extract_cmd, cwd=buildroot, input=output, |
| 122 redirect_stdout=True, print_cmd=debug) |
| 123 # Parse line using re to get tuple. |
| 124 result_array = re.findall('.+name=\"([\w-]+)\".+path=\"(\S+)".+', output) |
| 125 |
| 126 # Create the array. |
| 127 for result in result_array: |
| 128 if len(result) != 2: |
| 129 print >> sys.stderr, 'Found in correct xml object %s', result |
| 130 else: |
| 131 # Remove pre-pended src directory from manifest. |
| 132 manifest_tuples.append([result[0], result[1].replace('src/', '')]) |
| 133 return manifest_tuples |
| 134 |
| 135 |
| 136 def _GetCrosWorkOnSrcPath(buildroot, board, package, debug=False): |
| 137 """Returns ${CROS_WORKON_SRC_PATH} for given package.""" |
| 138 cwd = os.path.join(buildroot, 'src', 'scripts') |
| 139 equery_cmd = ('equery-%s which %s' % (board, package)).split() |
| 140 ebuild_path = RunCommand(equery_cmd, cwd=cwd, redirect_stdout=True, |
| 141 redirect_stderr=True, enter_chroot=True, |
| 142 error_ok=True, print_cmd=debug) |
| 143 if ebuild_path: |
| 144 ebuild_cmd = ('ebuild-%s %s info' % (board, ebuild_path)).split() |
| 145 cros_workon_output = RunCommand(ebuild_cmd, cwd=cwd, |
| 146 redirect_stdout=True, redirect_stderr=True, |
| 147 enter_chroot=True, print_cmd=debug) |
| 148 |
| 149 temp = re.findall('CROS_WORKON_SRCDIR="(\S+)"', cros_workon_output) |
| 150 if temp: |
| 151 return temp[0] |
| 152 return None |
| 153 |
| 154 |
| 155 def _CreateRepoDictionary(buildroot, board, debug=False): |
| 156 """Returns the repo->list_of_ebuilds dictionary.""" |
| 157 repo_dictionary = {} |
| 158 manifest_tuples = _GetAllGitRepos(buildroot) |
| 159 print >> sys.stderr, 'Creating dictionary of git repos to portage packages ...
' |
| 160 |
| 161 cwd = os.path.join(buildroot, 'src', 'scripts') |
| 162 get_all_workon_pkgs_cmd = './cros_workon list --all'.split() |
| 163 packages = RunCommand(get_all_workon_pkgs_cmd, cwd=cwd, |
| 164 redirect_stdout=True, redirect_stderr=True, |
| 165 enter_chroot=True, print_cmd=debug) |
| 166 for package in packages.split(): |
| 167 cros_workon_src_path = _GetCrosWorkOnSrcPath(buildroot, board, package) |
| 168 if cros_workon_src_path: |
| 169 for tuple in manifest_tuples: |
| 170 # This path tends to have the user's home_dir prepended to it. |
| 171 if cros_workon_src_path.endswith(tuple[1]): |
| 172 print >> sys.stderr, ('For %s found matching package %s' % |
| 173 (tuple[0], package)) |
| 174 if repo_dictionary.has_key(tuple[0]): |
| 175 repo_dictionary[tuple[0]] += [package] |
| 176 else: |
| 177 repo_dictionary[tuple[0]] = [package] |
| 178 return repo_dictionary |
| 179 |
| 180 |
| 181 def _ParseRevisionString(revision_string, repo_dictionary): |
| 182 """Parses the given revision_string into a revision dictionary. |
| 183 |
| 184 Returns a list of tuples that contain [portage_package_name, commit_id] to |
| 185 update. |
| 186 |
| 187 Keyword arguments: |
| 188 revision_string -- revision_string with format |
| 189 'repo1.git@commit_1 repo2.git@commit2 ...'. |
| 190 repo_dictionary -- dictionary with git repository names as keys (w/out git) |
| 191 to portage package names. |
| 192 |
| 193 """ |
| 194 # Using a dictionary removes duplicates. |
| 195 revisions = {} |
| 196 for revision in revision_string.split(): |
| 197 # Format 'package@commit-id'. |
| 198 revision_tuple = revision.split('@') |
| 199 if len(revision_tuple) != 2: |
| 200 print >> sys.stderr, 'Incorrectly formatted revision %s' % revision |
| 201 repo_name = revision_tuple[0].replace('.git', '') |
| 202 # May be many corresponding packages to a given git repo e.g. kernel) |
| 203 for package in repo_dictionary[repo_name]: |
| 204 revisions[package] = revision_tuple[1] |
| 205 return revisions.items() |
| 206 |
| 207 |
| 208 def _UprevFromRevisionList(buildroot, revision_list): |
| 209 """Uprevs based on revision list.""" |
| 210 package_str = '' |
| 211 commit_str = '' |
| 212 for package, revision in revision_list: |
| 213 package_str += package + ' ' |
| 214 commit_str += revision + ' ' |
| 215 package_str = package_str.strip() |
| 216 commit_str = commit_str.strip() |
| 217 |
| 218 cwd = os.path.join(buildroot, 'src', 'scripts') |
| 219 RunCommand(['./cros_mark_as_stable', |
| 220 '--tracking_branch="cros/master"', |
| 221 '--packages="%s"' % package_str, |
| 222 '--commit_ids="%s"' % commit_str, |
| 223 'commit'], |
| 224 cwd=cwd, enter_chroot=True) |
| 225 |
| 226 |
| 227 def _UprevAllPackages(buildroot): |
| 228 """Uprevs all packages that have been updated since last uprev.""" |
| 229 cwd = os.path.join(buildroot, 'src', 'scripts') |
| 230 RunCommand(['./cros_mark_all_as_stable', |
| 231 '--tracking_branch="cros/master"'], |
| 232 cwd=cwd, enter_chroot=True) |
| 233 |
| 234 # =========================== Main Commands =================================== |
77 | 235 |
78 def _FullCheckout(buildroot, rw_checkout=True, retries=_DEFAULT_RETRIES): | 236 def _FullCheckout(buildroot, rw_checkout=True, retries=_DEFAULT_RETRIES): |
| 237 """Performs a full checkout and clobbers any previous checkouts.""" |
79 RunCommand(['sudo', 'rm', '-rf', buildroot]) | 238 RunCommand(['sudo', 'rm', '-rf', buildroot]) |
80 MakeDir(buildroot, parents=True) | 239 MakeDir(buildroot, parents=True) |
81 RunCommand(['repo', 'init', '-u', 'http://src.chromium.org/git/manifest'], | 240 RunCommand(['repo', 'init', '-u', 'http://src.chromium.org/git/manifest'], |
82 cwd=buildroot, input='\n\ny\n') | 241 cwd=buildroot, input='\n\ny\n') |
83 RepoSync(buildroot, rw_checkout, retries) | 242 RepoSync(buildroot, rw_checkout, retries) |
84 | 243 |
| 244 |
85 def _IncrementalCheckout(buildroot, rw_checkout=True, | 245 def _IncrementalCheckout(buildroot, rw_checkout=True, |
86 retries=_DEFAULT_RETRIES): | 246 retries=_DEFAULT_RETRIES): |
| 247 """Performs a checkout without clobbering previous checkout.""" |
87 RepoSync(buildroot, rw_checkout, retries) | 248 RepoSync(buildroot, rw_checkout, retries) |
88 | 249 |
| 250 |
89 def _MakeChroot(buildroot): | 251 def _MakeChroot(buildroot): |
| 252 """Wrapper around make_chroot.""" |
90 cwd = os.path.join(buildroot, 'src', 'scripts') | 253 cwd = os.path.join(buildroot, 'src', 'scripts') |
91 RunCommand(['./make_chroot', '--fast'], cwd=cwd) | 254 RunCommand(['./make_chroot', '--fast'], cwd=cwd) |
92 | 255 |
| 256 |
93 def _SetupBoard(buildroot, board='x86-generic'): | 257 def _SetupBoard(buildroot, board='x86-generic'): |
| 258 """Wrapper around setup_board.""" |
94 cwd = os.path.join(buildroot, 'src', 'scripts') | 259 cwd = os.path.join(buildroot, 'src', 'scripts') |
95 RunCommand(['./setup_board', '--fast', '--default', '--board=%s' % board], | 260 RunCommand(['./setup_board', '--fast', '--default', '--board=%s' % board], |
96 cwd=cwd) | 261 cwd=cwd) |
97 | 262 |
| 263 |
98 def _Build(buildroot): | 264 def _Build(buildroot): |
| 265 """Wrapper around build_packages.""" |
99 cwd = os.path.join(buildroot, 'src', 'scripts') | 266 cwd = os.path.join(buildroot, 'src', 'scripts') |
100 RunCommand(['./build_packages'], cwd=cwd) | 267 RunCommand(['./build_packages'], cwd=cwd) |
101 | 268 |
102 def _UprevAllPackages(buildroot): | 269 |
103 cwd = os.path.join(buildroot, 'src', 'scripts') | 270 def _UprevPackages(buildroot, revisionfile, board): |
104 RunCommand(['./enter_chroot.sh', '--', './cros_mark_all_as_stable', | 271 """Uprevs a package based on given revisionfile. |
105 '--tracking_branch="cros/master"'], | 272 |
106 cwd=cwd) | 273 If revisionfile is set to None or does not resolve to an actual file, this |
107 | 274 function will uprev all packages. |
108 def _UprevPackages(buildroot, revisionfile): | 275 |
109 revisions = None | 276 Keyword arguments: |
| 277 revisionfile -- string specifying a file that contains a list of revisions to |
| 278 uprev. |
| 279 """ |
| 280 # Purposefully set to None as it means Force Build was pressed. |
| 281 revisions = 'None' |
110 if (revisionfile): | 282 if (revisionfile): |
111 try: | 283 try: |
112 rev_file = open(revisionfile) | 284 rev_file = open(revisionfile) |
113 revisions = rev_file.read() | 285 revisions = rev_file.read() |
114 rev_file.close() | 286 rev_file.close() |
115 except: | 287 except Exception, e: |
116 print >> sys.stderr, 'Error reading %s' % revisionfile | 288 print >> sys.stderr, 'Error reading %s, revving all' % revisionfile |
117 revisions = None | 289 print e |
118 | 290 revisions = 'None' |
119 # Note: Revisions == "None" indicates a Force Build. | 291 |
120 if revisions and revisions != 'None': | 292 revisions = revisions.strip() |
121 print 'CBUILDBOT - Revision list found %s' % revisions | 293 |
122 print 'Revision list not yet propagating to build, marking all instead' | 294 # Revisions == "None" indicates a Force Build. |
123 | 295 if revisions != 'None': |
124 _UprevAllPackages(buildroot) | 296 print >> sys.stderr, 'CBUILDBOT Revision list found %s' % revisions |
| 297 revision_list = _ParseRevisionString(revisions, |
| 298 _CreateRepoDictionary(buildroot, board)) |
| 299 _UprevFromRevisionList(buildroot, revision_list) |
| 300 else: |
| 301 print >> sys.stderr, 'CBUILDBOT Revving all' |
| 302 _UprevAllPackages(buildroot) |
| 303 |
125 | 304 |
126 def _UprevCleanup(buildroot): | 305 def _UprevCleanup(buildroot): |
| 306 """Clean up after a previous uprev attempt.""" |
127 cwd = os.path.join(buildroot, 'src', 'scripts') | 307 cwd = os.path.join(buildroot, 'src', 'scripts') |
128 RunCommand(['./cros_mark_as_stable', '--srcroot=..', | 308 RunCommand(['./cros_mark_as_stable', '--srcroot=..', |
129 '--tracking_branch="cros/master"', 'clean'], | 309 '--tracking_branch="cros/master"', 'clean'], |
130 cwd=cwd) | 310 cwd=cwd) |
131 | 311 |
| 312 |
132 def _UprevPush(buildroot): | 313 def _UprevPush(buildroot): |
| 314 """Pushes uprev changes to the main line.""" |
133 cwd = os.path.join(buildroot, 'src', 'scripts') | 315 cwd = os.path.join(buildroot, 'src', 'scripts') |
134 RunCommand(['./cros_mark_as_stable', '--srcroot=..', | 316 RunCommand(['./cros_mark_as_stable', '--srcroot=..', |
135 '--tracking_branch="cros/master"', | 317 '--tracking_branch="cros/master"', |
136 '--push_options', '--bypass-hooks -f', 'push'], | 318 '--push_options', '--bypass-hooks -f', 'push'], |
137 cwd=cwd) | 319 cwd=cwd) |
138 | 320 |
| 321 |
139 def _GetConfig(config_name): | 322 def _GetConfig(config_name): |
| 323 """Gets the configuration for the build""" |
140 default = config['default'] | 324 default = config['default'] |
141 buildconfig = {} | 325 buildconfig = {} |
142 if config.has_key(config_name): | 326 if config.has_key(config_name): |
143 buildconfig = config[config_name] | 327 buildconfig = config[config_name] |
144 for key in default.iterkeys(): | 328 for key in default.iterkeys(): |
145 if not buildconfig.has_key(key): | 329 if not buildconfig.has_key(key): |
146 buildconfig[key] = default[key] | 330 buildconfig[key] = default[key] |
147 return buildconfig | 331 return buildconfig |
148 | 332 |
| 333 |
149 def main(): | 334 def main(): |
150 # Parse options | 335 # Parse options |
151 usage = "usage: %prog [options] cbuildbot_config" | 336 usage = "usage: %prog [options] cbuildbot_config" |
152 parser = optparse.OptionParser(usage=usage) | 337 parser = optparse.OptionParser(usage=usage) |
153 parser.add_option('-r', '--buildroot', | 338 parser.add_option('-r', '--buildroot', |
154 help='root directory where build occurs', default=".") | 339 help='root directory where build occurs', default=".") |
155 parser.add_option('-n', '--buildnumber', | 340 parser.add_option('-n', '--buildnumber', |
156 help='build number', type='int', default=0) | 341 help='build number', type='int', default=0) |
157 parser.add_option('-f', '--revisionfile', | 342 parser.add_option('-f', '--revisionfile', |
158 help='file where new revisions are stored') | 343 help='file where new revisions are stored') |
(...skipping 17 matching lines...) Expand all Loading... |
176 _FullCheckout(buildroot) | 361 _FullCheckout(buildroot) |
177 else: | 362 else: |
178 _IncrementalCheckout(buildroot) | 363 _IncrementalCheckout(buildroot) |
179 chroot_path = os.path.join(buildroot, 'chroot') | 364 chroot_path = os.path.join(buildroot, 'chroot') |
180 if not os.path.isdir(chroot_path): | 365 if not os.path.isdir(chroot_path): |
181 _MakeChroot(buildroot) | 366 _MakeChroot(buildroot) |
182 boardpath = os.path.join(chroot_path, 'build', buildconfig['board']) | 367 boardpath = os.path.join(chroot_path, 'build', buildconfig['board']) |
183 if not os.path.isdir(boardpath): | 368 if not os.path.isdir(boardpath): |
184 _SetupBoard(buildroot, board=buildconfig['board']) | 369 _SetupBoard(buildroot, board=buildconfig['board']) |
185 if buildconfig['uprev']: | 370 if buildconfig['uprev']: |
186 _UprevPackages(buildroot, revisionfile) | 371 _UprevPackages(buildroot, revisionfile, board=buildconfig['board']) |
187 _Build(buildroot) | 372 _Build(buildroot) |
188 if buildconfig['uprev']: | 373 if buildconfig['uprev']: |
189 _UprevPush(buildroot) | 374 _UprevPush(buildroot) |
190 _UprevCleanup(buildroot) | 375 _UprevCleanup(buildroot) |
191 except: | 376 except: |
192 # something went wrong, cleanup (being paranoid) for next build | 377 # something went wrong, cleanup (being paranoid) for next build |
193 if clobber: | 378 if clobber: |
194 RunCommand(['sudo', 'rm', '-rf', buildroot], print_cmd=False) | 379 RunCommand(['sudo', 'rm', '-rf', buildroot], print_cmd=False) |
195 raise | 380 raise |
196 | 381 |
| 382 |
197 if __name__ == '__main__': | 383 if __name__ == '__main__': |
198 main() | 384 main() |
OLD | NEW |