Skip to content

Commit b57f7f9

Browse files
authored
Integrate new arguments data structure (#3167)
* Integrate new args dataclass * Integrate args * Update * Update * Update * Update
1 parent a9ae064 commit b57f7f9

50 files changed

Lines changed: 1354 additions & 1389 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

archinstall/__init__.py

Lines changed: 9 additions & 248 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Arch Linux installer - guided, templates etc."""
2+
23
import curses
34
import importlib
45
import os
@@ -9,29 +10,13 @@
910
from pathlib import Path
1011
from typing import TYPE_CHECKING, Any
1112

12-
from . import default_profiles
13-
from .lib import disk, exceptions, interactions, locale, luks, mirrors, models, networking, packages, profile
14-
from .lib.boot import Boot
15-
from .lib.configuration import ConfigurationOutput
16-
from .lib.general import (
17-
JSON,
18-
UNSAFE_JSON,
19-
SysCommand,
20-
SysCommandWorker,
21-
clear_vt100_escape_codes,
22-
generate_password,
23-
json_stream_to_structure,
24-
locate_binary,
25-
run_custom_user_commands,
26-
secret,
27-
)
28-
from .lib.global_menu import GlobalMenu
29-
from .lib.hardware import GfxDriver, SysInfo
30-
from .lib.installer import Installer, accessibility_tools_in_use
13+
from archinstall.lib.args import arch_config_handler
14+
from archinstall.lib.disk.utils import disk_layouts
15+
16+
from .lib.hardware import SysInfo
3117
from .lib.output import FormattedOutput, debug, error, info, log, warn
3218
from .lib.pacman import Pacman
3319
from .lib.plugins import load_plugin, plugins
34-
from .lib.storage import storage
3520
from .lib.translationhandler import DeferredTranslation, Language, translation_handler
3621
from .tui import Tui
3722

@@ -41,9 +26,6 @@
4126
_: Callable[[str], DeferredTranslation]
4227

4328

44-
__version__ = "3.0.2"
45-
storage['__version__'] = __version__
46-
4729
# add the custom _ as a builtin, it can now be used anywhere in the
4830
# project to mark strings as translatable with _('translate me')
4931
DeferredTranslation.install()
@@ -56,236 +38,18 @@
5638
debug(f"Graphics devices detected: {SysInfo._graphics_devices().keys()}")
5739

5840
# For support reasons, we'll log the disk layout pre installation to match against post-installation layout
59-
debug(f"Disk states before installing:\n{disk.disk_layouts()}")
60-
61-
parser = ArgumentParser()
62-
63-
64-
def define_arguments() -> None:
65-
"""
66-
Define which explicit arguments do we allow.
67-
Refer to https://docs.python.org/3/library/argparse.html for documentation and
68-
https://docs.python.org/3/howto/argparse.html for a tutorial
69-
Remember that the property/entry name python assigns to the parameters is the first string defined as argument and
70-
dashes inside it '-' are changed to '_'
71-
"""
72-
parser.add_argument("-v", "--version", action="version", version="%(prog)s " + __version__)
73-
parser.add_argument("--config", nargs="?", help="JSON configuration file or URL")
74-
parser.add_argument("--creds", nargs="?", help="JSON credentials configuration file")
75-
parser.add_argument("--silent", action="store_true",
76-
help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored")
77-
parser.add_argument("--dry-run", "--dry_run", action="store_true",
78-
help="Generates a configuration file and then exits instead of performing an installation")
79-
parser.add_argument("--script", default="guided", nargs="?", help="Script to run for installation", type=str)
80-
parser.add_argument("--mount-point", "--mount_point", default=Path("/mnt/archinstall"), nargs="?", type=Path,
81-
help="Define an alternate mount point for installation")
82-
parser.add_argument("--skip-ntp", action="store_true", help="Disables NTP checks during installation", default=False)
83-
parser.add_argument("--debug", action="store_true", default=False, help="Adds debug info into the log")
84-
parser.add_argument("--offline", action="store_true", default=False,
85-
help="Disabled online upstream services such as package search and key-ring auto update.")
86-
parser.add_argument("--no-pkg-lookups", action="store_true", default=False,
87-
help="Disabled package validation specifically prior to starting installation.")
88-
parser.add_argument("--plugin", nargs="?", type=str)
89-
parser.add_argument("--skip-version-check", action="store_true",
90-
help="Skip the version check when running archinstall")
41+
debug(f"Disk states before installing:\n{disk_layouts()}")
9142

9243

9344
if 'sphinx' not in sys.modules and 'pylint' not in sys.modules:
9445
if '--help' in sys.argv or '-h' in sys.argv:
95-
define_arguments()
96-
parser.print_help()
46+
arch_config_handler.print_help()
9747
exit(0)
9848
if os.getuid() != 0:
9949
print(_("Archinstall requires root privileges to run. See --help for more."))
10050
exit(1)
10151

10252

103-
def parse_unspecified_argument_list(unknowns: list, multiple: bool = False, err: bool = False) -> dict: # type: ignore[type-arg]
104-
"""We accept arguments not defined to the parser. (arguments "ad hoc").
105-
Internally argparse return to us a list of words so we have to parse its contents, manually.
106-
We accept following individual syntax for each argument
107-
--argument value
108-
--argument=value
109-
--argument = value
110-
--argument (boolean as default)
111-
the optional parameters to the function alter a bit its behaviour:
112-
* multiple allows multivalued arguments, each value separated by whitespace. They're returned as a list
113-
* error. If set any non correctly specified argument-value pair to raise an exception. Else, simply notifies the
114-
existence of a problem and continues processing.
115-
116-
To a certain extent, multiple and error are incompatible. In fact, the only error this routine can catch, as of now,
117-
is the event argument value value ...
118-
which isn't am error if multiple is specified
119-
"""
120-
tmp_list = [arg for arg in unknowns if arg != "="] # wastes a few bytes, but avoids any collateral effect of the destructive nature of the pop method()
121-
config = {}
122-
key = None
123-
last_key = None
124-
while tmp_list:
125-
element = tmp_list.pop(0) # retrieve an element of the list
126-
127-
if element.startswith('--'): # is an argument ?
128-
if '=' in element: # uses the arg=value syntax ?
129-
key, value = [x.strip() for x in element[2:].split('=', 1)]
130-
config[key] = value
131-
last_key = key # for multiple handling
132-
key = None # we have the kwy value pair we need
133-
else:
134-
key = element[2:]
135-
config[key] = True # every argument starts its lifecycle as boolean
136-
elif key:
137-
config[key] = element
138-
last_key = key # multiple
139-
key = None
140-
elif multiple and last_key:
141-
if isinstance(config[last_key], str):
142-
config[last_key] = [config[last_key], element]
143-
else:
144-
config[last_key].append(element)
145-
elif err:
146-
raise ValueError(f"Entry {element} is not related to any argument")
147-
else:
148-
print(f" We ignore the entry {element} as it isn't related to any argument")
149-
return config
150-
151-
152-
def cleanup_empty_args(args: Namespace | dict) -> dict: # type: ignore[type-arg]
153-
"""
154-
Takes arguments (dictionary or argparse Namespace) and removes any
155-
None values. This ensures clean mergers during dict.update(args)
156-
"""
157-
if type(args) is Namespace:
158-
args = vars(args)
159-
160-
clean_args = {}
161-
for key, val in args.items():
162-
if isinstance(val, dict):
163-
val = cleanup_empty_args(val)
164-
165-
if val is not None:
166-
clean_args[key] = val
167-
168-
return clean_args
169-
170-
171-
def get_arguments() -> dict[str, Any]:
172-
""" The handling of parameters from the command line
173-
Is done on following steps:
174-
0) we create a dict to store the arguments and their values
175-
1) preprocess.
176-
We take those arguments which use JSON files, and read them into the argument dict. So each first level entry
177-
becomes an argument on its own right
178-
2) Load.
179-
We convert the predefined argument list directly into the dict via the vars() function. Non specified arguments
180-
are loaded with value None or false if they are booleans (action="store_true"). The name is chosen according to
181-
argparse conventions. See above (the first text is used as argument name, but underscore substitutes dash). We
182-
then load all the undefined arguments. In this case the names are taken as written.
183-
Important. This way explicit command line arguments take precedence over configuration files.
184-
3) Amend
185-
Change whatever is needed on the configuration dictionary (it could be done in post_process_arguments but this
186-
ougth to be left to changes anywhere else in the code, not in the arguments dictionary
187-
"""
188-
config: dict[str, Any] = {}
189-
args, unknowns = parser.parse_known_args()
190-
# preprocess the JSON files.
191-
# TODO Expand the url access to the other JSON file arguments ?
192-
if args.config is not None:
193-
if not json_stream_to_structure('--config', args.config, config):
194-
exit(1)
195-
196-
if args.creds is not None:
197-
if not json_stream_to_structure('--creds', args.creds, config):
198-
exit(1)
199-
200-
# load the parameters. first the known, then the unknowns
201-
clean_args = cleanup_empty_args(args)
202-
config.update(clean_args)
203-
config.update(parse_unspecified_argument_list(unknowns))
204-
# amend the parameters (check internal consistency)
205-
# Installation can't be silent if config is not passed
206-
if clean_args.get('config') is None:
207-
config["silent"] = False
208-
else:
209-
config["silent"] = clean_args.get('silent')
210-
211-
# avoiding a compatibility issue
212-
if 'dry-run' in config:
213-
del config['dry-run']
214-
215-
return config
216-
217-
218-
def load_config() -> None:
219-
"""
220-
refine and set some arguments. Formerly at the scripts
221-
"""
222-
from .lib.models import NetworkConfiguration
223-
224-
arguments['locale_config'] = locale.LocaleConfiguration.parse_arg(arguments)
225-
226-
if (archinstall_lang := arguments.get('archinstall-language', None)) is not None:
227-
arguments['archinstall-language'] = translation_handler.get_language_by_name(archinstall_lang)
228-
229-
if disk_config := arguments.get('disk_config', {}):
230-
arguments['disk_config'] = disk.DiskLayoutConfiguration.parse_arg(disk_config)
231-
232-
if profile_config := arguments.get('profile_config', None):
233-
arguments['profile_config'] = profile.ProfileConfiguration.parse_arg(profile_config)
234-
235-
if mirror_config := arguments.get('mirror_config', None):
236-
arguments['mirror_config'] = mirrors.MirrorConfiguration.parse_args(mirror_config)
237-
238-
if arguments.get('servers', None) is not None:
239-
storage['_selected_servers'] = arguments.get('servers', None)
240-
241-
if (net_config := arguments.get('network_config', None)) is not None:
242-
config = NetworkConfiguration.parse_arg(net_config)
243-
arguments['network_config'] = config
244-
245-
if arguments.get('!users', None) is not None or arguments.get('!superusers', None) is not None:
246-
users = arguments.get('!users', None)
247-
superusers = arguments.get('!superusers', None)
248-
arguments['!users'] = models.User.parse_arguments(users, superusers)
249-
250-
if arguments.get('bootloader', None) is not None:
251-
arguments['bootloader'] = models.Bootloader.from_arg(arguments['bootloader'])
252-
253-
if arguments.get('uki') and not arguments['bootloader'].has_uki_support():
254-
arguments['uki'] = False
255-
256-
if arguments.get('audio_config', None) is not None:
257-
arguments['audio_config'] = models.AudioConfiguration.parse_arg(arguments['audio_config'])
258-
259-
if arguments.get('disk_encryption', None) is not None and disk_config is not None:
260-
arguments['disk_encryption'] = disk.DiskEncryption.parse_arg(
261-
arguments['disk_config'],
262-
arguments['disk_encryption'],
263-
arguments.get('encryption_password', '')
264-
)
265-
266-
267-
def post_process_arguments(args: dict[str, Any]) -> None:
268-
storage['arguments'] = args
269-
270-
if args.get('debug'):
271-
warn(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!")
272-
273-
if args.get('plugin'):
274-
path = args['plugin']
275-
load_plugin(path)
276-
277-
try:
278-
load_config()
279-
except ValueError as err:
280-
warn(str(err))
281-
exit(1)
282-
283-
284-
define_arguments()
285-
arguments: dict[str, Any] = get_arguments()
286-
post_process_arguments(arguments)
287-
288-
28953
# @archinstall.plugin decorator hook to programmatically add
29054
# plugins in runtime. Useful in profiles_bck and other things.
29155
def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
@@ -321,13 +85,10 @@ def main() -> None:
32185
OR straight as a module: python -m archinstall
32286
In any case we will be attempting to load the provided script to be run from the scripts/ folder
32387
"""
324-
if not arguments.get('skip_version_check'):
88+
if not arch_config_handler.args.skip_version_check:
32589
_check_new_version()
32690

327-
script = arguments.get('script', None)
328-
329-
if script is None:
330-
print('No script to run provided')
91+
script = arch_config_handler.args.script
33192

33293
mod_name = f'archinstall.scripts.{script}'
33394
# by loading the module we'll automatically run the script

archinstall/default_profiles/applications/pipewire.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import TYPE_CHECKING, override
22

3-
import archinstall
43
from archinstall.default_profiles.profile import Profile, ProfileType
54

65
if TYPE_CHECKING:
@@ -26,14 +25,12 @@ def packages(self) -> list[str]:
2625
]
2726

