Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(465)

Side by Side Diff: scripts/tools/cros/cros_builder_convert.py

Issue 1402253002: CrOS: Load Chromite pins from JSON. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Updates BuildBot builder directories to the new 'cbuildbot'-driven naming
7 scheme.
8
9 Classic BuildBot CrOS waterfalls define build directories by composing the
10 directory name from component parts resembling the target and a final branch
11 name. Oftentimes, these component parts (and, therefore, the composition) don't
12 actually match the name of the underlying 'cbuildbot' target.
13
14 This presents problems because the build target are fundamentally driven by
15 their underlying 'cbuildbot' target, but the composition scheme is extremely
16 arbitrary.
17
18 Consequently, BuildBot masters are being migrated to a new, deterministic,
19 'cbuildbot'-driven naming scheme. A builder building 'cbuildbot' target
20 <target> and checking Chromite/'cbuildbot' from branch <branch> will use the
21 builder name: <target>-<branch>. This is universally sustainable across all
22 waterfalls and ensures that 'cbuildbot' builds are tracked and numbered based
23 on their underlying 'cbuildbot' target.
24
25 This script is intended to be run on a stopped BuildBot master during build
26 directory migration. It will iterate through each build directory in the current
27 master naming scheme and rename the classic directories into their new
28 'cbuildbot'-driven namespace.
29 """
30
31 import argparse
32 import collections
33 import logging
34 import os
35 import re
36 import shutil
37 import sys
38
39 from common import cros_chromite
40
41
42 class UpdateInfo(collections.namedtuple(
43 'UpdateInfo',
44 ('src', 'cbb_name', 'branch'))):
45 """Information about a single directory update action."""
46
47 _STATIC_PERMUTATIONS = {
48 'Canary master': 'master-canary',
49 }
50
51 _TRANSFORMATIONS = (
52 (r'-canary-', r'-release-'),
53 (r'-full', r'-release'),
54 (r'-pre-flight', r'-pre-flight-branch'),
55 (r'(x86|amd64)$', r'\1-generic'),
56 (r'^chromium-tot-chromeos-(.+)-asan', r'\1-tot-asan-informational'),
57 (r'^chromium-tot-chromeos-(.+)', r'\1-tot-chrome-pfq-informational'),
58 (r'^chromium-(.+)-telemetry$', r'\1-telemetry'),
59 (r'(.+)-bin$', r'\1'),
60 )
61
62 @property
63 def dst(self):
64 """Constructs the <cbuildbot>-<branch> form."""
65 return '%s-%s' % (self.cbb_name, self.branch)
66
67 @classmethod
68 def permutations(cls, name):
69 """Attempts to permute a legacy BuildBot name into a Chromite target.
70
71 Args:
72 name (str): The source name to process and map.
73 Yields (str): Various permutations of 'name'.
74 """
75 # No cbuildbot targets use upper-case letters.
76 name = name.lower()
77
78 # If 'name' is already a 'cbuildbot' target, return it unmodified.
79 yield name
80
81 # Apply static permutations.
82 p = cls._STATIC_PERMUTATIONS.get(name)
83 if p:
84 yield p
85
86 # Replace 'canary' with 'release'.
87 for find, replace in cls._TRANSFORMATIONS:
88 name = re.sub(find, replace, name)
89 yield name
90
91 # Is 'name' valid if it was a release group?
92 if not name.endswith('-group'):
93 # We never build 'full' group variants.
94 name_group = ('%s-group' % (name,)).replace('-full-', '-release-')
95 yield name_group
96
97 @classmethod
98 def process(cls, config, name, branches=None, blacklist=None):
99 """Construct an UpdateInfo to map a source name.
100
101 This function works by attempting to transform a source name into a known
102 'cbuildbot' target name. If successful, it will use that successful
103 transformation as validation of the correctness and return an UpdateInfo
104 describing the transformation.
105
106 Args:
107 config (cros_chromite.ChromiteConfig) The Chromite config instance.
108 name (str): The source name to process and map.
109 branches (list): A list of valid branches, extracted from 'cros_chromite'.
110 Returns (UpdateInfo/None): The constructed UpdateInfo, or None if there was
111 no identified mapping.
112 """
113 def sliding_split_gen():
114 parts = name.split('-')
115 for i in xrange(len(parts), 0, -1):
116 yield '-'.join(parts[:i]), '-'.join(parts[i:])
117
118 logging.debug("Processing candidate name: %s", name)
119 candidates = set()
120 branch = None
121 for orig_name, branch in sliding_split_gen():
122 logging.debug("Trying construction: Name(%s), Branch(%s)",
123 orig_name, branch)
124 if branches and not branch in branches:
125 logging.debug("Ignoring branch value '%s'.", branch)
126 continue
127
128 # See if we can properly permute the original name.
129 for permuted_name in cls.permutations(orig_name):
130 if blacklist and any(b in permuted_name for b in blacklist):
131 logging.debug("Ignoring blacklisted config name: %s", permuted_name)
132 continue
133 if permuted_name in config:
134 candidates.add(permuted_name)
135 if not candidates:
136 logging.debug("No 'cbuildbot' config for attempts [%s] branch [%s].",
137 orig_name, branch)
138 continue
139
140 # We've found a permutation that matches a 'cbuildbot' target.
141 break
142 else:
143 logging.info("No 'cbuildbot' permutations for [%s].", name)
144 return None
145
146 if not branch:
147 # We need to do an update to add the branch. Default to 'master'.
148 branch = 'master'
149
150 candidates = sorted(candidates)
151 for candidate in candidates:
152 logging.debug("Identified 'cbuildbot' name [%s] => [%s] branch [%s].",
153 name, candidate, branch)
154 return [cls(name, p, branch) for p in candidates]
155
156
157 def main(args):
158 """Main execution function.
159
160 Args:
161 args (list): Command-line argument array.
162 """
163 parser = argparse.ArgumentParser()
164 parser.add_argument('path', nargs='+', metavar='PATH',
165 help='The path to the master directory to process.')
166 parser.add_argument('-v', '--verbose', action='count', default=0,
167 help='Increase verbosity. Can be specified multiple times.')
168 parser.add_argument('-d', '--dry-run', action='store_true',
169 help="Print what actions will be taken, but don't modify anything.")
170 parser.add_argument('-n', '--names', action='store_true',
171 help="If specified, then regard 'path' as directory names to test.")
172 parser.add_argument('-B', '--blacklist', action='append', default=[],
173 help="Blacklist configs that contain this text.")
174 args = parser.parse_args()
175
176 # Select verbosity.
177 if args.verbose == 0:
178 loglevel = logging.WARNING
179 elif args.verbose == 1:
180 loglevel = logging.INFO
181 else:
182 loglevel = logging.DEBUG
183 logging.getLogger().setLevel(loglevel)
184
185 # Load all availables Chromite configs. We're going to load ToT.
186 config_names = set()
187 branches = set()
188 for branch in cros_chromite.PINS.iterkeys():
189 branches.add(branch)
190 config_names.update(cros_chromite.Get(branch=branch).iterkeys())
191
192 # If we're just testing against names, do that.
193 if args.names:
194 errors = 0
195 for n in args.path:
196 update_info_list = UpdateInfo.process(config_names, n, branches=branches,
197 blacklist=args.blacklist)
198 if update_info_list:
199 for update_info in update_info_list:
200 logging.warning("[%s] => [%s]", update_info.src, update_info.dst)
201 else:
202 logging.warning("No transformation for name [%s].", n)
203 errors += 1
204 return errors
205
206 # Construct the set of actions to take.
207 cbb_already = set()
208 unmatched = set()
209 multiples = {}
210 updates = []
211 for path in args.path:
212 if not os.path.isdir(path):
213 raise ValueError("Supplied master directory is not valid: %s" % (path,))
214
215 seen = set()
216 for f in os.listdir(path):
217 f_path = os.path.join(path, f)
218 if not os.path.isdir(f_path):
219 continue
220
221 update_info_list = UpdateInfo.process(config_names, f, branches=branches,
222 blacklist=args.blacklist)
223 if not update_info_list:
224 logging.info("No update information for directory [%s]", f)
225 unmatched.add(f)
226 continue
227 elif len(update_info_list) != 1:
228 multiples[f] = update_info_list
229 continue
230 update_info = update_info_list[0]
231
232 # Make sure that we don't stomp on directory names. This shouldn't happen,
233 # since the mapping to 'cbuildbot' names is inherently deconflicting, but
234 # it's good to assert it just in case.
235 update_info_names = set((update_info.src, update_info.dst))
236 if update_info_names.intersection(seen):
237 logging.error("Updated names intersect with existing names: %s",
238 ", ".join(update_info_names.intersection(seen)))
239 return 1
240 seen.update(update_info_names)
241
242 # We are already in <cbuildbot>-<branch> format, so do nothing.
243 if update_info.src == update_info.dst:
244 cbb_already.add(update_info.src)
245 else:
246 updates.append((path, update_info))
247
248 # Execute the updates.
249 logging.info("Executing %d updates.", len(updates))
250 for master_dir, update_info in updates:
251 logging.info("Updating [%s]: [%s] => [%s]", master_dir, update_info.src,
252 update_info.dst)
253 if not args.dry_run:
254 shutil.move(os.path.join(master_dir, update_info.src),
255 os.path.join(master_dir, update_info.dst))
256 logging.info("Updated %d directories.", len(updates))
257 if logging.getLogger().isEnabledFor(logging.DEBUG):
258 logging.debug("%d directories already matching: %s",
259 len(cbb_already), ', '.join(sorted(cbb_already)))
260 if unmatched:
261 logging.warning("%d unmatched directories: %s",
262 len(unmatched), ', '.join(sorted(unmatched)))
263 if multiples:
264 for f in sorted(multiples.iterkeys()):
265 logging.warning("Multiple permutations of [%s]: %s\n%s",
266 f, ", ".join(m.dst for m in multiples[f]),
267 '\n'.join('mv %s %s' % (f, m.dst) for m in multiples[f]))
268 return 0
269
270
271 if __name__ == '__main__':
272 logging.basicConfig()
273 sys.exit(main(sys.argv[1:]))
OLDNEW
« scripts/common/env.py ('K') | « scripts/slave/unittests/recipe_lint_test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698