Skip to content

Commit bc75b4f

Browse files
committed
[IMP] packaging: add wine based windows packaging
Replace the initial KVM VM workflow with a Docker container using Wine for cross-platform Windows builds. - Add Dockerfile_wine: Wine + Python 3.14 + Inno Setup 6 + mingw-w64 + Rust - Add buildwine.sh: orchestrates the full build (Xvfb, Wine Python, cross-compilation via MinGW, PyInstaller, Inno Setup) - Makefile: add $(PYTHON) for Wine Python, exclude watchers from submodule loop - getversion.sh: add AW_VERSION environment variable - package-all.sh: add AW_PLATFORM and INNOSETUPDIR for Inno Setup via Wine - includes aw-systray-odoo.exe in NSIS installer The main idea is to cross compile rust binaries for windows within a Docker container. For that, the Cargo build target "x86_64-pc-windows-gnu" is used. On the other hand, the wine is needed for pyinstaller and Inno Setup.
1 parent 372c1e3 commit bc75b4f

7 files changed

Lines changed: 318 additions & 32 deletions

File tree

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ package:
196196
rm -rf dist
197197
mkdir -p dist/activitywatch
198198
for dir in $(PACKAGEABLES); do \
199+
if [[ "$(ODOO_WINDOWS_BUILD)" == "true" ]] && [[ "$$dir" == "aw-watcher-afk" || "$$dir" == "aw-watcher-window" ]]; then \
200+
continue; \
201+
fi; \
199202
make --directory=$$dir package; \
200203
cp -r $$dir/dist/$$dir dist/activitywatch; \
201204
done
@@ -205,11 +208,8 @@ ifeq ($(TAURI_BUILD),true)
205208
cp aw-server-rust/target/$(targetdir)/aw-sync dist/activitywatch/aw-server-rust/aw-sync
206209
else
207210
ifeq ($(ODOO_WINDOWS_BUILD),true)
208-
# ODOO_WINDOWS_BUILD: Install pystray and build aw-systray-odoo.exe via PyInstaller
209-
@echo "ODOO_WINDOWS_BUILD: Installing pystray..."
210-
python -m pip install pystray pillow pywin32
211211
@echo "ODOO_WINDOWS_BUILD: Building aw-systray-odoo.exe via PyInstaller..."
212-
pyinstaller --clean --noconfirm odoo-setup/aw-systray-odoo.spec
212+
$(PYTHON) -m PyInstaller --clean --noconfirm odoo-setup/aw-systray-odoo.spec
213213
cp dist/aw-systray-odoo.exe dist/activitywatch/aw-systray-odoo.exe
214214
endif
215215
endif

