4

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)}
};
Gabe Ron
  • 329
  • 1
  • 12
  • "but now it couldn't find some exported symbols." - Probably, this symbol actually is not exported (or even not defined). Please, show (add to the question post) the definition of this symbol. – Tsyvarev Mar 12 '21 at 10:55
  • BTW, documentation for [WINDOWS_EXPORT_ALL_SYMBOLS](https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html) property has following note: "For global *data* symbols, `__declspec(dllimport)` must still be used when compiling against the code in the `.dll`". It seems that `eztp::dice` **global variable** is treated as of "global data" kind. – Tsyvarev Mar 12 '21 at 11:02
  • @Tsyvarev I've added the definition of the symbol that supposedly can't be found. If you want the implementation and all that, I have it on my git repo but don't wanna make the post super crowded as it already is. [LibEZTP](https://github.com/Macr0Nerd/libeztp) – Gabe Ron Mar 12 '21 at 11:03
  • No, additional information is not needed: you have added exactly those things as I asked for. – Tsyvarev Mar 12 '21 at 11:04
  • I haven't worked with the fetch content module, but to me the logical place to store external projects wouldn't be the temporary dir of your os. (Not sure, but there may be scenarios where the os simply removes files from there.) Instead creating a subdirectory inside the binary directory of your project for use would be the first thing I'd try. I'm not familiar with GitHub Actions, but another alternative could be simply adding the external dependencies as submodules to your repo and make sure they are checked out before running cmake which would allow you to place the code in your source dir – fabian Mar 13 '21 at 09:13

0 Answers0