1# Copyright (c) 2022 Nordic Semiconductor ASA
2#
3# SPDX-License-Identifier: Apache-2.0
4
5'''Domain handling for west extension commands.
6
7This provides parsing of domains yaml file and creation of objects of the
8Domain class.
9'''
10
11from dataclasses import dataclass
12
13import yaml
14import pykwalify.core
15import logging
16
17DOMAINS_SCHEMA = '''
18## A pykwalify schema for basic validation of the structure of a
19## domains YAML file.
20##
21# The domains.yaml file is a simple list of domains from a multi image build
22# along with the default domain to use.
23type: map
24mapping:
25  default:
26    required: true
27    type: str
28  build_dir:
29    required: true
30    type: str
31  domains:
32    required: true
33    type: seq
34    sequence:
35      - type: map
36        mapping:
37          name:
38            required: true
39            type: str
40          build_dir:
41            required: true
42            type: str
43  flash_order:
44    required: false
45    type: seq
46    sequence:
47      - type: str
48'''
49
50schema = yaml.safe_load(DOMAINS_SCHEMA)
51logger = logging.getLogger('build_helpers')
52# Configure simple logging backend.
53formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
54handler = logging.StreamHandler()
55handler.setFormatter(formatter)
56logger.addHandler(handler)
57
58
59class Domains:
60
61    def __init__(self, domains_yaml):
62        try:
63            data = yaml.safe_load(domains_yaml)
64            pykwalify.core.Core(source_data=data,
65                                schema_data=schema).validate()
66        except (yaml.YAMLError, pykwalify.errors.SchemaError):
67            logger.critical(f'malformed domains.yaml')
68            exit(1)
69
70        self._build_dir = data['build_dir']
71        self._domains = {
72            d['name']: Domain(d['name'], d['build_dir'])
73            for d in data['domains']
74        }
75
76        # In the YAML data, the values for "default" and "flash_order"
77        # must not name any domains that aren't listed under "domains".
78        # Now that self._domains has been initialized, we can leverage
79        # the common checks in self.get_domain to verify this.
80        self._default_domain = self.get_domain(data['default'])
81        self._flash_order = self.get_domains(data.get('flash_order', []))
82
83    @staticmethod
84    def from_file(domains_file):
85        '''Load domains from a domains.yaml file.
86        '''
87        try:
88            with open(domains_file, 'r') as f:
89                domains_yaml = f.read()
90        except FileNotFoundError:
91            logger.critical(f'domains.yaml file not found: {domains_file}')
92            exit(1)
93
94        return Domains(domains_yaml)
95
96    @staticmethod
97    def from_yaml(domains_yaml):
98        '''Load domains from a string with YAML contents.
99        '''
100        return Domains(domains_yaml)
101
102    def get_domains(self, names=None, default_flash_order=False):
103        if names is None:
104            if default_flash_order:
105                return self._flash_order
106            return list(self._domains.values())
107        return list(map(self.get_domain, names))
108
109    def get_domain(self, name):
110        found = self._domains.get(name)
111        if not found:
112            logger.critical(f'domain "{name}" not found, '
113                    f'valid domains are: {", ".join(self._domains)}')
114            exit(1)
115        return found
116
117    def get_default_domain(self):
118        return self._default_domain
119
120    def get_top_build_dir(self):
121        return self._build_dir
122
123
124@dataclass
125class Domain:
126
127    name: str
128    build_dir: str
129