Lesson 5: Convert it to a CMake project (C++ optional lesson)

If we are going to provide our plugin with C++ on multiple platforms, we’ll need to build on both Mac and PC. But they are using different project format, if we are going to add/rename/delete project files, we’ll need to maintain two projects. It is not only annoying but also easy to create problems. CMake is one of the build system designed to solve this issue. In this tutorial, we’ll create a simple CMake project to generate build files on both Mac and Windows.

The sample code could be found here.

First, please make sure you have installed CMake. You can download it from here. If you are using MacOS, you can also install it with brew. There is a tutorial for CMake provided on CMake website. It provides more details for the CMake. If you have any questions when reading this tutorial, you can also use it as a reference. Now, let’s begin to convert our plugin into a CMake project.

Let’s copy required files. We’ll need source codes and resources(e.g., icons, .manifest, etc…). We’ll copy them into a new folder. It will have the following files and folders:

> resources > > AddInIcon.svg > > BasicAddinCPP.cpp > > BasicAddinCPP.manifest

A CMake project begins with CMakeLists.txt. Let’s create it in the folder we’ve just created and add some basic commands.

cmake_minimum_required(VERSION 3.22.1)
project(BasicAddinCPP VERSION 1.0)
add_library(${PROJECT_NAME} BasicAddinCPP.cpp)

The first two commands will specify the minimum CMake generator it requires, and set the project name. add_library will set the output for this project and the source files it needs.

To generate a make, you can simply use.

cmake .

CMake will generate a default building solution based on your platform. For windows, it will generate a Visual Studio solution; on Mac, it will generate a unix makefile. To keep the CMake project clean, we’ll usually put generated files into a different folder.

In this tutorial, we’ll create a folder called build. And we’ll run our cmake command inside the build folder like below:

cd build
cmake ..

To build on different platforms, we’ll need to specify different settings. Before that, we’ll add some settings for both platforms.

# Set library type to shared (DLL/dylib)
set(BUILD_SHARED_LIBS ON)

project(BasicAddinCPP VERSION 1.0)

# Set CXX Standard
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# If there wasn't a CMAKE_BUILD_TYPE is given, we'll make it debug.
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug)
endif()

# Enable Unicode support
add_compile_definitions(UNICODE _UNICODE)

By adding these commands, we tell CMake that we’ll build a DLL/dylib instead of static library, and the complier must support C++14. If we didn’t specify build type, it will generate a debug build file.

Now, we’ll begin with Windows first. To make the tutorial simpler, we’ll run our cmake … command in a VS x64 Native Tools command prompt. The CMake will generate a Visual Studio solution based on you environment by default.

When building a c++ program, we’ll need to provide path to include files and imported libraries. The paths and naming conventions are different on these two platforms. We can use following commands to check our targeting system and execute the commands we want.

# Check system
if(WIN32)
   #TODO: Add include path and libraries.
elseif(APPLE)
endif()

Add these to the end of your current CMakeLists.txt. If we are building on Windows, it will run the commands inside if(WIN32) block. If we are using mac, it will run the commands in the elseif(APPLE).

We can use target_include_directories, target_link_directories and target_link_libraries to set include path and required libraries for specified target. We can check the paths and libraries in the Visual Studio solution and copy them here. We’ll also use the set command to create variables for the paths.

# Windows settings.
set(FUSION_API_INCLUDE "$ENV{APPDATA}/Autodesk/Autodesk Fusion 360/API/CPP/include")
set(FUSION_API_LIB "$ENV{APPDATA}/Autodesk/Autodesk Fusion 360/API/CPP/lib")

target_include_directories(${PROJECT_NAME} PRIVATE ${FUSION_API_INCLUDE})

target_link_directories(${PROJECT_NAME} PRIVATE ${FUSION_API_LIB})

target_link_libraries(${PROJECT_NAME} PRIVATE
                        core.lib
                        fusion.lib
                        cam.lib
                        kernel32
                        user32
                        gdi32
                        winspool
                        comdlg32
                        advapi32
                        shell32
                        ole32
                        oleaut32
                        uuid
                        odbc32
                        odbccp32)

The paths for include/library on Windows are beginning with %APPDATA%. It is an environment variable, we can use $ENV{APPDATA} to get it. The path separator in CMake variables should always use / instead of \.

For the libraries, windows libraries like kernel32, we don’t have to provide the suffix for them. But for any other libraries, we’ll need to provide the full name. Since we don’t want to propagate our including to other libraries. We’ll use PRIVATE keyword here. It is cleaner, and it also avoids possible conflicts between libraries.

If you look at the VS project, we also need to add some compiling and linking options. Here they are:

target_compile_options(${PROJECT_NAME} PRIVATE
                            "-D_USRDLL;-DSIMPLE_EXPORTS")

