Ask HN: Programming without a build system?
I'm an old MS-DOS / Windows programmer used to Turbo Pascal, Delphi, etc. The old days were simple, in that you compiled code, and gave it to your customers. Everything just works.
Now every time I try to learn something new... there's always a build system in the way, with MSTOICAL it was Autoconf, with switching to Linux, I couldn't get WikidPad to build on Linux, trying to build a lifeboat for Twitter, Python works, but then modules require builds that break.
Is it possible to have a system without Make or the like?
Alternatively, any good resources for the above?
111 comments
[ 2.4 ms ] story [ 181 ms ] threadbut...
we have clouds now, so even if you find something, development will require CD/CI pipeline, github actions and so on.
Nope, definitely doesn't. You can just distribute the source code alone, same as has been done for decades. And with languages like Go and Rust, it's quite fast and simple to build from source, even on Windows.
Example: I have a molecular simulator I'm working on. Has 3D Vulkan graphics, a GUI, and a few dependencies. My mother got it working by opening the (3Mb compressed) binary after downloading from a dropbox folder.
The alternative is live systems where the source code is compiled and interned for immediate use in the running system. Examples include: Lisp machines, Smalltalk, and Forth.
There mught be other paradigms as well.
If OP wants to try it: https://squeak.org/
"Within each project, a set of changes you make to class descriptions is maintained. … Using a browser view of this set of changes, you can find out what you have been doing. Also, you can use the set of changes to create an external file containing descriptions of the modifications you have made to the system so that you can share your work with other users.
…
The storage of changes in the Smalltalk-80 system takes two forms: an internal form as a set of changes (actually a set of objects describing changes), and an external form as a file on which your actions are logged while you are working (in the form of executable expressions or expressions that can be filed into a system). … All the information stored in the internal change set is also written onto the changes file."
1984 Smalltalk-80 The Interactive Programming Environment page 461
https://rmod-files.lille.inria.fr/FreeBooks/TheInteractivePr...
> gave it to your customers
You could start with an HTML page and vanilla JS. Use Bootstrap, Tailwind, whatever with a <link> tag. But then instead of a build system, you're wrestling with home-grown infra if it's not cloud.
The need for a build system should become apparent, but only once you're comfortable with the idea.
Without make, you were running `cc` or `gcc`. Even your previous languages may have had a big "Play" button in the IDE. Usually even those invocations are--behind the scenes--basically one super long command.
Classic ASP is another option. It's on Windows, IIS is free, and Azure (Microsoft cloud) supports it.
By the way, it appears Delphi is alive and well via Embarcadero [1].
If you pick Delphi and a web framework, you can put it in a Docker container and run it in the cloud. The "build" mechanism in that case is whatever you need from the container image's OS to bootstrap Delphi runtime, but that's well worth learning.
[1] https://www.embarcadero.com/products/delphi
Most programmers I've worked with have become comfortable with a new language much faster than me.
Like everything, it has footguns, but for the most part there are really no new concepts to learn Go.
having a decent standard library saves you from needing to use a bunch of 3rd party modules that may or may not need to be compiled
I don't program in Go but when there's Go software that needs fixing, it's so much easier to "go build" it and fix bugs in this unfamiliar language than it is merely to compile software written in many more familiar languages. Build systems that just work is a huge advantage.
You can do this today with any newfangled languages, just avoid third party libraries, or at least use them as a last resort, and choose options that are stable.
Autoconf is just a complicated system for finding and detecting third party libraries. If an application just uses the standard library, the build system can be very simple and reliable.
Most of your pain is actually due to 'package managers', not build systems per se.
Another way to avoid autoconf is vendoring third party libraries. If they are open source, one could just use Git sub modules. You might need to call ‘./configure’ in the vendored library directory, but you don’t need to use autoconf for the application itself
Easier said than done. I have never gotten submodules to work the way I want them to, and I’ve since abandoned the idea completely.
The point is not how much effort you need to invest in getting git submodules to eventually or occasionally work.
The point is whether vending the subproject as a prebuild library/package is simpler or not, or whether it requires more work or not.
And it's abundantly clear that submodules are always inferior to vending packages, not to mention they are audit-friendly.
It's slightly more than that.
Autoconf is a Makefile generator. It was designed to do system introspection and perform sanity checks, and configure projects so that they could be built and installed in multiple platforms.
It's always possible to switch back to Makefiles. You'd be foregoing sanity checks, abstractions that come for free, and you'd have to fill in the blanks left out by missing features such as what compiler I use and how to configure the compiler and how to put together Release and Debug builds and how to consume dependencies and where do I install the project.
Complaining about build systems is like complaining about a high level language: you might focus on the bloat, but you completely miss the extra work you have to reinvent the wheel and have to maintain it forever.
- not need any dependencies - or use almost any more modern systems language like Rust, Zig, or Go
The above code '#run's any procedure at compile-time (in this case 'compile_to_binary' which is defined in the library I imported). That procedure (really a hygienic macro) configures my 'workspace' to compile down to a 'name_of_binary' executable in the current directory. Any third-party libraries I've imported will be linked against automatically as well.
To do this without a dedicated procedure, just put this in the same file as 'main':
Then compile: I've done this for projects with hundreds of files that link in SDL, BearSSL, etc.The best part is neither of these systems are a requirement to build your project. Running the compiler against a Jai file will do everything I do with my own system (I just like having the ability to configure it in code).
Jai has been a breath of fresh air in terms of "just let me write code," so I highly recommend it if you can get in the beta.
But! I think the "can I get by without needing $big_build_toolchain?" question is excellent, and would recommend that you start by building automations yourself. That means writing small programs (often shell/batch scripts) to smooth over repetitive parts of your build/packaging/deploy process.
That approach yields a ton of understanding about how build/transformation/delivery software work with your platform of choice, and that understanding in turn makes you a way better programmer in a ton of different ways.
Now, this doesn't mean that you should stick with home-rolled automations forever; past a point, they become more or less equivalent to others' build systems and you can switch if you like. But switching to something from a position of full understanding of what it provides is a much easier and friendlier process than "I have to use tools A, B, C, and D just to get 'hello world' working, and don't know what any of those do".
I'd recommend this approach to anyone with the time; it really confers a lot of understanding and confidence. And if you don't have the time/you need a prototype deployed yesterday, don't worry about it; copy the Medium article snippets and learn what the tools actually do some other time--just try to make sure "some other time" isn't "never".
It's worth having some skepticism of tools. By making some operations easy, tools encourage them. Build systems make it easy to bloat software. Package managers make it easy to bloat dependencies. This dynamic explains why Python in particular has such a terrible package management story. It's been around longer than Node or Rust, so if they seem better -- wait 10 years!
For many of my side projects I try to minimize moving parts for anyone (usually the '1' is literally true) who tries them out. I work in Unix, and one thing I built is a portable shell script that acts like a build system while being much more transparent about what it does: https://codeberg.org/akkartik/basic-build
When I use this script my build instructions are more verbose, but I think that's a good thing. They're more explicit for newcomers, and they also impose costs that nudge me to keep my programs minimalist.
You can see this build system evolve to add partial builds and parallel builds in one of my projects:
https://github.com/akkartik/mu1/blob/master/build0
https://github.com/akkartik/mu1/blob/master/build1
https://github.com/akkartik/mu1/blob/master/build2
https://github.com/akkartik/mu1/blob/master/build3
https://github.com/akkartik/mu1/blob/master/build4
Each of these does the same thing for this one repo -- build it -- but adding successively more bells and whistles.
I think providing just the most advanced version, build4, would do my users a disservice. It's also the most likely to break, where build0 is rock solid. If my builds do break for someone, they can poke around and downgrade to a simpler version.
Linking is done by describing it as part of the code with its `foreign` system [1][2]. The benefit of this approach is that your code itself describes what needs to be linked against what. It also has the benefit that if you don't use a foreign library, it doesn't get linked against due to its minimal dependency system.
If you want to see huge examples of this, I recommend checking out the vendor library collection[3].
n.b. I am the creator of Odin, and one of the original goals was to create a language that didn't necessitate a complex build system and just get to programming. Requiring a complex build system and package manager are anti-features in my opinion and make programming a worse experience to use. Odin borrows heavily from (in order of philosophy and impact): Pascal, C, Go, Oberon-2, Newsqueak, GLSL.[4]
Niklaus Wirth and Rob Pike have been the programming language design idols throughout this project.
[1] https://odin-lang.org/docs/overview/#foreign-system
[2] https://odin-lang.org/news/binding-to-c/
[3] https://github.com/odin-lang/Odin/tree/master/vendor
[4] https://odin-lang.org/docs/faq/#what-have-been-the-major-inf...
Overall, the design of the language is very clean and well thought out. Every decision that was made, and continues to be made, feels like it was put there for a reason; and, for the most part, nothing feels out of place.
https://github.com/odin-lang/Odin/discussions/2047
Additionally, the issue has a build script using a different compiler and the asker basically said it wasn't good enough for them.
[0]: https://www.jetbrains.com/lp/devecosystem-2021/cpp/
According to this survey, Visual Studio is preferred by 24% of respondents, just behind VsCode and CLion. And MSVC is the 3rd most popular compiler, just behing gcc and clang. And keep in mind these results are across all C++ developers, not just windows developers.
https://github.com/mstorsjo/llvm-mingw
Your source does not support your claim at all.
Even though vscose and Visual Studio are reported as popular IDEs, you're not talking about IDEs. You're discussing compilers and compiler toolchains, which is not the same thing as a IDE. Microsoft's msvc compiler is not the same as Microsoft's Visual Studio. Visual Studio is an IDE which, among other things, uses msbuild and msvc compiler.
The poll you quoted states that 78% use GCC regularly, followed by clang being used regularly by 43%. Microsoft's msvc compiler is in 3rd place at 30%. The same poll also points out that 55% uses cake and 36% use Makefiles regularly, and Visual Studio projects showing up in 3rd place with 31%.
> by far a highly preferred environment for C++ programming on Windows
The survey doesn't split the responses up by OS. That's besides the point, I'm not trying to say MSVC is the most popular toolchain. I'm refuting the claim that if you only support MSVC on Windows that constitutes "poor windows support".
> Microsoft's msvc compiler is in 3rd place at 30%.
Yes, and the question for this was "Which compilers do you regularly use?". 78% + 43% + 30% = 151% which is greater than 100%. I'm assuming this means that respondents were allowed to select multiple answers. It seems to make sense to me that people that use MSVC commonly use other compilers when developing for Unix, but the reverse isn't true nearly as much in my experience. Besides that, MSVC is in 3rd place and pretty close to the same amount of users as clang which was the proposed alternative. In that case it really doesn't matter which one you choose to support because you'll be supporting about the same number of people either way.
Anyways, this doesn't matter to the claim that I was trying to refute. OP said that Odin has poor Windows support and pointed to an issue that basically said if you only support MSVC on Windows that's not good enough. As a Windows developer, that seems like an odd stance to take imo. This survey shows that a third of developers use MSVC across all domains, including Unix. This seems to imply that if you support MSVC, you're at least supporting a large percentage of devs.
Also, I didn't mention the build systems because that's irrelevant. You can use CMake and Make with MSVC or whatever compiler you have installed. I say this as someone that regularly uses CMake and/or Make to compile projects on Windows using MSVC.[0][1]
[0]: https://learn.microsoft.com/en-us/cpp/build/reference/creati...
[1]: https://cmake.org/cmake/help/latest/variable/MSVC.html
You're the only one here complaining about MSVC. Have you stopped to consider why Rust and Odin and so many other languages are happily depending on MSVC instead of the alternatives? Or does none of that matter and your opinion is the only correct opinion?
incompatible? incompatible with what? Ive been using it for years with no problem.
> You're the only one here complaining about MSVC.
The ad-hominem isn't advancing your cause lol.
There was no ad-hominem. OP pointed out your initial claim contrasts with the facts.
> Rust defaults to the MSVC toolchain. Surely you knew that. (...)
This reads like a non-sequitur. The claim you've replied to was that Odin had poor windows support, and OP pointed out that Rust also supported Windows without msvc toolchains.
> You're the only one here complaining about MSVC.
Sorry, you're clearly speaking while oblivious to the discussion. The message you've replied to includes a link to a ticket asking Odin to shed it's dependency on msvc. It's clearly titled "move away from msvc".
https://github.com/odin-lang/Odin/discussions/2047
I believe you owe OP an apology.
Much the same can be said of simple Makefiles. If you are targeting your own machine, you can create a couple of variables to track libraries and such then tweak them much as you would tweak the configuration of a traditional IDE. The trouble comes with creating software that other people will build or while building software created by other people. Linux (actually, Unix in general) is notorious for its inconsistencies: libraries and headers aren't always in the same place, sometimes libraries are missing, sometimes one library is used in the place of another. Tools like autoconf help developers to work around that. Of course, modern build systems go a step further by pulling in dependencies.
It probably wouldn't be much of an issue, except for one thing: everyone seems to have their pet build system.
Ultimately, the answer to your question is going to depend on what you want to make. For instance, if you want to make an iOS app, it's going to be difficult to do so without using Xcode's build system.
Go is a very good language in terms of having a build system that stays out of your way. On Linux, you'd do something like `sudo apt-get install golang`, and then be on your way.
Plugging my own repo: https://github.com/jsebrech/create-react-app-zero
It is a version of create react app that works in that way, no build tools needed, only a static web server for local development.
Start with bash script.
Translate that to a tiny ninja script when it gets to be more than a page or two. (Ninja is technically a build system, but it is tiny and simple).
Translate that to a tiny python script that emits build.ninja when it gets to be more than a page or two. Not much more to it than globs and f-strings.
Overall that scales well, has no large dependencies (you can just dump a Ninja binary in the repo), and is fast.
> Alternatively, any good resources for the above?
There are many, unbelievably many writeups and tools for Python building and packaging. Some of them are really neat! But paralysis of choice is real. So is the reality that many of the new/all-in-one/cutting edge tools, however superior they may be, just won't get long term support needed to catch on and stay relevant.
When getting started with Python, I very personally like to choose from a few simple options (others are likely to pipe up with their own, and that's great; mine aren't The One Right Way, just some fairly cold/mainstream takes).
1. First pick what stack you'll be using to develop and test software. In Python this is sadly often going to be different from the stack you'll use to deploy/run it in production, but here we are. There are two sub-choices to be made here:
1.a. How will you be running the Python interpreter itself in dev/test (in other words, what Python language version you will use)? "I just want to use the Python that came with my laptop" is fine to a point, but breaks down a lot sooner than folks expect (again, the reasons for this are variously reasonable and stupid, but here we are). Personally, I like pyenv (https://github.com/pyenv/pyenv) here. It's a simple tool that builds interpreters on your system and provides shell aliases to adjust pathing so they can optionally be used. At the opposite extreme from pyenv, some folks choose Python-in-Docker here (pros: reproducible, makes deployment environments very consistent with dev; cons: IDE/quick build-and-run automations get tricker). There are some other tools that wrap/automate the same stuff that pyenv does.
1.b. How will you be isolating your project's dependencies? "I want to install dependencies globally" breaks down (or worse, breaks your laptop!) pretty quickly, yes it's a bummer. There are three options here: if you really eschew automations/wrappers/thick tools in general, you can do this yourself (i.e. via "pip install --local", optionally in a dedicated development workstation user account); you can use venv (https://docs.python.org/3/library/venv.html stdlib version of virtualenv, yes the names suck and confusing, here we are etc. etc.), which is widely standardized upon and manually use "pip install" while inside your virtualenv, and you can optionally integrate your virtualenv with pyenv so "inside your virtualenv" is easy to achieve via pyenv-virtualenv (https://github.com/pyenv/pyenv-virtualenv); or you can say "hell with this, I want maximum convenience via a wrapper that manages my whole project" and use Poetry or an equivalent all-in-one project management tool (https://python-poetry.org/). There's no right point on that spectrum, it's up to you to decide where you fall on the "I want an integrated experience and to start prototyping quickly" versus "I want to reduce customizations/wrappers/tooling layers" spectrum.
2. Then, pick how you'll be developing said software: what frameworks or tools you'll be using. A Twitter lifeboat sounds like a webapp, so you'll likely want a web framework. Python has a spectrum of those of varying "thickness"/batteries-included-ness. At the minimum of thickness are tools like Flask (
However it would be preferable to just have a smart enough language tooling to do it without such tricks.
One question if I may: how can I erase "$0.bin" after its execution?
Other projects are unfortunately not. Say you want to build with Visual Studio. That means you have to manually configure the include/lib paths first, and probably build some libraries before building your main project, etc etc. Annoying.
You can use a simple build script and a C/C++ compiler like Clang or Zig or Tcc.
You can also use the small libc by Justine Tunney to build once and run your code anywhere: https://justine.lol/cosmopolitan/
We have fast computers, if your project is not huge with millions of lines of code you can easily skip the build system.
You write PHP, put it behind php-fpm and httpd/nginx and you're done
Of course if you use this feature then the only difference it will make is that everybody has to use the jetbrains IDE.
ESM Modules need no build system.
(actually using Next.js)
In the browser too, using: <script type="module">
Very different case today. We need to use various database/cryptography/imaging/network/etc libraries fetched from github or some other servers, and so far the most reliable way to make the building process works regardless of your OS is to use a build system.