Live data from Hacker News

Using Makefile(s) for Go

danishpraka.sh

41–50 of 104 posts

Re: Using Makefile(s) for Go

#41
post #3

I've seen a lot of developers, especially developers with C backgrounds, reach for Makefiles when approaching Go development, and I think it's a harmful practice. So, I'd like to offer a respectful but firm rebuttal to this article. :) I dislike using make(1) with Go for two reasons. The first is that make was developed for building C projects, and therefore is oriented around that task. Building C projects is a lot…

"For code generation and other ancillary tasks, Go includes the 'go generate' facility. This feature was created specifically to free developers from depending on external build tools. ( https://blog.golang.org/generate)" Please please please don't use go generate! While I respect your position, go generate is the worst and I hope they eventually deprecate it in future go versions. We tried go generate in some of our…

Go build constraints (build tags) also go in the comments and I think it works well enough. The arguments against are similar to those against struct tags. Without a properly defined pragma paradigm in Go comments at the top of a file work Well Enough™ IMO.

I've also never seen anyone call out to protoc from a go generate flag. Sure, you can do that but it hasn't been common in the Go shops I've worked at.

> You can't order your generate commands in a way you want, you have to order them using file order, or keep all of your go generate lines in the same file, which defeats the purpose of go generate.

I've never had a problem with confining generated code to a single file. Do you have more details on why this is a problem? Given that all the files in a package are "flattened" I don't see why this causes a problem...

Re: Using Makefile(s) for Go

#42
post #3

I've seen a lot of developers, especially developers with C backgrounds, reach for Makefiles when approaching Go development, and I think it's a harmful practice. So, I'd like to offer a respectful but firm rebuttal to this article. :) I dislike using make(1) with Go for two reasons. The first is that make was developed for building C projects, and therefore is oriented around that task. Building C projects is a lot…

If you download some random tar file with go code in it, I agree that you should expect to be able to build it with only go installed. "go get" depends on this, and largely works well!

But the day to day act of developing a system that uses go involves more than just building a go binary. Your go program might depend on things like generated protocol buffers. You need some way to regenerate those when you edit the definitions. That then involves having the right version of protoc installed and also having the right version of protoc-gen-go. The go compiler can't help you there. go generate suffers from the same problem; it's not automatic, so you can pass it the wrong dependencies (generation tool flags, version of the generator, etc.).

People are using makefiles as a convenient place to write all these extra instructions. "What flags to I pass to protoc?" "What flags do I pass to docker build?" Why document it when you can "make protos" or "make container"?

Unfortunately, make isn't actually good at this. It doesn't version the generation tools (or itself), so you will end up with vastly different results on different machines. The result is things like a 300 line diff to a generated protobuffer because the second engineer to work on that file happened to have protoc 0.7.7 instead of protoc 0.6.42, or they installed protoc-gen-go@master instead of protoc-gen-go@v1.2.3. Make doesn't care. It exited with exit status 0, so it must have worked.

What started as a nice way to write down some instructions for hacking the code has now become a giant mess. Reasonable makefiles can only ever work for one person on one computer at one point in time. At that point, they might as well be a README.md. At least the README can mention the version numbers of the dependencies, and, most importantly, can wish the reader luck.

There are two long-term solutions. One is to only use go. Write a program that reads the protos at runtime. Write a program that runs your Typescript through a hand-written compiler whose source code lives in your project at runtime. Now you only need "go build". This is... impractical, though. It's a nice ideal, but you'll never get anything done in the real world.

So what you really need is a real build system that captures every dependency, knows about high level tasks ("make foo.proto available to a go program"), and knows every dependency between files like the go compiler does. With such a tool, you can get a working build on every computer with no instructions or manual setup. And since it is carefully written to understand what it's doing, you can get reliable incremental builds. (The full build and your incremental build should have the exact same md5sum of the resulting binary.)

Such a tool does not exist. bazel is close. If you have to build more than just go files, you probably want to look into it. It's crazy. It's a lot of work. Don't do it if you're the only developer on the project. But if you want 10 random people to be able to build a project that's written in more than one language, you have to invest in some sort of tooling. Make is good for a one person team. Make can be scaled to do crazy things poorly (hi, Buildroot!). But it's probably not what you want to be using. If a README isn't good enough, you need a real build system.

Re: Using Makefile(s) for Go

#43

Alternatively, you could use Bazel[1], and automatically generate most of your build rules with Gazelle[2]. This would allow you to extend your build system beyond what's available via "go build", while avoiding the well-known pitfalls of Makefiles (config complexity, reproducibility, etc.) [1] https://github.com/bazelbuild/rules_go [2] https://github.com/bazelbuild/bazel-gazelle

I tried Bazel for awhile on a bunch of projects and just found it to be more headache than it's worth. I like the promise, but the tool is... meh

Re: Using Makefile(s) for Go

#44
I don’t mind Makefiles, and usually use them for basic command configuration.

However, when the logic gets even a little complicated and I almost always reach for bash. Everybody knows a little bash, and it’s available in almost all systems.

Re: Using Makefile(s) for Go

#45
I suggest reading “recursive make considered harmful”. It is a wonderful introduction to make, and explains how to avoid a few pitfalls most make users (including this article) run into.

In particular, the targets in the subdirectory makefiles can and should be auto-generated using make itself. There’s no need for the makefiles in the subdirectories (there is also no need to use an external tool to generate them, which is the other mistake people often make).

Re: Using Makefile(s) for Go