target_link_options(${PROJECT_NAME} PRIVATE
                        "/SUBSYSTEM:WINDOWS")

We can generate the solution. Run cmake … command, and we’ll have some outputs like below:

D:\cases\My First Fusion Plug-in\cmake\build>cmake ..
-- Building for: Visual Studio 17 2022
-- Selecting Windows SDK version 10.0.22621.0 to target Windows 10.0.26100.
-- The C compiler identification is MSVC 19.43.34809.0
-- The CXX compiler identification is MSVC 19.43.34809.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Tools/MSVC/14.43.34808/bin/Hostx64/x64/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Tools/MSVC/14.43.34808/bin/Hostx64/x64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (3.0s)
-- Generating done (0.0s)
-- Build files have been written to: D:/cases/My First Fusion Plug-in/cmake/build

Now, we should be able to find generated solution in our current directory.

D:\cases\My First Fusion Plug-in\cmake\build>dir
 Volume in drive D is Work
 Volume Serial Number is CED2-EB1C

 Directory of D:\cases\My First Fusion Plug-in\cmake\build

06/09/2025  03:53 PM              .
05/30/2025  10:34 PM              ..
06/09/2025  03:53 PM            80,285 ALL_BUILD.vcxproj
06/09/2025  03:53 PM               293 ALL_BUILD.vcxproj.filters
06/09/2025  03:53 PM             3,126 BasicAddinCPP.sln
06/09/2025  03:53 PM           102,628 BasicAddinCPP.vcxproj
06/09/2025  03:53 PM               598 BasicAddinCPP.vcxproj.filters
06/09/2025  03:53 PM            14,954 CMakeCache.txt
06/09/2025  03:53 PM              CMakeFiles
06/09/2025  03:53 PM             1,776 cmake_install.cmake
06/09/2025  03:53 PM            80,373 ZERO_CHECK.vcxproj
06/09/2025  03:53 PM               536 ZERO_CHECK.vcxproj.filters
               9 File(s)        284,569 bytes
               3 Dir(s)  1,886,786,166,784 bytes free

D:\cases\My First Fusion Plug-in\cmake\build>

However, it only builds the project, but can’t copy the files to the addin folder. The project created by Fusion will copy the output library to the addin folder, so Fusion could always load the latest addin.

Let’s add this feature to our CMake project. The path for Windows/Mac is different, we still need the if macro. Add these command to the end of the CMakeLists.txt

# Let's copy our output to the fusion addin folder automatically.
# We also need to copy manifest here.
# Since the mac will add lib prefix to the output library, we'll need to make a name change there.
if(WIN32)
    # We'll need to change "\" in the path from environment variables to "/"
    file(TO_CMAKE_PATH "$ENV{APPDATA}/Autodesk/Autodesk Fusion 360/API/AddIns/BasicAddinCPP" TARGET_DIR)

    add_custom_command(TARGET BasicAddinCPP POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E make_directory "${TARGET_DIR}"

            COMMAND ${CMAKE_COMMAND} -E copy
            $
            ${TARGET_DIR}/$

            COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.manifest
            ${TARGET_DIR}/${PROJECT_NAME}.manifest

            COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/AddInIcon.svg
            ${TARGET_DIR}/AddInIcon.svg

	        COMMAND ${CMAKE_COMMAND} -E remove_directory ${TARGET_DIR}/resources
            COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${TARGET_DIR}/resources         
            VERBATIM)
elseif(APPLE)
endif()

First, we’ll need to convert the path to unix format. Here, we are using file command with TO_CMAKE_PATH sub-command. It will convert the path and store it into TARGET_DIR variable.

Then, we have added some custom commands with add_custom_command command. We specify these commands should be called the post building stage of BasicAddinCPP target. Each of the commands begins with COMMAND sub-command. It will call cmake itself with ${CMAKE_COMMAND} variable. The -E parameter will ask cmake to run a command-line tool. These tools can be used on different platforms, so you don’t have to remember the commands it provides on different platforms.

We have used copy, make_directory, remove_directory and copy_directory commands in above block. These commands are running quietly and will ignore some of the errors(e.g., you don’t have to check if the folder was created before create it). We’ll create a directory for our plugin and copy the addin, latest manifest and resources into it. So, when we are building on a new machine, we don’t have to create the folder or copying the resources files under the source folder ourselves. The building system will take care it for use.

We have finished the building commands on the Windows platform. Now we’ll focus on the Mac.

Please install XCode and Xcode command line tools first before processing with this tutorial.

Since we are building on Mac now, the default cmake generator is unix makefile now. We’ll need to use make command to build generated project.

Back to the compiler/linker options. It’ll be looking like this for make.