odoo-setup/Dockerfile_windows

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
FROM debian:trixie
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
ENV LC_ALL=C.UTF-8
5+
6+
RUN dpkg --add-architecture i386
7+
8+
ARG USID=1000 GRID=1000
9+
RUN groupadd -g $GRID odoo \
10+
&& useradd --create-home -u $USID -g odoo -G audio,video odoo \
11+
&& mkdir -p /run/user/$USID \
12+
&& chmod 700 /run/user/$USID
13+
14+
RUN apt-get update \
15+
&& apt-get install -y curl unzip 7zip xvfb mingw-w64 rustup make build-essential git zip \
16+
python3 python3-pip python3-venv python3-poetry \
17+
&& rm -rf /var/lib/apt/lists/*
18+
19+
RUN mkdir -pm755 /etc/apt/keyrings \
20+
&& curl -sSL https://dl.winehq.org/wine-builds/winehq.key -o /etc/apt/keyrings/winehq-archive.key \
21+
&& curl -sSL https://dl.winehq.org/wine-builds/debian/dists/trixie/winehq-trixie.sources -o /etc/apt/sources.list.d/winehq.sources \
22+
&& apt-get update \
23+
&& apt-get install --install-recommends -y winehq-stable \
24+
&& rm -rf /var/lib/apt/lists/*
25+
26+
USER odoo
27+
28+
ENV NVM_DIR=/home/odoo/.nvm
29+
ENV NODE_VERSION=22.22.1
30+
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \
31+
bash -c "source $NVM_DIR/nvm.sh && \
32+
nvm install $NODE_VERSION && \
33+
nvm alias default $NODE_VERSION && \
34+
nvm use default"
35+
36+
ENV PATH="$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH"
37+
38+
RUN rustup toolchain install nightly \
39+
&& rustup default nightly \
40+
&& rustup target add x86_64-pc-windows-gnu
41+
42+
RUN python3 -m venv /home/odoo/awvenv
43+
ENV PATH="/home/odoo/awvenv/bin:$PATH"
44+
ENV VIRTUAL_ENV=/home/odoo/awvenv
45+
46+
ENV XDG_RUNTIME_DIR=/run/user/$USID
47+
48+
ENV WINEPREFIX=/home/odoo/.wine
49+
ENV WINEARCH=win64
50+
# Ignoring debug and fixme's message when running wine
51+
ENV WINEDEBUG=-all
52+
53+
ENV CARGO_BUILD_TARGET=x86_64-pc-windows-gnu
54+
55+
RUN wineboot --init && wineserver -w
56+
57+
RUN curl -fsSL https://www.python.org/ftp/python/3.14.4/python-3.14.4-amd64.exe -o /tmp/python-installer.exe \
58+
&& chmod u+x /tmp/python-installer.exe
59+
60+
RUN xvfb-run --auto-servernum --server-args="-screen 0 1024x768x24" \
61+
sh -c "wine /tmp/python-installer.exe /quiet Include_doc=0 InstallAllUsers=1 PrependPath=1 \
62+
&& (while pgrep -u $(id -u) -x wineserver > /dev/null 2>&1; do sleep 1; done) \
63+
&& wineserver -w"
64+
65+
RUN PYTHON_EXE="/home/odoo/.wine/drive_c/Program Files/Python314/python.exe" \
66+
&& PIP_EXE="/home/odoo/.wine/drive_c/Program Files/Python314/Scripts/pip.exe" \
67+
&& xvfb-run --auto-servernum --server-args="-screen 0 1024x768x24" \
68+
sh -c "wine '$PIP_EXE' install pystray pillow pywin32 pyinstaller"
69+
70+
RUN curl -fsSL https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe -o /tmp/innosetup.exe \
71+
&& chmod u+x /tmp/innosetup.exe \
72+
&& xvfb-run --auto-servernum wine /tmp/innosetup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART \
73+
&& (while pgrep -u $(id -u) -x wineserver > /dev/null 2>&1; do sleep 1; done) \
74+
&& wineserver -w

odoo-setup/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# ActivityWatch Odoo Package Build
2+
3+
Build script for Odoo version of ActivityWatch packages for Linux (Debian/Ubuntu) and Windows.
4+
5+
## Requirements
6+
7+
- Docker
8+
- `realpath` (coreutils)
9+
- Sufficient disk space (~2GB for Docker images + build artifacts)
10+
11+
## Usage
12+
13+
```bash
14+
./buildpackage.sh <distro> [package_name]
15+
```
16+
17+
### Arguments
18+
19+
| Argument | Description | Default |
20+
|---|---|---|
21+
| `distro` | Target platform (`noble`, `jammy`, `windows`) | `noble` |
22+
| `package_name` | Output package name | `activitywatch-odoo-<distro>` |
23+
24+
### Examples
25+
26+
```bash
27+
# Build Ubuntu 24.04 package
28+
./buildpackage.sh noble
29+
30+
# Build Ubuntu 22.04 package
31+
./buildpackage.sh jammy
32+
33+
# Build Windows installer
34+
./buildpackage.sh windows
35+
36+
# Custom package name
37+
./buildpackage.sh jammy activitywatch-custom
38+
```
39+
40+
## Outputs
41+
42+
After a successful build, artifacts are available in `odoo-setup/dist/`:
43+
44+
| Platform | Output |
45+
|---|---|
46+
| Linux | `activitywatch-odoo-<distro>.deb` |
47+
| Windows | `activitywatch-odoo-YYYY-MM-DD-windows-x86_64-setup.exe` + `.zip` |
48+
49+
## Troubleshooting
50+
51+
### Docker permission errors
52+
53+
Ensure your user is in the `docker` group:
54+
55+
```bash
56+
sudo usermod -aG docker $USER
57+
```

odoo-setup/buildpackage.sh

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
set -xe
33

44
cd "$(dirname "$(realpath "$0")")" || exit
5+
mkdir -p dist
56

67
DISTRO_NAME="${1:-${DISTRO_NAME:-"noble"}}"
78
PACKAGE_NAME="${2:-${PACKAGE_NAME:-"activitywatch-odoo-$DISTRO_NAME"}}"
@@ -11,27 +12,38 @@ DOCKER_TAG="${DISTRO_NAME}-awbuilder"
1112

1213
docker build -t "${DOCKER_TAG}" . -f "${DOCKER_FILE}" --build-arg=USID=$(id -u) --build-arg=GRID=$(id -g)
1314

14-
DEBUILD_PATH="/data/build/dist/${PACKAGE_NAME}"
15+
if [[ "$DISTRO_NAME" == "windows" ]]; then
16+
docker run --rm \
17+
-v "$(realpath ../):/data/build/activitywatch" \
18+
-w /data/build/activitywatch/odoo-setup \
19+
-t "${DOCKER_TAG}:latest" \
20+
./buildwine.sh
21+
else
22+
DEBUILD_PATH="/data/build/activitywatch/dist/${PACKAGE_NAME}"
1523

16-
docker run --rm -v ../:/data/build/activitywatch -v ./dist:/data/build/dist -t "${DOCKER_TAG}:latest" \
17-
/bin/bash -c \
18-
"cd /data/build/activitywatch \
19-
&& mkdir -p ${DEBUILD_PATH} \
20-
&& rm -rf ${DEBUILD_PATH}/* \
21-
&& make clean \
22-
&& make build SUBMODULES='aw-core aw-client aw-qt aw-server aw-server-rust aw-watcher-afk aw-watcher-window awatcher' \
23-
&& make package SUBMODULES='aw-core aw-client aw-qt aw-server aw-server-rust aw-watcher-afk aw-watcher-window awatcher' \
24-
&& unzip /data/build/activitywatch/dist/activitywatch-*-linux-x86_64.zip -d ${DEBUILD_PATH}/opt/ \
25-
&& echo 'Preparing Debian Package' \
26-
&& cd ${DEBUILD_PATH} \
27-
&& cp -rav /data/build/activitywatch/odoo-setup/debian DEBIAN \
28-
&& mkdir -p usr/share/gnome-shell/extensions/ \
29-
&& unzip /data/build/activitywatch/odoo-setup/focused-window-dbus-${DISTRO_NAME}.zip -d usr/share/gnome-shell/extensions/ \
30-
&& cp /data/build/activitywatch/odoo-setup/aw-systray-odoo.py opt/activitywatch/aw-systray-odoo.py \
31-
&& mkdir -p etc/xdg/autostart/ \
32-
&& mkdir -p usr/share/applications/ \
33-
&& cp /data/build/activitywatch/odoo-setup/activitywatch-odoo.desktop etc/xdg/autostart/ \
34-
&& cp /data/build/activitywatch/odoo-setup/activitywatch-odoo.desktop usr/share/applications/ \
35-
&& echo 'Building Debian Package' \
36-
&& cd /data/build/dist \
37-
&& dpkg-deb --root-owner-group -Zxz --build ${PACKAGE_NAME}"
24+
docker run --rm \
25+
-v "$(realpath ../):/data/build/activitywatch" \
26+
-w "/data/build/activitywatch" \
27+
-t "${DOCKER_TAG}:latest" \
28+
/bin/bash -c " \
29+
mkdir -p ${DEBUILD_PATH} && \
30+
rm -rf ${DEBUILD_PATH}/* && \
31+
make clean && \
32+
make build SUBMODULES='aw-core aw-client aw-qt aw-server aw-server-rust aw-watcher-afk aw-watcher-window awatcher' && \
33+
make package SUBMODULES='aw-core aw-client aw-qt aw-server aw-server-rust aw-watcher-afk aw-watcher-window awatcher' && \
34+
mkdir -p ${DEBUILD_PATH}/opt && \
35+
unzip /data/build/activitywatch/dist/activitywatch-*-linux-x86_64.zip -d ${DEBUILD_PATH}/opt/ && \
36+
echo 'Preparing Debian Package' && \
37+
cd ${DEBUILD_PATH} && \
38+
cp -rav /data/build/activitywatch/odoo-setup/debian DEBIAN && \
39+
mkdir -p usr/share/gnome-shell/extensions/ && \
40+
unzip /data/build/activitywatch/odoo-setup/focused-window-dbus-${DISTRO_NAME}.zip -d usr/share/gnome-shell/extensions/ && \
41+
cp /data/build/activitywatch/odoo-setup/aw-systray-odoo.py opt/activitywatch/aw-systray-odoo.py && \
42+
mkdir -p etc/xdg/autostart/ && \
43+
mkdir -p usr/share/applications/ && \
44+
cp /data/build/activitywatch/odoo-setup/activitywatch-odoo.desktop etc/xdg/autostart/ && \
45+
cp /data/build/activitywatch/odoo-setup/activitywatch-odoo.desktop usr/share/applications/ && \
46+
echo 'Building Debian Package' && \
47+
cd /data/build/activitywatch/dist && \
48+
dpkg-deb --root-owner-group -Zxz --build ${PACKAGE_NAME}"
49+
fi

odoo-setup/buildwine.sh

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
: "${CARGO_BUILD_TARGET:=x86_64-pc-windows-gnu}"
5+
: "${ODOO_WINDOWS_BUILD:=true}"
6+
: "${SKIP_SERVER_PYTHON:=true}"
7+
: "${WINEPREFIX:=/home/odoo/.wine}"
8+
: "${WINEARCH:=win64}"
9+
10+
PYTHON_DIR="$WINEPREFIX/drive_c/Program Files/Python314"
11+
PYTHON_WIN="C:\\Program Files\\Python314\\python.exe"
12+
PIP_WIN="C:\\Program Files\\Python314\\Scripts\\pip.exe"
13+
AW_SOURCE="/data/build/activitywatch"
14+
15+
export CARGO_BUILD_TARGET ODOO_WINDOWS_BUILD SKIP_SERVER_PYTHON
16+
export WINEPREFIX WINEARCH WINEDEBUG=-all
17+
export XDG_RUNTIME_DIR=/run/user/1000
18+
export DISPLAY=${DISPLAY:-:99}
19+
20+
# Python venv wrappers needed for make subshells
21+
export VIRTUAL_ENV=/home/odoo/awvenv
22+
export PATH="/home/odoo/awvenv/bin:$PATH"
23+
mkdir -p /tmp/awvenv-wrappers
24+
cat > /tmp/awvenv-wrappers/pip << 'EOF'
25+
#!/bin/bash
26+
exec /home/odoo/awvenv/bin/pip "$@"
27+
EOF
28+
cat > /tmp/awvenv-wrappers/python << 'EOF'
29+
#!/bin/bash
30+
exec /home/odoo/awvenv/bin/python "$@"
31+
EOF
32+
chmod +x /tmp/awvenv-wrappers/{pip,python}
33+
export PATH="/tmp/awvenv-wrappers:$PATH"
34+
export PYTHON="wine '$PYTHON_WIN'"
35+
export WINE_PYTHON="wine '$PYTHON_WIN'"
36+
37+
# Xvfb start and stop functions
38+
XVFB_PID=""
39+
_start_xvfb() {
40+
if ! pgrep -x Xvfb > /dev/null 2>&1; then
41+
Xvfb $DISPLAY -screen 0 1024x768x24 &
42+
XVFB_PID=$!
43+
sleep 2
44+
echo "[Xvfb] started (PID $XVFB_PID)"
45+
fi
46+
}
47+
48+
_stop_xvfb() {
49+
if [[ -n "$XVFB_PID" ]] && kill -0 "$XVFB_PID" 2>/dev/null; then
50+
kill "$XVFB_PID"
51+
wait "$XVFB_PID" 2>/dev/null || true
52+
echo "[Xvfb] stopped"
53+
fi
54+
}
55+
56+
trap _stop_xvfb EXIT
57+
_start_xvfb
58+
59+
# Wine Python helpers
60+
wine_pip() {
61+
xvfb-run --auto-servernum wine "$PIP_WIN" "$@"
62+
}
63+
64+
wine_python() {
65+
xvfb-run --auto-servernum wine "$PYTHON_WIN" "$@"
66+
}
67+
68+
echo "[Wine] initializing prefix..."
69+
wineboot --init 2>/dev/null || true
70+
wineserver -w
71+
72+
echo "[Build] Starting ActivityWatch build..."
73+
export AW_VERSION="0.13.2"
74+
cd "$AW_SOURCE"
75+
make build
76+
77+
echo "[PyInstaller] Installing dependencies ..."
78+
wine_pip install --no-warn-script-location pystray pillow pynput wmi
79+
80+
echo "[PyInstaller] Installing aw packages in Wine..."
81+
wine_pip install --no-warn-script-location \
82+
"$AW_SOURCE/aw-core" \
83+
"$AW_SOURCE/aw-client"
84+
85+
echo "[PyInstaller] Building aw-systray-odoo.exe..."
86+
wine_python -m PyInstaller --clean --noconfirm odoo-setup/aw-systray-odoo.spec
87+
88+
echo "[PyInstaller] Building aw-watcher-afk.exe..."
89+
cd "$AW_SOURCE/aw-watcher-afk"
90+
wine_python -m PyInstaller --clean --noconfirm aw-watcher-afk.spec
91+
92+
echo "[PyInstaller] Building aw-watcher-window.exe..."
93+
cd "$AW_SOURCE/aw-watcher-window"
94+
wine_python -m PyInstaller --clean --noconfirm aw-watcher-window.spec
95+
96+
cd "$AW_SOURCE"
97+
STAGING_DIR="$AW_SOURCE/staging-watcher-build"
98+
mkdir -p "$STAGING_DIR"
99+
cp "$AW_SOURCE/dist/aw-systray-odoo.exe" "$STAGING_DIR/"
100+
cp -r "$AW_SOURCE/aw-watcher-afk/dist/aw-watcher-afk" "$STAGING_DIR/"
101+
cp -r "$AW_SOURCE/aw-watcher-window/dist/aw-watcher-window" "$STAGING_DIR/"
102+
103+
export AW_VERSION="odoo-$(date +%Y-%m-%d)"
104+
export AW_PLATFORM=windows
105+
export INNOSETUPDIR="C:\Program Files (x86)\Inno Setup 6"
106+
echo "[Package] Creating package..."
107+
rm -rf "$AW_SOURCE/dist"
108+
mkdir -p "$AW_SOURCE/dist/activitywatch"
109+
for dir in aw-server-rust; do
110+
make --directory="$AW_SOURCE/$dir" package
111+
cp -r "$AW_SOURCE/$dir/dist/$dir" "$AW_SOURCE/dist/activitywatch/"
112+
done
113+
cp -r "$STAGING_DIR/aw-watcher-afk" "$AW_SOURCE/dist/activitywatch/"
114+
cp -r "$STAGING_DIR/aw-watcher-window" "$AW_SOURCE/dist/activitywatch/"
115+
cp "$STAGING_DIR/aw-systray-odoo.exe" "$AW_SOURCE/dist/activitywatch/"
116+
rm -rf "$STAGING_DIR"
117+
118+
# Remove problem-causing binaries (see original Makefile)
119+
rm -f "$AW_SOURCE/dist/activitywatch/libdrm.so.2"
120+
rm -f "$AW_SOURCE/dist/activitywatch/libharfbuzz.so.0"
121+
rm -f "$AW_SOURCE/dist/activitywatch/libfontconfig.so.1"
122+
rm -f "$AW_SOURCE/dist/activitywatch/libfreetype.so.6"
123+
rm -rf "$AW_SOURCE/dist/activitywatch/pytz"
124+
125+
# Build zip and installer
126+
bash "$AW_SOURCE/scripts/package/package-all.sh"
127+
128+
echo ""
129+
echo "=== Build finished 🎉 ==="
130+
find "$AW_SOURCE/dist" -name "*.exe" -o -name "*.zip" 2>/dev/null | head -20

scripts/package/getversion.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
# TODO: Merge with scripts/package/getversion.sh
44
# set -e
55

6-
if [[ $TRAVIS_TAG ]]; then
6+
if [[ -n "${AW_VERSION:-}" ]]; then
7+
_version="$AW_VERSION";
8+
elif [[ $TRAVIS_TAG ]]; then
79
_version=$TRAVIS_TAG;
810
elif [[ $APPVEYOR_REPO_TAG_NAME ]]; then
911
_version=$APPVEYOR_REPO_TAG_NAME;

0 commit comments

Comments
 (0)