#46
rm -rf ${APP} is a code smell. If ${APP} is not a directory, -r should not be in this command. At best it is possibly confusing, and at worst if somehow ${APP} accidentally becomes a directory it will just remove it and you will have no idea that it was a directory, whereas just rm -f ${APP} will fail because it can't unlink a directory. Build success is an important factor in a CI/CD pipeline, therefore builds should fail immediately under unexpected behavior.

Also, on .PHONY on a single line:

  But for Makefiles which grow really big, this is not suggested as it
  could lead to ambiguity and unreadability, hence the preferred way is
  to explicitly set phony target right before the rule definition.
If your Makefile grows really big, it's going to become a nightmare to maintain. Either split up your codebase + builds into sub-directories, or figure out some other way to structure your builds so that it's not super complicated to reason about or maintain them.

Re: Using Makefile(s) for Go

#47
post #3

I've seen a lot of developers, especially developers with C backgrounds, reach for Makefiles when approaching Go development, and I think it's a harmful practice. So, I'd like to offer a respectful but firm rebuttal to this article. :) I dislike using make(1) with Go for two reasons. The first is that make was developed for building C projects, and therefore is oriented around that task. Building C projects is a lot…

> The first is that make was developed for building C projects

This is sort of a misconception. The C compiler was developed for building C projects. Make exists because those projects had to build other stuff and needed a way to stitch the files together. Make's only built-in support for "C" amounts to some default rules for building .o files out of .c files.

If all you want from your build system is to compile a big unified blob of source in a single language into some kind of output file (like the examples you cite) you don't need make, just use whatever it is that your local language provides.

When you have requirements that go beyond that, where you have programs (often themselves built locally, and often in variant languages or runtimes) generating custom intermediates and need to track that madness, that's when you need a more complicated build manager than your compiler provides.

And that's when you start to understand why, despite four decades now of attempts to replace it, some of us still reach for make.

Re: Using Makefile(s) for Go

#48
post #8
post #3

I've seen a lot of developers, especially developers with C backgrounds, reach for Makefiles when approaching Go development, and I think it's a harmful practice. So, I'd like to offer a respectful but firm rebuttal to this article. :) I dislike using make(1) with Go for two reasons. The first is that make was developed for building C projects, and therefore is oriented around that task. Building C projects is a lot…

>A Go project should only require the Go compiler to build successfully. But they don't. The go compiler doesn't support yarn, npm, protobuf, open-api generators, doc generators like md2man, go-bindata-assetfs, gox and everything you need to complete the code generation done in modern Go applications. So how do you orchestrate this? People use Makefiles, bash scripts, go scripts and everything in between and combined…

I went through a phase of using Grunt, Gulp, npm scripts, etc. for web projects.

Recently, after reading this article on make[1], I decided to give it a try and I've been pleasantly surprised with how much easier using make has been vs. some of the FUD I've read about it.

That's not to say there aren't some issues, but that's true of something that's been around since the '70s, including Unix itself.

But that also means it can address almost any situation where files need to be generated and there are dependencies involved, including what I'm doing with generating a static website, compiling and minifying CSS files (or not minifying but including a sourcemap if it's a dev build) and deploying to a production or staging server.

I don't love the syntax but it’s not that bad once you get used to it. Actually, I prefer it to the seemingly endless nesting of JavaScript objects in a Grunt or Gulp configuration file. My next step is to take advantage of Vim’s built-in support for a make-based workflow.

[1]: https://www.olioapps.com/blog/the-lost-art-of-the-makefile/

Re: Using Makefile(s) for Go

#49
post #24
post #3

I've seen a lot of developers, especially developers with C backgrounds, reach for Makefiles when approaching Go development, and I think it's a harmful practice. So, I'd like to offer a respectful but firm rebuttal to this article. :) I dislike using make(1) with Go for two reasons. The first is that make was developed for building C projects, and therefore is oriented around that task. Building C projects is a lot…

Make is available everywhere that matters, and is a simple declarative way to encompass build actions. What are the alternatives? Bash? Not declarative, and requires lots more code. Some go rewrite of Make? Not universal, possibly not maintained in the future. Rake? Ugh, Ruby. I strongly believe that make is the least worst way to build go projects, but please change my mind by suggesting some alternatives, not by co…

This is patently untrue. Make is not available by default on Windows, which - whether you like it or not - matters as a platform for a large number of developers.

_Fortunately_ some CI images (certainly GitHub Actions and Azure DevOps) install GNU Make on their windows images by default, but it absolutely cannot be assumed for average developers.

Re: Using Makefile(s) for Go

#50
This Makefile could as well have been a shell script. It doesn't track changes to dependencies even when it's obvious how to do so. For example, the build rule has an obvious dependency (main.go) and an obvious target ($(APP)). Instead of tracking these which IMO is the primary advantage of using Make, it deliberately destroys the existing build. docked-build always necessarily rebuilds the binary as well

Presumably, Go has some kind of build cache making such dependency tracking relatively useless anyway, maybe Docker has too, but if you aren't tracking dependencies and rebuilding only when necessary why use Make instead of a big switch in a shell script?

Personally I'd only use Make for Go if I introduce some task that takes significant time and isn't already handled by the go toolchain.

Another couple of notes: there are two docker-push rules. The first seems like it was meant to be docker-build. The other is that the docker build rule will tag the build with the HEAD hash, regardless of whether it's building from a clean checkout or a dirty repo.

Post reply on HN