跳转至

构建脚本基础

构建脚本

每个构建脚本(build script)都继承自 DefaulBuildScript 类,因此必须实现 DefaulBuildScript 中的两个抽象方法(abstract method)。

  • ConfigureTargets:用于创建将执行特定工作的新目标(targets)。

空构建脚本示例:

1
2
3
4
5
6
public class BuildScript : DefaultBuildScript
{
    protected override void ConfigureTargets(ITaskContext session)
    {
    }
}

目标


目标(target)用于按特定顺序执行特定工作。目标可执行诸如 FlubuCore 内置任务(如编译解决方案的任务)和一些自定义 C# 代码。目标也可以依赖于其他目标(other targets)。

创建新目标

下例将创建在任务中执行一次构建的新目标。

1
2
3
4
5
6
protected override void ConfigureTargets(ITaskContext context)
{
    context.CreateTarget("Compile")
        .SetDescription("Compiles the solution")
        .AddTask(x => x.CompileSolutionTask());
}

目标也可以通过方法上的特性来进行定义。

1
2
3
4
5
6
7
[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 任务,执行顺序与代码中指定的顺序一致。

1
2
3
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() - 任务日志将不会输出到控制台;

1
2
3
4
context.CreateTarget("Example")
    .AddTask(x => x.CompileSolutionTask())
    .AddTask(x => x.PublishNuGetPackageTask("packageId", "pathToNuspec"))
    .When(c => c.BuildSystems().Jenkins().IsRunningOnJenkins);
  • 当且仅当满足指定条件时设置任务参数:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 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() - 交互地将参数从控制台传递给任务的方法或参数。

Task attributes (Versioning)

It is possible to execute some tasks with attribute on property

Flubu will inject return value of the task to the property. This is especially usefull for all versioning tasks, basically all tasks that return a value. See FlubuCore.Tasks.Attributes namespace for all available attributes.

1
2
[FetchBuildVersionFromFile]
public BuildVersion BuildVersion { get;  }
1
2
[GitVersion]
public GitVersion GitVersion { get;  }

This allows you to access version information in ConfigureTarget which is not possible if versioning task is executed for example as target dependency

1
2
3
4
5
protected override void ConfigureTargets(ITaskContext context)
{
        context.CreateTarget("Build")
            .AddCoreTask(x => x.Build().Version(BuildVersion.Version.ToString()));
}

自定义 C# 代码/任务

下例将执行一些自定义代码。你可以在自定义代码中使用 FlubuCore 的内置任务:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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);
}

你也可以使用带参方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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。

1
2
3
var targetA = context.CreateTarget("TargetA");
var targetB = context.CreateTarget("TargetB");
var targetC = context.CreateTarget("TargetC").DependsOn(targetB, targetA);

It is also possible to reverse dependency

1
var targetC = context.CreateTarget("TargetC").DependenceOf(targetA);      

在目标中添加目标

通过 AddTarget,一个目标可在另一个目标内部执行。目标将按添加顺序执行。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    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 执行顺序为:

  1. 构建任务;
  2. TargetB 目标;
  3. JustAnExample 方法。

在不同目标中复用任务集

下例展示了如何在不同目标中复用任务集(reuse set of tasks):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 循环中为目标添加多个任务:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  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 子句有条件地执行任务。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 相同。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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:可以在组中任意一个任务发生错误时执行一些自定义操作。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 方法异步执行依赖项。

在下例目标中并行执行三个任务。

1
2
3
4
session.CreateTarget("run.tests")
    .AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName1"))
    .AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName1"))
    .AddTaskAsync(x => x.NUnitTaskForNunitV3("TestProjectName3"));

异步方法和同步方法也可以混合使用。

1
2
3
4
5
6
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) 来启用,且必须放在异步任务/目标依赖项之前,否则日志就不是顺序的了。

1
2
3
4
5
6
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 method: Parameter specified in required method must not be null otherwise target execution will fail before any task get executed.

上下文功能

  • Log:context.LogInfo("Some Text2", ConsoleColor.Blue);
  • GetVsSolution:获取解决方案和项目信息 context.GetVsSolution();
  • GetFiles: Get Files from specified directory with option to filter files with glob pattern context.GetFiles(OutputDirectory, "*.nupkg");
  • GetDirectories: Get Directories from specified directory with option to filter files with glob pattern context.GetFiles(OutputDirectory, "*.nupkg");
  • GetEnviromentVariable 方法:根据名称(name)获取环境变量 context.GetEnvironmentVariable("someVariable");

使用 RunProgramTask 在构建脚本中运行程序或命令

1
2
3
4
5
6
7
8
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 的例子:

1
2
3
4
5
6
7
protected override void ConfigureTargets(ITaskContext session)
{
    var runExternalProgramExample = session.CreateTarget("systemctl.example")
        AddTask(x => x.RunProgramTask(@"systemctl")
            .WithArguments("start")
            .WithArguments("nginx.service"));
}

构建属性

You can define various build properties with Attributes on properties or in ConfigureBuildProperties method (old way) to share them in different tasks and custom code.

Following example shows how to share solution file name and configuration across various targets/tasks.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    [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());
}

Alternative:

1
2
    [BuildProperty(BuildProps.BuildConfiguration)]
    public string BuildConfiguration { get; set; } = "Release";
If Solution file name and path would not be set through build property attributes you would have to set it in each task separately.

like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 配置文件的设置或环境变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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 的环境变量。

还有一种更为复杂的方法来给任务传递控制台参数、设置和环境变量:

1
2
3
4
5
6
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 等)的多种信息,如构建。提交等。

1
2
3
4
5
protected override void ConfigureTargets(ITaskContext context)
{
    bool isLocalBuild = context.BuildSystems().IsLocalBuild;
    var gitCommitId = context.BuildSystems().Jenkins().GitCommitId;
}

构建事件

  • OnBuildFailed 事件:
1
2
3
4
5
6
public class BuildScript : DefaultBuildScript
{
    protected override void OnBuildFailed(ITaskSession session, Exception ex)
    {
    }
}
  • 在目标执行前后执行的事件:
1
2
3
4
5
6
7
protected override void BeforeTargetExecution(ITaskContext context)
{
}

protected override void AfterTargetExecution(ITaskContext context)
{
}
  • 在构建执行前后执行的事件:
1
2
3
4
5
6
7
protected override void BeforeBuildExecution(ITaskContext context)
{
}

protected override void AfterBuildExecution(ITaskSession session)
{
}

脚本中的部分类和基类

如果部分类(partial classes)和基类(base classes)位于同一个目录下,则会自动加载它们;否则,必须使用 Include 特性来添加。