From 49c6a03da0f5ed37e6c81949c558e8e34d63373f Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sat, 25 Apr 2026 19:43:58 +0200 Subject: [PATCH 1/4] cmake: support building a shared SDL3 DOS executable --- .../actions/setup-djgpp-toolchain/action.yml | 1 + .github/workflows/create-test-plan.py | 4 +- CMakeLists.txt | 28 ++++- build-scripts/djgpp-dxe-linker-wrapper.py | 75 ++++++++++++ build-scripts/djgpp-platform-overrides.cmake | 7 +- build-scripts/i586-pc-msdosdjgpp.cmake | 11 +- cmake/test/CMakeLists.txt | 14 ++- include/SDL3/SDL_main.h | 1 + include/SDL3/SDL_main_impl.h | 112 ++++++++++++++++++ 9 files changed, 238 insertions(+), 15 deletions(-) create mode 100755 build-scripts/djgpp-dxe-linker-wrapper.py diff --git a/.github/actions/setup-djgpp-toolchain/action.yml b/.github/actions/setup-djgpp-toolchain/action.yml index 636c669b04..ca6ee14fe4 100644 --- a/.github/actions/setup-djgpp-toolchain/action.yml +++ b/.github/actions/setup-djgpp-toolchain/action.yml @@ -63,4 +63,5 @@ runs: id: final shell: pwsh run: | + echo "${{ runner.temp }}/djgpp/i586-pc-msdosdjgpp/bin" >> $env:GITHUB_PATH echo "${{ runner.temp }}/djgpp/bin" >> $env:GITHUB_PATH diff --git a/.github/workflows/create-test-plan.py b/.github/workflows/create-test-plan.py index c0e847c05f..2c9e3e4bb1 100755 --- a/.github/workflows/create-test-plan.py +++ b/.github/workflows/create-test-plan.py @@ -164,6 +164,7 @@ class SharedLibType(Enum): SO_0 = "libSDL3.so.0" SO = "libSDL3.so" DYLIB = "libSDL3.0.dylib" + DXE = "SDL3.dxe" FRAMEWORK = "SDL3.framework/Versions/A/SDL3" @@ -829,11 +830,10 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args job.apt_packages = ["ccache", "libfl-dev"] # djgpp needs libfl.so.2 job.cmake_build_type = "Release" job.setup_ninja = True + job.shared_lib = SharedLibType.DXE job.static_lib = StaticLibType.A - job.shared_lib = None job.clang_tidy = False job.werror = False # FIXME: enable SDL_WERROR - job.shared = False job.run_tests = False job.test_pkg_config = False job.cmake_toolchain_file = "$GITHUB_WORKSPACE/build-scripts/i586-pc-msdosdjgpp.cmake" diff --git a/CMakeLists.txt b/CMakeLists.txt index 6959d9f5c9..48b4fa36e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,7 +208,19 @@ if(EMSCRIPTEN) set(SDL_SHARED_AVAILABLE OFF) endif() -if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE OR DOS) +if(DOS) + if(NOT DJGPP) + message(FATAL_ERROR "SDL3 DOS support requires a DJGPP toolchain") + endif() + + find_program(DJGPP_DXE3GEN_BIN NAMES dxe3gen) + if(NOT EXISTS "${DJGPP_DXE3GEN_BIN}") + message(STATUS "Disabling shared library support: dxe3gen not found") + set(SDL_SHARED_AVAILABLE OFF) + endif() +endif() + +if(VITA OR PSP OR PS2 OR N3DS OR RISCOS OR NGAGE) set(SDL_SHARED_AVAILABLE OFF) endif() @@ -552,7 +564,7 @@ if (LIBC_IS_GLIBC AND CMAKE_SIZEOF_VOID_P EQUAL 4) endif() check_linker_supports_version_file(HAVE_WL_VERSION_SCRIPT) -if(HAVE_WL_VERSION_SCRIPT) +if(HAVE_WL_VERSION_SCRIPT AND (NOT DOS)) sdl_shared_link_options("-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/dynapi/SDL_dynapi.sym") else() # When building with tcc on Linux+glibc or Android, avoid emitting an error @@ -696,7 +708,7 @@ if(USE_GCC OR USE_CLANG OR USE_INTELCC OR USE_QCC) endif() endif() - if(NOT OPENBSD) + if(NOT (DOS OR OPENBSD)) cmake_push_check_state() check_linker_flag(C "-Wl,--no-undefined" LINKER_SUPPORTS_WL_NO_UNDEFINED) #FIXME: originally this if had an additional "AND NOT (USE_CLANG AND WINDOWS)" @@ -4075,6 +4087,16 @@ if(SDL_SHARED) ) endif() endif() + if(DJGPP) + # _LINKER_LAUNCHER was introduced in CMake 3.21. genex support was added in 3.27. + cmake_minimum_required(VERSION 3.27) + + set_property(TARGET SDL3-shared SDL_uclibc PROPERTY POSITION_INDEPENDENT_CODE FALSE) + set_property(TARGET SDL3-shared PROPERTY IMPORT_SUFFIX ".dxe.a") + set_property(TARGET SDL3-shared PROPERTY SUFFIX ".dxe") + set_property(TARGET SDL3-shared PROPERTY C_LINKER_LAUNCHER "${CMAKE_CURRENT_SOURCE_DIR}/build-scripts/djgpp-dxe-linker-wrapper.py;--dxe3gen=${DJGPP_DXE3GEN_BIN};--import-library;$;--library-paths;${DJGPP_LIBRARY_PATHS};--") + set_property(TARGET SDL3-shared APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build-scripts/djgpp-dxe-linker-wrapper.py") + endif() target_link_libraries(SDL3-shared PRIVATE ${SDL_CMAKE_DEPENDS}) target_include_directories(SDL3-shared diff --git a/build-scripts/djgpp-dxe-linker-wrapper.py b/build-scripts/djgpp-dxe-linker-wrapper.py new file mode 100755 index 0000000000..ddc7c9eab8 --- /dev/null +++ b/build-scripts/djgpp-dxe-linker-wrapper.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import subprocess + +def parse_wrapper_args(args): + parser = argparse.ArgumentParser(allow_abbrev=False) + parser.add_argument("--library-paths", nargs="*", help="Additional DJGPP Library paths") + parser.add_argument("--dxe3gen", default="dxe3gen", help="Path of dxe3gen") + parser.add_argument("--import-library", help="Import library name") + args = parser.parse_args(args) + return args + +def parse_linker_args(args): + unknown_args = [] + output = None + i = 1 + while i < len(args): + arg = args[i] + if arg in ("-fPIC", "-shared"): + consumed = 1 + elif arg.startswith("-Wl,"): + unknown_args.extend(arg[4:].split(",")) + elif arg.startswith("-I"): + if arg == "-I": + consumed = 2 + else: + consumed = 1 + elif arg == "-o": + output = args[i + 1] + consumed = 2 + elif arg.startswith("-D"): + consumed = 1 + elif arg.startswith("-O"): + consumed = 1 + else: + unknown_args.append(arg) + consumed = 1 + i += consumed + if not output: + print("Missing \"-o\" argument", file=sys.stderr) + return output, unknown_args + +try: + pos_double_dash = sys.argv.index("--") + wrapper_args = sys.argv[1:pos_double_dash] + original_args = sys.argv[pos_double_dash+1:] +except IndexError: + wrapper_args = [] + original_args = sys.argv[1:] + +wrapper_args = parse_wrapper_args(wrapper_args) +output_path, original_linker_args = parse_linker_args(original_args) + +dxe3gen_command = [ + wrapper_args.dxe3gen, + "-o", output_path, + "-U", +] +if wrapper_args.import_library: + dxe3gen_command.extend(["-Y", wrapper_args.import_library]) +for libpath in wrapper_args.library_paths: + dxe3gen_command.append("-L" + libpath) +dxe3gen_command.extend(original_linker_args) +if not "-nostlib" in original_linker_args: + dxe3gen_command.append("-lgcc") + +os.environ["DXE_LD_LIBRARY_PATH"] = "dontcare" +os.environ["DJDIR"] = "dontcare" + +process_result = subprocess.run(dxe3gen_command) + +raise SystemExit(process_result.returncode) diff --git a/build-scripts/djgpp-platform-overrides.cmake b/build-scripts/djgpp-platform-overrides.cmake index c919f843f5..933f843285 100644 --- a/build-scripts/djgpp-platform-overrides.cmake +++ b/build-scripts/djgpp-platform-overrides.cmake @@ -11,7 +11,12 @@ set(CMAKE_STATIC_LIBRARY_PREFIX "lib") set(CMAKE_STATIC_LIBRARY_SUFFIX ".a") +set(CMAKE_SHARED_LIBRARY_PREFIX "") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".dxe") +set(CMAKE_IMPORT_LIBRARY_PREFIX "lib") +set(CMAKE_IMPORT_LIBRARY_SUFFIX ".dxe.a") + set(CMAKE_LINK_LIBRARY_SUFFIX "") set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "") set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib") -set(CMAKE_EXECUTABLE_SUFFIX ".exe") \ No newline at end of file +set(CMAKE_EXECUTABLE_SUFFIX ".exe") diff --git a/build-scripts/i586-pc-msdosdjgpp.cmake b/build-scripts/i586-pc-msdosdjgpp.cmake index 8a4e765f14..61247494da 100644 --- a/build-scripts/i586-pc-msdosdjgpp.cmake +++ b/build-scripts/i586-pc-msdosdjgpp.cmake @@ -15,9 +15,9 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE "${DJGPP_PLATFORM_OVERRIDES}") set(CMAKE_STATIC_LIBRARY_PREFIX "lib") set(CMAKE_STATIC_LIBRARY_SUFFIX ".a") set(CMAKE_SHARED_LIBRARY_PREFIX "") -set(CMAKE_SHARED_LIBRARY_SUFFIX ".dll") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".dxe") set(CMAKE_IMPORT_LIBRARY_PREFIX "lib") -set(CMAKE_IMPORT_LIBRARY_SUFFIX ".a") +set(CMAKE_IMPORT_LIBRARY_SUFFIX ".dxe.a") set(CMAKE_EXECUTABLE_SUFFIX ".exe") set(CMAKE_LINK_LIBRARY_SUFFIX "") set(CMAKE_DL_LIBS "") @@ -67,6 +67,11 @@ endforeach() list(APPEND CMAKE_FIND_ROOT_PATH ${CC_ROOTS}) +set(DJGPP_LIBRARY_PATHS ) +foreach(__cc_root ${CC_ROOTS}) + list(APPEND DJGPP_LIBRARY_PATHS "${__cc_root}" "${__cc_root}/lib") +endforeach() + # search for programs in the host directories set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) @@ -79,4 +84,4 @@ if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}) endif() if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_PACKAGE}) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) -endif() \ No newline at end of file +endif() diff --git a/cmake/test/CMakeLists.txt b/cmake/test/CMakeLists.txt index ffaa1977a0..d276c61ab2 100644 --- a/cmake/test/CMakeLists.txt +++ b/cmake/test/CMakeLists.txt @@ -65,11 +65,13 @@ if(TEST_SHARED) ) endif() - add_library(sharedlib-shared SHARED main_lib.c) - target_link_libraries(sharedlib-shared PRIVATE SDL3::SDL3-shared) - generate_export_header(sharedlib-shared EXPORT_MACRO_NAME MYLIBRARY_EXPORT) - target_compile_definitions(sharedlib-shared PRIVATE "EXPORT_HEADER=\"${CMAKE_CURRENT_BINARY_DIR}/sharedlib-shared_export.h\"") - set_target_properties(sharedlib-shared PROPERTIES C_VISIBILITY_PRESET "hidden") + if(NOT DOS) + add_library(sharedlib-shared SHARED main_lib.c) + target_link_libraries(sharedlib-shared PRIVATE SDL3::SDL3-shared) + generate_export_header(sharedlib-shared EXPORT_MACRO_NAME MYLIBRARY_EXPORT) + target_compile_definitions(sharedlib-shared PRIVATE "EXPORT_HEADER=\"${CMAKE_CURRENT_BINARY_DIR}/sharedlib-shared_export.h\"") + set_target_properties(sharedlib-shared PROPERTIES C_VISIBILITY_PRESET "hidden") + endif() add_executable(cli-shared main_cli.c) target_link_libraries(cli-shared PRIVATE SDL3::SDL3-shared) @@ -96,7 +98,7 @@ if(TEST_STATIC) add_executable(gui-static WIN32 main_gui.c) target_link_libraries(gui-static PRIVATE SDL3::SDL3-static) - if(TEST_SHARED) + if(TEST_SHARED AND NOT DOS) # Assume SDL library has been built with `set(CMAKE_POSITION_INDEPENDENT_CODE ON)` add_library(sharedlib-static SHARED main_lib.c) target_link_libraries(sharedlib-static PRIVATE SDL3::SDL3-static) diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 64daa42c39..9ff872cf92 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -220,6 +220,7 @@ void reset_IOP() {} #elif defined(SDL_PLATFORM_DOS) + /* On DOS, SDL provides a main function that sets up memory page locking (code, data, stack are locked, future diff --git a/include/SDL3/SDL_main_impl.h b/include/SDL3/SDL_main_impl.h index bf5f583639..8ce498ee1a 100644 --- a/include/SDL3/SDL_main_impl.h +++ b/include/SDL3/SDL_main_impl.h @@ -148,4 +148,116 @@ #endif /* SDL_MAIN_HANDLED */ +#ifdef SDL_PLATFORM_DOS + + /* On DOS, the executable must export symbols for the SDL3 module. + */ + #define FOR_EACH_DXE_EXPORT(X) \ + X(__Exit) \ + X(___dj_huge_val) \ + X(___dj_stderr) \ + X(___dj_stdin) \ + X(___djgpp_base_address) \ + X(___djgpp_nearptr_enable) \ + X(___dpmi_free_physical_address_mapping) \ + X(___dpmi_int) \ + X(___dpmi_physical_address_mapping) \ + X(___dpmi_simulate_real_mode_procedure_retf) \ + X(___fpclassifyd) \ + X(___fpclassifyf) \ + X(__go32_dpmi_allocate_dos_memory) \ + X(__go32_dpmi_chain_protected_mode_interrupt_vector) \ + X(__go32_dpmi_free_dos_memory) \ + X(__go32_dpmi_get_protected_mode_interrupt_vector) \ + X(__go32_dpmi_lock_code) \ + X(__go32_dpmi_lock_data) \ + X(__go32_dpmi_set_protected_mode_interrupt_vector) \ + X(__go32_info_block) \ + X(_abort) \ + X(_atof) \ + X(_atoi) \ + X(_calloc) \ + X(_clearerr) \ + X(_close) \ + X(_closedir) \ + X(_delay) \ + X(_dlregsym) \ + X(_dosmemput) \ + X(_environ) \ + X(_errno) \ + X(_fclose) \ + X(_ferror) \ + X(_fflush) \ + X(_fgets) \ + X(_fileno) \ + X(_fopen) \ + X(_fprintf) \ + X(_fputs) \ + X(_fread) \ + X(_free) \ + X(_fseeko) \ + X(_fstat) \ + X(_ftello) \ + X(_fwrite) \ + X(_getcwd) \ + X(_getenv) \ + X(_gethostname) \ + X(_getpagesize) \ + X(_gettimeofday) \ + X(_gmtime_r) \ + X(_itoa) \ + X(_localtime_r) \ + X(_longjmp) \ + X(_lseek) \ + X(_malloc) \ + X(_memcmp) \ + X(_memcpy) \ + X(_memmove) \ + X(_mkdir) \ + X(_opendir) \ + X(_read) \ + X(_readdir) \ + X(_realloc) \ + X(_remove) \ + X(_rename) \ + X(_searchpath) \ + X(_setenv) \ + X(_setjmp) \ + X(_sigaction) \ + X(_stat) \ + X(_strchr) \ + X(_strcmp) \ + X(_strerror) \ + X(_strlcat) \ + X(_strlcpy) \ + X(_strlen) \ + X(_strncmp) \ + X(_strnlen) \ + X(_strpbrk) \ + X(_strrchr) \ + X(_strstr) \ + X(_strtod) \ + X(_strtok_r) \ + X(_strtol) \ + X(_strtoll) \ + X(_strtoul) \ + X(_strtoull) \ + X(_uclock) \ + X(_unsetenv) \ + X(_vsnprintf) \ + X(_vsscanf) \ + X(_write) + + #define EXTERN_ASM_SEMICOLON(V) extern_asm(V); + #include + FOR_EACH_DXE_EXPORT(EXTERN_ASM_SEMICOLON) + DXE_EXPORT_TABLE(sdl3_export_syms) + FOR_EACH_DXE_EXPORT(DXE_EXPORT_ASM) + DXE_EXPORT_END + static __attribute__((constructor)) void sdl3_export_syms_auto_register (void) + { + dlregsym (sdl3_export_syms); + } +#endif + #endif /* SDL_main_impl_h_ */ From bf43bbae31db89de32743b80e8a98cf2dd41ca85 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 26 Apr 2026 21:40:50 +0200 Subject: [PATCH 2/4] stdinc: excplicitly cast output of __builtin_bswap32 to Uint32 This fixes a compile warning when using the output of SDL_Swap32 directly using the SDL_PRIu32 macro in C90 mode: testplatform.c:158:17: warning: format '%lX' expects argument of type 'long unsigned int', but argument 3 has type 'unsigned int' [-Wformat=] 158 | SDL_Log("Value 32 = 0x%" SDL_PRIX32 ", swapped = 0x%" SDL_PRIX32, | --- include/SDL3/SDL_endian.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SDL3/SDL_endian.h b/include/SDL3/SDL_endian.h index 69f097edd8..7bb56740a5 100644 --- a/include/SDL3/SDL_endian.h +++ b/include/SDL3/SDL_endian.h @@ -286,7 +286,7 @@ SDL_FORCE_INLINE Uint16 SDL_Swap16(Uint16 x) /* Byte swap 32-bit integer. */ #ifndef SDL_WIKI_DOCUMENTATION_SECTION #if HAS_BUILTIN_BSWAP32 -#define SDL_Swap32(x) __builtin_bswap32(x) +#define SDL_Swap32(x) (Uint32)__builtin_bswap32(x) #elif (defined(_MSC_VER) && (_MSC_VER >= 1400)) && !defined(__ICL) #pragma intrinsic(_byteswap_ulong) #define SDL_Swap32(x) _byteswap_ulong(x) From d2117c4d03e93d2bfbcddd138c1d1f015aa7f17f Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 26 Apr 2026 21:42:22 +0200 Subject: [PATCH 3/4] SDL_test: build SDL3_test in C99 mode --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48b4fa36e1..cb59539de5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -542,6 +542,9 @@ if(SDL_TEST_LIBRARY) add_library(SDL3::SDL3_test ALIAS SDL3_test) SDL_AddCommonCompilerFlags(SDL3_test) target_compile_definitions(SDL3_test PRIVATE "$<$:DEBUG>") + if("c_std_99" IN_LIST CMAKE_C_COMPILE_FEATURES) + target_compile_features(SDL3_test PRIVATE c_std_99) + endif() endif() # Make sure SDL3::SDL3 always exists From 3f4334a92c5bfcccbf2e80a382823f34a7b20d62 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 26 Apr 2026 21:43:32 +0200 Subject: [PATCH 4/4] cmake: use in DOS tests --- test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b6e7b724bf..eb0da93e5e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -243,6 +243,7 @@ function(add_sdl_test_executable TARGET) if(AST_NAME83) set_property(TARGET ${TARGET} PROPERTY OUTPUT_NAME "${AST_NAME83}") endif() + target_compile_definitions(${TARGET} PRIVATE SDL_INCLUDE_INTTYPES_H) endif() if(OPENGL_FOUND)