Build the venv from the distribution's Python instead of uv
The backend now targets 3.11, which is what Raspberry Pi OS ships, so there is nothing left for uv to solve. It was only ever there to obtain a 3.13 the distro does not have, and it brought its own problems: the download had to be matched to the Pi's 32-bit userland by hand, and no armv7 wheel exists for a 3.13 ABI, so Pillow was compiled from source and needed image headers installed alongside. apt for the interpreter, python3 -m venv, pip for the rest - and piwheels then supplies prebuilt armhf wheels for the native dependencies. A venv cannot be migrated between interpreters (it holds absolute paths into the one that made it), and `creates:` would keep the old one forever, so check what built it and rebuild when it does not match the current python3. That is what carries a device off the uv-installed 3.13 without hand-holding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,47 +3,15 @@
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- git
|
||||
- libvlc5 # runtime lib python-vlc (installed into the venv by uv) binds against
|
||||
- libvlc5 # runtime lib python-vlc binds against
|
||||
- vlc-plugin-base
|
||||
- alsa-utils
|
||||
- samba
|
||||
# Pillow ships no armv7 wheel and Raspberry Pi OS's userland is 32-bit, so it is
|
||||
# compiled from source here and needs its image headers. (piwheels would have a
|
||||
# prebuilt one, but only for the system Python, not the 3.13 uv installs.)
|
||||
- libjpeg-dev
|
||||
- zlib1g-dev
|
||||
- python3
|
||||
- python3-venv
|
||||
- python3-pip
|
||||
cache_valid_time: 7200
|
||||
|
||||
- name: Ensure uv install directory exists
|
||||
ansible.builtin.file:
|
||||
path: "/opt/uv-{{ pi_musicmouse_uv_version }}-{{ pi_musicmouse_uv_target }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Download and extract uv {{ pi_musicmouse_uv_version }}
|
||||
ansible.builtin.unarchive:
|
||||
src: "https://github.com/astral-sh/uv/releases/download/{{ pi_musicmouse_uv_version }}/uv-{{ pi_musicmouse_uv_target }}.tar.gz"
|
||||
dest: "/opt/uv-{{ pi_musicmouse_uv_version }}-{{ pi_musicmouse_uv_target }}"
|
||||
remote_src: true
|
||||
extra_opts: ["--strip-components=1"]
|
||||
creates: "/opt/uv-{{ pi_musicmouse_uv_version }}-{{ pi_musicmouse_uv_target }}/uv"
|
||||
|
||||
- name: Symlink uv/uvx into /usr/local/bin
|
||||
ansible.builtin.file:
|
||||
src: "/opt/uv-{{ pi_musicmouse_uv_version }}-{{ pi_musicmouse_uv_target }}/{{ item }}"
|
||||
dest: "/usr/local/bin/{{ item }}"
|
||||
state: link
|
||||
force: true
|
||||
loop:
|
||||
- uv
|
||||
- uvx
|
||||
|
||||
- name: Verify uv runs on this host
|
||||
ansible.builtin.command:
|
||||
cmd: /usr/local/bin/uv --version
|
||||
register: pi_musicmouse_uv_version_out
|
||||
changed_when: false
|
||||
|
||||
- name: Checkout Musicmouse repo
|
||||
ansible.builtin.git:
|
||||
repo: "{{ pi_musicmouse_repo }}"
|
||||
@@ -73,9 +41,34 @@
|
||||
buried.
|
||||
when: not ansible_check_mode and not pi_musicmouse_pyproject.stat.exists
|
||||
|
||||
- name: Create virtualenv with Python {{ pi_musicmouse_python_version }}
|
||||
- name: Get the system Python version
|
||||
ansible.builtin.command:
|
||||
cmd: "/usr/local/bin/uv venv --python {{ pi_musicmouse_python_version }} /opt/musicmouse/.venv"
|
||||
cmd: python3 -c "import sys; print('%d.%d' % sys.version_info[:2])"
|
||||
register: pi_musicmouse_system_python
|
||||
changed_when: false
|
||||
|
||||
- name: Get the version the venv was built with
|
||||
ansible.builtin.command:
|
||||
cmd: /opt/musicmouse/.venv/bin/python -c "import sys; print('%d.%d' % sys.version_info[:2])"
|
||||
register: pi_musicmouse_venv_python
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Discard a venv built by a different Python
|
||||
# `creates:` below would otherwise keep an old interpreter forever - which is exactly
|
||||
# what a switch away from a separately-installed Python leaves behind. A venv holds
|
||||
# absolute paths into the interpreter that made it, so it cannot be migrated; the
|
||||
# cheap and correct move is to rebuild it.
|
||||
ansible.builtin.file:
|
||||
path: /opt/musicmouse/.venv
|
||||
state: absent
|
||||
when: >-
|
||||
pi_musicmouse_venv_python.rc == 0
|
||||
and pi_musicmouse_venv_python.stdout | trim != pi_musicmouse_system_python.stdout | trim
|
||||
|
||||
- name: Create the virtualenv from the system Python
|
||||
ansible.builtin.command:
|
||||
cmd: "python3 -m venv /opt/musicmouse/.venv"
|
||||
creates: /opt/musicmouse/.venv/bin/python
|
||||
|
||||
# Installing the backend is deliberately not a handler hanging off the checkout.
|
||||
@@ -85,9 +78,9 @@
|
||||
# ModuleNotFoundError. Instead, decide from what is actually on the device - the commit
|
||||
# it has checked out, and whether the venv can import the package at all.
|
||||
#
|
||||
# uv's own output cannot answer this: `musicmouse` is a local path dependency, so every
|
||||
# run rebuilds and reinstalls it and always prints "Installed 1 package". Keying off
|
||||
# that restarts the service on every run and rescans the whole library for nothing.
|
||||
# pip's own output cannot answer this either: `musicmouse` is a local path dependency,
|
||||
# so every run rebuilds and reinstalls it. Keying off that restarts the service on
|
||||
# every run and rescans the whole library for nothing.
|
||||
- name: Read which commit the venv was built from
|
||||
ansible.builtin.command:
|
||||
cmd: cat /opt/musicmouse/.venv/.installed-commit
|
||||
@@ -104,7 +97,7 @@
|
||||
|
||||
- name: Install the backend into the venv
|
||||
ansible.builtin.command:
|
||||
cmd: "/usr/local/bin/uv pip install --python /opt/musicmouse/.venv/bin/python /opt/musicmouse/python-backend"
|
||||
cmd: "/opt/musicmouse/.venv/bin/pip install /opt/musicmouse/python-backend"
|
||||
when: >-
|
||||
pi_musicmouse_importable.rc != 0
|
||||
or pi_musicmouse_stamp.stdout | trim != pi_musicmouse_commit
|
||||
|
||||
Reference in New Issue
Block a user