Sharpmake is a solution to generate mainly C++ and C# projects, file extensions like .vcxproj, .csproj, .sln, .bff (FastBuild). It is using C# as a scripting language with the implementation in C# as well, meaning complete support in Visual Studio for debugging and Intellisense. Sharpmake is easily usable by both C++ and C# programmers and is generating project files fast. Using a complete programming language like C# is offering a lot of ways to scale better with big code bases. Sharpmake has built-in support for FastBuild .bff files, easing using both FastBuild projects and normal VC++ projects.
One core and cool concept of Sharpmake are the fragments. Fragments are defined with an enum like this one:
[Fragment, Flags]
enum Optimization
{
Debug = 0x1,
Release = 0x2,
Retail = 0x4
}
Notice that the enum is using different bits for every value.
Fragments are used to define targets. Targets are classes with a certain number of properties of different fragment types:
class Target : Sharpmake.ITarget
{
public BuildSystem BuildSystem;
public Optimization Optimization;
public DevEnv DevEnv;
...
}
The idea behind fragments is that they can multiply your number of targets. You can combine the bits of fragment values to describre multiple targets easily:
new Target(
BuildSystem.MSBuild | BuildSystem.FastBuild,
Optimization.Debug | Optimization.Release | Optimization.Retail,
...)
That target instance will represent a target list, something useful when calling AddTargets.
A project in Sharpmake is a class with a specific attribute:
[Generate]
class MyProject : Project
{
...
}
Sharpmake supports 3 types of projects: Generate, Compile and Export. Export is used for libraries already compiled while Generate is when the project file (.vcxproj, .csproj, etc.) will be generated. Compile attribute is for marginal cases where a project generated outside of Sharpmake is reused in a solution (.sln) generated by Sharpmake.
That project class will be instantiated a single time. In the constructor, you add all the targets for the project.
[Generate]
class MyProject : Project
{
public MyProject() : base(typeof(Target))
{
AddTargets(new Target(
BuildSystem.MSBuild | BuildSystem.FastBuild,
Optimization.Debug | Optimization.Release | Optimization.Retail,
...);
}
}
If you ever wonder why these are not deduced from dependants, it is made that way to generate all projects in parallel, and result in much faster execution. This is typically not an issue as you can add the targets in a base class.
In the project, you have one or more Configure functions. They take a Target, which is the input, and the Configuration, which is the output. The function is executed for every Target added to that project.
class MyProject : Project
{
...
[Configure]
public void Configure(Configuration conf, Target target)
{
if (target.Optimization == Optimation.Debug)
conf.Defines.Add("UBI_DEBUG");
...
}
Since the function is executed for every target, you can use simple code to choose how to configure like if/else, switch/case, functions, whatever you prefer. This is normal C# code.
Since projects are classes, you can define a class hierarchy to represent your projects. You can use virtual functions and overrides, can use static public functions in other classes, etc.
By default, a Configure function is for all targets, but you can limit a Configure function to a subset with specific fragment values:
[Configure(Platform.win32 | Platform.win64)]
void ConfigureWindows(Target target, Configuration conf)
{
...
}
Adding dependencies between projects is done in Configure functions.
[Configure]
public void Configure(Configuration conf, Target target)
{
conf.AddPublicDependency<System>(target);
conf.AddPublicDependency<Engine>(target);
conf.AddPublicDependency<Havok>(target.ToHavokTarget());
}
You call AddPublicDependency or AddPrivateDependency with the project type to depend on. You also pass the target. The target is typically the same, but it's possible for different projects to use different Target types.
If the case, you call code to make the correspondence. It can be a property, a function, whatever you prefer.
Sharpmake was designed initially for C++ needs, so it provides mechanisms to define include paths, library paths, preprocessor definitions for a project a single time.
[Configure]
public void Configure(Configuration conf, Target target)
{
conf.IncludePaths.Add(...); // also for dependants
conf.IncludePrivatePaths.Add(...);
conf.ExportDefines.Add(...); // for dependants
conf.Defines.Add(...);
...
}
There's a clear distinction in Configuration class between include paths and preprocessor definitions to compile the current project and the ones that are also necessary in dependent projects, with direct or indirect dependencies.
With distinction between public and private dependencies, it's also possible to limit if dependencies are propagated to dependants or not.
A project in Sharpmake doesn't mean necessarily a single project file on disk. Files in Sharpmake are like any output and are handled automatically.
For example, if in a Configure function you have this line:
[Configure]
public void Configure(Configuration conf, Target target)
{
conf.ProjectFileName = "[project.Name].[target.DevEnv]";
Here we can see that strings in Sharpmake supports to refer to some objects properties through reflection. It's actually useful to use that rather than normal C# features since that way they are evaluated much later.
If this line is changed into this:
[Configure]
public void Configure(Configuration conf, Target target)
{
conf.ProjectFileName = "[project.Name].[target.Platform].[target.DevEnv]";
Then Sharpmake will generate a file for each platform. There is nothing more to do.
The configuration and generation of .sln files is done in a similar fashion to .vcxproj and .csproj files. Instead of Project class, the Solution class is used:
[Generate]
class MySolution : Solution
{
public MySolution() : base(typeof(Target))
{
AddTargets(new Target(
BuildSystem.MSBuild | BuildSystem.FastBuild,
Optimization.Debug | Optimization.Release | Optimization.Retail,
...);
}
[Configure]
public void Configure(Configuration conf, Target target)
{
conf.AddProject<MyProject>(target);
}
}
The entry point is the Main, a function called once and found in the main .sharpmake.cs file passed to Sharpmake.exe:
static class Main
{
[Sharpmake.Main]
public static void SharpmakeMain(Sharpmake.Arguments arguments)
{
arguments.Generate<MySolution>();
}
}
Sharpmake arguments can then be used to generate different things. Sharpmake has built-in support for easy custom command line arguments as well:
class MyArguments
{
public bool SomeOption = false;
[CommandLine.Option("someoption", @"Some option: ex: /someoption(<true|false>)")]
public void CommandLineGenerateMapFile(bool value)
{
SomeOption = value;
}
}
static class Main
{
public static MyArguments Arguments = new MyArguments();
[Sharpmake.Main]
public static void SharpmakeMain(Sharpmake.Arguments arguments)
{
CommandLine.ExecuteOnObject(Arguments);
...
if (Arguments.SomeOption) ...
}
}
From the main file and from any included file, you can include other Sharpmake files:
[module: Sharpmake.Include("otherfile.sharpmake.cs")]
In any file it's also possible to refer to any dll:
[module: Sharpmake.Reference("Sharpmake.ShellTools.dll")]
It concludes that quick overview of Sharpmake.