Say Goodbye to NAnt and MSBuild for .NET Builds With IronRuby

Writing

Just the other day I put a tweet out on Twitter where I exclaimed that I am not a fan of XML based build systems. Yes, I understand their purpose, and yes they were great in their time, and yes I still use them on almost a daily basis, but I really think that we have better ways that we could be doing this. The most common response from my tweet was “but was is the alternative?” And I thought, yeah, one of the most important things that I have learned in my lifetime is that you never complain about something unless you have a solution. And so I am here today to propose to you a solution…

The Problem With XML Based Build Systems

Before we get into this entire discussion, let me ask you a question… what is it that developers do better than anything else? Got your answer? Well, I know what mine is, and that answer is “write code”. And if that is the case, then why are we so intent on shoving things into giant xml files? Why can’t we write code? The usual argument is that XML based applications allow us to more easily make changes without recompiling, they allow for more flexibility, and they make it easier to write tooling since they are based on a format that everyone can parse. But personally, I don’t think that they offer much value, and instead are the project of the XML hysteria that swept through the programming world a few years ago. In fact, those of us who have been working with these XML based build tools for years probably forget just how ridiculous and obtuse they looked when we first saw them.

So while many people are quick to extol the virtues of XML, they often people forget to point out all of the things we lose when we start writing code in XML. For example, how about all of the tools that our industry has spent so many years crafting and refining. We lose our editors, debuggers, libraries, and all of the other tools that we use in order to get a few minor advantages that XML provides us. Those XML files limit us to the degree where we often find ourselves falling back to writing custom tasks in order to get around those limitations. Custom tasks which are not written in XML, but instead have to be written in code, since after all, the XML in these files is really just a hideously complex configuration system for the code which is actually running underneath.

Okay, so what are the advantages that we have with XML again? We can parse it easily? Okay, but how often do you write a parser for your build files? You just write your build file and pass it into NAnt. No parsing needs to be done by you. Doesn’t need to be compiled? Okay, that is a decent argument, since after all we don’t want to have to build our build script before we can build our application. In fact, if the build script is there to build your software, then would you need a build script for your build script? Hmmmm. Chicken, meet egg. But you see, this is merely a limitation of the short list of languages that most of us box ourselves into. Wouldn’t it be nice if we had a language that we didn’t need to compile? And we could execute it in the same way that NAnt or MSBuild works, by just pointing an executable at a file? Well, we have numerous languages like that, and in fact, we have several which run on the .NET platform.

If you have followed my blog for any period of time you will know that I am a fan of Ruby. I don’t program in it much professionally, but I am in constant awe of the simplicity and the beauty of the language. Within the Ruby community, there is just as much of a need for a build process as there is in our statically typed languages, even though they don’t really have a compile step. This is one of the biggest misconception about having a build in the .NET world, it isn’t just about compiling the software people! Phew, I feel better. In languages like C#, sure the build needs to compile the software, but after that you’ve got a lot more work to do. The build often needs to prepare a testing environment, run tests, copy files, package up deployments, and in some cases even push out deployments. The build is the rock upon which a repeatable process is built.

Okay, so we have Ruby, and in fact, IronRuby 1.0 just got release a little bit ago. Surely we can leverage IronRuby and some other Ruby goodness in order to create builds that are both easy and fun! Right? Sure we can!

Very Short Guide To Getting IronRuby On Your Machine

The first step in your journey is to get IronRuby on your system and working. Go to http://ironruby.net/ and click the download link. Then go and download the zip file for the correct version of IronRuby for your version of .NET. At the time of writing, there are two versions, one for .NET 2.0 and the other for .NET 4.0. Right now I am still working with the .NET 2.0 version, but the process should be identical.

Once you have downloaded the zip file, extract it to somewhere on your computer such as c:\tools\ironruby. The next thing that you need to do is to add that path to your IronRuby bin folder to your path. This can be done by going into your System Properties, clicking on the advanced tab and then clicking the “Environment Variables…” button. If you are using Windows 7, just type environment into the start menu and it’ll have an option for editing your system environment variables. Just find your path variable and add “c:\tools\ironruby\bin” to it. Obviously replace “c:\tools\ironruby” with wherever on your machine you saved it to.

