A lot of folks miss out on some of the key tools that I use in nearly all of my projects, and I thought this issue would be a great time to share them with all of you!
Paket
First, up Paket. Paket is a fully open-source, minimal package manager for .NET that aims to integrate with NuGet, but fixes a few of the issues that drove creators to create Paket and improve the ecosystem. Paket supports all .NET languages, even Nemerle!
Dependencies are organized with three files. There's a single paket.dependencies at the solution level that lists every dependency for your whole solution. That looks something like this first example. (If you downloaded the code samples from my article in CODE Magazine's March/April 2017 issue, “Accessing Your Data with F# Type Providers,” http://www.codemag.com/Article/1703051), you'll recognize this paket.dependencies
file. At the top, include a list of sources. Here, I'm using NuGet, but you can also use GitHub and HTTP. Then, list the packages you need to collect from each source.
source https://api.nuget.org/v3/index.json
nuget FsLab
nuget SwaggerProvider
Next, each project has a paket.references
file listing the explicit references that the particular project will use. In this case, there's only one project, so you list both of the references from the paket.dependencies
file, FsLab and SwaggerProvider.
FsLab
SwaggerProvider
Finally, a paket.lock
file is generated for the entire solution, containing additional data, such as the specific version. In this case, FsLab requires several additional dependencies, and the file itself is quite large. For a sample of the file, you can look at Figure 1, but I'm not going to print the entire file for the article.
These files make Paket use simple, thorough, and convenient.
Why should you even consider an alternative when NuGet seems perfectly sufficient? Here are a few of my favorite personal reasons.
No Package Version in the File Path
NuGet specifically includes the package version in the file path, and Paket doesn't. This change in Paket is an especially large improvement for F# script files. Because a script file can be treated as an entire project in a single file, it means that you must specifically declare your references in the file. This is most often done with a #r directive, using the full path to the dll. This means that every time you update your versions, every script you have that references the previous version no longer works. By updating the path to no longer include the explicit package version, Paket saves you a mountain of work.
By updating the path to no longer include the explicit package version, Paket saves you a mountain of work.
Transitive Dependencies
Transitive dependencies are the dependencies that the dependencies you've added have. Makes complete sense, right? Let's look at Figure 2 to make that a little more clear. Within a project that you've created, you have several dependencies. In this case, I've called out two: dependency #1 and dependency #2. Dependency #1 has a dependency that you haven't explicitly added but that will need to be included in your project for dependency #1 to function correctly. This is called a transitive dependency because you haven't yet added it to your project because you specifically needed the functionality it contains. It's only there because another dependency requires it.
When you use NuGet, all your installed packages are listed in one list, in the packages.config
file, and it isn't very clear which are transitive dependencies and which are your actual dependencies. In Paket's three-file system, only your direct dependencies are listed in both the paket.references
file and the paket.dependencies
file. The only place that transitive dependencies appear is in the generated paket.lock
file. For further clarification, they've even indented the file. As an example, look back to Figure 1 to see part of the paket.lock
file from my previous article's example code.
Reference Single Files
In addition to referencing files from NuGet and similar internal package management tools such as MyGet or ProGet, you can reference projects and single files straight from GitHub into your solution. You can also reference a single file through HTTP. Let's look at an example that builds on the paket.dependencies
file that I showed above. First, I'll add a whole GitHub repository. This needs only the owner and repository name combo. Next, to reference a specific single file, add the path from the top-level directory in that repository. Finally, let's reference a file using HTTP. In this case, the URL doesn't end with a qualified file name, so you leave a space and create one. This downloads the file. I call it 6c.fs
in this example.
source https://api.nuget.org/v3/index.json
nuget FsLab
nuget SwaggerProvider
github rachelreese/StockTicker
github rachelreese/Minesweeper MS/Utils.fs
http http://www.fssnip.net/raw/6c 6c.fs
To reference a single file into your solution using the paket.references
file, preface it with File
, like so:
File: Utils.fs
File: 6c.fs
You can't use the paket.references
file to reference the full repository into your solution, but because the whole repository is downloaded locally, you can browse and add a specific project or file to your solution.
Only One Package Version Per Solution
If you've ever run into endless accidental dependency hell, you'll be relieved to know that Paket has the perfect fix. There's only one package version per solution. All dependencies are fully reconciled so that you don't accidentally have several versions of one transitive dependency. Now, if you must have separate versions, and you have a very good reason for needing them, it's possible to override this behavior by using dependency groups. To create a dependency group in your paket.dependencies
file, just use the keyword group
.
If you've ever run into endless accidental dependency hell, you'll be relieved to know that Paket has the perfect fix: one package version per solution.
For example, let's add a group containing the Azure Storage Type Provider to the same paket.dependencies
file that I've been using as an example. To do so, you simply add a group with any name you like. In this case, I've chosen the name AzureTypeProvider
to indicate what's contained in the group. Within the group, you'll need to add a new source and then specify the packages, repositories, or files to reference. This time, I'm only going to reference the type provider itself. The indentation isn't required, but I find that it makes it significantly more readable.
source https://api.nuget.org/v3/index.json
nuget FsLab
nuget SwaggerProvider
group AzureTypeProvider
source https://api.nuget.org/v3/index.json
nuget FSharp.Azure.StorageTypeProvider
Updating Framework Versions is Easier
When you reference a package with NuGet, only the correct references for the currently targeted framework version are downloaded. This is fine until you decide to upgrade framework versions. At this point, you may start finding very strange errors hidden deep in your code that you may eventually be able to trace back to using the wrong references that target the wrong framework version. When you switch framework versions, it's important to fully reinstall all packages. I've worked in plenty of shops that didn't know this. With Paket, it's not necessary to remember to reinstall. All framework versions are downloaded on install and use attributes to select the appropriate version for the currently targeted framework version.
Semantic Versioning
Finally, the issue that particularly concerns many, many developers that I see: NuGet doesn't fully support Semantic Versioning.
Semantic Versioning is a set of guidelines to use for versioning your software. It means that version numbers across several projects can make sense relative to one another. Basically, given a version number (such as 14.05.23) that's separated into three parts, you should increase:
- The first number, 14, when you have made backward-incompatible changes
- The second number, 05, when you have added functionality in a backward-compatible manner
- The final number, 23, when you make backward-compatible bug fixes.
There are a few additional points for adding labels for additional data, such as 14.05.23+alpha or 14.05.23-alpha. Paket fully supports Semantic Versioning, and, as of this writing, NuGet doesn't.
Paket fully supports Semantic Versioning, and, as of this writing, NuGet doesn't.
For more information on Paket, and a few additional reasons to use it, check out https://fsprojects.github.io/Paket/faq.html.
F# Formatting
F# Formatting is a fantastic way to generate literate documentation for your projects. Although it's called F# Formatting, the project also generates literate documentation for other languages. There are a few extra special features if you're working with F# code, though.
F# Formatting works by parsing markdown files and formatting your code, and generating docs that combine both in a beautiful manner. If you're using F#, you can parse F# script files and include your documentation right alongside your code. The F# Formatting project also provides hover tips for F#. Let's look at an example F# script file. You can see the full file in Listing 1 and the output in Figure 3.
Listing 1: Example F# Formatting script contained in an F# Script file. Documentation works side by side with code.
(**
Partial Application
===============
Here's an example of partial application.
This function is the Minkowski distance. Then, I use
partial application to create the Manhattan distance
and the Euclidean distance from the Minkowski distance.
*)
(*** include: Minkowski ***)
(** where `list_difference` is defined as: *)
let list_difference = List.map2 (fun x y -> x - y)
(*** define: Minkowski ***)
/// Minkowski distance = (SUM (| x ? y |^p) )^(1/p)
let minkowski p list1 list2 =
let abs_powered p (x:float) = abs x ** p
let distance =
list_difference list1 list2
|> List.map (abs_powered p)
|> List.sum
distance ** (1.0/p)
/// Manhattan distance = SUM(| x - y |)
let manhattan = minkowski 1.0
/// Euclidean distance = SQRT ( SUM(| x - y |^2) )
let euclidean = minkowski 2.0
First, you can use standard markup to create headings, change font styles, or add links, so here I'm just creating a heading for the page, and adding some up-front text about what should come next.
(**
Partial Application
===============
Here's an example of partial application.
This function is the Minkowski distance.
Then, I use partial application to create the
Manhattan distance and the Euclidean distance
from the Minkowski distance. *)
Let's skip to the end of the example and look at some simple code next. This is basic code. I'm simply defining two functions, Manhattan and Euclidean. There's no need to add tags or tell the program that you've written code. The parsers just know. Adding comments using a triple slash, as is standard, provides the description to the tooltips for that function. See Figure 4 for an example.
/// Manhattan distance =
/// SUM(| x - y |)
let manhattan = minkowski 1.0
/// Euclidean distance =
/// SQRT ( SUM(| x - y |^2) )
let euclidean = minkowski 2.0
Now, here's a fun example. Sometimes, with documentation, it doesn't make sense to discuss the code in strict order, but for the tooltips to show up correctly, it's important to have all the code in the file in the correct order. In this next snippet, I use include
and define
to reorganize how the code should read.
I want to first discuss a code snippet, the longer Minkowski
function, and define one of the functions that it relies upon later. I use define
above the Minkowski
function. Then, I can use include
wherever I'd like the function to be placed in the documentation. Finally, I display the list_difference
function after the include
section but before the define
section, using back ticks to make sure that the name of the function correctly stands out. Using this trick, I can make sure that the tooltips correctly display, as in Figure 5.
(*** include: Minkowski ***)
(** where `list_difference` is defined as: *)
let list_difference = List.map2 (fun x y -> x - y)
(*** define: Minkowski ***)
/// Minkowski distance =
/// (SUM (| x ? y |^p) )^(1/p)
let minkowski p list1 list2 =
let abs_powered p (x:float) =
abs x ** p
let distance =
list_difference list1 list2
|> List.map (abs_powered p)
|> List.sum
distance ** (1.0/p)
Note that I can add text comments at any point in and around the code for clarification.
FAKE
Next, let's check out FAKE. FAKE is a robust build-automation tool, similar to MAKE and RAKE, that uses the concept of targets to break apart the many pieces of a build process into more tangible chunks. Targets require a name and an action, and can be linked together.
Let's look at a sample FAKE script file to understand them better. You start by referencing and opening the FAKE library. Then there are two targets, Test
and Build
. Within the lambda function, you define what work they should do. Finally, you need to declare that Test
depends upon Build
.
#r "tools/FAKE/tools/FakeLib.dll"
open Fake
Target "Test" (fun _ ->
// Run some tests
)
Target "Build" (fun _ ->
// Build project
)
"Build"
==> "Test"
Let's look at a more complicated target now. For example, it's possible to run your tests as part of the build by creating a RunTests
target. This starts by finding the test assemblies, which I'll look at in more detail in a moment, and passing it to the included NUnit
function along with a set of parameters. In this case, I'm sending:
- DisableShadowCopy = true, which disables shadow copying of the assembly. This improves performance.
- TimeOut = TimeSpan.FromMinutes 20, which times out test running 20 minutes after starting.
- OutputFile = “TestResults.xml”, which specifies where the output file should be.
Target "RunTests" (fun _ ->
!! "/.test/NUnit.Test.*.dll"
|> NUnit (fun p ->
{ p with
DisableShadowCopy = true
TimeOut = TimeSpan.FromMinutes 20
OutputFile = "TestResults.xml" })
)
Now, let's look a little closer at how to find the test assemblies. FAKE uses its own globbing syntax. It's closely related to the syntax used in .gitignore
files, but does differ in a few places and includes several operators. First, the !! before the value means to include and scan all files matching the following pattern. In this case, the pattern is fairly simple. In the .test directory, search for all files that end in .dll, and start with NUnit.Test.
Here, I'm recursively searching in the tests directory for all bin subdirectories. Then, depending on whether the configuration
value is set to Debug
or Release
, I look in only the appropriate directory. This is flanked by </>, which adds a “/” to either side and preserves the path. Finally, I'm looking for items that contain the word “Tests” and end in .dll. Like this:
"tests/**/bin" </>
configuration </>
"*Tests*.dll"
It's also possible to automatically run your FxCop rules, create a NuGet package, or integrate Chocolatey, Slack, TeamCity, Canopy, and many other programs. Check out the FAKE documentation for more information on these.
It's also possible to automatically run your FxCop rules, create a NuGet package, or integrate Chocolatey, Slack, TeamCity, Canopy, and many other programs.
Using Paket with FAKE to Restore Packages at Build Time
Using Paket and FAKE together to restore packages at build time is usually done in a separate batch file as part of the set-up process. Let's go through an example file. First, you need a couple of standard commands. It's very common at the top of batch files to include the command. Ordinarily a script echoes the commands that it runs, and echo off turns that off. And by adding the @ at the beginning of the command, command echoing is turned off, and the request to turn off command echoing is turned off.
Next, there's a call to cls
, which clears the screen.
@echo off
cls
Next, there's a check to see that the Paket executable exists. If it doesn't, the Paket bootstrapper should be run. This causes the latest version of the executable to be downloaded and installed.
IF NOT EXIST .paket\paket.exe (
.paket\paket.bootstrapper.exe
)
Next, there?s a call to Paket restore to restore the packages.
.paket\paket.exe restore
If the previous statement returned any errors, you should exit. In general, successful code returns 0, and errors can be anything non-zero. In general, they're positive, but not always. The if errorlevel 1 construct has a fun little feature though: it checks whether the error code is anything larger than 1. In this case, I happen to know that Paket doesn't contain negative error numbers, so it's safe to only check positive non-zero numbers.
if errorlevel 1 (
exit /b %errorlevel%
)
Next, run a Paket update:
.paket\paket.exe update
Finally, you'll need a call to FAKE, sending the build.fsx
file and any other arguments from the command line.
packages\FAKE\FAKE.exe build.fsx %*
Putting this all together, you can see the script in full.
@echo off
Cls
IF NOT EXIST .paket\paket.exe (
.paket\paket.bootstrapper.exe
)
.paket\paket.exe restore
if errorlevel 1 (
exit /b %errorlevel%
)
.paket\paket.exe update
packages\FAKE\FAKE.exe build.fsx %*
More Information
For more information about using the three projects together, I recommend checking out the Project Scaffold repository: http://fsprojects.github.io/ProjectScaffold/. This project sets up FAKE to build documentation and create a NuGet package, as well as a few other useful examples.