Skip to content
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

Use uv instead of pip on production images #112496

Merged
merged 25 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
packages: write
id-token: write
strategy:
fail-fast: false
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
Expand Down
25 changes: 14 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,50 @@ FROM ${BUILD_FROM}

# Synchronize with homeassistant/core.py:async_stop
ENV \
S6_SERVICES_GRACETIME=240000
S6_SERVICES_GRACETIME=240000 \
UV_SYSTEM_PYTHON=true

ARG QEMU_CPU

# Install uv
RUN pip3 install uv==0.1.22

WORKDIR /usr/src

## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \
pip3 install \
--only-binary=:all: \
uv pip install \
--no-build \
-r homeassistant/requirements.txt

COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
RUN \
if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
pip3 install homeassistant/home_assistant_frontend-*.whl; \
uv pip install homeassistant/home_assistant_frontend-*.whl; \
fi \
&& if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \
pip3 install homeassistant/home_assistant_intents-*.whl; \
uv pip install homeassistant/home_assistant_intents-*.whl; \
fi \
&& if [ "${BUILD_ARCH}" = "i386" ]; then \
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
linux32 pip3 install \
--only-binary=:all: \
linux32 uv pip install \
--no-build \
-r homeassistant/requirements_all.txt; \
else \
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
pip3 install \
--only-binary=:all: \
uv pip install \
--no-build \
-r homeassistant/requirements_all.txt; \
fi

## Setup Home Assistant Core
COPY . homeassistant/
RUN \
pip3 install \
--only-binary=:all: \
uv pip install \
-e ./homeassistant \
&& python3 -m compileall \
homeassistant/homeassistant
Expand Down
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ types-pytz==2023.3.1.1
types-PyYAML==6.0.12.12
types-requests==2.31.0.3
types-xmltodict==0.13.0.3
uv==0.1.17
uv==0.1.22
52 changes: 40 additions & 12 deletions script/hassfest/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from homeassistant.util import executor, thread

from .model import Config, Integration
from .requirements import PACKAGE_REGEX, PIP_VERSION_RANGE_SEPARATOR

DOCKERFILE_TEMPLATE = r"""# Automatically generated by hassfest.
#
Expand All @@ -13,47 +14,50 @@

# Synchronize with homeassistant/core.py:async_stop
ENV \
S6_SERVICES_GRACETIME={timeout}
S6_SERVICES_GRACETIME={timeout} \
UV_SYSTEM_PYTHON=true

ARG QEMU_CPU

# Install uv
RUN pip3 install uv=={uv_version}

WORKDIR /usr/src

## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \
pip3 install \
--only-binary=:all: \
uv pip install \
--no-build \
-r homeassistant/requirements.txt

COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
RUN \
if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
pip3 install homeassistant/home_assistant_frontend-*.whl; \
uv pip install homeassistant/home_assistant_frontend-*.whl; \
fi \
&& if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \
pip3 install homeassistant/home_assistant_intents-*.whl; \
uv pip install homeassistant/home_assistant_intents-*.whl; \
fi \
&& if [ "${{BUILD_ARCH}}" = "i386" ]; then \
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
linux32 pip3 install \
--only-binary=:all: \
linux32 uv pip install \
--no-build \
-r homeassistant/requirements_all.txt; \
else \
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
pip3 install \
--only-binary=:all: \
uv pip install \
--no-build \
-r homeassistant/requirements_all.txt; \
fi

## Setup Home Assistant Core
COPY . homeassistant/
RUN \
pip3 install \
--only-binary=:all: \
uv pip install \
-e ./homeassistant \
&& python3 -m compileall \
homeassistant/homeassistant
Expand All @@ -65,6 +69,28 @@
"""


def _get_uv_version() -> str:
with open("requirements_test.txt") as fp:
for _, line in enumerate(fp):
if match := PACKAGE_REGEX.match(line):
pkg, sep, version = match.groups()

if pkg != "uv":
continue

if sep != "==" or not version:
raise RuntimeError(
'Requirement uv need to be pinned "uv==<version>".'
)

for part in version.split(";", 1)[0].split(","):
version_part = PIP_VERSION_RANGE_SEPARATOR.match(part)
if version_part:
return version_part.group(2)

raise RuntimeError("Invalid uv requirement in requirements_test.txt")


def _generate_dockerfile() -> str:
timeout = (
core.STOPPING_STAGE_SHUTDOWN_TIMEOUT
Expand All @@ -75,7 +101,9 @@ def _generate_dockerfile() -> str:
+ thread.THREADING_SHUTDOWN_TIMEOUT
+ 10
)
return DOCKERFILE_TEMPLATE.format(timeout=timeout * 1000)
return DOCKERFILE_TEMPLATE.format(
timeout=timeout * 1000, uv_version=_get_uv_version()
)


def validate(integrations: dict[str, Integration], config: Config) -> None:
Expand Down
Loading