Now that you have IronRuby on your machine, and the bin folder in your path, you should be able to open up a command prompt and type in the command “ir” and run the IronRuby interpreter. You’ll get a prompt for the IronRuby REPL (Read Execute Print Loop) and you can type in something like:

puts "CodeThinked.com is awesome!!"

In order to test it. Just type “exit” to quit. Congrats, you now have IronRuby up and running on your system! Wasn’t that easy? Now we are going to take a look at Ruby’s build tool, rake.

Running Rake

Rake is exactly the sort of build tool that we were talking about earlier. It doesn’t use any XML, it is build scripts that are written entirely in Ruby. Honestly, if you are writing in an interpreted language, then there is virtually no advantage to shoving this sort of task into XML when you can just write code to do it. IronRuby comes with its own version of rake, and in order to make sure that we have it up and running correctly, go ahead and type “rake” into the command prompt. After a few seconds you should get an error telling you that it could not find any rake files. So, let’s just start off by creating a project and a simple rake file.

The first thing I am going to do is create a new folder for my project. I’m going to create mine at c:\development\RakeTest, but you can create yours wherever you like. In that folder create a file called Rakefile.rb. Open that file in your favorite text editor, and put in the following source:

require 'rake'

task :default => [:congratulate_me]

desc "Tells you that you are awesome"
task :congratulate_me do 
    puts "Congrats, you have rake running. Wasn't that easy?"
end

Now all you have to do is navigate to the folder where this file is located (in your command prompt), and run the “rake” command. It should write out to the screen “Congrats, you have rake running. Wasn’t that easy?”

And it sure was, wasn’t it? You now have a full build tool at your disposal. In the ruby code above, you can see the we are importing the rake library, setting the default task to our congratulate_me task, and then we define the task along with a description of the task. It looks almost like a simple nant script, but *gasp*, we are writing code.

Let’s see now what we would need to do in order to have some additional tasks and then chain those together, or start up our rake script from alternate tasks. So first, let’s go ahead and add two additional tasks to this script:

task :congratulate_team do
    puts "Congrats to the team!"
end

task :congratulate_everyone => [:congratulate_me,:congratulate_team] do
    puts "Phew, that was a lot of congrats"
end

You see that things are getting a bit more complex. We have one more task which just writes out to the console, but we have another task with some sort of array next to the name. Well, that is the same functionality we get with NAnt when we tell it that a task depends on other tasks. This allows us to run additional tasks before a give task is run. Now if we update our Rakefile.rb so that the default task is congratulate_everyone, if we run it we will see that the other two tasks are executed before the congratulate_everyone task.

We don’t have to specify the task as the default task though, we can also run specific tasks by name. So if we want to execute the congratulate_team task, we simply need to execute “rake congratulate_team”.

At this point, we have emulated much of what the NAnt or MSBuild frameworks provide us. This is because when it really comes down to it, they just provide us with a set of tasks that we can string together in order to perform specific actions. We have tasks here, along with the ability to string them together and execute them in an organized fashion. We don’t need to anything special around configuration because we can just throw variables in the file or read variables from whatever data source we want. We can also include other ruby files, so we can easily bring in other settings, code, or tasks from different places. It seems that we just get so much of the stuff that XML build tools give us, but for free.

At this point though, there is just one problem. It is the plethora of tasks that both MSBuild and NAnt provide us that really gives us the power, right? If we don’t have tasks that build solutions, run tests, access source control systems, copy files, zip files, push to ftp, etc… then aren’t we losing quite a bit? I’m glad you asked.

Making Rake More .NET Friendly

Now you might be wondering, where are all of these tasks going to come from? Either I am going to have to write all of these tasks (as my colleague Chris Allport did), or there has to be a project out there which is already doing this. And guess what! There is! There is a cool little project out on GitHub, called Albacore (don’t go there to install it, I’ll show you how in a second), and it already has many of these tasks in there just waiting for you to use them. But first, let’s go ahead and get Albacore setup on your machine.

Since you already installed IronRuby, you have IronRuby’s version of gem, called igem. gem (and igem) are a tool which allows you to install packages onto your machine for use within your ruby projects. It is responsible for copying the source down to your machine along with dependencies so that you can be up and running in mere seconds. Since IronRub is already in your path, you should be able to just type “igem install albacore” in console, and away you go! Once it is finished, all you have to do is to add a single line into your rake file and you can immediately being using it:

require 'albacore'

So let’s take a look at a very simple build on a sample project. In this case I am just going to create a .NET console application called RakeTestApp. Then I can start off by adding a build config to my projects called something like “AutomatedRelease”. Then I can go into the properties on each project in the solution and set the output directory for the build to “../build_output”. This way I can simply invoke the msbuild task that albacore provides on the solution file in order to compile my solution.

The first task I am going to create is one that will invoke all of my other tasks in order:

task :full => [:clean,:build_release,:run_tests]

The second task I am going to create is one that will delete my build_output folder so that I can ensure a clean build:

task :clean do
    FileUtils.rm_rf 'build_output'
end

As you can see here, we don’t need any special tasks to do this because we already have classes within the standard ruby library which do things like delete folders. Next I will create my first task which is based on an Albacore task, and that is the msbuild task which is going to compile the solution:

msbuild :build_release do |msb|
  msb.properties :configuration => :AutomatedRelease
  msb.targets :Build
  msb.solution = "RakeTestApp/RakeTestApp.sln"
end

So instead of basing this task off of “task” you see that we are instead using “msbuild” and then we are naming it “build_release”. The msbuild task takes a single parameter which we use to set the configuration for the task. Here we are setting the build configuration to AutomatedRelease and we are setting the path of the solution to build. We also have some tests that we need to run, and so we have included nunit in the project. We can base a task off of the Albacore NUnit task which looks like this:

nunit :run_tests do |nunit|
    nunit.path_to_command = "tools/nunit/nunit-console.exe"
    nunit.assemblies "build_output/RakeTestAppTests.dll"
end

Again, this task takes just one parameter which lets us set some parameters on the task. Here we simply need to tell the task what executable to use for nunit and a comma separated list of assemblies to run tests within.

All Together Now

Now that we have gone over each individual part, let’s look at it as a whole:

require 'rake'
require 'albacore'

task :default => [:full]

task :full => [:clean,:build_release,:run_tests]

task :clean do
    FileUtils.rm_rf 'build_output'
end

msbuild :build_release do |msb|
  msb.properties :configuration => :AutomatedRelease
  msb.targets :Build
  msb.solution = "RakeTestApp/RakeTestApp.sln"
end

nunit :run_tests do |nunit|
    nunit.path_to_command = "tools/nunit/nunit-console.exe"
    nunit.assemblies "build_output/RakeTestAppTests.dll"
end

That is it! We have created a simple build using IronRuby, Rake, and Albacore. Now this is far from everything that Albacore can do. If I wanted to run ndepend, ncover, sftp, sql commands, zip directories, ssh, unzip, etc… I can easily leverage Albacore tasks in order to perform these actions.

Final Thoughts And Source

If you feel like I do and you are tired of being shackled to your typical XML based builds, then I would encourage you to give IronRuby, Rake, and Albacore a shot. Worse case scenario, you waste a little bit of time. Best case scenario, you are freed from a life of XML configuration stress. Hope you enjoy! Check out the link below to get the source.

Download Sample IronRuby And Rake Build

Loved the article? Hated it? Didn’t even read it?

We’d love to hear from you.

Reach Out

