Packaging with CMake CPack and NSIS on Windows
Tags: cmake, nsis, cpack, windows
Published: 2021-04-27 10:06:35

Assuming you have your base project(ABC) setup with CMake, let's get started. 

Download and Install `NSIS` from [here](https://nsis.sourceforge.io/Download). We'll need this to use CPack with NSIS.

Let's add an icon to our built application by creating a file called `abc.rc` which is a `resource file` used by MSVCC to apply icons, among other stuff.
```
IDI_ICON1               ICON        DISCARDABLE            "abc.ico"
```

Add this definition to our `add_executable` command
```cmake
add_executable(abc 
${ABC_SOURCE_DIR}/assets/abc.rc
${SOURCES} 
# other stuff
)
```

Next we need to define our install targets that CPack will pick up on
```cmake
install(TARGETS abc 
         #other stuff
         DESTINATION bin
    )
```
Here, bin is any folder you want to put your built application in. In this case, running the installer will install `abc.exe` into `C:\Program Files(x86)\abc\bin\abc.exe` .

Normally, you would set the install target's `DESTINATION` to `${CMAKE_INSTALL_PREFIX}`. However, CPack will give you an error :
`
ABSOLUTE path INSTALL DESTINATION forbidden (by caller):`

After what seemed like an eternity of google searching, I came across [a solution](https://cmake.org/pipermail/cmake/2013-May/054656.html)

> You are trying to install some files using an ABSOLUTE path DESTINATION.
99.999% of the time this is an error on the Windows platform.

> Normally you should be installing files to a RELATIVE path DESTINATION.


## Cpack Config
Additional stuff that you can configure
```cmake
    include(InstallRequiredSystemLibraries)
    set(CPACK_PACKAGE_INSTALL_DIRECTORY "
    set(CPACK_GENERATOR "NSIS")
    set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
    set(CPACK_PACKAGE_VERSION "${ABC_VERSION_MAJOR}.${ABC_VERSION_MINOR}")
    set(CPACK_PACKAGE_VERSION_PATCH "0")
    set(CPACK_PACKAGE_VENDOR "David Velho")
    set (CPACK_NSIS_MODIFY_PATH "ON")
    set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY ${PROJECT_NAME})
    set(CPACK_SOURCE_GENERATOR "TGZ")
    set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
    set(CPACK_SOURCE_IGNORE_FILES ${CPACK_IGNORE_FILES})
```
Some additional fields
```cmake
    set(CPACK_NSIS_INSTALLED_ICON_NAME "${ABC_SOURCE_DIR}/assets/abc.ico")
    set(CPACK_NSIS_HELP_LINK ${APP_URL})
    set(CPACK_NSIS_URL_INFO_ABOUT ${APP_URL})
    set(CPACK_NSIS_CONTACT ${APP_EMAIL})
 set(CPACK_NSIS_CREATE_ICONS_EXTRA
    "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\abc.lnk' '$INSTDIR\\\\bin\\\\abc.exe' "
    )
```

Let's give the generated installer an icon
```cmake
set(CPACK_NSIS_MUI_ICON "${ABC_SOURCE_DIR}/assets/abc.ico")
```

And last, the NSIS installer script that CMake will read from and substitute the values into

```cmake
set(CMAKE_MODULE_PATH  ${PROJECT_SOURCE_DIR}/assets/nsis ${CMAKE_MODULE_PATH})
```

Where `assets/nsis` has a file called `NSIS.definitions.nsh.in` which is a script file needed by NSIS. CMake will read this file and substitute the values in so that `NSIS` can read it

`NSIS.definitions.nsh.in` : 
```
!define VERSION "@APP_VERSION@"
!define APP_VERSION "@APP_VERSION@"
!define APP_NAME "@APP_NAME@"
!define EXE_NAME "@EXE_NAME@"
!define README_FILE "README"
!define LICENSE_FILE "@PROJECT_SOURCE_DIR@resourcestextLICENSE"
!define MUI_ICON "@PROJECT_SOURCE_DIR@resourcesgraphics@APP_LOW_NAME@.ico"
!define MUI_UNICON "@PROJECT_SOURCE_DIR@resourcesgraphics@APP_LOW_NAME@.ico"
!define MUI_WELCOMEFINISHPAGE_BITMAP "@PROJECT_SOURCE_DIR@resourcesgraphics@APP_LOW_NAME@-banner.bmp"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "@PROJECT_SOURCE_DIR@resourcesgraphics@APP_LOW_NAME@-banner.bmp"
!define PATCH  "0"
```

Let's include `CPack` and some NSIS config files
```cmake
    include(CPack)
    configure_file(
    ${PROJECT_SOURCE_DIR}/assets/nsis/NSIS.definitions.nsh.in
    ${CMAKE_CURRENT_BINARY_DIR}/assets/nsis/NSIS.definitions.nsh
    )
```

Assuming you've added `CMake` to `PATH` during installation, you should have `cpack` in path. You can (while in the build directory) build the installer with 
```bash
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
MSBuild.exe abc.sln
cpack -C Release
```

### Additional Resources
- [CPack NSIS](https://cmake.org/cmake/help/latest/cpack_gen/nsis.html)
- [CPack Start Menu Link](https://crascit.com/2015/08/07/cmake_cpack_nsis_shortcuts_with_parameters/)
- [CMake CPack](https://embeddeduse.com/2020/03/21/creating-simple-installers-with-cpack/)
- [CMake tutorial](https://foonathan.net/2016/07/cmake-dependency-handling/)
- [CMake NSIS](https://martinrotter.github.io/it-programming/2014/05/09/integrating-nsis-cmake/)
- [Absolute Path CPack error fix](https://cmake.org/pipermail/cmake/2013-May/054656.html)