elseif(APPLE)
    set(FUSION_API_INCLUDE "$ENV{HOME}/Library/Application Support/Autodesk/Autodesk Fusion 360/API/CPP/include")
    set(FUSION_API_LIB "$ENV{HOME}/Library/Application Support/Autodesk/Autodesk Fusion 360/API/CPP/lib")

    target_include_directories(${PROJECT_NAME} PRIVATE ${FUSION_API_INCLUDE})

    # The library provided by fusion aren't in standard naming convention(without lib prefix).
    # We'll need to use find_library to specify the name manually.
    find_library(FUSION_CORE_LIBRARY
                 NAMES core.dylib
                 PATHS ${FUSION_API_LIB}
    )
    if (NOT FUSION_CORE_LIBRARY)
        message(FATAL_ERROR "can't find core.dylib!")
    endif()

    find_library(FUSION_FUSION_LIBRARY
            NAMES fusion.dylib
            PATHS ${FUSION_API_LIB}
    )
    if (NOT FUSION_FUSION_LIBRARY)
        message(FATAL_ERROR "can't find fusion.dylib!")
    endif()

    find_library(FUSION_CAM_LIBRARY
            NAMES cam.dylib
            PATHS ${FUSION_API_LIB}
    )
    if (NOT FUSION_CAM_LIBRARY)
        message(FATAL_ERROR "can't find cam.dylib!")
    endif()
    target_link_libraries(${PROJECT_NAME} PRIVATE
                            ${FUSION_CORE_LIBRARY}
                            ${FUSION_FUSION_LIBRARY}
                            ${FUSION_CAM_LIBRARY})

    set_target_properties(${PROJECT_NAME} PROPERTIES
        XCODE_ATTRIBUTE_HEADER_SEARCH_PATHS "\"${FUSION_API_INCLUDE}\""
    )

endif()

It looks much more complex than the Windows counterpart. We are still using target_include_directories and target_link_libraries. However, we’ll need to give absolute path this time. On MacOS, when we are linking to a library, if we don’t give the fullpath, it will add lib as prefix and .dylib as suffix. For example, if we want to link cam, the option for linker would be -lcam. Linker will find the __libCam.dylib in the library searching path. The API library of Fusion doesn’t have lib prefix, so we’ll need to use find_library and make sure the linker use the absolute path instead.

CMake could also generate Xcode projects, so we’ll add an option for Xcode in the end.

Here is the post build part.

elseif(APPLE)
    file(TO_CMAKE_PATH "$ENV{HOME}/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns/BasicAddinCPP" TARGET_DIR)
    # We'll need to rename when copying the library.
    add_custom_command(TARGET BasicAddinCPP POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E make_directory "${TARGET_DIR}"

            COMMAND ${CMAKE_COMMAND} -E copy
            $
            ${TARGET_DIR}/${PROJECT_NAME}.dylib

            COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.manifest
            ${TARGET_DIR}/${PROJECT_NAME}.manifest

	    COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/AddInIcon.svg
            ${TARGET_DIR}/AddInIcon.svg

	    COMMAND ${CMAKE_COMMAND} -E remove_directory ${TARGET_DIR}/resources
            COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${TARGET_DIR}/resources
            VERBATIM)
endif()

We have the same naming issue for our addin: it will build as libBasicAddinCPP.dylib. However, we want BasicAddinCPP.dylib instead. So, we’ll rename it while we are copying it.

But wait, it is only for the platform we build it now. Fusion libraries could support both Apple Silicon/x86_64 right now, could we also make our add-in support that? The answer is yes, we only to add following commands after cmake_minimum_required command.

# We want a UB2 binary for using the library on both x86_64/Apple silicon macs.
# We'll need to set the architectures before setting the project
if(APPLE)
    set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64")
    set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0")
endif()

If we want to drop x86_64 in the future, we could simply remove this part.

Now we have finished our CMake project. The whole CMakeLists.txt looks like below:

cmake_minimum_required(VERSION 3.22.1)

# We want a UB2 binary for using the library on both x86_64/Apple silicon macs.
# We'll need to set the architectures before setting the project
if(APPLE)
    set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64")
    set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0")
endif()

# Set library type to shared (DLL/dylib)
set(BUILD_SHARED_LIBS ON)

project(BasicAddinCPP VERSION 1.0)

# Set CXX Standard
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)


# Add our source code to the target.
# The output target has the same name as the project name
add_library(${PROJECT_NAME} BasicAddinCPP.cpp)

# If there wasn't a CMAKE_BUILD_TYPE is given, we'll make it debug.
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Debug)
endif()

# Enable Unicode support
add_compile_definitions(UNICODE _UNICODE)

