Skip to content

Commit 9ed2333

Browse files
committed
feat: optimize compiler wrapper generation
1 parent 845d821 commit 9ed2333

1 file changed

Lines changed: 136 additions & 139 deletions

File tree

zig-toolchain.cmake

Lines changed: 136 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,50 @@
1+
# ==============================================================================
2+
# zig-toolchain.cmake v0.2.0
3+
#
4+
# Copyright (c) 2025 tayne3
5+
# Licensed under the MIT License.
6+
# ==============================================================================
17
include_guard(GLOBAL)
28

3-
set(ZIG_TOOLCHAIN_VERSION "0.2.0")
9+
option(ZIG_USE_CCACHE "Enable ccache optimization for Zig toolchain" OFF)
10+
set(ZIG_MCPU "" CACHE STRING "Target CPU (e.g. 'baseline', 'native', 'cortex_a53'). See: zig targets")
11+
set(ZIG_MCPU_FEATURES "" CACHE STRING "CPU feature modifiers appended directly to -mcpu=<cpu>, e.g. '+avx2-sse4_1'")
12+
set(ZIG_COMPILER_FLAGS "" CACHE STRING "Additional compilation flags")
13+
14+
if(ZIG_MCPU_FEATURES AND NOT ZIG_MCPU)
15+
message(WARNING "Zig Toolchain: ZIG_MCPU_FEATURES='${ZIG_MCPU_FEATURES}' is set but ZIG_MCPU is empty.")
16+
endif()
417

518
if(CMAKE_GENERATOR MATCHES "Visual Studio")
619
message(FATAL_ERROR "Zig Toolchain: Visual Studio generator is not supported. Please use '-G Ninja' or '-G MinGW Makefiles'.")
720
endif()
821

9-
find_program(ZIG_COMPILER zig)
10-
if(NOT ZIG_COMPILER)
22+
unset(_zig_compiler_exe CACHE)
23+
find_program(_zig_compiler_exe zig)
24+
if(NOT _zig_compiler_exe)
1125
message(FATAL_ERROR "Zig Toolchain: Zig compiler not found. Please install Zig and ensure it is in your PATH.")
1226
endif()
13-
string(REPLACE "\\" "\\\\" _ZIG_COMPILER_EXE "${ZIG_COMPILER}")
1427

1528
execute_process(
16-
COMMAND "${ZIG_COMPILER}" version
29+
COMMAND "${_zig_compiler_exe}" version
1730
OUTPUT_VARIABLE ZIG_COMPILER_VERSION
1831
OUTPUT_STRIP_TRAILING_WHITESPACE
19-
RESULT_VARIABLE ZIG_VERSION_RESULT
32+
RESULT_VARIABLE _zig_version_result
2033
)
21-
if(NOT ZIG_VERSION_RESULT EQUAL 0)
34+
if(NOT _zig_version_result EQUAL 0)
2235
message(FATAL_ERROR "Zig Toolchain: Zig compiler found but failed to get version.")
2336
endif()
2437

38+
unset(_zig_ccache_exe CACHE)
39+
if(ZIG_USE_CCACHE)
40+
find_program(_zig_ccache_exe ccache)
41+
if(_zig_ccache_exe)
42+
message(STATUS "Zig Toolchain: ccache enabled at ${_zig_ccache_exe}")
43+
else()
44+
message(WARNING "Zig Toolchain: ZIG_USE_CCACHE is ON but 'ccache' was not found in PATH.")
45+
endif()
46+
endif()
47+
2548
if(NOT ZIG_TARGET)
2649
if(NOT CMAKE_SYSTEM_NAME)
2750
set(CMAKE_SYSTEM_NAME "${CMAKE_HOST_SYSTEM_NAME}")
@@ -72,188 +95,162 @@ else()
7295
set(CMAKE_SYSTEM_NAME ${ZIG_OS})
7396
message(WARNING "Unknown OS: ${ZIG_OS}")
7497
endif()
75-
message(STATUS "Zig Toolchain: v${ZIG_COMPILER_VERSION}${ZIG_TARGET}")
7698

