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