-
Notifications
You must be signed in to change notification settings - Fork 110
/
common.py
174 lines (146 loc) · 6.12 KB
/
common.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
"""
Common CLI writing utilities.
"""
import functools
import itertools
import logging
from pathlib import Path
from typing import Iterable
import click
import atopile.config
from atopile import address, errors, version
from atopile.address import AddrStr
log = logging.getLogger(__name__)
def project_options(f):
"""
Utility decorator to ingest common config options to build a project.
"""
@click.argument("entry", required=False, default=None)
@click.option("-b", "--build", multiple=True, envvar="ATO_BUILD")
@click.option("-t", "--target", multiple=True, envvar="ATO_TARGET")
@click.option("-o", "--option", multiple=True, envvar="ATO_OPTION")
@functools.wraps(f)
@errors.muffle_fatalities
def wrapper(
*args,
entry: str,
build: Iterable[str],
target: Iterable[str],
option: Iterable[str],
**kwargs,
):
"""Wrap a CLI command to ingest common config options to build a project."""
# basic the entry address if provided, otherwise leave it as None
if entry is not None:
entry = AddrStr(entry)
if address.get_file(entry) is None:
raise click.BadParameter(
f"Invalid entry address {entry} - entry must specify a file.",
param_hint="entry",
)
# get the project
if entry is None:
entry_arg_file_path = Path.cwd()
else:
entry_arg_file_path = (
Path(address.get_file(entry)).expanduser().resolve().absolute()
)
try:
project_config = atopile.config.get_project_config_from_addr(str(entry_arg_file_path))
except FileNotFoundError as ex:
# FIXME: this raises an exception when the entry is not in a project
raise click.BadParameter(
f"Could not find project from path {str(entry_arg_file_path)}. "
"Is this file path within a project?"
) from ex
# Make sure I an all my sub-configs have appropriate versions
check_compiler_versions(project_config)
log.info("Using project %s", project_config.location)
# add custom config overrides
if option:
raise NotImplementedError(
"Custom config overrides have been removed in a refactor. "
"It's planned to re-add them in a future release. "
"If this is a blocker for you, please raise an issue. "
"In the meantime, you can use the `ato.yaml` file to set these options."
)
else:
config: atopile.config.ProjectConfig = project_config
# if we set an entry-point, we now need to deal with that
entry_addr_override = None
if entry:
if entry_arg_file_path.is_file():
if entry_section := address.get_entry_section(entry):
entry_addr_override = address.from_parts(
str(entry_arg_file_path.absolute()),
entry_section,
)
else:
raise click.BadParameter(
"If an entry of a file is specified, you must specify"
" the node within it you want to build.",
param_hint="entry",
)
elif entry_arg_file_path.is_dir():
pass
elif not entry_arg_file_path.exists():
raise click.BadParameter(
"The entry you have specified does not exist.",
param_hint="entry",
)
else:
raise ValueError(
f"Unexpected entry path type {entry_arg_file_path} - this should never happen!"
)
# Configure project context
project_ctx = atopile.config.ProjectContext.from_config(config)
atopile.config.set_project_context(project_ctx)
# Make build contexts
with errors.handle_ato_errors():
if build_names := build or config.builds.keys():
build_ctxs: list[atopile.config.BuildContext] = [
atopile.config.BuildContext.from_config_name(config, build_name)
for build_name in build_names
]
else:
build_ctxs = [atopile.config.BuildContext.from_config(
"default",
atopile.config.ProjectBuildConfig(),
project_ctx
)]
for build_ctx in build_ctxs:
if entry_addr_override is not None:
build_ctx.entry = entry_addr_override
if target:
build_ctx.targets = list(target)
# Do the thing
return f(*args, **kwargs, build_ctxs=build_ctxs)
return wrapper
def check_compiler_versions(config: atopile.config.ProjectConfig):
"""
Check that the compiler version is compatible with the version
used to build the project.
"""
with errors.handle_ato_errors():
dependency_cfgs = (
errors.downgrade(atopile.config.get_project_config_from_path, FileNotFoundError)(p)
for p in Path(config.location).glob("*")
)
for cltr, cfg in errors.iter_through_errors(
itertools.chain([config], dependency_cfgs)
):
if cfg is None:
continue
with cltr():
semver_str = cfg.ato_version
# FIXME: this is a hack to the moment to get around us breaking
# the versioning scheme in the ato.yaml files
for operator in version.OPERATORS:
semver_str = semver_str.replace(operator, "")
built_with_version = version.parse(semver_str)
if not version.match_compiler_compatability(built_with_version):
raise version.VersionMismatchError(
f"{cfg.location} ({cfg.ato_version}) can't be"
" built with this version of atopile "
f"({version.get_installed_atopile_version()})."
)