diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..34c7693 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,408 @@ +name: Build (Windows/Linux) + +on: + pull_request: + workflow_dispatch: + + +env: + REGISTRY: ghcr.io/euro-office + CACHE_IMAGE_NAME: desktopeditors-build-cache + PRODUCT_VERSION: 9.3.1 + BUILD_NUMBER: dev.1 + BUILD_ROOT: /package + +jobs: + + # ───────────────────────────────────────────────────────────── + # BUILD + # + # Single job that builds the full target graph in one bake + # invocation per arch × brand. Because develop depends on + # docker which depends on packages, bake resolves the shared + # graph and never rebuilds a layer twice. + # + # Pushing per target: + # docker → pushed on default branch (:nightly) and git tags (:latest) + # develop → pushed on default branch only (:latest-dev) + # packages → never pushed; exported to local filesystem for release upload + # (euro-office → GitHub Release, nextcloud-office → SSH) + # ───────────────────────────────────────────────────────────── + build-common: + runs-on: 'ubuntu-latest' + permissions: + contents: write + packages: write + strategy: + fail-fast: false + matrix: + brand: + - name: euro-office + image_name: desktop-common + extra_bake_files: "" + extra_targets: "" + cache_prefix: "" + #- name: nextcloud-office + # image_name: no-desktop-common + # extra_bake_files: "./brands/nextcloud-office-brand/brand-server.hcl" + # extra_targets: "brand-icons," + # cache_prefix: "nextcloud-office-" + + steps: + - name: Checkout fork repository (with submodules) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + path: . + submodules: recursive + token: ${{ secrets.EURO_OFFICE_MIRROR_TOKEN }} + + - name: Checkout nextcloud-office-brand repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: Euro-Office/nextcloud-office-brand + path: build/brands/nextcloud-office-brand + token: ${{ secrets.EURO_OFFICE_MIRROR_TOKEN }} + + - name: Debug workspace + run: | + echo "workspace: ${{ github.workspace }}" + ls -la "${{ github.workspace }}" + ls -la "${{ github.workspace }}/build" || echo "build/ MISSING" + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - uses: actions/cache@v4 + id: wasm-cache + with: + path: | + wasm-ccache + wasm-em-cache + key: ${{ runner.os }}-wasm-${{ hashFiles('core/**/CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-wasm- + + - uses: reproducible-containers/buildkit-cache-dance@v3.4.0 + with: + builder: ${{ steps.buildx.outputs.name }} + cache-map: | + { + "wasm-ccache": { "target": "/ccache", "id": "wasm-ccache" }, + "wasm-em-cache": { "target": "/em-cache", "id": "wasm-em-cache" } + } + skip-extraction: ${{ steps.wasm-cache.outputs.cache-hit }} + + - name: Log in to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute tags and push flags + id: meta + run: | + REF_NAME=$(echo '${{ github.ref_name }}' | tr '/' '-') + echo "ref_name=${REF_NAME}" >> $GITHUB_OUTPUT + + echo "sha_short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + + # Parse PRODUCT_VERSION / BUILD_NUMBER from tag of the form v${PRODUCT_VERSION}-${BUILD_NUMBER} + # (e.g. v9.3.1-beta.1, v9.3.1-stable.1). Fall back to env defaults otherwise. + if [[ "$IS_TAG" == "true" && "${{ github.ref_name }}" =~ ^v([^-]+)-(.+)$ ]]; then + echo "product_version=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT + echo "build_number=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT + else + echo "product_version=${{ env.PRODUCT_VERSION }}" >> $GITHUB_OUTPUT + echo "build_number=${{ env.BUILD_NUMBER }}" >> $GITHUB_OUTPUT + fi + + - name: Build all targets (packages → docker → develop) + uses: docker/bake-action@4a9a8d494466d37134e2bfca2d3a8de8fb2681ad # v5.13.0 + with: + workdir: ./build + # For nextcloud-office, brand-server.hcl is appended; empty lines are + # filtered by bake-action so euro-office sees only the base file. + files: | + ./docker-bake.hcl + # extra_targets is either "" (euro-office) or "brand-icons," so the + # comma-joined target list resolves correctly for both. + targets: desktop-common + # push=false here; per-target pushing is controlled via tags below. + # Targets with empty tags= are not pushed regardless of this flag. + push: false + set: | + core-base.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}core-base-buildcache + core-base.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}core-base-buildcache,mode=max + core-base.tags= + core-wasm.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}core-wasm-buildcache + core-wasm.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}core-wasm-buildcache,mode=max + core-wasm.tags= + sdkjs-desktop.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}sdkjs-desktop-buildcache + sdkjs-desktop.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}sdkjs-desktop-buildcache,mode=max + sdkjs-desktop.tags= + desktop-js.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}desktop-js-buildcache + desktop-js.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}desktop-js-buildcache,mode=max + desktop-js.tags= + web-apps.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}web-apps-buildcache + web-apps.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}web-apps-buildcache,mode=max + web-apps.tags= + desktop-common.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}desktop-common-buildcache + desktop-common.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}desktop-common-buildcache,mode=max + desktop-common.tags=${{format('{0}/{1}:{2}', env.REGISTRY, matrix.brand.image_name, steps.meta.outputs.sha_short)}} + desktop-common.output=type=image,push=true + env: + REGISTRY: ${{ env.REGISTRY }} + PRODUCT_VERSION: ${{ steps.meta.outputs.product_version }} + BUILD_NUMBER: ${{ steps.meta.outputs.build_number }} + BUILD_ROOT: ${{ env.BUILD_ROOT }} + NUGET_CACHE: local + NEXTCLOUD_USER: ${{ secrets.NEXTCLOUD_USER }} + NEXTCLOUD_PASS: ${{ secrets.NEXTCLOUD_PASS }} + + - name: Extract files from image + run: | + docker create --name tmp ${{ env.REGISTRY }}/${{ matrix.brand.image_name }}:${{ steps.meta.outputs.sha_short }} true + docker cp tmp:/ ./common + docker rm tmp + - uses: actions/upload-artifact@v4 + with: + name: common-files + path: | + common + + build-windows: + runs-on: windows-2022 + needs: build-common + + env: + PYTHONUTF8: "1" + NEXTCLOUD_USER: ${{ secrets.NEXTCLOUD_USER }} + NEXTCLOUD_PASS: ${{ secrets.NEXTCLOUD_PASS }} + SCCACHE_GHA_ENABLED: "true" + + steps: + # ---- orchestration / environment setup (stays in the workflow) ---- # + - name: Checkout fork repository (with submodules) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + path: . + submodules: recursive + token: ${{ secrets.EURO_OFFICE_MIRROR_TOKEN }} + + - name: Checkout nextcloud-office-brand repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: Euro-Office/nextcloud-office-brand + path: build/brands/nextcloud-office-brand + token: ${{ secrets.EURO_OFFICE_MIRROR_TOKEN }} + + - uses: actions/download-artifact@v4 + with: + name: common-files + path: .\common + + # Install Cygwin WITHOUT touching PATH (the script does the PATH ordering). + # add-to-path:false stops the action from prepending C:\cygwin\bin and + # shadowing the native perl/python/git/cmake. + - name: Install Cygwin + uses: cygwin/cygwin-install-action@v4 + with: + install-dir: 'C:\cygwin64' + add-to-path: false + packages: >- + automake + cmake + make + git + python3 + python3-devel + + - name: Install Windows SDK, v141 toolset and ATL + shell: pwsh + run: | + $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vsInstaller = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vs_installer.exe" + $installPath = & $vsWhere -latest -products * -property installationPath + & $vsInstaller modify ` + --installPath $installPath ` + --add Microsoft.VisualStudio.Component.Windows10SDK.19041 ` + --add Microsoft.VisualStudio.Component.VC.v141.x86.x64 ` + --add Microsoft.VisualStudio.Component.VC.v141.ATL ` + --add Microsoft.VisualStudio.Component.VC.v141.MFC ` + --quiet --norestart --force + if ($LASTEXITCODE -notin @(0, 3010)) { + Write-Warning "vs_installer returned $LASTEXITCODE — components may already be installed, continuing." + } + + + - name: Set up sccache + uses: mozilla-actions/sccache-action@v0.0.10 # puts sccache on PATH + exports the cache tokens + + - name: Install Ninja + shell: pwsh + run: choco install ninja -y --no-progress # usually already on windows-2022; harmless if so + + - name: Set up vcpkg + uses: lukka/run-vcpkg@v11 # exports VCPKG_ROOT for the script + + # ---- build + package (single source of truth: the script) ---- # + # Covers: copy login assets, deterministic PATH (native > Cygwin > rest), + # tool verification, vcvars + CMake configure/build/install, robocopy + # overlay of common, and make.ps1 / make_zip.ps1 / make_inno.ps1. + - name: Build & package + shell: pwsh + run: | + .\build\windows\build.ps1 ` + -CommonDir "${{ github.workspace }}\common" ` + -ProductVersion "${{ env.PRODUCT_VERSION }}" ` + -Arch x64 ` + -Target standalone ` + -CompanyName Euro-Office ` + -ProductName DesktopEditors + + # ---- collect outputs (stays in the workflow) ---- # + - uses: actions/upload-artifact@v4 + with: + name: windows-packages + path: | + desktop-apps/package/zip/*.zip + desktop-apps/package/inno/*.exe + desktop-apps/package/advinst/*.msi + + + build-linux: + runs-on: 'ubuntu-latest' + needs: build-common + permissions: + contents: write + packages: write + strategy: + fail-fast: false + matrix: + brand: + - name: euro-office + extra_bake_files: "" + extra_targets: "" + cache_prefix: "" + #- name: nextcloud-office + # extra_bake_files: "./brands/nextcloud-office-brand/brand-server.hcl" + # extra_targets: "brand-icons," + # cache_prefix: "nextcloud-office-" + + steps: + - name: Checkout fork repository (with submodules) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + path: . + submodules: recursive + token: ${{ secrets.EURO_OFFICE_MIRROR_TOKEN }} + + - name: Checkout nextcloud-office-brand repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: Euro-Office/nextcloud-office-brand + path: build/brands/nextcloud-office-brand + token: ${{ secrets.EURO_OFFICE_MIRROR_TOKEN }} + + - name: Debug workspace + run: | + echo "workspace: ${{ github.workspace }}" + ls -la "${{ github.workspace }}" + ls -la "${{ github.workspace }}/build" || echo "build/ MISSING" + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Restore desktop build caches + uses: actions/cache@v4 + id: desktop-cache + with: + path: | + ccache + nuget-cache + key: ${{ runner.os }}-desktop-${{ hashFiles('core/vcpkg.json', 'desktop-apps/win-linux/CMakeLists.txt') }} + restore-keys: | + ${{ runner.os }}-desktop- + + - name: Inject caches into BuildKit + uses: reproducible-containers/buildkit-cache-dance@v3.4.0 + with: + builder: ${{ steps.buildx.outputs.name }} + cache-map: | + { + "ccache": { "target": "/ccache", "id": "ccache" }, + "nuget-cache": { "target": "/nuget-cache", "id": "nuget-cache" } + } + skip-extraction: ${{ steps.desktop-cache.outputs.cache-hit }} + + - name: Log in to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute tags and push flags + id: meta + run: | + REF_NAME=$(echo '${{ github.ref_name }}' | tr '/' '-') + echo "ref_name=${REF_NAME}" >> $GITHUB_OUTPUT + + echo "sha_short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + + # Parse PRODUCT_VERSION / BUILD_NUMBER from tag of the form v${PRODUCT_VERSION}-${BUILD_NUMBER} + # (e.g. v9.3.1-beta.1, v9.3.1-stable.1). Fall back to env defaults otherwise. + if [[ "$IS_TAG" == "true" && "${{ github.ref_name }}" =~ ^v([^-]+)-(.+)$ ]]; then + echo "product_version=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT + echo "build_number=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT + else + echo "product_version=${{ env.PRODUCT_VERSION }}" >> $GITHUB_OUTPUT + echo "build_number=${{ env.BUILD_NUMBER }}" >> $GITHUB_OUTPUT + fi + + - name: Build all targets (packages → docker → develop) + uses: docker/bake-action@4a9a8d494466d37134e2bfca2d3a8de8fb2681ad # v5.13.0 + with: + workdir: ./build/linux + # For nextcloud-office, brand-server.hcl is appended; empty lines are + # filtered by bake-action so euro-office sees only the base file. + files: | + ./docker-bake.hcl + # extra_targets is either "" (euro-office) or "brand-icons," so the + # comma-joined target list resolves correctly for both. + targets: packages + # push=false here; per-target pushing is controlled via tags below. + # Targets with empty tags= are not pushed regardless of this flag. + push: false + set: | + core-base.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}core-base-buildcache + core-base.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}core-base-buildcache,mode=max + core-base.tags= + third-party.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}third-party-buildcache + third-party.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}third-party-buildcache,mode=max + third-party.tags= + desktop-linux.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}desktop-linux-buildcache + desktop-linux.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}desktop-linux-buildcache,mode=max + desktop-linux.tags= + packages.cache-from=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}packages-buildcache + packages.cache-to=type=registry,ref=${{ env.REGISTRY }}/${{ env.CACHE_IMAGE_NAME }}:${{ steps.meta.outputs.ref_name }}-${{ matrix.brand.cache_prefix }}packages-buildcache,mode=max + packages.tags= + env: + GIT_COMMIT: ${{ steps.meta.outputs.sha_short }} + REGISTRY: ${{ env.REGISTRY }} + PRODUCT_VERSION: ${{ steps.meta.outputs.product_version }} + BUILD_NUMBER: ${{ steps.meta.outputs.build_number }} + BUILD_ROOT: ${{ env.BUILD_ROOT }} + NUGET_CACHE: local + NEXTCLOUD_USER: ${{ secrets.NEXTCLOUD_USER }} + NEXTCLOUD_PASS: ${{ secrets.NEXTCLOUD_PASS }} + + - uses: actions/upload-artifact@v4 + with: + name: linux-packages + path: | + build/linux/deploy/packages/*.deb + build/linux/deploy/packages/*.rpm \ No newline at end of file diff --git a/README.md b/README.md index 9e84d7f..b4c9f9e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The suite empowers you to create, edit, save, and export text documents, spreads ## Localization 🌐 - Constantly improving localization of the editors to make the suite accessible to all users, all over the world. +Constantly improving localization of the editors to make the suite accessible to all users, all over the world. * Interface available in 46 languages * RTL support @@ -47,8 +47,42 @@ Desktop Editors contain the following components: * [web-apps](https://github.com/Euro-Office/web-apps) - the frontend for [Document Server][1] which is a part of Desktop Editors that allows the user to create, edit, save and export text, spreadsheet and presentation documents using the common interface of a document editor. * [dictionaries](https://github.com/Euro-Office/dictionaries) - the dictionaries of various languages used for spellchecking in Desktop Editors. +## Build it yourself 🛠️ + +You can build Desktop Editors from source on **Windows** and **Linux**. This is a +super-repository, so the first step is always to check out the submodules: + +```sh +git clone https://github.com/Euro-Office/DesktopEditors.git +cd DesktopEditors +git submodule update --init --recursive +``` + +Then head to the build docs: + +* **[build/](./build/README.md)** — start here for the overall build model (the + shared CMake definition, the common editors payload, vcpkg, and caching). +* **[build/windows/](./build/windows/README.md)** — the Windows build (`build.ps1`, MSVC + CMake). +* **[build/linux/](./build/linux/README.md)** — the Linux build (Docker / `docker buildx bake`). + +## Get involved 🤝 + +Contributions are welcome! Whether it's a bug report, a feature idea, a +translation, or a pull request, here's how to take part: + +* **Found a bug or have an idea?** Open an [issue](https://github.com/Euro-Office/DesktopEditors/issues) + and describe what you ran into or what you'd like to see. +* **Want to contribute code?** Fork the relevant [component](#components-) repo, + make your change, and open a pull request. For build changes, see the + [build docs](./build/README.md) above. +* **Want to help translate?** Localization improvements are always appreciated — + see + the editors' interface translations. + +Please keep contributions compatible with the project's AGPL v3 license. + ## License 📄 Desktop Editors is licensed under the GNU Affero Public License, version 3.0, ensuring its transparency and commitment to the open-source community. - [1]: https://github.com/Euro-Office/DocumentServer + [1]: https://github.com/Euro-Office/DocumentServer \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..4fb2e5d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +9.3.1 \ No newline at end of file diff --git a/build/.docker/desktop-composer.bake.Dockerfile b/build/.docker/desktop-composer.bake.Dockerfile index 429a497..e49647e 100644 --- a/build/.docker/desktop-composer.bake.Dockerfile +++ b/build/.docker/desktop-composer.bake.Dockerfile @@ -4,53 +4,37 @@ # docker-bake.hcl file in this monorepo. # ============================================================================== -FROM desktop-builder AS desktop-composer +FROM scratch AS desktop-common + ARG BUILD_ROOT - COPY --from=sdkjs-desktop ${BUILD_ROOT} /desktopeditors/editors/ - COPY --from=web-apps ${BUILD_ROOT} /desktopeditors/editors/ + COPY --from=sdkjs-desktop ${BUILD_ROOT} /editors/ + COPY --from=web-apps ${BUILD_ROOT} /editors/ - COPY --from=desktop-js /app/loginpage/deploy/index.html /desktopeditors/index.html - COPY --from=desktop-js /app/loginpage/deploy/noconnect.html /desktopeditors/editors/webext/noconnect.html + COPY --from=desktop-js /app/loginpage/deploy/index.html /index.html + COPY --from=desktop-js /app/loginpage/deploy/noconnect.html /editors/webext/noconnect.html - COPY web-apps/apps/api/documents/index.html.desktop /desktopeditors/editors/web-apps/apps/api/documents/index.html + COPY web-apps/apps/api/documents/index.html.desktop /editors/web-apps/apps/api/documents/index.html - COPY desktop-apps/common/converter/* /desktopeditors/converter/ + COPY desktop-apps/common/converter/* /converter/ # Support only Nextcloud for now - COPY desktop-apps/common/loginpage/providers/nextcloud /desktopeditors/providers/nextcloud - COPY desktop-apps/common/templates /desktopeditors/converter/templates + COPY desktop-apps/common/loginpage/providers/nextcloud /providers/nextcloud + COPY desktop-apps/common/templates /converter/templates - COPY desktop-sdk/ChromiumBasedEditors/resources/ /desktopeditors/editors/sdkjs/common/Images/ - RUN mkdir /desktopeditors/editors/sdkjs-plugins + COPY desktop-sdk/ChromiumBasedEditors/resources/ /editors/sdkjs/common/Images/ - COPY build/configs/core/DoctRenderer.config.desktop /desktopeditors/converter/DoctRenderer.config + COPY build/configs/core/DoctRenderer.config.desktop /converter/DoctRenderer.config - COPY document-templates/new /desktopeditors/converter/empty + COPY document-templates/new /converter/empty - COPY dictionaries/ /desktopeditors/dictionaries + COPY dictionaries/ /dictionaries - COPY core-fonts/opensans /desktopeditors/fonts - COPY core-fonts/asana /desktopeditors/fonts/asana - COPY core-fonts/caladea /desktopeditors/fonts/caladea - COPY core-fonts/crosextra /desktopeditors/fonts/crosextra - COPY core-fonts/openoffice /desktopeditors/fonts/openoffice - COPY core-fonts/ASC.ttf /desktopeditors/fonts/ASC.ttf - - RUN /desktopeditors/converter/allfontsgen \ - --use-system=1 \ - --input=/desktopeditors/fonts \ - --input=/core-fonts \ - --allfonts=/desktopeditors/converter/AllFonts.js \ - --selection=/desktopeditors/converter/font_selection.bin - - RUN /desktopeditors/converter/allthemesgen \ - --converter-dir=/desktopeditors/converter \ - --src=/desktopeditors/editors/sdkjs/slide/themes \ - --allfonts=/desktopeditors/converter/AllFonts.js \ - --output=/desktopeditors/editors/sdkjs/common/Images - - RUN echo 'LD_LIBRARY_PATH=$PWD:$PWD/converter:$LD_LIBRARY_PATH LD_PRELOAD=libcef.so ./DesktopEditors' > /desktopeditors/start_desktop.sh && \ - chmod +x /desktopeditors/start_desktop.sh + COPY core-fonts/opensans /fonts + COPY core-fonts/asana /fonts/asana + COPY core-fonts/caladea /fonts/caladea + COPY core-fonts/crosextra /fonts/crosextra + COPY core-fonts/openoffice /fonts/openoffice + COPY core-fonts/ASC.ttf /fonts/ASC.ttf -FROM scratch AS desktop-export - COPY --from=desktop-composer /desktopeditors / \ No newline at end of file + # Create sdkjs-plugins dir in scratch image + WORKDIR /editors/sdkjs-plugins diff --git a/build/.docker/packages.bake.Dockerfile b/build/.docker/packages.bake.Dockerfile index f7b603b..d48d436 100644 --- a/build/.docker/packages.bake.Dockerfile +++ b/build/.docker/packages.bake.Dockerfile @@ -39,7 +39,7 @@ FROM ubuntu:24.04 AS package rm -rf /var/lib/apt/lists/* #Build files - COPY --from=desktop-export / ${OUT_DIR}/ + COPY --from=desktop-linux /desktopeditors ${OUT_DIR}/ # Upstream packaging repo COPY desktop-apps/package/ /desktop-editors-package/ diff --git a/build/README.md b/build/README.md index 970f99c..10def5c 100644 --- a/build/README.md +++ b/build/README.md @@ -1,25 +1,116 @@ -# Desktop Editors +# Building Euro-Office DesktopEditors -Docker image where we are experimenting with building the OnlyOffice Desktop Editors. +This directory holds everything needed to build **Euro-Office DesktopEditors** +(a fork of ONLYOFFICE DesktopEditors) from source, on Linux and Windows. -## Building the Image +If you just want to build, go straight to your platform: -First, clone the repositories for the core-fonts, sdkjs, web-apps, and server components: +- **[Linux](./linux/README.md)** — a Docker-based build (`docker buildx bake`) +- **[Windows](./windows/README.md)** — a PowerShell-driven MSVC build (`build.ps1`) -```sh -git clone --recurse-submodules https://github.com/Euro-Office/DesktopEditors.git -``` +The rest of this page explains the model both platforms share. Read it once and +the platform guides will make a lot more sense. + +## Prerequisites (all platforms) + +This is a *super-repository*: almost none of the source lives here directly. The +real code is in submodules — `core`, `core-fonts`, `desktop-apps`, `desktop-sdk`, +`dictionaries`, `document-templates`, `sdkjs`, `sdkjs-forms`, `web-apps`. You +**must** check them out before building: -If the repo was cloned without --recurse-submodules, initialize and download the submodules with: ```sh +git clone https://github.com/Euro-Office/DesktopEditors.git +cd DesktopEditors git submodule update --init --recursive ``` -Then, you can build the full image by running: +Every command in these guides assumes you are running from the **repository +root** (the directory that contains this `build/` folder), not from inside +`build/` itself. -```sh -cd DesktopEditors/build -docker buildx bake -``` +## The big picture: three jobs, two platforms, one definition + +A full release is produced by three jobs in the **"Build (Windows/Linux)"** CI +workflow: + +| Job | Runs on | Produces | +| --------------- | ---------------- | ------------------------------------------------------------------------------ | +| `build-common` | Linux / Docker | the JS + WASM **editors web payload** ("common"), published as `common-files` | +| `build-linux` | Linux / Docker | the desktop app + Linux packages | +| `build-windows` | Windows / MSVC | the desktop app + ZIP and Inno installer (optionally an MSI) | + +The single most important thing to understand: + +> **Both `build-linux` and `build-windows` consume the output of `build-common`.** + +`build-common` compiles the web editors (HTML/JS) and the core WASM once, on +Linux, because that part is platform-independent. The two desktop builds then +overlay that payload onto the native application they compile. So when you build +the desktop app, you don't rebuild the editors — you **supply** them. How you +supply them differs per platform and is covered in each guide. + +## Build *definition* vs. build *orchestration* + +The build is split into two layers, and keeping them straight is the key to +understanding why there are two very different-looking build systems here: + +- **The build definition** — *what* to compile and how to link it — lives in + **`desktop-apps/win-linux/CMakeLists.txt`**. This is a single, shared, + cross-platform CMake project. It is the same on Linux and Windows. + +- **The orchestration** — provision a toolchain, fetch dependencies, invoke + CMake, run post-build steps, package — is **per-platform**: + - Linux: a Dockerfile driven by `docker-bake.hcl` (see [linux/](./linux/README.md)) + - Windows: `windows/build.ps1` (see [windows/](./windows/README.md)) + +Both orchestrators do essentially the same sequence — configure CMake with the +vcpkg toolchain and a compiler cache, build, install, overlay the common payload, +generate fonts and theme thumbnails, package — just with platform-appropriate +tooling. + +### Why not just use Docker for both? + +The native Windows toolchain (MSVC, Qt, CEF, the v8 engine, the Windows SDK) +cannot be hermetically containerized on the standard hosted runners the way the +Linux toolchain can, and a Linux container obviously can't emit Windows binaries. +So the Linux build is a clean, reproducible Docker build, while the Windows build +is a script that provisions and runs against the host. The asymmetry is +deliberate, not an oversight. + +## Shared concepts + +These apply to both platforms; the platform guides won't repeat them. + +### Dependencies via vcpkg (manifest mode) + +Native third-party libraries are resolved by **vcpkg in manifest mode**. The +manifest and its version pins live in **`core/vcpkg.json`** (`VCPKG_MANIFEST_DIR` +points CMake at `core`). The exact dependency versions are pinned through the +manifest's `builtin-baseline`, so a fresh checkout of vcpkg still resolves the +same versions. If a bleeding-edge vcpkg HEAD ever misbehaves, check out the +baseline commit referenced in `core/vcpkg.json`. + +### Compiler caching + the Ninja generator + +Both builds use a compiler cache to avoid recompiling unchanged translation +units — **ccache** on Linux, **sccache** on Windows — wired in through +`CMAKE_C_COMPILER_LAUNCHER` / `CMAKE_CXX_COMPILER_LAUNCHER`. Because of that, +**both builds use the Ninja generator**: MSBuild ignores the compiler-launcher +variables, Ninja honours them. The cache is optional locally (the build just runs +slower without it) but strongly recommended in CI. + +### Versioning and branding + +These come from the workflow's top-level environment and can be overridden +locally: + +| Variable | Meaning | Default | +| --------------------------------- | ---------------------------------------- | ---------------------- | +| `PRODUCT_VERSION` | marketing version | `9.3.1` | +| `BUILD_NUMBER` | build identifier | `dev.1` | +| `COMPANY_NAME` / `PRODUCT_NAME` | shown in the app's About page | `Euro-Office` / `DesktopEditors` | + +## Where to go next -After it finishes the desktop editors will be in build/deploy/desktop \ No newline at end of file +- Building on **[Linux](./linux/README.md)** +- Building on **[Windows](./windows/README.md)** \ No newline at end of file diff --git a/build/docker-bake.hcl b/build/docker-bake.hcl index 82d6063..462b10a 100644 --- a/build/docker-bake.hcl +++ b/build/docker-bake.hcl @@ -1,7 +1,11 @@ # docker-bake.hcl +variable "GIT_COMMIT" { + default = "" +} + variable "REGISTRY" { - default = "euro-office" + default = "ghcr.io/euro-office" } variable "TAG" { @@ -58,7 +62,7 @@ variable "PRODUCT_NAME" { # ────────────────────────────────────────────── group "default" { - targets = ["desktop-export"] + targets = ["desktop-common"] } group "deps" { @@ -137,62 +141,26 @@ target "web-apps" { cache-to = ["type=local,dest=/tmp/${REGISTRY}/web-apps,mode=max"] } -# ────────────────────────────────────────────── -# BUILD TARGET -# ────────────────────────────────────────────── - -target "desktop-builder" { - inherits = ["_common"] - context = ".." - dockerfile = "./desktop-apps/.docker/desktop-apps.bake.Dockerfile" - target = "desktop-builder" - tags = ["${REGISTRY}/desktop-builder:${TAG}"] - contexts = { - core-base = "target:core-base" - desktop-js = "target:desktop-js" - sdkjs-desktop = "target:sdkjs-desktop" - web-apps = "target:web-apps" - } - cache-from = ["type=local,src=/tmp/${REGISTRY}/desktop-builder"] - cache-to = ["type=local,dest=/tmp/${REGISTRY}/desktop-builder,mode=max"] -} - # ────────────────────────────────────────────── # EXPORT TARGET # ────────────────────────────────────────────── -target "desktop-export" { + +### Compose files that are common to all operating systems +target "desktop-common" { inherits = ["_common"] context = ".." dockerfile = "./build/.docker/desktop-composer.bake.Dockerfile" - target = "desktop-export" # points to the FROM scratch stage - tags = ["${REGISTRY}/desktop-export:${TAG}"] + target = "desktop-common" # points to the FROM scratch stage + tags = ["${REGISTRY}/desktop-common:${GIT_COMMIT}"] contexts = { - core-base = "target:core-base" # ← needed because Dockerfile references them - desktop-js = "target:desktop-js" # even in stages before desktop-export + desktop-js = "target:desktop-js" # even in stages before desktop-common sdkjs-desktop = "target:sdkjs-desktop" web-apps = "target:web-apps" - desktop-builder = "target:desktop-builder" - } - - # Export the filesystem directly to a local directory instead of an image - output = ["type=local,dest=./deploy/desktop"] - - cache-from = ["type=local,src=/tmp/${REGISTRY}/desktop-builder"] # reuses builder cache -} - -target "packages" { - inherits = ["_common"] - context = ".." - dockerfile = "./build/.docker/packages.bake.Dockerfile" - target = "packages" # points to the FROM scratch stage - tags = ["${REGISTRY}/packages:${TAG}"] - contexts = { - desktop-export = "target:desktop-export" } # Export the filesystem directly to a local directory instead of an image - output = ["type=local,dest=./deploy/packages"] + output = ["type=docker"] - cache-from = ["type=local,src=/tmp/${REGISTRY}/packages"] # reuses builder cache + cache-from = ["type=local,src=/tmp/${REGISTRY}/desktop-common"] # reuses builder cache } \ No newline at end of file diff --git a/build/linux/README.md b/build/linux/README.md new file mode 100644 index 0000000..09112dc --- /dev/null +++ b/build/linux/README.md @@ -0,0 +1,96 @@ +# Building on Linux + +The Linux build runs entirely in Docker via `docker buildx bake`. It compiles the +desktop application, overlays the common editors payload, generates fonts and +theme thumbnails, and produces the installable packages — all inside containers, +so it needs almost nothing installed on the host. + +> New here? Read the **[build overview](../README.md)** first — it explains the +> three CI jobs, the common payload, vcpkg, and caching, none of which are +> repeated below. + +## Prerequisites + +- **Docker** with the **Buildx** plugin (Docker Desktop, or `docker buildx` + available on a Docker Engine install). +- The submodules checked out (see the [overview](../README.md#prerequisites-all-platforms)). + +That's it — the C++ toolchain, Qt, the WASM toolchain, and all system `-dev` +packages are installed inside the images, not on your machine. + +## Quick start + +Run from the **repository root**. The bake graph is defined in +`build/docker-bake.hcl`; build a target with: + +```sh +cd build +docker buildx bake -f ./docker-bake.hcl +``` +or +```sh +cd build/linux +docker buildx bake -f ./docker-bake.hcl +``` + +The two targets you'll care about: + +- **`desktop-common`** — the shared web/WASM editors payload. This is the same + artifact the `build-common` CI job produces, and it's what the Windows build + consumes. Build it on its own when you only need the common content: + + ```sh + cd build + docker buildx bake -f ./docker-bake.hcl desktop-common + ``` + +- **`desktop-linux`** — the full desktop application build (depends on + `desktop-common`), which compiles the native app and runs the font/theme + post-steps. + +Check `docker-bake.hcl` for the authoritative list of targets, their outputs, +and any overrides the workflow uses (version, branding, output type). + +You can also use the build.sh script in this directory to build **`desktop-common`** and **`desktop-linux`** at once: + +```sh +cd build/linux +./build.sh +``` + +## How the desktop image is built + +The desktop stage (in the bake Dockerfile) follows the standard sequence: + +1. Install the build/system dependencies into the image. +2. Copy in the relevant submodules (`desktop-sdk`, `desktop-apps`, `core-fonts`) + plus branding overlays. +3. Configure CMake with the vcpkg toolchain, Ninja, and `ccache`, then + `cmake --build` / `cmake --install`. +4. Overlay the `desktop-common` payload onto the installed tree + (`COPY --from=desktop-common`). +5. Run `allfontsgen` (fonts) and `allthemesgen` (slide-theme thumbnails), then + remove the generator binaries so they don't ship. + +## Caching + +Compilation is cached with **ccache** via a BuildKit cache mount. If you're +working on the build itself, a few things are worth knowing: + +- The ccache mount uses a **stable id** (`id=ccache`), so it persists and is + reused across builds. +- Some other cache mounts (the build and NuGet caches) currently fold a + `CACHE_BUST` arg into their mount **id**. Changing that arg starts a fresh + cache rather than reusing the old one — so don't expect those two to persist + across a bust. ccache is unaffected. +- The compile step builds inside a cache mount and copies the result out. That + buys fast incremental rebuilds at the cost of strict layer reproducibility — + fine for development and CI, worth knowing if you need a bit-reproducible + release. + +## Output + +The build produces the installed application tree and the Linux packages inside +the image / bake output, default is `build/linux/deploy`. Use the bake `output` setting (e.g. +`type=docker`, `type=local,dest=...`) to control where artifacts land; see +`docker-bake.hcl`. \ No newline at end of file diff --git a/build/linux/build.sh b/build/linux/build.sh new file mode 100755 index 0000000..4335139 --- /dev/null +++ b/build/linux/build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +NEXTCLOUD_USER="" +NEXTCLOUD_PASS="" +REGISTRY="ghcr.io/euro-office" +TAG="latest" +PRODUCT_VERSION=$(cat ../../VERSION) +BUILD_NUMBER="dev.0" +BRANDING_DIR="../" +COMPANY_NAME="Euro-Office" +PRODUCT_NAME="Desktop Editors" +GIT_COMMIT=$(git rev-parse --short HEAD) + +export NEXTCLOUD_USER NEXTCLOUD_PASS REGISTRY TAG PRODUCT_VERSION \ + BUILD_NUMBER BRANDING_DIR COMPANY_NAME PRODUCT_NAME GIT_COMMIT + +(cd .. && docker buildx bake desktop-common) +docker buildx bake packages \ No newline at end of file diff --git a/build/linux/docker-bake.hcl b/build/linux/docker-bake.hcl new file mode 100644 index 0000000..d123017 --- /dev/null +++ b/build/linux/docker-bake.hcl @@ -0,0 +1,157 @@ +# docker-bake.hcl + +variable "GIT_COMMIT" { + default = "" +} + +variable "REGISTRY" { + default = "ghcr.io/euro-office" +} + +variable "TAG" { + default = "latest" +} + +variable "PRODUCT_VERSION" { + default = "9.3.1" +} + +variable "BUILD_NUMBER" { + default = "dev.0" +} + + +variable "BUILD_ROOT" { + default = "/package" +} + +variable "NUGET_CACHE" { + default = "local" + validation { + condition = contains(["local", "remote"], NUGET_CACHE) + error_message = "NUGET_CACHE must be 'local' or 'remote'." + } +} + +variable "NUGET_SOURCE_PATH" { + default = "/nuget-cache" +} + +variable "CACHE_BUST" { + default = "1" +} + +variable "BRANDING_DIR" { + default = "." +} + +variable "COMPANY_NAME" { + default = "Euro-Office" +} + +variable "COMPANY_NAME_LOW" { + default = regex_replace(lower(COMPANY_NAME), "\\s+", "-") +} + +variable "PRODUCT_NAME" { + default = "Desktop Editors" +} + +# ────────────────────────────────────────────── +# BUILD GROUPS +# ────────────────────────────────────────────── + +group "default" { + targets = ["packages"] +} + + +# ────────────────────────────────────────────── +# SHARED ARGS (inherited by all targets) +# ────────────────────────────────────────────── + +target "_common" { + args = { + PRODUCT_VERSION = "${PRODUCT_VERSION}" + BUILD_NUMBER = "${BUILD_NUMBER}" + BUILD_ROOT = "${BUILD_ROOT}" + NUGET_CACHE = "${NUGET_CACHE}" + CACHE_BUST = "${CACHE_BUST}" + BRANDING_DIR = "${BRANDING_DIR}" + PRODUCT_NAME = "${PRODUCT_NAME}" + COMPANY_NAME = "${COMPANY_NAME}" + COMPANY_NAME_LOW = "${COMPANY_NAME_LOW}" + } +} + +# ────────────────────────────────────────────── +# DEPENDENCY TARGETS +# ────────────────────────────────────────────── + +target "third-party" { + inherits = ["_common"] + context = "../.." + dockerfile = "./core/.docker/third-party.bake.Dockerfile" + target = "third-party-builder" + secret = [ + "id=nextcloud_user,env=NEXTCLOUD_USER", + "id=nextcloud_pass,env=NEXTCLOUD_PASS", + ] + tags = ["${REGISTRY}/third-party:${TAG}"] + cache-from = ["type=local,src=/tmp/${REGISTRY}/third-party"] + cache-to = ["type=local,dest=/tmp/${REGISTRY}/third-party,mode=max"] +} + + +target "core-base" { + inherits = ["_common"] + context = "../.." + dockerfile = "./core/.docker/core.bake.Dockerfile" + target = "core-base" + tags = ["${REGISTRY}/core-base:${TAG}"] + cache-from = ["type=local,src=/tmp/${REGISTRY}/core-base"] + cache-to = ["type=local,dest=/tmp/${REGISTRY}/core-base,mode=max"] +} + +# ────────────────────────────────────────────── +# BUILD TARGET +# ────────────────────────────────────────────── + +target "desktop-linux" { + inherits = ["_common"] + context = "../.." + dockerfile = "./desktop-apps/.docker/desktop-apps.bake.Dockerfile" + target = "desktop-linux" + tags = ["${REGISTRY}/desktop-linux:${TAG}"] + contexts = { + desktop-common = "docker-image://${REGISTRY}/desktop-common:${GIT_COMMIT}" + core-base = "target:core-base" + third-party = "target:third-party" + } + secret = [ + "id=nextcloud_user,env=NEXTCLOUD_USER", + "id=nextcloud_pass,env=NEXTCLOUD_PASS", + ] + cache-from = ["type=local,src=/tmp/${REGISTRY}/desktop-linux"] + cache-to = ["type=local,dest=/tmp/${REGISTRY}/desktop-linux,mode=max"] +} + +# ────────────────────────────────────────────── +# EXPORT TARGET +# ────────────────────────────────────────────── + +target "packages" { + inherits = ["_common"] + context = "../.." + dockerfile = "./build/.docker/packages.bake.Dockerfile" + target = "packages" # points to the FROM scratch stage + tags = ["${REGISTRY}/packages:${TAG}"] + contexts = { + desktop-linux = "target:desktop-linux" + } + + # Export the filesystem directly to a local directory instead of an image + output = ["type=local,dest=./deploy/packages"] + + cache-from = ["type=local,src=/tmp/${REGISTRY}/packages"] # reuses builder cache +} \ No newline at end of file diff --git a/build/windows/README.md b/build/windows/README.md new file mode 100644 index 0000000..e94baa1 --- /dev/null +++ b/build/windows/README.md @@ -0,0 +1,115 @@ +# Building on Windows + +The Windows build is driven by **`build.ps1`**, a PowerShell script that sets up the toolchain environment, +configures and builds the native app with MSVC + CMake, overlays the common +editors payload, generates fonts and theme thumbnails, and packages the result +as a ZIP and an Inno Setup installer (optionally an MSI). + +> New here? Read the **[build overview](../README.md)** first — it explains the +> three CI jobs, the common payload, vcpkg, and caching, none of which are +> repeated below. + +## Prerequisites + +- **Windows** with **Visual Studio 2022** including the C++ x64 toolset. The + build additionally needs the **MSVC v141 toolset**, the **Windows 10 SDK + (10.0.19041.0)**, and **ATL + MFC**. If you don't have those components, + `build.ps1 -InstallDeps` can add them (see below). +- **Cygwin** (parts of the native build shell out to its `bash`/`make`/`sh`). +- **CMake**, **Ninja**, and native (non-Cygwin) **perl**, **python**, and **git** + on `PATH`. +- For packaging: **Inno Setup 6.2.2** and **7-Zip**; for the optional MSI, + **Advanced Installer**. +- *(Optional but recommended)* **sccache** on `PATH` for a compiler cache. +- The submodules checked out (see the [overview](../README.md#prerequisites-all-platforms)). + +`-InstallDeps` can install most of this for you (Cygwin, the VS components, and +the packaging tools via Chocolatey). It mutates the host and writes into +`Program Files`, so run it from an **elevated** PowerShell. You only need it +once per machine — omit it on subsequent builds. + +## Getting the common payload first + +`build.ps1` compiles the *native* app but does **not** build the editors web +content — that's the [common payload](../README.md#the-common-payload-again) +from the Linux `build-common` job. Supply it one of three ways: + +1. **Download the `common-files` CI artifact**, and point the script at + it with `-CommonDir `. +2. **Build it locally with Docker** (Linux containers) by passing `-BuildCommon`. + Slow; only worth it if you can't grab the artifact. +3. **Place it at `.\common`** (the default location) and pass nothing. + +The expected layout is: + +``` +\index.html +\editors\webext\noconnect.html +\editors\... (the full editors payload) +``` + +## Quick start + +Run from the **repository root** (the script derives the root from its own +location, so it works regardless of your current directory): + +```powershell +# Common content already at .\common, tools already installed: +.\build\windows\build.ps1 + +# Point at a downloaded CI artifact: +.\build\windows\build.ps1 -CommonDir C:\downloads\common-files + +# First-time machine: install everything (run elevated) and build common via Docker: +.\build\windows\build.ps1 -InstallDeps -BuildCommon +``` + +## Parameters + +| Parameter | Purpose | Default | +| ----------------- | ----------------------------------------------------------------------- | ---------------------- | +| `-RepoRoot` | Repository root | two levels up from the script | +| `-CommonDir` | Folder holding the Linux-built common payload | `\common` | +| `-BuildCommon` | Build the common payload locally with Docker | off | +| `-InstallDeps` | Install Cygwin, VS components, and packaging tools (admin; one-time) | off | +| `-BuildMsi` | Also build the MSI with Advanced Installer (needs a license) | off | +| `-SkipPackaging` | Build and install only; skip ZIP / installer steps | off | +| `-ProductVersion`, `-BuildNumber`, `-CompanyName`, `-ProductName` | Version / branding | see [overview](../README.md#versioning-and-branding) | + +## What the script does + +In order: validate the repo layout → *(optional)* install dependencies → +resolve the common payload → set up a deterministic `PATH` (native tools ahead of +Cygwin) and import the MSVC environment → set up vcpkg → CMake configure (Ninja, +Release, vcpkg toolchain, sccache if present) → build → install → overlay the +common payload → generate fonts (`allfontsgen`) and theme thumbnails +(`allthemesgen`) → package (ZIP, Inno installer, optional MSI). + +## Output + +With packaging enabled (the default), artifacts land under +`desktop-apps\package\`: + +- `...\zip\*.zip` — portable ZIP +- `...\inno\*.exe` — Inno Setup installer +- `...\advinst\*.msi` — MSI (only with `-BuildMsi`) + +Use `-SkipPackaging` to stop after the install step; the unpackaged app tree is +then at `\desktopeditors`. + +## Good to know + +- **sccache is optional.** Without it on `PATH` the script just builds without a + compiler cache (and says so). With it, object files are cached by content hash; + embedded debug info (`/Z7`) is required for caching to work and the script sets + it for you. +- **The packaging step pulls a couple of inputs at build time.** It stages the + VC++ redistributable and fetches Inno Setup's "unofficial" language files (which + no stock Inno install ships) into the Inno `Languages` folder. Both run on every + packaging build — including CI, which does *not* pass `-InstallDeps`. Writing + the language files into the Inno install dir needs write access there, so a + plain local packaging run may need elevation. If that's a hassle, consider + vendoring those files in the fork. +- **The build mutates the host** when `-InstallDeps` is used (VS components, global + tools). That's fine on a throwaway CI runner; on your own machine, know that it + changes your Visual Studio installation and `Program Files`. \ No newline at end of file diff --git a/build/windows/build.ps1 b/build/windows/build.ps1 new file mode 100644 index 0000000..dd7baa5 --- /dev/null +++ b/build/windows/build.ps1 @@ -0,0 +1,596 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Local Windows build for Euro-Office DesktopEditors. + + This mirrors the "build-windows" job of the "Build (Windows/Linux)" GitHub + Actions workflow, step for step, so a developer can reproduce a CI build on + their own machine. + +.DESCRIPTION + The CI pipeline has two jobs: + + build-common (Linux / Docker / WASM) -> produces the editors web payload + build-windows (Windows / MSVC / CMake) -> compiles the desktop app & packages + + Only the second job can run natively on Windows. The first job builds the + JS/WASM "common" editors content inside a Linux container, so locally you + must SUPPLY that content. Three ways to get it: + + 1. Download the "common-files" artifact from a CI run and unzip it, then + pass its folder via -CommonDir. + 2. Build it yourself with Docker Desktop (Linux containers) via -BuildCommon. + 3. Place it at .\common (the default location) and run with no extra flags. + + The expected layout of the common folder is: + \index.html + \editors\webext\noconnect.html + \editors\... (the full editors payload) + +.PARAMETER RepoRoot + Root of the checked-out fork (with submodules). This script lives at + \build\windows\, so the default is two levels up from the script - + meaning it works no matter which directory you launch it from. + +.PARAMETER CommonDir + Folder holding the Linux-built "common" editors content. Defaults to + "\common". + +.PARAMETER BuildCommon + Build the common content locally with Docker (requires Docker Desktop in + Linux-container mode). Slow; only needed if you can't grab the CI artifact. + +.PARAMETER InstallDeps + Install build/packaging dependencies (Cygwin, MSVC v141 + Win10 SDK + ATL/MFC, + Inno Setup, 7-Zip, and optionally Advanced Installer). Requires admin and, + for the packaging tools, Chocolatey. Omit if you already have everything. + (Inno's unofficial language files are staged at packaging time, not here, so + they're present on CI too - which doesn't pass this switch.) + +.PARAMETER BuildMsi + Also build the MSI with Advanced Installer. Off by default to match the + workflow, where the MSI step is currently commented out. Requires a license. + +.EXAMPLE + # Common content already at .\common, all tools installed: + .\build-windows.ps1 + +.EXAMPLE + # First-time machine: install everything, build common via Docker: + .\build-windows.ps1 -InstallDeps -BuildCommon + +.EXAMPLE + # Point at a downloaded CI artifact: + .\build-windows.ps1 -CommonDir C:\downloads\common-files +#> +[CmdletBinding()] +param( + [string]$RepoRoot = '', + [string]$CommonDir = '', # default: \common + [switch]$BuildCommon, + + # Values that the workflow takes from its top-level env block. + [string]$ProductVersion = '9.3.1', + [string]$BuildNumber = 'dev.1', + [string]$Arch = 'x64', + [string]$Target = 'standalone', + [string]$CompanyName = 'Euro-Office', + [string]$ProductName = 'DesktopEditors', + [string]$WinSdkVersion = '10.0.19041.0', + + # Tool locations / install knobs. + [string]$VcpkgRoot = $env:VCPKG_ROOT, + [string]$CygwinRoot = 'C:\cygwin64', + [string]$InnoRoot = "${env:ProgramFiles(x86)}\Inno Setup 6", + [string]$SevenZipRoot = 'C:\Program Files\7-Zip', + [string]$AdvInstLicense = '', + + [switch]$InstallDeps, + [switch]$BuildMsi, + [switch]$SkipPackaging +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' # speeds up Invoke-WebRequest + +# Match the workflow env. +$env:PYTHONUTF8 = '1' + +# This script lives at \build\windows\, but the build must run from the +# repo root (where the workflow operates). Derive the root from the script's +# own location so it works regardless of the current directory; -RepoRoot +# still overrides. +if (-not $RepoRoot) { + if ($PSScriptRoot) { + $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path + } else { + $RepoRoot = (Get-Location).Path + } +} + +if (-not $CommonDir) { $CommonDir = Join-Path $RepoRoot 'common' } +$VersionFull = "$ProductVersion.0" # make.ps1 wants a 4-part System.Version +$InstallDir = Join-Path $RepoRoot 'desktopeditors' +$PackageDir = Join-Path $RepoRoot 'desktop-apps\package' + +# ───────────────────────────── helpers ────────────────────────────────────── +# When this runs inside GitHub Actions, emit ::group::/::endgroup:: so each +# phase is a collapsible, individually-timed section in the Actions log - +# recovering the per-step UI you'd otherwise lose by calling one script. +# Locally it prints a plain banner instead. +$script:InActions = ($env:GITHUB_ACTIONS -eq 'true') +$script:GroupOpen = $false + +function Write-Step([string]$Msg) { + if ($script:InActions) { + if ($script:GroupOpen) { Write-Host '::endgroup::' } + Write-Host "::group::$Msg" + $script:GroupOpen = $true + } else { + Write-Host '' + Write-Host ('=' * 78) -ForegroundColor Cyan + Write-Host " $Msg" -ForegroundColor Cyan + Write-Host ('=' * 78) -ForegroundColor Cyan + } +} + +function Close-StepGroup { + if ($script:InActions -and $script:GroupOpen) { + Write-Host '::endgroup::' + $script:GroupOpen = $false + } +} + +function Assert-LastExit([string]$What) { + if ($LASTEXITCODE -ne 0) { throw "$What failed (exit $LASTEXITCODE)." } +} + +function Get-VsInstallPath { + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { throw "vswhere.exe not found - is Visual Studio 2022 installed?" } + $p = & $vswhere -latest -products * ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath + if (-not $p) { throw "No Visual Studio install with the C++ x64 toolset was found." } + return $p +} + +# Locate the Inno Setup program directory (the folder with iscc.exe and its +# Languages\ subfolder). Prefer a real install (it carries the compiler support +# files) over a Chocolatey shim, then any iscc.exe on PATH, then -InnoRoot. +# Returns $null if none found. Used by both the dependency install (to stage +# language files) and the packaging step (to set INNOPATH). +function Get-InnoRoot([string]$Fallback) { + $isccItem = Get-ChildItem 'C:\Program Files (x86)\Inno Setup*','C:\Program Files\Inno Setup*' ` + -Recurse -Filter iscc.exe -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($isccItem) { return Split-Path $isccItem.FullName } + $iscc = Get-Command iscc.exe -ErrorAction SilentlyContinue + if ($iscc) { return Split-Path $iscc.Source } + if ($Fallback -and (Test-Path (Join-Path $Fallback 'iscc.exe'))) { return $Fallback } + return $null +} + +# Stage jrsoftware's "unofficial" Inno translations (Greek, etc.) that +# common.iss references but that ship in NO stock Inno install - they live in a +# separate translations collection. This is a PACKAGING INPUT (like the +# vc_redist pre-stage), not a heavy tool install, so it must run on every +# packaging build regardless of -InstallDeps - which is why it's called from the +# packaging step, not gated behind -InstallDeps (CI doesn't pass that). It's +# idempotent (skips files already present). It writes into the Inno install's +# Languages dir, so it needs write access there: fine on CI (admin); a plain +# local packaging run may need elevation. +function Sync-InnoLanguages([string]$LanguagesDir) { + if (-not (Test-Path $LanguagesDir)) { + Write-Warning "Inno Languages dir not found ($LanguagesDir) - skipping unofficial language staging." + return + } + # Pin $issTag to the tag matching your Inno version to avoid message-version + # mismatches (e.g. 'is-6_7_1'); 'main' = latest. + $issTag = 'main' + $apiUrl = "https://api.github.com/repos/jrsoftware/issrc/contents/Files/Languages/Unofficial?ref=$issTag" + $headers = @{ 'User-Agent' = 'eo-build' } + # Authenticate the API call when a token is available (CI) so the single + # contents listing doesn't trip the 60/hr anonymous limit on shared runner IPs. + if ($env:GITHUB_TOKEN) { $headers['Authorization'] = "Bearer $env:GITHUB_TOKEN" } + $unofficial = Invoke-RestMethod -Uri $apiUrl -Headers $headers + foreach ($f in ($unofficial | Where-Object { $_.name -match '\.islu?$' })) { + $langDest = Join-Path $LanguagesDir $f.name + if (-not (Test-Path $langDest)) { + Invoke-WebRequest -Uri $f.download_url -OutFile $langDest + Write-Host "Staged unofficial language: $($f.name)" + } + } +} + +# Run vcvars in a child cmd and import the resulting environment into THIS +# PowerShell process. vcvars only prepends MSVC/SDK dirs, so it preserves the +# deterministic PATH ordering we set up below (native tools > Cygwin > rest). +function Import-VcVars([string]$Arch, [string]$SdkVersion) { + $batName = if ($Arch -eq 'x86') { 'vcvars32.bat' } else { 'vcvars64.bat' } + $vcvars = Join-Path (Get-VsInstallPath) "VC\Auxiliary\Build\$batName" + if (-not (Test-Path $vcvars)) { throw "vcvars not found at $vcvars" } + + $capture = & cmd /c "`"$vcvars`" $SdkVersion >NUL 2>&1 && set" + foreach ($line in $capture) { + $i = $line.IndexOf('=') + if ($i -gt 0) { + $name = $line.Substring(0, $i) + $value = $line.Substring($i + 1) + [Environment]::SetEnvironmentVariable($name, $value, 'Process') + } + } + if (-not $env:VCINSTALLDIR) { throw "vcvars import failed (VCINSTALLDIR empty)." } + Write-Host "Imported MSVC environment from $batName ($SdkVersion)." +} + +# ───────────────────────── 0. sanity checks ───────────────────────────────── +Write-Step "0. Validating repository layout" +Push-Location $RepoRoot +try { + foreach ($p in @('desktop-apps\win-linux\CMakeLists.txt', 'core\vcpkg.json', 'build\docker-bake.hcl')) { + if (-not (Test-Path (Join-Path $RepoRoot $p))) { + throw "Expected '$p' under RepoRoot. Run from the repo root and make sure submodules are checked out (git submodule update --init --recursive)." + } + } + Write-Host "RepoRoot : $RepoRoot" + Write-Host "CommonDir: $CommonDir" + + # ──────────────────── 1. install dependencies (optional) ──────────────── + if ($InstallDeps) { + Write-Step "1. Installing dependencies" + + # 1a. Cygwin -> C:\cygwin64 (NOT added to PATH; we order PATH ourselves). + if (Test-Path (Join-Path $CygwinRoot 'bin\bash.exe')) { + Write-Host "Cygwin already present at $CygwinRoot - skipping." + } else { + Write-Host "Installing Cygwin to $CygwinRoot ..." + $setup = Join-Path $env:TEMP 'cygwin-setup-x86_64.exe' + Invoke-WebRequest 'https://www.cygwin.com/setup-x86_64.exe' -OutFile $setup + $pkgs = 'automake,cmake,make,git,python3,python3-devel' + $args = @('-q','-n','-N','-d','-B', + '-R', $CygwinRoot, + '-s','https://mirrors.kernel.org/sourceware/cygwin/', + '-l', (Join-Path $env:TEMP 'cygwin-pkgs'), + '-P', $pkgs) + Start-Process -FilePath $setup -ArgumentList $args -Wait -NoNewWindow + } + + # 1b. Windows 10 SDK + MSVC v141 toolset + ATL + MFC (x86 & x64). + Write-Host "Adding Win10 SDK 19041 + VC v141 + ATL + MFC ..." + $vsInstaller = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vs_installer.exe" + $installPath = Get-VsInstallPath + & $vsInstaller modify ` + --installPath $installPath ` + --add Microsoft.VisualStudio.Component.Windows10SDK.19041 ` + --add Microsoft.VisualStudio.Component.VC.v141.x86.x64 ` + --add Microsoft.VisualStudio.Component.VC.v141.ATL ` + --add Microsoft.VisualStudio.Component.VC.v141.MFC ` + --quiet --norestart --force + if ($LASTEXITCODE -notin @(0, 3010)) { + Write-Warning "vs_installer returned $LASTEXITCODE - components may already be installed, continuing." + } + + # 1c. Packaging tools via Chocolatey. + if (Get-Command choco -ErrorAction SilentlyContinue) { + choco install innosetup --version=6.2.2 -y --no-progress + choco install 7zip -y --no-progress + if ($BuildMsi) { + choco install advanced-installer -y --no-progress + if ($AdvInstLicense) { + $ai = "${env:ProgramFiles(x86)}\Caphyon\Advanced Installer*\bin\x86\AdvancedInstaller.com" + $aiExe = (Get-Item $ai | Select-Object -First 1).FullName + & $aiExe /RegisterCI $AdvInstLicense + } + } + } else { + Write-Warning "Chocolatey not found - skipping Inno Setup / 7-Zip / Advanced Installer install. Install them manually or install choco first." + } + } + + # ───────────────── 2. obtain the Linux-built common content ────────────── + Write-Step "2. Resolving 'common' editors content" + if ($BuildCommon) { + Write-Host "Building common content with Docker (this is slow) ..." + if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + throw "-BuildCommon requires Docker Desktop on PATH (Linux containers)." + } + # The bake graph reads these (euro-office brand needs no Nextcloud creds). + $env:PRODUCT_VERSION = $ProductVersion + $env:BUILD_NUMBER = $BuildNumber + $env:BUILD_ROOT = '/package' + $env:NUGET_CACHE = 'local' + + Push-Location (Join-Path $RepoRoot 'build') + try { + docker buildx bake -f ./docker-bake.hcl desktop-common ` + --set "desktop-common.tags=desktop-common:local" ` + --set "desktop-common.output=type=docker" + Assert-LastExit "docker bake" + } finally { Pop-Location } + + if (Test-Path $CommonDir) { Remove-Item -Recurse -Force $CommonDir } + docker create --name eo_common_tmp desktop-common:local true | Out-Null + docker cp eo_common_tmp:/ $CommonDir + docker rm eo_common_tmp | Out-Null + } + + if (-not (Test-Path (Join-Path $CommonDir 'index.html')) -or + -not (Test-Path (Join-Path $CommonDir 'editors'))) { + throw @" +Common content not found at: $CommonDir +Expected '$CommonDir\index.html' and '$CommonDir\editors\'. +Either download the 'common-files' CI artifact and pass -CommonDir, or rerun with -BuildCommon. +"@ + } + Write-Host "Common content OK." + + # ─────────── 3. copy login-page assets into place (workflow step) ──────── + Write-Step "3. Copying common files into the loginpage deploy folder" + $dest = Join-Path $RepoRoot 'desktop-apps\common\loginpage\deploy' + New-Item -ItemType Directory -Force -Path $dest | Out-Null + Copy-Item (Join-Path $CommonDir 'index.html') (Join-Path $dest 'index.html') -Force + Copy-Item (Join-Path $CommonDir 'editors\webext\noconnect.html') (Join-Path $dest 'noconnect.html') -Force + + # ─────────── 4. deterministic PATH (native tools > Cygwin > rest) ──────── + # + # Why: parts of the native build (e.g. ICU pulled in through vcpkg) shell + # out to Cygwin's bash/make/sh, but the build BREAKS if perl/python/git/ + # cmake resolve to Cygwin copies. So we front-load the Windows-native dirs + # for those four, then Cygwin's bin (so bash/sh/make are Cygwin's, not Git's + # MSYS ones), then the rest of PATH. + Write-Step "4. Setting up PATH ordering + CYGWIN_ROOT" + $nativeDirs = @() + foreach ($tool in 'perl','python','git','cmake') { + $cmd = Get-Command $tool -ErrorAction SilentlyContinue + if ($cmd -and ($cmd.Source -notlike "$CygwinRoot\*")) { + $dir = Split-Path $cmd.Source + # Git\bin also ships bash.exe/sh.exe which would shadow Cygwin's; + # the sibling Git\cmd has only the git launcher, so prefer it. + if ($tool -eq 'git' -and $dir -like '*\Git\bin') { + $cmdDir = Join-Path (Split-Path $dir) 'cmd' + if (Test-Path (Join-Path $cmdDir 'git.exe')) { $dir = $cmdDir } + } + if ($nativeDirs -notcontains $dir) { $nativeDirs += $dir } + Write-Host ("native {0,-8} -> {1} (PATH dir: {2})" -f $tool, $cmd.Source, $dir) + } + } + $env:PATH = ($nativeDirs + "$CygwinRoot\bin" + $env:PATH) -join ';' + $env:CYGWIN_ROOT = $CygwinRoot + + # ─────────────────────── 5. verify tool resolution ────────────────────── + Write-Step "5. Verifying tool versions" + foreach ($tool in 'perl','python','git','cmake') { + $cmd = Get-Command $tool -ErrorAction SilentlyContinue + if ($cmd) { + Write-Host ("{0,-8} -> {1}" -f $tool, $cmd.Source) + if ($cmd.Source -like "$CygwinRoot\*") { throw "ERROR: '$tool' resolves to the Cygwin copy at $($cmd.Source)." } + } else { + throw "ERROR: '$tool' not found in PATH." + } + } + foreach ($tool in 'bash','sh','make') { + $cmd = Get-Command $tool -ErrorAction SilentlyContinue + if ($cmd) { + Write-Host ("{0,-8} -> {1}" -f $tool, $cmd.Source) + if ($cmd.Source -notlike "$CygwinRoot\*") { throw "ERROR: '$tool' resolves to a non-Cygwin copy at $($cmd.Source)." } + } else { + throw "ERROR: '$tool' not found in PATH (need Cygwin's)." + } + } + $perlOut = (perl --version) -join '' + if ($perlOut -match 'cygwin') { throw "ERROR: 'perl' is Cygwin Perl." } + + # ───────────────────────────── 6. vcpkg ───────────────────────────────── + Write-Step "6. Setting up vcpkg" + if ($VcpkgRoot -and (Test-Path (Join-Path $VcpkgRoot 'vcpkg.exe'))) { + $env:VCPKG_ROOT = $VcpkgRoot + } else { + $VcpkgRoot = Join-Path $RepoRoot '.vcpkg' + if (-not (Test-Path (Join-Path $VcpkgRoot 'vcpkg.exe'))) { + if (-not (Test-Path $VcpkgRoot)) { + git clone https://github.com/microsoft/vcpkg.git $VcpkgRoot + Assert-LastExit "git clone vcpkg" + } + & (Join-Path $VcpkgRoot 'bootstrap-vcpkg.bat') -disableMetrics + Assert-LastExit "bootstrap-vcpkg" + } + $env:VCPKG_ROOT = $VcpkgRoot + } + Write-Host "VCPKG_ROOT = $env:VCPKG_ROOT" + Write-Host "NOTE: manifest mode pins versions via core\vcpkg.json's builtin-baseline. If a fresh vcpkg HEAD misbehaves, check out the baseline commit referenced there." + + # ─────────── load MSVC env (vcvars) on top of our ordered PATH ─────────── + Write-Step "Loading MSVC environment (vcvars)" + Import-VcVars -Arch $Arch -SdkVersion $WinSdkVersion + + # ───────────────────────── 7. CMake Configure ─────────────────────────── + # Generator is Ninja (NOT the VS/MSBuild generator) on purpose: MSBuild + # ignores CMAKE__COMPILER_LAUNCHER, Ninja honors it - that launcher + # is how the compiler cache attaches. cl.exe is already on PATH from the + # vcvars import above; the target arch follows vcvars (x64 via vcvars64). + Write-Step "7. CMake Configure" + $cmakeArgs = @( + '-G', 'Ninja', + '-DCMAKE_BUILD_TYPE=Release', + "-DCMAKE_TOOLCHAIN_FILE=$($env:VCPKG_ROOT)\scripts\buildsystems\vcpkg.cmake", + '-DVCPKG_MANIFEST_MODE=ON', + '-DVCPKG_MANIFEST_DIR=core', + '-DABOUT_PAGE_APP_NAME=Desktop Editors' + ) + # sccache caches MSVC object files by content hash and (with + # SCCACHE_GHA_ENABLED=true) persists them in the GitHub Actions cache, so a + # re-run recompiles only what changed. /Z7 embedded debug info is REQUIRED - + # with separate PDBs (/Zi) sccache refuses to cache and you get zero hits. + # Verify with `sccache --show-stats` after the build. + if (Get-Command sccache -ErrorAction SilentlyContinue) { + Write-Host "sccache detected - enabling compiler cache." + $cmakeArgs += @( + '-DCMAKE_C_COMPILER_LAUNCHER=sccache', + '-DCMAKE_CXX_COMPILER_LAUNCHER=sccache', + '-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded' + ) + } else { + Write-Warning "sccache not found on PATH - building WITHOUT a compiler cache." + } + $cmakeArgs += 'desktop-apps/win-linux/' + cmake @cmakeArgs + Assert-LastExit "CMake configure" + + # ─────────────────────── 8. CMake Build + Install ─────────────────────── + # Single-config Ninja: build type comes from CMAKE_BUILD_TYPE, so no + # --config here, and the MSBuild-only /p: flags are gone. + Write-Step "8. CMake Build" + if (Get-Command sccache -ErrorAction SilentlyContinue) { sccache --zero-stats | Out-Null } + cmake --build . --parallel + Assert-LastExit "CMake build" + if (Get-Command sccache -ErrorAction SilentlyContinue) { sccache --show-stats } + + Write-Step "8b. CMake Install" + cmake --install . + Assert-LastExit "CMake install" + + # ─────────────── 9. overlay the common tree onto the install dir ──────── + # Mirror the entire common payload over the installed tree (matches the + # workflow's robocopy). robocopy uses exit codes 0-7 for success and >=8 + # for real errors, so don't treat any nonzero code as failure - and reset + # $LASTEXITCODE afterward so later Assert-LastExit checks aren't tripped. + Write-Step "9. Overlaying common content onto the install dir" + robocopy $CommonDir $InstallDir /E /IS /IT /NFL /NDL /NJH /NJS + $rc = $LASTEXITCODE + if ($rc -ge 8) { throw "robocopy overlay failed (exit $rc)." } + $global:LASTEXITCODE = 0 + + $converter = Join-Path $InstallDir 'converter' + + # Deploy the SxS assembly manifest that makes converter\ a private "converter" + # assembly. graphics.dll/kernel.dll carry an embedded dependency on it, so + # without this the converter tools (and the app) fail to launch with + # 0xC0150002 (STATUS_SXS_CANT_GEN_ACTCTX). + Copy-Item (Join-Path $RepoRoot 'core\Common\msvc\converter.manifest') ` + (Join-Path $converter 'converter.manifest') -Force + + # ─────────── 9b. generate fonts + slide-theme thumbnails ──────────────── + # allfontsgen builds the native AllFonts.js + font_selection.bin (in + # converter\) AND the web AllFonts.js that doctrenderer loads via + # DoctRenderer.config (editors\sdkjs\common\AllFonts.js). The web variant is + # only emitted when --output-web is set - omit it and the file is silently + # skipped, which makes allthemesgen's doctrenderer fault on first run. + # allthemesgen then renders the slide-theme thumbnails through doctrenderer. + # The generators run as native exes from converter\ and launch correctly + # because step 9 deployed converter.manifest. Both tools are deleted + # afterward so they don't ship in the package. + Write-Step "9b. Generate fonts and theme thumbnails" + + & "$converter\allfontsgen.exe" ` + --use-system=1 ` + "--input=$InstallDir\fonts" ` + "--input=$RepoRoot\core-fonts" ` + "--allfonts=$converter\AllFonts.js" ` + "--allfonts-web=$InstallDir\editors\sdkjs\common\AllFonts.js" ` + "--output-web=$InstallDir\editors\fonts" ` + "--selection=$converter\font_selection.bin" + $genExit = $LASTEXITCODE + + # allfontsgen can exit 0 even when it wrote nothing, so verify the outputs + # exist rather than trusting the exit code: both the native AllFonts.js and + # the web one doctrenderer needs. Checking the web file guards against + # regressing the --output-web omission that silently drops it. + if ($genExit -ne 0 -or + -not (Test-Path "$converter\AllFonts.js") -or + -not (Test-Path "$InstallDir\editors\sdkjs\common\AllFonts.js")) { + throw "allfontsgen failed (exit $genExit) or did not produce AllFonts.js (native + web)." + } + + & "$converter\allthemesgen.exe" ` + "--converter-dir=$converter" ` + "--src=$InstallDir\editors\sdkjs\slide\themes" ` + "--allfonts=$converter\AllFonts.js" ` + "--output=$InstallDir\editors\sdkjs\common\Images" + Assert-LastExit "allthemesgen" + + Remove-Item -Force "$converter\allfontsgen.exe", "$converter\allthemesgen.exe" + + # ───────────────────────── 10/11. packaging ───────────────────────────── + if ($SkipPackaging) { + Write-Step "Packaging skipped (-SkipPackaging). Build output is at: $InstallDir" + } else { + Push-Location $PackageDir + try { + Write-Step "10. Stage build (make.ps1)" + .\make.ps1 ` + -Version $VersionFull ` + -Arch $Arch ` + -Target $Target ` + -CompanyName $CompanyName ` + -ProductName $ProductName ` + -SourceDir $InstallDir + Assert-LastExit "make.ps1" + + Write-Step "11a. Build ZIP (make_zip.ps1)" + $env:PATH = "$SevenZipRoot;$env:PATH" + .\make_zip.ps1 -Version $VersionFull -Arch $Arch -Target $Target + Assert-LastExit "make_zip.ps1" + + Write-Step "11b. Build Inno installer (make_inno.ps1)" + # INNOPATH must point at the Inno Setup program directory. The + # unofficial language files it relies on are staged during -InstallDeps. + $env:INNOPATH = Get-InnoRoot $InnoRoot + if (-not $env:INNOPATH) { + throw "Inno Setup (iscc.exe) not found. Install it (run with -InstallDeps) or pass -InnoRoot." + } + Write-Host "INNOPATH=$env:INNOPATH" + + # common.iss references jrsoftware's unofficial translations, which + # ship in no stock Inno install - stage them now (idempotent). + Sync-InnoLanguages (Join-Path $env:INNOPATH 'Languages') + + # make_inno.ps1 bundles the VC++ redistributable, fetching it at + # package time via WebClient from aka.ms (which failed on the + # runner). It SKIPS that download when inno\vc_redist..exe + # already exists with a valid ProductVersion, so pre-stage it here + # with a modern, redirect-following, retrying fetch and let + # make_inno reuse it. + $vcRedist = Join-Path $PackageDir "inno\vc_redist.$Arch.exe" + $vcValid = (Test-Path $vcRedist) -and (Get-Item $vcRedist).VersionInfo.ProductVersion + if (-not $vcValid) { + $vcUrl = "https://aka.ms/vs/17/release/vc_redist.$Arch.exe" + New-Item -ItemType Directory -Force -Path (Split-Path $vcRedist) | Out-Null + $got = $false + for ($i = 1; $i -le 5 -and -not $got; $i++) { + try { + Write-Host "Pre-fetching VCRedist (attempt $i): $vcUrl" + Invoke-WebRequest -Uri $vcUrl -OutFile $vcRedist + if ((Get-Item $vcRedist).VersionInfo.ProductVersion) { $got = $true } + else { Write-Warning "Downloaded file has no ProductVersion; retrying." } + } catch { + Write-Warning "VCRedist fetch failed (attempt $i): $($_.Exception.Message)" + } + if (-not $got) { Start-Sleep -Seconds 5 } + } + if (-not $got) { throw "Could not obtain a valid vc_redist.$Arch.exe after 5 attempts." } + } + Write-Host "VCRedist staged: $((Get-Item $vcRedist).VersionInfo.ProductVersion)" + + .\make_inno.ps1 -Version $VersionFull -Arch $Arch -Target $Target + Assert-LastExit "make_inno.ps1" + + if ($BuildMsi) { + Write-Step "11c. Build MSI (make_advinst.ps1)" + $aiRoot = (Get-Item "${env:ProgramFiles(x86)}\Caphyon\Advanced Installer*").FullName + $env:ADVINSTPATH = Join-Path $aiRoot 'bin\x86' + .\make_advinst.ps1 -Version $VersionFull -Arch $Arch + Assert-LastExit "make_advinst.ps1" + } + } finally { Pop-Location } + + Write-Step "DONE - artifacts:" + Write-Host " ZIP : $PackageDir\zip\*.zip" + Write-Host " EXE : $PackageDir\inno\*.exe" + if ($BuildMsi) { Write-Host " MSI : $PackageDir\advinst\*.msi" } + } +} +finally { + Close-StepGroup + Pop-Location +} \ No newline at end of file diff --git a/core b/core index ab71097..ec2bd75 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit ab710975ac3e5b7f5ea4eea53207ec58e5c869ed +Subproject commit ec2bd75f7e4d91856afcbd9996cafaff42ad7ea3 diff --git a/desktop-apps b/desktop-apps index 83f4d92..de61732 160000 --- a/desktop-apps +++ b/desktop-apps @@ -1 +1 @@ -Subproject commit 83f4d92487bf2111a3df459f5ca856730070fc3e +Subproject commit de61732967e7a639b913146054a2a14df8467b93 diff --git a/desktop-sdk b/desktop-sdk index 55917e4..00827af 160000 --- a/desktop-sdk +++ b/desktop-sdk @@ -1 +1 @@ -Subproject commit 55917e46b08173df5271ea0bb83369e58ffca049 +Subproject commit 00827afd78f555d4d8f2d5181aba319f8cd684f5