Who needs Chrome when you can use "IBM Web Browser"?
taylor price

祝日 / Permanent Vacation

PR's Tumblrdome
Xuebing Du
NASA

roma★

oozey mess

Discoholic 🪩
Keni

if i look back, i am lost

Love Begins
Show & Tell
wallacepolsom
todays bird
TVSTRANGERTHINGS

@theartofmadeline
art blog(derogatory)
I'd rather be in outer space 🛸
Misplaced Lens Cap
seen from United States
seen from United States
seen from United Kingdom

seen from France

seen from United States
seen from United States

seen from Türkiye
seen from United States
seen from Peru

seen from United States

seen from Germany

seen from Iceland
seen from United Kingdom

seen from United States

seen from Iceland
seen from Vietnam
seen from Netherlands
seen from France

seen from Germany
seen from United States
@waywardcode
Who needs Chrome when you can use "IBM Web Browser"?

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
A great way to see ASCII's internal organization.
Win32 equivalent of isatty()
I have started playing with Win32 C programming, which I've never done much (been a UNIX guy for a long time). One of the first things I had to search for was: how do I tell if a file handle is pointed at the console?
In POSIX-land, you use isatty(fileno(stdin)) to see if standard input is on a TTY. On Windows, here is the closest equivalent I can devise:
/* determine if the stdin handle is the console */ HANDLE h = GetStdHandle(STD_INPUT_HANDLE); if (h == NULL) return 1; DWORD ft = GetFileType(h); bool is_console = (ft == FILE_TYPE_CHAR);
All of the needed prototypes are pulled into Windows.h, which it seems all Win32 programs are expected to include. A little more granularity might have been nice, but I guess "include it all" is just how it evolved over time.
I plan to do more Win32 explorations in the coming months.
Go I Ching
Last night for fun I made a small library for I Ching exploration, and a toy cmdline app to generate hexagrams. It uses the same probabilities as the coin method, and just has a simple ascii interface:
$ ~/projects/go/bin/casthex Random I-Ching Cast: -- -- -- -- ----- -- -- -- -- -- -- 16 Yu -- Enthusiasm Changing to: -- -- ----- ----- -- -- ----- -- -- 47 K'un -- Exhaustion
I have made several toy interactive I Ching "explorers" in the past, which let you interactively browse the hexagrams. There are in my small programs repo. This latest Go lib is also on github, of course.
For fun, I wrote a little tool to convert images into ASCII. First I did a Scala version, and then a Go version. Both were based on some sample Clojure code I ran across. The example picture I posted here is the Plan-9 OS mascot, Glenda.
It can be interesting to compare implementations of programs across languages (which I why I re-implement so many programs in different languages and styles). In this case, though the Scala version is still more compact, the Go version is surprisingly close.

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
On Embedded DSLs
I've been working through the exercises in Functional Programming in Scala, which is a really great book. I just finished the chapter on parser combinators, and I asked myself: how did we do stuff like that in primitive languages?
Of course, after a few moments, the answer was obvious: we did it with macros and pre-processors. For parsing, there is lex/yacc. I have used combinations of macros and pre-processors for state machines in C. Embedded SQL in C has often been done as a pre-processor.
So, rather than embedding a DSL in the host language, you would make a mini-language and a program that compiles it to the host language. Is it really that different?
Advantages of eDSLs: No tools to install or learn. IDEs (more or less) help you with the syntax. Easier to inject raw non-DSL code into the process to handle edge cases. Easier to debug, since the eDSL is just code from the host language.
Advantages of mini-languages: The language can be anything you want, not just what's easy to stuff into the host language. The output doesn't have to have any relationship to the input. Can have multiple back-ends to compile your mini-language to multiple host languages.
I'm not sure there's a clear winner here. At the end of the day, the goal is to make the computer do something useful... we got it done yesterday and we'll get it done tomorrow.
Relations and Functions
Math is funny sometimes.
One of the first things we learn about relations and functions is: a function is a relation with certain restrictions. Clearly, there are relations which are not functions (such as the ‘less-than’ relation among integers). When you hear this, it’s easy to assume that there are more relations than there are functions.
Then, you think about it and you realize: you can model any relation between A and B as a function from (AxB) to Bool. So, the set of all relations appears to be isomorphic to a small subset of all functions! Thinking about it this way, it would seem that there are more functions than there are relations.
I guess it’s another one of those crazy puzzlers with anything that’s infinite. For finite ranges of natural numbers, you can always say “half of them are even.” But take the infinite set of all natural numbers, and now you have to say “there are the same number of even numbers as there are numbers”. It’s ridiculous, but true.
Kotlin BasCat
For fun, I spent some time this weekend playing with Kotlin. I implemented a toy javaFX program to play a sound, and yet another implementation of BasCat. The latter is a utility to convert tokenized GWBASIC/BASICA programs into plain text.
My thoughts on Kotlin so far?
The language feels very clean and ceremony-free, as advertised
Kotlin's when is nicer than Java's switch but doesn't seem as capable as Scala's match.
Kotlin needs method references (they are slated to come in a future release).
I need more time to get comfortable with Kotlin's blocks and implicit it variables, etc. But I was able to stumble through this implementation without too much trouble. So it is fairly intuitive so far.
I am leery of the performance impact of all the behind-the-scenes null checks Kotlin inserts into public interfaces. I realize that they do it in case Java code calls the method, but I wish there were a way to assure Kotlin that it isn't necessary.
The JAR file was 2k smaller than the Java version, which surprised me. To be fair, if one includes the kotlin-runtime jar in the comparison then the kotlin version is much bigger.
Overall, my impression is favorable, and I could see myself implementing more projects in Kotlin in the future.
I implemented a bunch of dithering algorithms the other day in a javaFX "DitherTool", and a big part of that tool is the palette designer. To get great results out of a few colors, you have to choose wisely! So, tonight, I added a K-Means algorithm to quantize the source colors down to a user-specified size. It does a pretty good job. Sometimes, as shown in the image I posted here, 8 colors will suffice to render a good-looking photograph.
K-Means is really simple to implement; the hardest part was working out a good method to choose initial values. I tried just selecting n random source pixels, but that was hit-and-miss. I also tried randomly-assigning source pixels to clusters, and computing an initial centroid from those. The problem with that is, you basically get n gray colors by averaging a bunch together, and then K-means has to do its best to separate them back out again.
So, what I settled on was: I divide the image into a Sqrt(n)-by-Sqrt(n) grid, and choose a random pixel from each of those squares. Then, any extra values would be chosen at random across the whole image. So, let's say we need 17 colors.. I divide the image into a 4x4 grid and pick a color from each square, and then pick the 1 extra color from anywhere in the image. This seems to work pretty well to ensure that we get some representative color from all over the image to start with.
It's ok if I happen to pick the same color twice: I wrote the code so that it breaks ties fairly when comparing distances, so that k-means should pull the identical centroids apart pretty quickly.
The code is in my custom palette design control on github.
I finally added color to my JavaFX fractal program... I had recently made an image dithering tool which I'll post about soon, and as part of that I made a palette-designing component. It was relatively easy to pull that component into the fractal program, so I did!

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
Paying with JavaFX animations, I implemented a MineSweeper game. It's on github, of course. I'm pleased with how it turned out, with the screen changes all radiating from the point of the mouse click. The animated GIF here doesn't do it justice.
Overall my experience with JavaFX has been pleasant. A few things seem half-baked: like feeding parameters to a "Controller" class, for example, is a bit convoluted. I have also found the HTMLEditor to be a little flakey, when trying to organize bulleted lists among other things. I hope that all steadily improves as releases go forward. It would be a shame if Oracle let JavaFX wither now that it's clear it won't take over the world.
Encryption Format
One of my pet projects, which I've implemented in a ton of languages now, is a Spritz-based encryption and hashing program. This post will go over the latest incarnation of the file format I've devised. Right now it's only implemented in the Go version, but I plan to bring the Java version to a compatible state today.
There are 6 parts to the file format:
The Initialization Vector
The Password Key Generation
The Password Check
The Encryption Key
The Original Filename
The Payload
Initialization Vector
If you encrypt multiple files with the same password, it is useful to also include some random addition to the encryption process. That way, the cipher stream is different every time despite your input being the same. These random extra bytes are called the "Initialization Vector" (IV).
Since the IV is random, I believe most programs store it unencrypted at the start of their file. Just in case having easy access to the IV is useful to some future attacker, I actually XOR my IV with a hash of the given password. To me, it's so easy, why not take the extra step? Now you aren't even sure you have the right IV unless you have the right password.
So, that's the first 4 bytes of my output... the 32-bit random IV, XOR'ed with the 32-bit Spritz hash of the password string.
Key Generation
Generally, a cipher needs a fixed-size block of bytes for its key. Spritz doesn't need that, so I could have just used the password bytes + the IV bytes for my key. However, a second useful feature of keygen is to eat up resources, to make brute force attacks on the password take longer. As a result, I generate a key by multiple 512-bit hashes of the password string and IV, as follows:
Take the 512-bit output (hash) of the password Loop 20,000 + (fourth byte of IV) times: Initialize a fresh Spritz cipher Absorb the IV Absorb the latest 512-bits of data Store 512-bits of output for the next iteration Increment the IV by 1 The key is the last 512-bits generated
A couple points of interest that I'm happy with:
The number of iterations is slightly random (based on one of the bytes of the random IV). The goal is to complicate any kind of automated attack on the keygen, since the IV itself is also encrypted in the file based on the password.
The IV portion is re-added (and incremented) on each iteration, just in case there is some kind of hash-of-hashes weakness in Spritz where repeatedly re-hashing data converges on a smaller-than-512-bit space somehow.
It's nice and self-contained, using Spritz itself for the hash means not needing a separate SHA256 or whatever for use in keygen
Now, 20,000 iterations might sound low, but at least on my machine, it takes about 0.4 seconds for each key. So, even if you pulled together something 10,000 times more powerful, you'd only test about 25,000 keys per second. A long password is going to be hard to crack at that rate. So, I think I'm ok there. The general slowness of Spritz pays off in this case.
Key Check
At this point, usually an encryption scheme wants to check that the given password is correct. A common way to do this is to store a hash of the generated key. If the hash of the key you just generated is the same as the hashed key you stored off, then the password was probably right.
I chose a different route. During encryption, I write 4 random bytes, and their 4-byte hash. I write both of these into the file encryped by the key. So, during decryption, once I have a key to test, I decrypt the next 8 bytes. If the second 4 are a hash of the first 4, then the key is probably correct.
This way, I still get the same kind of assurance as the typical method, but the bytes I use to do it have no relation to the key itself. This removes any chance that the key check data leaks any information about the key. It's a paranoid kind of approach, but this is an encryption scheme after all!
Actually, I left out a step in the above description of the key check: between the 4 random bytes and their hash, I actually skip a number of cipher stream bytes equal to the value of the fourth random byte. This way, anyone hoping to use some kind of bias in the early cipher bytes against me is dealing with an incomplete stream with a random-sized hole in it, which was encrypting random data in the first place.
The Encryption Key
We've done all this work to generate a key, and check it, but we're only going to use it to decrypt 512 bits. Those 512 bits are the real key used to encrypt the payload.
Why do this? Why not just use the key we painstakingly generated in the above steps? Because this way, you can change the password without re-encrypting the whole payload. You just change out the fixed-length header and leave the rest of the file still intact, still encrypted by the same payload key.
The downside of this approach is that there are actually two ways to crack the file: crack the password, or guess the payload encryption key. However, in practice this isn't a problem because the encryption key is fully-random noise. The user-supplied password is almost certainly easier to crack, so this approach doesn't reduce security in any meaningful way.
So, at encryption time, random bytes are written with the keygen'ed key. At decryption time, the keygen'ed key is used to read those bytes back. A fresh spritz cipher stream is created from those bytes and used for the rest of the file.
Just in case there is a useful way to attack the early bytes of the cipher stream, I skip the first bytes of the stream and don't use them. The number I skip also leverages random data: it's 2048 + the fourth byte of the payload key. The goal with the random skips is to defy easy analysis: if the starting point of the cipher stream is dependent on a random number, it's that much harder to reason about.
(It's common now when using RC4 to skip the initial bytes, which are known to be weak. RC4 and Spritz are somewhat related so it makes sense to bake in this precaution, since it's cheap to do.)
The Original File Name
Part of securely encrypting data is not leaking any meta information about the payload. I want to be able to give the file its original name upon decryption, but I don't want that reflected in the encypted file name. So, the first part of the encrypted payload is:
1 byte giving the length of the filename
the file name
This also has the advantage of delaying the start of the actual payload by an unknown number of bytes, in case the initial portion of the payload is of use to an attacker.
The Payload
I use zlib to compress the payload prior to encryption, which helps in three ways: the payload is usually smaller, the contents have less structure, and it obscures the size of the original plaintext.
Conclusion
What I like about this scheme is that every byte in the file is encrypted by some method, and I've tried to leverage random amounts where possible to help complicate any analysis. It's possible I could make some improvements to the keygen, and if so, I should be able to upgrade existing files without re-encrypting the payload.
The one feature that might be nice, which I didn't tackle, is random access into the payload. To do this, I'd need to operate in some form of counter (CTR) mode, so I could configure a cipher stream for region of the file in close to O(1) time. The complexity didn't seem to be worth it, for my needs.
It's been fun playing around with this, and I've learned a lot about encryption schemes and modes of operation in the process.
Converting a Mac to Windows
I have converted macOS machines to run Windows several times at this point, and each time I have to go look up details again, so I thought I'd write down the steps I use.
Note: This is not the recommended method of getting Windows on your mac. I don't like the idea of running Windows from BootCamp, as I want Windows to be able to use the SSD portion of my Fusion Drive.
Disclaimer
These are just the steps I follow, which have worked for me. I am not making any promises that they will work for you, and it's definitely not the officially-recommended way to put Windows on a mac. So, do this at your own risk!
One "problem" people always mention when skipping Boot Camp is that you won't be able to install firmware updates anymore. The thing is, I can count on one hand the number of firmware upgrades I've ever had to do. In these days of commodity recycle-and-replace hardware, I'm just not concerned about that problem.
Step One: Prepare via macOS
First, in macOS, run the BootCamp Assistant program and pull the latest Apple drivers onto a USB stick. You'll need this for later.
Second, I always split my Fusion Drive from macOS. Windows can't use the Fusion Drive as one drive... that's an Apple LVM feature at work, which to the best of my knowledge isn't available on Windows 10. It's not clear to me if I have to do this step, or if the Windows installer would be able to make sense of the drives by itself and let me reconfigure them. However, since it's not hard I go ahead and split the drive from OS X.
Boot into recovery mode by holding cmd+R
Open a termina window
Run diskutil corestorage list and copy the LVM id at the top
Run diskutil corestorage delete {lvm-id}
Now macOS sees it as two drives, and they are empty. No turning back, now!
Step Two: Run the Windows Installer
Next, I run the Windows installer.
From a Windows machine, run the media creation tool (google it if needed) to make a USB stick installer.
Boot into options mode by holding down option (also known as Alt)
Select the Windows installer drive
At this point, one would think it would be fine to use the graphical tool to repartition the drives, but I find that one of two things will happen if you do that: the installer will almost-but-not-quite install Windows, or it will say it can't even try because it needs a GPT-partitioned drive. That's confusing, because on the macOS side, diskutil claimed the drives were GPT-partitioned aready. There must be subtle incompatibilities.
Step Three: Repair GPT
So instead of going right into "Install Windows," sepect the "Repair" option, even though there isn't yet a Windows installation to repair. In the advanced settings you can get to a command prompt. Do that, and type: diskpart (sorry, I can't recall if it's in the shell's path, or if you need to go to X:\Windows first).
In diskpart, assuming your disks are 0 and 1, type:
select disk 0 clean convert gpt select disk 1 clean convert gpt
... and exit out of it. Now you have clean, Windows-approved GPT volumes. No start the setup program, X:\sources\setup.exe. Or, if you prefer, just reboot again.
Step Four: Windows Installer
Now you should be able to simply install Windows normally. Let it partition the volumes the way it wants to, and install Windows on the SSD.
Once you are booted into Windows, put in the USB stick with the Apple drivers, and run the setup program. Now you have better-supported display drivers, WiFi, and peripherals.
Step Five: Minimize SSD Use
Since we have two drives, and the SSD is relatively tiny, I want to divert as much storage to the secondary disk as possible. Here are some steps I took:
Run powercfg /hibernate /type reduced to cut the hibernation file in half, while still supporting fast booting. This means I don't hibernate the machine... it's either on, asleep, or off.
Migrate all user "library" folders to the data drive via Properties -> Location. Documents, Music, etc.
Set the Recycle Bin properties on per-drive sizing.
Resize the paging files via Advanced System Properties.
Configure System Protection for a small number of restore points
Move the Windows search index to the secondary drive
I got most of these steps from a very informative article from 2011, "Cutting your system drive down to size". That article has more screenshots and details that I wrote down here in my notes.
The one I'm least sure about is the search index. Ideally, you'd like reading that index to be fast. So that one's a toss-up to me.
Installing .NET Core
I decided to try out .NET Core today, on my mac. I think Microsoft has done some amazing work recently open-sourcing both it and Powershell. I have been wondering how my little web-based UIs in Go would compare to a small ASP.NET app, and with .NET Core I should be able to compare them directly. Should be fun!
Installation
The first thing I noticed about the install instructions was: I have to put a couple openssl libs in my /usr/local/lib directory. I prefer to install things under my home dir, since macOS is getting progressively more possessive of its standard directories with each release. So, hopefully that will not always be necessary.
The next step was to install .NET Core itself. I was a little surprised that the name of the installer said "preview2" on it, because I thought it had been officially released at 1.0 back in June. Anyway, that install went without a hitch.
Running "Hello World"
Now I run the dotnet new command in a fresh directory. It says it's populating a local package cache, and then succeeds. An ls and ls -a don't reveal any package cache in the project dir, but I see a new .dotnet directory in my home dir. Fair enough.
Generating the app from source was two steps:
dotnet restore
dotnet run
I believe that first step is only needed initially, and perhaps when new dependencies are added to the project. The second command noticed that I hadn't compiled the project yet, so it compiles and then runs.
One strange thing: find . -name '*.exe' gets me nothing. I had expected an .exe file, even on OS X, because I believe that's how mono works. But no. So I get a normal UNIX-y plain executable then, right? Nope. It looks like I just get a DLL.
So, I can run my program via dotnet run /path/to/dll? It looks like no! How do I deploy a compiled program then?? I had to resort to the internet, which told me to leave out the 'run' part:
dotnet /path/to/my/dll
Fair enough... So it will be like java stuff, where I make a little shell script for each app that knows how to run it. I'm ok with that.
A Go/Java/Scala/Kotlin Benchmark
I'm always re-evaluating what the right language for me should be. Even though I know there isn't an unqualified right answer, I still search, and as a result you often find multiple implementations of my programs in my github account.
Tonight, I upgraded my scala compiler to the 2.12.0-M5 version, and rebuilt spritz. I also downloaded Kotlin 1.0.3 and let Intellij convert the java version to kotlin. Here are some updated benchmarks for hashing a directory of 20 large files (3.1 GB total).
Go 1.7rc5: 2.4 minutes, using 2 MB RAM Java 1.8: 2.7 minutes, using 14 MB RAM Kotlin 1.0.3 2.9 minutes, using 20 MB RAM Scala 2.12.0-M5: 3.3 minutes, using 41 MB RAM
For a number of reasons, I really like Go. However, every time I do evaluations like this I start leaning toward Java again. Reasons include:
With java I get a full platform, including graphics, sound, GUI toolkits, Nashorn, etc. And it's all cross-platform and semi-stable. For Go, I'd have to depend on any number of third-party libraries of varying quality to get this stuff, and usually it would mean cgo with its complications and overhead.
Java 8 really made huge modernizing strides
Most JVM languages try hard to interoperate with java code, so if I ever do want to focus on Scala or Clojure or whatever, I can still use the code I've already amassed.
Downsides of focusing new development on Java:
It's not cool anymore.
Hard to engineer a compact memory representation of objects in performance-sensitive code. Even though Java 10 is trying for value classes, that is years away, and it will be interesting to see if they get widespread use (I don't see to many structs in C# code I work on...)
Despite Java 8 imrpovements, the language is still very verbose. Little changes like type inference for local variables would go a long way.

Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
Free to watch • No registration required • HD streaming
More Types Help
The Go programming language's standard library is one of its strongest features--fun programs like CGAify came together very quickly because I'm basically just pulling together packages it already has. However, one minor way I feel the standard packages let me down is by using ints and raw numbers where domain-specific types would have been easy to provide.
Whenever you are programming, you are ultimately producing a kind of homomorphism between your problem-space and the bitwise-computation-space that your microprocessor works in. Depending on your language of choice, the language itself either barely participates (e.g., a macro assembler) or does so much you might forget that it's all bits in the end (e.g., Prolog).
Generally, you want to hold on to the most problem-oriented encoding of your data possible, up until the point where there is a compelling efficiency to dropping your abstractions. Besides the obvious advantages of programming in terms of your domain concepts, note that mapping to less-abstract spaces often throws away information that you can't get back. I recently saw this downside referred to as "Boolean Blindness", which is a catchy way to remember it, but it really applies to all types.
An Example
Here's a concrete example: seek modes when seeking in a file. On a POSIX-type system, you can either seek from the beginning, end, or current position in a file. Down at the system-call level, these three options are represented as magic integers. Conceptually, however, the options form a set of three elements. If we can avoid computational overhead, it's a no-brainer to stay at the conceptual level. In haskell, for example, that's exactly what happens:
data SeekMode = AbsoluteSeek | RelativeSeek | SeekFromEnd
Now imagine you write a function that helps you find some record in a file. Compare these two options:
skipToRec :: Int -> SeekMode -> IO () -- Good! skipToRec :: Int -> Int -> IO () -- Bad!
In the more type-rich option:
It's a compile-time problem if you get the argument order wrong
It's a compile-time problem if you try to specify a SeekMode that doesn't exist.
The compiler should be able to compile both versions down to the same machine code, so the abstraction is "free"
Now let's see how Go specifies seek modes:
const ( SEEK_SET int = 0 // seek relative to the origin of the file SEEK_CUR int = 1 // seek relative to the current offset SEEK_END int = 2 // seek relative to the end )
They went straight for int. This means, unless I create the seek mode abstraction myself, I'm forced to create the "bad" version of my skipToRec function:
func skipToRec(n int, seekmode int) { ... }
There are a handful of examples of this in the standard packages... see file open modes, lots of syscall arguments, GIF Disposal methods, etc. And, it didn't have to be that way. Though no one claims Go's type system is overly sophisticated, it's totally capable of reining in magic constants:
type SeekMode int const ( SEEK_SET SeekMode = iota SEEK_CUR SEEK_END ) func skipToRec(n int, sm SeekMode) { ... }
With this encoding, I get most of the benefits of the haskell encoding. The main differences are: it doesn't protect me if I give literal integers when I call it, and won't tell me about a switch that forgets one of the cases:
skipToRec(3,21) // no error here switch ( sm ) { // no warning about forgetting SEEK_END case SEEK_SET: ... case SEEK_CUR: ... }
But still, the argument types are more meaningful, it's a lot harder to accidentally mess up the argument order, IDEs should be able to show me the right options much easier, and as an abstraction it's essentially free.
Not Just For the Compiler
It's as much about human understanding as it is about anything else. The GIF Disposal options are confusing. Check out the docs.
There are untyped constants which seem applicable to the GIF.Disposal byte-slice, because of their name:
const ( DisposalNone = 0x01 DisposalBackground = 0x02 DisposalPrevious = 0x03 )
However, the comment on the GIF.Disposal field itself mentions a zero-value:
// Disposal is the successive disposal methods, one per frame. For // backwards compatibility, a nil Disposal is valid to pass to EncodeAll, // and implies that each frame's disposal method is 0 (no disposal // specified). Disposal []byte
So, should there have been a DisposalUnspecified of 0 different from the DisposalNone of 1? Or is the comment wrong? Is this field even where those constants should be applied? I had to browse the source to be sure.
Overall, the Go standard packages are excellent. I just wish they had used their type system more aggressively to clarify their API in places. It would have been better for people, better for tooling, performant, and it would have served as a good example to people writing libraries of their own. Note that they do make a fresh type sometimes, like time.Weekday; they just weren't consistent about it.
A great investigation-in-progress to track the cause of some pathological build times for Go projects.