Open huangqinjin opened 1 year ago
I wrote a bash script cmcldeps
for the same purpose of cmcldeps.exe
on Windows (shamelessly copied the implementation from https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.1/Source/cmcldeps.cxx). I put the source code here if someone also need it.
This script must be put next to the cmake executable, see https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.1/Source/cmSystemTools.cxx#L2611-2615.
Edit: There is a loophole invoking "cmcldeps LANG": cmake doesn't check the emptiness of the path, see https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.1/Source/cmNinjaTargetGenerator.cxx#L804-821. So Ninja actually treats LANG as the executable in the case cmake doesn't find cmcldeps. One can rename this script to LANG (or using symlink) and put it in PATH, if putting the script next to the cmake executable is not doable.
This CMakePresets.json
sets two more cmake variables compared to https://github.com/mstorsjo/msvc-wine/issues/62#issue-1667681085. The CMake minimum version could be lowered down.
CMAKE_NINJA_CMCLDEPS_RC=ON
: It only sets to ON
on Windows since CMake 3.23. https://gitlab.kitware.com/cmake/cmake/-/commit/d49e168e1b32ac7d3ee0a4a52791d28a52d8f615CMAKE_CL_SHOWINCLUDES_PREFIX="Note: including file: "
: Currently CMake successfully extracts the prefix only if paths begin with X:\
or ./
https://gitlab.kitware.com/cmake/cmake/-/blob/fae6e8c2cdb5ce6049439f4defd1367b507d1e4b/Modules/CMakeDetermineCompilerId.cmake#L1147Edit: Set "Note: including file: "
as the default prefix (for English MSVC) in cmcldeps
script, which is same as Ninja https://github.com/ninja-build/ninja/blob/v1.11.1/src/clparser.cc#L46, so now no need to set CMAKE_CL_SHOWINCLUDES_PREFIX
.
Edit 2: This is actually a regression in CMake 3.26.0 comfirmed by upstream https://gitlab.kitware.com/cmake/cmake/-/issues/24908. It will be fixed in 3.26.4, after that we do not need touch CMAKE_CL_SHOWINCLUDES_PREFIX
.
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 23,
"patch": 0
},
"configurePresets": [
{
"name": "msvc-wine",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_C_COMPILER": "/opt/msvc/bin/x64/cl.exe",
"CMAKE_CXX_COMPILER": "/opt/msvc/bin/x64/cl.exe",
"CMAKE_RC_COMPILER": "/opt/msvc/bin/x64/rc.exe",
"CMAKE_NINJA_CMCLDEPS_RC": "ON",
"CMAKE_SYSTEM_NAME": "Windows",
"CMAKE_SYSTEM_VERSION": "10",
"CMAKE_CROSSCOMPILING_EMULATOR": "env;WINEDEBUG=-all;wine64",
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
},
"condition": {
"string": "${hostSystemName}",
"type": "notInList",
"list" : [ "Windows" ]
}
}
]
}
#!/bin/bash
# This is a bash implementation of https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.1/Source/cmcldeps.cxx
# This script must be put next to the cmake executable, see
# https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.1/Source/cmSystemTools.cxx#L2611-2615.
# There is a loophole invoking "cmcldeps LANG": cmake doesn't check the emptiness of the path, see
# https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.1/Source/cmNinjaTargetGenerator.cxx#L804-821.
# So Ninja actually treats LANG as the executable in the case cmake doesn't find cmcldeps.
# One can rename this script to LANG (or using symlink) and put it in PATH, if putting the script next to
# the cmake executable is not doable.
usage() {
printf \
"ninja: FATAL: $1\n\nusage:\n"\
"cmcldeps "\
"<language C, CXX or RC> "\
"<source file path> "\
"<output path for *.d file> "\
"<output path for *.obj file> "\
"<prefix of /showIncludes> "\
"<path to cl.exe> "\
"<path to tool (cl or rc)> "\
"<rest of command ...>\n"\
>&2
exit 1
}
outputDepFile() {
local dfile=$1 ; shift
local objfile=$1 ; shift
if [[ -z $dfile ]]; then
return
fi
mapfile -t incs < <(printf '%s\n' "$@" | sort -u)
local cwd="$(pwd)/"
printf '%q \\\n' "$objfile:" "${incs[@]#$cwd}" >$dfile
}
process() {
local srcfilename=$1 ; shift
local dfile=$1 ; shift
local objfile=$1 ; shift
local prefix=$1 ; shift
local cmd=$1 ; shift
local dir=$1 ; shift
local quiet=${1:-false} ; shift
exec {fd}< <([[ -n $dir ]] && cd "$dir"; eval $cmd 2>&1)
local pid=$!
mapfile -t -u $fd
wait $pid
local exit_code=$?
exec {fd}<&-
# process the include directives and output everything else
local includes=()
local isFirstLine=true # cl prints always first the source filename
for inc in "${MAPFILE[@]}"; do
if [[ $inc =~ ^$prefix[[:blank:]]*(.*)$ ]]; then
includes+=("${BASH_REMATCH[1]}")
else
if ! $isFirstLine || [[ $inc != ${srcfilename}* ]]; then
if ! $quiet || [[ $exit_code -ne 0 ]]; then
printf '%s\n' "$inc"
fi
else
isFirstLine=false
fi
fi
done
# don't update .d until/unless we succeed compilation
if [[ $exit_code -eq 0 ]]; then
outputDepFile "$dfile" "$objfile" "${includes[@]}"
fi
return $exit_code
}
lang=$(basename "$0")
if [[ $lang == cmcldeps ]]; then
lang=$1; shift
fi
srcfile=$1; shift
dfile=$1; shift
objfile=$1; shift
prefix=$1; shift
cl=$1; shift
binpath=$1; shift
rest=("$@")
if [[ -z $binpath ]]; then
usage "Couldn't parse arguments."
fi
# https://github.com/ninja-build/ninja/blob/v1.11.1/src/clparser.cc#L46
if [[ -z $prefix ]]; then
prefix="Note: including file: "
fi
# needed to suppress filename output of msvc tools
srcfilename=$(basename "$srcfile")
if [[ $lang == C || $lang == CXX ]]; then
process "$srcfilename" "$dfile" "$objfile" "$prefix" "'$binpath' /nologo /showIncludes ${rest[*]@Q}"
elif [[ $lang == RC ]]; then
# "misuse" cl.exe to get headers from .rc files
clrest=()
for a in "${rest[@]}"; do
case $a in
[-/]fo | *$objfile) ;;
*) clrest+=("$a") ;;
esac
done
# call cl in object dir so the .i is generated there
objdir=$(dirname "$objfile")
# extract dependencies with cl.exe
process "$srcfilename" "$dfile" "$objfile" "$prefix" "'$cl' /P /DRC_INVOKED /TC /nologo /showIncludes ${clrest[*]@Q}" "$objdir" true
exit_code=$?
if [[ $exit_code -ne 0 ]]; then
exit $exit_code
fi
# compile rc file with rc.exe
process "$srcfilename" "" "$objfile" "$prefix" "'$binpath' ${rest[*]@Q}" "" true
else
usage "Invalid language specified."
fi
Nice hacks with reimplementing this in bash!
I took a look at the cmcldeps tool itself, and it doesn't seem all too reliant on Windows specific APIs, so I made an attempt at tweaking it so that it can be built and used on all platforms. See https://gitlab.kitware.com/mstorsjo/cmake/-/commits/cmcldeps. Note, these patches are entirely untested (I've only tested that it does compile), but if you're interested in progressing on this issue, you can have a look at this - hopefully it's not too far away from getting it to work.
Nice! Regarding rc: /fo x.dir\x.rc.res -> cl: /out:x.dir\x.rc.res.dep.obj
, I have reported https://gitlab.kitware.com/cmake/cmake/-/issues/24906, /fo x.dir\x.rc.res
should be removed when invoking cl.exe
. Hopefully this makes your work slightly simpler.
P.S. It would be better to merge cmcldeps
into cmake
finally (if cmcldeps
works on all OSes), just like vs_link_exe
and vs_link_dll
.
resource.h
: empty fileembed.rc
:#include "resource.h"
main.cpp
:int main() {}
CMakeLists.txt
:On Windows, touching
resource.h
and executeninja -d explain -d keepdepfile -v -j1
, RC file is correctly rebuilt.cmcldeps.exe
generates depfile forembed.rc
:But using msvc-wine on Linux, no depfile for
embed.rc
is generated. It is due toCMAKE_NINJA_CMCLDEPS_RC
is only defined on Windows. https://gitlab.kitware.com/cmake/cmake/-/blob/0b552eb877b887638e8130bb6c982106a76827d8/Modules/Platform/Windows-MSVC.cmake#L511Also see related https://github.com/mstorsjo/msvc-wine/issues/22.