Skip to content

Commit

Permalink
build: add Windows CE / CeGCC support, with CI jobs
Browse files Browse the repository at this point in the history
Make it possible to build curl for Windows CE using the CeGCC toolchain.
With both CMake and autotools, including tests and examples, also in CI.
The build configuration is the default one with Schannel enabled. No
3rd-party dependencies have been tested.

Also revive old code to make Schannel build with Windows CE, including
certificate verification.

Builds have been throughougly tested. But, I've made no functional tests
for this PR. Some parts (esp. file operations, like truncate and seek)
are stubbed out and likely broken as a result. Test servers build, but
they do not work on Windows CE. This patch substitutes `fstat()` calls
with `stat()`, which operate on filenames, not file handles. This may or
may not work and/or may not be secure.

About CeGCC: I used the latest available macOS binary build v0.59.1
r1397 from 2009, in native `mingw32ce` build mode. CeGCC is in effect
MinGW + GCC 4.4.0 + old/classic-mingw Windows headers. It targets
Windows CE v3.0 according to its `_WIN32_WCE` value. It means this PR
restores portions of old/classic-mingw support. It makes the Windows CE
codepath compatible with GCC 4.4.0. It also adds workaround for CMake,
which cannot identify and configure this toolchain out of the box.

Notes:
- CMake doesn't recognize CeGCC/mingw32ce, necessitating tricks as seen
  with Amiga and MS-DOS.
- CMake doesn't set `MINGW` for mingw32ce. Set it and `MINGW32CE`
  manually as a helper variable, in addition to `WINCE` which CMake sets
  based on `CMAKE_SYSTEM_NAME`.
- CMake fails to create an implib for `libcurl.dll`, due to not
  recognizing the platform as a Windowsy one. This patch adds the
  necessary workaround to make it work.
- headers shipping with CeGCC miss some things curl needs for Schannel
  support. Fixed by restoring and renovating code previously deleted
  old-mingw code.
- it's sometime non-trivial to figure out if a fallout is WinCE,
  mingw32ce, old-mingw, or GCC version-specific.
- WinCE is always Unicode. With exceptions: no `wmain`,
  `GetProcAddress()`.
- `_fileno()` is said to convert from `FILE *` to `void *` which is
  a Win32 file `HANDLE`. (This patch doesn't use this, but with further
  effort it probably could be.)
  https://stackoverflow.com/questions/3989545/how-do-i-get-the-file-handle-from-the-fopen-file-structure
- WinCE has no signals, current directory, stdio/CRT file handles, no
  `_get_osfhandle()`, no `errno`, no `errno.h`. Some of this stuff is
  standard C89, yet missing from this platform. Microsoft expects
  Windows CE apps to use Win32 file API and `FILE *` exclusively.
- revived CeGCC here (not tested for this PR):
  https://building.enlyze.com/posts/a-new-windows-ce-x86-compiler-in-2024/

On `UNDER_CE` vs. `_WIN32_WCE`: (This patch settled on `UNDER_CE`)

- A custom VS2008 WinCE toolchain does not set any of these.
  The compiler binaries don't contain these strings, and has no compiler
  option for targeting WinCE, hinting that a vanilla toolchain isn't
  setting any of them either.
- `UNDER_CE` is automatically defined by the CeGCC compiler.
  https://cegcc.sourceforge.net/docs/details.html
- `UNDER_CE` is similar to `_WIN32`, except it's not set automatically
  by all compilers. It's not supposed to have any value, like a version.
  (Though e.g. OpenSSL sets it to a version)
- `_WIN32_WCE` is the CE counterpart of the non-CE `_WIN32_WINNT` macro.
  That does return the targeted Windows CE version.
- `_WIN32_WCE` is not defined by compilers, and relies on a header
  setting it to a default, or the build to set it to the desired target
  version. This is also how `_WIN32_WINNT` works.
- `_WIN32_WCE` default is set by `windef.h` in CeGCC.
- `_WIN32_WCE` isn't set to a default by MSVC Windows CE headers (the
  ones I checked at least).
- CMake sets `_WIN32_WCE=<ver>`, `UNDER_CE`, `WINCE` for MSVC WinCE.
- `_WIN32_WCE` seems more popular in other projects, including CeGCC
  itself. `zlib` is a notable exception amongst curl dependencies,
  which uses `UNDER_CE`.
- Since `_WIN32_WCE` needs "certain" headers to have it defined, it's
  undefined depending on headers included beforehand.
