blob: fcebd82f6b176318b94ad5dc3eaac181bd67b6de [file] [log] [blame]
George Burgess IV85e69332020-01-16 17:33:16 -08001# -*- coding: utf-8 -*-
2# Copyright 2020 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"""Enumerates all Chrome OS packages that are marked as `hot`.
7
8Dumps results as a list of package names to a JSON file. Hotness is
9determined by statically analyzing an ebuild.
10
11Primarily intended for use by the Chrome OS toolchain team.
12"""
13
14from __future__ import print_function
15
16import json
17import os
18
19from chromite.lib import commandline
20from chromite.lib import cros_logging as logging
21from chromite.lib import portage_util
22
23
24def is_ebuild_marked_hot(ebuild_path):
25 with open(ebuild_path) as f:
26 # The detection of this is intentionally super simple.
27 #
28 # It's important to note that while this is a function, we also use it in
29 # comments in packages that are forcibly optimized for speed in some other
30 # way, like chromeos-chrome.
31 return any('cros_optimize_package_for_speed' in line for line in f)
32
33
34def enumerate_package_ebuilds():
35 """Determines package -> ebuild mappings for all packages.
36
37 Yields a series of (package_path, package_name, [path_to_ebuilds]). This may
38 yield the same package name multiple times if it's available in multiple
39 overlays.
40 """
41 for overlay in portage_util.FindOverlays(overlay_type='both'):
42 logging.debug('Found overlay %s', overlay)
43
44 # Note that portage_util.GetOverlayEBuilds can't be used here, since that
45 # specifically only searches for cros_workon candidates. We care about
46 # everything we can possibly build.
47 for dir_path, dir_names, file_names in os.walk(overlay):
48 ebuilds = [x for x in file_names if x.endswith('.ebuild')]
49 if not ebuilds:
50 continue
51
52 # os.walk directly uses `dir_names` to figure out what to walk next. If
53 # there are ebuilds here, walking any lower is a waste, so don't do it.
54 del dir_names[:]
55
56 ebuild_dir = os.path.basename(dir_path)
57 ebuild_parent_dir = os.path.basename(os.path.dirname(dir_path))
58 package_name = '%s/%s' % (ebuild_parent_dir, ebuild_dir)
59 yield dir_path, package_name, ebuilds
60
61
62def main(argv):
63 parser = commandline.ArgumentParser(description=__doc__)
64 parser.add_argument('--output', required=True)
65 opts = parser.parse_args(argv)
66
67 ebuilds_found = 0
68 packages_found = 0
69 merged_packages = 0
70 mappings = {}
71 for package_dir, package, ebuilds in enumerate_package_ebuilds():
72 packages_found += 1
73 ebuilds_found += len(ebuilds)
74 logging.debug('Found package %r in %r with ebuilds %r', package,
75 package_dir, ebuilds)
76
77 is_marked_hot = any(
78 is_ebuild_marked_hot(os.path.join(package_dir, x)) for x in ebuilds)
79 if is_marked_hot:
80 logging.debug('Package is marked as hot')
81 else:
82 logging.debug('Package is not marked as hot')
83
84 if package in mappings:
85 logging.warning('Multiple entries found for package %r; merging', package)
86 merged_packages += 1
87 mappings[package] = is_marked_hot or mappings[package]
88 else:
89 mappings[package] = is_marked_hot
90
91 hot_packages = sorted(
92 package for package, is_hot in mappings.items() if is_hot)
93
94 logging.info('%d ebuilds found', ebuilds_found)
95 logging.info('%d packages found', packages_found)
96 logging.info('%d packages merged', merged_packages)
97 logging.info('%d hot packages found, total', len(hot_packages))
98
99 with open(opts.output, 'w') as f:
100 json.dump(hot_packages, f)