# Check system
if(WIN32)
    # Windows settings.
    set(FUSION_API_INCLUDE "$ENV{APPDATA}/Autodesk/Autodesk Fusion 360/API/CPP/include")
    set(FUSION_API_LIB "$ENV{APPDATA}/Autodesk/Autodesk Fusion 360/API/CPP/lib")

    target_include_directories(${PROJECT_NAME} PRIVATE ${FUSION_API_INCLUDE})

    target_link_directories(${PROJECT_NAME} PRIVATE ${FUSION_API_LIB})

    target_link_libraries(${PROJECT_NAME} PRIVATE
                            core.lib
                            fusion.lib
                            cam.lib
                            kernel32
                            user32
                            gdi32
                            winspool
                            comdlg32
                            advapi32
                            shell32
                            ole32
                            oleaut32
                            uuid
                            odbc32
                            odbccp32)
    target_compile_options(${PROJECT_NAME} PRIVATE
                            "-D_USRDLL;-DSIMPLE_EXPORTS")

    target_link_options(${PROJECT_NAME} PRIVATE
                        "/SUBSYSTEM:WINDOWS")
elseif(APPLE)
    set(FUSION_API_INCLUDE "$ENV{HOME}/Library/Application Support/Autodesk/Autodesk Fusion 360/API/CPP/include")
    set(FUSION_API_LIB "$ENV{HOME}/Library/Application Support/Autodesk/Autodesk Fusion 360/API/CPP/lib")

    target_include_directories(${PROJECT_NAME} PRIVATE ${FUSION_API_INCLUDE})

    # The library provided by fusion aren't in standard naming convention(without lib prefix).
    # We'll need to use find_library to specify the name manually.
    find_library(FUSION_CORE_LIBRARY
                 NAMES core.dylib
                 PATHS ${FUSION_API_LIB}
    )
    if (NOT FUSION_CORE_LIBRARY)
        message(FATAL_ERROR "can't find core.dylib!")
    endif()

    find_library(FUSION_FUSION_LIBRARY
            NAMES fusion.dylib
            PATHS ${FUSION_API_LIB}
    )
    if (NOT FUSION_FUSION_LIBRARY)
        message(FATAL_ERROR "can't find fusion.dylib!")
    endif()

    find_library(FUSION_CAM_LIBRARY
            NAMES cam.dylib
            PATHS ${FUSION_API_LIB}
    )
    if (NOT FUSION_CAM_LIBRARY)
        message(FATAL_ERROR "can't find cam.dylib!")
    endif()
    target_link_libraries(${PROJECT_NAME} PRIVATE
                            ${FUSION_CORE_LIBRARY}
                            ${FUSION_FUSION_LIBRARY}
                            ${FUSION_CAM_LIBRARY})

    set_target_properties(${PROJECT_NAME} PROPERTIES
        XCODE_ATTRIBUTE_HEADER_SEARCH_PATHS "\"${FUSION_API_INCLUDE}\""
    )

endif()


# Let's copy our output to the fusion addin folder automatically.
# We also need to copy manifest here.
# Since the mac will add lib prefix to the output library, we'll need to make a name change there.
if(WIN32)
    # We'll need to change "\" in the path from environment variables to "/"
    file(TO_CMAKE_PATH "$ENV{APPDATA}/Autodesk/Autodesk Fusion 360/API/AddIns/BasicAddinCPP" TARGET_DIR)

    add_custom_command(TARGET BasicAddinCPP POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E make_directory "${TARGET_DIR}"

            COMMAND ${CMAKE_COMMAND} -E copy
            $
            ${TARGET_DIR}/$

            COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.manifest
            ${TARGET_DIR}/${PROJECT_NAME}.manifest

	    COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/AddInIcon.svg
            ${TARGET_DIR}/AddInIcon.svg

	    COMMAND ${CMAKE_COMMAND} -E remove_directory ${TARGET_DIR}/resources
            COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${TARGET_DIR}/resources         
            VERBATIM)
elseif(APPLE)
    file(TO_CMAKE_PATH "$ENV{HOME}/Library/Application Support/Autodesk/Autodesk Fusion 360/API/AddIns/BasicAddinCPP" TARGET_DIR)
    # We'll need to rename when copying the library.
    add_custom_command(TARGET BasicAddinCPP POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E make_directory "${TARGET_DIR}"

            COMMAND ${CMAKE_COMMAND} -E copy
            $
            ${TARGET_DIR}/${PROJECT_NAME}.dylib

            COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.manifest
            ${TARGET_DIR}/${PROJECT_NAME}.manifest

	    COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_SOURCE_DIR}/AddInIcon.svg
            ${TARGET_DIR}/AddInIcon.svg

	    COMMAND ${CMAKE_COMMAND} -E remove_directory ${TARGET_DIR}/resources
            COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/resources ${TARGET_DIR}/resources
            VERBATIM)
endif()

We can generate the project with cmake command manually. There are lots of IDE or plugins support CMake projects(e.g., VSCode, Clion, etc…). You can also use them to generate and build your projects.

This tutorial only covers the basic usage of CMake. If you want to have more settings for your project, please check out the official document.


Comments

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading