| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Validation functions for the Meta-Build config file""" |
| |
| import ast |
| import collections |
| import difflib |
| import json |
| import os |
| import re |
| |
| |
| def GetAllConfigs(builder_groups): |
| """Build a list of all of the configs referenced by builders. |
| """ |
| all_configs = {} |
| for builder_group in builder_groups: |
| for config in builder_groups[builder_group].values(): |
| if isinstance(config, dict): |
| for c in config.values(): |
| all_configs[c] = builder_group |
| else: |
| all_configs[config] = builder_group |
| return all_configs |
| |
| |
| def CheckAllConfigsAndMixinsReferenced(errs, all_configs, configs, mixins): |
| """Check that every actual config is actually referenced.""" |
| for config in configs: |
| if not config in all_configs: |
| errs.append('Unused config "%s".' % config) |
| |
| # Figure out the whole list of mixins, and check that every mixin |
| # listed by a config or another mixin actually exists. |
| referenced_mixins = set() |
| for config, mixin_names in configs.items(): |
| for mixin in mixin_names: |
| if not mixin in mixins: |
| errs.append( |
| 'Unknown mixin "%s" referenced by config "%s".' % (mixin, config)) |
| referenced_mixins.add(mixin) |
| |
| for mixin in mixins: |
| for sub_mixin in mixins[mixin].get('mixins', []): |
| if not sub_mixin in mixins: |
| errs.append( |
| 'Unknown mixin "%s" referenced by mixin "%s".' % (sub_mixin, mixin)) |
| referenced_mixins.add(sub_mixin) |
| |
| # Check that every mixin defined is actually referenced somewhere. |
| for mixin in mixins: |
| if not mixin in referenced_mixins: |
| errs.append('Unreferenced mixin "%s".' % mixin) |
| |
| return errs |
| |
| |
| def _GetConfigsByBuilder(builder_groups): |
| """Builds a mapping from buildername -> [config] |
| |
| Args |
| builder_groups: the builder_group's dict from mb_config.pyl |
| """ |
| |
| result = collections.defaultdict(list) |
| for builder_group in builder_groups.values(): |
| for buildername, builder in builder_group.items(): |
| result[buildername].append(builder) |
| |
| return result |
| |
| |
| def CheckDuplicateConfigs(errs, config_pool, mixin_pool, grouping, |
| flatten_config): |
| """Check for duplicate configs. |
| |
| Evaluate all configs, and see if, when |
| evaluated, differently named configs are the same. |
| """ |
| evaled_to_source = collections.defaultdict(set) |
| for group, builders in grouping.items(): |
| for builder in builders: |
| config = grouping[group][builder] |
| if not config: |
| continue |
| |
| if isinstance(config, dict): |
| # Ignore for now |
| continue |
| |
| if config.startswith('//'): |
| args = config |
| else: |
| flattened_config = flatten_config(config_pool, mixin_pool, config) |
| args = flattened_config['gn_args'] |
| if 'error' in args: |
| continue |
| # Force the args_file into consideration when testing for duplicate |
| # configs. |
| args_file = flattened_config['args_file'] |
| if args_file: |
| args += ' args_file=%s' % args_file |
| |
| evaled_to_source[args].add(config) |
| |
| for v in evaled_to_source.values(): |
| if len(v) != 1: |
| errs.append( |
| 'Duplicate configs detected. When evaluated fully, the ' |
| 'following configs are all equivalent: %s. Please ' |
| 'consolidate these configs into only one unique name per ' |
| 'configuration value.' % (', '.join(sorted('%r' % val for val in v)))) |
| |
| |
| def CheckDebugDCheckOrOfficial(errs, gn_args, builder_group, builder, phase): |
| # TODO(crbug.com/1227171): Figure out how to check this properly |
| # for simplechrome-based bots. |
| if gn_args.get('is_chromeos_device'): |
| return |
| |
| if ((gn_args.get('is_debug') == True) |
| or (gn_args.get('is_official_build') == True) |
| or ('dcheck_always_on' in gn_args)): |
| return |
| |
| if phase: |
| errs.append('Phase "%s" of builder "%s" on %s did not specify ' |
| 'one of is_debug=true, is_official_build=true, or ' |
| 'dcheck_always_on=(true|false).' % |
| (phase, builder, builder_group)) |
| else: |
| errs.append('Builder "%s" on %s did not specify ' |
| 'one of is_debug=true, is_official_build=true, or ' |
| 'dcheck_always_on=(true|false).' % (builder, builder_group)) |
| |
| |
| def CheckExpectations(mbw, jsonish_blob, expectations_dir): |
| """Checks that the expectation files match the config file. |
| |
| Returns: True if expectations are up-to-date. False otherwise. |
| """ |
| # Assert number of builder_groups == number of expectation files. |
| if len(mbw.ListDir(expectations_dir)) != len(jsonish_blob): |
| return False |
| for builder_group, builders in jsonish_blob.items(): |
| if not mbw.Exists(os.path.join(expectations_dir, builder_group + '.json')): |
| return False # No expecation file for the builder_group. |
| expectation = mbw.ReadFile(os.path.join(expectations_dir, |
| builder_group + '.json')) |
| builders_json = json.dumps(builders, |
| indent=2, |
| sort_keys=True, |
| separators=(',', ': ')) |
| if builders_json != expectation: |
| return False # Builders' expectation out of sync. |
| return True |
| |
| |
| def CheckKeyOrdering(errs, groups, configs, mixins): |
| # Check ordering of groups within "builder_groups". |
| group_names = list(groups.keys()) |
| sorted_group_names = sorted(group_names) |
| if group_names != sorted_group_names: |
| errs.append('\nThe keys in "builder_groups" are not sorted:') |
| errs.extend(difflib.context_diff(group_names, sorted_group_names)) |
| |
| # Check ordering of builders within each group. |
| for group, builders in groups.items(): |
| builder_names = list(builders.keys()) |
| sorted_builder_names = sorted(builder_names) |
| if builder_names != sorted_builder_names: |
| errs.append('\nThe builders in group "%s" are not sorted:' % group) |
| errs.extend(difflib.context_diff(builder_names, sorted_builder_names)) |
| |
| # Check ordering of configs names, but don't bother checking the ordering |
| # of mixins within a config. |
| config_names = list(configs.keys()) |
| sorted_config_names = sorted(config_names) |
| if config_names != sorted_config_names: |
| errs.append('\nThe config names are not sorted:') |
| errs.extend(difflib.context_diff(config_names, sorted_config_names)) |
| |
| # Check ordering of mixin names. |
| mixin_names = list(mixins.keys()) |
| sorted_mixin_names = sorted(mixin_names) |
| if mixin_names != sorted_mixin_names: |
| errs.append('\nThe mixin names are not sorted:') |
| errs.extend(difflib.context_diff(mixin_names, sorted_mixin_names)) |