Software rot is inevitable if projects are not maintained with care. It is nice to think that in today’s write once, run anywhere world, a rock-solid application with no planned changes can continue to work perhaps without even recompiling; of course, reality is a lot more complex. Frameworks are updated (hello, .NET 5!), new libraries are released, and you never know when a security vulnerability will require an immediate patch and redeploy regardless of how stable your own code is. To that end, let’s revisit the Letter Boxed codebase and update it for the new decade (already in progress).
First order of business: upgrade to .NET 5.0! Two years ago, .NET Core 2.2 was the latest and greatest. Needless to say, a lot has happened in the .NET world since then. Luckily, compatibility is a strong focus for every .NET release. In this case, I encountered no major issues in the upgrade — it was almost as simple as changing the
<TargetFramework> in each .csproj file: [see commit on GitHub]
Next up: use CMake instead of .vcxproj files! As you may recall, I wrote a series of posts to show how to leverage CMake for native app development of a cross-platform project. While cross-platform is not a primary focus here, the simplicity that CMake brings combined with relatively good Visual Studio support is a boon for development. This change was significantly more involved, since I opted to concurrently migrate to GTest for better CMake integration. However, I hope you’ll agree that the result is quite a bit better than carrying around .sln and .vcxproj files: [see commit on GitHub]
On to the next improvement: slngen + traversal projects! Checked in solution files are passé. It’s easy to generate a solution file with slngen given a set of
dirs.proj traversal projects using the corresponding MSBuild SDK. Even though I added several files (including global.json), I achieved net negative lines after removing the .sln: [see commit on GitHub]
Embrace central package versioning! Did you know that the .NET SDK now supports central package versioning out of the box? I didn’t either until I saw a tutorial from Stuart Lang. Previously I had only been aware of the add-on SDK for the same, but I’ll gladly take a simpler built-in solution: [see commit on GitHub]
Upgrade all the packages! With our new central registry of packages, it’s simple to do a comprehensive upgrade in one localized change: [see commit on GitHub]
Unify the output directory! In my earlier exploration of CMake + .NET interop, I showed how to ensure the .NET and native artifacts landed in the same output root (effectively achieving a read-only source tree). This time there was a small complication because of the BenchmarkDotNet projects; normally, these would generate customized benchmark projects at runtime and build them with the .NET SDK, but this causes issues with a heavily customized output path. Luckily there is an
[InProcess] job option to completely bypass the external compile/run step which got things working perfectly: [see commit on GitHub]
Finally, enhance the command line experience! Again, drawing from the CMake saga, I added a build.cmd script to automate the build steps for both C++ and .NET, all from the comfort of the command line: [see commit on GitHub]
As a final note, I have a new computer which means I need to reestablish the benchmark baselines. Let’s run
build release and execute the managed and native Letter Boxed solvers for old time’s sake:
|Puzzle||.NET Core Solver||C++ Solver|
[000.000] Loading trie... [000.052] Loaded 162309 words. [000.053] Finding valid words... [000.059] Found 754 valid words. [000.059] Finding solutions... MARKETPLACE-EARWIG WIGWAM-MARKETPLACE PRAGMATIC-CAKEWALK [000.065] Done.
[000.000] Loading trie... [000.044] Loaded 162309 words. [000.044] Finding valid words... [000.048] Found 754 valid words. [000.048] Finding solutions... MARKETPLACE-EARWIG WIGWAM-MARKETPLACE PRAGMATIC-CAKEWALK [000.050] Done.
[000.000] Loading trie... [000.052] Loaded 162309 words. [000.052] Finding valid words... [000.059] Found 1436 valid words. [000.060] Finding solutions... ROBAND-DEFECTING -- snipped 72 other solutions -- OBTECTED-DRAFTING [000.095] Done.
[000.000] Loading trie... [000.045] Loaded 162309 words. [000.045] Finding valid words... [000.050] Found 1436 valid words. [000.050] Finding solutions... BAITED-DEFORCING -- snipped 72 other solutions -- OBTECTED-DRAFTING [000.080] Done.
As expected, we see a nice improvement over the previous results thanks to Moore’s Law (and perhaps the steady improvement of the .NET runtime). Whew! Nothing like a good spring cleaning to revitalize a “legacy” project.
Pingback: Letter Boxed: Rust impl, part 3 (modules, trie, I/O) – WriteAsync .NET
Pingback: Letter Boxed: Rust impl, part 4 (complete solver) – WriteAsync .NET