77-
option(ZIG_USE_CCACHE "Enable ccache optimization for Zig toolchain" OFF)
78-
if(ZIG_USE_CCACHE)
79-
find_program(ZIG_CCACHE_EXECUTABLE ccache)
80-
if(ZIG_CCACHE_EXECUTABLE)
81-
message(STATUS "Zig Toolchain: ccache enabled at ${ZIG_CCACHE_EXECUTABLE}")
82-
else()
83-
message(WARNING "Zig Toolchain: ZIG_USE_CCACHE is ON but 'ccache' was not found in PATH.")
84-
endif()
85-
endif()
86-
if(ZIG_USE_CCACHE AND ZIG_CCACHE_EXECUTABLE)
87-
string(REPLACE "\\" "\\\\" _ZIG_CCACHE_EXE "${ZIG_CCACHE_EXECUTABLE}")
88-
else()
89-
set(_ZIG_CCACHE_EXE "")
90-
endif()
91-
92-
set(ZIG_MCPU "" CACHE STRING "Target CPU (e.g. 'baseline', 'native', 'cortex_a53'). See: zig targets")
93-
set(ZIG_MCPU_FEATURES "" CACHE STRING "CPU feature modifiers appended directly to -mcpu=<cpu>, e.g. '+avx2-sse4_1'. Each feature must start with + or -.")
94-
set(ZIG_COMPILER_FLAGS "" CACHE STRING "Additional compilation flags")
99+
message(STATUS "Zig Toolchain: v${ZIG_COMPILER_VERSION}${ZIG_TARGET}")
95100

96-
if(ZIG_MCPU_FEATURES AND NOT ZIG_MCPU)
97-
message(WARNING "Zig Toolchain: ZIG_MCPU_FEATURES='${ZIG_MCPU_FEATURES}' is set but ZIG_MCPU is empty.")
98-
endif()
99-
set(_ZIG_EXTRA_FLAGS_LIST "")
101+
# Build base compilation flags list
102+
set(_zig_target_flags "-target" "${ZIG_TARGET}")
100103
if(ZIG_MCPU)
101-
list(APPEND _ZIG_EXTRA_FLAGS_LIST "-mcpu=${ZIG_MCPU}${ZIG_MCPU_FEATURES}")
104+
list(APPEND _zig_target_flags "-mcpu=${ZIG_MCPU}${ZIG_MCPU_FEATURES}")
102105
endif()
103106
if(ZIG_COMPILER_FLAGS)
104-
separate_arguments(_ZIG_COMPILER_FLAGS NATIVE_COMMAND "${ZIG_COMPILER_FLAGS}")
105-
list(APPEND _ZIG_EXTRA_FLAGS_LIST ${_ZIG_COMPILER_FLAGS})
107+
separate_arguments(_zig_extra_flags NATIVE_COMMAND "${ZIG_COMPILER_FLAGS}")
108+
list(APPEND _zig_target_flags ${_zig_extra_flags})
106109
endif()
107110

108-
set(_ZIG_COMPILER_INJECTED_FLAGS "-target" "${ZIG_TARGET}" ${_ZIG_EXTRA_FLAGS_LIST})
109-
list(LENGTH _ZIG_COMPILER_INJECTED_FLAGS _ZIG_INJECT_COUNT)
110-
set(_ZIG_INJECTED_C_CODE "")
111-
foreach(_arg IN LISTS _ZIG_COMPILER_INJECTED_FLAGS)
112-
string(REPLACE "\\" "\\\\" _arg_escaped "${_arg}")
113-
string(REPLACE "\"" "\\\"" _arg_escaped "${_arg_escaped}")
114-
string(APPEND _ZIG_INJECTED_C_CODE "\"${_arg_escaped}\", ")
115-
endforeach()
116-
117111
if(CMAKE_HOST_WIN32)
118-
set(_ZIG_WRAPPER_EXT ".exe")
119-
set(_ZIG_IS_WIN32 1)
120-
else()
121-
set(_ZIG_WRAPPER_EXT "")
122-
set(_ZIG_IS_WIN32 0)
123-
endif()
124-
125-
if(CMAKE_HOST_APPLE)
126-
set(_ZIG_SHIM_STRIP_FLAG "")
112+
set(_zig_wrapper_ext ".exe")
113+
set(_zig_host_win32 1)
127114
else()
128-
set(_ZIG_SHIM_STRIP_FLAG "-s")
115+
set(_zig_wrapper_ext "")
116+
set(_zig_host_win32 0)
129117
endif()
130118

