Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_list_modified_packages.py
1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Calculate what workon packages have changed since the last build.
6
7 A workon package is treated as changed if any of the below are true:
8   1) The package is not installed.
9   2) A file exists in the associated repository which has a newer modification
10      time than the installed package.
11   3) The source ebuild has a newer modification time than the installed package.
12
13 Some caveats:
14   - We do not look at eclasses. This replicates the existing behavior of the
15     commit queue, which also does not look at eclass changes.
16   - We do not try to fallback to the non-workon package if the local tree is
17     unmodified. This is probably a good thing, since developers who are
18     "working on" a package want to compile it locally.
19   - Portage only stores the time that a package finished building, so we
20     aren't able to detect when users modify source code during builds.
21 """
22
23 import errno
24 import logging
25 import multiprocessing
26 import optparse
27 import os
28 try:
29   import Queue
30 except ImportError:
31   # Python-3 renamed to "queue".  We still use Queue to avoid collisions
32   # with naming variables as "queue".  Maybe we'll transition at some point.
33   # pylint: disable=F0401
34   import queue as Queue
35
36 from chromite.cbuildbot import constants
37 from chromite.cbuildbot import portage_utilities
38 from chromite.lib import cros_build_lib
39 from chromite.lib import git
40 from chromite.lib import osutils
41 from chromite.lib import parallel
42
43
44 class WorkonProjectsMonitor(object):
45   """Class for monitoring the last modification time of workon projects.
46
47   Members:
48     _tasks: A list of the (project, path) pairs to check.
49     _result_queue: A queue. When GetProjectModificationTimes is called,
50       (project, mtime) tuples are pushed onto the end of this queue.
51   """
52
53   def __init__(self, projects):
54     """Create a new object for checking what projects were modified and when.
55
56     Args:
57       projects: A list of the project names we are interested in monitoring.
58     """
59     manifest = git.ManifestCheckout.Cached(constants.SOURCE_ROOT)
60     self._tasks = []
61     for project in set(projects).intersection(manifest.checkouts_by_name):
62       for checkout in manifest.FindCheckouts(project):
63         self._tasks.append((project, checkout.GetPath(absolute=True)))
64     self._result_queue = multiprocessing.Queue(len(self._tasks))
65
66   def _EnqueueProjectModificationTime(self, project, path):
67     """Calculate the last time that this project was modified, and enqueue it.
68
69     Args:
70       project: The project to look at.
71       path: The path associated with the specified project.
72     """
73     if os.path.isdir(path):
74       self._result_queue.put((project, self._LastModificationTime(path)))
75
76   def _LastModificationTime(self, path):
77     """Calculate the last time a directory subtree was modified.
78
79     Args:
80       path: Directory to look at.
81     """
82     cmd = 'find . -name .git -prune -o -printf "%T@\n" | sort -nr | head -n1'
83     ret = cros_build_lib.RunCommand(cmd, cwd=path, shell=True, print_cmd=False,
84                                     capture_output=True)
85     return float(ret.output) if ret.output else 0
86
87   def GetProjectModificationTimes(self):
88     """Get the last modification time of each specified project.
89
90     Returns:
91       A dictionary mapping project names to last modification times.
92     """
93     task = self._EnqueueProjectModificationTime
94     parallel.RunTasksInProcessPool(task, self._tasks)
95
96     # Create a dictionary mapping project names to last modification times.
97     # All of the workon projects are already stored in the queue, so we can
98     # retrieve them all without waiting any longer.
99     mtimes = {}
100     while True:
101       try:
102         project, mtime = self._result_queue.get_nowait()
103       except Queue.Empty:
104         break
105       mtimes[project] = mtime
106     return mtimes
107
108
109 class WorkonPackageInfo(object):
110   """Class for getting information about workon packages.
111
112   Members:
113     cp: The package name (e.g. chromeos-base/power_manager).
114     mtime: The modification time of the installed package.
115     project: The project associated with the installed package.
116     src_ebuild_mtime: The modification time of the source ebuild.
117   """
118
119   def __init__(self, cp, mtime, projects, src_ebuild_mtime):
120     self.cp = cp
121     self.pkg_mtime = int(mtime)
122     self.projects = projects
123     self.src_ebuild_mtime = src_ebuild_mtime
124
125
126 def ListWorkonPackages(board, host, all_opt=False):
127   """List the packages that are currently being worked on.
128
129   Args:
130     board: The board to look at. If host is True, this should be set to None.
131     host: Whether to look at workon packages for the host.
132     all_opt: Pass --all to cros_workon. For testing purposes.
133   """
134   cmd = [os.path.join(constants.CROSUTILS_DIR, 'cros_workon'), 'list']
135   cmd.extend(['--host'] if host else ['--board', board])
136   if all_opt:
137     cmd.append('--all')
138   result = cros_build_lib.RunCommand(cmd, print_cmd=False, capture_output=True)
139   return result.output.split()
140
141
142 def ListWorkonPackagesInfo(board, host):
143   """Find the specified workon packages for the specified board.
144
145   Args:
146     board: The board to look at. If host is True, this should be set to None.
147     host: Whether to look at workon packages for the host.
148
149   Returns:
150     A list of unique packages being worked on.
151   """
152   # Import portage late so that this script can be imported outside the chroot.
153   # pylint: disable=F0401
154   import portage.const
155   packages = ListWorkonPackages(board, host)
156   if not packages:
157     return []
158   results = {}
159   install_root = cros_build_lib.GetSysroot(board=board)
160   vdb_path = os.path.join(install_root, portage.const.VDB_PATH)
161   buildroot, both = constants.SOURCE_ROOT, constants.BOTH_OVERLAYS
162   for overlay in portage_utilities.FindOverlays(both, board, buildroot):
163     for filename, projects in portage_utilities.GetWorkonProjectMap(overlay,
164                                                                     packages):
165       # chromeos-base/power_manager/power_manager-9999
166       # cp = chromeos-base/power_manager
167       # cpv = chromeos-base/power_manager-9999
168       category, pn, p = portage_utilities.SplitEbuildPath(filename)
169       cp = '%s/%s' % (category, pn)
170       cpv = '%s/%s' % (category, p)
171
172       # Get the time the package finished building. TODO(build): Teach Portage
173       # to store the time the package started building and use that here.
174       pkg_mtime_file = os.path.join(vdb_path, cpv, 'BUILD_TIME')
175       try:
176         pkg_mtime = int(osutils.ReadFile(pkg_mtime_file))
177       except EnvironmentError as ex:
178         if ex.errno != errno.ENOENT:
179           raise
180         pkg_mtime = 0
181
182       # Get the modificaton time of the ebuild in the overlay.
183       src_ebuild_mtime = os.lstat(os.path.join(overlay, filename)).st_mtime
184
185       # Write info into the results dictionary, overwriting any previous
186       # values. This ensures that overlays override appropriately.
187       results[cp] = WorkonPackageInfo(cp, pkg_mtime, projects, src_ebuild_mtime)
188
189   return results.values()
190
191
192 def ListModifiedWorkonPackages(board, host):
193   """List the workon packages that need to be rebuilt.
194
195   Args:
196     board: The board to look at. If host is True, this should be set to None.
197     host: Whether to look at workon packages for the host.
198   """
199   packages = ListWorkonPackagesInfo(board, host)
200   if packages:
201     projects = []
202     for info in packages:
203       projects.extend(info.projects)
204     mtimes = WorkonProjectsMonitor(projects).GetProjectModificationTimes()
205     for info in packages:
206       mtime = int(max([mtimes.get(p, 0) for p in info.projects] +
207                       [info.src_ebuild_mtime]))
208       if mtime >= info.pkg_mtime:
209         yield info.cp
210
211
212 def _ParseArguments(argv):
213   parser = optparse.OptionParser(usage='USAGE: %prog [options]')
214
215   parser.add_option('--board', default=None,
216                     dest='board',
217                     help='Board name')
218   parser.add_option('--host', default=False,
219                     dest='host', action='store_true',
220                     help='Look at host packages instead of board packages')
221
222   flags, remaining_arguments = parser.parse_args(argv)
223   if not flags.board and not flags.host:
224     parser.print_help()
225     cros_build_lib.Die('--board or --host is required')
226   if flags.board is not None and flags.host:
227     parser.print_help()
228     cros_build_lib.Die('--board and --host are mutually exclusive')
229   if remaining_arguments:
230     parser.print_help()
231     cros_build_lib.Die('Invalid arguments')
232   return flags
233
234
235 def main(argv):
236   logging.getLogger().setLevel(logging.INFO)
237   flags = _ParseArguments(argv)
238   modified = ListModifiedWorkonPackages(flags.board, flags.host)
239   print ' '.join(sorted(modified))