- `curl/curl.h` re-uses `_WIN32_WCE`'s as a self-guard, relying on
  its not-(necessarily)-defined-by-default property:
  https://github.com/curl/curl/blob/25b445e4796bcbf9f842de686a8c384b30f6c2a2/include/curl/curl.h#L77

Toolchain downloads:
- Windows:
  https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_cygwin1.7_r1399.tar.bz2
- macOS Intel:
  https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_snowleopard_r1397.tar.bz2

Closes curl#15975
  • Loading branch information
vszakats committed Feb 21, 2025
1 parent 9395849 commit 2a292c3
Show file tree
Hide file tree
Showing 89 changed files with 943 additions and 237 deletions.
105 changes: 105 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,111 @@ jobs:
make -C bld -j5 examples
fi
wince:
name: "mingw32ce, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} 4.4.0-arm schannel"
runs-on: 'macos-latest'
timeout-minutes: 10
env:
toolchain-version: '0.59.1'
strategy:
matrix:
build: [autotools, cmake]
fail-fast: false
steps:
- name: 'install packages'
if: ${{ matrix.build == 'autotools' }}
run: |
echo automake libtool | xargs -Ix -n1 echo brew '"x"' > /tmp/Brewfile
while [[ $? == 0 ]]; do for i in 1 2 3; do brew update && brew bundle install --no-lock --file /tmp/Brewfile && break 2 || { echo Error: wait to try again; sleep 10; } done; false Too many retries; done
- name: 'cache compiler (mingw32ce)'
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4
id: cache-compiler
with:
path: ~/opt/mingw32ce
key: ${{ runner.os }}-mingw32ce-${{ env.toolchain-version }}-amd64

- name: 'install compiler (mingw32ce)'
if: ${{ steps.cache-compiler.outputs.cache-hit != 'true' }}
run: |
cd "${HOME}" || exit 1
curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 3 --retry-connrefused --proto-redir =https \
--location 'https://downloads.sourceforge.net/cegcc/cegcc/${{ env.toolchain-version }}/cegcc_mingw32ce_snowleopard_r1397.tar.bz2' | tar -x
ls -l
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: 'configure'
run: |
MINGW32CE_ROOT="${HOME}/opt/mingw32ce"
if [ '${{ matrix.build }}' = 'cmake' ]; then
cmake -B bld \
-DCMAKE_SYSTEM_NAME=WindowsCE \
-DCMAKE_SYSTEM_VERSION=8.0 \
-DCMAKE_SYSTEM_PROCESSOR=arm \
-DCMAKE_C_COMPILER_TARGET=arm-wince-mingw32ce \
-DCMAKE_C_COMPILER="${MINGW32CE_ROOT}/bin/arm-mingw32ce-gcc" \
-DCMAKE_RC_COMPILER="${MINGW32CE_ROOT}/bin/arm-mingw32ce-windres" \
-DMINGW32CE_LIBRARY_DIR="${MINGW32CE_ROOT}/arm-mingw32ce/lib" \
-DCMAKE_IGNORE_PREFIX_PATH="$(brew --prefix)" \
-DCMAKE_UNITY_BUILD=ON -DCURL_TEST_BUNDLES=ON \
-DBUILD_SHARED_LIBS=ON -DBUILD_STATIC_LIBS=ON -DBUILD_STATIC_CURL=OFF \
-DCURL_WERROR=ON \
-DCURL_USE_SCHANNEL=ON \
-DCURL_USE_LIBPSL=OFF
else
autoreconf -fi
mkdir bld && cd bld && ../configure --disable-dependency-tracking --enable-unity --enable-test-bundles --enable-warnings --enable-werror \
ac_cv_prog_cc_c99=no \
CC="${MINGW32CE_ROOT}/bin/arm-mingw32ce-gcc" \
AR="${MINGW32CE_ROOT}/bin/arm-mingw32ce-ar" \
RANLIB="${MINGW32CE_ROOT}/bin/arm-mingw32ce-ranlib" \
RC="${MINGW32CE_ROOT}/bin/arm-mingw32ce-windres" \
--host=arm-wince-mingw32ce \
--with-schannel \
--without-libpsl \
--disable-shared
fi
- name: 'configure log'
if: ${{ !cancelled() }}
run: cat bld/config.log bld/CMakeFiles/CMake*.yaml 2>/dev/null || true

