一、初识Sharpmake
1.简介
Sharpmake是一款由Ubisoft开发的以C#为脚本语言的开源Make工具,适用于各种C/C++,C#应用程序的开发管理过程,保证团队项目拥有一个统一化规则的项目配置管理与构建工具,其支持的功能有:
-
项目生成与配置管理
- 支持C++项目(.vcxproj),C#(.csproj)项目的生成,并且支持将Sharpmake脚本转换为makefile以便在cmake等其他make工具中进行项目生成。
- 根据项目文件目录层级与包含规则,管理大量的项目源码文件,并生成对应的项目配置文件(.proj).
- 可灵活管理项目配置文件(.proj)的内容,无需频繁的手动修改。
- 管理生成项目间的依赖关系,实现增量构建、并行构建。
-
解决方案生成与构建配置管理
- 定义目标平台类型(win,linux,mac,game console...)与优化级别(debug,release...)
- 动态配置包含项目
Github: https://github.com/ubisoft/Sharpmake
2.基本使用方式
2.1 Sharpmake常用特性
https://github.com/ubisoft/Sharpmake/blob/main/Sharpmake/Attributes.cs
[Sharpmake.Generate]: 标记需要被生成的解决方案和项目类。被此属性修饰的类会被Sharpmake编译器处理和生成对应的Visual Studio解决方案或项目文件。
[Sharpmake.Export]: 标记需要被导出以供其他Sharpmake脚本使用的类型。是一种"逻辑项目",不会实际生成项目文件。
[Configure]: 用于标记项目或解决方案的配置方法。这些方法会在生成过程中被调用,用于设置编译选项、依赖关系、项目预编译宏等构建配置。
[Fragment]: 用于定义项目配置的特性标记。通过Fragment可以为项目指定不同的构建变体,并通过AddFragmentMask控制项目间的依赖关系。
2.2 Sharpmake常用函数
2.2.1 AddTargets
可以在Project或Solution的CTOR中调用,以声明解决方案或项目所支持的开发环境与目标平台等,但是这里有一些需要注意的点,以明白"缺失败xxx构建目标"错误的由来:
-
Solution构造函数中的AddTargets是在解决方案层面定义构建目标,它的主要作用是:
- 确定整个解决方案支持的平台和配置组合
- 为解决方案中的所有项目提供一个统一的构建环境
- 控制构建方案级别的构建选项
-
Project构造函数中的AddTargets的作用基本与Solution差不多,但是仅针对单个项目自身的目标配置
-
关于Solution与Project在AddTargets调用方面的关系:
- 如果Project没有显式定义自己的Targets,则会使用包含它的Solution中定义的Targets会作为默认值(额外注意,Project的显式Targets定义可以继承自父类的构造函数中的定义)
- Project最终显式定义的所有Targets必须等价于Solution Targets,如果Project中定义了Solution中的不存在的Target,这些额外的Target最终会被过滤掉。
-
关于Project间依赖在AddTargets调用方面的关系:
//ProjectA.sharpmake.cs [Configure] public void Configure(Configuration conf, Target target) { conf.AddPrivateDependency<ProjectB>(target); }此时ProjectB的Targets也需要等价于当前ProjectA的Targets,否则将在依赖解析过程发生报错,即使ProjectA添加了一个Solution中不存在,且最终会被忽略的Target。
2.2.2 AddFragment
可以在Project或Solution的CTOR中调用,用于标记和过滤项目配置的机制,为项目定义特定的构建特征或属性,这些特征会影响项目在依赖关系中的行为。
当项目 A 依赖项目 B 时,AddFragmentMask 决定了项目 A 在不同 Target 配置下应该链接项目 B 的哪些配置版本。这种机制通过以下方式工作:
public class CoreLibrary : Project
{
public CoreLibrary()
{
AddTargets(new Target(
Optimization.Debug | Optimization.Release
));
[Configure]
public void ConfigureAll(Configuration conf, Target target)
{
if(target.HaveFragment<Optimization>())
{
var fragment = target.GetFragment<Optimization>();
System.Console.WriteLine($"{fragment} fragment be setted.");
}
}
}
}
public class GameProject : Project
{
public GameProject()
{
AddTargets(new Target(
Optimization.Debug | Optimization.Release | Optimization.Retail
));
// 指定在Game只使用CoreLibrary的Debug和Release版本,此时将不会产生依赖解析报错
AddFragmentMask(Optimization.Debug | Optimization.Release);
}
[Configure]
public void ConfigureAll(Configuration conf, Target target)
{
conf.AddPrivateDependency<CoreLibrary>(ReferencePrivate);
}
}
Output:
Debug fragment be setted.
Release fragment be setted.
当依赖者添加了具体的FragmentMask,则被依赖者的Targets就必须含有对应的定义,否则将会产生缺少配置项错误。
2.2.3 AddPublicDependency/AddPrivateDependency
在有[Configure]特性修饰的成员函数中调用,用于管理项目间依赖关系。
[Sharpmake.Generate]
public class MathLibInvoker : Project
{
public MathLibInvoker()
{
Name = "MathLibInvoker";
SourceRootPath = @"[project.sourcePath]\MathLibInvoker";
}
public override void Config(Configuration conf, Target target)
{
// ... 其他代码
conf.AddPublicDependency<Mathematics.MathLibraryDLL>(target);
}
}
2.2.4 AddDefine/AddExportDefine
在有[Configure]特性修饰的成员函数中调用,用于为生成的项目添加预编译宏,其中:
- AddDefine仅作用于生成的项目自身
- AddExportDefine将会把宏定义传播到其他依赖它的项目中,假设有一个单条依赖链:
- 若为私有依赖,则传播终止于依赖者。
- 若为公有依赖,则传播将根据依赖链继续向外传递,直到遇到某一个私有依赖为止。
二、SDK集成
1.前期准备
1.1 前往SDK官网下载目标版本的SDK包,并解压到本地
- 参考SDK官方提供的SDK文档,检查SDK中的文件是否存在缺失或配置不正确的情况。
1.2 将SDK文件拷贝到项目目录中
- 参考SDK文档,将必要的头文件与源文件拷贝到项目对应的SDK管理目录下。
2.开始集成
2.1 一般SDK的常见形式与对应的基本接入流程

2.2 编写Sharpmake脚本
- 根据SDK内容,确定SDK对应的sharpmake项目结构,并编写xxx.sharpmake.cs脚本。
2.3 运行Sharpmake.Application
可以通过cmd直接运行sharpmake.application.exe
- 示例: sharpmake.application /souces('${project.main.sharpmake.cs}')
2.4 编写SDK的调试代码
打开生成/更新后的解决方案,并根据团队的代码规范,参考SDK官方文档编写SDK的测试代码(可以是SDK初始化/接口调用等简单代码),并在API调用的关键步骤打印 入参、结果等日志以验证SDK的运行情况。
2.5 构建目标平台
对于本地测试,如果你集成了一个客户端的SDK,那么可以编译客户端的target,相应的,如果是服务端SDK,那么编译服务端target。
对于提交测试,请注意条件编译,因为你的测试代码可能没有同时兼顾这些情况。
2.6运行以测试调试代码
- 运行目标平台构建出的可执行文件,收集日志文件,结束运行。
- 根据输出的日志结果判断调试代码是否存在错误调用,并视情况修改调试代码。
当调试代码运行结果均符合期望,则基本完成了SDK的接入工作。