blob: aa1e7f2c8d0ec120fecdd68270d01fe122fefd09 [file] [log] [blame]
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -07001#!/usr/bin/env python3
2#
3# Copyright 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070016
17"""Provide the utilities for framework generation.
18"""
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070019
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070020import os
21import subprocess
22import xml.etree.ElementTree as element_tree
Adithya Srinivasan8dce9d72019-07-11 14:26:04 -070023
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070024# Extensions unsupported on Android.
Yiwei Zhang95924222020-06-15 09:39:03 -070025_BLOCKED_EXTENSIONS = [
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070026 'VK_EXT_acquire_xlib_display',
27 'VK_EXT_direct_mode_display',
Yiwei Zhang6be097b2020-10-19 20:22:05 -070028 'VK_EXT_directfb_surface',
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070029 'VK_EXT_display_control',
Yiwei Zhangdc792f52019-10-10 16:29:42 -070030 'VK_EXT_display_surface_counter',
31 'VK_EXT_full_screen_exclusive',
32 'VK_EXT_headless_surface',
33 'VK_EXT_metal_surface',
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070034 'VK_FUCHSIA_imagepipe_surface',
Yiwei Zhangdc792f52019-10-10 16:29:42 -070035 'VK_GGP_stream_descriptor_surface',
Trevor David Black4c622a02021-06-28 22:46:14 +000036 'VK_HUAWEI_subpass_shading',
Yiwei Zhangdc792f52019-10-10 16:29:42 -070037 'VK_KHR_display',
38 'VK_KHR_display_swapchain',
39 'VK_KHR_external_fence_win32',
40 'VK_KHR_external_memory_win32',
41 'VK_KHR_external_semaphore_win32',
42 'VK_KHR_mir_surface',
43 'VK_KHR_wayland_surface',
44 'VK_KHR_win32_keyed_mutex',
45 'VK_KHR_win32_surface',
46 'VK_KHR_xcb_surface',
47 'VK_KHR_xlib_surface',
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070048 'VK_MVK_ios_surface',
49 'VK_MVK_macos_surface',
50 'VK_NN_vi_surface',
Trevor David Black4c622a02021-06-28 22:46:14 +000051 'VK_NV_acquire_winrt_display',
Yiwei Zhangdc792f52019-10-10 16:29:42 -070052 'VK_NV_cooperative_matrix',
53 'VK_NV_coverage_reduction_mode',
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070054 'VK_NV_external_memory_win32',
55 'VK_NV_win32_keyed_mutex',
Yiwei Zhangdc792f52019-10-10 16:29:42 -070056 'VK_NVX_image_view_handle',
Trevor David Black4c622a02021-06-28 22:46:14 +000057 'VK_QNX_screen_surface',
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070058]
59
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070060# Extensions having functions exported by the loader.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070061_EXPORTED_EXTENSIONS = [
Yiwei Zhangdc792f52019-10-10 16:29:42 -070062 'VK_ANDROID_external_memory_android_hardware_buffer',
63 'VK_KHR_android_surface',
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070064 'VK_KHR_surface',
65 'VK_KHR_swapchain',
Yiwei Zhangdc792f52019-10-10 16:29:42 -070066]
67
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070068# Functions optional on Android even if extension is advertised.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070069_OPTIONAL_COMMANDS = [
Yiwei Zhangdc792f52019-10-10 16:29:42 -070070 'vkGetSwapchainGrallocUsageANDROID',
71 'vkGetSwapchainGrallocUsage2ANDROID',
Trevor David Black2cc44682022-03-09 00:31:38 +000072 'vkGetSwapchainGrallocUsage3ANDROID',
Trevor David Blackb6ca8422023-07-26 20:00:04 +000073 'vkGetSwapchainGrallocUsage4ANDROID',
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -070074]
75
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070076# Dict for mapping dispatch table to a type.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070077_DISPATCH_TYPE_DICT = {
78 'VkInstance ': 'Instance',
79 'VkPhysicalDevice ': 'Instance',
80 'VkDevice ': 'Device',
81 'VkQueue ': 'Device',
82 'VkCommandBuffer ': 'Device'
83}
Adithya Srinivasan8dce9d72019-07-11 14:26:04 -070084
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070085# Dict for mapping a function to its alias.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070086alias_dict = {}
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070087
88# List of all the Vulkan functions.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070089command_list = []
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070090
91# Dict for mapping a function to an extension.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070092extension_dict = {}
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070093
94# Dict for mapping a function to all its parameters.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070095param_dict = {}
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070096
97# Dict for mapping a function to its return type.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -070098return_type_dict = {}
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -070099
Yiwei Zhang7cc36a52019-10-11 19:02:09 -0700100# List of the sorted Vulkan version codes. e.g. '1_0', '1_1'.
101version_code_list = []
102
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700103# Dict for mapping a function to the core Vulkan API version.
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700104version_dict = {}
Adithya Srinivasan01364142019-07-02 15:52:49 -0700105
Yiwei Zhang7c0c07c2020-07-04 23:49:47 -0700106# Dict for mapping a promoted instance extension to the core Vulkan API version.
107promoted_inst_ext_dict = {}
108
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700109
110def indent(num):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700111 """Returns the requested indents.
112
113 Args:
114 num: Number of the 4-space indents.
115 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700116 return ' ' * num
117
118
119def copyright_and_warning(year):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700120 """Returns the standard copyright and warning codes.
121
122 Args:
123 year: An integer year for the copyright.
124 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700125 return """\
126/*
127 * Copyright """ + str(year) + """ The Android Open Source Project
128 *
129 * Licensed under the Apache License, Version 2.0 (the "License");
130 * you may not use this file except in compliance with the License.
131 * You may obtain a copy of the License at
132 *
133 * http://www.apache.org/licenses/LICENSE-2.0
134 *
135 * Unless required by applicable law or agreed to in writing, software
136 * distributed under the License is distributed on an "AS IS" BASIS,
137 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138 * See the License for the specific language governing permissions and
139 * limitations under the License.
140 */
141
142// WARNING: This file is generated. See ../README.md for instructions.
143
144"""
145
146
147def run_clang_format(args):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700148 """Run clang format on the file.
149
150 Args:
151 args: The file to be formatted.
152 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700153 clang_call = ['clang-format', '--style', 'file', '-i', args]
154 subprocess.check_call(clang_call)
155
156
Yiwei Zhang5365a7b2019-10-11 17:26:44 -0700157def is_extension_internal(ext):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700158 """Returns true if an extension is internal to the loader and drivers.
159
160 The loader should not enumerate this extension.
161
162 Args:
Yiwei Zhang5365a7b2019-10-11 17:26:44 -0700163 ext: Vulkan extension name.
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700164 """
Yiwei Zhang5365a7b2019-10-11 17:26:44 -0700165 return ext == 'VK_ANDROID_native_buffer'
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700166
167
168def base_name(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700169 """Returns a function name without the 'vk' prefix.
170
171 Args:
172 cmd: Vulkan function name.
173 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700174 return cmd[2:]
175
176
Yiwei Zhang5365a7b2019-10-11 17:26:44 -0700177def base_ext_name(ext):
178 """Returns an extension name without the 'VK_' prefix.
179
180 Args:
181 ext: Vulkan extension name.
182 """
183 return ext[3:]
184
185
Yiwei Zhang7cc36a52019-10-11 19:02:09 -0700186def version_code(version):
187 """Returns the version code from a version string.
188
189 Args:
190 version: Vulkan version string.
191 """
192 return version[11:]
193
194
Yiwei Zhang7c0c07c2020-07-04 23:49:47 -0700195def version_2_api_version(version):
196 """Returns the api version from a version string.
197
198 Args:
199 version: Vulkan version string.
200 """
201 return 'VK_API' + version[2:]
202
203
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700204def is_function_supported(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700205 """Returns true if a function is core or from a supportable extension.
206
207 Args:
208 cmd: Vulkan function name.
209 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700210 if cmd not in extension_dict:
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700211 return True
212 else:
Yiwei Zhang95924222020-06-15 09:39:03 -0700213 if extension_dict[cmd] not in _BLOCKED_EXTENSIONS:
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700214 return True
215 return False
216
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700217
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700218def get_dispatch_table_type(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700219 """Returns the dispatch table type for a function.
220
221 Args:
222 cmd: Vulkan function name.
223 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700224 if cmd not in param_dict:
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700225 return None
226
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700227 if param_dict[cmd]:
228 return _DISPATCH_TYPE_DICT.get(param_dict[cmd][0][0], 'Global')
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700229 return 'Global'
230
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700231
232def is_globally_dispatched(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700233 """Returns true if the function is global, which is not dispatched.
234
235 Only global functions and functions handled in the loader top without calling
236 into lower layers are not dispatched.
237
238 Args:
239 cmd: Vulkan function name.
240 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700241 return is_function_supported(cmd) and get_dispatch_table_type(cmd) == 'Global'
242
243
244def is_instance_dispatched(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700245 """Returns true for functions that can have instance-specific dispatch.
246
247 Args:
248 cmd: Vulkan function name.
249 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700250 return (is_function_supported(cmd) and
251 get_dispatch_table_type(cmd) == 'Instance')
252
253
254def is_device_dispatched(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700255 """Returns true for functions that can have device-specific dispatch.
256
257 Args:
258 cmd: Vulkan function name.
259 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700260 return is_function_supported(cmd) and get_dispatch_table_type(cmd) == 'Device'
261
262
Yiwei Zhang5365a7b2019-10-11 17:26:44 -0700263def is_extension_exported(ext):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700264 """Returns true if an extension has functions exported by the loader.
265
266 E.g. applications can directly link to an extension function.
267
268 Args:
Yiwei Zhang5365a7b2019-10-11 17:26:44 -0700269 ext: Vulkan extension name.
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700270 """
Yiwei Zhang5365a7b2019-10-11 17:26:44 -0700271 return ext in _EXPORTED_EXTENSIONS
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700272
273
274def is_function_exported(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700275 """Returns true if a function is exported from the Android Vulkan library.
276
277 Functions in the core API and in loader extensions are exported.
278
279 Args:
280 cmd: Vulkan function name.
281 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700282 if is_function_supported(cmd):
283 if cmd in extension_dict:
284 return is_extension_exported(extension_dict[cmd])
285 return True
286 return False
287
288
289def is_instance_dispatch_table_entry(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700290 """Returns true if a function is exported and instance-dispatched.
291
292 Args:
293 cmd: Vulkan function name.
294 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700295 if cmd == 'vkEnumerateDeviceLayerProperties':
296 # deprecated, unused internally - @dbd33bc
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700297 return False
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700298 return is_function_exported(cmd) and is_instance_dispatched(cmd)
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700299
300
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700301def is_device_dispatch_table_entry(cmd):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700302 """Returns true if a function is exported and device-dispatched.
303
304 Args:
305 cmd: Vulkan function name.
306 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700307 return is_function_exported(cmd) and is_device_dispatched(cmd)
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700308
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700309
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700310def init_proc(name, f):
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700311 """Emits code to invoke INIT_PROC or INIT_PROC_EXT.
312
313 Args:
314 name: Vulkan function name.
315 f: Output file handle.
316 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700317 f.write(indent(1))
318 if name in extension_dict:
Yiwei Zhangaeaa8672019-10-16 18:59:41 -0700319 f.write('INIT_PROC_EXT(' + base_ext_name(extension_dict[name]) + ', ')
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700320 else:
321 f.write('INIT_PROC(')
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700322
Yiwei Zhang6be097b2020-10-19 20:22:05 -0700323 if name in _OPTIONAL_COMMANDS:
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700324 f.write('false, ')
Yiwei Zhang6be097b2020-10-19 20:22:05 -0700325 elif version_dict[name] == 'VK_VERSION_1_0':
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700326 f.write('true, ')
Yiwei Zhang6be097b2020-10-19 20:22:05 -0700327 else:
328 f.write('false, ')
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700329
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700330 if is_instance_dispatched(name):
331 f.write('instance, ')
332 else:
333 f.write('dev, ')
334
335 f.write(base_name(name) + ');\n')
336
337
338def parse_vulkan_registry():
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700339 """Parses Vulkan registry into the below global variables.
340
341 alias_dict
342 command_list
343 extension_dict
344 param_dict
345 return_type_dict
Yiwei Zhang7cc36a52019-10-11 19:02:09 -0700346 version_code_list
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700347 version_dict
Yiwei Zhang7c0c07c2020-07-04 23:49:47 -0700348 promoted_inst_ext_dict
Yiwei Zhang6ca5d0c2019-10-11 17:15:02 -0700349 """
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700350 registry = os.path.join(os.path.dirname(__file__), '..', '..', '..', '..',
351 'external', 'vulkan-headers', 'registry', 'vk.xml')
352 tree = element_tree.parse(registry)
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700353 root = tree.getroot()
Chris Forbes9d84eae2024-06-12 12:06:28 +1200354
355 for exts in root.iter('extensions'):
356 for extension in exts.iter('extension'):
357 if 'vulkan' not in extension.get('supported').split(','):
358 # ANDROID_native_buffer is a weird special case -- it's declared in vk.xml as
359 # disabled but we _do_ want to generate plumbing for it in the Android loader.
360 if extension.get('name') != 'VK_ANDROID_native_buffer':
361 print('skip extension disabled or not for vulkan: ' + extension.get('name'))
362 continue
363
364 apiversion = 'VK_VERSION_1_0'
365 if extension.tag == 'extension':
366 extname = extension.get('name')
367 if (extension.get('type') == 'instance' and
368 extension.get('promotedto') is not None):
369 promoted_inst_ext_dict[extname] = \
370 version_2_api_version(extension.get('promotedto'))
371 for req in extension.iter('require'):
372 if req.get('feature') is not None:
373 apiversion = req.get('feature')
374 for commands in req:
375 if commands.tag == 'command':
376 cmd_name = commands.get('name')
377 if cmd_name not in extension_dict:
378 extension_dict[cmd_name] = extname
379 version_dict[cmd_name] = apiversion
380
381 for feature in root.iter('feature'):
Chris Forbesa5296cd2024-10-01 09:59:47 +1300382 # hack, 'feature' element has multiple meanings.. should be more precise with path match
383 if feature.get('api') is None:
384 continue
Chris Forbes9d84eae2024-06-12 12:06:28 +1200385 if 'vulkan' not in feature.get('api').split(','):
386 continue
387
388 apiversion = feature.get('name')
389 for req in feature.iter('require'):
390 for command in req:
391 if command.tag == 'command':
392 cmd_name = command.get('name')
393 version_dict[cmd_name] = apiversion
394
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700395 for commands in root.iter('commands'):
396 for command in commands:
397 if command.tag == 'command':
Chris Forbes9d84eae2024-06-12 12:06:28 +1200398 if command.get('api') == 'vulkansc':
399 continue
400
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700401 parameter_list = []
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700402 protoset = False
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700403 cmd_name = ''
404 cmd_type = ''
405 if command.get('alias') is not None:
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700406 alias = command.get('alias')
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700407 cmd_name = command.get('name')
Chris Forbes9d84eae2024-06-12 12:06:28 +1200408 # At this stage all valid commands have been added to the version
409 # dict so we can use it to filter valid commands
410 if cmd_name in version_dict:
411 alias_dict[cmd_name] = alias
412 command_list.append(cmd_name)
413 param_dict[cmd_name] = param_dict[alias].copy()
414 return_type_dict[cmd_name] = return_type_dict[alias]
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700415 for params in command:
Yiwei Zhang4bc489b2019-09-23 15:17:22 -0700416 if params.tag == 'param':
Chris Forbes9d84eae2024-06-12 12:06:28 +1200417 if params.get('api') == 'vulkansc':
418 # skip SC-only param variant
419 continue
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700420 param_type = ''
421 if params.text is not None and params.text.strip():
422 param_type = params.text.strip() + ' '
423 type_val = params.find('type')
424 param_type = param_type + type_val.text
425 if type_val.tail is not None:
426 param_type += type_val.tail.strip() + ' '
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700427 pname = params.find('name')
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700428 param_name = pname.text
429 if pname.tail is not None and pname.tail.strip():
430 parameter_list.append(
431 (param_type, param_name, pname.tail.strip()))
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700432 else:
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700433 parameter_list.append((param_type, param_name))
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700434 if params.tag == 'proto':
435 for c in params:
436 if c.tag == 'type':
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700437 cmd_type = c.text
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700438 if c.tag == 'name':
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700439 cmd_name = c.text
Chris Forbes9d84eae2024-06-12 12:06:28 +1200440 if cmd_name in version_dict:
441 protoset = True
442 command_list.append(cmd_name)
443 return_type_dict[cmd_name] = cmd_type
Yiwei Zhang1ca59c12019-10-10 12:54:42 -0700444 if protoset:
445 param_dict[cmd_name] = parameter_list.copy()
Adithya Srinivasan751a7dc2019-07-02 17:17:25 -0700446
Yiwei Zhang7cc36a52019-10-11 19:02:09 -0700447
448 version_code_set = set()
449 for version in version_dict.values():
450 version_code_set.add(version_code(version))
451 for code in sorted(version_code_set):
452 version_code_list.append(code)