fix: SLIC3R_PCH=OFF now builds on Windows, allowing compiler caching (#15552)

This commit is contained in:
Kris Austin
2026-09-06 16:06:18 -05:00
committed by GitHub
parent 0365304ae0
commit 43ce8c5e46
14 changed files with 159 additions and 4 deletions

View File

@@ -52,6 +52,7 @@ call :add_section "Build configuration"
call :add_arg config string "" config "release, debug, relwithdebinfo or minsizerel (default: release)"
call :add_arg target_arch string "" arch "x64 or arm64 (default: the host architecture)"
call :add_arg slicer_asan bool a asan "Build the slicer with ASAN enabled"
call :add_arg no_pch bool "" no-pch "Build without the precompiled header, implied by --cache"
call :add_section "Toolchain"
call :add_arg use_clang_cl bool l clang-cl "Use clang-cl as the compiler"
@@ -60,6 +61,7 @@ call :add_arg use_ninja bool x ninja "Use the Ninja Multi-Config generator"
call :add_arg use_msbuild bool "" msbuild "Use the Visual Studio generator (default)"
call :add_arg vs_version string "" vs "Visual Studio release: 2019, 2022 or 2026 (default: autodetect)"
call :add_arg clang_path string "" clang-path "Path to clang-cl.exe, requires -x (default: the one from Visual Studio)"
call :add_arg cache string "" cache "Compiler cache: ccache, sccache or off, requires -l -x (default: off)"
call :add_section "How much gets rebuilt"
call :add_arg slicer_target string "" slicer-target "Build one slicer target instead of all, e.g. libslic3r"
@@ -391,6 +393,49 @@ if not "%clang_path%" == "" if not "%using_ninja%" == "ON" (
exit /b 1
)
set "cache_args="
if "%cache%" == "" goto :cache_ready
if /I "%cache%" == "off" goto :cache_ready
if /I "%cache%" == "ccache" goto :cache_named
if /I "%cache%" == "sccache" goto :cache_named
echo Unknown --cache value "%cache%". Expected ccache, sccache or off.
exit /b 1
REM CMake accepts a compiler launcher under any generator but only runs it
REM under Makefile and Ninja. ccache also refuses cl.exe because the build
REM passes /Zi.
:cache_named
REM Only a configure records the launcher, so --no-configure needs none.
if "%no_configure%" == "ON" goto :cache_ready
if "%build_deps%%build_slicer%" == "" goto :cache_ready
if not "%using_ninja%" == "ON" goto :cache_needs_clang
if not "%use_clang_cl%" == "ON" goto :cache_needs_clang
goto :cache_tool
:cache_needs_clang
echo --cache needs clang-cl and Ninja; add -l -x.
exit /b 1
REM Name a full path, so the recorded launcher does not depend on PATH.
:cache_tool
set "cache_exe="
for /f "tokens=*" %%i in ('where %cache% 2^>nul') do (
if not defined cache_exe set "cache_exe=%%i"
)
if not defined cache_exe (
echo %cache% is not on PATH. Install it, or leave --cache off.
exit /b 1
)
REM Forward slashes, so CMake does not read a backslash as an escape.
set "cache_exe=%cache_exe:\=/%"
set "cache_args=-DCMAKE_C_COMPILER_LAUNCHER="%cache_exe%" -DCMAKE_CXX_COMPILER_LAUNCHER="%cache_exe%""
REM Neither cache stores a compile that uses a precompiled header. sccache
REM refuses /Fp outright. ccache does too unless its sloppiness is loosened,
REM and even then most hits fall back to the slower preprocessed mode.
set "no_pch=ON"
:cache_ready
REM Ninja prints [123/456] by default, which says nothing about how far
REM along it is or how long is left. %p is a percentage, %w and %W are
REM elapsed and remaining, but those two arrived in ninja 1.12 and an
@@ -474,6 +519,8 @@ REM real path rather than one glued onto the repository root.
for %%p in ("!build_dir!") do set "build_full=%%~fp"
echo Configuration: %build_type%, %arch%
if not "%clang_exe%" == "" echo Compiler: %clang_exe%
if "%no_pch%" == "ON" echo Precompiled header: off
if not "%cache_args%" == "" echo Compiler cache: %cache_exe%
set "SIG_FLAG="
if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%"
@@ -570,6 +617,10 @@ if "%build_deps%" == "ON" (
%error_check%
)
if not "!cache_args!" == "" (
set "deps_args=!deps_args! !cache_args!"
)
if not "%no_configure%" == "ON" (
call :print_and_run cmake -S deps -B "!DEP_TREE!" -G "%generator%" %gen_args% -DCMAKE_BUILD_TYPE=%build_type% !deps_args! %ORCA_DEPS_CMAKE_ARGS%
%error_check%
@@ -641,6 +692,16 @@ if "%build_slicer%" == "ON" (
set "slicer_args=!slicer_args! -DSLIC3R_ASAN=ON"
)
REM A later -DSLIC3R_PCH=ON in ORCA_SLICER_CMAKE_ARGS still wins, because
REM CMake takes the last definition on the command line.
if "%no_pch%" == "ON" (
set "slicer_args=!slicer_args! -DSLIC3R_PCH=OFF"
)
if not "!cache_args!" == "" (
set "slicer_args=!slicer_args! !cache_args!"
)
REM Configuring against a tree that was never built fails deep inside
REM package resolution. Name it here instead. Skipped when -d is about to
REM build it in this same run, and under a dry run, which configures
@@ -870,6 +931,7 @@ REM get_str_len <string> -> length in %ret%
echo %script_name% -s --no-configure -j 8 Rebuild quickly while iterating
echo %script_name% -s --slicer-target glad Compile one target to check the toolchain
echo %script_name% -l -x --run-tests Test that toolchain's build, not the default one
echo %script_name% -s -l -x --cache ccache Rebuild through a compiler cache
echo.
echo Environment:
echo ORCA_DEPS_CMAKE_ARGS Extra arguments for the deps configure
@@ -879,8 +941,8 @@ REM get_str_len <string> -> length in %ret%
echo NINJA_STATUS Ninja progress format, if you want your own
echo debugscript Set to ON to trace this script
echo.
echo set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_PCH=OFF -DSLIC3R_MSVC_PDB=OFF
echo $env:ORCA_SLICER_CMAKE_ARGS = '-DSLIC3R_PCH=OFF' (PowerShell)
echo set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_BUILD_SANDBOXES=ON -DSLIC3R_WARNINGS=OFF
echo $env:ORCA_SLICER_CMAKE_ARGS = '-DSLIC3R_BUILD_SANDBOXES=ON' (PowerShell)
echo.
echo --deps-args and --slicer-args cannot carry a value with spaces; use
echo these instead. Neither form supports a value containing an ampersand.

View File

@@ -92,6 +92,12 @@ New-Item -ItemType Directory -Force -Path $clangDir | Out-Null
Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $clangDir 'clang-cl.exe') -Force
$clangOnPath = "$clangDir;$env:PATH"
# A ccache that only has to exist. Nothing runs it; the script only locates it.
$cacheDir = Join-Path $fixtures 'cache'
New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null
Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $cacheDir 'ccache.exe') -Force
$ccacheOnPath = "$cacheDir;$env:PATH"
# ProgramFiles(x86) is where the script looks for vswhere, so an empty one
# stands in for a machine whose Visual Studio has no clang toolset.
$noVs = Join-Path $fixtures 'no-vs'
@@ -121,7 +127,7 @@ $cases = @(
'Examples:', 'Environment:') }
@{ Name = 'the environment section shows what to set'; Args = @('--help'); DryRun = $false
Contains = @('ORCA_DEPS_CMAKE_ARGS', 'ORCA_SLICER_CMAKE_ARGS', 'ORCA_UPDATER_SIG_KEY', 'NINJA_STATUS',
'set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_PCH=OFF', '(PowerShell)', 'debugscript') }
'set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_BUILD_SANDBOXES=ON', '(PowerShell)', 'debugscript') }
@{ Name = 'section headers do not widen the flag column'; Args = @('--help'); DryRun = $false
Match = @('^ -d, --deps +Download') }
# Windows Terminal opens at 120 columns and wraps at 120, so 119 is the
@@ -294,6 +300,14 @@ $cases = @(
Contains = @('-DBUILD_TESTS=ON') }
@{ Name = '-a enables ASAN for the slicer'; Args = @('-s', '-a')
Contains = @('-DSLIC3R_ASAN=ON') }
@{ Name = '--no-pch turns the precompiled header off'; Args = @('-s', '--no-pch')
Contains = @('-DSLIC3R_PCH=OFF') }
@{ Name = '--no-pch says so in the banner'; Args = @('-s', '--no-pch')
Contains = @('Precompiled header: off') }
@{ Name = 'the precompiled header is on unless asked'; Args = @('-s')
NotContains = @('SLIC3R_PCH') }
@{ Name = '--no-pch works without a cache'; Args = @('-s', '--no-pch')
NotContains = @('COMPILER_LAUNCHER') }
@{ Name = 'the slicer build runs gettext'; Args = @('-s')
Contains = @('run_gettext.bat') }
# tools\7z.exe needs a 7z.dll beside it, which the repo does not carry,
@@ -324,6 +338,42 @@ $cases = @(
@{ Name = 'deps and slicer build in one invocation'; Args = @('-d', '-s', '-x', '-l')
Contains = @('cmake -S deps', 'cmake -B "build-clang" ') }
'the compiler cache'
@{ Name = '--cache needs clang-cl and Ninja'; Args = @('-s', '--cache', 'ccache'); ExpectExit = 1
Contains = @('needs clang-cl and Ninja') }
# cl.exe is out of scope, since ccache refuses every compile under /Zi.
@{ Name = '--cache under Ninja still needs clang-cl'; Args = @('-s', '-x', '--cache', 'ccache'); ExpectExit = 1
Contains = @('needs clang-cl and Ninja') }
@{ Name = 'an unknown --cache value is rejected'; Args = @('-s', '-x', '--cache', 'nope'); ExpectExit = 1
Contains = @('Expected ccache, sccache or off') }
# A bare PATH, since the machine running the tests may have sccache installed.
@{ Name = 'a --cache tool that is not there is caught early'; Args = @('-s', '-l', '-x', '--cache', 'sccache'); ExpectExit = 1
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
Contains = @('is not on PATH') }
@{ Name = '--cache takes any casing'; Args = @('-s', '-l', '-x', '--cache', 'CCACHE')
Env = @{ PATH = $ccacheOnPath }
Contains = @('ccache.exe') }
@{ Name = '--cache off asks for no launcher'; Args = @('-s', '-x', '--cache', 'off')
NotContains = @('COMPILER_LAUNCHER') }
@{ Name = 'no --cache asks for no launcher'; Args = @('-s', '-x')
NotContains = @('COMPILER_LAUNCHER') }
@{ Name = '--cache turns the precompiled header off'; Args = @('-s', '-l', '-x', '--cache', 'ccache')
Env = @{ PATH = $ccacheOnPath }
Contains = @('-DSLIC3R_PCH=OFF', 'COMPILER_LAUNCHER') }
# The resolved path, not the bare name, so PATH cannot change it later.
@{ Name = '--cache names the resolved path in the banner'; Args = @('-s', '-l', '-x', '--cache', 'ccache')
Env = @{ PATH = $ccacheOnPath }
Match = @('^Compiler cache: .*/ccache\.exe$') }
@{ Name = '--cache reaches the dependency configure too'; Args = @('-d', '-l', '-x', '--cache', 'ccache')
Env = @{ PATH = $ccacheOnPath }
Contains = @('-DCMAKE_C_COMPILER_LAUNCHER=') }
# Nothing records a launcher without a configure, so the tool is not needed.
# Reaching the cmake check on a bare PATH is what proves it was skipped.
@{ Name = '--no-configure asks for no cache tool'; Args = @('-s', '-l', '-x', '--no-configure', '--cache', 'ccache'); ExpectExit = 1
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
Contains = @('CMake was not found')
NotContains = @('is not on PATH') }
'the developer loop'
@{ Name = '--slicer-target builds one target'; Args = @('-s', '--slicer-target', 'libslic3r')
Contains = @('--config Release --target libslic3r') }

View File

@@ -1,6 +1,7 @@
#include "BlacklistedLibraryCheck.hpp"
#include <cstdio>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/convert.hpp>
#ifdef WIN32

View File

@@ -665,6 +665,13 @@ if(SLIC3R_PROFILE)
target_link_libraries(libslic3r PRIVATE Shiny)
endif()
if (WIN32)
# Public, since BlacklistedLibraryCheck.hpp includes windows.h. Empty
# WIN32_LEAN_AND_MEAN matches the sources that define it themselves; bare
# NOMINMAX matches the one libigl already passes.
target_compile_definitions(libslic3r PUBLIC "WIN32_LEAN_AND_MEAN=" "NOMINMAX")
endif ()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif ()

View File

@@ -24,6 +24,7 @@ public:
explicit MultiPoint(const Points &_points) : points(_points) {}
MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
virtual ~MultiPoint() = default;
void scale(double factor);
void scale(double factor_x, double factor_y);
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }

View File

@@ -1,3 +1,8 @@
#ifdef _WIN32
// Keep this first. A header below reaches boost/regex, whose w32_regex_traits
// needs the Win32 types declared already.
#include <Windows.h>
#endif
#include "Config.hpp"
#include "Exception.hpp"
#include "Print.hpp"

View File

@@ -1,4 +1,5 @@
#include <functional>
#include <numeric>
#include <optional>
#include <libslic3r/OpenVDBUtils.hpp>

View File

@@ -916,6 +916,9 @@ endif ()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
elseif (MSVC)
# Puts the Windows headers first when the PCH is off.
target_compile_options(libslic3r_gui PRIVATE "/FIslic3r/win_platform.hpp")
endif ()
if (APPLE)

View File

@@ -3,6 +3,7 @@
#include "wx/bitmap.h"
#include "wx/dragimag.h"
#include "wx/panel.h"
namespace Slic3r { namespace GUI {

View File

@@ -12,6 +12,7 @@
#include "BuildCommit.hpp"
#include "Downloader.hpp"
#include <boost/chrono/duration.hpp>
#include <boost/locale/encoding_utf.hpp>
#include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp>
#include <mutex>

View File

@@ -199,7 +199,7 @@ public:
OptionsGroup(wxWindow *_parent, const wxString &title, const wxString &icon, bool is_tab_opt = false,
column_t extra_clmn = nullptr);
~OptionsGroup() { clear(true); }
virtual ~OptionsGroup() { clear(true); }
wxGridSizer* get_grid_sizer() { return m_grid_sizer; }
const std::vector<Line>& get_lines() { return m_lines; }

View File

@@ -8,6 +8,9 @@
#ifndef wxMediaCtrl3_h
#define wxMediaCtrl3_h
#include <chrono>
#include "wx/window.h"
#include "wx/bitmap.h"
#include "wx/uri.h"
#include "wx/mediactrl.h"

View File

@@ -115,6 +115,7 @@ class UdpSession
{
public:
UdpSession(Bonjour::ReplyFn rfn);
virtual ~UdpSession() = default;
virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0;
std::vector<char> buffer;
boost::asio::ip::udp::endpoint remote_endpoint;

View File

@@ -0,0 +1,19 @@
#pragma once
// Force-included into libslic3r_gui when SLIC3R_PCH is OFF, standing in for
// pchheader.hpp, which includes <Windows.h> before anything else. Arriving
// late and transitively, rpcndr.h defines a global `byte` that collides with
// std::byte under `using namespace std`, and the control and URL moniker
// types the GUI uses are never declared.
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#include <CommCtrl.h>
#include <urlmon.h>
#endif // _WIN32