Skip to content

Commit 39a5596

Browse files
Add CMake build system (with Windows support) (#253)
This adds a cross-platform CMake build system, which supports basic building and testing on Linux, macOS and Windows. (It does not yet have parity on Linux/macOS debug builds, sanitizer builds, or running test harnesses, but those could be added in the future with CMAKE_BUILD_TYPE conditionals and CTest. It also doesn't cover the install step yet.) I also rewrote the bash test harnesses in Python, as a cross-platform substitute, and fixed a couple portability issues with the test programs. * Added CMake projects. * Added Python versions of shell scripts. * Moved Windows compatibility defines to common.h, so common.c would see them. (Prior to this, read_cb was checking `errno` rather than `WSAGetLastError()`, failing to properly handle `WSAEWOULDBLOCK`, and reporting an EOF too early. * Added a forward declaration of `ws_strerror()`. Without this declaration, the compiler would assume it returned an `int` rather than a pointer, which resulted in the pointer getting truncated, and a later `fprintf` call performing a wild read. (In retrospect, this was foretold in #235) * Added CMake CI jobs on Windows, macOS, and Linux. Fixes #192, fixes #121
1 parent 96cb4ad commit 39a5596

12 files changed

+310
-139
lines changed

.github/workflows/test.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,36 @@ jobs:
6363
- run: rm "C:\msys64\usr\bin\link.exe"
6464
- run: make -f Makefile.Windows
6565

66+
test-windows-cmake-debug:
67+
name: Windows CMake, Debug configuration
68+
runs-on: windows-latest
69+
steps:
70+
- uses: actions/checkout@v2
71+
with:
72+
persist-credentials: false
73+
- name: Configure CMake
74+
run: cmake -S . -B build
75+
- name: Build, debug configuration
76+
run: cmake --build build --config Debug
77+
- name: Client-server test, debug
78+
run: python tests/client-server.py build/tests/Debug/client.exe build/tests/Debug/server.exe
79+
- name: Check static library list
80+
run: python tests/verify-static-libraries.py
81+
82+
test-windows-cmake-release:
83+
name: Windows CMake, Release configuration
84+
runs-on: windows-latest
85+
steps:
86+
- uses: actions/checkout@v2
87+
with:
88+
persist-credentials: false
89+
- name: Configure CMake
90+
run: cmake -S . -B build
91+
- name: Build, release configuration
92+
run: cmake --build build --config Release
93+
- name: Client-server test, release
94+
run: python tests/client-server.py build/tests/Release/client.exe build/tests/Release/server.exe
95+
6696
ensure-header-updated:
6797
runs-on: ubuntu-latest
6898
steps:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
target
22
Cargo.lock
3+
/build

CMakeLists.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
3+
project(rustls-ffi)
4+
add_subdirectory(tests)
5+
6+
include(ExternalProject)
7+
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
8+
9+
ExternalProject_Add(
10+
rustls-ffi
11+
DOWNLOAD_COMMAND ""
12+
CONFIGURE_COMMAND ""
13+
BUILD_COMMAND cbindgen --lang C --output "${CMAKE_SOURCE_DIR}/src/rustls.h" "${CMAKE_SOURCE_DIR}"
14+
COMMAND cargo build "$<IF:$<CONFIG:Release>,--release,-->"
15+
# Rely on cargo checking timestamps, rather than tell CMake where every
16+
# output is.
17+
BUILD_ALWAYS true
18+
INSTALL_COMMAND ""
19+
# Run cargo test with --quiet because msbuild will treat the presence
20+
# of "error" in stdout as an error, and we have some test functions that
21+
# end in "_error". Quiet mode suppresses test names, so this is a
22+
# sufficient workaround.
23+
TEST_COMMAND cargo test "$<IF:$<CONFIG:Release>,--release,-->" --quiet
24+
)

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ endif
2323
all: target/client target/server
2424

2525
test: all test-rust
26-
./tests/verify-static-libraries.sh
27-
./tests/client-server.sh
26+
./tests/verify-static-libraries.py
27+
./tests/client-server.py ./target/client ./target/server
2828

2929
test-rust:
3030
cargo test

tests/CMakeLists.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
IF(WIN32)
2+
add_compile_definitions(
3+
_WIN32_WINNT=0x601
4+
_CRT_SECURE_NO_WARNINGS
5+
_CRT_NONSTDC_NO_WARNINGS
6+
ssize_t=int
7+
)
8+
ENDIF(WIN32)
9+
10+
add_executable(client client.c common.c)
11+
add_dependencies(client rustls-ffi)
12+
target_include_directories(client PUBLIC ${CMAKE_SOURCE_DIR}/src)
13+
IF(WIN32)
14+
target_link_libraries(
15+
client
16+
debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib"
17+
optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib"
18+
advapi32.lib cfgmgr32.lib credui.lib gdi32.lib kernel32.lib msimg32.lib opengl32.lib secur32.lib user32.lib winspool.lib kernel32.lib ws2_32.lib bcrypt.lib advapi32.lib userenv.lib kernel32.lib msvcrt.lib
19+
)
20+
set_property(TARGET client PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
21+
ENDIF(WIN32)
22+
IF(UNIX)
23+
# TODO
24+
ENDIF(UNIX)
25+
IF(APPLE)
26+
# TODO
27+
ENDIF(APPLE)
28+
29+
add_executable(server server.c common.c)
30+
add_dependencies(server rustls-ffi)
31+
target_include_directories(server PUBLIC ${CMAKE_SOURCE_DIR}/src)
32+
IF(WIN32)
33+
target_link_libraries(
34+
server
35+
debug "${CMAKE_SOURCE_DIR}/target/debug/rustls_ffi.lib"
36+
optimized "${CMAKE_SOURCE_DIR}/target/release/rustls_ffi.lib"
37+
advapi32.lib cfgmgr32.lib credui.lib gdi32.lib kernel32.lib msimg32.lib opengl32.lib secur32.lib user32.lib winspool.lib kernel32.lib ws2_32.lib bcrypt.lib advapi32.lib userenv.lib kernel32.lib msvcrt.lib
38+
)
39+
set_property(TARGET server PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
40+
ENDIF(WIN32)
41+
IF(UNIX)
42+
# TODO
43+
ENDIF(UNIX)
44+
IF(APPLE)
45+
# TODO
46+
ENDIF(APPLE)

tests/client-server.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env python3
2+
import atexit
3+
import os
4+
import socket
5+
import subprocess
6+
import sys
7+
import time
8+
9+
MAX_TRIES = 24
10+
HOST = "localhost"
11+
PORT = 8443
12+
13+
14+
def port_is_open(host, port):
15+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
16+
errno = s.connect_ex((host, port))
17+
if errno == 0:
18+
s.close()
19+
return True
20+
else:
21+
return False
22+
23+
24+
def wait_tcp_port(host, port):
25+
for _ in range(MAX_TRIES):
26+
if port_is_open(host, port):
27+
break
28+
else:
29+
print("{} - still trying to connect to {}:{}"
30+
.format(time.strftime("%c"), host, port))
31+
time.sleep(0.5)
32+
else:
33+
print("unable to connect")
34+
sys.exit(1)
35+
print("Connected to {}:{}".format(host, port))
36+
37+
38+
def run_with_maybe_valgrind(args, env, valgrind):
39+
if valgrind is not None:
40+
args = [valgrind] + args
41+
process_env = os.environ.copy()
42+
process_env.update(env)
43+
subprocess.check_call(args, env=process_env, stdout=subprocess.DEVNULL)
44+
45+
46+
def run_client_tests(client, valgrind):
47+
run_with_maybe_valgrind(
48+
[
49+
client,
50+
HOST,
51+
str(PORT),
52+
"/"
53+
],
54+
{
55+
"CA_FILE": "minica.pem"
56+
},
57+
valgrind
58+
)
59+
run_with_maybe_valgrind(
60+
[
61+
client,
62+
HOST,
63+
str(PORT),
64+
"/"
65+
],
66+
{
67+
"NO_CHECK_CERTIFICATE": ""
68+
},
69+
valgrind
70+
)
71+
run_with_maybe_valgrind(
72+
[
73+
client,
74+
HOST,
75+
str(PORT),
76+
"/"
77+
],
78+
{
79+
"CA_FILE": "minica.pem",
80+
"VECTORED_IO": ""
81+
},
82+
valgrind
83+
)
84+
85+
86+
def run_server(server, valgrind, env):
87+
args = [
88+
server,
89+
"localhost/cert.pem",
90+
"localhost/key.pem"
91+
]
92+
if valgrind is not None:
93+
args = [valgrind] + args
94+
process_env = os.environ.copy()
95+
process_env.update(env)
96+
server = subprocess.Popen(args, env=process_env)
97+
98+
atexit.register(server.kill)
99+
100+
return server
101+
102+
103+
def main():
104+
valgrind = os.getenv("VALGRIND")
105+
if len(sys.argv) != 3:
106+
if sys.platform.startswith("win32"):
107+
print("Usage: client-server.py client.exe server.exe")
108+
else:
109+
print("Usage: python3 client-server.py ./client ./server")
110+
sys.exit(1)
111+
client = sys.argv[1]
112+
server = sys.argv[2]
113+
114+
if port_is_open(HOST, PORT):
115+
print("Cannot run tests; something is already listening on port {}"
116+
.format(PORT))
117+
sys.exit(1)
118+
119+
server_popen = run_server(server, valgrind, {})
120+
wait_tcp_port(HOST, PORT)
121+
run_client_tests(client, valgrind)
122+
server_popen.kill()
123+
server_popen.wait()
124+
125+
run_server(server, valgrind, {
126+
"VECTORED_IO": ""
127+
})
128+
wait_tcp_port(HOST, PORT)
129+
run_client_tests(client, valgrind)
130+
131+
132+
if __name__ == "__main__":
133+
main()

tests/client-server.sh

Lines changed: 0 additions & 66 deletions
This file was deleted.

tests/client.c

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,6 @@
2121
#include <stdlib.h>
2222
#include <errno.h>
2323

24-
#ifdef _WIN32
25-
#define sleep(s) Sleep(1000 * (s))
26-
#define read(s, buf, n) recv(s, buf, n, 0)
27-
#define close(s) closesocket(s)
28-
#define bzero(buf, n) memset(buf, '\0', n)
29-
30-
/* Hacks for 'errno' stuff
31-
*/
32-
#undef EAGAIN
33-
#define EAGAIN WSAEWOULDBLOCK
34-
#undef EWOULDBLOCK
35-
#define EWOULDBLOCK WSAEWOULDBLOCK
36-
#undef errno
37-
#define errno WSAGetLastError()
38-
#define perror(str) fprintf(stderr, str ": %d.\n", WSAGetLastError())
39-
#define strerror(e) ws_strerror(e)
40-
#ifndef STDOUT_FILENO
41-
#define STDOUT_FILENO 1 /* MinGW has this */
42-
#endif
43-
#endif
44-
4524
/* rustls.h is autogenerated in the Makefile using cbindgen. */
4625
#include "rustls.h"
4726
#include "common.h"

tests/common.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
#ifndef COMMON_H
22
#define COMMON_H
33

4+
#ifdef _WIN32
5+
#define sleep(s) Sleep(1000 * (s))
6+
#define read(s, buf, n) recv(s, buf, n, 0)
7+
#define close(s) closesocket(s)
8+
#define bzero(buf, n) memset(buf, '\0', n)
9+
10+
/* Hacks for 'errno' stuff
11+
*/
12+
#undef EAGAIN
13+
#define EAGAIN WSAEWOULDBLOCK
14+
#undef EWOULDBLOCK
15+
#define EWOULDBLOCK WSAEWOULDBLOCK
16+
#undef errno
17+
#define errno WSAGetLastError()
18+
#define perror(str) fprintf(stderr, str ": %d.\n", WSAGetLastError())
19+
const char * ws_strerror(int err);
20+
#define strerror(e) ws_strerror(e)
21+
#ifndef STDOUT_FILENO
22+
#define STDOUT_FILENO 1 /* MinGW has this */
23+
#endif /* !STDOUT_FILENO */
24+
#endif /* _WIN32 */
25+
426
enum crustls_demo_result
527
{
628
CRUSTLS_DEMO_OK,

0 commit comments

Comments
 (0)