131-
set(ZIG_SHIMS_DIR "${CMAKE_BINARY_DIR}/.zig-shims")
132-
file(MAKE_DIRECTORY "${ZIG_SHIMS_DIR}")
133-
134-
function(generate_shim_binary TOOL_NAME ZIG_SUBCOMMAND INJECT_FLAGS USE_CCACHE)
135-
set(WRAPPER_SOURCE "${ZIG_SHIMS_DIR}/${TOOL_NAME}.c")
136-
set(WRAPPER_EXE "${ZIG_SHIMS_DIR}/${TOOL_NAME}${_ZIG_WRAPPER_EXT}")
137-
set(WRAPPER_HASH "${ZIG_SHIMS_DIR}/${TOOL_NAME}.hash")
138-
139-
if(${INJECT_FLAGS})
140-
set(WRAPPER_INJECT_CODE "${_ZIG_INJECTED_C_CODE}")
141-
set(WRAPPER_INJECT_COUNT ${_ZIG_INJECT_COUNT})
142-
else()
143-
set(WRAPPER_INJECT_CODE "")
144-
set(WRAPPER_INJECT_COUNT 0)
145-
endif()
146-
147-
if(${USE_CCACHE} AND _ZIG_CCACHE_EXE)
148-
set(WRAPPER_USE_CCACHE 1)
149-
else()
150-
set(WRAPPER_USE_CCACHE 0)
151-
endif()
119+
set(_zig_shims_dir "${CMAKE_BINARY_DIR}/.zig-shims")
120+
file(MAKE_DIRECTORY "${_zig_shims_dir}")
152121

