Building DotNet Core with Bazel

- Developer and Blogger
Published: Sun Aug 11 2019

It’s still a bit too early to use the .Net Bazel rules in a real enterprise project, but it’s at least possible to start doing some small POCs. In this article I will show how I built a .Net Core RabbitMQ queue producer/consumer using a Bazel setup.

For some background on Bazel and its benefits I recommend checking out my previous article first.

Bazel Rules

The Bazel rules can be downloaded from the Bazelbuild org on Github. In this experiment I am primarily using the core_binary rule, which is the rule designed to create .Net Core binaries like console applications. See the example below:

load("@io_bazel_rules_dotnet//dotnet:defs.bzl", "core_binary") core_binary( name = "src", srcs = [ "Program.cs" ], deps = [ "@io_bazel_rules_dotnet//dotnet/stdlib.core:mscorlib.dll", "@rabbitmq.client//:netcoreapp2.1_core", ], visibility = ["//visibility:public"], )

In this sample rule I am building the console app that will act as the producer of my RabbitMQ messages. The consumer is identical since it’s just another console application.

In addition to telling Bazel to build Program.cs, I am also declaring dependencies on the RabbitMQ nuget package and .Net’s mscore lib. The nuget package is declared separately using the nuget_package rule as seen below:

nuget_package( name = "RabbitMQ.Client", package = "RabbitMQ.Client", version = "5.1.0", core_files = { "netcoreapp2.1": [ "lib/netstandard1.5/RabbitMQ.Client.dll", "lib/netstandard1.5/RabbitMQ.Client.xml", ], }, core_lib = { "netcoreapp2.1": "lib/netstandard1.5/RabbitMQ.Client.dll", } )

In my sample I am only showing the .Net Core parts of the Nuget package rule, but it can be configured to support other .Net environments as well.

The nuget integration is perhaps the most painful part with the current Bazel rules. Since packages tend to come as a large dependency graph of recursive dependencies, it’s impractical to hand code the nuget_package rules. To help with this there is a tool in the rules repo to generate WORKSPACE rules for all necessary nuget packages. Ideally this tool should be integrated as a Visual Studio plugin to make the integration more seamless.

Dev Environment

In my experiment I used regular Visual Studio on my Mac to do the coding, but triggered builds on the command line using IBazel, which is a Bazel file watcher. This enables me to automatically restart the application on every file save, which makes for a good dev experience.

In order to benefit from intelisense inside Visual Studio I ended up installing the nuget packages in Visual Studio as well. This is kind of redundant since Bazel already downloads them, but it makes for a better dev experience in the editor since you get intelisense through Visual Studio. Hopefully there will be better Visual Studio integration eventually.

Demo

I have uploaded the code to a Github repo in case you are interested in trying it out yourself.

For reference I have included the RabbitMQ Producer/Consumer code below:

Producer
using System; using System.Text; using System.Threading; using RabbitMQ.Client; namespace Producer { class Program { static void Main(string[] args) { var factory = new ConnectionFactory() { HostName = "localhost" }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) { channel.QueueDeclare(queue: "demo-greeting", durable: true, exclusive: false, autoDelete: false, arguments: null); while (true) { string message = $"Hello World! {DateTime.Now.ToString()}"; var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "", routingKey: "demo-greeting", basicProperties: null, body: body); Console.WriteLine(" [x] Sent {0}", message); Thread.Sleep(1000); } } } } }
Consumer
using System; using System.Text; using RabbitMQ.Client; using RabbitMQ.Client.Events; namespace Consumer { class Program { static void Main(string[] args) { var factory = new ConnectionFactory() { HostName = "localhost" }; var connection = factory.CreateConnection(); var channel = connection.CreateModel(); channel.QueueDeclare(queue: "demo-greeting", durable: true, exclusive: false, autoDelete: false, arguments: null); var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) => { var body = ea.Body; var message = Encoding.UTF8.GetString(body); Console.WriteLine(" [x] Received {0}", message); }; channel.BasicConsume(queue: "demo-greeting", autoAck: true, consumer: consumer); Console.WriteLine(" Press [enter] to exit."); Console.ReadLine(); } } }

I invite you to follow me on twitter