- name: 'curl_config.h'
run: |
echo '::group::raw'; cat bld/lib/curl_config.h || true; echo '::endgroup::'
grep -F '#define' bld/lib/curl_config.h | sort || true
- name: 'build'
run: |
if [ '${{ matrix.build }}' = 'cmake' ]; then
cmake --build bld
else
make -j5 -C bld
fi
- name: 'curl info'
run: |
find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -exec file '{}' \;
- name: 'build tests'
if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time
run: |
if [ '${{ matrix.build }}' = 'cmake' ]; then
cmake --build bld --target testdeps
else
make -j5 -C bld -C tests
fi
- name: 'build examples'
if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time
run: |
if [ '${{ matrix.build }}' = 'cmake' ]; then
cmake --build bld --target curl-examples
else
make -j5 -C bld examples
fi
msvc:
name: 'msvc, CM ${{ matrix.arch }}-${{ matrix.plat }} ${{ matrix.name }}'
runs-on: windows-latest
Expand Down
2 changes: 1 addition & 1 deletion CMake/CurlSymbolHiding.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ if(WIN32 AND (ENABLE_DEBUG OR ENABLE_CURLDEBUG))
# e.g. curl_easy_perform_ev() or curl_dbg_*(),
# so disable symbol hiding for debug builds and for memory tracking.
set(CURL_HIDDEN_SYMBOLS OFF)
elseif(DOS OR AMIGA)
elseif(DOS OR AMIGA OR MINGW32CE)
set(CURL_HIDDEN_SYMBOLS OFF)
endif()

Expand Down
17 changes: 17 additions & 0 deletions CMake/win32-cache.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ if(MINGW)
set(HAVE_UTIME_H 1) # wrapper to sys/utime.h
set(HAVE_DIRENT_H 1)
set(HAVE_OPENDIR 1)
if(MINGW32CE)
set(HAVE_STRTOK_R 0)
set(HAVE_FILE_OFFSET_BITS 0)
endif()
else()
set(HAVE_LIBGEN_H 0)
set(HAVE_FTRUNCATE 0)
Expand Down Expand Up @@ -191,3 +195,16 @@ set(STDC_HEADERS 1)

set(HAVE_SIZEOF_SUSECONDS_T 0)
set(HAVE_SIZEOF_SA_FAMILY_T 0)

if(WINCE) # Windows CE exceptions
set(HAVE_LOCALE_H 0)
set(HAVE_GETADDRINFO 0)
set(HAVE_FREEADDRINFO 0)
set(HAVE_SETLOCALE 0)
set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 0)
set(HAVE_SIGNAL 0)
set(HAVE_SETMODE 0)
if(MINGW32CE)
set(HAVE__SETMODE 0)
endif()
endif()
92 changes: 72 additions & 20 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,33 @@ if(WINDOWS_STORE AND MINGW) # mingw UWP build
# CMake (as of v3.31.2) gets confused and applies the MSVC rc.exe command-line
# template to windres. Reset it to the windres template via 'Modules/Platform/Windows-windres.cmake':
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff <DEFINES> <INCLUDES> <FLAGS> <SOURCE> <OBJECT>")
elseif(WIN32 AND WINCE AND CMAKE_COMPILER_IS_GNUCC) # mingw32ce build
if(NOT MINGW32CE_LIBRARY_DIR)
message(FATAL_ERROR "Set MINGW32CE_LIBRARY_DIR variable to the mingw32ce platform library directory.")
endif()

set(MINGW 1)
set(MINGW32CE 1)

# Build implib with libcurl DLL. Copied from CMake's 'Modules/Platform/Windows-GNU.cmake'.
set(CMAKE_C_CREATE_SHARED_LIBRARY "<CMAKE_C_COMPILER> <CMAKE_SHARED_LIBRARY_C_FLAGS> <LANGUAGE_COMPILE_FLAGS> <LINK_FLAGS>")
string(APPEND CMAKE_C_CREATE_SHARED_LIBRARY " <CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS> -o <TARGET> -Wl,--out-implib,<TARGET_IMPLIB>")
string(APPEND CMAKE_C_CREATE_SHARED_LIBRARY " ${CMAKE_GNULD_IMAGE_VERSION} <OBJECTS> <LINK_LIBRARIES>")

# Build resources. Copied from CMake's 'Modules/Platform/Windows-windres.cmake'.
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff <DEFINES> <INCLUDES> <FLAGS> <SOURCE> <OBJECT>")
enable_language(RC)

set(CMAKE_C_COMPILE_OPTIONS_PIC "") # CMake sets it to '-fPIC', confusing the toolchain and breaking builds. Zap it.

set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
set(CMAKE_STATIC_LIBRARY_SUFFIX ".a")
set(CMAKE_SHARED_LIBRARY_PREFIX "lib")
set(CMAKE_SHARED_LIBRARY_SUFFIX ".dll")
set(CMAKE_IMPORT_LIBRARY_PREFIX "lib")
set(CMAKE_IMPORT_LIBRARY_SUFFIX ".dll.a")
set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll.a" ".a" ".lib")
elseif(DOS AND CMAKE_COMPILER_IS_GNUCC) # DJGPP
set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
set(CMAKE_STATIC_LIBRARY_SUFFIX ".a")
Expand Down Expand Up @@ -119,6 +146,9 @@ endif()
if(WIN32)
string(APPEND _target_flags " WIN32")
endif()
if(WINCE)
string(APPEND _target_flags " WINCE")
endif()
if(WINDOWS_STORE)
string(APPEND _target_flags " UWP")
endif()
Expand Down Expand Up @@ -195,12 +225,12 @@ if(WIN32)
endif()

option(ENABLE_UNICODE "Use the Unicode version of the Windows API functions" OFF)
if(WINDOWS_STORE)
if(WINDOWS_STORE OR WINCE)
set(ENABLE_UNICODE ON)
endif()
if(ENABLE_UNICODE)
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "UNICODE" "_UNICODE")
if(MINGW)
if(MINGW AND NOT MINGW32CE)
set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-municode")
endif()
endif()
Expand Down Expand Up @@ -476,7 +506,7 @@ if(HTTP_ONLY)
set(CURL_DISABLE_TFTP ON)
endif()

if(WINDOWS_STORE)
if(WINDOWS_STORE OR WINCE)
set(CURL_DISABLE_TELNET ON) # telnet code needs fixing to compile for UWP.
endif()

Expand Down Expand Up @@ -567,7 +597,19 @@ if(ENABLE_THREADED_RESOLVER)
endif()

# Check for all needed libraries
if(DOS)
if(WIN32)
if(WINCE)
set(_win32_winsock "ws2")
else()
set(_win32_winsock "ws2_32")
endif()
set(_win32_crypt32 "crypt32")

if(MINGW32CE) # FIXME upstream: must specify the full path to avoid CMake converting "ws2" to "ws2.lib"
set(_win32_winsock "${MINGW32CE_LIBRARY_DIR}/lib${_win32_winsock}.a")
set(_win32_crypt32 "${MINGW32CE_LIBRARY_DIR}/lib${_win32_crypt32}.a")
endif()
elseif(DOS)
if(WATT_ROOT)
set(USE_WATT32 ON)
# FIXME upstream: must specify the full path to avoid CMake converting "watt" to "watt.lib"
Expand All @@ -588,7 +630,7 @@ elseif(AMIGA)
set(CURL_USE_OPENSSL ON)
set(CURL_CA_FALLBACK ON CACHE BOOL "")
endif()
elseif(NOT WIN32 AND NOT APPLE)
elseif(NOT APPLE)
check_library_exists("socket" "connect" "" HAVE_LIBSOCKET)
if(HAVE_LIBSOCKET)
set(CURL_LIBS "socket" ${CURL_LIBS})
Expand Down Expand Up @@ -623,7 +665,7 @@ if(ENABLE_IPV6)
endif()
endif()
endif()
if(ENABLE_IPV6)
if(ENABLE_IPV6 AND NOT WINCE)
set(USE_IPV6 ON)
endif()

Expand Down Expand Up @@ -954,8 +996,8 @@ macro(curl_openssl_check_exists)
if(HAVE_LIBZ)
list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB)
endif()
if(WIN32)
list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32")
if(WIN32 AND NOT WINCE)
list(APPEND CMAKE_REQUIRED_LIBRARIES "${_win32_winsock}")
list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL
endif()
endif()
Expand All @@ -967,7 +1009,7 @@ macro(curl_openssl_check_exists)
list(APPEND CMAKE_REQUIRED_LIBRARIES ZLIB::ZLIB) # Public wolfSSL headers also require zlib headers
endif()
if(WIN32)
list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32" "crypt32")
list(APPEND CMAKE_REQUIRED_LIBRARIES "${_win32_winsock}" "${_win32_crypt32}")
endif()
list(APPEND CMAKE_REQUIRED_DEFINITIONS "-DHAVE_UINTPTR_T") # to pull in stdint.h (as of wolfSSL v5.5.4)
endif()
Expand Down Expand Up @@ -1174,7 +1216,7 @@ if(NOT CURL_DISABLE_SRP AND (HAVE_GNUTLS_SRP OR HAVE_OPENSSL_SRP))
endif()

if(NOT CURL_DISABLE_LDAP)
if(WIN32 AND NOT WINDOWS_STORE)
if(WIN32 AND NOT WINDOWS_STORE AND NOT WINCE)
option(USE_WIN32_LDAP "Use Windows LDAP implementation" ON)
if(USE_WIN32_LDAP)
list(APPEND CURL_LIBS "wldap32")
Expand Down Expand Up @@ -1460,7 +1502,7 @@ if(USE_LIBRTMP)
endif()

option(ENABLE_UNIX_SOCKETS "Enable Unix domain sockets support" ON)
if(ENABLE_UNIX_SOCKETS)
if(ENABLE_UNIX_SOCKETS AND NOT WINCE)
if(WIN32 OR DOS)
set(USE_UNIX_SOCKETS ON)
else()
Expand Down Expand Up @@ -1571,13 +1613,13 @@ if(WIN32)
list(APPEND CURL_INCLUDES "ws2tcpip.h")

if(HAVE_WIN32_WINNT)
if(HAVE_WIN32_WINNT LESS 0x0501)
if(HAVE_WIN32_WINNT LESS 0x0501 AND NOT WINCE)
# Windows XP is required for freeaddrinfo, getaddrinfo
message(FATAL_ERROR "Building for Windows XP or newer is required.")
endif()

# Pre-fill detection results based on target OS version
if(MINGW OR MSVC)
if(MINGW OR MSVC OR WINCE)
if(HAVE_WIN32_WINNT LESS 0x0600)
set(HAVE_INET_NTOP 0)
set(HAVE_INET_PTON 0)
Expand Down Expand Up @@ -1685,7 +1727,7 @@ endif()

# Apply to all feature checks
if(WIN32)
list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32")
list(APPEND CMAKE_REQUIRED_LIBRARIES "${_win32_winsock}")
elseif(HAVE_LIBSOCKET)
list(APPEND CMAKE_REQUIRED_LIBRARIES "socket")
elseif(DOS)
Expand Down Expand Up @@ -1737,7 +1779,6 @@ check_symbol_exists("getpeername" "${CURL_INCLUDES}" HAVE_GETPEERNAME) # wi
check_symbol_exists("getsockname" "${CURL_INCLUDES}" HAVE_GETSOCKNAME) # winsock2.h unistd.h proto/bsdsocket.h
check_function_exists("getrlimit" HAVE_GETRLIMIT)
check_function_exists("setlocale" HAVE_SETLOCALE)
check_function_exists("setmode" HAVE_SETMODE)
check_function_exists("setrlimit" HAVE_SETRLIMIT)

if(NOT WIN32)
Expand All @@ -1749,8 +1790,11 @@ if(NOT WIN32)
check_symbol_exists("strcmpi" "string.h" HAVE_STRCMPI)
endif()

if(WIN32 OR CYGWIN)
check_function_exists("_setmode" HAVE__SETMODE)
if(NOT MINGW32CE) # Avoid false detections
check_function_exists("setmode" HAVE_SETMODE)
if(WIN32 OR CYGWIN)
check_function_exists("_setmode" HAVE__SETMODE)
endif()
endif()

if(AMIGA)
Expand Down Expand Up @@ -1930,9 +1974,14 @@ include(CMake/OtherTests.cmake)
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H")

if(WIN32)
list(APPEND CURL_LIBS "ws2_32" "bcrypt")
list(APPEND CURL_LIBS "${_win32_winsock}")
if(NOT WINCE)
list(APPEND CURL_LIBS "bcrypt")
endif()

set(USE_WIN32_LARGE_FILES ON)
if(NOT WINCE)
set(USE_WIN32_LARGE_FILES ON)
endif()

# Use the manifest embedded in the Windows Resource
string(APPEND CMAKE_RC_FLAGS " -DCURL_EMBED_MANIFEST")
Expand All @@ -1944,7 +1993,10 @@ if(WIN32)

# Link required libraries for USE_WIN32_CRYPTO or USE_SCHANNEL
if(USE_WIN32_CRYPTO OR USE_SCHANNEL)
list(APPEND CURL_LIBS "advapi32" "crypt32")
if(NOT WINCE)
list(APPEND CURL_LIBS "advapi32")
endif()
list(APPEND CURL_LIBS "${_win32_crypt32}")
endif()
endif()

Expand Down
Loading

0 comments on commit 2a292c3

Please sign in to comment.