diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml new file mode 100644 index 00000000000..5988d941f02 --- /dev/null +++ b/.github/workflows/windows-ci.yml @@ -0,0 +1,284 @@ +name: windows-ci + +on: + workflow_dispatch: + push: + branches: + - main + - windows-*.*.x + tags: + - windows-* + + pull_request: + branches: + - '*' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + # INFO: We are cancelling the concurrency group if the change is on PR. For workflow dispatch, this will not work. + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +permissions: + id-token: write # needed for AWS + actions: read # needed for CodeQL + contents: read # needed for CodeQL + security-events: write # needed for CodeQL + +jobs: + pre_job: + runs-on: windows-2022 + outputs: + should_skip: ${{ github.event_name != 'workflow_dispatch' && steps.changed-files.outputs.any_modified != 'true' }} + steps: + - name: Set git core.longpaths flag + run: | + git config --system core.longpaths true + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Get all Windows files that have changed + if: github.event_name != 'workflow_dispatch' + id: changed-files + uses: tj-actions/changed-files@v45 + + - name: List changed files + if: steps.changed-files.outputs.any_modified == 'true' + shell: bash + run: | + echo "Changed file(s): ${{ steps.changed-files.outputs.all_changed_files }}" + + windows-configure: + if: needs.pre_job.outputs.should_skip != 'true' + needs: pre_job + strategy: + fail-fast: true + matrix: + renderer: [opengl, egl, vulkan, osmesa] + runs-on: windows-2022 + steps: + - name: Restore vcpkg cache + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/platform/windows/vendor/vcpkg + !${{ github.workspace }}/platform/windows/vendor/vcpkg/buildtrees + !${{ github.workspace }}/platform/windows/vendor/vcpkg/packages + !${{ github.workspace }}/platform/windows/vendor/vcpkg/downloads + !${{ github.workspace }}/platform/windows/vendor/vcpkg/installed + key: | + ${{ github.job }}-${{ matrix.renderer }}-${{ hashFiles( '.git/modules/platform/windows/vendor/vcpkg/HEAD' ) }}-${{ hashFiles( 'platform/windows/Get-VendorPackages.ps1' ) }} + + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Download and build vcpkg packages + env: + VCPKG_OVERLAY_TRIPLETS: ${{ github.workspace }}/platform/windows/vendor/vcpkg-custom-triplets + VCPKG_DEFAULT_TRIPLET: ${{ env.VSCMD_ARG_TGT_ARCH }}-windows + VCPKG_TARGET_TRIPLET: ${{ env.VSCMD_ARG_TGT_ARCH }}-windows + run: | + ${{ github.workspace }}\platform\windows\Get-VendorPackages.ps1 -Triplet ${{ env.VCPKG_TARGET_TRIPLET }} -Renderer ${{ matrix.renderer }} + + windows-build-and-test: + if: needs.pre_job.outputs.should_skip != 'true' + needs: windows-configure + strategy: + fail-fast: true + matrix: + renderer: [opengl, egl, vulkan, osmesa] + rendering_mode: [legacy, drawable] + exclude: + - renderer: vulkan + rendering_mode: legacy + runs-on: windows-2022 + steps: + - name: Set git core.longpaths flag + run: | + git config --system core.longpaths true + + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: cpp + + - uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }} + + - name: "Create directory '${{ github.workspace }}/platform/windows/vendor/vcpkg/bincache'" + shell: bash + run: mkdir -p ${{ github.workspace }}/platform/windows/vendor/vcpkg/bincache + + - name: Restore vcpkg cache + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/platform/windows/vendor/vcpkg + !${{ github.workspace }}/platform/windows/vendor/vcpkg/buildtrees + !${{ github.workspace }}/platform/windows/vendor/vcpkg/packages + !${{ github.workspace }}/platform/windows/vendor/vcpkg/downloads + !${{ github.workspace }}/platform/windows/vendor/vcpkg/installed + key: | + ${{ github.job }}-${{ matrix.renderer }}-${{ hashFiles( '.git/modules/platform/windows/vendor/vcpkg/HEAD' ) }}-${{ hashFiles( 'platform/windows/Get-VendorPackages.ps1' ) }} + + - if: matrix.rendering_mode == 'legacy' + shell: bash + run: echo rendering_mode_flag_cmake=-DMLN_LEGACY_RENDERER=ON >> "$GITHUB_ENV" + + - if: matrix.rendering_mode == 'drawable' + shell: bash + run: echo rendering_mode_flag_cmake=-DMLN_DRAWABLE_RENDERER=ON >> "$GITHUB_ENV" + + - if: matrix.renderer == 'opengl' + shell: bash + run: echo renderer_flag_cmake="-DMLN_WITH_OPENGL=ON" >> "$GITHUB_ENV" + + - if: matrix.renderer == 'egl' + shell: bash + run: echo renderer_flag_cmake="-DMLN_WITH_EGL=ON" >> "$GITHUB_ENV" + + - if: matrix.renderer == 'vulkan' + shell: bash + run: echo renderer_flag_cmake="-DMLN_WITH_VULKAN=ON -DMLN_WITH_OPENGL=OFF" >> "$GITHUB_ENV" + + - if: matrix.renderer == 'osmesa' + shell: bash + run: echo renderer_flag_cmake="-DMLN_WITH_OSMESA=ON" >> "$GITHUB_ENV" + + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Build MapLibre Native Core + env: + CI: 1 + run: | + cmake --version + cmake -B build -GNinja ` + -DCMAKE_BUILD_TYPE=RelWithDebInfo ` + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ` + ${{ env.renderer_flag_cmake }} ` + ${{ env.rendering_mode_flag_cmake }} + cmake --build build --target mbgl-core mbgl-test-runner mbgl-render-test-runner mbgl-expression-test mbgl-render mbgl-benchmark-runner + + # mbgl-render (used for size test) & mbgl-benchmark-runner + + - name: Upload mbgl-render as artifact + if: matrix.renderer == 'drawable' && github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: mbgl-render + path: | + build/bin/mbgl-render.exe + + - name: Upload mbgl-benchmark-runner as artifact + if: matrix.renderer == 'drawable' && github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: mbgl-benchmark-runner + path: | + build/mbgl-benchmark-runner.exe + + - name: Configure AWS Credentials + if: matrix.renderer == 'drawable' && github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-west-2 + role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} + role-session-name: ${{ github.run_id }} + + - name: Upload mbgl-render & mbgl-benchmark-runner to S3 + if: matrix.renderer == 'drawable' && github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + run: | + aws s3 cp build/bin/mbgl-render.exe s3://maplibre-native/mbgl-render-main + aws s3 cp build/mbgl-benchmark-runner.exe s3://maplibre-native/mbgl-benchmark-runner-main + + # CodeQL + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:cpp" + + - name: Download Mesa3D and extract base files + run: | + Invoke-WebRequest https://github.com/pal1000/mesa-dist-win/releases/download/24.2.5/mesa3d-24.2.5-release-msvc.7z -OutFile mesa3d.7z + & 'C:\Program Files\7-Zip\7z.exe' e -obuild .\mesa3d.7z + + - name: Extract Mesa3D files for OpenGL + if: matrix.renderer != 'vulkan' + run: | + & 'C:\Program Files\7-Zip\7z.exe' e -obuild .\mesa3d.7z x64\opengl32.dll x64\libgallium_wgl.dll x64\libGLESv2.dll x64\libglapi.dll + + - name: Extract Mesa3D files for Vulkan + if: matrix.renderer == 'vulkan' + run: | + & 'C:\Program Files\7-Zip\7z.exe' e -obuild .\mesa3d.7z x64\lvp_icd.x86_64.json x64\vulkan_lvp.dll + + - name: Extract Mesa3D files for OSMesa + if: matrix.renderer == 'osmesa' + run: | + & 'C:\Program Files\7-Zip\7z.exe' e -obuild .\mesa3d.7z x64\osmesa.dll + + # unit tests + + - name: Run C++ tests + continue-on-error: ${{ matrix.renderer == 'vulkan' }} + env: + GALLIUM_DRIVER: ${{ matrix.renderer != 'vulkan' && 'llvmpipe' }} + VK_DRIVER_FILES: ${{ matrix.renderer == 'vulkan' && 'build\lvp_icd.x86_64.json' }} + VK_LAYER_PATH: ${{ matrix.renderer == 'vulkan' && 'build' }} + VK_LOADER_DRIVERS_SELECT: ${{ matrix.renderer == 'vulkan' && '*lvp*' }} + shell: bash + run: build/mbgl-test-runner.exe + + # render tests + + - name: Run render test + id: render_test + env: + GALLIUM_DRIVER: ${{ matrix.renderer != 'vulkan' && 'llvmpipe' }} + VK_DRIVER_FILES: ${{ matrix.renderer == 'vulkan' && 'build\lvp_icd.x86_64.json' }} + VK_LAYER_PATH: ${{ matrix.renderer == 'vulkan' && 'build' }} + VK_LOADER_DRIVERS_SELECT: ${{ matrix.renderer == 'vulkan' && '*lvp*' }} + shell: bash + run: build/mbgl-render-test-runner.exe --manifestPath=metrics/windows-${{ matrix.renderer }}.json + + - name: Upload render test result + if: always() && steps.render_test.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: render-test-result-${{ matrix.renderer }} + path: | + metrics/windows-${{ matrix.renderer }}.html + + # expression tests + + - name: Run expression test + env: + GALLIUM_DRIVER: ${{ matrix.renderer != 'vulkan' && 'llvmpipe' }} + VK_DRIVER_FILES: ${{ matrix.renderer == 'vulkan' && 'build\lvp_icd.x86_64.json' }} + VK_LAYER_PATH: ${{ matrix.renderer == 'vulkan' && 'build' }} + VK_LOADER_DRIVERS_SELECT: ${{ matrix.renderer == 'vulkan' && '*lvp*' }} + shell: bash + run: build/expression-test/mbgl-expression-test.exe + + - if: github.event_name == 'pull_request' + uses: ./.github/actions/save-pr-number + + windows-ci-result: + name: Windows CI Result + if: needs.pre_job.outputs.should_skip != 'true' && always() + runs-on: windows-2022 + needs: + - pre_job + - windows-build-and-test + steps: + - name: Mark result as failed + if: needs.windows-build-and-test.result != 'success' + shell: bash + run: exit 1 diff --git a/metrics/windows-drawable.json b/metrics/windows-drawable.json new file mode 100644 index 00000000000..bab628bbb71 --- /dev/null +++ b/metrics/windows-drawable.json @@ -0,0 +1,14 @@ +{ + "base_test_path": "integration", + "cache_path": "cache-style.db", + "expectation_paths": [ + ], + "ignore_paths": [ + "ignores/platform-all.json" + ], + "metric_path": "linux-gcc8-release", + "probes": [ + "probeGFX", + "probeNetwork" + ] +} diff --git a/metrics/windows-legacy.json b/metrics/windows-legacy.json new file mode 100644 index 00000000000..bab628bbb71 --- /dev/null +++ b/metrics/windows-legacy.json @@ -0,0 +1,14 @@ +{ + "base_test_path": "integration", + "cache_path": "cache-style.db", + "expectation_paths": [ + ], + "ignore_paths": [ + "ignores/platform-all.json" + ], + "metric_path": "linux-gcc8-release", + "probes": [ + "probeGFX", + "probeNetwork" + ] +} diff --git a/metrics/windows-vulkan.json b/metrics/windows-vulkan.json new file mode 100644 index 00000000000..bab628bbb71 --- /dev/null +++ b/metrics/windows-vulkan.json @@ -0,0 +1,14 @@ +{ + "base_test_path": "integration", + "cache_path": "cache-style.db", + "expectation_paths": [ + ], + "ignore_paths": [ + "ignores/platform-all.json" + ], + "metric_path": "linux-gcc8-release", + "probes": [ + "probeGFX", + "probeNetwork" + ] +} diff --git a/platform/windows/Get-VendorPackages.ps1 b/platform/windows/Get-VendorPackages.ps1 index f492124be31..69394c5a83e 100644 --- a/platform/windows/Get-VendorPackages.ps1 +++ b/platform/windows/Get-VendorPackages.ps1 @@ -23,7 +23,7 @@ switch($Renderer) 'EGL' { $renderer_packages = @('egl', 'opengl-registry'); break } 'OSMesa' { $renderer_packages = @(''); break } 'OpenGL' { $renderer_packages = @('opengl-registry'); break } - 'Vulkan' { $renderer_packages = @(''); break } + 'Vulkan' { $renderer_packages = @(''); break } } if(-not (Test-Path "$vcpkg_temp_dir\vcpkg.exe")) diff --git a/test/src/mbgl/test/http_server.cpp b/test/src/mbgl/test/http_server.cpp index 42e604a1387..25bb6cee3a1 100644 --- a/test/src/mbgl/test/http_server.cpp +++ b/test/src/mbgl/test/http_server.cpp @@ -42,6 +42,7 @@ void runServer(std::unique_ptr& server) { uint64_t start = std::strtoull(str.substr(0, str.find("-")).c_str(), nullptr, 10); uint64_t end = std::strtoull(str.substr(str.find("-") + 1).c_str(), nullptr, 10); content = content.substr(start, end - start + 1); + res.status = 206; } res.set_content(content, "text/plain"); }); diff --git a/test/storage/online_file_source.test.cpp b/test/storage/online_file_source.test.cpp index 1673b13c151..c8a0de213cf 100644 --- a/test/storage/online_file_source.test.cpp +++ b/test/storage/online_file_source.test.cpp @@ -13,6 +13,13 @@ using namespace mbgl; +#ifdef WIN32 +// Windows doesn't fail immediately like other OS +constexpr double connectionTimeout = 2.0; +#else +constexpr double connectionTimeout = 0; +#endif + TEST(OnlineFileSource, Cancel) { util::RunLoop loop; std::unique_ptr fs = std::make_unique(ResourceOptions::Default(), ClientOptions()); @@ -93,7 +100,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(ConnectionError)) { const auto start = Clock::now(); int counter = 0; - int wait = 0; + double wait = connectionTimeout; std::unique_ptr req = fs->request({Resource::Unknown, "http://127.0.0.1:3001/"}, [&](Response res) { const auto duration = std::chrono::duration(Clock::now() - start).count(); @@ -111,7 +118,7 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(ConnectionError)) { req.reset(); loop.stop(); } - wait += (1 << counter); + wait += (1 << counter) + connectionTimeout; counter++; }); @@ -318,13 +325,16 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(NetworkStatusChangePreempt)) { int counter = 0; const Resource resource{Resource::Unknown, "http://127.0.0.1:3001/test"}; + + double wait = connectionTimeout; + std::unique_ptr req = fs->request(resource, [&](Response res) { const auto duration = std::chrono::duration(Clock::now() - start).count(); if (counter == 0) { - EXPECT_GT(0.2, duration) << "Response came in too late"; + EXPECT_GT(wait + 0.2, duration) << "Response came in too late"; } else if (counter == 1) { - EXPECT_LT(0.39, duration) << "Preempted retry triggered too early"; - EXPECT_GT(0.6, duration) << "Preempted retry triggered too late"; + EXPECT_LT(wait + 0.39, duration) << "Preempted retry triggered too early"; + EXPECT_GT(wait + 0.6, duration) << "Preempted retry triggered too late"; } else if (counter > 1) { FAIL() << "Retried too often"; } @@ -336,15 +346,19 @@ TEST(OnlineFileSource, TEST_REQUIRES_SERVER(NetworkStatusChangePreempt)) { EXPECT_FALSE(bool(res.modified)); EXPECT_FALSE(bool(res.etag)); + wait += connectionTimeout; + if (counter++ == 1) { req.reset(); loop.stop(); } }); - // After 400 milliseconds, we're going to trigger a NetworkStatus change. + // After 400 milliseconds + connectionTimeout, we're going to trigger a NetworkStatus change. util::Timer reachableTimer; - reachableTimer.start(Milliseconds(400), Duration::zero(), []() { mbgl::NetworkStatus::Reachable(); }); + reachableTimer.start(Milliseconds(static_cast(connectionTimeout * 1000) + 400), Duration::zero(), []() { + mbgl::NetworkStatus::Reachable(); + }); fs->resume(); loop.run(); diff --git a/test/util/timer.test.cpp b/test/util/timer.test.cpp index 4b4010405d7..ae95d9a7722 100644 --- a/test/util/timer.test.cpp +++ b/test/util/timer.test.cpp @@ -63,7 +63,7 @@ TEST(Timer, TEST_REQUIRES_ACCURATE_TIMING(Repeat)) { auto totalTime = std::chrono::duration_cast(mbgl::Clock::now() - first); EXPECT_GE(totalTime, expectedTotalTime * 0.8); - EXPECT_LE(totalTime, expectedTotalTime * 1.2); + EXPECT_LE(totalTime, expectedTotalTime * 1.3); } TEST(Timer, TEST_REQUIRES_ACCURATE_TIMING(Stop)) { @@ -170,7 +170,7 @@ TEST(Timer, TEST_REQUIRES_ACCURATE_TIMING(StoppedDuringExpiration)) { auto totalTime = std::chrono::duration_cast(mbgl::Clock::now() - first); EXPECT_GE(totalTime, expireTimeout * 0.8); - EXPECT_LE(totalTime, expireTimeout * 1.2); + EXPECT_LE(totalTime, expireTimeout * 1.3); } TEST(Timer, TEST_REQUIRES_ACCURATE_TIMING(StoppedAfterExpiration)) {