| #!/usr/bin/env python2.7 |
| # Copyright 2020 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Validate the sysmgr config for the product being built. |
| |
| Sysmgr's configuration is provided through the config-data package as a set of |
| JSON files, which are all individually read by sysmgr at runtime and merged to |
| form its overall configuration. This tool validates that there are no conflicts |
| between files, e.g. multiple files providing different component URLs for the |
| same "services" key. For example, it catches invalid configuration like this:: |
| |
| |
| file1.config: |
| { |
| "services": { |
| "fuchsia.my.Service": "fuchsia-pkg://fuchsia.com/package_a#meta/foo.cmx" |
| } |
| } |
| |
| file2.config: |
| { |
| "services": { |
| "fuchsia.my.Service": "fuchsia-pkg://fuchsia.com/package_b#meta/bar.cmx" |
| } |
| } |
| |
| It also tests for conflicts in the 'diagnostics' key. |
| |
| The input provided to this tool is expected to be the config-data package |
| manifest, formatted like this:: |
| |
| data/some_package/foo=../../src/somewhere/foo |
| data/sysmgr/services.config=../../src/sys/sysmgr/config/services.config |
| data/sysmgr/file1.config=../../src/bar/file1.config |
| data/other_package/baz=../../some/other/path/baz |
| |
| where the path before the '=' is the destination path in the package, and the |
| path after the '=' is the source file (rebased to the root build directory). |
| """ |
| |
| import argparse |
| import json |
| import os |
| import sys |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--stamp', metavar='FILE', help='Touch FILE at the end.', required=True) |
| parser.add_argument( |
| '--depfile', |
| metavar='FILE', |
| help='Write a GN depfile to the given path', |
| required=True) |
| parser.add_argument('manifest', help='config-data package manifest') |
| args = parser.parse_args() |
| |
| with open(args.manifest, 'r') as f: |
| lines = f.read().splitlines() |
| |
| # Build a list of all the source paths that contribute to sysmgr's config-data |
| sysmgr_lines = [line for line in lines if line.startswith('data/sysmgr/')] |
| sysmgr_config_files = [line.split('=')[1] for line in sysmgr_lines] |
| |
| # Parse all config files and build a list of all conflicts, rather than |
| # failing immediately on the first conflict. This allows us to print a more |
| # useful build failure so that developers don't need to play whack-a-mole with |
| # multiple conflicts. |
| seen_services = {} |
| service_conflicts = {} |
| seen_diag = None |
| diag_conflicts = [] |
| for config_file in sysmgr_config_files: |
| with open(config_file, 'r') as f: |
| config = json.load(f) |
| |
| diag = config.get('diagnostics') |
| if diag is not None: |
| if seen_diag is None: |
| seen_diag = config_file |
| else: |
| if not diag_conflicts: |
| diag_conflicts.append(seen_diag) |
| diag_conflicts.append(config_file) |
| |
| services = config.get('services') |
| if not services: |
| continue |
| for service in services.iterkeys(): |
| if service in seen_services: |
| if not service_conflicts: |
| service_conflicts[service] = [seen_services[service]] |
| service_conflicts[service].append(config_file) |
| else: |
| seen_services[service] = config_file |
| |
| # If any conflicts were detected, print a useful error message and then |
| # exit. |
| if service_conflicts or diag_conflicts: |
| print('Error: conflicts detected in sysmgr configuration') |
| for service, config_files in service_conflicts.items(): |
| print( |
| 'Duplicate configuration for service {} in files: {}'.format( |
| service, ', '.join(config_files))) |
| if diag_conflicts: |
| print( |
| 'Duplicate diagnostics config in files: {}'.format( |
| ', '.join(diag_conflicts))) |
| return 1 |
| |
| # Write the depfile, which is a Makefile format file that has a single output |
| # (the stamp file) and lists all input files as dependencies. |
| with open(args.depfile, 'w') as f: |
| f.write('{}: {}\n'.format(args.stamp, ' '.join(sysmgr_config_files))) |
| |
| # Write the stampfile. |
| with open(args.stamp, 'w') as f: |
| os.utime(f.name, None) |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |