-
-
Notifications
You must be signed in to change notification settings - Fork 14.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Port switch-to-configuration script to Python #48378
Conversation
CCing @shlevy @Ericson2314 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move the main code flow into main()
and add
if __name__ == "__main__":
main()
import
statements should all be at the top. Please also add docstrings.
restartListFile = "/run/systemd/restart-list" | ||
reloadListFile = "/run/systemd/reload-list" | ||
|
||
def die_with_usage(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind to type annotations, then changing this file becomes easier for type checking with mypy.
I can also add them if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was very tempted to add type annotations when I started but wasn't quite sure whether we would want to retain Python 2 support so (mostly) refrained. I would be happy to add them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we definitely don't want more legacy python2 code.
reloadListFile = "/run/systemd/reload-list" | ||
|
||
def die_with_usage(): | ||
print(""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider a function error
which calls print with file=stderr
https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python
else: | ||
return otherwise | ||
|
||
if len(sys.argv) < 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use argparse for building the parser. Call it from main()
.
out = "@out@" | ||
|
||
# To be robust against interruption, record what units need to be started etc. | ||
startListFile = "/run/systemd/start-list" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constants should be in uppercase
|
||
# Install or update the bootloader. | ||
if action == "switch" or action == "boot": | ||
subprocess.checkCall(["@installBootLoader@", out]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check_call
|
||
# Just in case the new configuration hangs the system, do a sync now. | ||
if os.environ.get('NIXOS_NO_SYNC') != "1": | ||
subprocess.checkCall(["@coreutils@/bin/sync", "-f", "/nix/store"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check_call
# absolute path as well. | ||
def fingerprint_unit(filename): | ||
fprint = os.path.abspath(filename) | ||
override = '%s.d/overrides.conf' % filename |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use .format
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or python3.6 format strings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we ready to accept a >= Python 3.6 as a requirement for NixOS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. python36 is the default python3, and soon it will be python37.
if not os.path.exists(prev_unit_file) \ | ||
and not os.path.exists(new_unit_file) \ | ||
and m is not None | ||
baseUnit = "%s@%s" % (m.group(1), m.group(1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
underscores
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
transitions = UnitTransitions() | ||
active_prev = get_active_units() | ||
|
||
for unit, state in active_prev.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what's below this for loop should be in a separate function
prev_exists = os.path.exists(prev_unit_file) | ||
new_exists = os.path.exists(new_unit_file) | ||
if prev_exists and state['state'] in ['active', 'activating']: | ||
if (not new_exists) or os.path.abspath(new_unit_file) == "/dev/null": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
too much nested. Split up in functions.
output = subprocess.check_output(args) | ||
return output.strip() | ||
|
||
def unique(*args): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused
if len(sys.argv) < 1: | ||
die_with_usage() | ||
|
||
action = sys.argv[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add those lines in a one main function instead of mixing functions and code?
@Mic92 I think we can add a tiny builder in |
There is also pystemd: https://github.com/facebookincubator/pystemd that wraps around libsystemd, |
@FRidh I also have some other scripts flying around that can profit from mypy builders: https://github.com/NixOS/nixpkgs/blob/master/pkgs/misc/vim-plugins/update.py https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/tools/analysis/radare2/update.py |
I also started rewriting |
|
||
if reload_if_changed: | ||
transitions.reload_unit(unit) | ||
elif !restart_if_changed or refuse_manual_stop: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not valid python syntax.
elif !restart_if_changed or refuse_manual_stop: | ||
transitions.skip_unit(unit) | ||
else: | ||
if !stop_if_changed: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also this one should be not.
Pystemd would indeed be very nice for this usecase. @flokli and I have been experimenting with it and it seemed to work really well. I'll have a look at Cython's cross-compilation story. Because I'm not sure either. |
5dcbc76
to
02ce1c6
Compare
What about using Jeepney for DBus? It is a pure Python library. |
I considered it but decided against it for now for concerns of correctness and verbosity. I'd be happy to go this direction if others think it best, however. |
The current head typechecks with mypy. |
print_err(msg) | ||
sys.exit(1) | ||
|
||
def die(why: str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functions should also always have a return type also if it its None
. Otherwise the type check is skipped if a function does not have any typed arguments.
|
||
# This is a NixOS installation if it has /etc/NIXOS or a proper | ||
# /etc/os-release. | ||
def is_nixos(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def is_nixos() -> bool:
The build process relies on the glib-marshal utility.
Strangely, they were previously marked as nativeBuildInputs.
13699b1
to
bcc9a6b
Compare
@Mic92, this now includes a port of |
FWIW, see #48611 regarding |
Alright, I will put this aside then. Thanks to everyone who reviewed! |
The only Python script in NixOS is |
So that one script effectively increases closure sizes by 55 MiB. |
@FRidh I just bisected to that very commit. Tough :/ |
@edolstra - OOC, is the primary concern closure size? Any thoughts on a more acceptable number (e.g. is a 5MB increase okay)? |
@edolstra,j while I'm not attached to Python in particular, I really don't see the sense in using Perl over Python. If anything it seems to me that the residual Perl should be moved to Python, not the other way around. My reasons are three-fold:
A compiled language (Rust or C++) also would be fine here. Really, just anything but Perl. |
@andrew-d No, the main concern is not doing gratuitous rewrites. The motivation (cross-compilation) is a weak one because NixOS does not support cross-compilation. Also, I find Python a horrible language to maintain (worse than Perl, even). Rewriting NixOps in Python was the worst decision I ever made. Never again. |
A Rust binary would not be bigger than the 50+ MiB that Python adds to the closure size. All the current Perl scripts (switch-to-configuration.pl, setup-etc.pl, install-grub.pl, nixos-generate-config.pl, update-users-groups.pl, command-not-found.pl) could be replaced by a single Rust binary that shouldn't be bigger than a megabyte or two. |
I'm not sure what you mean by this; Both @Ericson2314 and I have put quite a bit of work into fixing cross-compilation in both Nixpkgs and NixOS. However, at this point I cross-compile NixOS routinely and use it to support a client project. |
I give others credit for NixOS in particular :). But yeah as I understand it the only obstical remaining is Perl. I don't love either but Python > Perl is also by far the more common opinion as others have said, and that should be taken into account. I don't think Rust buys us much either. I'm a huge believer in static types, but if everything but the dbus part is shelling out, it's still fundamentally stringly typed. Hardening non-weakest links is a rather pointless exercise. Still, if Rust is the best way for this to move forward, so be it. I haven't looked that much in the nixpkgs some of things, but in principle it should work very well. |
Does that actually work at the moment? The shebang paths in switch-to-configuration and other scripts in 64-bit closures won't work on 32-bit systems either. To be honest I don't think it's a use case that needs to be a hard requirement. |
Rust would be soon usable w.r.t to cross compiling: #50866 |
We can also rewrite those scripts in Bash, bash has a small closure size ;) |
They were in bash before, but where rewritten in perl because they were too slow according to the git history. |
Concerning
I would just like to repeat my previous point:
I would hope that this disqualifies the language from any use in something as central as the |
This is unique experience. Can you write-up a separate post on "why"? Or is it more personal, so nothing to share? |
If I had to guess, breaking changes in the standard library between major/minor releases. |
Besides increasing minimal system closure size, switching to python would increase number of packets required to cross-compile a system closure: Python + some libs + deps would need to be cross-compilable. I nice idea I think is to rewrite
|
That raises questions how the decision on test-driver perl->python rewrite had been passed and why @edolstra did not vetoed it (I agree that as embed language python is terrible + having tab-language within Nix string literals with non-trivial indent stripping and escape sequences is awful) |
Because not many contributors know perl anymore. |
Also note perl is probably worse when it comes to cross-compilation than python. |
Yes, we depend on https://arsv.github.io/perl-cross/ which is not very reliable. The project is not very popular and they might stop supporting it at any moment. That's +1 for C++ |
Motivation for this change
Note that this has yet to be tested. Rather I am just posting this as a placeholder.
This is a port of the
switch-to-configuration
script from Perl to Python. The motivation for this change is to ease cross-compilation, which is documented to be quite difficult in the case of Perl (see #36675).One tricky issue that this raises is the lack of sensible
dbus
bindings for Python. Each of the options has its share of issues:dbus-python
is largely unmaintained and binds thelibdbus
implementation, use of which is somewhat actively discouragedpydbus
usesgobject-introspection
, which sadly appears to be very unfriendly to cross-compilationQtDBus
uses Qt, which is rather heavy-weight for this purpose.Ideally I would like a small binding for
sdbus
but, that not being available, I have simply useddbus-python
here.Things done
sandbox
innix.conf
on non-NixOS)nix-shell -p nox --run "nox-review wip"
./result/bin/
)nix path-info -S
before and after)