2827
def _enable_pipewire_for_all(self, install_session: 'Installer') -> None:
29-
users: User | list[User] | None = archinstall.arguments.get('!users', None)
28+
from archinstall.lib.args import arch_config_handler
29+
users: list[User] | None = arch_config_handler.config.users
3030

3131
if users is None:
3232
return
3333

34-
if not isinstance(users, list):
35-
users = [users]
36-
3734
for user in users:
3835
# Create the full path for enabling the pipewire systemd items
3936
service_dir = install_session.target / "home" / user.username / ".config" / "systemd" / "user" / "default.target.wants"

archinstall/default_profiles/profile.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from enum import Enum, auto
55
from typing import TYPE_CHECKING
66

7-
from ..lib.storage import storage
8-
97
if TYPE_CHECKING:
108
from collections.abc import Callable
119

@@ -110,7 +108,8 @@ def _advanced_check(self) -> bool:
110108
Used to control if the Profile() should be visible or not in different contexts.
111109
Returns True if --advanced is given on a Profile(advanced=True) instance.
112110
"""
113-
return self.advanced is False or storage['arguments'].get('advanced', False) is True
111+
from archinstall.lib.args import arch_config_handler
112+
return self.advanced is False or arch_config_handler.args.advanced is True
114113

115114
def install(self, install_session: 'Installer') -> None:
116115
"""

archinstall/default_profiles/servers/docker.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from typing import TYPE_CHECKING, override
22

3-
import archinstall
43
from archinstall.default_profiles.profile import Profile, ProfileType
5-
from archinstall.lib.models import User
4+
from archinstall.lib.args import arch_config_handler
65

76
if TYPE_CHECKING:
87
from archinstall.lib.installer import Installer
@@ -27,9 +26,5 @@ def services(self) -> list[str]:
2726

2827
@override
2928
def post_install(self, install_session: 'Installer') -> None:
30-
users: User | list[User] = archinstall.arguments.get('!users', [])
31-
if not isinstance(users, list):
32-
users = [users]
33-
34-
for user in users:
29+
for user in arch_config_handler.config.users:
3530
install_session.arch_chroot(f'usermod -a -G docker {user.username}')

0 commit comments

Comments
 (0)