aboutsummaryrefslogtreecommitdiff
path: root/.github/workflows/ci.yml
blob: 9f0e11b5e3abcda88475140e9004604a19ff5858 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
name: CI

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
  workflow_dispatch:

env:
  CCACHE_DIR: /__w/.ccache
  CCACHE_BASEDIR: /__w/Fluorine-Manager/Fluorine-Manager
  CCACHE_COMPILERCHECK: content
  CCACHE_NOHASHDIR: "1"
  CCACHE_SLOPPINESS: time_macros
  CCACHE_MAXSIZE: 2G
  CMAKE_BUILD_TYPE: RelWithDebInfo

jobs:
  build:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/sulfurnitride/fluorine-builder:latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Restore ccache
        uses: actions/cache@v4
        with:
          path: /__w/.ccache
          key: ccache-v1-${{ github.ref_name }}-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt') }}
          restore-keys: |
            ccache-v1-${{ github.ref_name }}-
            ccache-v1-

      - name: Rust cache
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: |
            libs/nak_ffi
            libs/bsa_ffi

      - name: Configure
        run: |
          cmake -S . -B build -G Ninja \
            -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \
            -DCMAKE_C_COMPILER_LAUNCHER=ccache \
            -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
            -DPython3_EXECUTABLE="$(command -v python3)" \
            -DBUILD_PLUGIN_PYTHON=ON

      - name: Build
        run: cmake --build build --parallel

      - name: Build AppImage
        run: |
          set -euo pipefail

          MODORG_BIN="$(find build -type f -name ModOrganizer | head -n1)"
          if [ -z "${MODORG_BIN}" ]; then
            echo "::error::ModOrganizer binary not found under build/"
            exit 1
          fi
          RUNDIR="$(dirname "${MODORG_BIN}")"

          # ── Populate AppDir ──
          APPDIR="${GITHUB_WORKSPACE}/AppDir"
          rm -rf "${APPDIR}"
          mkdir -p "${APPDIR}/usr/bin" "${APPDIR}/usr/plugins" "${APPDIR}/usr/dlls" "${APPDIR}/usr/python"

          cp -f "${RUNDIR}/ModOrganizer" "${APPDIR}/usr/bin/ModOrganizer"
          [ -f "${RUNDIR}/umu-run" ] && cp -f "${RUNDIR}/umu-run" "${APPDIR}/usr/bin/"
          [ -f "${RUNDIR}/README-PORTABLE.txt" ] && cp -f "${RUNDIR}/README-PORTABLE.txt" "${APPDIR}/usr/bin/"

          # Gather built plugin .so files
          find build/libs -type f \( \
            -name 'libgame_*.so' -o \
            -name 'libinstaller_*.so' -o \
            -name 'libpreview_*.so' -o \
            -name 'libdiagnose_*.so' -o \
            -name 'libcheck_*.so' -o \
            -name 'libtool_*.so' -o \
            -name 'libinieditor.so' -o \
            -name 'libinibakery.so' -o \
            -name 'libbsa_extractor.so' -o \
            -name 'libbsa_packer.so' -o \
            -name 'libproxy.so' -o \
            -name 'librunner.so' \
          \) -exec cp -f {} "${APPDIR}/usr/plugins/" \;

          # Python plugin payload
          for f in libplugin_python.so lzokay.py winreg.py pyCfg.py \
                   DDSPreview.py Form43Checker.py ScriptExtenderPluginChecker.py; do
            [ -f "build/src/src/plugins/${f}" ] && cp -f "build/src/src/plugins/${f}" "${APPDIR}/usr/plugins/"
          done
          for d in basic_games data libs dlls; do
            [ -d "build/src/src/plugins/${d}" ] && cp -a "build/src/src/plugins/${d}" "${APPDIR}/usr/plugins/"
          done
          rm -f "${APPDIR}/usr/plugins/FNIS"*.py

          # 7z runtime
          SO7="$(find build -type f -name 7z.so | head -n1 || true)"
          if [ -n "${SO7}" ]; then
            cp -f "${SO7}" "${APPDIR}/usr/dlls/7z.so"
            cp -f "${SO7}" "${APPDIR}/usr/dlls/7zip.dll"
          fi

          # Python runtime payload
          [ -d build/src/src/python ] && cp -a build/src/src/python/. "${APPDIR}/usr/python/"

          # icoutils
          for tool in wrestool icotool; do
            command -v "${tool}" >/dev/null 2>&1 && cp -f "$(command -v "${tool}")" "${APPDIR}/usr/bin/"
          done

          # Desktop metadata
          printf '%s\n' \
            '[Desktop Entry]' \
            'Type=Application' \
            'Name=ModOrganizer' \
            'Exec=ModOrganizer' \
            'Icon=modorganizer' \
            'Categories=Game;' \
            'Terminal=false' \
            > "${APPDIR}/ModOrganizer.desktop"
          cp -f src/src/resources/mo_icon.png "${APPDIR}/modorganizer.png"

          # ── Run linuxdeploy ──
          export PATH="/opt/linuxdeploy:${PATH}"
          export LINUXDEPLOY_PLUGIN_QT="/opt/linuxdeploy/linuxdeploy-plugin-qt.AppDir/AppRun"
          export QMAKE=/usr/bin/qmake6
          export NO_STRIP=1
          export EXTRA_QT_PLUGINS="waylandcompositor;wayland-decoration-client;wayland-graphics-integration-client;wayland-shell-integration"

          /opt/linuxdeploy/linuxdeploy-x86_64.AppImage --appimage-extract-and-run \
            --appdir "${APPDIR}" \
            --desktop-file "${APPDIR}/ModOrganizer.desktop" \
            --icon-file "${APPDIR}/modorganizer.png" \
            --executable "${APPDIR}/usr/bin/ModOrganizer" \
            --plugin qt \
            --output appimage

          # ── Verify + collect artifact ──
          APPIMAGE_FILE="$(find "${GITHUB_WORKSPACE}" -maxdepth 1 -type f -name '*.AppImage' | head -n1)"
          if [ -z "${APPIMAGE_FILE}" ]; then
            echo "::error::AppImage was not produced"
            exit 1
          fi

          # Sanity checks
          copy_relocs="$(readelf -r "${APPDIR}/usr/bin/ModOrganizer" | grep "R_X86_64_COPY" || true)"
          if [ -n "${copy_relocs}" ]; then
            echo "::error::ModOrganizer contains COPY relocations"
            echo "${copy_relocs}"
            exit 1
          fi

          mkdir -p artifact
          mv -f "${APPIMAGE_FILE}" "artifact/ModOrganizer-x86_64.AppImage"
          echo "AppImage size: $(du -sh artifact/)"

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ModOrganizer-AppImage
          path: artifact/
          if-no-files-found: error

      - name: ccache stats
        if: always()
        run: ccache -sv || true

  release:
    if: startsWith(github.ref, 'refs/tags/v')
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: ModOrganizer-AppImage
          path: artifact/

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: artifact/ModOrganizer-x86_64.AppImage
          generate_release_notes: true