构建脚本基础
构建脚本¶
每个构建脚本(build script)都继承自 DefaulBuildScript 类,因此必须实现 DefaulBuildScript 中的两个抽象方法(abstract method)。
- ConfigureTargets:用于创建将执行特定工作的新目标(targets)。
空构建脚本示例:
public class BuildScript : DefaultBuildScript
{
protected override void ConfigureTargets(ITaskContext session)
{
}
}
目标¶
目标(target)用于按特定顺序执行特定工作。目标可执行诸如 FlubuCore 内置任务(如编译解决方案的任务)和一些自定义 C# 代码。目标也可以依赖于其他目标(other targets)。
创建新目标¶
下例将创建在任务中执行一次构建的新目标。
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("Compile")
.SetDescription("Compiles the solution")
.AddTask(x => x.CompileSolutionTask());
}
目标也可以通过方法上的特性来进行定义。
[Target("targetName", "a", "b")]
[Target("targetName2", "c", "d")]
[Target("targetName3", "e", "f")]
public void Example(ITarget target, string source, string destination)
{
target.AddTask(x => x.CopyFileTask(source, destination, true));
}
你还可以通过控制台参数(console arguments)或 FlubuCore 配置文件向参数(parameter)传递值。
Flubu targetName2 -destination=SomeOtherDestination
任务¶
任务分为两种类型:(一般)任务(task)和 Core 任务。(一般)任务可以在 .NET 和 .NET Core 项目中执行,而 Core 任务只能在 .NET Core 项目中执行。
下例代码,在目标中执行了两个 Core 任务,执行顺序与代码中指定的顺序一致。
context.CreateTarget("Build")
.AddCoreTask(x => x.Restore())
.AddCoreTask(x => x.Build());
所有任务都有以下方法:
-
.OnError((c, ex) => { c.LogInfo("Example");}))
- onError 可在指定任务发生错误时执行一些自定义操作; -
.Retry(5, 1000)
- 重试机制(Retry Mechanism)。可在该机制重试任务期间应用特定条件(specific condition); -
.Finally(c => { c.LogInfo("Example");}))
- Finally 就像 try-cache-finally 里的 finally 块; -
.DoNotFailOnError()
- 脚本不会因发生异常而失败,你可为任务不失败时应用特定条件;
- .NoLog()
- 任务日志将不会输出到控制台;¶
-
.SetDescription()
- 覆盖(overrides)任务的默认描述; -
.ForMember()
- 将控制台参数传递给方法或属性,相关详细信息请查阅向任务传递控制台参数、JSON 配置文件的设置,以及基于 ForMember 的环境变量; -
在单个任务上使用 When 从句(when cluase),使任务有条件地执行(参阅下面的任务组):
context.CreateTarget("Example")
.AddTask(x => x.CompileSolutionTask())
.AddTask(x => x.PublishNuGetPackageTask("packageId", "pathToNuspec"))
.When(c => c.BuildSystems().Jenkins().IsRunningOnJenkins);
- 当且仅当满足指定条件时设置任务参数:
var compile = context
.CreateTarget("compile")
.SetDescription("Compiles the VS solution")
.AddCoreTask(x => x.Build().Configuration("Release")
.When(
() =>
{
return context.BuildSystems().IsLocalBuild;
},
task => { task.Configuration("Debug"); }));
.Interactive()
- 交互地将参数从控制台传递给任务的方法或参数。
任务特性(版本控制)¶
可以通过属性上的特定(Attribute)来执行某些任务。
Flubu 将会把任务的返回值注入到属性中。这对所有版本控制任务、尤其是有返回值的任务特别有用。有关所有可用的特性,请参阅 FlubuCore.Tasks.Attributes
命名空间。
[FetchBuildVersionFromFile]
public BuildVersion BuildVersion { get; }
[GitVersion]
public GitVersion GitVersion { get; }
你可以通过 ConfigureTarget 获取版本信息,而这些在版本控制任务已执行后是不可获得的(比如目标依赖项等)。
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("Build")
.AddCoreTask(x => x.Build().Version(BuildVersion.Version.ToString()));
}
自定义 C# 代码/任务¶
下例将执行一些自定义代码。你可以在自定义代码中使用 FlubuCore 的内置任务:
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("Example")
.Do(CustomCodeExample);
}
private static void CustomCodeExample(ITaskContext context)
{
//// You can put any c# code here and use any .net libraries.
Console.WriteLine("Dummy custom code");
context.Tasks().NUnitTaskForNunitV3("project name").Execute(context);
}
你也可以使用带参方法:
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("Example")
.Do(CustomCodeExample, "some value", 1);
}
private static void CustomCodeExample(ITaskContext context, string arg1, int arg2)
{
Console.WriteLine("Dummy custom code");
context.Tasks().NUnitTaskForNunitV3("project name").Execute(context);
}
目标依赖¶
目标可依赖于其他目标。所有依赖项将按指定顺序在目标执行前执行。
当 targetC 执行时,目标的执行顺序将是:TargetB、TargetA 和 TargetC。
var targetA = context.CreateTarget("TargetA");
var targetB = context.CreateTarget("TargetB");
var targetC = context.CreateTarget("TargetC").DependsOn(targetB, targetA);
也可以反转依赖关系:
var targetC = context.CreateTarget("TargetC").DependenceOf(targetA);
在目标中添加目标¶
通过 AddTarget,一个目标可在另一个目标内部执行。目标将按添加顺序执行。
示例:
protected override void ConfigureTargets(ITaskContext context)
{
var exampleB = context.CreateTarget("TargetB")
.Do(Something);
context.CreateTarget("TargetA")
.AddCoreTask(x => x.Build())
.AddTarget(exampleB)
.Do(JustAnExample);
}
public void JustAnExample(ITaskContext context)
{
...
}
TargetA 执行顺序为:
- 构建任务;
- TargetB 目标;
- JustAnExample 方法。
在不同目标中复用任务集¶
下例展示了如何在不同目标中复用任务集(reuse set of tasks):
protected override void ConfigureTargets(ITaskContext session)
{
session.CreateTarget("deploy.local").AddTasks(Deploy, "c:\\ExamplaApp").SetAsDefault();
session.CreateTarget("deploy.test").AddTasks(Deploy, "d:\\ExamplaApp");
session.CreateTarget("deploy.prod").AddTasks(Deploy, "e:\\ExamplaApp");
}
private void Deploy(ITarget target, string deployPath)
{
target
.AddTask(x => x.IisTasks().CreateAppPoolTask("Example app pool").Mode(CreateApplicationPoolMode.DoNothingIfExists))
.AddTask(x => x.IisTasks().ControlAppPoolTask("Example app pool", ControlApplicationPoolAction.Stop).DoNotFailOnError())
.Do(UnzipPackage)
.AddTask(x => x.CopyDirectoryStructureTask(@"Packages\ExampleApp", @"C:\ExampleApp", true).Retry(20, 5000))
.Do(CreateWebSite)
}
在 foreach 循环中为目标添加任务¶
下例展示了如何在 foreach 循环中为目标添加多个任务:
protected override void ConfigureTargets(ITaskContext context)
{
var solution = context.GetVsSolution();
context.CreateTarget("Pack")
.ForEach(solution.Projects, (item, target) =>
{
target.AddCoreTask(x => x.Pack().Project(item.ProjectName))
.Do(JustAnExample, item);
});
}
private void JustAnExample(ITaskContext context, VSProjectInfo vsProjectInfo)
{
//// Do something.
}
例中,示例程序将为每个项目执行 Pack 任务。
分组任务,并应用 When、OnError 和 Finally¶
- 在任务组上使用 When 子句有条件地执行任务。
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("Example")
.AddCoreTask(x => x.Build())
.Group(
target =>
{
target.AddCoreTask(x => x.Pack());
target.AddCoreTask(x => x.NugetPush("pathToPackage"));
},
when: c => !c.BuildSystems().Jenkins().IsRunningOnJenkins);
}
- 在任务组中使用 Finally:onFinally 的行为与 try-catch-finally 中的 Finally 相同。
context.CreateTarget("Example")
.AddCoreTask(x => x.Build())
.Group(
target =>
{
target.AddCoreTask(x => x.Pack());
target.AddCoreTask(x => x.NugetPush("pathToPackage"));
},
onFinally: c =>
{
c.Tasks().DeleteFilesTask("pathToNupkg", "*.*", true).Execute(c);
});
- 在任务组中使用 OnError:可以在组中任意一个任务发生错误时执行一些自定义操作。
context.CreateTarget("Example")
.AddCoreTask(x => x.Build())
.Group(
target =>
{
target.AddCoreTask(x => x.Pack());
target.AddCoreTask(x => x.NugetPush("pathToPackage"));
},
onError: (c, error) =>
{
//// some custom action when error occures in any of the task in group.
});
任务、自定义代码与依赖项的异步执行与并行执行¶
- 可通过 AddTaskAsync 或 AddCoreTaskAsync 方法异步执行任务。
- 可使用 DoAsync 方法异步执行自定义代码。
- 可通过 DependsOnAsync 方法异步执行依赖项。
在下例目标中并行执行三个任务。
session.CreateTarget("run.tests")
.AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName1"))
.AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName1"))
.AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName3"));
异步方法和同步方法也可以混合使用。
session.CreateTarget("async.example")
.AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName1"))
.AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName1"))
.Do(SomeCustomMethod)
.DoAsync(SomeCustomAsyncMethod2)
.DoAsync(SomeCustomAsyncMethod3);
上面代码中,将首先异步执行两个 nunit 任务,并等待两个任务完成;随后将同步执行 SOmeCustomMethod,执行完后再并行执行 SomeCustomAsyncMethod2 和 SomeCustomAsyncMethod3。
在异步执行的任务和目标中顺序打日志¶
通常来讲,在异步或并行执行多个任务时,日志是不可读(not readable)的。这就是为啥 FlubuCore 在异步任务中提供顺序记录(sequential logging)的原因。你可以在目标上使用 .SequentialLogging(true)
来启用,且必须放在异步任务/目标依赖项之前,否则日志就不是顺序的了。
context.CreateTarget("Test")
.SetAsDefault()
.SequentialLogging(true)
.AddCoreTaskAsync(x => x.Pack())
.AddCoreTaskAsync(x => x.Pack())
.DependsOnAsync(test2, test3);
在 FlubuCore runner 中并行执行的目标在默认情况下是顺序记录日志的。
flubu target1 target2 --parallel
其它功能¶
目标功能¶
- SetAsDefault 方法:当应用于目标时,如果在使用 runner 运行脚本时没有指定目标,则默认运行该目标;
- SetAsHidden 方法:当应用于目标时,目标将不会被显示在帮助信息中,并且它只能作为其它目标的依赖项来运行;
- Must 方法:设置必要条件,该条件必须满足,不然在任务执行之前目标就会执行失败。
- Requires 方法:在
required
方法中指定的参数不能为null
,否则在执行任务前就会失败。
上下文功能¶
- Log:
context.LogInfo("Some Text2", ConsoleColor.Blue);
; - GetVsSolution:获取解决方案和项目信息
context.GetVsSolution();
。 - GetFiles: 在选项中使用 Glob Pattern 模式筛选指定目录获得文件
context.GetFiles(OutputDirectory, "*.nupkg");
; - GetDirectories: 在选项中使用 Glob Pattern 模式从制定目录中获取目录
context.GetFiles(OutputDirectory, "*.nupkg");
; - GetEnviromentVariable 方法:根据名称(name)获取环境变量
context.GetEnvironmentVariable("someVariable");
;
使用 RunProgramTask 在构建脚本中运行程序或命令¶
protected override void ConfigureTargets(ITaskContext session)
{
var runExternalProgramExample = session.CreateTarget("run.libz")
.AddTask(x => x.RunProgramTask(@"packages\LibZ.Tool\1.2.0\tools\libz.exe")
.WorkingFolder(@".\src")
.WithArguments("add")
.WithArguments("--libz", "Assemblies.libz"));
}
Linux 的例子:
protected override void ConfigureTargets(ITaskContext session)
{
var runExternalProgramExample = session.CreateTarget("systemctl.example")
AddTask(x => x.RunProgramTask(@"systemctl")
.WithArguments("start")
.WithArguments("nginx.service"));
}
构建属性¶
你可以通过属性上的特性(attribute)或 ConfigureBuildProperties
方法(以前的方法)定义多个构件属性,在不同的任务和自定义代码中共享它们。
下例展示了如何在各种目标(Target)/任务(Task)间共享解决方案文件名和配置。
[SolutionFileName]
public string SolutionFileName { get; set; } = "FlubuExample.sln";
[BuildConfiguration]
public string BuildConfiguration { get; set; } = "Release";
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("build")
.AddCoreTask(x => x.Build());
context.CreateTarget("pack")
.AddCoreTask(x => x.Pack());
}
另外:
[BuildProperty(BuildProps.BuildConfiguration)]
public string BuildConfiguration { get; set; } = "Release";
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("build")
.AddCoreTask(x => x.Build()
.Project("FlubuExample.sln")
.Configuration("Release"));
context.CreateTarget("pack")
.AddCoreTask(x => x.Pack()
.Project("FlubuExample.sln")
.Configuration("Release"));
}
预定义的构建属性¶
一些构件属性已被预定义,可以通过接口获取:
context.Properties.Get(PredefinedBuildProperties.OsPlatform);
可用的预定义构件属性有:
- OsPlatform
- PathToDotnetExecutable
- UserProfileFolder
- OutputDir
- ProductRootDir
所有这些构建参数都可被覆盖。
向构建脚本属性传递命令行参数、JSON 配置文件设置或环境变量¶
可通过在属性上打 FromArg 特性的方式向构建脚本属性传递命令行参数、JSON 配置文件的设置或环境变量。
public class SimpleScript : DefaultBuildScript
{
[FromArg("sn", "If true app is deployed on second node. Otherwise not.")]
public bool deployOnSecondNode { get; set; }
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("Deploy.Exapmle")
.AddTask(x => x.FlubuWebApiTasks().GetTokenTask("user", "pass").SetWebApiBaseUrl("noade1Url"))
.AddTask(x => x.FlubuWebApiTasks().UploadPackageTask("packageDir", "*.zip"))
.AddTask(x => x.FlubuWebApiTasks().ExecuteScriptTask("Deploy", "DeployScript.cs"))
.Group(target =>
{
target.AddTask(x => x.FlubuWebApiTasks().GetTokenTask("user", "pass").SetWebApiBaseUrl("noade2Url"))
.AddTask(x => x.FlubuWebApiTasks().UploadPackageTask("packageDir", "*.zip"))
.AddTask(x => x.FlubuWebApiTasks().ExecuteScriptTask("Deploy", "DeployScript.cs"));
},
when: c => deployOnSecondNode);
}
}
FromArg 特性第一个参数(parameter)是参数键(argument key)。第二个参数用于在 flubu runner 中显示属性的帮助描述。实际上在属性上打特性并不是必须的,如果你没有添加这个特性,那么参数键(第一个参数)会与属性同名,属性的帮助信息不会显示在构建脚本的 runner 上。
支持的属性类型有:string、boolean、int、long、decimal、double 和 DateTime。
向构建脚本参数传递命令行参数。¶
Dotnet flubu Deploy.Example -sn=true
向构建脚本传递 JSON 配置文件的设置¶
- 在 FLubu runner 所在的目录下创建 FlubuSettings.json 文件;
- 以 JSON 格式的方式向文件中添加参数的键和值;
- 对于上面的例子,JSON 文件将看上去是这样子的:
json {“sn”:true,“SomeOtherKey”:“SomeOtherValue”}
- 对于不同的环境(如开发、测试和生产环境),通常有不同的配置。只需创建不同的 JSON 文件
FlubuSettings.{Environment}.Json
,并在需要的机器上设置环境变量 'ASPNETCORE_ENVIRONMENT' 即可; - 还可以按机器名
FlubuSettings.{MachineName}.Json
创建 JSON 配置文件,如果文件中的 MachineName 与本机机器名匹配,Flubu 将自动从该文件中读取设置。
向构建脚本传递环境变量¶
还可以通过环境变量设置脚本参数。环境变量前必须有前缀 flubu_
。
对于上面的例子,你可以通过 Windows 命令行工具添加系统环境变量: set flubu_sn = true
。
向任务传递控制台参数、JSON 配置文件的设置,以及基于 ForMember 的环境变量。¶
还有一种更为复杂的方法来给任务传递控制台参数、设置和环境变量:
protected override void ConfigureTargets(ITaskContext context)
{
context.CreateTarget("compile")
.AddTask(x => x.CompileSolutionTask()
.ForMember(y => y.SolutionFileName("someSolution.sln"), "solution", "The solution to build."));
}
- 第一个参数是需要传递的方法或属性的参数,如果在运行构建脚本时没有指定参数,则使用默认值;
- 第二个参数是参数键(argument key);
- 第三个参数是可选的,在目标的帮助中显式帮助信息。如果参数没有设置,则显示默认生成的帮助。
Dotnet flubu compile -solution=someothersolution.sln
构建系统提供者程序¶
你可以获取不同的构建系统(如 Jenkins、TeamCity、AppVeyor、Travis 等)的多种信息,如构建。提交等。
protected override void ConfigureTargets(ITaskContext context)
{
bool isLocalBuild = context.BuildSystems().IsLocalBuild;
var gitCommitId = context.BuildSystems().Jenkins().GitCommitId;
}
构建事件¶
- OnBuildFailed 事件:
public class BuildScript : DefaultBuildScript
{
protected override void OnBuildFailed(ITaskSession session, Exception ex)
{
}
}
- 在目标执行前后执行的事件:
protected override void BeforeTargetExecution(ITaskContext context)
{
}
protected override void AfterTargetExecution(ITaskContext context)
{
}
- 在构建执行前后执行的事件:
protected override void BeforeBuildExecution(ITaskContext context)
{
}
protected override void AfterBuildExecution(ITaskSession session)
{
}
脚本中的部分类和基类¶
如果部分类(partial classes)和基类(base classes)位于同一个目录下,则会自动加载它们;否则,必须使用 Include 特性来添加。