Writing .NET Core Global Tools with F#

The release of .NET Core 2.1 brought with it a bunch of great additions, and one of the ones I've been looking forward to the most is the addition of support for creating global tools. This has always been a great feature in the JavaScript world, allowing you to write and distribute command-line tools via npm.

F# has long been my favorite go-to language for scripting, and as such I've compiled quite a few F# scripts that I run regularly for a variety of things. With the addition of global tooling support in .NET Core, now I can create actual command-line tools from those scripts and even distribute them via NuGet!

Introducing: fsharpsay

I'll admit up front that in order to come up with an easy example to show off, I decided to reinterpret Microsoft's dotnetsay example from the aforementioned blog post. This one will be totally different, though...I'll use the F# logo instead of .NET Bot! Let's take a look at how easy it is to create a new tool.

First, we'll create a new F# console app:

dotnet new console -lang F#

In the generated fsproj file add a PackAsTool element such that the whole file looks like:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <RootNamespace>FSharpSay</RootNamespace>
    <PackAsTool>true</PackAsTool>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>
</Project>

Next we'll create a function that takes in a message and prints it coming from the F# logo:

let sayIt = printfn @"
            %s
            __________________
                              \
                                \
                                `  ` 
                              `/+  /:` 
                            `/ss+  /++:`
                          `/ssss+  /++++:.
                        ./ssssss+  /++++++/.
                      ./ssssssss+  /++++++++/.
                    ./ssssssssss+  /++++++++++/.
                  ./ssssssssssss+  +++++++++++++/-
                ./ssssssssssssss/  /++++++++++++++/-`
              ./ssssssssssssss+.    ./+++++++++++++++-`
            ./ssssssssssssss+.        ./+++++++++++++++-`
          .+ssssssssssssss+.  `:+       ./+++++++++++++++:`
        .+ssssssssssssss+.  `:os+         ./+++++++++++++++:`
      .+ssssssssssssss+.  `:osss+           ./+++++++++++++++:`
    .+ssssssssssssss+.  `:osssss+             ./+++++++++++++++/.
  .+ssssssssssssss+.  `:osssssss+               ./+++++++++++++++/.
  /sssssssssssssso-   .osssssssss+                 -++++++++++++++++:
  -+ssssssssssssso/`  `:osssssss+               `:+++++++++++++++/.
    -+ssssssssssssso/`  `:osssss+             `:+++++++++++++++/.
      -+ssssssssssssss/.  `:osss+           `:+++++++++++++++/.
        -+ssssssssssssss/.  `:os+         .:+++++++++++++++:.
          -+ssssssssssssss/.  `:+       ./+++++++++++++++:`
            .+ssssssssssssss/.  `     ./+++++++++++++++:`
              .+ssssssssssssss/.    ./+++++++++++++++:`
                .+ssssssssssssss/  :+++++++++++++++-`
                  .+ssssssssssss+  /+++++++++++++-` 
                    .+ssssssssss+  /++++++++++/-` 
                      .+ssssssss+  /++++++++/-  
                        .+ssssss+  /++++++/. 
                          .+ssss+  /++++/. 
                            .+ss+  /++/. 
                              .++  /:. 
                                `  `      "

F# is known for brevity, but unfortunately there's not much that can be done with the big logo!

Finally, the entry point for the app:

[<EntryPoint>]
let main argv =
    match argv with 
        | [|message|] -> message
        | _ -> "F# rocks!"
    |> sayIt

    0

That's actually all you need. A global tool really is just a console app, meaning you can even test it out via dotnet run the way you normally would for a console app.

Packaging and Installing

Now let's go ahead and create a redistributable tool out of the app by creating a NuGet package:

dotnet pack -c release -o nupkg

This will create a standard nupkg package for the app, which you could then push to NuGet itself or any other feed of your choosing. For now, let's just install it from the local file:

dotnet tool install --add-source ./nupkg -g fsharpsay

By specifying -g, we're saying that we want this tool to be available globally on the machine.

Running the Tool

Now that it's installed, we can go ahead and run it from the command line the same way you would any other app in your path:

> fsharpsay "F# rocks"

            F# rocks
            __________________
                              \
                                \
                                `  ` 
                              `/+  /:` 
                            `/ss+  /++:`
                          `/ssss+  /++++:.
                        ./ssssss+  /++++++/.
                      ./ssssssss+  /++++++++/.
                    ./ssssssssss+  /++++++++++/.
                  ./ssssssssssss+  +++++++++++++/-
                ./ssssssssssssss/  /++++++++++++++/-`
              ./ssssssssssssss+.    ./+++++++++++++++-`
            ./ssssssssssssss+.        ./+++++++++++++++-`
          .+ssssssssssssss+.  `:+       ./+++++++++++++++:`
        .+ssssssssssssss+.  `:os+         ./+++++++++++++++:`
      .+ssssssssssssss+.  `:osss+           ./+++++++++++++++:`
    .+ssssssssssssss+.  `:osssss+             ./+++++++++++++++/.
  .+ssssssssssssss+.  `:osssssss+               ./+++++++++++++++/.
  /sssssssssssssso-   .osssssssss+                 -++++++++++++++++:
  -+ssssssssssssso/`  `:osssssss+               `:+++++++++++++++/.
    -+ssssssssssssso/`  `:osssss+             `:+++++++++++++++/.
      -+ssssssssssssss/.  `:osss+           `:+++++++++++++++/.
        -+ssssssssssssss/.  `:os+         .:+++++++++++++++:.
          -+ssssssssssssss/.  `:+       ./+++++++++++++++:`
            .+ssssssssssssss/.  `     ./+++++++++++++++:`
              .+ssssssssssssss/.    ./+++++++++++++++:`
                .+ssssssssssssss/  :+++++++++++++++-`
                  .+ssssssssssss+  /+++++++++++++-` 
                    .+ssssssssss+  /++++++++++/-` 
                      .+ssssssss+  /++++++++/-  
                        .+ssssss+  /++++++/. 
                          .+ssss+  /++++/. 
                            .+ss+  /++/. 
                              .++  /:. 
                                `  `

Available on NuGet

Clearly this is a must-have tool that the world needs, so I went ahead and made the source available on GitHub and published the tool to NuGet. To install it, simply run this and you're good to go:

dotnet tool install -g fsharpsay

Now if you'll excuse me, I've got some more global tools to convert!

comments powered by Disqus
Navigation