Our cross-platform project has interoperable C++ and .NET code. However, the command line and IDE experience still only accounts for the C++ side.
Let’s start with the IDE. First, a bit of bad news — there doesn’t seem to be a good way to seamlessly combine the CMake native project and the .NET projects in a single Visual Studio instance. The “Open Folder” experience will detect the .csproj and .cs files and allow you to open and edit them. For build and test purposes, though, it will only use the info from CMakeLists.txt. But all is not lost — this is where SlnGen comes in.
SlnGen is a VS solution generator. Rather than hand-editing and maintaining files yourself, you can point slngen
at a *proj
file and it will automatically create a .sln
with all the project references transitively resolved. Due to some Win32 dependencies (registry, etc.), it won’t work on Linux, but neither does Visual Studio so we can live with that. Here is a helpful sln.cmd
script that will ensure the generated file goes into the ignored .vs
folder and sets up the right configuration:
@echo off setlocal pushd %~dp0 set ROOT_PATH=%CD% set BUILD_TYPE=%~1 if NOT DEFINED BUILD_TYPE set BUILD_TYPE=Debug if /i "%BUILD_TYPE%" == "Debug" goto :Gen if /i "%BUILD_TYPE%" == "Release" goto :Gen set ERR_MSG=Usage: %0 [build_type] set EXIT_CODE=1 goto :Quit :Gen slngen.exe -c %BUILD_TYPE% -d .vs dirs.proj set EXIT_CODE=%ERRORLEVEL% if NOT "%EXIT_CODE%" == "0" set ERR_MSG=slngen.exe failed. :Quit popd if NOT "%EXIT_CODE%" == "0" echo %ERR_MSG% exit /b %EXIT_CODE%
Simple! Now we have one-click VS integration for our .NET projects.
The final step is to ensure the existing command line build script can also invoke the dotnet
commands. Now that we have two different build steps, we can add some command line arguments to control the behavior (--no-cpp
to skip the C++ part and --no-cs
to skip the C# part). The main changes for build.cmd
are as follows:
:MakeCS if "%NO_CS%" == "1" goto :Quit cd "%ROOT_PATH%" set DOTNET_BUILD_ARGS=build -c %BUILD_TYPE% if "%VERBOSE%" == "1" set DOTNET_BUILD_ARGS=%DOTNET_BUILD_ARGS% -v d echo == dotnet.exe %DOTNET_BUILD_ARGS% dotnet.exe %DOTNET_BUILD_ARGS% set EXIT_CODE=%ERRORLEVEL% set ERR_MSG=dotnet build failed. if NOT "%EXIT_CODE%" == "0" goto :Quit if "%NO_TEST%" == "1" goto :Quit set DOTNET_TEST_ARGS=test -c %BUILD_TYPE% if "%VERBOSE%" == "1" set DOTNET_TEST_ARGS=%DOTNET_TEST_ARGS% -v d echo == dotnet.exe %DOTNET_TEST_ARGS% dotnet.exe %DOTNET_TEST_ARGS% set EXIT_CODE=%ERRORLEVEL% set ERR_MSG=dotnet test failed.
Given that lack of GOTO in Bash, the build.sh
changes require wrapping the C++ build steps in a function. Really, that’s an improvement though. The rest of the changes are pretty much equivalent to the Windows script:
[[ $NO_CS -eq 1 ]] && quit 'Done' 0 cd "${ROOT_PATH}" DOTNET_BUILD_ARGS="build -c ${BUILD_TYPE}" [[ $VERBOSE -eq 1 ]] && DOTNET_BUILD_ARGS="${DOTNET_BUILD_ARGS} -v d" echo == dotnet $DOTNET_BUILD_ARGS dotnet $DOTNET_BUILD_ARGS EXIT_CODE=$? [[ $EXIT_CODE -ne 0 ]] && quit 'dotnet build failed' $EXIT_CODE [[ $NO_TEST -eq 1 ]] && quit 'Done' 0 DOTNET_TEST_ARGS="test -c ${BUILD_TYPE}" [[ $VERBOSE -eq 1 ]] && DOTNET_TEST_ARGS="${DOTNET_TEST_ARGS} -v d" echo == dotnet $DOTNET_TEST_ARGS dotnet $DOTNET_TEST_ARGS EXIT_CODE=$? [[ $EXIT_CODE -ne 0 ]] && quit 'dotnet test failed' $EXIT_CODE
GitHub repo with all changes: CMakeSampleVS
This concludes our cross-platform code exploration. Hopefully this can serve as an example of how to provide a solid multi-OS development experience without accidental complexity at the very least.