7 Commits
Author SHA1 Message Date
stilobique 8445a95233 Disable the CI 2024-05-31 10:09:39 +02:00
stilobique bf4144a84c Add more blender tags 2024-05-25 17:05:51 +02:00
stilobique a104f50532 Add all latest build 2024-05-10 19:19:43 +02:00
stilobique 5cee49a567 Add more docker blender version 2024-05-10 15:12:25 +02:00
stilobique 8ef6d72ffe Clear unused comment 2024-05-09 16:55:40 +02:00
stilobique 5a16e9de93 Update the dockerfile to be functional ? 2024-05-09 16:04:14 +02:00
stilobique e41f332f60 Reset all build list.
Refactoring the docker file to be functional with the new blender platform and update the pycharm run config
2024-05-09 00:21:22 +02:00
19 changed files with 245 additions and 797 deletions
-6
View File
@@ -1,9 +1,3 @@
### Logs
**/logs/**
### Config/setup """
*.env
### PyCharm ### ### PyCharm ###
.idea/ .idea/
+38
View File
@@ -0,0 +1,38 @@
#cache:
# key: blender-source
# paths:
# - /opt/blender-git/blender # Blender git repository source
# - /opt/blender-git/lib # Blender Subversion lib package
# policy: pull
#
#update-cache:
# stage: .pre
# cache:
# key: blender-source
# image: stilobique/csv:latest
# script:
# - mkdir -p /opt/blender-git/lib
# - cd /opt/blender-git
# - git clone https://projects.blender.org/blender/blender.git
# - cd /opt/blender-git/blender
# - git submodule update --init --recursive
# - cd /opt/blender-git/lib
# - svn checkout https://svn.blender.org/svnroot/bf-blender/trunk/lib/linux_centos7_x86_64
#
#build-dockerfile:
# image: docker:19.03.12
# rules:
# - changes:
# - build.json
# stage: build
# services:
# - docker:dind
# cache:
# key: blender-source
# script:
# - apk add python3
# - apk add py3-pip
# - python3 -m pip install --upgrade pip
# - python3 -m pip install -r requirement-build.txt
# - python3 main.py
# - python3 main.py --tag=latest
-66
View File
@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2"
version="1.1"
viewBox="0 0 181 148"
xml:space="preserve"
id="svg4"
sodipodi:docname="blender-docker.svg"
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
id="defs4" /><sodipodi:namedview
id="namedview4"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="5.2541436"
inkscape:cx="90.404837"
inkscape:cy="74.036803"
inkscape:window-width="2648"
inkscape:window-height="1350"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<title
id="title1">Blender logo (without text)</title>
<g
transform="matrix(.281 0 0 .281 -41.8 -43.7)"
id="g4"
style="fill:#1c90ed;fill-opacity:1">
<g
transform="matrix(21.6 0 0 21.6 -4857 7665)"
id="g1"
style="fill:none;fill-opacity:1">
<path
d="m243-334c0.106-1.89 1.03-3.56 2.43-4.74 1.37-1.16 3.21-1.87 5.23-1.87 2.01 0 3.85 0.709 5.22 1.87 1.4 1.18 2.32 2.85 2.43 4.74 0.106 1.94-0.675 3.75-2.04 5.09-1.4 1.36-3.38 2.22-5.61 2.22s-4.22-0.854-5.61-2.22c-1.37-1.34-2.15-3.14-2.04-5.08z"
style="fill-rule:nonzero;fill:none;fill-opacity:1"
id="path1" />
</g>
<g
transform="matrix(11.1,0,0,11.1,-2215,4153)"
id="g3"
style="display:inline;fill:#1c90ed;fill-opacity:1">
<path
d="m 243,-334 c 0.106,-1.89 1.03,-3.56 2.43,-4.74 1.37,-1.16 3.21,-1.87 5.23,-1.87 2.01,0 3.85,0.709 5.22,1.87 1.4,1.18 2.32,2.85 2.43,4.74 0.106,1.94 -0.675,3.75 -2.04,5.09 -1.4,1.36 -3.38,2.22 -5.61,2.22 -2.23,0 -4.22,-0.854 -5.61,-2.22 -1.37,-1.34 -2.15,-3.14 -2.04,-5.08 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero"
id="path2" />
<path
d="m 231,-330 c 0.013,0.74 0.249,2.18 0.603,3.3 0.744,2.38 2.01,4.58 3.76,6.51 1.8,1.99 4.02,3.59 6.58,4.73 2.69,1.19 5.61,1.8 8.64,1.8 3.03,-0.004 5.95,-0.624 8.64,-1.83 2.56,-1.15 4.78,-2.75 6.58,-4.75 1.76,-1.95 3.02,-4.15 3.76,-6.53 0.375,-1.2 0.612,-2.42 0.707,-3.64 0.093,-1.2 0.054,-2.41 -0.117,-3.62 -0.334,-2.35 -1.15,-4.56 -2.4,-6.56 -1.14,-1.85 -2.62,-3.46 -4.38,-4.82 l 0.004,-0.003 -17.7,-13.6 c -0.016,-0.012 -0.029,-0.025 -0.046,-0.036 -1.16,-0.892 -3.12,-0.889 -4.39,0.005 -1.29,0.904 -1.44,2.4 -0.29,3.34 l -0.005,0.005 7.39,6.01 -22.5,0.024 h -0.03 c -1.86,0.002 -3.65,1.22 -4,2.77 -0.364,1.57 0.9,2.88 2.84,2.88 l -0.003,0.007 11.4,-0.022 -20.4,15.6 c -0.026,0.019 -0.054,0.039 -0.078,0.058 -1.92,1.47 -2.54,3.92 -1.33,5.46 1.23,1.57 3.84,1.58 5.78,0.009 l 11.1,-9.1 c 0,0 -0.162,1.23 -0.149,1.96 z m 28.6,4.11 c -2.29,2.33 -5.5,3.66 -8.96,3.66 -3.47,0.006 -6.68,-1.3 -8.97,-3.63 -1.12,-1.14 -1.94,-2.44 -2.45,-3.83 -0.497,-1.37 -0.69,-2.82 -0.562,-4.28 0.121,-1.43 0.547,-2.8 1.23,-4.03 0.668,-1.21 1.59,-2.31 2.72,-3.24 2.23,-1.81 5.06,-2.8 8.02,-2.8 2.97,-0.004 5.8,0.969 8.03,2.78 1.13,0.924 2.05,2.02 2.72,3.23 0.683,1.23 1.11,2.59 1.23,4.03 0.126,1.46 -0.067,2.91 -0.564,4.28 -0.508,1.4 -1.33,2.7 -2.45,3.84 z"
style="fill:#1c90ed;fill-opacity:1;fill-rule:nonzero"
id="path3" />
</g>
</g>
<metadata
id="metadata4"><rdf:RDF><cc:Work
rdf:about=""><dc:title>Blender logo (without text)</dc:title></cc:Work></rdf:RDF></metadata></svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

-27
View File
@@ -1,27 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clean docker Images " type="PythonConfigurationType" factoryName="Python">
<module name="rider.module" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="BlenderDocker" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="DEBUG_JUST_MY_CODE" value="false" />
<option name="RUN_TOOL" value="" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/blender/misc.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
</component>
@@ -1,26 +1,33 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build BlenderDocker" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="stilobique/blender:4.5.11" /> <option name="imageTag" value="stilobique/blender:3.6.12" />
<option name="buildArgs"> <option name="buildArgs">
<list> <list>
<DockerEnvVarImpl> <DockerEnvVarImpl>
<option name="name" value="b3d_vs_major" /> <option name="name" value="b3d_vs_major" />
<option name="value" value="4.5" /> <option name="value" value="3.6" />
</DockerEnvVarImpl> </DockerEnvVarImpl>
<DockerEnvVarImpl> <DockerEnvVarImpl>
<option name="name" value="b3d_vs_minor" /> <option name="name" value="b3d_vs_minor" />
<option name="value" value="11" /> <option name="value" value="12" />
</DockerEnvVarImpl> </DockerEnvVarImpl>
</list> </list>
</option> </option>
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="containerName" value="blender-latest" /> <option name="containerName" value="blender-latest" />
<option name="sourceFilePath" value="Dockerfile" /> <option name="sourceFilePath" value="Dockerfile" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/moderlab-plugin" />
<option name="hostPath" value="E:\Projects\Git\Blender-Moderlab-Config\addons\moderlab-plugin\moderlab-plugin" />
</DockerVolumeBindingImpl>
</list>
</option>
</settings> </settings>
</deployment> </deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>
@@ -1,20 +1,27 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Automation Builder" type="PythonConfigurationType" factoryName="Python"> <configuration default="false" name="Generate image" type="PythonConfigurationType" factoryName="Python">
<module name="rider.module" /> <module name="blender-docker" />
<option name="ENV_FILES" value="$PROJECT_DIR$/config.env" />
<option name="INTERPRETER_OPTIONS" value="" /> <option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" /> <option name="PARENT_ENVS" value="true" />
<envs> <envs>
<env name="PYTHONUNBUFFERED" value="1" /> <env name="PYTHONUNBUFFERED" value="1" />
</envs> </envs>
<option name="SDK_HOME" value="" /> <option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="BlenderDocker" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="false" /> <option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" />
<option name="DEBUG_JUST_MY_CODE" value="false" /> <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="RUN_TOOL" value="" /> <EXTENSION ID="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
</ENTRIES>
</EXTENSION>
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" /> <option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
<option name="PARAMETERS" value="" /> <option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" /> <option name="SHOW_COMMAND_LINE" value="false" />
+74 -86
View File
@@ -1,103 +1,91 @@
# Pinned to the exact Python version Blender's own CMake requires for this FROM ubuntu:22.04 AS blender
# release (build_files/cmake/Modules/FindPythonLibsUnix.cmake,
# _PYTHON_VERSION_SUPPORTED) - a floating `python` tag drifts to whatever
# "latest" currently is, which stops matching once it moves past what an
# older/LTS Blender release hard-pins (it has no matching apt package to
# install after the fact, unlike a missing library).
ARG python_version=3.13
FROM python:${python_version} AS b3dock
# Setup all software version request # Setup all software version request
ARG b3d_vs_major=5.2 ARG b3d_vs_major=3.6
ARG b3d_vs_minor=0 ARG b3d_vs_minor=10
ARG version=v${b3d_vs_major}.${b3d_vs_minor}
LABEL Author="stilobique" LABEL Author="stilobique"
LABEL Title="Blender Docker for Unit Test" LABEL Title="Blender Docker for Unit Test"
LABEL Maintainer="Aurelien Vaillant contact@aurelien-vaillant.net"
## 01. Start First stage to build blender
# Install dependencies # Install dependencies
RUN apt-get update && apt-get install -y git git-lfs sudo RUN apt-get update && apt-get install -y build-essential \
git \
git-lfs \
cmake \
python3 \
python3-dev \
cython3 \
sudo
# Get source code RUN apt-get install -y libx11-dev \
RUN git clone --depth 1 --branch v${b3d_vs_major}.${b3d_vs_minor} \ libxxf86vm-dev \
https://projects.blender.org/blender/blender.git /opt/blender libxcursor-dev \
libxi-dev \
libxrandr-dev \
libxinerama-dev \
libxkbcommon-dev \
libwayland-dev \
libdecor-0-dev \
wayland-protocols \
libdbus-1-dev \
libgl-dev \
libegl-dev
# Start build RUN apt-get install -y libjpeg-dev \
# install_linux_packages.py (default, no --all) skips a handful of packages libpng-dev \
# that are only provided via the precompiled-libs bundle or via --all: libfreetype-dev \
# libepoxy-dev, Boost (all components), OpenImageIO. --all itself is not an libfontconfig-dev \
# option here: it also checks its own apt catalog for a python3-dev matching libzstd-dev \
# [3.11, 3.13[, which fails on Debian trixie regardless of which Python the libbz2-dev \
# base image runs (that check queries apt's package catalog, not the actual liblzma-dev \
# interpreter on PATH). Since `make update` below skips the precompiled libsdl2-dev \
# bundle on Linux by default, these are needed from apt regardless - install libepoxy-dev \
# them explicitly instead. libxml2-dev \
RUN cd /opt/blender/build_files/build_environment && \ libhpdf-dev \
chmod a+x ./install_linux_packages.py && \ libpystring-dev
python install_linux_packages.py && \
apt-get install -y libepoxy-dev libboost-all-dev libopenimageio-dev libembree-dev libpugixml-dev && \
pip install numpy cython && \
cd /opt/blender && make update && make -j$(nproc)
# Discover Blender's *actual* runtime shared-library needs from the compiled RUN apt-get install -y libopenjp2-7-dev \
# binary itself (ldd), instead of guessing from install_linux_packages.py libtiff-dev \
# (that script only covers build-time deps; libSM/libICE were never listed libjack-jackd2-dev \
# there but the binary needs them at runtime). Every resolved .so is mapped libpulse-dev \
# back to the apt package that owns it, so stage 02 can install exactly that. libopenal-dev \
RUN ldd /opt/build_linux/bin/blender | tee /dev/stderr | grep 'not found' && \ libsndfile1-dev \
(echo 'Unresolved libs in the build stage itself, investigate first' && exit 1) || true libjemalloc-dev \
# `realpath` is required: /lib is a merged-usr symlink to /usr/lib, and dpkg's libvulkan-dev \
# file database is keyed on the canonical /usr/lib/... path (with the full libgmp-dev \
# soname suffix, e.g. libX11.so.6.4.0) — querying the /lib/....so.6 symlink libpugixml-dev \
# path directly returns "no path found matching pattern". libfftw3-dev \
# These names are resolved against Debian (this stage's distro), but stage 02 libpotrace-dev \
# installs them on Ubuntu - the two occasionally disagree on a package name libyaml-cpp-dev \
# for the same library, and not just cosmetically: Debian's `libjpeg62-turbo` libdeflate-dev
# provides libjpeg.so.62, but Ubuntu's identically-ABI-sounding
# `libjpeg-turbo8` actually provides a DIFFERENT soname (libjpeg.so.8) -
# Ubuntu ships the old libjpeg.so.62 ABI as a separate `libjpeg62` package.
# Debian's generic `libxml2` is Ubuntu's soname-suffixed `libxml2-16`.
# Remap the known cases here rather than in stage 02.
RUN ldd /opt/build_linux/bin/blender \
| awk '{print $3}' | grep '^/' | sort -u \
| xargs -r realpath | sort -u \
| xargs -r dpkg -S 2>/dev/null | cut -d: -f1 | tr ',' '\n' | sed 's/^ //' | sort -u \
| sed -e 's/^libjpeg62-turbo$/libjpeg62/' -e 's/^libxml2$/libxml2-16/' \
| tee /opt/build_linux/runtime_packages.txt
# ADD . /opt/blender-git
RUN git clone --depth 1 --branch ${version} https://projects.blender.org/blender/blender.git /opt/blender-git
# Use the local repo has current directory
#WORKDIR /opt/blender-git
# Compile Blender
RUN cd /opt/blender-git && \
sudo ./build_files/utils/make_update.py --use-linux-libraries
RUN cd /opt/blender-git && \
make
## 02. Build optimissed image
## Setup a Multistage optimisation ## Setup a Multistage optimisation
FROM ubuntu:25.10 AS final FROM ubuntu:22.04
COPY --from=blender /opt/build_linux/bin /opt/blender
ARG b3d_vs_major
ENV B3D_ADDON_PATH "$HOME/.config/blender/${b3d_vs_major}/scripts/addons"
# Re-declared: ARGs before the first FROM only apply to that FROM line, not RUN apt-get update && apt-get install -y \
# to instructions in later stages. libxi6 \
ARG python_version=3.13 libxxf86vm1 \
libxfixes3 \
# Install exactly the runtime packages ldd found the compiled binary needs libxkbcommon-x11-0 \
# (see the b3dock stage above) instead of guessing from Blender's build-time libjack0 \
# package list. Ubuntu and Debian share package names for these core libs. pulseaudio \
COPY --from=b3dock /opt/build_linux/runtime_packages.txt /tmp/runtime_packages.txt libgl1-mesa-glx
RUN apt-get update && \
xargs -a /tmp/runtime_packages.txt apt-get install -y --no-install-recommends && \
rm -rf /tmp/runtime_packages.txt /var/lib/apt/lists/*
# libpython<version>.so isn't an apt package: the b3dock base image builds
# Python from source into /usr/local, so ldd/dpkg-S (above) can never find an
# owning package for it - Ubuntu's apt only ships whatever Python versions it
# currently defaults to, which won't match an older/LTS Blender's pin. Copy
# the .so Blender actually linked against directly from the build stage.
COPY --from=b3dock /usr/local/lib/libpython${python_version}.so.1.0 /usr/local/lib/libpython${python_version}.so.1.0
RUN ldconfig
RUN useradd -m -s /bin/bash bld
USER bld
COPY --from=b3dock /opt/build_linux/bin /opt/blender
# ARG b3d_vs_major
# ENV B3D_ADDON_PATH="/home/bld/.config/blender/${b3d_vs_major}/scripts/addons"
# Working Directory setup # Working Directory setup
WORKDIR /opt/blender WORKDIR /opt/blender
+5 -64
View File
@@ -1,37 +1,13 @@
[![Blender](https://img.shields.io/badge/blender-5.1.2-%23F5792A.svg?style=flat&logo=blender&logoColor=white)](https://www.blender.org/download/) [![Blender](https://img.shields.io/badge/blender-3.0.0-%23F5792A.svg?style=flat&logo=blender&logoColor=white)](https://www.blender.org/download/)
[![pipeline status](https://gitlab.com/stilobique/blender-docker/badges/main/pipeline.svg)](https://gitlab.com/stilobique/blender-docker/-/commits/main)
# Blender Docker # Blender Docker
This repository is an automated blender docker image file generated, it's usefully with blender addon unit test. This repository is an automated blender docker image file generated, it's usefully with blender addon unit test. To
build a new specific image, you need to update the `build.json` file with the new version witched ; or
# Building # Adding an Addon
## Docker Hub credentials
`main.py` logs into Docker Hub before building, so it needs `DOCKER_HUB_USER` and `DOCKER_HUB_PASSWORD` in the environment.
Create a `config.env` file at the repo root (gitignored):
```shell
DOCKER_HUB_USER=your_docker_hub_username
DOCKER_HUB_PASSWORD=your_docker_hub_password
```
It's loaded automatically by the `Automation Builder` run configuration (`ENV_FILES`) when running `main.py` from the IDE. Running it another way? Export those two variables yourself first.
## Start your build
You can use Invoke to easily start a build, a clean or more.
```shell
invoke --list
```
## Debug and logs
Some log are generated inside a `logs` folder.
- `build`: all operation on build docker file.
- `clean`: from the dedicated function to clean all dockers images and cache.
- `test`: simple class method to try a specific docker image.
## Check if the build work
# Use case
## Adding an Addon
Mount a new volume with your addon inside a folder (ex `/blender-plugin/plugin-archive.zip`), when you run your docker container add this volume and execute a script to add-it with the blender installed. Mount a new volume with your addon inside a folder (ex `/blender-plugin/plugin-archive.zip`), when you run your docker container add this volume and execute a script to add-it with the blender installed.
Python function to install an addon with Blender. The file his named `install.py` Python function to install an addon with Blender. The file his named `install.py`
@@ -50,38 +26,3 @@ Call this function with blender inside this container
#!/bin/sh #!/bin/sh
/opt/blender/blender --background --python "install.py" /opt/blender/blender --background --python "install.py"
```` ````
# Check Image build
```shell
docker run --rm stilobique/blender:5.1.2 ./blender --version
```
```shell
# Taille de l'image
docker image inspect stilobique/blender:5.1.2 --format='{{.Size}}' | numfmt --to=iec
# Dépendances manquantes
docker run --rm --entrypoint /bin/bash stilobique/blender:5.1.2 -c 'ldd /opt/blender/blender | grep "not found" || echo "All deps OK"'
# Test fonctionnel
docker run --rm stilobique/blender:5.1.2 ./blender -b --python-expr "import bpy; print('OK:', bpy.app.version_string)"
```
# CLI helper tasks
Reusable helpers (`tasks.py`) are exposed as [invoke](https://www.pyinvoke.org/) tasks.
```shell
# List available tasks
invoke clean
# Clean Docker (all contexts: default, podman, ...)
python -m blender.cli clean
# List Blender tags (Gitea)
python -m blender.cli get-tags-release
# List stilobique/blender tags (Docker Hub)
python -m blender.cli get-tags-docker
```
View File
-79
View File
@@ -1,79 +0,0 @@
import os
import requests
import tempfile
import importlib.util
from blender.version import BlenderVersion
# "Wayland Protocols" has no "Library" in its name but is still a
# build-critical dependency, so it's matched by name too.
_EXTRA_PACKAGE_NAMES = {'wayland protocols'}
class BlenderDependency:
def __init__(self, blender: BlenderVersion):
self._blender_version = blender
self._url = rf'https://projects.blender.org/api/v1/repos/blender/blender/raw/build_files/build_environment/install_linux_packages.py?ref=v{self._blender_version.tag_name()}'
self.module = self._get_source_file()
self.packages = list()
self._get_list()
self.packages_complete = list()
self._get_complete_list()
def _get_source_file(self):
"""Download the Blender dependency script and import it at runtime."""
r = requests.get(self._url)
r.raise_for_status()
with tempfile.NamedTemporaryFile(mode='wb', suffix='.py', delete=False) as t:
t.write(r.content)
path = t.name
try:
spec = importlib.util.spec_from_file_location('install_linux_packages', path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
finally:
os.remove(path)
def _get_list(self):
"""Keep the mandatory 'library' packages (and Wayland Protocols) as Debian/Ubuntu apt names."""
sources = self.module.BUILD_MANDATORY_SUBPACKAGES + self.module.DEPS_CRITICAL_SUBPACKAGES
for package in self._flatten(sources):
if 'library' not in package.name.lower() and package.name.lower() not in _EXTRA_PACKAGE_NAMES:
continue
name = package.distro_package_names.get(self.module.DISTRO_ID_DEBIAN)
if isinstance(name, str):
self.packages.append(name)
def _get_complete_list(self):
"""All packages required to build Blender (PACKAGES_BASICS_BUILD) as Debian/Ubuntu apt names."""
for package in self._flatten(self.module.PACKAGES_BASICS_BUILD):
name = package.distro_package_names.get(self.module.DISTRO_ID_DEBIAN)
if isinstance(name, str):
self.packages_complete.append(name)
def _flatten(self, packages):
"""Yield every leaf Package, descending into is_group sub_packages."""
for package in packages:
if package.is_group:
yield from self._flatten(package.sub_packages)
else:
yield package
def __str__(self):
return ' '.join(self.packages)
def complete_str(self):
return ' '.join(self.packages_complete)
if __name__ == "__main__":
bld = BlenderVersion(version='5.0.0')
dep = BlenderDependency(blender=bld)
print('Show All packages:')
print(dep.__str__())
-264
View File
@@ -1,264 +0,0 @@
import os
import re
import docker
import logging
import datetime
import requests
from .version import BlenderVersion
from .dependency import BlenderDependency
from pathlib import Path
from docker.errors import BuildError, APIError, ContainerError, ImageNotFound
_ANSI_ESCAPE_RE = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]')
def _clean_log_line(text: str) -> str:
"""Strip ANSI escape codes and keep only the last update of a \r-rewritten line."""
text = _ANSI_ESCAPE_RE.sub('', text)
return text.split('\r')[-1].strip()
class BlenderDocker:
def __init__(self, blender: BlenderVersion, severity: int = logging.WARNING):
self._blender_version = blender
self._docker_client = docker.from_env()
self.login()
self._docker_image = None
self._repository = 'stilobique/blender'
self._docker_image_name = self._blender_version.format_to_tag(self._repository)
self._docker_file = Path(os.getcwd(), 'Dockerfile')
if not self._docker_file.exists():
print(f'Dockerfile not found: {self._docker_file}')
exit(1)
self._packages = BlenderDependency(blender)
self._args = {
'b3d_vs_major': f'{self._blender_version.major}',
'b3d_vs_minor': f'{self._blender_version.minor}.{self._blender_version.hotfix}',
'b3d_dependency': self._packages.__str__(),
'python_version': self._get_required_python_version(),
}
self._severity = severity
def _get_required_python_version(self) -> str:
"""
Fetch the exact Python version (major.minor) Blender's own CMake pins for this
release, so the build stage's base image can match it instead of drifting with
whatever a floating `python` tag currently resolves to.
"""
url = (f'https://projects.blender.org/api/v1/repos/blender/blender/raw/'
f'build_files/cmake/Modules/FindPythonLibsUnix.cmake?ref=v{self._blender_version.tag_name()}')
r = requests.get(url)
r.raise_for_status()
match = re.search(r'set\(_PYTHON_VERSION_SUPPORTED\s+([0-9]+\.[0-9]+)\)', r.text)
if not match:
print(f'Could not determine required Python version from {url}')
exit(1)
return match.group(1)
def _get_logger(self, name: str) -> logging.Logger:
"""
Private Method to set a logger for a specific method set (by his name)
:param name: Make the logger file name.
:return:
"""
log_dir = Path('logs')
log_dir.mkdir(parents=True, exist_ok=True)
logger = logging.getLogger(f'blender_docker.{name}')
logger.setLevel(self._severity)
logger.propagate = False
if not logger.handlers:
handler = logging.FileHandler(log_dir / f'{name}.log', encoding='utf-8')
handler.setFormatter(logging.Formatter(
'%(asctime)s | [%(levelname)s] %(message)s'
))
logger.addHandler(handler)
return logger
def login(self):
"""Login to the Docker Hub"""
try:
print('Login to Hub Docker.')
self._docker_client.login(
username=os.environ['DOCKER_HUB_USER'],
password=os.environ['DOCKER_HUB_PASSWORD']
)
except APIError as e:
print(f'Login failed with error: {e}')
exit(1)
def build(self):
"""Build the Blender Docker Image"""
time = datetime.datetime.now()
logger = self._get_logger(f'build-{time.strftime("%Y-%m-%d-%H-%M")}')
collected_logs = []
try:
print('Building Blender Docker Image')
logger.info('Building Blender Docker Image')
logger.info(f'\tBuilds withs args: {self._args}')
logger.info(f'\tTag generate: {self._docker_image_name}')
# Use the low-level API: images.build() only returns once the whole
# build is finished, so it gives no live progress. api.build() with
# decode=True streams each step (including cache hits) as it happens.
build_logs = self._docker_client.api.build(
path=os.getcwd(),
# nocache=True,
buildargs=self._args,
tag=self._docker_image_name,
rm=True,
forcerm=True,
decode=True,
)
for chunk in build_logs:
collected_logs.append(chunk)
if 'stream' in chunk:
for raw_line in chunk['stream'].split('\n'):
line = _clean_log_line(raw_line)
if not line:
continue
if 'Using cache' in line:
line = f'[CACHE] {line}'
logger.info(line)
elif 'status' in chunk:
status = _clean_log_line(chunk['status'])
progress = _clean_log_line(chunk.get('progress', ''))
line = f"{status} {chunk.get('id', '')} {progress}".strip()
if 'Already exists' in status:
line = f'[CACHE] {line}'
logger.info(line)
elif 'error' in chunk:
logger.error(chunk['error'])
raise BuildError(chunk['error'], collected_logs)
self._docker_image = self._docker_client.images.get(self._docker_image_name)
except BuildError as e:
logger.exception(f'Build docker image failed: {e}')
raise
@staticmethod
def tag(self):
print('\tSet Tags version and/or Latest')
pass
def test(self) -> bool:
"""
This method request an image (call the method build before) and try to execute the blender inside the container
to check if the program can be correctly executed.
Based on `docker run --rm stilobique/blender:5.1.2 ./blender --version`
:return: True if no issue are returned
"""
logger = self._get_logger('test')
container_name = f"Blender-Docker-Testing-{self._blender_version.tag_name_slugify()}"
command = ['./blender', '--version']
container = None
try:
print(f'Testing your Blender Docker image {self._docker_image_name}')
logger.info(f'Testing image "{self._docker_image_name}" in container "{container_name}" '
f'with command: {" ".join(command)}')
# auto_remove is intentionally NOT used: Docker can remove the container
# right after it exits, racing with the logs() call below and turning a
# real container-side error into an unrelated 409 "dead or marked for
# removal" API error. We remove the container ourselves once logs are read.
container = self._docker_client.containers.run(
name=container_name,
image=self._docker_image_name,
command=command,
detach=True,
stdout=True,
stderr=True,
)
result = container.wait()
exit_code = result.get('StatusCode')
container_logs = container.logs(stdout=True, stderr=True).decode('utf-8', errors='replace').strip()
for raw_line in container_logs.split('\n'):
line = _clean_log_line(raw_line)
if line:
logger.info(f'[container] {line}')
if exit_code == 0:
logger.info(f'Test succeeded (exit code {exit_code}) for image "{self._docker_image_name}".')
return True
logger.error(f'Test failed (exit code {exit_code}) for image "{self._docker_image_name}".')
print(f'Testing your Blender Docker image {self._docker_image_name} failed.')
return False
except ContainerError as e:
logger.exception(f'Container error while testing "{self._docker_image_name}": {e}')
print(f'Container error: {e}')
exit(1)
except ImageNotFound as e:
logger.exception(f'Image "{self._docker_image_name}" not found: {e}')
print(f'Image not found: {e}')
exit(1)
except APIError as e:
logger.exception(f'API error while testing "{self._docker_image_name}": {e}')
print(f'API error: {e}')
exit(1)
finally:
if container is not None:
try:
container.remove(force=True)
except APIError:
pass
def push(self):
"""Push the Blender Docker Image"""
time = datetime.datetime.now()
logger = self._get_logger(f'push')
collected_logs = []
try:
print(f'Pushing Blender Docker image {self._docker_image_name}')
logger.info(f'Pushing Blender Docker Image')
logger.info(f'\tTag pushed: {self._docker_image_name}')
# Use the low-level API: images.push() only returns once the whole
# push is finished, so it gives no live progress. api.push() with
# decode=True streams each layer's progress as it happens.
push_logs = self._docker_client.api.push(
repository=self._repository,
tag=self._blender_version.tag_name(),
stream=True,
decode=True,
)
for chunk in push_logs:
collected_logs.append(chunk)
if 'status' in chunk:
status = _clean_log_line(chunk['status'])
progress = _clean_log_line(chunk.get('progress', ''))
line = f"{status} {chunk.get('id', '')} {progress}".strip()
if 'Layer already exists' in status:
line = f'[CACHE] {line}'
logger.info(line)
elif 'error' in chunk:
logger.error(chunk['error'])
raise APIError(chunk['error'])
logger.info(f'Push succeeded for image "{self._docker_image_name}".')
except APIError as e:
logger.exception(f'Push docker image failed: {e}')
raise
-92
View File
@@ -1,92 +0,0 @@
import logging
import os
import json
import requests
from pathlib import Path
from subprocess import run, CalledProcessError
def clean_all_dockers():
"""
Nettoie Docker pour tous les contextes disponibles (default, podman, ...).
Equivalent a, pour chaque contexte :
docker context use <context>
docker system prune -a -f
Puis remet le contexte d'origine en place a la fin.
"""
logs_dir = Path(os.getcwd(), 'logs')
logs_dir.mkdir(parents=True, exist_ok=True)
logger = logging.getLogger('blender_docker.clean')
logger.setLevel(logging.DEBUG)
logger.propagate = False
if not logger.handlers:
handler = logging.FileHandler(logs_dir / 'clean.log')
handler.setFormatter(logging.Formatter(
'%(asctime)s|%(name)s [%(levelname)s] %(message)s'
))
logger.addHandler(handler)
# Contexte courant, pour le restaurer a la fin
original_context = run(
['docker', 'context', 'show'],
capture_output=True, text=True, check=True
).stdout.strip()
logger.info(f'Contexte d\'origine: {original_context}')
# Liste de tous les contextes disponibles (format json, un objet par ligne)
contexts_raw = run(
['docker', 'context', 'ls', '--format', '{{json .}}'],
capture_output=True, text=True, check=True
).stdout.strip().splitlines()
contexts = [json.loads(line)['Name'] for line in contexts_raw if line]
print(f'\tContextes trouves: {contexts}')
logger.info(f'Contextes trouves: {contexts}')
for context in contexts:
try:
print(f'\tNettoyage du contexte: {context}')
logger.info(f'Nettoyage du contexte: {context}')
run(['docker', 'context', 'use', context], check=True)
result = run(
['docker', 'system', 'prune', '-a', '-f'],
capture_output=True, text=True, check=True
)
print(f'\t{result.stdout.strip()}')
logger.info(result.stdout.strip())
except CalledProcessError as e:
print(f'\tErreur sur le contexte {context}: {e.stderr.strip()}')
logger.error(f'Erreur sur le contexte {context}: {e.stderr.strip()}')
# On remet le contexte d'origine
run(['docker', 'context', 'use', original_context], check=True)
print(f'\tContexte restaure: {original_context}')
logger.info(f'Contexte restaure: {original_context}')
def get_all_tags_from_blender():
"""From the Gitea api, get all blender tags release"""
r = requests.get(r"https://projects.blender.org/api/v1/repos/blender/blender/tags")
print(r.text)
j = r.json()
# All tags data:
for release in j:
print(release)
def get_all_tags_from_docker():
"""From the Gitea api, get all blender tags release"""
r = requests.get(r"https://hub.docker.com/v2/namespaces/stilobique/repositories/blender/tags")
print(r.text)
j = r.json()
# All tags data:
for release in j:
print(release)
-43
View File
@@ -1,43 +0,0 @@
import re
class BlenderVersion:
def __init__(self, version: str = '5.1.0'):
"""
Need an object like 'X.Y.Z' to work correctly, build a pythonic object about a blender version.
:param version:
"""
self._check_pattern(version)
self.major : str = ''
self.minor : str = ''
self.hotfix : str = ''
self.version_object(version)
def version_object(self, version: str):
"""Set all version properties to a string"""
version = version.split('.')
self.major = version[0]
self.minor = version[1]
self.hotfix = version[2]
def format_to_tag(self, repository: str) -> str:
"""From version information, format to return a string"""
return f'{repository}:{self.major}.{self.minor}.{self.hotfix}'
def tag_name(self) -> str:
"""Return the tag name 'latest', 'X.Y.Z'..."""
return f'{self.major}.{self.minor}.{self.hotfix}'
def tag_name_slugify(self):
return f'{self.major}-{self.minor}-{self.hotfix}'
@staticmethod
def _check_pattern(tested: str):
"""Simple test to look if the string give can be a blender version name."""
test = re.match(r'^[0-9]{1}.[0-9]+.[0-9]+$', tested)
if test is None:
print(f'Bad version name set: {tested}')
exit(1)
def __str__(self):
return str(self.major + '.' + self.minor + '.' + self.hotfix)
+32
View File
@@ -0,0 +1,32 @@
{
"tags":
[{
"b3d_vs_major": 4.1,
"b3d_vs_minor": 0
},
{
"b3d_vs_major": 3.6,
"b3d_vs_minor": 12
},
{
"b3d_vs_major": 3.6,
"b3d_vs_minor": 11
},
{
"b3d_vs_major": 3.6,
"b3d_vs_minor": 10
},
{
"b3d_vs_major": 3.3,
"b3d_vs_minor": 19
},
{
"b3d_vs_major": 3.3,
"b3d_vs_minor": 18
},
{
"b3d_vs_major": 3.3,
"b3d_vs_minor": 17
}
]
}
+7
View File
@@ -0,0 +1,7 @@
FROM alpine AS csv
LABEL Author="stilobique"
LABEL Title="Linux image with subversion and git"
# Install dependencies
RUN apk add git && apk add subversion
+60 -19
View File
@@ -1,24 +1,65 @@
from logging import INFO import os
import pathlib
import json
import docker
import sys
from blender.version import BlenderVersion # TODO Update readme file badge with blender version
from blender.docker import BlenderDocker # TODO add a check if the image build are stored in the docker register
# Todo Update readme file badge with blender version
def get_data_from_json() -> (str, str):
"""From the json file, get blender version (major and minor), and return this information."""
file = pathlib.Path(os.getcwd(), 'build.json')
with open(file, 'r') as f:
data = json.load(f)
f.close()
version_values = data['tags'][0]
major, minor = version_values['b3d_vs_major'], version_values['b3d_vs_minor']
return major, minor
def build_docker(b3d_version: (str, str), latest: bool = False):
"""
Generate docker image, request the blender version (major, minor) with the keyword b3d_version, and can be
tagged with latest or only blender version info.
"""
client = docker.from_env()
if os.environ.get('docker_hub_password'):
client.login(username=os.environ['docker_hub_user'], password=os.environ['docker_hub_password'])
# Init all data to build the docker image
args = {
'b3d_vs_major': str(b3d_version[0]),
'b3d_vs_minor': str(b3d_version[1]),
}
docker_repo = 'stilobique/blender'
if latest:
tag = 'latest'
else:
tag = f'{args["b3d_vs_major"]}.{args["b3d_vs_minor"]}'
completed_tag_name = f'{docker_repo}:{args["b3d_vs_major"]}.{args["b3d_vs_minor"]}'
print('\t1 -> Build from image file')
client.images.build(path=os.getcwd(), buildargs=args, tag=completed_tag_name, rm=True, forcerm=True)
print(f'\t2 -> Push the new images with tag {tag}')
push = client.images.push(repository=docker_repo, tag=tag, stream=True)
print(f'\t\tDebug push :\n\t\t{push}')
if __name__ == "__main__": if __name__ == "__main__":
version = BlenderVersion(input("Give the blender version (X.Y.Z): ")) version = get_data_from_json()
print(f'Start -> Setup the package with blender version "{version[0]}.{version[1]}"')
print(f"Build a blender version {version}.") for arg in sys.argv:
docker = BlenderDocker(blender=version, severity=INFO) if '--tag=' in arg:
docker.build() print(f'Build with the tag "{arg}".')
build_docker(b3d_version=version, latest=True)
print('Test your image.') break
test = docker.test() else:
build_docker(b3d_version=version)
if test: print(f'Start to build the docker file.')
print('Test work, push this image.') pass
docker.push()
# Todo: Add a latest tag, check if this version are the latest
# Todo: Add a LTS tag, check if this version are a LTS
+1
View File
@@ -0,0 +1 @@
docker
-3
View File
@@ -1,3 +0,0 @@
docker==7.2.0
requests==2.34.2
invoke==3.0.3
-34
View File
@@ -1,34 +0,0 @@
"""
Invoke CLI entry point for the reusable helpers in `blender/misc.py`.
Ex: python -m blender.cli clean
Ex: python -m blender.cli get-tags-release
"""
from invoke import Collection, Program, task
from blender.misc import clean_all_dockers, get_all_tags_from_blender, get_all_tags_from_docker
@task(name='clean')
def clean(ctx):
"""Nettoie Docker pour tous les contextes disponibles (default, podman, ...)."""
clean_all_dockers()
@task(name='get-tags-release')
def get_tags_release(ctx):
"""Recupere tous les tags de release Blender depuis Gitea."""
get_all_tags_from_blender()
@task(name='get-tags-docker')
def get_tags_docker(ctx):
"""Recupere tous les tags de l'image Docker stilobique/blender depuis Docker Hub."""
get_all_tags_from_docker()
namespace = Collection(clean, get_tags_release, get_tags_docker)
program = Program(namespace=namespace, version='0.1.0')
if __name__ == '__main__':
program.run()