name: Build Releases on: push: branches: - "**" env: APP_NAME: gitree jobs: build-windows: name: Build Windows x64 runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: Sync submodules run: | git submodule sync --recursive git submodule update --init --force --recursive git submodule status --recursive - name: Generate build names env: GITEA_SHA: ${{ gitea.sha }} run: | short_sha="$(printf '%s' "$GITEA_SHA" | cut -c1-7)" echo "BUILD_HASH=${short_sha}" >> "$GITHUB_ENV" echo "WINDOWS_EXE=${APP_NAME}-windows-x64-${short_sha}.exe" >> "$GITHUB_ENV" echo "WINDOWS_ARTIFACT=${APP_NAME}-windows-x64-${short_sha}" >> "$GITHUB_ENV" - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y mingw-w64 cmake ninja-build curl jq - name: Create MinGW toolchain run: | cat > mingw-toolchain.cmake <<'EOF' set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86_64) set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) EOF - name: Configure Windows release build run: | cmake -S . -B build-win -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=mingw-toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_EXE_LINKER_FLAGS="-static -static-libgcc -static-libstdc++" - name: Build Windows executable run: cmake --build build-win --parallel - name: Locate Windows EXE run: | exe_path="" if [ -f "build-win/bin/${APP_NAME}.exe" ]; then exe_path="build-win/bin/${APP_NAME}.exe" elif [ -f "build-win/bin/Gitree.exe" ]; then exe_path="build-win/bin/Gitree.exe" elif [ -f "build-win/bin/gitree.exe" ]; then exe_path="build-win/bin/gitree.exe" else exe_path="$(find build-win -type f -iname '*.exe' | head -n 1)" fi if [ -z "$exe_path" ]; then echo "Could not find built Windows EXE." exit 1 fi echo "WINDOWS_EXE_PATH=${exe_path}" >> "$GITHUB_ENV" echo "Found Windows EXE: ${exe_path}" - name: Verify static runtime linkage run: | dependencies="$(x86_64-w64-mingw32-objdump -p "$WINDOWS_EXE_PATH" | sed -n 's/.*DLL Name: //p')" printf '%s\n' "$dependencies" if printf '%s\n' "$dependencies" | grep -Eqi 'lib(gcc|stdc\+\+|winpthread).*\.dll'; then echo "The executable still depends on a MinGW runtime DLL." exit 1 fi - name: Prepare Windows artifact run: | mkdir -p dist cp "$WINDOWS_EXE_PATH" "dist/${WINDOWS_EXE}" - name: Upload Windows build uses: actions/upload-artifact@v3 with: name: ${{ env.WINDOWS_ARTIFACT }} path: dist/${{ env.WINDOWS_EXE }} if-no-files-found: error build-linux: name: Build Linux x64 DEB runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: Sync submodules run: | git submodule sync --recursive git submodule update --init --force --recursive git submodule status --recursive - name: Generate build names env: GITEA_SHA: ${{ gitea.sha }} run: | short_sha="$(printf '%s' "$GITEA_SHA" | cut -c1-7)" echo "BUILD_HASH=${short_sha}" >> "$GITHUB_ENV" echo "LINUX_DEB=${APP_NAME}-deb-x64-${short_sha}.deb" >> "$GITHUB_ENV" echo "LINUX_ARTIFACT=${APP_NAME}-deb-x64-${short_sha}" >> "$GITHUB_ENV" - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y \ cmake ninja-build curl jq dpkg-dev \ libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev \ libgl1-mesa-dev - name: Configure Linux release build run: | cmake -S . -B build-linux -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF - name: Build Linux executable run: cmake --build build-linux --parallel - name: Locate Linux executable run: | exe_path="" if [ -f "build-linux/bin/${APP_NAME}" ]; then exe_path="build-linux/bin/${APP_NAME}" elif [ -f "build-linux/bin/Gitree" ]; then exe_path="build-linux/bin/Gitree" elif [ -f "build-linux/bin/gitree" ]; then exe_path="build-linux/bin/gitree" else exe_path="$(find build-linux -type f -executable -name "${APP_NAME}" | head -n 1)" fi if [ -z "$exe_path" ]; then echo "Could not find built Linux executable." exit 1 fi echo "LINUX_EXE_PATH=${exe_path}" >> "$GITHUB_ENV" echo "Found Linux executable: ${exe_path}" - name: Package Linux DEB run: | mkdir -p "pkg/DEBIAN" mkdir -p "pkg/usr/bin" cp "$LINUX_EXE_PATH" "pkg/usr/bin/${APP_NAME}" chmod 755 "pkg/usr/bin/${APP_NAME}" version="0.0.0+${BUILD_HASH}" cat > "pkg/DEBIAN/control" < Description: ${APP_NAME} ${APP_NAME} Linux x64 build. EOF mkdir -p dist dpkg-deb --build pkg "dist/${LINUX_DEB}" - name: Upload Linux DEB build uses: actions/upload-artifact@v3 with: name: ${{ env.LINUX_ARTIFACT }} path: dist/${{ env.LINUX_DEB }} if-no-files-found: error release: name: Create Release runs-on: ubuntu-latest needs: - build-windows - build-linux if: ${{ always() && gitea.ref_name == 'prod' }} env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_SERVER_URL: ${{ gitea.server_url }} GITEA_REPOSITORY: ${{ gitea.repository }} GITEA_SHA: ${{ gitea.sha }} WINDOWS_RESULT: ${{ needs.build-windows.result }} LINUX_RESULT: ${{ needs.build-linux.result }} APP_NAME: gitree steps: - name: Check build results run: | echo "Windows result: ${WINDOWS_RESULT}" echo "Linux result: ${LINUX_RESULT}" if [ "$WINDOWS_RESULT" != "success" ] && [ "$LINUX_RESULT" != "success" ]; then echo "Both Windows and Linux builds failed. Refusing to create release." exit 1 fi - name: Check out repository uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: Sync submodules run: | git submodule sync --recursive git submodule update --init --force --recursive git submodule status --recursive - name: Generate release names run: | short_sha="$(printf '%s' "$GITEA_SHA" | cut -c1-7)" echo "BUILD_HASH=${short_sha}" >> "$GITHUB_ENV" echo "RELEASE_TAG=release-${short_sha}" >> "$GITHUB_ENV" echo "RELEASE_NAME=Release ${short_sha}" >> "$GITHUB_ENV" echo "WINDOWS_EXE=${APP_NAME}-windows-x64-${short_sha}.exe" >> "$GITHUB_ENV" echo "WINDOWS_ARTIFACT=${APP_NAME}-windows-x64-${short_sha}" >> "$GITHUB_ENV" echo "LINUX_DEB=${APP_NAME}-deb-x64-${short_sha}.deb" >> "$GITHUB_ENV" echo "LINUX_ARTIFACT=${APP_NAME}-deb-x64-${short_sha}" >> "$GITHUB_ENV" - name: Install release dependencies run: | sudo apt-get update sudo apt-get install -y curl jq - name: Download Windows artifact if: ${{ needs.build-windows.result == 'success' }} uses: actions/download-artifact@v3 with: name: ${{ env.WINDOWS_ARTIFACT }} path: release-assets - name: Download Linux artifact if: ${{ needs.build-linux.result == 'success' }} uses: actions/download-artifact@v3 with: name: ${{ env.LINUX_ARTIFACT }} path: release-assets - name: Create prod release run: | if [ -z "$GITEA_TOKEN" ]; then echo "The repository secret GITEA_TOKEN is required to publish prod releases." exit 1 fi git fetch --tags --force api="${GITEA_SERVER_URL%/}/api/v1/repos/${GITEA_REPOSITORY}" repo_url="${GITEA_SERVER_URL%/}/${GITEA_REPOSITORY}" previous_tag="$( curl --fail-with-body --silent --show-error \ -H "Authorization: token ${GITEA_TOKEN}" \ "${api}/releases?limit=50" | jq -r '[.[] | select(.tag_name | startswith("release-"))][0].tag_name // empty' )" if [ -n "$previous_tag" ]; then range="${previous_tag}..${GITEA_SHA}" else range="${GITEA_SHA}" fi { if [ -n "$previous_tag" ]; then echo "## Changes since ${previous_tag}" else echo "## Changes" fi echo commit_count="$(git rev-list --count "$range")" if [ "$commit_count" -eq 0 ]; then echo "- No commits found since the previous release." else git log "$range" \ --reverse \ --pretty=format:'%H%x1f%s' | while IFS="$(printf '\037')" read -r hash subject; do short="$(printf '%s' "$hash" | cut -c1-7)" echo "- ([${short}](${repo_url}/commit/${hash})) ${subject}" done fi echo echo "## Builds" echo if [ "$WINDOWS_RESULT" = "success" ]; then echo "- ${WINDOWS_EXE}" else echo "- Windows x64 failed" fi if [ "$LINUX_RESULT" = "success" ]; then echo "- ${LINUX_DEB}" else echo "- Linux DEB x64 failed" fi echo echo "## Authors" echo echo "Sorted by total lines added or removed." echo git log "$range" --numstat --format='author:%an <%ae>' | awk ' /^author:/ { author = substr($0, 8) next } NF >= 3 { added = ($1 == "-" ? 0 : $1) removed = ($2 == "-" ? 0 : $2) adds[author] += added dels[author] += removed churn[author] += added + removed } END { for (a in churn) { printf "%d\t%d\t%d\t%s\n", churn[a], adds[a], dels[a], a } } ' | sort -nr | while IFS="$(printf '\t')" read -r total added removed author; do echo "- ${author} — ${total} lines changed (+${added} / -${removed})" done } > release-notes.md payload="$(jq -n \ --arg tag "$RELEASE_TAG" \ --arg sha "$GITEA_SHA" \ --arg name "$RELEASE_NAME" \ --rawfile body release-notes.md \ '{ tag_name: $tag, target_commitish: $sha, name: $name, body: $body, draft: false, prerelease: false }')" release="$(curl --fail-with-body --silent --show-error \ -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ --data "$payload" \ "${api}/releases")" release_id="$(printf '%s' "$release" | jq -er '.id')" for asset in release-assets/*; do if [ ! -f "$asset" ]; then continue fi asset_name="$(basename "$asset")" echo "Uploading ${asset_name}" curl --fail-with-body --silent --show-error \ -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -F "attachment=@${asset}" \ "${api}/releases/${release_id}/assets?name=${asset_name}" done