Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / lib / chrome_util.py
1 #!/usr/bin/python
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.
5
6
7 """Library containing utility functions used for Chrome-specific build tasks."""
8
9
10 import functools
11 import glob
12 import logging
13 import os
14 import re
15 import shlex
16 import shutil
17
18 from chromite.buildbot import cbuildbot_results as results_lib
19 from chromite.lib import cros_build_lib
20 from chromite.lib import osutils
21
22
23 # Taken from external/gyp.git/pylib.
24 def _NameValueListToDict(name_value_list):
25   """Converts Name-Value list to dictionary.
26
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.
30   """
31   result = { }
32   for item in name_value_list:
33     tokens = item.split('=', 1)
34     if len(tokens) == 2:
35       # If we can make it an int, use that, otherwise, use the string.
36       try:
37         token_value = int(tokens[1])
38       except ValueError:
39         token_value = tokens[1]
40       # Set the variable to the supplied value.
41       result[tokens[0]] = token_value
42     else:
43       # No value supplied, treat it as a boolean and set it.
44       result[tokens[0]] = True
45   return result
46
47
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))
52
53
54 def DictToGypDefines(def_dict):
55   """Convert a dict to GYP_DEFINES format."""
56   def_list = []
57   for k, v in def_dict.iteritems():
58     def_list.append("%s='%s'" % (k, v))
59   return ' '.join(def_list)
60
61
62 class Conditions(object):
63   """Functions that return conditions used to construct Path objects.
64
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().
68   """
69
70   @classmethod
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)
74
75   @classmethod
76   def _GypNotSet(cls, flag, gyp_defines, staging_flags):
77     return not cls._GypSet(flag, None, gyp_defines, staging_flags)
78
79   @classmethod
80   def _StagingFlagSet(cls, flag, _gyp_defines, staging_flags):
81     return flag in staging_flags
82
83   @classmethod
84   def _StagingFlagNotSet(cls, flag, gyp_defines, staging_flags):
85     return not cls._StagingFlagSet(flag, gyp_defines, staging_flags)
86
87   @classmethod
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)
91
92   @classmethod
93   def GypNotSet(cls, flag):
94     """Returns condition that tests a gyp flag is not set."""
95     return functools.partial(cls._GypNotSet, flag)
96
97   @classmethod
98   def StagingFlagSet(cls, flag):
99     """Returns condition that tests a staging_flag is set."""
100     return functools.partial(cls._StagingFlagSet, flag)
101
102   @classmethod
103   def StagingFlagNotSet(cls, flag):
104     """Returns condition that tests a staging_flag is not set."""
105     return functools.partial(cls._StagingFlagNotSet, flag)
106
107
108 class MultipleMatchError(results_lib.StepFailure):
109   """A glob pattern matches multiple files but a non-dir dest was specified."""
110
111
112 class MissingPathError(results_lib.StepFailure):
113   """An expected path is non-existant."""
114
115
116 class MustNotBeDirError(results_lib.StepFailure):
117   """The specified path should not be a directory, but is."""
118
119
120 class Copier(object):
121   """File/directory copier.
122
123   Provides destination stripping and permission setting functionality.
124   """
125
126   DEFAULT_BLACKLIST = (r'(^|.*/)\.svn($|/.*)',)
127
128   def __init__(self, strip_bin=None, strip_flags=None, default_mode=0o644,
129                dir_mode=0o755, exe_mode=0o755, blacklist=None):
130     """Initialization.
131
132     Args:
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.
140     """
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
149
150   @staticmethod
151   def Log(src, dest, directory):
152     sep = ' [d] -> ' if directory else ' -> '
153     logging.debug('%s %s %s', src, sep, dest)
154
155   def _PathIsBlacklisted(self, path):
156     for pattern in self.blacklist:
157       if re.match(pattern, path):
158         return True
159     return False
160
161   def _CopyFile(self, src, dest, path):
162     """Perform the copy.
163
164     Args:
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.)
169     """
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
174                      self.strip_flags)
175       cros_build_lib.DebugRunCommand(
176           [self.strip_bin] + strip_flags + ['-o', dest, src])
177       shutil.copystat(src, dest)
178     else:
179       shutil.copy2(src, dest)
180
181     mode = path.mode
182     if mode is None:
183       mode = self.exe_mode if path.exe else self.default_mode
184     os.chmod(dest, mode)
185
186   def Copy(self, src_base, dest_base, path, strict=False, sloppy=False):
187     """Copy artifact(s) from source directory to destination.
188
189     Args:
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.
195
196     Returns:
197       A list of the artifacts copied.
198     """
199     assert not (strict and sloppy), 'strict and sloppy are not compatible.'
200     copied_paths = []
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)
206     if not paths:
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)
215       else:
216         logging.warn('%s does not exist and is required.  Skipping anyway.',
217                      src)
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))
223     else:
224       for p in paths:
225         rel_src = os.path.relpath(p, src_base)
226         if path.dest is None:
227           rel_dest = rel_src
228         elif path.dest.endswith('/'):
229           rel_dest = os.path.join(path.dest, os.path.basename(p))
230         else:
231           rel_dest = path.dest
232         assert not rel_dest.endswith('/')
233         dest = os.path.join(dest_base, rel_dest)
234
235         copied_paths.append(p)
236         self.Log(p, dest, os.path.isdir(p))
237         if 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):
242               continue
243             if sub_path.endswith('/'):
244               osutils.SafeMakedirs(sub_dest, mode=self.dir_mode)
245             else:
246               self._CopyFile(sub_path, sub_dest, path)
247         else:
248           self._CopyFile(p, dest, path)
249
250     return copied_paths
251
252
253 class Path(object):
254   """Represents an artifact to be copied from build dir to staging dir."""
255
256   def __init__(self, src, exe=False, cond=None, dest=None, mode=None,
257                optional=False, strip=True, owner=None):
258     """Initializes the object.
259
260     Args:
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
268            not be stripped.
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
275             directories.
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
281              remote system.
282     """
283     self.src = src
284     self.exe = exe
285     self.cond = cond
286     self.dest = dest
287     self.mode = mode
288     self.optional = optional
289     self.strip = strip
290     self.owner = owner
291
292   def ShouldProcess(self, gyp_defines, staging_flags):
293     """Tests whether this artifact should be copied."""
294     if self.cond:
295       return self.cond(gyp_defines, staging_flags)
296     return True
297
298
299 _DISABLE_NACL = 'disable_nacl'
300 _USE_DRM = 'use_drm'
301
302
303 _APP_SHELL_FLAG = 'app_shell'
304 _CHROME_INTERNAL_FLAG = 'chrome_internal'
305 _CONTENT_SHELL_FLAG = 'content_shell'
306 _ECS_FLAG = 'ecs'
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)
311
312 _CHROME_SANDBOX_DEST = 'chrome-sandbox'
313 C = Conditions
314
315
316 _COPY_PATHS = (
317   Path('app_shell',
318        exe=True,
319        cond=C.StagingFlagSet(_APP_SHELL_FLAG)),
320   Path('app_shell.pak',
321        cond=C.StagingFlagSet(_APP_SHELL_FLAG)),
322   Path('ash_shell',
323        cond=C.GypSet(_USE_DRM)),
324   Path('aura_demo',
325        cond=C.GypSet(_USE_DRM)),
326   Path('chrome',
327        exe=True),
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',
335        exe=True,
336        cond=C.StagingFlagSet(_CONTENT_SHELL_FLAG)),
337   Path('content_shell.pak',
338        cond=C.StagingFlagSet(_CONTENT_SHELL_FLAG)),
339   Path('icudtl.dat',
340        cond=C.GypSet('icu_use_data_file_flag')),
341   Path('keyboard_resources.pak'),
342   Path('lib/*.so',
343        exe=True,
344        cond=C.GypSet('component', value='shared_library')),
345   # Set as optional for backwards compatibility.
346   Path('lib/libpeerconnection.so',
347        exe=True,
348        cond=C.StagingFlagSet(_CHROME_INTERNAL_FLAG),
349        optional=True),
350   # Set as optional for backwards compatibility.
351   Path('libexif.so',
352        exe=True,
353        optional=True),
354   Path('libffmpegsumo.so',
355        exe=True),
356   Path('libpdf.so',
357        exe=True,
358        cond=C.StagingFlagSet(_PDF_FLAG)),
359   Path('libppGoogleNaClPluginChrome.so',
360        exe=True,
361        cond=C.GypNotSet(_DISABLE_NACL)),
362   Path('libosmesa.so',
363        exe=True,
364        optional=True),
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',
368        exe=True,
369        strip=False,
370        cond=C.StagingFlagSet(_CHROME_INTERNAL_FLAG)),
371   Path('libwidevinecdm.so',
372        exe=True,
373        strip=False,
374        cond=C.StagingFlagSet(_CHROME_INTERNAL_FLAG)),
375   Path('locales/'),
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)),
383   Path('nacl_helper',
384        exe=True,
385        optional=True,
386        cond=C.GypNotSet(_DISABLE_NACL)),
387   Path('pnacl/',
388        cond=C.GypNotSet(_DISABLE_NACL)),
389   Path('resources/'),
390   Path('resources.pak'),
391   Path('xdg-settings'),
392   Path('*.png'),
393 )
394
395 _COPY_PATHS_CONTENT_SHELL = (
396   Path('chrome[-_]sandbox', exe=True, mode=0o4755, owner='root:chrome'),
397   Path('dogfood/dumpstate_uploader',
398        exe=True,
399        cond=C.StagingFlagNotSet(_ECS_FLAG)),
400   Path('dogfood/fake.dmp', cond=C.StagingFlagNotSet(_ECS_FLAG)),
401   Path('content_shell',
402        exe=True,
403        cond=C.StagingFlagSet(_ECS_FLAG)),
404   Path('content_shell.pak', cond=C.StagingFlagSet(_ECS_FLAG)),
405   Path('eureka_shell',
406        exe=True,
407        cond=C.StagingFlagNotSet(_ECS_FLAG),
408        mode=0o755,
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')),
417   Path('libck_pe.so',
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'),
425   Path('osd.conf',
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',
444        exe=True,
445        cond=C.StagingFlagNotSet(_ECS_FLAG),
446        mode=0o755,
447        owner='updater:updater'),
448   Path('v2mirroring',
449        exe=True,
450        cond=C.StagingFlagNotSet(_ECS_FLAG),
451        mode=0o755,
452        owner='chrome:chrome'),
453   Path('watchdog/wd.dmp', cond=C.StagingFlagNotSet(_ECS_FLAG)),
454 )
455
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', '{}', '+'])
461
462
463 def GetCopyPaths(content_shell=False):
464   """Returns the list of copy paths used as a filter for staging files.
465
466   Args:
467     content_shell: Boolean on whether to return the copy paths used for a
468                    content shell deployment or a chrome deployment.
469
470   Returns:
471     The list of paths to use as a filter for staging files.
472   """
473   if content_shell:
474     return _COPY_PATHS_CONTENT_SHELL
475   else:
476     return _COPY_PATHS
477
478
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.
483
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.
487
488   Args:
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
500       STAGING_FLAGS.
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.
503   """
504   os.mkdir(os.path.join(staging_dir, 'plugins'), 0o755)
505
506   if gyp_defines is None:
507     gyp_defines = {}
508   if staging_flags is None:
509     staging_flags = []
510
511   copier = Copier(strip_bin=strip_bin, strip_flags=strip_flags)
512   copied_paths = []
513   for p in copy_paths:
514     if not strict or p.ShouldProcess(gyp_defines, staging_flags):
515       copied_paths += copier.Copy(build_dir, staging_dir, p, strict=strict,
516                                   sloppy=sloppy)
517
518   if not copied_paths:
519     raise MissingPathError('Couldn\'t find anything to copy!\n'
520                            'Are you looking in the right directory?\n'
521                            'Aborting copy...')
522
523   _FixPermissions(staging_dir)