153-
set(C_CODE_CONTENT "
154-
#include <stddef.h>
122+
# Use Bracket Argument [=[ ... ]=] to avoid escaping hell
123+
set(_zig_shim_template [=[
124+
// @_zig_tool_name@ wrapper for Zig toolchain
155125
#include <stdio.h>
156126
#include <stdlib.h>
157-
#include <string.h>
158127
159-
#if ${_ZIG_IS_WIN32}
128+
#if @_zig_host_win32@
160129
#include <process.h>
161130
#else
162131
#include <unistd.h>
163132
#endif
164133
165-
#define ZIG_EXE \"${_ZIG_COMPILER_EXE}\"
166-
#define ZIG_CMD \"${ZIG_SUBCOMMAND}\"
167-
#define CCACHE_EXE \"${_ZIG_CCACHE_EXE}\"
168-
#define USE_CCACHE ${WRAPPER_USE_CCACHE}
169-
#define INJECT_COUNT ${WRAPPER_INJECT_COUNT}
170-
171134
int main(int argc, char** argv) {
172-
const char* ijlist[] = {${WRAPPER_INJECT_CODE} NULL};
173-
const int exargc = USE_CCACHE + 2 + INJECT_COUNT + (argc - 1);
174-
const char** exargv = (const char**)malloc(sizeof(char*) * (size_t)(exargc + 1));
175-
176-
if (!exargv) {
177-
fprintf(stderr, \"malloc failed\\n\");
178-
return 1;
179-
}
180-
135+
// User args (argc - 1) + NULL terminator (1) + Injected args count
136+
const char* exargv[argc + @_zig_extra_args_count@];
181137
const char **p = exargv;
182-
if (USE_CCACHE) { *p++ = CCACHE_EXE; }
183-
*p++ = ZIG_EXE;
184-
*p++ = ZIG_CMD;
185-
for (int i = 0; i < INJECT_COUNT; ++i) { *p++ = ijlist[i]; }
138+
@_zig_ccache_stmt@
139+
*p++ = "@_zig_compiler_exe_escaped@";
140+
*p++ = "@_zig_subcommand@";
141+
@_zig_flags_stmts@
186142
for (int i = 1; i < argc; ++i) { *p++ = argv[i]; }
187143
*p = NULL;
188144
189-
#if ${_ZIG_IS_WIN32}
190-
intptr_t ret = _spawnvp(_P_WAIT, exargv[0], (const char* const*)exargv);
191-
free(exargv);
192-
return (int)ret;
193-
#else
194-
execvp(exargv[0], (char* const*)exargv);
195-
perror(\"execvp failed\");
196-
free(exargv);
197-
return 1;
198-
#endif
199-
}")
200-
201-
string(MD5 _new_hash "${C_CODE_CONTENT}")
202-
if(EXISTS "${WRAPPER_HASH}" AND EXISTS "${WRAPPER_EXE}")
203-
file(READ "${WRAPPER_HASH}" _old_hash)
145+
#if @_zig_host_win32@
146+
return (int)_spawnvp(_P_WAIT, exargv[0], (const char* const*)exargv);
147+
#else
148+
execvp(exargv[0], (char* const*)exargv);
149+
perror("execvp failed");
150+
return 1;
151+
#endif
152+
}
153+
]=])
154+
155+
function(_zig_generate_shim tool_name subcommand inject_flags use_ccache)
156+
set(_zig_tool_name "${tool_name}")
157+
set(_zig_subcommand "${subcommand}")
158+
159+
# 2 injected args baseline: zig executable + subcommand
160+
set(_zig_extra_args_count 2)
161+
set(_zig_ccache_stmt "")
162+
set(_zig_flags_stmts "")
163+
164+
# Escape executable paths for C string literal
165+
string(REPLACE "\\" "\\\\" _zig_compiler_exe_escaped "${_zig_compiler_exe}")
166+
167+
# Process ccache injection
168+
if(use_ccache AND _zig_ccache_exe)
169+
string(REPLACE "\\" "\\\\" _zig_ccache_exe_escaped "${_zig_ccache_exe}")
170+
set(_zig_ccache_stmt " *p++ = \"${_zig_ccache_exe_escaped}\";")
171+
math(EXPR _zig_extra_args_count "${_zig_extra_args_count} + 1")
172+
endif()
173+
174+
# Process target flags injection
175+
if(inject_flags)
176+
foreach(_flag IN LISTS _zig_target_flags)
177+
string(REPLACE "\\" "\\\\" _flag_escaped "${_flag}")
178+
string(REPLACE "\"" "\\\"" _flag_escaped "${_flag_escaped}")
179+
string(APPEND _zig_flags_stmts " *p++ = \"${_flag_escaped}\";\n")
180+
math(EXPR _zig_extra_args_count "${_zig_extra_args_count} + 1")
181+
endforeach()
182+
endif()
183+
184+
# Render C template
185+
string(CONFIGURE "${_zig_shim_template}" _shim_content @ONLY)
186+
187+
set(_shim_src "${_zig_shims_dir}/${tool_name}.c")
188+
set(_shim_exe "${_zig_shims_dir}/${tool_name}${_zig_wrapper_ext}")
189+
set(_shim_hash "${_zig_shims_dir}/${tool_name}.hash")
190+
191+
# Avoid recompilation using MD5 hash check
192+
string(MD5 _new_hash "${_shim_content}")
193+
if(EXISTS "${_shim_hash}" AND EXISTS "${_shim_exe}")
194+
file(READ "${_shim_hash}" _old_hash)
204195
string(STRIP "${_old_hash}" _old_hash)
205196
if(_old_hash STREQUAL _new_hash)
206197
return()
207198
endif()
208199
endif()
209200

210-
file(WRITE "${WRAPPER_SOURCE}" "${C_CODE_CONTENT}")
211-
message(STATUS "Zig Toolchain: Compiling native wrapper for '${TOOL_NAME}'...")
201+
file(WRITE "${_shim_src}" "${_shim_content}")
202+
message(STATUS "Zig Toolchain: Compiling native wrapper for '${tool_name}'...")
212203

204+
if(CMAKE_HOST_APPLE)
205+
set(_shim_strip_flag "")
206+
else()
207+
set(_shim_strip_flag "-s")
208+
endif()
213209
execute_process(
214-
COMMAND "${ZIG_COMPILER}" cc -O2 ${_ZIG_SHIM_STRIP_FLAG} "${WRAPPER_SOURCE}" -o "${WRAPPER_EXE}"
215-
RESULT_VARIABLE COMPILE_RESULT
216-
ERROR_VARIABLE COMPILE_STDERR
210+
COMMAND "${_zig_compiler_exe}" cc -O2 ${_shim_strip_flag} "${_shim_src}" -o "${_shim_exe}"
211+
RESULT_VARIABLE _compile_result
212+
ERROR_VARIABLE _compile_stderr
217213
OUTPUT_QUIET
218214
)
219-
if(NOT COMPILE_RESULT EQUAL 0)
215+
if(NOT _compile_result EQUAL 0)
220216
message(FATAL_ERROR
221-
"Zig Toolchain: Failed to compile wrapper executable for '${TOOL_NAME}'.\n"
222-
"${COMPILE_STDERR}")
217+
"Zig Toolchain: Failed to compile wrapper executable for '${tool_name}'.\n"
218+
"${_compile_stderr}"
219+
)
223220
endif()
224221

225222
if(NOT CMAKE_HOST_WIN32)
226-
execute_process(COMMAND chmod +x "${WRAPPER_EXE}" OUTPUT_QUIET)
223+
execute_process(COMMAND chmod +x "${_shim_exe}" OUTPUT_QUIET)
227224
endif()
228225

229-
file(WRITE "${WRAPPER_HASH}" "${_new_hash}")
226+
file(WRITE "${_shim_hash}" "${_new_hash}")
230227
endfunction()
231228

232-
generate_shim_binary("zig-cc" "cc" TRUE TRUE)
233-
generate_shim_binary("zig-c++" "c++" TRUE TRUE)
234-
generate_shim_binary("zig-ar" "ar" FALSE FALSE)
235-
generate_shim_binary("zig-rc" "rc" FALSE FALSE)
236-
generate_shim_binary("zig-ranlib" "ranlib" FALSE FALSE)
237-
generate_shim_binary("zig-nm" "nm" FALSE FALSE)
238-
generate_shim_binary("zig-objcopy" "objcopy" FALSE FALSE)
239-
generate_shim_binary("zig-strip" "strip" FALSE FALSE)
240-
241-
set(CMAKE_C_COMPILER "${ZIG_SHIMS_DIR}/zig-cc${_ZIG_WRAPPER_EXT}")
242-
set(CMAKE_CXX_COMPILER "${ZIG_SHIMS_DIR}/zig-c++${_ZIG_WRAPPER_EXT}")
243-
set(CMAKE_AR "${ZIG_SHIMS_DIR}/zig-ar${_ZIG_WRAPPER_EXT}" CACHE FILEPATH "Archiver" FORCE)
244-
set(CMAKE_RANLIB "${ZIG_SHIMS_DIR}/zig-ranlib${_ZIG_WRAPPER_EXT}" CACHE FILEPATH "Ranlib" FORCE)
245-
set(CMAKE_NM "${ZIG_SHIMS_DIR}/zig-nm${_ZIG_WRAPPER_EXT}" CACHE FILEPATH "NM" FORCE)
246-
set(CMAKE_OBJCOPY "${ZIG_SHIMS_DIR}/zig-objcopy${_ZIG_WRAPPER_EXT}" CACHE FILEPATH "Objcopy" FORCE)
247-
set(CMAKE_STRIP "${ZIG_SHIMS_DIR}/zig-strip${_ZIG_WRAPPER_EXT}" CACHE FILEPATH "Strip" FORCE)
229+
_zig_generate_shim("zig-cc" "cc" TRUE TRUE)
230+
_zig_generate_shim("zig-c++" "c++" TRUE TRUE)
231+
_zig_generate_shim("zig-ar" "ar" FALSE FALSE)
232+
_zig_generate_shim("zig-rc" "rc" FALSE FALSE)
233+
_zig_generate_shim("zig-ranlib" "ranlib" FALSE FALSE)
234+
_zig_generate_shim("zig-nm" "nm" FALSE FALSE)
235+
_zig_generate_shim("zig-objcopy" "objcopy" FALSE FALSE)
236+
_zig_generate_shim("zig-strip" "strip" FALSE FALSE)
237+
238+
set(CMAKE_C_COMPILER "${_zig_shims_dir}/zig-cc${_zig_wrapper_ext}")
239+
set(CMAKE_CXX_COMPILER "${_zig_shims_dir}/zig-c++${_zig_wrapper_ext}")
240+
set(CMAKE_AR "${_zig_shims_dir}/zig-ar${_zig_wrapper_ext}" CACHE FILEPATH "Archiver" FORCE)
241+
set(CMAKE_RANLIB "${_zig_shims_dir}/zig-ranlib${_zig_wrapper_ext}" CACHE FILEPATH "Ranlib" FORCE)
242+
set(CMAKE_NM "${_zig_shims_dir}/zig-nm${_zig_wrapper_ext}" CACHE FILEPATH "NM" FORCE)
243+
set(CMAKE_OBJCOPY "${_zig_shims_dir}/zig-objcopy${_zig_wrapper_ext}" CACHE FILEPATH "Objcopy" FORCE)
244+
set(CMAKE_STRIP "${_zig_shims_dir}/zig-strip${_zig_wrapper_ext}" CACHE FILEPATH "Strip" FORCE)
248245

249246
if(CMAKE_HOST_WIN32)
250-
# unsupported linker arg: --dependency-file. see https://github.com/ziglang/zig/issues/22213
247+
# Unsupported linker arg: --dependency-file. See https://github.com/ziglang/zig/issues/22213
251248
set(CMAKE_C_LINKER_DEPFILE_SUPPORTED FALSE)
252249
set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED FALSE)
253250
endif()
254251

255252
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
256-
set(CMAKE_RC_COMPILER "${ZIG_SHIMS_DIR}/zig-rc${_ZIG_WRAPPER_EXT}" CACHE FILEPATH "Resource Compiler" FORCE)
253+
set(CMAKE_RC_COMPILER "${_zig_shims_dir}/zig-rc${_zig_wrapper_ext}" CACHE FILEPATH "Resource Compiler" FORCE)
257254
# Explicitly specify MSVC syntax because zig rc only supports this format
258255
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> /fo <OBJECT> <SOURCE>")
259256
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")

0 commit comments

Comments
 (0)