blob: ee3f27a4c233a034612886c3fd77b45dec871140 [file] [log] [blame]
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +01001#!/usr/bin/env python3
2# Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5
6import adb_utils
Christoffer Quist Adamsen1c07af92022-08-11 13:46:25 +02007import profile_utils
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +01008
9import argparse
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020010import os
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010011import sys
12import time
13
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020014class Device:
15
16 def __init__(self, device_id, device_pin):
17 self.device_id = device_id
18 self.device_pin = device_pin
19
20def extend_startup_descriptors(startup_descriptors, iteration, device, options):
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020021 (logcat, profile, profile_classes_and_methods) = \
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020022 generate_startup_profile(device, options)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020023 if options.logcat:
24 write_tmp_logcat(logcat, iteration, options)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020025 current_startup_descriptors = get_r8_startup_descriptors_from_logcat(
26 logcat, options)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020027 else:
28 write_tmp_profile(profile, iteration, options)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020029 write_tmp_profile_classes_and_methods(
30 profile_classes_and_methods, iteration, options)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020031 current_startup_descriptors = \
Christoffer Quist Adamsen1c07af92022-08-11 13:46:25 +020032 profile_utils.transform_art_profile_to_r8_startup_list(
Christoffer Quist Adamsen81a87922022-08-11 13:46:16 +020033 profile_classes_and_methods)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020034 write_tmp_startup_descriptors(current_startup_descriptors, iteration, options)
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +020035 new_startup_descriptors = add_r8_startup_descriptors(
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010036 startup_descriptors, current_startup_descriptors)
Christoffer Quist Adamsen1c07af92022-08-11 13:46:25 +020037 number_of_new_startup_descriptors = \
38 len(new_startup_descriptors) - len(startup_descriptors)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010039 if options.out is not None:
40 print(
41 'Found %i new startup descriptors in iteration %i'
42 % (number_of_new_startup_descriptors, iteration + 1))
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +020043 return new_startup_descriptors
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010044
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020045def generate_startup_profile(device, options):
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020046 logcat = None
47 profile = None
48 profile_classes_and_methods = None
49 if options.use_existing_profile:
50 # Verify presence of profile.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020051 adb_utils.check_app_has_profile_data(options.app_id, device.device_id)
52 profile = adb_utils.get_profile_data(options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020053 profile_classes_and_methods = \
54 adb_utils.get_classes_and_methods_from_app_profile(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020055 options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020056 else:
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010057 # Unlock device.
58 tear_down_options = adb_utils.prepare_for_interaction_with_device(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020059 device.device_id, device.device_pin)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010060
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020061 logcat_process = None
62 if options.logcat:
63 # Clear logcat and start capturing logcat.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020064 adb_utils.clear_logcat(device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020065 logcat_process = adb_utils.start_logcat(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020066 device.device_id, format='tag', filter='r8:I ActivityTaskManager:I *:S')
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020067 else:
68 # Clear existing profile data.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020069 adb_utils.clear_profile_data(options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020070
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010071 # Launch activity to generate startup profile on device.
72 adb_utils.launch_activity(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020073 options.app_id, options.main_activity, device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010074
75 # Wait for activity startup.
76 time.sleep(options.startup_duration)
77
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020078 if options.logcat:
79 # Get startup descriptors from logcat.
80 logcat = adb_utils.stop_logcat(logcat_process)
81 else:
82 # Capture startup profile.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020083 adb_utils.capture_app_profile_data(options.app_id, device.device_id)
84 profile = adb_utils.get_profile_data(options.app_id, device.device_id)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020085 profile_classes_and_methods = \
86 adb_utils.get_classes_and_methods_from_app_profile(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020087 options.app_id, device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010088
89 # Shutdown app.
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020090 adb_utils.stop_app(options.app_id, device.device_id)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020091 adb_utils.teardown_after_interaction_with_device(
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +020092 tear_down_options, device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +010093
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020094 return (logcat, profile, profile_classes_and_methods)
95
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +020096def get_r8_startup_descriptors_from_logcat(logcat, options):
97 post_startup = False
98 startup_descriptors = {}
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +020099 for line in logcat:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200100 line_elements = parse_logcat_line(line)
101 if line_elements is None:
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200102 continue
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200103 (priority, tag, message) = line_elements
104 if tag == 'ActivityTaskManager':
105 if message.startswith('START') \
106 or message.startswith('Activity pause timeout for') \
107 or message.startswith('Activity top resumed state loss timeout for') \
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200108 or message.startswith('Force removing') \
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200109 or message.startswith(
110 'Launch timeout has expired, giving up wake lock!'):
111 continue
112 elif message.startswith('Displayed %s/' % options.app_id):
113 print('Entering post startup: %s' % message)
114 post_startup = True
115 continue
116 elif tag == 'r8':
117 if is_startup_descriptor(message):
118 startup_descriptors[message] = {
119 'conditional_startup': False,
120 'post_startup': post_startup
121 }
122 continue
123 # Reaching here means we didn't expect this line.
124 report_unrecognized_logcat_line(line)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200125 return startup_descriptors
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100126
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200127def is_startup_descriptor(string):
128 # The descriptor should start with the holder (possibly prefixed with 'S').
129 if not any(string.startswith('%sL' % flags) for flags in ['', 'S']):
130 return False
131 # The descriptor should end with ';', a primitive type, or void.
132 if not string.endswith(';') \
133 and not any(string.endswith(c) for c in get_primitive_descriptors()) \
134 and not string.endswith('V'):
135 return False
136 return True
137
138def get_primitive_descriptors():
139 return ['Z', 'B', 'S', 'C', 'I', 'F', 'J', 'D']
140
141def parse_logcat_line(line):
142 if line == '--------- beginning of kernel':
143 return None
144 if line == '--------- beginning of main':
145 return None
146 if line == '--------- beginning of system':
147 return None
148
149 priority = None
150 tag = None
151
152 try:
153 priority_end = line.index('/')
154 priority = line[0:priority_end]
155 line = line[priority_end + 1:]
156 except ValueError:
157 return report_unrecognized_logcat_line(line)
158
159 try:
160 tag_end = line.index(':')
161 tag = line[0:tag_end].strip()
162 line = line[tag_end + 1 :]
163 except ValueError:
164 return report_unrecognized_logcat_line(line)
165
166 message = line.strip()
167 return (priority, tag, message)
168
169def report_unrecognized_logcat_line(line):
170 print('Unrecognized line in logcat: %s' % line)
171
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200172def add_r8_startup_descriptors(old_startup_descriptors, startup_descriptors_to_add):
173 new_startup_descriptors = {}
174 if len(old_startup_descriptors) == 0:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200175 for startup_descriptor, flags in startup_descriptors_to_add.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200176 new_startup_descriptors[startup_descriptor] = flags.copy()
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200177 else:
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200178 # Merge the new startup descriptors with the old descriptors in a way so
179 # that new startup descriptors are added next to the startup descriptors
180 # they are close to in the newly generated list of startup descriptors.
181 startup_descriptors_to_add_after_key = {}
182 startup_descriptors_to_add_in_the_end = {}
183 closest_seen_startup_descriptor = None
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200184 for startup_descriptor, flags in startup_descriptors_to_add.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200185 if startup_descriptor in old_startup_descriptors:
186 closest_seen_startup_descriptor = startup_descriptor
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200187 else:
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200188 if closest_seen_startup_descriptor is None:
189 # Insert this new startup descriptor in the end of the result.
190 startup_descriptors_to_add_in_the_end[startup_descriptor] = flags
191 else:
192 # Record that this should be inserted after
193 # closest_seen_startup_descriptor.
194 pending_startup_descriptors = \
195 startup_descriptors_to_add_after_key.setdefault(
196 closest_seen_startup_descriptor, {})
197 pending_startup_descriptors[startup_descriptor] = flags
198 for startup_descriptor, flags in old_startup_descriptors.items():
199 # Merge flags if this also exists in startup_descriptors_to_add.
200 if startup_descriptor in startup_descriptors_to_add:
201 merged_flags = flags.copy()
202 other_flags = startup_descriptors_to_add[startup_descriptor]
203 assert not other_flags['conditional_startup']
204 if other_flags['post_startup']:
205 merged_flags['post_startup'] = True
206 new_startup_descriptors[startup_descriptor] = merged_flags
207 else:
208 new_startup_descriptors[startup_descriptor] = flags.copy()
209 # Flush startup descriptors that followed this item in the new trace.
210 if startup_descriptor in startup_descriptors_to_add_after_key:
211 pending_startup_descriptors = \
212 startup_descriptors_to_add_after_key[startup_descriptor]
213 for pending_startup_descriptor, pending_flags \
214 in pending_startup_descriptors.items():
215 new_startup_descriptors[pending_startup_descriptor] = \
216 pending_flags.copy()
217 # Insert remaining new startup descriptors in the end.
218 for startup_descriptor, flags \
219 in startup_descriptors_to_add_in_the_end.items():
220 assert startup_descriptor not in new_startup_descriptors
221 new_startup_descriptors[startup_descriptor] = flags.copy()
222 return new_startup_descriptors
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100223
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200224def write_tmp_binary_artifact(artifact, iteration, options, name):
225 if not options.tmp_dir:
226 return
227 out_dir = os.path.join(options.tmp_dir, str(iteration))
228 os.makedirs(out_dir, exist_ok=True)
229 path = os.path.join(out_dir, name)
230 with open(path, 'wb') as f:
231 f.write(artifact)
232
233def write_tmp_textual_artifact(artifact, iteration, options, name, item_to_string=None):
234 if not options.tmp_dir:
235 return
236 out_dir = os.path.join(options.tmp_dir, str(iteration))
237 os.makedirs(out_dir, exist_ok=True)
238 path = os.path.join(out_dir, name)
239 with open(path, 'w') as f:
240 for item in artifact:
241 f.write(item if item_to_string is None else item_to_string(item))
242 f.write('\n')
243
244def write_tmp_logcat(logcat, iteration, options):
245 write_tmp_textual_artifact(logcat, iteration, options, 'logcat.txt')
246
247def write_tmp_profile(profile, iteration, options):
248 write_tmp_binary_artifact(profile, iteration, options, 'primary.prof')
249
250def write_tmp_profile_classes_and_methods(
251 profile_classes_and_methods, iteration, options):
252 def item_to_string(item):
Christoffer Quist Adamsen81a87922022-08-11 13:46:16 +0200253 (descriptor, flags) = item
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200254 return '%s%s%s%s' % (
255 'H' if flags.get('hot') else '',
256 'S' if flags.get('startup') else '',
257 'P' if flags.get('post_startup') else '',
258 descriptor)
259 write_tmp_textual_artifact(
Christoffer Quist Adamsen81a87922022-08-11 13:46:16 +0200260 profile_classes_and_methods.items(),
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200261 iteration,
262 options,
263 'profile.txt',
264 item_to_string)
265
266def write_tmp_startup_descriptors(startup_descriptors, iteration, options):
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200267 lines = [
268 startup_descriptor_to_string(startup_descriptor, flags)
269 for startup_descriptor, flags in startup_descriptors.items()]
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200270 write_tmp_textual_artifact(
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200271 lines, iteration, options, 'startup-descriptors.txt')
272
273def startup_descriptor_to_string(startup_descriptor, flags):
274 result = ''
275 if flags['conditional_startup']:
276 pass # result += 'C'
277 if flags['post_startup']:
278 pass # result += 'P'
279 result += startup_descriptor
280 return result
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200281
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200282def should_include_startup_descriptor(descriptor, flags, options):
283 if flags.get('conditional_startup') \
284 and not options.include_conditional_startup:
285 return False
286 if flags.get('post_startup') \
287 and not flags.get('startup') \
288 and not options.include_post_startup:
289 return False
290 return True
291
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100292def parse_options(argv):
293 result = argparse.ArgumentParser(
294 description='Generate a perfetto trace file.')
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200295 result.add_argument('--apk',
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200296 help='Path to the .apk')
297 result.add_argument('--apks',
298 help='Path to the .apks')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100299 result.add_argument('--app-id',
300 help='The application ID of interest',
301 required=True)
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200302 result.add_argument('--bundle',
303 help='Path to the .aab')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100304 result.add_argument('--device-id',
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200305 help='Device id (e.g., emulator-5554).',
306 action='append')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100307 result.add_argument('--device-pin',
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200308 help='Device pin code (e.g., 1234)',
309 action='append')
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200310 result.add_argument('--logcat',
311 action='store_true',
312 default=False)
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200313 result.add_argument('--include-conditional-startup',
314 help='Include conditional startup classes and methods in '
315 'the R8 startup descriptors',
316 action='store_true',
317 default=False)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100318 result.add_argument('--include-post-startup',
319 help='Include post startup classes and methods in the R8 '
320 'startup descriptors',
321 action='store_true',
322 default=False)
323 result.add_argument('--iterations',
324 help='Number of profiles to generate',
325 default=1,
326 type=int)
327 result.add_argument('--main-activity',
328 help='Main activity class name')
329 result.add_argument('--out',
330 help='File where to store startup descriptors (defaults '
331 'to stdout)')
332 result.add_argument('--startup-duration',
333 help='Duration in seconds before shutting down app',
334 default=15,
335 type=int)
Christoffer Quist Adamsenc6b19de2022-04-21 13:07:53 +0200336 result.add_argument('--tmp-dir',
337 help='Directory where to store intermediate artifacts'
338 ' (by default these are not emitted)')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100339 result.add_argument('--until-stable',
340 help='Repeat profile generation until no new startup '
341 'descriptors are found',
342 action='store_true',
343 default=False)
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200344 result.add_argument('--until-stable-iterations',
345 help='Number of times that profile generation must must '
346 'not find new startup descriptors before exiting',
347 default=1,
348 type=int)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100349 result.add_argument('--use-existing-profile',
350 help='Do not launch app to generate startup profile',
351 action='store_true',
352 default=False)
353 options, args = result.parse_known_args(argv)
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200354
355 # Read the device pins.
356 device_pins = options.device_pin or []
357 del options.device_pin
358
359 # Convert the device ids and pins into a list of devices.
360 options.devices = []
361 if options.device_id is None:
362 # Assume a single device is attached.
363 options.devices.append(
364 Device(None, device_pins[0] if len(device_pins) > 0 else None))
365 else:
366 for i in range(len(options.device_id)):
367 device_id = options.device_id[i]
368 device_pin = device_pins[i] if i < len(device_pins) else None
369 options.devices.append(Device(device_id, device_pin))
370 del options.device_id
371
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200372 paths = [
373 path for path in [options.apk, options.apks, options.bundle]
374 if path is not None]
375 assert len(paths) <= 1, 'Expected at most one .apk, .apks, or .aab file.'
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100376 assert options.main_activity is not None or options.use_existing_profile, \
377 'Argument --main-activity is required except when running with ' \
378 '--use-existing-profile.'
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200379
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100380 return options, args
381
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200382def run_on_device(device, options, startup_descriptors):
383 adb_utils.root(device.device_id)
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200384 if options.apk:
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200385 adb_utils.uninstall(options.app_id, device.device_id)
386 adb_utils.install(options.apk, device.device_id)
Christoffer Quist Adamsen4d9fc512022-08-11 19:59:44 +0200387 elif options.apks:
388 adb_utils.uninstall(options.app_id, device.device_id)
389 adb_utils.install_apks(options.apks, device.device_id)
390 elif options.bundle:
391 adb_utils.uninstall(options.app_id, device.device_id)
392 adb_utils.install_bundle(options.bundle, device.device_id)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100393 if options.until_stable:
394 iteration = 0
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200395 stable_iterations = 0
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100396 while True:
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200397 old_startup_descriptors = startup_descriptors
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200398 startup_descriptors = extend_startup_descriptors(
399 old_startup_descriptors, iteration, device, options)
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200400 diff = len(startup_descriptors) - len(old_startup_descriptors)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100401 if diff == 0:
Christoffer Quist Adamsen2d5d9632022-04-06 14:48:30 +0200402 stable_iterations = stable_iterations + 1
403 if stable_iterations == options.until_stable_iterations:
404 break
405 else:
406 stable_iterations = 0
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100407 iteration = iteration + 1
408 else:
409 for iteration in range(options.iterations):
Christoffer Quist Adamsen3937f502022-08-08 14:44:31 +0200410 startup_descriptors = extend_startup_descriptors(
411 startup_descriptors, iteration, device, options)
412 return startup_descriptors
413
414def main(argv):
415 (options, args) = parse_options(argv)
416 startup_descriptors = {}
417 for device in options.devices:
418 startup_descriptors = run_on_device(device, options, startup_descriptors)
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100419 if options.out is not None:
420 with open(options.out, 'w') as f:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200421 for startup_descriptor, flags in startup_descriptors.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200422 if should_include_startup_descriptor(startup_descriptor, flags, options):
423 f.write(startup_descriptor_to_string(startup_descriptor, flags))
424 f.write('\n')
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100425 else:
Christoffer Quist Adamsen1f60e522022-08-03 14:13:03 +0200426 for startup_descriptor, flags in startup_descriptors.items():
Christoffer Quist Adamsen13e43132022-08-05 11:31:56 +0200427 if should_include_startup_descriptor(startup_descriptor, flags, options):
428 print(startup_descriptor_to_string(startup_descriptor, flags))
Christoffer Quist Adamsen2771c362022-03-14 15:36:43 +0100429
430if __name__ == '__main__':
431 sys.exit(main(sys.argv[1:]))