Comments (40)

  1. Thanks for writing about this. I have re-visited this topic again and again during the pre-release stages of IronRuby. IronRuby is very well suited for this type of use, as it is smaller than the one-click installation of MRI and it is self-contained. That means, I can (probably) include it in a project’s tooling if necessary.

  2. @Curtis Yep, that was one of my first thoughts, and I am pretty sure that this is doable. It is something that I am going to be experimenting with next. I absolutely insist that everything (within reason) be put into source control.

  3. The key take away is that the logic of a build script is a programming problem and proper programming tools should be used. XML was NEVER designed for programming it really was more for data exchange. Some people just keep trying to twist it for scripting purposes though, big mistake.

  4. Strange, because on my fresh install of IronRuby 1.04 I got this message:

    "ERROR: could not find gem albacore locally or in a repository"

    Adding gemcutter as a source corrected this.

    Running "igem sources" shows me:

    http://gems.rubyforge.org/
    http://gemcutter.org

    I don’t know for sure that gemcutter wasn’t there beforehand, but I’m betting IronRuby’s gem only comes with rubyforge.org as a source.

  5. @Troy Yeah, but in that post I referenced above, one of the line items is "gemcutter.org, gems.rubyforge.org, and rubygems.org all point to the same place" They should all be the same. I definitely installed albacore without adding a source. Hmmmm, this is definitely an odd situation.

  6. Blargh. Sorry for the comment spam. I now see what you mean – rubyforge.org and gemcutter.org resolve to the same address. Still odd that I got that error message until I added the source though, eh? Probably just an environment issue on my end – [b]nothing to see here, people![/b] 🙂

  7. @Troy Nah, don’t worry about it, I appreciate the comments. If there is an issue with my post, I want to make sure it gets fixed! Hopefully someone else will comment on whether or not it worked for them with a fresh IronRuby install.

  8. I used IronRuby instead of MRI because my focus is on .NET and Microsoft development. If need be it can interop with .NET assemblies and other .NET code in order to perform different actions. In the end though, you can use either Ruby runtime, they will both get the job done.

  9. @Jonathan I think we are talking about two different things. You are talking about actually compiling the code, I am talking about all of the things that go into a build. Sure, for the actual compiling part, I agree that storing this information as data makes sense. I wouldn’t want to specify every single source file in Ruby, that would be dumb. Hence why in the IronRuby based solution we are still calling into MSBuild for the process of compiling the software, which uses the solution file, which is XML.

    When I am talking about a build, I am mostly referring to the plethora of things that need to occur around the process of compiling the software. Personally, I think that most of these tasks are just simple development tasks that have been shoehorned into xml.

  10. In my world, the "plethora of things that need to occur around the process of compiling the software" is usually handled by autotools configure+make. Which sucks, hard, and is something I’d like to remove (by migrating to xbuild/MSBuild).

    In my world, if it’s not handled by configure or make, it doesn’t exist.

    So I suppose Rake could be a replacement for configure, but configure isn’t "in charge of" the build. It’s responsible for setting up an environment in which make(1) can execute, and I consider make(1) to be "in charge of" the build. (Though I suppose this depends upon the definition of "in charge of" — is the worker following instructions in charge of the work, or the manager that wrote the instructions? Hm…)

  11. How about implementing this in C# using the mono "Compiler as a Service" offering? Ruby is cool but if I’m working in the C# world it would be nice to write my build scripts directly in C# too.

    Another benefit is that you could use the huge library of C# tools for zipping, compiling and FTPing (is that a word?) the code around.

  12. This looks great! I always thought XML was the wrong tool for an obviously procedural task like a build script.

    What’s the status of integration with CruiseControl.NET? Will it work with the MSBuild logger, is there a Rake logger, or does a module need to be written before this will play nice with CC.NET?

  13. @Josh I don’t use CC.NET, so I’m not sure how well it integrates with it. I would assume that CC.NET captures anything that is written out to the console. I’d be curious to hear your results if you try it out.

    @Todd Thanks, I’ll make a note of that in the post.

  14. Good article. However, let me point something out since I’m already using NAnt.

    So I can install IronRuby, learn Ruby and write new build scripts to do the same thing as I’m already doing? And if so, why Ruby and not do the same thing in .NET? I can build my script in pure C# and avoid having to learn Ruby all together. Besides, it’s much easier to use NAnt build scripts in a production/deploy scenario where I only have to copy the NAnt bin directory and my build scripts run fine.

  15. @Boyan, your points make perfect sense. If you’re comfortable with Nant or MSBuild, then there is no reason for you to change.

    However, I would not use C# over a scripting language for this type of automation for a couple of reasons.

    First, the slower development cycle of a compiled, static language is a turn-off when it comes to this type of automation. Even MSBuild allows me to have a faster development cycle than the edit-compile-execute cycle of C#.

    Second, I think another good argument is having the right tool for the job. XML was not intended to be used as a declarative programming language in the way made popular by Ant, Nant, and MSBuild. In fact, this type of task-oriented development is one of the strong suits of scripting languages.

    If you’re going to add a scripting language to your toolbox, Ruby is a great one to have. Rake is a great complimentary library to both your Ruby skills and your .NET skills.

    Note: this is not about Ruby evangelism, as much as it’s about practical use.

    Sorry for the comment spam. I received some, so I thought I’d dish out a little bit too 🙂

  16. OMG!! We are back to makefiles… scary !! I juste appends to dev in c++ after a few years in C#, and this kind of build process is one of the thing that make me sad… Please do not make this an habit !

  17. Great article! been looking for an excuse to move away from my Nant/MSBuild "scripts" since, like you, I prefer to code than write noisy xml.

    IronRuby is a great language/tool for .Net developer and well worth the time learning it. Where I work, we’ll now be using it as a build and QA test platform, and for pretty much any coding task for which a full-fledged .Net project is plain overkill.

    We’ve also been integrating IronRuby in our in-house apps to provide for a quick, yet very powerful, built-in scripting engine. It’s been amazingly easy to do so and the added benefits has saved our a…s many times.

    I only wish our web guys would move to Rails…

  18. Brilliant. I’m not a .NET user (I dabble occasionally but I’m a Mac/Ruby/Java guy) firmly in the "just say NO to XML configuration file" camp. IronRuby+Rake+albacore look like a great alternative to NAnt.

    As for Jonathon Pryor’s objection about making the build run concurrently? I’m not sure why this can’t be done with Ruby. If we suddenly decide on a new way to compile C# code, one just needs to swap out the code at the relevant bit of, say, albacore. The user simply updates the gem. If you can structure your XML to describe the higher-level description of what you want, your Rakefile can similarly contain Ruby syntax describing the project at an equally high level (by declaring things as objects) that some library code can read. You don’t need XML or SQL for to describe a build process declaratively.

    All sorts of Ruby packages are doing this, as well as deploy scripts like Capistrano. Look at a deploy.rb script if you don’t believe me: my capistrano script contains a lot of "set" and "role" commands that describe the things like SSH settings, and then at the end it has one or two tasks defined procedurally. The difference is that I can understand and edit this file in a real programming language (Ruby) not a bloody XML file. If Capistrano gets optimized to run concurrently, that’s fine by me…

    In Java-land, Buildr is slowly becoming an alternative to Ant and Maven. Certainly, for simpler projects you can use Buildr. The only thing it is a bit ropey on is following the dependency graph.

    I don’t buy your argument about compiling though. You can have a build script that is written in a compiled language like C#. Scala’s simple-build-tool (sbt) shows the way here. Here, you define a class that inherits from a variety of different project templates and you put it in the project/build/ folder. The methods on the class become tasks and you define dependencies as instance properties. You write the project definition in Scala. Then you have a script called sbt, which just invokes the Java runtime which runs a file called sbt-launcher.jar – this is the runtime which invokes a compiler on the project definition. Once that is done compiling, it moves onto compiling the rest of the project. Compiler gymnastics are pretty much standard anyway as the Scala compiler often has to deal with mixed Scala-Java code.

    sbt really is a great build tool – it has combined the simplicity and extensibility of Rake with the power of managed dependencies that Maven gives. Now Buildr is coming along and offering similarly that to the rest of the Java world – hopefully IronRuby and Rake will do similarly for the .NET world – and then we might be free of Ant-style XML configuration files. That’d be great, right?

  19. Thanks Justin for a well written Article. It makes complete sense to provide other alternatives to current build systems. In my opinion, currently, many developers do not feel comfortable with their build systems where as most of them feel comfortable in their programming language.

    I think it is good have many alternatives. Why not provide native support in C#, VB .Net etc. People can pick and choose their favorite languages. The ability to pick preferred language must have contributed to success of .Net already. Why not extend the same concept to build systems.

  20. Getting tired of throwing new languages into the mix. We’re switching to C# from an Nant build file. Loved Nant but it’s becoming umanageable. So, compiling the code will be done by MSBuild THEN the compiled code will be called (via a single Nant Task) to deploy itself to the webservers etc.

    No need for XML or ruby, just good old C# and Visual Studio.

  21. I love Rake/Albacore about as much as I hate Nant!

    It didn’t use to be this way and nant was my tool of choice for automating svn, build, packaging, and test deployment. It finally enabled me to convert the team to continuous build and unit-testing in a productive manner. However, over time, the xml just became so large and complex that nobody wanted to work with it! xml is just too noisy and definitely *not* eye friendly.

    Rake, on the other hand, was very appealing from the get-go. Very human readable DSL (no you don’t really need to learn Ruby, just the DSL, as with any other scripting tool) and terse syntax makes for very explicit “make” files. None of our engineers complained about it once they got the basics (unlike with nant.)

    Additionally, for those who know Ruby, you’ve got a full language at your fingertip built in for free! Now, that’s of extreme value for anyone administrating custom/complex build scenarios.

    On top of that, if you use IronRuby instead of native Ruby, you’ve got a .Net language from which you can use your .Net dlls as is. How powerful is that! For example, we have candidate releases being built, packaged, and pushed to embedded test devices using a proprietary protocol implemented in .Net. The Rake script is fairly small compared to nant but mostly it’s so easy to work with. A very powerful build platform indeed.

    Yes, we could have scrapped a C# app to do the same but I challenge you to write something as terse and flexible as a set of Rake tasks.

    Rake rocks! play with it and you’ll probably love it.

  22. Interesting proposition, I myself however am firmly in the MSBuild Camp to be honest, but it is always interesting to hear the “other side’s” opinion on the matter.

    Based on your post it doesn’t seem like you have the ability to integrate into Visual Studio or Team Foundation Server builds as easily as you can with MSBuild. It also seems like you lose some of the nice built in abilities of MSBuild such as multi-processor builds (http://msdn.microsoft.com/en-us/library/bb383805.aspx) and the built in logging from the get go.

    Part of your complains seem to be around the loss of “the tools that our industry has spent so many years crafting and refining”. The post is almost a year old so your opinions might have changed, but most of the ‘tools’ that are missing are in fact present in MSBuild:

    1. Editor: VS2010 (and I believe 2008, but I am not 100% sure) support the editing of MSBuild Tasks (along with Intelisense!) and is the only way I edit my MSBuild scripts
    2. Debugger: While this is a pain, there is an undocumented debugger included with VS2010 that can be turned on with the flip of a few registry settings (http://blogs.msdn.com/b/visualstudio/archive/2010/07/06/debugging-msbuild-script-with-visual-studio.aspx) prior to this I used MSBuild Sidekick for most of my debugging.
    3. Libraries: as you noted yourself MSBuild can leverage the power of C# via CustomTasks. In addition you can also directly call some Framework libraries inline in the script should you choose to via PropertyFunctions http://msdn.microsoft.com/en-us/library/dd633440.aspx (I personally avoid this). It appears you can even create tasks on the fly now via Inline tasks http://msdn.microsoft.com/en-us/library/dd722601.aspx (although this seems like another thing that I’d personally avoid).

    I personally am a fan of the batching model within MSBuild. I know that is a sore point for a lot of developers (and catches me on occasion as well), however I feel once you understand the model writing a for loop to iterate through elements seems old hat.

    Other thing that I’ve become accustomed to is the ease in which you can create property groups with file listings and appending meta data to them. I admit that I have not had experience with Ruby so I don’t know if there is an equivalent but I can’t tell you how nice it is to be able to do something like this:

    csprojtypes

    vbprojtypes

    or something like this

    In the second example you get the advantage of not having to launch a second copy of MSBuild every time you go to build.

    The other obvious advantage is that MSBuild is shipped with the .NET Runtime and is a core component of Visual Studio, therefore you’re pretty much guaranteed to have it on your build box without jumping through hoops to install additional software.

    At the end of the day what works for you is what works. We have a division here at my company that refuses to use MSBuild for their build process and they’ve thrown together some python scripts to accomplish their build process. In my opinion I don’t think people invest enough time in learning the ins and outs of MSBuild to use if effectively and pass it off as ‘too difficult to use’.

    Cheers.

  23. MSBuild is nothing like the bad days of ant or Nant, anybody who tells you declarative XML is “programming in XML” hasn’t actually used the modern toolset.

    Hiarchy of Build tools, as follows.

    Worse is,
    Nant/Ant, procedural XML… it is programming in XML, and all you get is parsing, which nobody actually cares about except implementors.

    Slightly better, but not much,
    Rake, nRake, psake, Programming in scripting launguges, … if your gonna programming a custom process everytime, sure I guess it’s at least not terrible for the dev… considering we get nothing more anyway,…

    ideal,
    Maven, MSBuild, Declarative Build DSL, they provide actual structure for advanced tooling. If you need to program, you just program in a class to extend the DSL, cleanly separates your standard build from actual programming… AND AND AND, they prevent the programmer from coding themselves into an unsupportable custom build pipeline.

    Truth? Developers would rather just shit all over the floor, rather than plan for where the bathroom should be located. Most companies don’t have the expertise to pushback outside of development, thus when builds inevitably fail fingers get pointed as usual.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All