Browse Source

Add basic DOS port (#8155)

pull/8159/head
Anders Jenbo 7 months ago committed by GitHub
parent
commit
01507d532c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      3rdParty/Lua/CMakeLists.txt
  2. 2
      3rdParty/SDL_image/CMakeLists.txt
  3. 2
      3rdParty/libsmackerdec/CMakeLists.txt
  4. 4
      CMake/Assets.cmake
  5. 2
      CMake/Dependencies.cmake
  6. 2
      CMake/Platforms.cmake
  7. 27
      CMake/platforms/djcpp.toolchain.cmake
  8. 29
      CMake/platforms/dos.cmake
  9. 19
      CMakeLists.txt
  10. 49
      Packaging/windows/dos-prep.sh
  11. 4
      Source/CMakeLists.txt
  12. 12
      Source/effects_stubs.cpp
  13. 6
      Source/engine/assets.cpp
  14. 2
      Source/mpq/mpq_writer.cpp
  15. 14
      Source/options.cpp
  16. 2
      Source/panels/console.cpp
  17. 2
      Source/pfile.cpp
  18. 1
      Source/utils/display.cpp
  19. 12
      Source/utils/file_util.cpp
  20. 2
      Source/utils/sdl2_to_1_2_backports.cpp
  21. 18
      Source/utils/sdl_mutex.h
  22. 2
      Source/utils/sdl_thread.cpp
  23. 32
      Source/utils/sdl_thread.h
  24. 20
      docs/building.md

2
3rdParty/Lua/CMakeLists.txt vendored

@ -26,6 +26,8 @@ if(CMAKE_SYSTEM_NAME MATCHES "Darwin" AND DARWIN_MAJOR_VERSION VERSION_EQUAL 8)
# localtime_r gmtime_r
find_package(MacportsLegacySupport REQUIRED)
target_link_libraries(lua_static PRIVATE MacportsLegacySupport::MacportsLegacySupport)
elseif(TARGET_PLATFORM STREQUAL "dos")
target_compile_definitions(lua_static PUBLIC -DLUA_USE_C89)
elseif(ANDROID AND ("${ANDROID_ABI}" STREQUAL "armeabi-v7a" OR "${ANDROID_ABI}" STREQUAL "x86"))
target_compile_definitions(lua_static PUBLIC -DLUA_USE_C89)
elseif(NINTENDO_3DS OR VITA OR NINTENDO_SWITCH OR NXDK)

2
3rdParty/SDL_image/CMakeLists.txt vendored

@ -35,7 +35,7 @@ target_include_directories(SDL_image PRIVATE ${sdl_image_SOURCE_DIR})
target_compile_definitions(SDL_image PRIVATE LOAD_PNG SDL_IMAGE_USE_COMMON_BACKEND)
target_link_libraries(SDL_image PNG::PNG)
if(TARGET SDL2::SDL2)
if(TARGET SDL2::SDL2 AND NOT (DEVILUTIONX_STATIC_SDL2 AND TARGET SDL2::SDL2-static))
target_link_libraries(SDL_image SDL2::SDL2)
add_library(SDL2::SDL2_image ALIAS SDL_image)
elseif(TARGET SDL2::SDL2-static)

2
3rdParty/libsmackerdec/CMakeLists.txt vendored

@ -17,7 +17,7 @@ target_include_directories(libsmackerdec PUBLIC ${libsmackerdec_SOURCE_DIR}/incl
if(USE_SDL1)
target_link_libraries(libsmackerdec PUBLIC ${SDL_LIBRARY})
elseif(TARGET SDL2::SDL2)
elseif(TARGET SDL2::SDL2 AND NOT (DEVILUTIONX_STATIC_SDL2 AND TARGET SDL2::SDL2-static))
target_link_libraries(libsmackerdec PUBLIC SDL2::SDL2)
elseif(TARGET SDL2::SDL2-static)
target_link_libraries(libsmackerdec PUBLIC SDL2::SDL2-static)

4
CMake/Assets.cmake

@ -236,7 +236,11 @@ else()
endif()
if(BUILD_ASSETS_MPQ)
if(TARGET_PLATFORM STREQUAL "dos")
set(DEVILUTIONX_MPQ "${CMAKE_CURRENT_BINARY_DIR}/devx.mpq")
else()
set(DEVILUTIONX_MPQ "${CMAKE_CURRENT_BINARY_DIR}/devilutionx.mpq")
endif()
add_custom_command(
COMMENT "Building devilutionx.mpq"
OUTPUT "${DEVILUTIONX_MPQ}"

2
CMake/Dependencies.cmake

@ -104,7 +104,7 @@ if(USE_SDL1)
target_link_libraries(DevilutionX::SDL INTERFACE ${SDL_LIBRARY})
target_compile_definitions(DevilutionX::SDL INTERFACE USE_SDL1)
else()
if(TARGET SDL2::SDL2)
if(TARGET SDL2::SDL2 AND NOT (DEVILUTIONX_STATIC_SDL2 AND TARGET SDL2::SDL2-static))
target_link_libraries(DevilutionX::SDL INTERFACE SDL2::SDL2)
elseif(TARGET SDL2::SDL2-static)
target_link_libraries(DevilutionX::SDL INTERFACE SDL2::SDL2-static)

2
CMake/Platforms.cmake

@ -33,6 +33,8 @@ elseif(TARGET_PLATFORM STREQUAL "windows9x")
include(platforms/windows9x)
elseif(TARGET_PLATFORM STREQUAL "windowsXP")
include(platforms/windowsXP)
elseif(TARGET_PLATFORM STREQUAL "dos")
include(platforms/dos)
elseif(WIN32)
include(platforms/windows)
endif()

27
CMake/platforms/djcpp.toolchain.cmake

@ -0,0 +1,27 @@
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)
if($ENV{DJGPP_PREFIX})
set(DJGPP_PREFIX "$ENV{DJGPP_PREFIX}")
else()
set(DJGPP_PREFIX "/opt/i386-pc-msdosdjgpp-toolchain")
endif()
set(CMAKE_C_COMPILER "${DJGPP_PREFIX}/bin/i386-pc-msdosdjgpp-gcc")
set(CMAKE_CXX_COMPILER "${DJGPP_PREFIX}/bin/i386-pc-msdosdjgpp-g++")
set(CMAKE_STRIP "${DJGPP_PREFIX}/bin/i386-pc-msdosdjgpp-strip")
set(PKG_CONFIG_EXECUTABLE "${DJGPP_PREFIX}/bin/i386-pc-msdosdjgpp-pkg-config" CACHE STRING "Path to pkg-config")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-static")
set(DJGPP_ROOT "${DJGPP_PREFIX}/i386-pc-msdosdjgpp")
set(CMAKE_FIND_ROOT_PATH "${DJGPP_ROOT}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
link_directories("${DJGPP_ROOT}/lib")
include_directories(BEFORE SYSTEM "${DJGPP_ROOT}/sys-include" "${DJGPP_ROOT}/include")

29
CMake/platforms/dos.cmake

@ -0,0 +1,29 @@
set(ASAN OFF)
set(UBSAN OFF)
set(DIST ON)
set(NONET ON)
set(NOSOUND ON)
set(PREFILL_PLAYER_NAME ON)
set(DEVILUTIONX_SYSTEM_BZIP2 OFF)
set(DEVILUTIONX_SYSTEM_LIBFMT OFF)
set(DEVILUTIONX_SYSTEM_ZLIB OFF)
set(DEVILUTIONX_STATIC_ZLIB ON)
set(DEVILUTIONX_SYSTEM_SDL2 ON)
set(DEVILUTIONX_STATIC_SDL2 ON)
set(DEVILUTIONX_SYSTEM_SDL_IMAGE OFF)
set(DEVILUTIONX_SYSTEM_LIBPNG OFF)
set(DEVILUTIONX_PLATFORM_FILE_UTIL_LINK_LIBRARIES "")
list(APPEND DEVILUTIONX_PLATFORM_COMPILE_OPTIONS $<$<CONFIG:Debug>:-gstabs>)
add_compile_definitions(
SDL_DISABLE_IMMINTRIN_H
SDL_DISABLE_XMMINTRIN_H
SDL_DISABLE_EMMINTRIN_H
SDL_DISABLE_PMMINTRIN_H
SDL_DISABLE_MMINTRIN_H
)

19
CMakeLists.txt

@ -320,12 +320,19 @@ if(GPERF)
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS OFF)
# On some platforms, such as DJGPP,
# `-std=c++20` defines `__STRICT_ANSI__` which disables
# all POSIX extensions, so we need to use `-std=gnu++20` instead.
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # for clang-tidy
set(CMAKE_THREAD_PREFER_PTHREAD ON)
set(THREADS_PREFER_PTHREAD_FLAG ON)
if(NOT TARGET_PLATFORM STREQUAL "dos")
find_package(Threads REQUIRED)
endif()
# Dependencies must be included after Platforms.
include(Dependencies)
@ -335,6 +342,8 @@ add_subdirectory(Source)
set(BIN_TARGET devilutionx)
if(NINTENDO_3DS)
set(BIN_TARGET ${BIN_TARGET}.elf)
elseif(TARGET_PLATFORM STREQUAL "dos")
set(BIN_TARGET devx)
endif()
if(ANDROID)
@ -358,6 +367,14 @@ else()
endif()
endif()
if(TARGET_PLATFORM STREQUAL "dos")
# Allow multiple definitions for math stubs in DJGPP
set_target_properties(${BIN_TARGET} PROPERTIES
LINK_FLAGS "-Wl,--allow-multiple-definition -static"
)
target_link_libraries(${BIN_TARGET} PRIVATE m)
endif()
if(NOT UWP_LIB)
target_link_dependencies(${BIN_TARGET} PRIVATE libdevilutionx)
endif()

49
Packaging/windows/dos-prep.sh

@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -euo pipefail
# only use sudo when necessary
if [ `id -u` -ne 0 ]; then
SUDO=sudo
else
SUDO=""
fi
# Install dependencies on Debian / Ubuntu:
if which apt-get 2>/dev/null; then
set -x
$SUDO apt-get update
$SUDO apt-get install bison flex curl gcc g++ make texinfo zlib1g-dev tar bzip2 \
gzip xz-utils unzip python3-dev m4 dos2unix nasm cmake
{ set +x; } 2>/dev/null
fi
INSTALL_PREFIX=/opt/i386-pc-msdosdjgpp-toolchain
set -x
mkdir -p tmp/dos-prep
cd tmp/dos-prep
# Build and install DJGPP
git clone https://github.com/jwt27/build-gcc.git
cd build-gcc
$SUDO ./build-djgpp.sh --prefix="$INSTALL_PREFIX" --batch binutils gcc-14.2.0 djgpp-cvs
cd -
$SUDO rm -rf build-gcc
# Activate DJGPP environment
{ set +x; } 2>/dev/null
set +eu
source "${INSTALL_PREFIX}/bin/i386-pc-msdosdjgpp-setenv"
set -eu
set -x
# Build and install SDL2 for DOS
git clone --branch=dos-vbe https://github.com/diasurgical/SDL.git
cd SDL
autoreconf -fi
./configure --host=i386-pc-msdosdjgpp --prefix="${INSTALL_PREFIX}/i386-pc-msdosdjgpp" --disable-shared --enable-static --enable-video-svga --enable-timer-dos --enable-uclock
make -j$(nproc)
$SUDO "${INSTALL_PREFIX}/bin/i386-pc-msdosdjgpp-setenv" make install
cd -
rm -rf SDL

4
Source/CMakeLists.txt

@ -877,7 +877,6 @@ endif()
add_devilutionx_object_library(libdevilutionx ${libdevilutionx_SRCS})
target_include_directories(libdevilutionx PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_dependencies(libdevilutionx PUBLIC
Threads::Threads
DevilutionX::SDL
fmt::fmt
libsmackerdec
@ -931,6 +930,9 @@ target_link_dependencies(libdevilutionx PUBLIC
libdevilutionx_utf8
libdevilutionx_utils_console
)
if(NOT TARGET_PLATFORM STREQUAL "dos")
target_link_dependencies(libdevilutionx PUBLIC Threads::Threads)
endif()
if(DEVILUTIONX_SCREENSHOT_FORMAT STREQUAL DEVILUTIONX_SCREENSHOT_FORMAT_PNG)
target_link_dependencies(libdevilutionx PUBLIC libdevilutionx_surface_to_png)
endif()

12
Source/effects_stubs.cpp

@ -1,6 +1,9 @@
// Stubbed implementations of effects for the NOSOUND mode.
#include "effects.h"
#include <expected.hpp>
#include <magic_enum/magic_enum.hpp>
#include "engine/random.hpp"
namespace devilution {
@ -47,4 +50,13 @@ void ui_sound_init() { }
void effects_play_sound(SfxID id) { }
int GetSFXLength(SfxID nSFX) { return 0; }
tl::expected<SfxID, std::string> ParseSfxId(std::string_view value)
{
const std::optional<SfxID> enumValueOpt = magic_enum::enum_cast<SfxID>(value);
if (enumValueOpt.has_value()) {
return enumValueOpt.value();
}
return tl::make_unexpected("Unknown enum value.");
}
} // namespace devilution

6
Source/engine/assets.cpp

@ -88,7 +88,7 @@ AssetRef FindAsset(std::string_view filename)
char *const relativePath = &pathBuf[AssetRef::PathBufSize - filename.size() - 1];
*BufCopy(relativePath, filename) = '\0';
#ifndef _WIN32
#if !defined(_WIN32) && !defined(__DJGPP__)
std::replace(relativePath, pathEnd, '\\', '/');
#endif
// Absolute path:
@ -383,7 +383,11 @@ void LoadCoreArchives()
#if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__3DS__) && !defined(__SWITCH__)
// Load devilutionx.mpq first to get the font file for error messages
#ifdef __DJGPP__
LoadMPQ(paths, "devx", DevilutionXMpqPriority);
#else
LoadMPQ(paths, "devilutionx", DevilutionXMpqPriority);
#endif
#endif
LoadMPQ(paths, "fonts", FontMpqPriority); // Extra fonts
HasHellfireMpq = FindMPQ(paths, "hellfire");

2
Source/mpq/mpq_writer.cpp

@ -89,7 +89,9 @@ bool IsUnallocatedBlock(const MpqBlockEntry *block)
MpqWriter::MpqWriter(const char *path)
{
const std::string dir = std::string(Dirname(path));
if (!dir.empty()) {
RecursivelyCreateDir(dir.c_str());
}
LogVerbose("Opening {}", path);
bool isNewFile = false;
std::string error;

14
Source/options.cpp

@ -157,7 +157,9 @@ void SaveIni()
{
if (!ini.has_value()) return;
if (!ini->changed()) return;
if (!paths::ConfigPath().empty()) {
RecursivelyCreateDir(paths::ConfigPath().c_str());
}
const std::string iniPath = GetIniPath();
LoggedFStream out;
if (!out.Open(iniPath.c_str(), "wb")) {
@ -194,7 +196,7 @@ Options &GetOptions()
#if SDL_VERSION_ATLEAST(2, 0, 0)
bool HardwareCursorSupported()
{
#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1)
#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) || __DJGPP__
return false;
#else
SDL_version v;
@ -686,11 +688,17 @@ GraphicsOptions::GraphicsOptions()
: OptionCategoryBase("Graphics", N_("Graphics"), N_("Graphics Settings"))
, fullscreen("Fullscreen", OnlyIfSupportsWindowed | OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fullscreen"), N_("Display the game in windowed or fullscreen mode."), true)
#if !defined(USE_SDL1) || defined(__3DS__)
, fitToScreen("Fit to Screen", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fit to Screen"), N_("Automatically adjust the game window to your current desktop screen aspect ratio and resolution."), true)
, fitToScreen("Fit to Screen", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fit to Screen"), N_("Automatically adjust the game window to your current desktop screen aspect ratio and resolution."),
#ifdef __DJGPP__
false
#else
true
#endif
)
#endif
#ifndef USE_SDL1
, upscale("Upscale", OptionEntryFlags::Invisible | OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Upscale"), N_("Enables image scaling from the game resolution to your monitor resolution. Prevents changing the monitor resolution and allows window resizing."),
#ifdef NXDK
#if defined(NXDK) || defined(__DJGPP__)
false
#else
true

2
Source/panels/console.cpp

@ -579,7 +579,7 @@ void DrawConsole(const Surface &out)
};
if (FirstRender) {
const SDL_Rect sdlInputRect = MakeSdlRect(InputRect);
SDL_Rect sdlInputRect = MakeSdlRect(InputRect);
SDL_SetTextInputRect(&sdlInputRect);
SDL_StartTextInput();
FirstRender = false;

2
Source/pfile.cpp

@ -170,7 +170,9 @@ void CopySaveFile(uint32_t saveNum, std::string targetPath)
#ifdef DVL_NO_FILESYSTEM
#error "UNPACKED_SAVES requires either DISABLE_DEMOMODE or C++17 <filesystem>"
#endif
if (!targetPath.empty()) {
CreateDir(targetPath.c_str());
}
for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(savePath)) {
CopyFileOverwrite(entry.path().string().c_str(), (targetPath + entry.path().filename().string()).c_str());
}

1
Source/utils/display.cpp

@ -242,6 +242,7 @@ void UpdateAvailableResolutions()
ErrSdl();
}
for (auto &size : sizes) {
if (mode.h == 0) continue;
// Ensure that the ini specified resolution remains present in the resolution list
if (size.height == configuredSize.height)
size.width = configuredSize.width;

12
Source/utils/file_util.cpp

@ -28,7 +28,11 @@
#endif
#endif
#if (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR)
#if _POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__) || defined(__DJGPP__)
#define DVL_HAS_POSIX_2001
#endif
#if defined(DVL_HAS_POSIX_2001) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR)
#include <sys/stat.h>
#include <unistd.h>
@ -103,7 +107,7 @@ bool FileExists(const char *path)
return false;
}
return true;
#elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__)
#elif defined(DVL_HAS_POSIX_2001) && !defined(__ANDROID__)
return ::access(path, F_OK) == 0;
#elif defined(DVL_HAS_FILESYSTEM)
std::error_code ec;
@ -167,7 +171,7 @@ bool FileExistsAndIsWriteable(const char *path)
#ifdef _WIN32
const DWORD attr = WindowsGetFileAttributes(path);
return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY) == 0;
#elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__)
#elif defined(DVL_HAS_POSIX_2001) && !defined(__ANDROID__)
return ::access(path, W_OK) == 0;
#else
if (!FileExists(path))
@ -351,7 +355,7 @@ bool ResizeFile(const char *path, std::uintmax_t size)
}
::CloseHandle(file);
return true;
#elif _POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)
#elif defined(DVL_HAS_POSIX_2001)
return ::truncate(path, static_cast<off_t>(size)) == 0;
#else
static_assert(false, "truncate not implemented for the current platform");

2
Source/utils/sdl2_to_1_2_backports.cpp

@ -862,7 +862,7 @@ extern "C" char *SDL_GetPrefPath(const char *org, const char *app)
*
* https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
*/
#if defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0)
#if (defined(WINVER) && WINVER <= 0x0500 && (!defined(_WIN32_WINNT) || _WIN32_WINNT == 0)) || defined(__DJGPP__)
// On Windows9x there is no such thing as PrefPath. Simply use the current directory.
char *result = (char *)SDL_malloc(1);
*result = '\0';

18
Source/utils/sdl_mutex.h

@ -13,6 +13,23 @@ namespace devilution {
* RAII wrapper for SDL_mutex. Satisfies std's "Lockable" (SDL 2) or "BasicLockable" (SDL 1)
* requirements so it can be used with std::lock_guard and friends.
*/
#ifdef __DJGPP__
class SdlMutex final {
public:
SdlMutex() noexcept { }
~SdlMutex() noexcept { }
SdlMutex(const SdlMutex &) = delete;
SdlMutex(SdlMutex &&) = delete;
SdlMutex &operator=(const SdlMutex &) = delete;
SdlMutex &operator=(SdlMutex &&) = delete;
void lock() noexcept { }
void unlock() noexcept { }
void *get() noexcept { return nullptr; } // Dummy
};
#else
class SdlMutex final {
public:
SdlMutex()
@ -64,5 +81,6 @@ public:
private:
SDL_mutex *mutex_;
};
#endif
} // namespace devilution

2
Source/utils/sdl_thread.cpp

@ -2,6 +2,7 @@
namespace devilution {
#ifndef __DJGPP__
int SDLCALL SdlThread::ThreadTranslate(void *ptr)
{
auto handler = (void (*)())ptr;
@ -16,5 +17,6 @@ void SdlThread::ThreadDeleter(SDL_Thread *thread)
if (thread != nullptr)
app_fatal("Joinable thread destroyed");
}
#endif
} // namespace devilution

32
Source/utils/sdl_thread.h

@ -15,10 +15,40 @@ namespace devilution {
namespace this_sdl_thread {
inline SDL_threadID get_id()
{
#if defined(__DJGPP__)
return 1;
#else
return SDL_GetThreadID(nullptr);
#endif
}
} // namespace this_sdl_thread
#if defined(__DJGPP__)
class SdlThread final {
public:
SdlThread(int(SDLCALL *handler)(void *), void *data)
{
if (handler != nullptr) handler(data);
}
explicit SdlThread(void (*handler)(void))
{
if (handler != nullptr) handler();
}
SdlThread() = default;
bool joinable() const
{
return false;
}
SDL_threadID get_id() const
{
return this_sdl_thread::get_id();
}
void join()
{
}
};
#else
class SdlThread final {
static int SDLCALL ThreadTranslate(void *ptr);
static void ThreadDeleter(SDL_Thread *thread);
@ -66,4 +96,6 @@ public:
}
};
#endif
} // namespace devilution

20
docs/building.md

@ -623,6 +623,26 @@ sudo port select --set python3 python312
</details>
<details><summary>DOS</summary>
You can build for DOS from Linux using DJGPP.
First, install / compile the dependencies (only needs to be done once):
~~~ bash
Packaging/windows/dos-prep.sh
~~~
Then, build DevilutionX:
~~~ bash
cmake -S. -Bbuild-dos -DCMAKE_TOOLCHAIN_FILE=CMake/platforms/djcpp.toolchain.cmake -DTARGET_PLATFORM="dos" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF
cmake --build build-dos -j $(getconf _NPROCESSORS_ONLN)
~~~
</details>
<details><summary><b>CMake build options</b></summary>
### General

Loading…
Cancel
Save