I have an open source CMake project that runs perfectly well on Linux. However, to make it more portable, I decided to add in Windows build options. To test this, I set up the GitHub Actions CMake Based Applications, and set the conditions to build on ubuntu-latest
, windows-latest
, and macos-latest
.
My workflow file is as follows:
name: CMake
on: [push]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally
# well on Windows or Mac. You can convert this to a matrix build if you need
# cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- name: Create Build Environment
# Some projects don't allow in-source building, so create a separate build directory
# We'll use this as our working directory for all subsequent commands
run: cmake -E make_directory ${{github.workspace}}/build
- name: Configure CMake
# Use a bash shell so we can use the same syntax for environment variable
# access regardless of the host operating system
shell: bash
working-directory: ${{github.workspace}}/build
# Note the current convention is to use the -S and -B options here to specify source
# and build directories, but this is only available with CMake 3.13 and higher.
# The CMake binaries on the Github Actions machines are (as of this writing) 3.12
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
- name: Build
working-directory: ${{github.workspace}}/build
shell: bash
# Execute the build. You can specify a specific target with "--target <NAME>"
run: cmake --build . --config $BUILD_TYPE
- name: Test
working-directory: ${{github.workspace}}/build
shell: bash
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C $BUILD_TYPE
It's pretty standard and works fine. The problem arrives when I try to build on Windows, and only Windows. To add portability to Windows, I added this to my CMakeLists.txt
:
if(WIN32)
set(FETCHCONTENT_BASE_DIR "C:/Users/$ENV{USERNAME}/AppData/Local/Temp")
endif()
This worked perfectly to keep my fetched dependencies outside of the source directory (CMake would complain). But when I tried to build with Windows, the library would get built fine but couldn't be found to be linked with the testing suite (Catch2 + CTest).
Link:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29910\bin\HostX64\x64\link.exe /ERRORREPORT:QUEUE /OUT:"D:\a\libeztp\libeztp\build\tests\Release\tests.exe" /INCREMENTAL:NO /NOLOGO ..\Release\libeztp.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /PDB:"D:/a/libeztp/libeztp/build/tests/Release/tests.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"D:/a/libeztp/libeztp/build/tests/Release/tests.lib" /MACHINE:X64 /machine:x64 tests.dir\Release\mainTest.obj
LINK : fatal error LNK1181: cannot open input file '..\Release\libeztp.lib' [D:\a\libeztp\libeztp\build\tests\tests.vcxproj]
Done Building Project "D:\a\libeztp\libeztp\build\tests\tests.vcxproj" (default targets) -- FAILED.
Done Building Project "D:\a\libeztp\libeztp\build\ALL_BUILD.vcxproj" (default targets) -- FAILED.
Build FAILED
...
D:\a\libeztp\libeztp\build\ALL_BUILD.vcxproj" (default target) (1) ->
"D:\a\libeztp\libeztp\build\tests\tests.vcxproj" (default target) (5) ->
(Link target) ->
LINK : fatal error LNK1181: cannot open input file '..\Release\libeztp.lib' [D:\a\libeztp\libeztp\build\tests\tests.vcxproj]
I was pulling my hair out, and found this SO post that seemed to have a somewhat related problem. So to my CMakeLists.txt
, I changed the Windows if to:
if(WIN32)
set(FETCHCONTENT_BASE_DIR "C:/Users/$ENV{USERNAME}/AppData/Local/Temp")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) # I did try commenting out this line, but it was the same error from before.
set(BUILD_SHARED_LIBS TRUE)
endif()
Now the library was linking properly (or so it seems), but now it couldn't find some exported symbols.
Link:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29910\bin\HostX64\x64\link.exe /ERRORREPORT:QUEUE /OUT:"D:\a\libeztp\libeztp\build\tests\Release\tests.exe" /INCREMENTAL:NO /NOLOGO ..\Release\libeztp.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /PDB:"D:/a/libeztp/libeztp/build/tests/Release/tests.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"D:/a/libeztp/libeztp/build/tests/Release/tests.lib" /MACHINE:X64 /machine:x64 tests.dir\Release\mainTest.obj
mainTest.obj : error LNK2019: unresolved external symbol "class std::map<int,class eztp::Die,struct std::less<int>,class std::allocator<struct std::pair<int const ,class eztp::Die> > > eztp::dice" (?dice@eztp@@3V?$map@HVDie@eztp@@U?$less@H@std@@V?$allocator@U?$pair@$$CBHVDie@eztp@@@std@@@4@@std@@A) referenced in function "void __cdecl ____C_A_T_C_H____T_E_S_T____0(void)" (?____C_A_T_C_H____T_E_S_T____0@@YAXXZ) [D:\a\libeztp\libeztp\build\tests\tests.vcxproj]
D:\a\libeztp\libeztp\build\tests\Release\tests.exe : fatal error LNK1120: 1 unresolved externals [D:\a\libeztp\libeztp\build\tests\tests.vcxproj]
Done Building Project "D:\a\libeztp\libeztp\build\tests\tests.vcxproj" (default targets) -- FAILED.
Done Building Project "D:\a\libeztp\libeztp\build\ALL_BUILD.vcxproj" (default targets) -- FAILED.
...
"D:\a\libeztp\libeztp\build\ALL_BUILD.vcxproj" (default target) (1) ->
"D:\a\libeztp\libeztp\build\tests\tests.vcxproj" (default target) (5) ->
(Link target) ->
mainTest.obj : error LNK2019: unresolved external symbol "class std::map<int,class eztp::Die,struct std::less<int>,class std::allocator<struct std::pair<int const ,class eztp::Die> > > eztp::dice" (?dice@eztp@@3V?$map@HVDie@eztp@@U?$less@H@std@@V?$allocator@U?$pair@$$CBHVDie@eztp@@@std@@@4@@std@@A) referenced in function "void __cdecl ____C_A_T_C_H____T_E_S_T____0(void)" (?____C_A_T_C_H____T_E_S_T____0@@YAXXZ) [D:\a\libeztp\libeztp\build\tests\tests.vcxproj]
D:\a\libeztp\libeztp\build\tests\Release\tests.exe : fatal error LNK1120: 1 unresolved externals [D:\a\libeztp\libeztp\build\tests\tests.vcxproj]
At this point, I'm exhausted and just want this to be over, as I haven't plan on developing on Windows for quite some time. Could someone point me in the right direction and help me figure out what I'm missing? I'm just throwing stuff at a wall at this point and hoping something sticks.
Edit:
Here is the implementation of the exported symbol:
dice.hpp
namespace eztp {
class Die {
//Implementation probably irrelevant
};
extern std::map<int, Die> dice;
}
dice.cpp
std::map<int, eztp::Die> eztp::dice = {
{0, eztp::Die(0)},
{1, eztp::Die(1)},
{2, eztp::Die(2)},
{4, eztp::Die(4)},
{6, eztp::Die(6)},
{8, eztp::Die(8)},
{10, eztp::Die(10)},
{12, eztp::Die(12)},
{20, eztp::Die(20)},
{100, eztp::Die(100)}
};