2 # Copyright (c) 2012 The Chromium OS 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.
7 """Library containing utility functions used for Chrome-specific build tasks."""
18 from chromite.buildbot import cbuildbot_results as results_lib
19 from chromite.lib import cros_build_lib
20 from chromite.lib import osutils
23 # Taken from external/gyp.git/pylib.
24 def _NameValueListToDict(name_value_list):
25 """Converts Name-Value list to dictionary.
27 Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
28 of the pairs. If a string is simply NAME, then the value in the dictionary
29 is set to True. If VALUE can be converted to an integer, it is.
32 for item in name_value_list:
33 tokens = item.split('=', 1)
35 # If we can make it an int, use that, otherwise, use the string.
37 token_value = int(tokens[1])
39 token_value = tokens[1]
40 # Set the variable to the supplied value.
41 result[tokens[0]] = token_value
43 # No value supplied, treat it as a boolean and set it.
44 result[tokens[0]] = True
48 def ProcessGypDefines(defines):
49 """Validate and convert a string containing GYP_DEFINES to dictionary."""
50 assert defines is not None
51 return _NameValueListToDict(shlex.split(defines))
54 def DictToGypDefines(def_dict):
55 """Convert a dict to GYP_DEFINES format."""
57 for k, v in def_dict.iteritems():
58 def_list.append("%s='%s'" % (k, v))
59 return ' '.join(def_list)
62 class Conditions(object):
63 """Functions that return conditions used to construct Path objects.
65 Condition functions returned by the public methods have signature
66 f(gyp_defines, staging_flags). For description of gyp_defines and
67 staging_flags see docstring for StageChromeFromBuildDir().
71 def _GypSet(cls, flag, value, gyp_defines, _staging_flags):
72 val = gyp_defines.get(flag)
73 return val == value if value is not None else bool(val)
76 def _GypNotSet(cls, flag, gyp_defines, staging_flags):
77 return not cls._GypSet(flag, None, gyp_defines, staging_flags)
80 def _StagingFlagSet(cls, flag, _gyp_defines, staging_flags):
81 return flag in staging_flags
84 def _StagingFlagNotSet(cls, flag, gyp_defines, staging_flags):
85 return not cls._StagingFlagSet(flag, gyp_defines, staging_flags)
88 def GypSet(cls, flag, value=None):
89 """Returns condition that tests a gyp flag is set (possibly to a value)."""
90 return functools.partial(cls._GypSet, flag, value)
93 def GypNotSet(cls, flag):
94 """Returns condition that tests a gyp flag is not set."""
95 return functools.partial(cls._GypNotSet, flag)
98 def StagingFlagSet(cls, flag):
99 """Returns condition that tests a staging_flag is set."""
100 return functools.partial(cls._StagingFlagSet, flag)
103 def StagingFlagNotSet(cls, flag):
104 """Returns condition that tests a staging_flag is not set."""
105 return functools.partial(cls._StagingFlagNotSet, flag)
108 class MultipleMatchError(results_lib.StepFailure):
109 """A glob pattern matches multiple files but a non-dir dest was specified."""
112 class MissingPathError(results_lib.StepFailure):
113 """An expected path is non-existant."""
116 class MustNotBeDirError(results_lib.StepFailure):
117 """The specified path should not be a directory, but is."""
120 class Copier(object):
121 """File/directory copier.
123 Provides destination stripping and permission setting functionality.
126 DEFAULT_BLACKLIST = (r'(^|.*/)\.svn($|/.*)',)
128 def __init__(self, strip_bin=None, strip_flags=None, default_mode=0o644,
129 dir_mode=0o755, exe_mode=0o755, blacklist=None):
133 strip_bin: Path to the program used to strip binaries. If set to None,
134 binaries will not be stripped.
135 strip_flags: A list of flags to pass to the |strip_bin| executable.
136 default_mode: Default permissions to set on files.
137 dir_mode: Mode to set for directories.
138 exe_mode: Permissions to set on executables.
139 blacklist: A list of path patterns to ignore during the copy.
141 self.strip_bin = strip_bin
142 self.strip_flags = strip_flags
143 self.default_mode = default_mode
144 self.dir_mode = dir_mode
145 self.exe_mode = exe_mode
146 self.blacklist = blacklist
147 if self.blacklist is None:
148 self.blacklist = self.DEFAULT_BLACKLIST
151 def Log(src, dest, directory):
152 sep = ' [d] -> ' if directory else ' -> '
153 logging.debug('%s %s %s', src, sep, dest)
155 def _PathIsBlacklisted(self, path):
156 for pattern in self.blacklist:
157 if re.match(pattern, path):
161 def _CopyFile(self, src, dest, path):
165 src: The path of the file/directory to copy.
166 dest: The exact path of the destination. Should not already exist.
167 path: The Path instance containing copy operation modifiers (such as
168 Path.exe, Path.strip, etc.)
170 assert not os.path.isdir(src), '%s: Not expecting a directory!' % src
171 osutils.SafeMakedirs(os.path.dirname(dest), mode=self.dir_mode)
172 if path.exe and self.strip_bin and path.strip and os.path.getsize(src) > 0:
173 strip_flags = (['--strip-unneeded'] if self.strip_flags is None else
175 cros_build_lib.DebugRunCommand(
176 [self.strip_bin] + strip_flags + ['-o', dest, src])
177 shutil.copystat(src, dest)
179 shutil.copy2(src, dest)
183 mode = self.exe_mode if path.exe else self.default_mode
186 def Copy(self, src_base, dest_base, path, strict=False, sloppy=False):
187 """Copy artifact(s) from source directory to destination.
190 src_base: The directory to apply the src glob pattern match in.
191 dest_base: The directory to copy matched files to. |Path.dest|.
192 path: A Path instance that specifies what is to be copied.
193 strict: If set, enforce that all optional files are copied.
194 sloppy: If set, ignore when mandatory artifacts are missing.
197 A list of the artifacts copied.
199 assert not (strict and sloppy), 'strict and sloppy are not compatible.'
201 src = os.path.join(src_base, path.src)
202 if not src.endswith('/') and os.path.isdir(src):
203 raise MustNotBeDirError('%s must not be a directory\n'
204 'Aborting copy...' % (src,))
205 paths = glob.glob(src)
207 if ((strict and not path.optional) or
208 (not strict and not (path.optional or path.cond) and not sloppy)):
209 msg = ('%s does not exist and is required.\n'
210 'You can bypass this error with --sloppy.\n'
211 'Aborting copy...' % src)
212 raise MissingPathError(msg)
213 elif path.optional or (not strict and path.cond):
214 logging.debug('%s does not exist and is optional. Skipping.', src)
216 logging.warn('%s does not exist and is required. Skipping anyway.',
218 elif len(paths) > 1 and path.dest and not path.dest.endswith('/'):
219 raise MultipleMatchError(
220 'Glob pattern %r has multiple matches, but dest %s '
221 'is not a directory.\n'
222 'Aborting copy...' % (path.src, path.dest))
225 rel_src = os.path.relpath(p, src_base)
226 if path.dest is None:
228 elif path.dest.endswith('/'):
229 rel_dest = os.path.join(path.dest, os.path.basename(p))
232 assert not rel_dest.endswith('/')
233 dest = os.path.join(dest_base, rel_dest)
235 copied_paths.append(p)
236 self.Log(p, dest, os.path.isdir(p))
238 for sub_path in osutils.DirectoryIterator(p):
239 rel_path = os.path.relpath(sub_path, p)
240 sub_dest = os.path.join(dest, rel_path)
241 if self._PathIsBlacklisted(rel_path):
243 if sub_path.endswith('/'):
244 osutils.SafeMakedirs(sub_dest, mode=self.dir_mode)
246 self._CopyFile(sub_path, sub_dest, path)
248 self._CopyFile(p, dest, path)
254 """Represents an artifact to be copied from build dir to staging dir."""
256 def __init__(self, src, exe=False, cond=None, dest=None, mode=None,
257 optional=False, strip=True, owner=None):
258 """Initializes the object.
261 src: The relative path of the artifact. Can be a file or a directory.
262 Can be a glob pattern.
263 exe: Identifes the path as either being an executable or containing
264 executables. Executables may be stripped during copy, and have
265 special permissions set. We currently only support stripping of
266 specified files and glob patterns that return files. If |src| is a
267 directory or contains directories, the content of the directory will
269 cond: A condition (see Conditions class) to test for in deciding whether
270 to process this artifact. If supplied, the artifact will be treated
271 as optional unless --strict is supplied.
272 dest: Name to give to the target file/directory. Defaults to keeping the
273 same name as the source.
274 mode: The mode to set for the matched files, and the contents of matched
276 optional: Whether to enforce the existence of the artifact. If unset, the
277 script errors out if the artifact does not exist. In 'sloppy'
278 mode, the Copier class treats all artifacts as optional.
279 strip: If |exe| is set, whether to strip the executable.
280 owner: The target owner for this artifact, to be used with chown on the
288 self.optional = optional
292 def ShouldProcess(self, gyp_defines, staging_flags):
293 """Tests whether this artifact should be copied."""
295 return self.cond(gyp_defines, staging_flags)
299 _DISABLE_NACL = 'disable_nacl'
303 _APP_SHELL_FLAG = 'app_shell'
304 _CHROME_INTERNAL_FLAG = 'chrome_internal'
305 _CONTENT_SHELL_FLAG = 'content_shell'
307 _HIGHDPI_FLAG = 'highdpi'
308 _PDF_FLAG = 'chrome_pdf'
309 STAGING_FLAGS = (_APP_SHELL_FLAG, _CHROME_INTERNAL_FLAG, _CONTENT_SHELL_FLAG,
310 _HIGHDPI_FLAG, _PDF_FLAG)
312 _CHROME_SANDBOX_DEST = 'chrome-sandbox'
319 cond=C.StagingFlagSet(_APP_SHELL_FLAG)),
320 Path('app_shell.pak',
321 cond=C.StagingFlagSet(_APP_SHELL_FLAG)),
323 cond=C.GypSet(_USE_DRM)),
325 cond=C.GypSet(_USE_DRM)),
328 Path('chrome_sandbox', mode=0o4755,
329 dest=_CHROME_SANDBOX_DEST),
330 Path('chrome-wrapper'),
331 Path('chrome_100_percent.pak'),
332 Path('chrome_200_percent.pak',
333 cond=C.StagingFlagSet(_HIGHDPI_FLAG)),
334 Path('content_shell',
336 cond=C.StagingFlagSet(_CONTENT_SHELL_FLAG)),
337 Path('content_shell.pak',
338 cond=C.StagingFlagSet(_CONTENT_SHELL_FLAG)),
340 cond=C.GypSet('icu_use_data_file_flag')),
341 Path('keyboard_resources.pak'),
344 cond=C.GypSet('component', value='shared_library')),
345 # Set as optional for backwards compatibility.
346 Path('lib/libpeerconnection.so',
348 cond=C.StagingFlagSet(_CHROME_INTERNAL_FLAG),
350 # Set as optional for backwards compatibility.
354 Path('libffmpegsumo.so',
358 cond=C.StagingFlagSet(_PDF_FLAG)),
359 Path('libppGoogleNaClPluginChrome.so',
361 cond=C.GypNotSet(_DISABLE_NACL)),
365 # Widevine binaries are already pre-stripped. In addition, they don't
366 # play well with the binutils stripping tools, so skip stripping.
367 Path('libwidevinecdmadapter.so',
370 cond=C.StagingFlagSet(_CHROME_INTERNAL_FLAG)),
371 Path('libwidevinecdm.so',
374 cond=C.StagingFlagSet(_CHROME_INTERNAL_FLAG)),
376 # Do not strip the nacl_helper_bootstrap binary because the binutils
377 # objcopy/strip mangles the ELF program headers.
378 Path('nacl_helper_bootstrap',
379 exe=True, strip=False,
380 cond=C.GypNotSet(_DISABLE_NACL)),
381 Path('nacl_irt_*.nexe',
382 cond=C.GypNotSet(_DISABLE_NACL)),
386 cond=C.GypNotSet(_DISABLE_NACL)),
388 cond=C.GypNotSet(_DISABLE_NACL)),
390 Path('resources.pak'),
391 Path('xdg-settings'),
395 _COPY_PATHS_CONTENT_SHELL = (
396 Path('chrome[-_]sandbox', exe=True, mode=0o4755, owner='root:chrome'),
397 Path('dogfood/dumpstate_uploader',
399 cond=C.StagingFlagNotSet(_ECS_FLAG)),
400 Path('dogfood/fake.dmp', cond=C.StagingFlagNotSet(_ECS_FLAG)),
401 Path('content_shell',
403 cond=C.StagingFlagSet(_ECS_FLAG)),
404 Path('content_shell.pak', cond=C.StagingFlagSet(_ECS_FLAG)),
407 cond=C.StagingFlagNotSet(_ECS_FLAG),
409 owner='chrome:chrome'),
410 Path('eureka_shell.pak',
411 cond=C.StagingFlagNotSet(_ECS_FLAG),
412 owner='chrome:chrome'),
413 Path('fake_log_report.dmp',
414 cond=C.StagingFlagNotSet(_ECS_FLAG),
415 owner='chrome:chrome'),
416 Path('icudtl.dat', cond=C.GypSet('icu_use_data_file_flag')),
418 cond=C.StagingFlagNotSet(_ECS_FLAG),
419 owner='chrome:chrome'),
420 Path('libffmpegsumo.so', owner='chrome:chrome'),
421 Path('libosmesa.so', exe=True, optional=True),
422 Path('locales/*.pak',
423 cond=C.StagingFlagNotSet(_ECS_FLAG),
424 owner='chrome:chrome'),
426 cond=C.StagingFlagNotSet(_ECS_FLAG),
427 owner='chrome:chrome'),
428 Path('osd_images/1080p/*.png',
429 cond=C.StagingFlagNotSet(_ECS_FLAG),
430 owner='chrome:chrome'),
431 Path('osd_images/720p/*.png',
432 cond=C.StagingFlagNotSet(_ECS_FLAG),
433 owner='chrome:chrome'),
434 Path('setup/http/jquery.min.js',
435 cond=C.StagingFlagNotSet(_ECS_FLAG),
436 owner='chrome:chrome'),
437 Path('setup/http/setup.html',
438 cond=C.StagingFlagNotSet(_ECS_FLAG),
439 owner='chrome:chrome'),
440 Path('setup/http/welcome-video.mp4',
441 cond=C.StagingFlagNotSet(_ECS_FLAG),
442 owner='chrome:chrome'),
443 Path('update_engine',
445 cond=C.StagingFlagNotSet(_ECS_FLAG),
447 owner='updater:updater'),
450 cond=C.StagingFlagNotSet(_ECS_FLAG),
452 owner='chrome:chrome'),
453 Path('watchdog/wd.dmp', cond=C.StagingFlagNotSet(_ECS_FLAG)),
456 def _FixPermissions(dest_base):
457 """Last minute permission fixes."""
458 cros_build_lib.DebugRunCommand(['chmod', '-R', 'a+r', dest_base])
459 cros_build_lib.DebugRunCommand(
460 ['find', dest_base, '-perm', '/110', '-exec', 'chmod', 'a+x', '{}', '+'])
463 def GetCopyPaths(content_shell=False):
464 """Returns the list of copy paths used as a filter for staging files.
467 content_shell: Boolean on whether to return the copy paths used for a
468 content shell deployment or a chrome deployment.
471 The list of paths to use as a filter for staging files.
474 return _COPY_PATHS_CONTENT_SHELL
479 def StageChromeFromBuildDir(staging_dir, build_dir, strip_bin, strict=False,
480 sloppy=False, gyp_defines=None, staging_flags=None,
481 strip_flags=None, copy_paths=_COPY_PATHS):
482 """Populates a staging directory with necessary build artifacts.
484 If |strict| is set, then we decide what to stage based on the |gyp_defines|
485 and |staging_flags| passed in. Otherwise, we stage everything that we know
486 about, that we can find.
489 staging_dir: Path to an empty staging directory.
490 build_dir: Path to location of Chrome build artifacts.
491 strip_bin: Path to executable used for stripping binaries.
492 strict: If set, decide what to stage based on the |gyp_defines| and
493 |staging_flags| passed in, and enforce that all optional files
494 are copied. Otherwise, we stage optional files if they are
495 there, but we don't complain if they're not.
496 sloppy: Ignore when mandatory artifacts are missing.
497 gyp_defines: A dictionary (i.e., one returned by ProcessGypDefines)
498 containing GYP_DEFINES Chrome was built with.
499 staging_flags: A list of extra staging flags. Valid flags are specified in
501 strip_flags: A list of flags to pass to the tool used to strip binaries.
502 copy_paths: The list of paths to use as a filter for staging files.
504 os.mkdir(os.path.join(staging_dir, 'plugins'), 0o755)
506 if gyp_defines is None:
508 if staging_flags is None:
511 copier = Copier(strip_bin=strip_bin, strip_flags=strip_flags)
514 if not strict or p.ShouldProcess(gyp_defines, staging_flags):
515 copied_paths += copier.Copy(build_dir, staging_dir, p, strict=strict,
519 raise MissingPathError('Couldn\'t find anything to copy!\n'
520 'Are you looking in the right directory?\n'
523 _FixPermissions(staging_dir)