Workspace 6.21.5
Writing a Simple Workspace Plugin - looking at the code

In this section, we're going to understand the code that was automatically generated by Workspace so that we know how to customise plugins to our needs. Click here to continue on with the tutorial Writing a Simple Workspace Plugin Building your plugin with CMake (continue on with tutorial Writing a Simple Workspace Plugin )

simpleplugin.h

The start of every header file contains a header guard followed by any other header files this one depends on:

#ifndef CSIRO_SIMPLEPLUGIN_H
#define CSIRO_SIMPLEPLUGIN_H
#include <memory>
#include "simpleplugin_api.h"

The name following #ifndef and #define must be unique among all header files in Workspace or your own project. A good guideline is to choose a name based on the namespaces and class name, all converted to uppercase and with _H appended to the end.

The minimal set of header files for a Workspace plugin is just the file Workspace/Application/workspaceplugin.h which contains the definition of the WorkspacePlugin base class. The simpleplugin_api.h file defines the SIMPLEPLUGIN_API symbol which we will use shortly, but don't worry about it for the moment.

Next we will see namespace and class definitions. First item in our namespace is the forward declaration of our implementation class:

namespace CSIRO
{
class SimplePluginImpl;
class SIMPLEPLUGIN_API SimplePlugin : public CSIRO::Application::WorkspacePlugin
{
std::unique_ptr<SimplePluginImpl> pImpl_;
SimplePlugin();
~SimplePlugin() override;
// Prevent copying and assignment
SimplePlugin(const SimplePlugin&) = delete;
SimplePlugin(SimplePlugin&&) = delete;
SimplePlugin& operator=(const SimplePlugin&) = delete;
SimplePlugin& operator=(SimplePlugin&&) = delete;
protected:
const CSIRO::DataExecution::DataFactory* getAliasedDataFactory(const QString& dataType) const override;
const CSIRO::DataExecution::OperationFactory* getAliasedOperationFactory(const QString& opType) const override;
public:
static SimplePlugin& getInstance();
QStringList getCustomWidgetPaths() const override;
bool setup() override;
};
Base class for all workspace plugin classes.
Definition: workspaceplugin.h:98
Base class for all data type factories.
Definition: datafactory.h:71
Base class for all operation factories.
Definition: operationfactory.h:64
Top level namespace for all Workspace code.
Definition: applicationsupportplugin.cpp:32
QStringList
Definition: vectornumbertostringlistadaptor.cpp:133

Next we can see our class definition. The class itself is derived from WorkspacePlugin and provides:

  1. a private unique pointer to our implementation class (SimplePluginImpl)
  2. private constructor and destructor
  3. private copy-constructor and assignment operator (to prohibit copying)
  4. getAliasedDataFactory
  5. getAliasedOperationFactory
  6. getInstance
  7. getCustomWidgetPaths
  8. setup

These methods will be discussed in more detail when we look in the implementation file next.

#endif

Don't forget to end with the #endif preprocessing symbol. This is the end of our header guard block.



Building your plugin with CMake (continue on with tutorial Writing a Simple Workspace Plugin )


simpleplugin.cpp

simpleplugin.cpp contains the definitions of our class's members. Firstly it includes some required headers needed, which normally amounts to the headers for your custom data types and Workspace operations, plus the header declaring your plugin class.

#include <QString>
#include <QStringList>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

Don't worry about the STRINGIFY macros for now, we will discuss them shortly.

class SimplePluginImpl
{
public:
// You can add, remove or modify anything in here without
// breaking binary compatibility. It starts as empty, but
// leave it here in case at some time in the future you
// want to add some data for your plugin without breaking
// binary compatibility.
};

SimplePluginImpl is a private implementation class that will actually implement the functionality of our plug-in.

Next we see the constructor for our SimplePlugin class:

SimplePlugin::SimplePlugin() :
CSIRO::Application::WorkspacePlugin("www.csiro.au/workspace/simple",
"Simple",
TOSTRING(SIMPLEPLUGIN_VERSION)),
pImpl_(std::make_unique<SimplePluginImpl>())
{
}
#define TOSTRING(x)

Here we can see our "Project Internal Name" that we defined in the wizard. We are setting our plugin up so that Workspace can uniquely identify our plugin from the others that it has already loaded.

The constructor also assigns a display name to the plugin. This is what the user sees in things like the user interface of GUI applications. It should be relatively short but also human readable and give some indication of what the plugin is about. The display name may contain spaces.

The constructor specifies the plugin version as well. The example code uses the two #define's related to STRINGIFY to embed the version number from the project file. We will skip over this for now and return to it towards the end of the tutorial where the project file is discussed in detail. You could just embed the version number here as an ordinary string, but then you would have to manually keep the version number in sync with the project file. Finally we see how our unique pointer to the implementation class is initialised. We will skip over the simple class destructor.

SimplePlugin& SimplePlugin::getInstance()
{
static SimplePlugin plugin;
return plugin;
}

The getInstance() function should be the only way to obtain a reference to a SimplePlugin object, since all Workspace plugins should be singletons (ie no more than one SimplePlugin object should ever exist at any time). The above example code uses the simplest form for ensuring this singleton behavior.

bool SimplePlugin::setup()
{
// Add your data factories like this:
//addFactory( CSIRO::DataExecution::DataFactoryTraits<MyDataType>::getInstance() );
// Add your operation factories like this:
//addFactory( CSIRO::DataExecution::OperationFactoryTraits<MyOperation>::getInstance() );
// Add your widget factories like this:
//addFactory( MyNamespace::MyWidgetFactory::getInstance() );
// Add your adaptor factories like this:
//addFactory( CSIRO::DataExecution::SimpleAdaptorFactory<MyDataType, OtherDataType>::getInstance());
// Add your collection workflows here like this:
//addWorkspaceCollection(":/appdata/<MyPlugin>/myworkflow.wsx");
return true;
}

The setup() function is where you list all your custom data types, operations, widgets and tell Workspace about their existence. If you follow the recommended way to define data factories, operation factories and widget factories in these tutorial, then all you need to do is use another addFactory(...) line for each data type or operation or widget and replace MyClass and MyOperation with the class/operation/widget you want to add.

Next we see two Workspace-specific methods that assist with code maintenance by allowing developers to rename operations or datatypes. Naturally it is preferable not to rename such items but given the realities of working with large evolving codebases it is better practice to provide a mechanism than leave it up to individual developers to fend for themselves.

const CSIRO::DataExecution::OperationFactory* SimplePlugin::getAliasedOperationFactory(const QString& opType) const
{
// If you rename an operation, you can provide backwards
// compatibility using something like this (don't forget to
// include namespaces in the names if relevant):
// if (opType == "SomeOperationName")
// {
// return &CSIRO::DataExecution::OperationFactoryTraits<NewOperationName>::getInstance();
// }

If an operation is renamed, getAliasedOperationFactory provides a backwards compatibility mechanism so that the plugin can provide the new factory that corresponds to an old name. See the getAliasedOperationFactory documentation for more details.

const CSIRO::DataExecution::DataFactory* SimplePlugin::getAliasedDataFactory(const QString& dataType) const
{
// If you rename a data type, you can provide backwards
// compatibility using something like this (don't forget to
// include namespaces in the names if relevant):
//if (dataType == "SomeDataType")
// return &CSIRO::DataExecution::DataFactoryTraits<NewDataType>::getInstance();
// If you make use of dataType, you can delete the following Q_UNUSED line
Q_UNUSED(dataType);
// If we get here, dataType is not something we renamed, so return a
// a null pointer to tell the caller
return nullptr;
}

If a data type is renamed, getAliasedDataFactory provides a backwards compatibility mechanism so that the plugin can provide the new factory that corresponds to an old name. See the getAliasedDataFactory documentation for more details.

QStringList SimplePlugin::getCustomWidgetPaths() const
{
QStringList result;
result.push_back("widgets:Simple");
return result;
}

This method provides the path to any custom widget defined in our plug-in. Essentially this value is add to the list of paths used when searching for custom .ui files when creating widgets for our operations, inputs and outputs. The path supplied here is a resource path.

The last thing to be done is to provide the function Workspace looks for in your plugin's final binary to identify it as a Workspace plugin (we have to close off the namespace scopes first too):

#ifndef CSIRO_STATIC_BUILD
extern "C"
{
{
return &CSIRO::SimplePlugin::getInstance();
}
{
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
return TOSTRING(CSIRO_WORKSPACE_VERSION_CHECK);
}
}
#endif
#define CSIRO_EXPORTSPEC
Definition: api_workspace.h:80
CSIRO_IMPORTSPEC const char * builtAgainstWorkspace()
Definition: applicationsupportplugin.cpp:167
CSIRO_IMPORTSPEC CSIRO::Application::WorkspacePlugin * getWorkspacePlugin()
Definition: applicationsupportplugin.cpp:159

The extern "C" block ensures that the functions can be called as an ordinary C function. This is a portability requirement. Inside the extern block there are two functions.

  1. The getWorkspacePlugin function simply returns the plugin's singleton instance.
  2. The builtAgainstWorkspace identifies which version of Workspace the plugin was built against, if you try to load a plug-in built against an incompatible version of Workspace then this will be detected and the plugin will not be loaded.
    The #ifndef surrounding the extern "C" block is to handle the situation where you might want to build the plugin as a static library and link it into a standalone executable rather than as a shared library. In this case, the exported functions are not required and would cause name clashes with other Workspace plugins if not handled in this way.

Building your plugin with CMake ((continue on with tutorial Writing a Simple Workspace Plugin ))


simpleplugin_api.h

Workspace implements plugins as shared libraries on unix-like platforms, or their equivalent (DLL's) under Windows. When creating shared libraries, it is necessary to tell the compiler what things you want to be visible from outside your plugin or else your code won't be usable by Workspace or any other calling applications. Unfortunately, different platforms and compilers use different syntax for how they specify what should be externally visible.

To assist with this, Workspace provides a header to help with defining the required preprocessor symbols so that you can use one specification across your whole plugin for all compilers and platforms. The simpleplugin_api.h example file shows how to make use of it:

#ifndef SIMPLEPLUGIN_API_H
#define SIMPLEPLUGIN_API_H
#ifdef SIMPLEPLUGIN_EXPORT
#define SIMPLEPLUGIN_API CSIRO_EXPORTSPEC
#else
#define SIMPLEPLUGIN_API CSIRO_IMPORTSPEC
#endif
Defines symbol import/export macros.

As you can see, the file simply defines SIMPLEPLUGIN_API to be an import or export specification depending on whether or not CSIRO_EXPORT is defined. The Workspace/api_workspace.h header provides the platform-specific definitions for CSIRO_EXPORTSPEC and CSIRO_IMPORTSPEC, so you don't need to worry about them. In fact, you don't really need to understand how any of this works except for two key points:

  • Put SIMPLEPLUGIN_API in your header files' class definitions as shown earlier in this tutorial. If you use free functions, they will need it too.
  • Your build system should define the CSIRO_EXPORT symbol when building your plugin, but importantly it must NOT define it when building other things that might include some of your plugin's headers. If you're using CMake to build your plugin (highly recommended), this will be taken care by the CMakeLists.txt file auto-generated by the wizard (see CMakeLists.txt).

Export symbols may seem unnecessarily complex, but rest assured that when writing your own plugins, you will quickly realize that it just boils down to a bit of cut and paste that mostly only needs to be done when first setting up your new plugin.


Building your plugin with CMake ((continue on with tutorial Writing a Simple Workspace Plugin ))


CMakeLists.txt

You now have all the source files you need to create your plugin. All you need now is a way to build it. By far, the easiest way to do this is to use the same build system that Workspace uses.

With CMake, the project file is always called CMakeLists.txt and it is case-sensitive on all platforms except Windows. It is strongly recommended that you use this case-specific form as a habit on all platforms (if you're using the code wizard, this will be done for you). You would normally have each Workspace plugin/project in its own directory, as this allows you to keep your CMakeLists.txt project file relatively simple. The example used here will assume that your CMakeLists.txt project file and all your sources and headers (*.cpp and *.h) are in the same directory.

As the file extension suggests, the CMakeLists.txt is just an ordinary human-readable text file. The example used in this tutorial can be thought of as having three sections: preamble, source file definitions and build target details.

The start of the CMakeLists.txt project file contains the preamble section:

project(SIMPLEPLUGIN)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
find_path(WORKSPACE_CMAKE_ROOT Workspace_CMake_Root.txt
HINTS ${CMAKE_SOURCE_DIR} ENV WORKSPACE_CMAKE_ROOT
DOC "Directory containing the Workspace_CMake_Root.txt file")
if (NOT WORKSPACE_CMAKE_ROOT)
message(FATAL_ERROR "Please set WORKSPACE_CMAKE_ROOT to the directory containing the file Workspace_CMake_Root.txt")
endif()
include( ${WORKSPACE_CMAKE_ROOT}/CMakeLists.txt NO_POLICY_SCOPE )
endif()

The first part of the header section gives a name to the project. The project line defines a name for your project which must be unique among all other cmake projects that you might be building. Since we are only building a single plugin in this tutorial, the name can essentially be whatever you like, but a good convention to adopt is to make your project name the same as the name of the plugin except in capitals (the reason for the capitals will become clearer further below). The first if/endif block should appear exactly as shown and can simply be cut and pasted directly. It's purpose is to allow the build system to find the files provided by Workspace, whether your project is using an installed version of Workspace or whether it is incorporated within another project (this latter case is how plugins that come with Workspace are built). We will postpone discussion of this section until the end where running cmake on the project is explained.

include_directories(${SIMPLEPLUGIN_SOURCE_DIR})
if (NOT ${SIMPLEPLUGIN_SOURCE_DIR} STREQUAL ${SIMPLEPLUGIN_BINARY_DIR})
include_directories(${SIMPLEPLUGIN_BINARY_DIR})
endif()
set(SIMPLEPLUGIN_VERSION 0.1.0)
string(REGEX MATCH "^[0-9]+" SIMPLEPLUGIN_SOVERSION ${SIMPLEPLUGIN_VERSION})

The line set is responsible for setting the version number of the plugin. Notice how the first argument to the set() command is the same variable name that we saw inside the TOSTRING() macro used in the simpleplugin.cpp file. This is how we set the project version in the project file and have it picked up automatically within the C++ source files. You are strongly encouraged to define your project version in this way rather than having it set in more than one location or else you risk the multiple version numbers getting out of sync. The next line of the header section relating to string(REGEX_MATCH ...) extracts the first part of the version number and stores it in the variable called SIMPLE_PLUGIN_SOVERSION. It will be used shortly, but you don't really need to know much about it. You can simply cut and paste this particular line and put it just after you set the project version number.

The version should always be a number in the form X.Y.Z where X, Y and Z are all integers. This is sometimes called major.minor.patch form. There is a fairly well established convention on the meaning of these three numbers and if possible, you are encouraged to follow that convention. Roughly speaking, the convention goes like this:
  • The major version number should change rarely, since that normally indicates that it will break code written for the previous major version. If you change the major version, all code built against the plugin will have to be recompiled.
  • The minor version number should be incremented when you add a new feature or make some other significant improvement. Any code that was built against the same major version and an earlier minor version should still continue to be valid without having to be recompiled. If the major number changes, the minor part should be reset to zero.
  • The patch number should be incremented every time you make a smaller change or fix a bug and where you release an update or repackage the plugin. If the major or minor part changes, the patch number should be reset to zero. Any code that was built against the same major version and the same or earlier minor version should still continue to be valid without having to be recompiled. The convention is less clear about whether code built against a later patch version should be able to run with a library built with an earlier patch version for the same major.minor version, but at the very least, if someone builds code against a particular patch number then that code should not need to be recompiled for that or any later patch number within a given major.minor combination.
The above logic may sound a bit complicated, but essentially what it is saying is that any code built against a particular major.minor version should be able to be used against any later version up until the major number changes. For a discussion with examples of this convention, the interested reader may wish to look at the Apache APR library versioning information which provides a good treatment of this subject.
# Add other Qt modules as required by your plugin - eg. QtNetwork, QtOpenGL, QtSql
find_package(Qt5Core)
find_package(Qt5Widgets)
set(QT_LIBRARIES Qt5::Core;Qt5::Widgets)

The last part of the header sets up Qt-related information. The find_package and include lines will make sure that Qt's headers, libraries and build tools can be found. For most Workspace plugins, you will likely only need the QtCore and QtGui modules, but if you use others, all you need to do is add them to the find_package line.

The second section of the CMakeLists.txt project file specifies the sources and headers for your plugin. In this example, the files are straightforward to specify:

set(CMAKE_AUTOMOC ON)
set(HEADERS
${SIMPLEPLUGIN_SOURCE_DIR}/simpleplugin_api.h
${SIMPLEPLUGIN_SOURCE_DIR}/simpleplugin.h
)
set(INSTALL_HEADERS
${SIMPLEPLUGIN_SOURCE_DIR}/simpleplugin_api.h
${SIMPLEPLUGIN_SOURCE_DIR}/simpleplugin.h
)
set(MOC_HEADERS
)
set(SOURCES
${SIMPLEPLUGIN_SOURCE_DIR}/simpleplugin.cpp
)
set(UI_SOURCES
)

The set(...) blocks list the C++ headers, install headers, MOC headers and source files respectively.

It is recommended that in your production code you edit these variable names and use names that are the same as the name of your project with _HEADERS or _SOURCES appended. This will avoid potential conflicts with other projects should you later want to build more than one Workspace plugin.

The third and final section of the CMakeLists.txt project file controls how the plugin is built:

add_definitions(-DSIMPLEPLUGIN_VERSION=${SIMPLEPLUGIN_VERSION})
# The next line is used by the simple application generator wizard
# add_subdirectory(${SIMPLEPLUGIN_SOURCE_DIR}/Application)
# The below line can be used to import sub-directories
include( ${SIMPLEPLUGIN_SOURCE_DIR}/Collection/CMakeLists.txt )
# qtx macros are defined in the ${WORKSPACE_CMAKE_ROOT}/CMakeLists.txt included at the top of this file
# to support both Qt4 and Qt5 builds.
qtx_wrap_ui(UIC_SOURCES ${UI_SOURCES})
qtx_add_resources(RES_SOURCES ${RESOURCES})
add_library(simpleplugin ${SOURCES} ${HEADERS} ${MOC_SOURCES} ${UIC_SOURCES} ${RES_SOURCES})
target_link_libraries(simpleplugin workspace ${QT_LIBRARIES})
set(SIMPLEPLUGIN_INTERFACE_INCLUDE_DIRECTORIES "include/SimplePlugin")
target_include_directories(simpleplugin INTERFACE "${CSIRO_INSTALL_AREA}/${SIMPLEPLUGIN_INTERFACE_INCLUDE_DIRECTORIES}")
set_target_properties(simpleplugin PROPERTIES
DEFINE_SYMBOL SIMPLEPLUGIN_EXPORT
VERSION ${SIMPLEPLUGIN_VERSION}
SOVERSION ${SIMPLEPLUGIN_SOVERSION}
)
setTargetOutputDirectory(simpleplugin ${CSIRO_INSTALL_AREA}/lib/Plugins)
configure_file(pkg-simpleplugin.cmake ${CSIRO_INSTALL_AREA}/cmake/Exports/pkg-simpleplugin.cmake @ONLY)
# If your plugin is dependent on any other dynamic libraries you can add the root path
# of that library (the path above the lib, bin and include) to the following EXTRA_INSTALL_AREAS variable
set_install_area_file(simpleplugin "${CSIRO_INSTALL_AREA}" ${EXTRA_INSTALL_AREAS})
# Copy our install headers into the install directory so that others can build against our plugin.
foreach(inFile ${INSTALL_HEADERS})
string(REGEX REPLACE "(${SIMPLEPLUGIN_SOURCE_DIR}/)(.*)" "${CSIRO_INSTALL_AREA}/${SIMPLEPLUGIN_INTERFACE_INCLUDE_DIRECTORIES}/\\2" outFile "${inFile}")
configure_file(${inFile} ${outFile} COPYONLY)
endforeach(inFile)
add_subdirectory(Designer)
Workspace * workspace
Definition: mongodbremotescheduler.cpp:171
const QString plugin("plugin")

The add_definitions line makes the plugin version available to C++ files as a defined symbol called SIMPLE_PLUGIN_VERSION. This is used in the simpleplugin.cpp file to set the plugin version in the SimplePlugin constructor. For your projects, change the name of the variable (it appears twice on this line) to match the one you used just after the project line. If you needed to add other compiler defines, you can simply repeat this line as often as necessary. If you just want to define a symbol without giving it a value, omit the equals sign and what comes after it.

The add_library line is where we tell CMake to build the plugin library. The first argument to add_library is the name of the target for our plugin. A useful convention is to use your project name in lowercase followed by the word "plugin". The rest of the arguments specify the source files to include in the library. In our example, we use the SOURCES variable to hold the list of source files.

The target_link_libraries line specifies the other libraries that the plugin needs to link in. The first argument is the target name given to add_library and the rest of the arguments are library names. These library names can be either the name of another CMake target or they can be the library name that compilers would normally understand. For example, let's say that there is a library called foobar. Under Windows, its name would be foobar.dll and under linux it would be libfoobar.so . The library name to use in the target_link_libraries command would simply be foobar - under linux, lib is prepended automatically. At the end of the target_link_libraries line you can see that we have also included the contents of a variable called QT_LIBRARIES. This variable was set for us by the find_package and include(${QT_USE_FILE}) lines earlier in the project file.

The set_target_properties command is doing a number of things, so we will describe each line within it individually. The first line contains the target for the plugin as the first argument. This will be the same as what was used for add_library and target_link_libraries. The PROPERTIES entry is a CMake keyword indicating that the following arguments are properties on the target specified.

The DEFINE_SYMBOL line tells CMake to define the preprocessor symbol CSIRO_EXPORT only when compiling files for this plugin. If you were building multiple plugins within the one CMakeLists.txt project file, this would ensure that the symbol was only defined for this target and not the others. The symbol name matches the one used in simpleplugin_api.h, and in fact that is the only file that uses the symbol directly. You may wonder why we go to all this trouble instead of just defining it with an add_definitions or even just hard-coding it. The answer lies in when another plugin wants to include a header from this target. It will be built without the CSIRO_EXPORT symbol defined, which will result in symbols being imported instead of exported. This is precisely what we want, because the simple plugin will define everything and the other plugin will simply create external references and import them.

The VERSION and SOVERSION lines have different effects on different platforms. A Workspace plugin is just a shared library, but libraries usually have version information stored in them and/or encoded as part of their file name. Under Windows, the VERSION number may be embedded in the plugin itself and can be inspected by right-clicking on the library and viewing its properties. Under linux, the VERSION is appended to the file name and symbolic links are set up for related names as is common on linux platforms. In this example, after building the Workspace plugin, the following three entries will exist in the file system:

  libsimpleplugin.so
  libsimpleplugin.so.0
  libsimpleplugin.so.0.1.0

The first two of these will simply be symbolic links to the last entry. Under linux, the SOVERSION is also encoded into the library itself so that compilers know what form of the library name to link to. Following standard linux conventions, the SOVERSION is just the major part of the version number in this example (recall that this was set near the top of the project file). Any executable that links against this plugin will link to the name that matches the SOVERSION, which in this case would be libsimpleplugin.so.0 and this would be the only file that you would need to include in a software package to be installed on other systems.

The last line in this block is the setTargetOutputDirectory command. This is a custom command provided by the framework to help set the desired output directory for the built target while also working around an issue with CMake and Visual Studio. The parameters to the command are the target name and the target output directory.

The setTargetOutputDirectory command sets three target properties XXX_OUTPUT_DIRECTORY which control where the library and any associated files will be put. Unfortunately, their precise meanings vary depending on platform. Under non-Windows platforms, it is fairly straightforward. The RUNTIME_OUTPUT_DIRECTORY controls where executables will be put, LIBRARY_OUTPUT_DIRECTORY controls where shared libraries are put and ARCHIVE_OUTPUT_DIRECTORY controls where static libraries are put. Under Windows, things are a bit more murky. The RUNTIME_OUTPUT_DIRECTORY is where executables and DLL files will be built. The LIBRARY_OUTPUT_DIRECTORY specifies where export libraries (*.exp), import libraries (*.lib) and other things like PDB files go for shared libraries. The ARCHIVE_OUTPUT_DIRECTORY controls where static libraries are put, just like for other platforms, but note that these also have a *.lib file extension. This confusion of the RUNTIME and LIBRARY locations is an artefact of how Windows treats DLL files almost like executables (eg they share the PATH environment variable when the operating system is searching for them).

Happily, for Workspace plugins, you can simply set all three XXX_OUTPUT_DIRECTORY entries to the same location which is what setTargetOutputDirectory does. This will ensure that all your plugin library files end up in a predictable place. The location shown in this tutorial is the canonical place where Workspace plugins are put for the plugins that come with Workspace itself. You are encouraged to keep the same structure for simplicity, but you are free to choose something else if you prefer.

When building Visual Studio project files with CMake, the targets that are built (ie the libraries, executables, etc.) are placed in a configuration-specific subdirectory below the locations specified with the XXX_OUTPUT_DIRECTORY properties. These subdirectories would have names such as Debug, Release and so on. However, this can interfere with how Workspace tries to load plugins at runtime and can be an annoying nuisance during development.

The setTargetOutputDirectory command addresses this issue by also setting configuration specific versions of XXX_OUTPUT_DIRECTORY to the target output directory. These properties are used by the CMake Visual Studio generator and force the output to the intended directory.


Building your plugin with CMake (continue on with tutorial Writing a Simple Workspace Plugin )