📜 ⬆️ ⬇️

Use Cake to build C # code.

Hello! I want to talk about a tool like Cake (C # Make).


Cake build


So what is Cake?


Cake is a cross-platform build system that uses DSL with C # syntax to do such things as build binaries from source codes, copy files, create / clean up / delete folders, archive artifacts, package nuget packages, runs a unit during the build process. -Tests and much more. Cake also has an advanced add-on system (just C # classes, often packaged in nuget). It is worth noting that a large number of useful functions are already built into Cake, and even more, for almost every occasion, are written by the community and are distributed quite successfully.


Sake uses a programming model called dependency based programming , like other similar systems like Rake or Fake . The essence of this model is that for the execution of our program we define the tasks and the dependencies between them. Read more about this model can be read by Martin Fowler .


This model forces us to represent our build process as some tasks (Task) and dependencies between them. In this case, the logical execution goes in the reverse order: we indicate the task that we want to perform and its dependencies, but Cake determines which tasks can be performed (for which they are resolved or there are no dependencies) and executes them.


dependency based programming example


So, for example, we want to execute A, but it depends on B and C, and B depends on D. Thus, Cake will execute them in the following order:


  1. C or D
  2. B
  3. A

The task (Task) in Cake is usually a complete piece of work on the assembly / testing / packaging. Declared as follows


Task("A") //  .Does(() => { // Task A }); 

To indicate that task A is dependent on, for example, task B, you can use the IsDependentOn method:


 Task("A") //  .IsDependentOn("B") .Does(() => { // Task A }); 

You can also easily set the conditions under which the task will or will not be performed using the WithCriteria method:


 Task("B") //  .IsDependentOn("C") .WithCriteria(DateTime.Now.Second % 2 == 0) .Does(() => { // Task A }); 

If some task depends on task B, and the criterion is false, then task B will not be executed, but the execution flow will go on and perform the tasks on which B. depends.
There is also an overload of the WithCriteria method, which takes as a parameter a function that returns bool. In this case, the expression will be counted only when the queue reaches the task, and not at the time of building the task tree.


Cake also supports some specific preprocessor directives, such as load , reference , tool, and break . You can read more about them on the corresponding documentation page .


I think that people who collect their projects in the era of DevOps with their hands are not so many. The advantage of any assembly system in comparison with manual assembly is obvious - an automatically tuned process is always better than manual manipulations.


Cake Benefits in C Development


Why use Cake, since there are many alternatives? If you are not developing in C #, then most likely there is nothing. And if you are developing, it seems reasonable to write assembly scripts in the same language in which the main project is written, since there is no need to learn another programming language and produce their zoo within the same code base. Therefore, build systems like Rake (Ruby) , Psake (Powershell) or Fake (F #) began to appear.


Cake is definitely not the only way to build a C # project. As options, you can cite as an example pure MSBuild, Powershell, Bat-scripts or CI Server like Teamcity or Jenkins, however all of them have both advantages and disadvantages:



Cake Installation


Now let's talk about how to execute scripts with tasks. Cake has a boot loader that does everything for you. You can download it either manually or by the following powershell command:


 Invoke-WebRequest http://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1 

The downloaded build.ps1 file will then load the required cake.exe itself, if it has not yet been loaded, and execute the cake script (by default, this is build.cake ), if we call it with the following command:


 Powershell -File ".\build.ps1" -Configuration "Debug" 

We can also pass command line arguments to build.ps1 , which will then be executed. They can be either built-in, for example, the configuration , which is usually responsible for the configuration of the assembly, or they can be set independently - in this case there are two ways:



Examples


Well, now let's move on to practice. You can easily imagine a typical build cycle of a nuget package:


nuget pack pipeline



Build dll


To build our solution from sources, you need to do 2 things:



We describe the task of building the solution on cake-dsl:


 var configuration = Argument("configuration", "Debug"); Task("Build") .Does(() => { NuGetRestore("../Solution/Solution.sln"); DotNetBuild("../Solution/Solution.sln", x => x .SetConfiguration(configuration) .SetVerbosity(Verbosity.Minimal) .WithTarget("build") .WithProperty("TreatWarningsAsErrors", "false") ); }); RunTarget("Build"); 

The build configuration, respectively, is read from the command line arguments using the Argument function with the default value "Debug". The RunTarget function starts the specified task, so that we can immediately verify the correctness of our cake-script operation.


Unit tests


To run unit tests compatible with nunit v3.x, we need the NUnit3 function, which is not included in the Cake distribution and therefore requires connection via the preprocessor directive #tool. The #tool directive allows you to connect tools from nuget packages, which we will use:


 #tool "nuget:?package=NUnit.ConsoleRunner&version=3.6.0" 

In this case, the task itself is extremely simple. Do not forget, of course, that it depends on the Build task:


 #tool "nuget:?package=NUnit.ConsoleRunner&version=3.6.0" Task("Tests::Unit") .IsDependentOn("Build") .Does(()=> { NUnit3(@"..\Solution\MyProject.Tests\bin\" + configuration + @"\MyProject.Tests.dll"); }); RunTarget("Tests::Unit"); 

We pack everything in nuget


To package our assembly in nuget, we use the following nuget specification:


 <?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>Solution</id> <version>1.0.0</version> <title>Test solution for demonstration purposes</title> <description> Test solution for demonstration purposes </description> <authors>Gleb Smagliy</authors> <owners>Gleb Smagliy</owners> <requireLicenseAcceptance>false</requireLicenseAcceptance> <tags></tags> <references> <reference file="MyProject.dll" /> </references> </metadata> <files> <file src=".\MyProject.dll" target="lib\net45"/> <file src=".\MyProject.pdb" target="lib\net45"/> </files> </package> 

Put it in the folder with the build.cake script. When executing the Pack task, transfer all the necessary artifacts for packaging to the ".. \. Artefacts" folder. To do this, make sure that it is (and if not, create it) using the EnsureDirectoryExists function and clear it using the CleanDirectory function built into Cake. Using the same functions for copying files, we move the necessary dlls and pdb to the folder with refractories.


By default, the collected nupkg will go into the current folder, so we specify the folder ".. \ package" as the OutputDirectory , which we also created and cleared.


 Task("Pack") .IsDependentOn("Tests::Unit") .Does(()=> { var packageDir = @"..\package"; var artefactsDir = @"..\.artefacts"; MoveFiles("*.nupkg", packageDir); EnsureDirectoryExists(packageDir); CleanDirectory(packageDir); EnsureDirectoryExists(artefactsDir); CleanDirectory(artefactsDir); CopyFiles(@"..\Solution\MyProject\bin\" + configuration + @"\*.dll", artefactsDir); CopyFiles(@"..\Solution\MyProject\bin\" + configuration + @"\*.pdb", artefactsDir); CopyFileToDirectory(@".\Solution.nuspec", artefactsDir); NuGetPack(new FilePath(artefactsDir + @"\Solution.nuspec"), new NuGetPackSettings { OutputDirectory = packageDir }); }); RunTarget("Pack"); 

Publish


To publish packages, use the NuGetPush function, which takes the path to the nupkg file, as well as the settings: a link to the nuget feed and API key. Of course, we will not store the API Key in the repository, but transmit it outside again using the Argument function. As nupkg, we simply take the first file in the package directory that matches the mask using GetFiles . We can do this because the directory has been pre-cleaned before packing. So, the publication task is described by the following dsl:


 var nugetApiKey = Argument("NugetApiKey", ""); Task("Publish") .IsDependentOn("Pack") .Does(()=> { NuGetPush(GetFiles(@"..\package\*.nupkg").First(), new NuGetPushSettings { Source = "https://www.nuget.org/api/v2", ApiKey = nugetApiKey }); }); RunTarget("Publish"); 

Simplify your life


While debugging a cake-script, and just for debugging a nuget-package, you can not publish it every time to a remote feed. This is where the WithCriteria function that we looked at will come to us with help. We will pass the PublishRemotely flag to the script with the parameter (by default set to false) in order to determine whether to put the package in a remote feed by the value of this flag. However, cake will not execute the script if we miss the task that the RunTarget functions specified . Therefore, we will create a dummy empty BuildAndPublish task, which will depend on Publish :


 Task("BuildAndPublish") .IsDependentOn("Publish") .Does(()=> { }); RunTarget("BuildAndPublish"); 

And add a condition to the Publish task:


 var nugetApiKey = Argument("NugetApiKey", ""); var publishRemotely = Argument("PublishRemotely", false); Task("Publish") .IsDependentOn("Pack") .WithCriteria(publishRemotely) .Does(()=> { NuGetPush(GetFiles(@"..\package\*.nupkg").First(), new NuGetPushSettings { Source = "https://www.nuget.org/api/v2", ApiKey = nugetApiKey }); }); 

The script for assembling and publishing a nuget-package is almost ready, it remains only to combine all the tasks together. The final version of the code can be found in the repository on github .


Conclusion


We looked at the simplest example of using cake. This could be integration with slack, monitoring code coverage with tests and much more. Having a rich system of add-ons, an active community, as well as pretty good documentation, cake is a pretty good alternative to CI systems and MSBuild for building C # code.


')

Source: https://habr.com/ru/post/329194/


All Articles