Skip to content

Custom Targets

Jean-Sébastien Pelletier edited this page Oct 11, 2023 · 3 revisions

Custom Target Tutorial

The Sharpmake.Target class contains a premade collection of fragments that you can use to create your set of targets, but if you need extra options to configure your targets, it is also possible to create your own target class and your own fragment enumerations.

In this tutorial we are going to create a fragment that allows your script to select whether C++ exceptions are enabled in a simple C++ project.

The Sample Code

Here is the sample code we are going to work with.

// main.cpp
#include <cstdlib>
#include <cstdio>

// The compiler generates warnings if it sees exception handling code while
// exceptions are turned off, so use a macro magic hack.
#if ALLOW_THROW
#   define THROW(ex, msg)                   throw ex(msg)
#   include <stdexcept>
#else
#   define THROW(...)                       abort()
#endif // ALLOW_THROW

int Divide(int left_operand, int right_operand)
{
    if (right_operand == 0)
        THROW(std::domain_error, "DIV/0!");

    return left_operand / right_operand;
}

int main()
{
    // Using the C I/O library instead of iostream because the C library
    // does not generate warnings when C++ exceptions are disabled.

    int x, y;
    printf("First operand: ");
    scanf("%i", &x);

    printf("\nSecond operand: ");
    scanf("%i", &y);

    int quotient = Divide(x, y);
    printf("%i / %i = %i", x, y, quotient);

    return 0;
}

The ALLOW_THROW hack is just there so that the code does not attempt to throw if exceptions are disabled. This is just so that the compiler does not warn that there is a throw when exceptions are disabled.

The Script

Create the Fragment

Let's start by defining our custom fragment. This will let us configure the exception settings.

// main.sharpmake.cs
using System;
using Sharpmake;


// A fragment is a bit flag enumeration with the [Fragment] attribute on it.
[Fragment, Flags]
public enum ErrorHandlingModes
{
    CppExceptions = 1,          // Use C++ exceptions.
    Abort = 2                   // Use abort().
}

A fragment enumeration is just an ordinary bit flag enumeration decorated with the [Fragment] attribute. The name of the elements have no semantics to Sharpmake; it is up to the script to interpret it.

Create the Custom Target

Since our fragment is not found anywhere in Sharpmake.Target, we have no choice to create a custom target.

// Create a custom target that allows the code to specify the error handling
// mode, on top of other common fragments we care about.
public class CustomTarget : ITarget
{
    // DevEnv and Platform are mandatory on all targets so we define them.
    public DevEnv VisualStudioVersion;
    public Platform Platform;

    // Also put debug/release configurations since this is common.
    public Optimization Optimization;

    // Specifies how that platform handles exceptions. This is our custom
    // fragment.
    public ErrorHandlingModes ErrorHandling;

    //
    // Put any other fragment you want to configure here.
    //
}

A custom target is simply a class that implements Sharpmake.ITarget with a list of fragments. The fragments are simple C# fields.

The Project Definition

// The project definition.
[Generate]
public class CustomTargetProject : Project
{
    // Projects and solutions that use a custom target (ie: that is not the
    // class Target) must pass the type of the target class it's going to be
    // using to the base class constructor, otherwise Sharpmake will not know
    // how to call the Configure methods.
    public CustomTargetProject()
        : base(typeof(CustomTarget))
    {
        Name = "CustomTarget";
        SourceRootPath = @"[project.SharpmakeCsPath]";
        AddTargets(new CustomTarget
        {
            VisualStudioVersion = DevEnv.vs2015,
            Platform = Platform.win32 | Platform.win64,
            Optimization = Optimization.Debug | Optimization.Release,
            ErrorHandling = ErrorHandlingModes.CppExceptions | ErrorHandlingModes.Abort
        });
    }

    // Because we are using a custom target class, the configure method accepts
    // a CustomTarget instead of a Target. There is no need to type cast.
    [Configure]
    public void ConfigureAll(Configuration conf, CustomTarget target)
    {
        conf.ProjectPath = @"[project.SharpmakeCsPath]\generated";

        // No Microsoft, you don't deprecate standard C library functions
        // unless the ISO commitee says it's deprecated.........
        conf.Defines.Add("_CRT_SECURE_NO_WARNINGS");

        // Sets the compiler exception flag depending on whether throwing is
        // enabled. Also define the ALLOW_THROW symbol to let the code know
        // that it can throw.
        if (target.ErrorHandling == ErrorHandlingModes.CppExceptions)
        {
            // This just let the code know that it can throw so it defines the
            // right stuff. Nothing to do with Sharpmake.
            conf.Defines.Add("ALLOW_THROW");

            // Turns the Exception compiler option ON.
            //
            // Compiler options are set by adding values to the Options
            // collection. Sharpmake knows which option you want to set simply
            // by looking at the type of the option you added. Here, since we
            // added a value of type Options::Vc::Compiler::Exceptions,
            // Sharpmake understands that we are setting the exception setting.
            //
            // Sharpmake options for Visual C++ are sorted by their section in
            // the project properties dialog box. You can use IntelliSense to
            // explore the options that are available.
            conf.Options.Add(Options.Vc.Compiler.Exceptions.Enable);
        }
        else if (target.ErrorHandling == ErrorHandlingModes.Abort)
        {
            conf.Options.Add(Options.Vc.Compiler.Exceptions.Disable);
        }
    }
}

// The solution definition.
[Generate]
public class CustomTargetSolution : Solution
{
    public CustomTargetSolution()
        : base(typeof(CustomTarget))
    {
        Name = "CustomTarget";
        AddTargets(new CustomTarget
        {
            VisualStudioVersion = DevEnv.vs2015,
            Platform = Platform.win32 | Platform.win64,
            Optimization = Optimization.Debug | Optimization.Release,
            ErrorHandling = ErrorHandlingModes.CppExceptions | ErrorHandlingModes.Abort
        });
    }

    [Configure]
    public void ConfigureAll(Configuration conf, CustomTarget target)
    {
        conf.SolutionPath = @"[solution.SharpmakeCsPath]\generated";
        conf.AddProject<CustomTargetProject>(target);
    }
}

public static class Main
{
    [Sharpmake.Main]
    public static void SharpmakeMain(Sharpmake.Arguments arguments)
    {
        arguments.Generate<CustomTargetSolution>();
    }
}

When creating projects and solutions that use a custom target (like our CustomTarget) you must pass the type of your custom target type to the base class constructor.

When using a custom target, your configure methods' second argument can be your custom target instead of the usual Target or ITarget, so there is no need to type cast to your custom target type in your configure methods.

Run Sharpmake

Done! Save your file and run Sharpmake. It should generate a project with a configuration option for the C++ exceptions.