[HN Gopher] How to Structure C Projects: These Best Practices Wo...
       ___________________________________________________________________
        
       How to Structure C Projects: These Best Practices Worked for Me
        
       Author : ingve
       Score  : 173 points
       Date   : 2024-03-06 13:14 UTC (2 days ago)
        
 (HTM) web link (www.lucavall.in)
 (TXT) w3m dump (www.lucavall.in)
        
       | foofie wrote:
       | This sounds like a carbon copy of the project tree layout
       | specified in C++'s pitchfork conventions.
       | 
       | https://github.com/vector-of-bool/pitchfork
        
       | Joker_vD wrote:
       | > The downside of this approach is that you will need to keep the
       | Makefile updated with the new files you add to the project so
       | that they are compiled and linked correctly.
       | 
       | Does it? IIRC something like                   build/%.o: src/%.c
       | include/*.h             $(CC) -c $(CFLAGS) $(CPPFLAGS) -c -o $@
       | $<
       | 
       | works well enough for small projects.
        
         | seabass-labrax wrote:
         | You don't even need to specify that rule with GNU Make:
         | building %.o is a built-in rule[1].
         | 
         | Personally, I tend to prefer specifying the files individually
         | - it doesn't take up that much developer time nor much space in
         | your Makefile, and makes it considerably easier to debug
         | building issues when using a sandboxed environment (such as Nix
         | or Bazel).
         | 
         | [1]:
         | https://www.gnu.org/software/make/manual/html_node/Catalogue...
        
           | Joker_vD wrote:
           | Doesn't the built-in rule put files in the wrong folder? That
           | is, the same folder the source file is in?
        
             | seabass-labrax wrote:
             | Yes; I've just tested it on GNU Make 4.3 and you are quite
             | right. It only works when the *.c files are in the
             | directory where object files are referenced. E.g.:
             | example: build/blah.o           echo nothing special
             | .PHONY: example
             | 
             | ...requires blah.c to be in build/.
        
         | lsllc wrote:
         | I use make's `wildcard`, for example to locate all of my unit
         | tests with the project sources (I don't use a `test` subdir):
         | TEST_SRC = $(wildcard *_test.c)
         | 
         | If you follow TFA's guidelines about having a `src` dir, then:
         | SRC = $(wildcard src/*.c)
         | 
         | And if you need to convert that to a list of matching .o files,
         | `patsubst` is your friend:                 OBJ = $(patsubst
         | %.c,%.o,$(SRC))
         | 
         | So now simply dropping a file into the right place (or a unit
         | test ending in _test.c) won't require any makefile changes.
         | 
         | I tend to copy my most recent Makefile into my next project and
         | generally it stays 90% the same (I have to edit a few macro
         | definitions and rules, but not many).
        
       | aninteger wrote:
       | I've always preferred Stephen Brennan's approach mentioned here:
       | https://brennan.io/2020/05/08/meson/
       | 
       | This is the approach I take with my C and C++ projects, although
       | I've swapped meson out for CMake. I still have my CMake projects
       | generate pkg-config files as well so that I don't force CMake on
       | any consuming projects (just because I suffered with CMake
       | doesn't mean you have to, although you can as well :))
        
       | emmelaich wrote:
       | FWIW practices is misspelled "pratices" twice.
       | 
       | Just remove best-practices, it's a silly phrase.
        
       | veltas wrote:
       | Keeping .c and .h files separate is pointless, *.c and *.h can
       | pick them out when necessary. 'include' is a good choice for
       | libraries, the external interface, but not necessary for internal
       | headers.
        
         | actionfromafar wrote:
         | If you have a bunch of platform specific code it can be quite
         | neat to have .c and .h files separately.
         | 
         | Then each platform gets its own directory with .c files.
        
         | masfoobar wrote:
         | When I build C projects which, admittedly, is making a game, I
         | always start off with platform specific code. This is very
         | handy by splitting up .h and .c files.
         | 
         | ie... platform.h
         | 
         | platform_windows.c platform_linux.c etc.
         | 
         | This way, my build file for windows uses platform_windows.c,
         | etcetc.
        
       | kerkeslager wrote:
       | This (among other reasons) is why I'm moving a few of my C
       | projects over to Rust. Whenever I'm spending a half hour
       | debugging why my build system isn't picking up some file or some
       | flag is getting passed wrong, I end up asking myself, "Why am I
       | doing this?"
       | 
       | My entire build system in Rust projects consists of the commands
       | "cargo build", "cargo run", and "cargo test" and I have yet to
       | find a reason to deviate from the default filesystem structure or
       | configurations.
       | 
       | To be clear there are real reasons to still be writing C. No hate
       | to the author of this post. I just don't want to be doing it any
       | more in most cases.
        
         | colonwqbang wrote:
         | Your problems are not with C the language but with the chosen
         | build tools. Have you tried using a more modern tool like
         | Meson? It is more comparable to Cargo.
         | 
         | To start a new project today using plain GNU Make like the
         | author of the article recommends, is not a good idea unless you
         | enjoy fiddling with the build.
        
           | flykespice wrote:
           | > Have you tried using a more modern tool like Meson? It is
           | more comparable to Cargo.
           | 
           | Not comparable at all.
           | 
           | The difference is Cargo is the official standard _package
           | manager_ of the Rust language. Meson is one among the sea of
           | incompatible _build systems_ for the C language.
        
             | binary132 wrote:
             | That's why we need a new build system / package manager for
             | C. /s (but not really)
        
               | funnyflywheel wrote:
               | https://xkcd.com/927 comes to mind.
        
               | binary132 wrote:
               | Yeah, that's the joke. But I mean, you have to admit the
               | status quo isn't that good. Something really very much
               | better might be able to claw its way to the top.
        
               | keybored wrote:
               | Please perish the thought.
        
             | tialaramex wrote:
             | Right, crucially cargo is in the box with your Rust
             | compiler, you have to go out of your way to have just the
             | actual Rust compiler but not cargo.
             | 
             | As with the lesson gofmt taught, you must supply the basic
             | feature in the box. Humans are lazy, so if it doesn't come
             | in the box only people who know they can't live without it
             | will even bother, put it in the box and only people who
             | can't live with it will remove it.
        
             | humanrebar wrote:
             | But C ships to all sorts of places, so why would C limit
             | its options by limiting its support to its own package
             | manager?
        
               | kerkeslager wrote:
               | How would having a builtin build system (I'm not even
               | asking for a package manager) limit where C can ship to?
        
           | jjgreen wrote:
           | With make, you don't have to install anything, it's POSIX
        
             | yjftsjthsd-h wrote:
             | Being POSIX doesn't mean it's included by default; for
             | example, you can start with the default installation of
             | debian, install a compiler, and still not have make
             | installed
        
             | kerkeslager wrote:
             | This technically isn't true, but I'll give you the point
             | because I don't see `sudo apt install build-essentials` as
             | a significant barrier to entry.
             | 
             | But being able to print "make: ** No targets specified and
             | no makefile found. Stop." to stderr isn't particularly
             | useful, and having to write a Makefile _is_ a significant
             | barrier.
        
           | kerkeslager wrote:
           | > Your problems are not with C the language but with the
           | chosen build tools.
           | 
           | That's not a distinction I care about. The tooling around a
           | language _should_ be a part of discussing the merits of a
           | language. Your language can be the most ergonomic language in
           | the world, but if all your development gains are lost
           | fiddling with build tools, you haven 't gained anything.
           | 
           | > Have you tried using a more modern tool like Meson?
           | 
           | I haven't, but now that I know about it, I'll give it a try.
           | Thanks for the info! It looks promising.
           | 
           | > To start a new project today using plain GNU Make like the
           | author of the article recommends, is not a good idea unless
           | you enjoy fiddling with the build.
           | 
           | Maybe, but that just brings up the problem that there isn't
           | really a clear second-tier system outside the standard
           | library for tools. Certain tools (i.e. Valgrind) are well-
           | known, but there are a million different ways to install a
           | million different tools and for most things it's ultimately
           | more common to just roll your own.
        
         | nanolith wrote:
         | It is true that C does not have a single tool enforced by the
         | language platform.
         | 
         | But, there is plenty of excellent documentation available for
         | using build and packaging tools of your choice to work
         | similarly to Cargo.
        
           | jerf wrote:
           | In practice this comes off as a rather motte and bailey
           | argument. "There's plenty of ways to make building C as easy
           | as Cargo." OK, show me. "Well, you use this and do this and
           | this." OK, that didn't work because of $THIS_REASON. "Oh
           | that's easy to fix you just do this thing." {Repeat several
           | times.} OK, I guess it's working now, but there's no way that
           | was as easy as cargo. "What are you talking about, it was a
           | series of easy steps that only involved reading dozens of
           | pages of docs, a couple of not-quite-current-but-close-to-
           | accurate Stack Overflow posts, upgrading to the correct
           | version of the build tool, and directly posting on a couple
           | of mailing lists!"
           | 
           | To be honest, the moment I have to evaluate a dozen tools,
           | pick one, and use it, even _if_ that tool does manage to be
           | "$TOOL build" and literally _nothing else_ which I don 't
           | think is anywhere near something people can count on in the C
           | world, it has _already_ proved to be harder than  "cargo
           | build".
        
             | norir wrote:
             | There is a price to easy. In my experience, cargo build
             | times tend to be soulkillingly long. But I don't like
             | waiting more than a few hundred ms for incremental builds.
             | So for me, cargo build is actually harder than make to
             | attain an enjoyable build system.
             | 
             | Make is annoying but Makefile issues are found quickly and
             | easily when running `find . | entr -c make` and saving the
             | source file often.
        
               | alpaca128 wrote:
               | Build times have nothing to do with cargo but with the
               | language itself. They're not the "price to easy", it's
               | the price of the static compiler checks.
        
               | PH95VuimJjqBqy wrote:
               | that's only true if you believe dependency resolution is
               | free.
        
               | alpaca128 wrote:
               | I have never witnessed cargo being delayed from that by a
               | noteworthy amount of time.
               | 
               | I just tried it, on my PC it takes cargo 0.3 seconds to
               | start compiling the first of 309 dependencies in a clean
               | Bevy repo. The entire compilation takes 31 seconds, and
               | that's the best case with lots of multithreading and all
               | packages already downloaded. That's close enough to
               | "free" for me.
        
               | PH95VuimJjqBqy wrote:
               | doesn't really matter. You said "nothing to do with" and
               | I said "is not free".
               | 
               | 1/3 of a second for a build that has already cached all
               | dependencies means I was correct that it's not free and
               | you were being dismissive when you said it had nothing to
               | do with build times.
               | 
               | If you want to argue that it's a minimal amount of time
               | then argue that instead of what you've been arguing.
        
               | steveklabnik wrote:
               | Cargo does not do dependency resolution on every build.
        
               | PH95VuimJjqBqy wrote:
               | how does a dependency management system ensure the
               | dependencies are there without doing dependency
               | resolution?
        
               | keybored wrote:
               | Isn't it the price of the LLVM backend? That would make
               | it a rustc implementation price.
        
             | nanolith wrote:
             | First, I did not say anywhere in my reply, "as easy as
             | Cargo". I said "similar to Cargo." Huge difference.
             | 
             | C has been around for over 50 years. It predates many of
             | the modern "everything including the kitchen sink" style of
             | language platforms. It has spread across so many platforms
             | and uses that a Cargo like integration is not going to
             | happen.
             | 
             | But, this does not mean that build, package, and install
             | tools don't exist. If you want me to provide you with a
             | single tool to rule them all, well, that's impossible for
             | the reasons I listed above. But, depending on which
             | platforms you care about and which features you want, there
             | are plenty of existing tools.
             | 
             | If your goalpost is "change C to be like Rust and have a
             | single build tool", then that's impossible. If your
             | goalpost is "find me a tool that does X, Y, and Z", then
             | there that is certainly possible. There are Cargo clones
             | for C/C++ that work just fine.
        
               | jerf wrote:
               | While less aggressively "motte & bailey" then the sibling
               | reply from PH95VuimJjqBqy, you basically proved my point
               | rather than disproving it. You can't name a single tool
               | that is as easy as cargo. I know that's because if you do
               | there will be a dozen replies from users of that tool
               | going "Oh, my, no it was quite difficult to set up,
               | ultimately didn't work for us, and we had to abandon it
               | for this other tool."
               | 
               | I do not understand the mindset that believes that if you
               | explain why a problem is hard, it is somehow no longer
               | hard, or somehow it no longer "counts". Yes. C is a
               | sprawling multi-decade monster that never started out
               | with a good build tool because it was just too early in
               | history for that to be the sort of concern it is now. I
               | certainly wouldn't dream of claiming there aren't
               | _reasons_ C is complicated to build, even ones that are
               | generally _good_ when you consider the entire history of
               | the project. The world would not be better off if we had
               | somehow waited for C to be perfect before using it,
               | because it is precisely the act of using C that has
               | taught us the ways to improve other languages. I credit
               | the platform for that, I do not deduct points.
               | 
               | However, none of that changes the fact that it is a bear
               | to build anything non-trivial in C, and the simple act of
               | reading a list of your tool options is literally already
               | harder than "cargo build". I basically don't believe by
               | assertion that there are tools that can solve this with
               | just a relatively dainty (by C standards) little
               | declarative file and a single command execution. I'm sure
               | they have a "happy path" that looks like that but as soon
               | as I leave it it'll explode in complexity, and I'm
               | basically guaranteed to leave it at some point for any
               | non-trivial project.
        
               | PH95VuimJjqBqy wrote:
               | I'm going to recommend everyone ignore this poster, just
               | don't reply to them.
               | 
               | They're not going to accept anything other than "yes,
               | you're right jerf" and any attempt to do so is going to
               | be met with squawking about motte and bailey fallacy
               | because they believe internally they've found the perfect
               | defense.
               | 
               | it's not worth engaging.
        
               | kerkeslager wrote:
               | So basically you recommend everyone ignore them because
               | they disagree with you?
               | 
               | It's strange that you think this post is you "not
               | engaging".
        
               | PH95VuimJjqBqy wrote:
               | oh snap, you totally got me...
               | 
               | https://en.wikipedia.org/wiki/Paradox_of_tolerance
        
               | kerkeslager wrote:
               | If you don't think it's helpful to engage, put your money
               | where your mouth is, and don't engage, i.e. stop posting.
               | 
               | Note I'm saying _if you don 't think it's helpful to
               | engage_. I don't have any objections to you posting. It
               | just makes you look a bit silly to keep engaging while
               | claiming that engaging isn't worthwhile.
               | 
               | I'm really not sure what relevance you think that link
               | has--I'm not seeing any particular intolerance happening
               | here. Disagreement != intolerance.
        
               | PH95VuimJjqBqy wrote:
               | I'm not surprised you don't understand the point.
        
               | nanolith wrote:
               | If you're going to put words in my mouth, then there's no
               | need for me to respond any further.
               | 
               | If you are genuinely curious about build systems in any
               | language, I can certainly help you. But, if this is the
               | "Cargo is the best vs all others" debate, kindly count me
               | out. I don't care. Cargo is fine if you're doing Rust
               | work. If you're using other languages or platforms, there
               | are plenty of excellent alternatives.
               | 
               | This sort of Rust proselytizing -- on an article about C
               | of all things -- is why so many perceive the Rust
               | community as being toxic. You might think you're helping
               | to spread the glory of Rust, but that's not how you are
               | coming across.
               | 
               | Note that I'm not even comparing the merits between the
               | two languages. If you like Rust, write software in Rust.
               | If you like building your Rust projects in Cargo, have at
               | it.
        
               | kerkeslager wrote:
               | > But, if this is the "Cargo is the best vs all others"
               | debate...
               | 
               | It isn't. It's certainly better than any C build systems,
               | but I'm aware of a few other similar build systems (just
               | not for C).
               | 
               | > ...kindly count me out. I don't care.
               | 
               | Only you have the power to stop posting. And if you did,
               | it would make your claim that you don't care a lot more
               | convincing.
               | 
               | > If you're using other languages or platforms, there are
               | plenty of excellent alternatives.
               | 
               | True! But C isn't one of those languages.
               | 
               | And let's be real, that's really why you're mad. It isn't
               | that I'm bringing up Rust that's making you mad, it's
               | that I'm criticizing C.
               | 
               | C isn't perfect and it's not the best at everything. And
               | that's fine! It's great for what it is. But what it _isn
               | 't_ is a modern language with modern tooling. And that's
               | a real downside to consider when choosing a language to
               | write a project in.
               | 
               | That's not the only consideration, and there are a lot of
               | reasons why I, myself, would choose C for a new project
               | (not the least of being that I'm a lot better at C than
               | at Rust).
               | 
               | > This sort of Rust proselytizing -- on an article about
               | C of all things -- is why so many perceive the Rust
               | community as being toxic. You might think you're helping
               | to spread the glory of Rust, but that's not how you are
               | coming across.
               | 
               | I'm not particularly concerned with how I represent the
               | Rust community. I don't even view myself as part of the
               | Rust community (yet). If anything, I have a lot more
               | claim to being a member of the C community than of the
               | Rust community, as I've written orders of magnitude more
               | C code than Rust code.
        
               | nanolith wrote:
               | > > But, if this is the "Cargo is the best vs all others"
               | debate...
               | 
               | > It isn't.
               | 
               | You could've fooled me with how uncharitable you are
               | being here by blowing comments way out of proportion.
               | 
               | > Only you have the power to stop posting.
               | 
               | Full context: "But, if this is the "Cargo is the best vs
               | all others" debate, kindly count me out."
               | 
               | Using ellipses and quote mining to make your opponent
               | look foolish is the lowest form of sophistry and against
               | HN policy. There isn't a "debate" to "win" here. So, why
               | go to this sort of trouble over nothing?
               | 
               | > > If you're using other languages or platforms, there
               | are plenty of excellent alternatives.
               | 
               | > True! But C isn't one of those languages.
               | 
               | I fail to see what C has to do with the choice of build
               | tools. C isn't a build tool; it's a language.
               | 
               | > And let's be real, that's really why you're mad.
               | 
               | Who is mad? Literally, I replied with a comment about
               | build tools. You keep trying to have the debate you want
               | here by inserting words in people's mouths. It's
               | disingenuous.
               | 
               | > C isn't perfect and it's not the best at everything.
               | 
               | No one. NO ONE, has made any claims even remotely like
               | that. Yet another silly strawman for the debate you
               | desperately want to have here.
               | 
               | > But what it isn't is a modern language with modern
               | tooling.
               | 
               | Actually, there is excellent modern tooling for C. There
               | are proof assistants and model checkers. It's possible to
               | do similar things in modern C as in Rust or other
               | languages, from proving the absence of UB to proving
               | memory safety features. But, seriously, no one is having
               | this debate, so it's silly. We were talking about build
               | tools...
               | 
               | > I'm not particularly concerned with how I represent the
               | Rust community.
               | 
               | Good, because these silly "debates" -- over a build tool
               | that has somehow mushroomed into a C vs Rust debate in
               | only your head and no one else's -- might seem cool to
               | you, but are counter-productive.
               | 
               | > as I've written orders of magnitude more C code than
               | Rust code.
               | 
               | And yet, you've never done any reading about modern build
               | tools or package systems with C support. That's a mighty
               | shame.
               | 
               | By the way, did you know that you can build C/C++
               | projects using Cargo? Yeah. Me neither. Huh.
        
               | kerkeslager wrote:
               | > Full context: "But, if this is the "Cargo is the best
               | vs all others" debate, kindly count me out."
               | 
               | Okay, then my response to the full context is:
               | 
               | This isn't the "Cargo is the best vs all others" debate.
               | 
               | If you wish to be counted out, you can stop posting at
               | any time.
               | 
               | You'll note that this is basically what I said to your
               | post when I was "taking it out of context", with the
               | exception that I also responded to some context _you_
               | decided to leave out when _you_ quoted yourself.
               | 
               | > Using ellipses and quote mining to make your opponent
               | look foolish is the lowest form of sophistry and against
               | HN policy.
               | 
               | I split up your comments when I quoted them to make it
               | clearer what I was responding to.
               | 
               | > I fail to see what C has to do with the choice of build
               | tools.
               | 
               | You are definitely smart enough to figure that out.
               | 
               | The author of the original article seems to see the
               | connection, given they titled their article "How to
               | Structure C Projects" and then went on to write a bunch
               | about a build system.
               | 
               | > > C isn't perfect and it's not the best at everything.
               | 
               | > No one. NO ONE, has made any claims even remotely like
               | that.
               | 
               | Sure, nobody is foolish enough to make such a claim so
               | obviously. But if a person responds to any criticism of C
               | with vitriol, one begins to think that person is invested
               | in a belief that C is perfect.
               | 
               | > Actually, there is excellent modern tooling for C.
               | There are proof assistants and model checkers. It's
               | possible to do similar things in modern C as in Rust or
               | other languages, from proving the absence of UB to
               | proving memory safety features.
               | 
               | And I'm sure these modern tools are easy enough to use
               | that everyone uses them, given their obvious benefits,
               | right? Have _you_ even used a proof assistant?
               | 
               | To be clear what point I'm making: these tools are
               | amazing, feats of engineering, but they're not really
               | viable for most C projects. Using a proof assistant is
               | _far_ harder than you 're giving it credit for, enough so
               | that I very much doubt that you've one to prove things
               | about C programs. If you have, you're much smarter than
               | me, because I've tried, and proving anything non-trivial
               | was beyond what I could do with reasonable effort.
               | 
               | > By the way, did you know that you can build C/C++
               | projects using Cargo? Yeah. Me neither. Huh.
               | 
               | If I go into a average Rust project, and run "cargo run",
               | Cargo will build the project, and the resulting binary
               | will run.
               | 
               | If I go into a random C project, and run "cargo run",
               | that won't happen.
               | 
               | And in fact, there is not a tool in existence where I can
               | go into a random C project and run that tool, and it will
               | build the C project. "make" is about as close as you can
               | get to that, and in most C projects that simple four
               | letter command is glossing over a whole lot of work that
               | went into producing the Makefile.
               | 
               | I'll remind you of when you said, "I fail to see what C
               | has to do with the choice of build tools."
        
               | kerkeslager wrote:
               | > If your goalpost is "change C to be like Rust and have
               | a single build tool", then that's impossible. If your
               | goalpost is "find me a tool that does X, Y, and Z", then
               | there that is certainly possible. There are Cargo clones
               | for C/C++ that work just fine.
               | 
               | My goal is to be able to build my project with as little
               | effort as possible. "Effort" includes evaluating
               | different tools and reading their documentation.
               | 
               | When selecting a language to write a new project in, the
               | history of the language isn't something I care about.
        
               | nanolith wrote:
               | If your goal is "I want to use Cargo and nothing else
               | because that would require effort to learn to type a
               | different command", then use Cargo.
               | 
               | But, there are similar build and package systems for C.
               | Cargo is nothing special.
        
               | kerkeslager wrote:
               | > If your goal is "I want to use Cargo and nothing else
               | because that would require effort to learn to type a
               | different command", then use Cargo.
               | 
               | That's not my goal, and you know that.
               | 
               | > But, there are similar build and package systems for C.
               | 
               | The reason you aren't naming one is that they obviously
               | _aren 't_ similar in ways that matter.
        
               | nanolith wrote:
               | > That's not my goal, and you know that.
               | 
               | Didn't you just say that you didn't want to read about
               | any other build system, as that would involve effort?
               | Uh... okay.
               | 
               | > The reason you aren't naming one is that they obviously
               | _aren't_ similar in ways that matter.
               | 
               | Or, as I said previously, I'm not interested in endorsing
               | build tools I haven't used, because I'm not in the market
               | for a cargo clone. They exist. Plenty of folks I know are
               | happy with one or another. Build tools are not difficult
               | to write. As with the article, if you don't like existing
               | build tools, spend an afternoon writing your own. It's a
               | little graph theory and job management.
        
               | kerkeslager wrote:
               | > Didn't you just say that you didn't want to read about
               | any other build system, as that would involve effort?
               | 
               | No, I did not say that, and you also know that.
               | 
               | > Or, as I said previously, I'm not interested in
               | endorsing build tools I haven't used, because I'm not in
               | the market for a cargo clone. They exist.
               | 
               | If you've not used them, then your confidence that they
               | are equivalent to Cargo comes from where?
               | 
               | > Build tools are not difficult to write. As with the
               | article, if you don't like existing build tools, spend an
               | afternoon writing your own. It's a little graph theory
               | and job management.
               | 
               | I'm the author of the Fur programming language, which
               | does need a package management system, so I will be doing
               | this at some point. But I very much doubt that it will be
               | as easy as you claim.
        
               | nanolith wrote:
               | > > Didn't you just say that you didn't want to read
               | about any other build system, as that would involve
               | effort?
               | 
               | > No, I did not say that, and you also know that.
               | 
               | and
               | 
               | > My goal is to be able to build my project with as
               | little effort as possible. "Effort" includes evaluating
               | different tools and reading their documentation.
               | 
               | Apparently, I don't know that, or I'm being trolled. What
               | even is knowing something when folks contradict
               | themselves two replies in?
               | 
               | > If you've not used them, then your confidence that they
               | are equivalent to Cargo comes from where?
               | 
               | The endorsements of others who have used the tools. But,
               | I'm starting to suspect that you wouldn't consider any
               | endorsement of any tool other than cargo as valid...
               | 
               | > which does need a package management system, so I will
               | be doing this at some point. But I very much doubt that
               | it will be as easy as you claim.
               | 
               | Note that I said "build tool". A package management
               | system is a hair more complicated. You'll need a week
               | instead of an afternoon.
        
             | PH95VuimJjqBqy wrote:
             | > To be honest, the moment I have to evaluate a dozen
             | tools, pick one, and use it, even if that tool does manage
             | to be "$TOOL build" and literally nothing else which I
             | don't think is anywhere near something people can count on
             | in the C world, it has already proved to be harder than
             | "cargo build".
             | 
             | C will build and run in places rust can't.
             | 
             | so I should argue C is better because in my chosen metric
             | (diversity of platforms) it wins.
        
               | jerf wrote:
               | Thank you for that demonstration of the motte & bailey
               | argument.
        
               | PH95VuimJjqBqy wrote:
               | thank you for being a dismissive jackass.
        
           | kerkeslager wrote:
           | > But, there is plenty of excellent documentation available
           | for using build and packaging tools of your choice to work
           | similarly to Cargo.
           | 
           | Sure, and there's plenty of documentation for Cargo too. Or
           | at least I assume there is, but I've never had reason to read
           | it.
           | 
           | Do you see the problem with what you're saying?
        
             | nanolith wrote:
             | > Do you see the problem with what you're saying?
             | 
             | No, I don't. At some point, you read documentation, or a
             | tutorial, or example code to use Cargo.
             | 
             | These exist for other build and package systems, some of
             | which are clones of Cargo.
             | 
             | If you want a white glove experience (e.g. cargo new), such
             | systems exist as well for C. Cargo is nothing new.
        
               | kerkeslager wrote:
               | > At some point, you read documentation, or a tutorial,
               | or example code to use Cargo.
               | 
               | ...and the point you're pretending you don't understand,
               | is that the parts I needed were simple enough that I now
               | have it memorized.
               | 
               | > These exist for other build and package systems, some
               | of which are clones of Cargo.
               | 
               | These exist... but you're not going to even mention any,
               | because you're aware that they aren't equivalent. They
               | don't ship with your C compiler, they don't work for
               | every C project without configuration, etc.
               | 
               | I'm open to the possibility that there are better C build
               | tools than the ones I've been using, but they literally
               | cannot be as easy as Cargo because they're hampered by
               | supporting decades of legacy features (and mistakes) of
               | C, the fact that none of them are _the_ standard, etc.
               | 
               | And look, no hate on C. It's done its job for decades and
               | if we're measuring by the problems that have been solved
               | in a language, it's arguably the best language in
               | existence. I've certainly chosen it for a lot of my own
               | projects. But do you really think it's _perfect_? Are you
               | really unwilling to admit that there is _anything_ newer
               | languages do better?
        
               | nanolith wrote:
               | > ...and the point you're pretending you don't
               | understand...
               | 
               | You're making some very interesting assumptions about
               | what I'm thinking here.
               | 
               | > These exist... but you're not going to even mention
               | any, because you're aware that they aren't equivalent.
               | 
               | Again, you're assigning motives to me based on unfounded
               | assumptions. Actually, I haven't mentioned any because I
               | have no reason to endorse any of them. Not because they
               | are inferior, but because I'm not in the market for a
               | cargo clone. If you were interested in learning more,
               | Google is your friend. If not, well, I'm not here to have
               | some silly debate about how Rust and Cargo are superior
               | to everything ever invented. I don't care.
               | 
               | > but they literally cannot be as easy as Cargo because
               | they're hampered by supporting decades of legacy features
               | 
               | New tools are opinionated. They choose features commonly
               | used in modern software.
               | 
               | > the fact that none of them are _the_ standard
               | 
               | None of them _could_ be the standard, because C is used
               | on thousands of different platforms.
               | 
               | > But do you really think it's _perfect_?
               | 
               | No one, anywhere in this thread, has made this argument
               | or anything approaching this argument. No language is
               | perfect.
               | 
               | > Are you really unwilling to admit that there is
               | _anything_ newer languages do better?
               | 
               | That's another bizarre strawman. No one on this thread
               | has made claims remotely approaching this.
               | 
               | You claimed that you were switching projects to Rust
               | because you don't like C build systems. Taking your
               | comment charitably, I replied that there are build and
               | package tools for C that are literal clones of Cargo.
               | But, now you're making assumptions about my motivations
               | for pointing out this in the comment section of an
               | article about C development and building strawman
               | arguments. Why?
               | 
               | I'm not getting sucked into a Rust debate. I don't care.
               | You like Rust? Use it. You like Cargo? Go for it. But, if
               | the reason why you are using either is because you can't
               | find a similar tool for C development, then I recommend
               | doing the reading. The amount of reading required to
               | switch to a new build tool is significantly less than the
               | amount of work required to port even a medium complexity
               | C project to Rust. Of course, if you're looking for an
               | excuse to use Rust, you don't need to use build tools as
               | that excuse. Just write code in Rust.
               | 
               | Debating this is silly.
        
         | lelanthran wrote:
         | Every C discussion on the net always gets derailed by Rust
         | proselytising.
         | 
         | No other language community does this to other language
         | discussion. Even C++ fanatics didn't do this back when C++ was
         | being sold as a C replacement.
         | 
         | This is an entirely new and somewhat sad, phenomenon: the need
         | for the rust community to derail any and all discussion about
         | some other language reeks of a community that feels threatened.
        
           | twic wrote:
           | The comment you're responding to wasn't written by "the rust
           | community", it was written by kerkeslager, who has written a
           | bunch of C programs, but has found that the Rust build tools
           | are better. There is nothing new or sad about it.
        
             | lelanthran wrote:
             | > The comment you're responding to wasn't written by "the
             | rust community"
             | 
             | This makes no sense. Almost by definition, anyone derailing
             | a discussion to mention $FOO is a member of the $FOO
             | community.
             | 
             | It's disingenuous to suggest that all derailment is due to
             | non-$FOO advocates.
             | 
             | > There is nothing new or sad about it.
             | 
             | The phenomenon of persistent derailment is, in fact,
             | something that I had not seen until Rust arrived.
             | 
             | It _is_ new, it 's limited _only_ to Rust advocates as a
             | group, and _I_ find it sad.
        
               | jcranmer wrote:
               | > The phenomenon of persistent derailment is, in fact,
               | something that I had not seen until Rust arrived.
               | 
               | Apparently you have never seen a thread on X11, which
               | almost invariably is derailed by a discussion about
               | Wayland. Or in general, any discussion on Wayland or
               | systemd is derailed into a rant how they're the worst
               | software ever developed or something, to a tedious degree
               | that makes me long for the Rewrite-it-in-Rust comments
               | because at least those sometime make compelling points.
        
               | lelandbatey wrote:
               | I also find that Go has delightful build tooling; running
               | 'go build ./...' is nearly the universal build tool.
               | 
               | There, now we're not limited to just Rust advocates, so I
               | hope you no longer find this thread sad : )
        
               | kerkeslager wrote:
               | Agreed, go has good build tooling. And pretty good
               | tooling in general.
               | 
               | And if you search my post history, you'll find some posts
               | that make it clear I _despise_ go.
               | 
               | As it turns out, languages aren't religions--you can like
               | a language and admit it has flaws, and you can hate a
               | language and admit it has strengths.
        
             | PH95VuimJjqBqy wrote:
             | It turns out, the rust community is made up of individuals.
             | No one made the claim that there's some single entity
             | called the rust community that's doing this. It's the
             | individuals w/i that community doing it.
             | 
             | so yes, you pointed out the moniker of the individual doing
             | this. It doesn't obviate the point.
        
           | keybored wrote:
           | Every <lang> discussion here gets sidetracked/sidethreaded by
           | <lang2> mentions.
           | 
           | - Rust? Why not Nim?
           | 
           | - Nim? Why not Zig?
        
           | kerkeslager wrote:
           | 1. I've written, at most, 10,000 lines of Rust. Calling me
           | "the Rust community" is quite a stretch.
           | 
           | 2. For comparison, I've almost certainly crossed the million
           | lines of C mark years ago--if anything, I'm the C community.
           | 
           | 3. You and I remember very different things about the C++
           | community. In fact, I also remember a number of other
           | languages being offered as alternatives to C over the years
           | including some obvious ones like Pascal or Basic, and also
           | including some real absurdities like Lua (nothing wrong with
           | Lua, but it's really not for the same kinds of projects as
           | C). The idea that comparing your language to C is some sort
           | of new phenomenon, is flat wrong.
           | 
           | 4. You're ascribing a lot of intent to me bringing up Rust
           | which isn't there. I'm not trying to derail the discussion--
           | if you want to talk about C, do it.
           | 
           | 5. The idea that the Rust community is threatened by C is a
           | bit bizarre.
        
         | jvanderbot wrote:
         | I have the same problem with Rust modules:
         | 
         | e.g., _where_ do I put crate::, vs module X, vs module X{ }
         | again? Do I have to specify it in the automatic lib.rs too? Or
         | ...
         | 
         | C at least lives on the filesystem and you tell it where to
         | grab things.
        
           | treyd wrote:
           | Module definitions in Rust have simple rules that resolve
           | `mod foo;` into foo.rs or foo/mod.rs, which lives in the
           | filesystem layout just as it does in C, you just don't have
           | to use quotes and it has to form a tree from a root module at
           | lib.rs. If you want to be even more explicit about it there's
           | an attribute you can put on a `mod` item to override the
           | normal path resolution rules.
        
           | kerkeslager wrote:
           | > e.g., where do I put crate::, vs module X, vs module X{ }
           | again? Do I have to specify it in the automatic lib.rs too?
           | 
           | Sure, but there's one clear answer to that question which you
           | can refresh your memory of in under 3 minutes of reading.
           | 
           | C does live in the filesystem, but _where_ in the filesystem?
           | Oh and it doesn 't _always_ live in the filesystem--sometimes
           | it lives in the package system.
        
         | cmovq wrote:
         | I don't understand why people bother with complex build systems
         | for small C projects.
         | 
         | Here is a C build system:                   cat all.c
         | #include "foo.c"         #include "bar.c"         #include
         | "main.c"              clang all.c
        
           | eschneider wrote:
           | #include "/dev/tty0"
           | 
           | so you can add run-time defines.
        
           | humanrebar wrote:
           | Now add an address sanitizer pass, reasonable unit tests, and
           | wire it up to CI so you don't accidentally break things or
           | trash memory.
        
           | kerkeslager wrote:
           | Sure, trivial problems only require trivial solutions.
           | Obviously.
           | 
           | But I don't want to solve trivial problems. That's not where
           | the value is.
        
         | hgs3 wrote:
         | > "cargo build", "cargo run", and "cargo test"
         | 
         | What about "gcc mycode.c" or "make"? You shouldn't need to
         | reinvent your build system every project.
        
           | kerkeslager wrote:
           | `gcc mycode.c` has to be some sort of joke you're making. I'm
           | not trying to compile "Hello, world" in C, I'm writing C code
           | that will be run in production to solve real-world problems.
           | 
           | For projects of any significant size, you're going to run
           | into some constraint which requires you to use `make`
           | differently than you have before.
        
             | cantours wrote:
             | >For projects of any significant size, you're going to run
             | into some constraint which requires you to use `make`
             | differently than you have before.
             | 
             | That's precisley why official/standard build systems suck,
             | they are extremly cumbersome to wrangle when you go off the
             | beaten path.
             | 
             | So the irony is that standard build systems/package manager
             | are whats good for hello worlds, non-trivial programs
             | require custom build steps.
             | 
             | Just let me write a build.bat/build.sh per
             | platform/compiler/configuration that are explicit and
             | precise in compiler flags, paths, output files, pre/post
             | processing, etc so nothing magical is happening under the
             | hood.
        
               | kerkeslager wrote:
               | > That's precisley why official/standard build systems
               | suck, they are extremly cumbersome to wrangle when you go
               | off the beaten path.
               | 
               | That is the first sensible criticism I've heard in this
               | thread. Thanks for engaging maturely and with an insight
               | that can only come from actual experience.
               | 
               | My counterargument is that the Make experience is
               | cumbersome to wrangle on projects that are very much _on_
               | the beaten path. But I see your point that it is
               | flexible, i.e. it doesn 't get _more_ cumbersome if you
               | go off the beaten path.
               | 
               | The other caveat I'll give is that my rosy view of cargo
               | may be in part because I haven't written a _lot_ of Rust
               | code.
               | 
               | > Just let me write a build.bat/build.sh per
               | platform/compiler/configuration that are explicit and
               | precise in compiler flags, paths, output files, pre/post
               | processing, etc so nothing magical is happening under the
               | hood.
               | 
               | I agree with this completely, and this is why I avoid
               | what I call "configuration-oriented programming", which
               | is where you're trying to do something that requires a
               | programming language, but you have something like JSON
               | /YAML/TOML instead of a programming language.
               | 
               | But at a glance it appears to me that `cargo` can farm
               | out to the programming language of your choice pretty
               | easily so it doesn't feel like this critique applies.
        
       | fforflo wrote:
       | I'm not so sure about having bin/lib in the project dir.
       | 
       | I've found it more helpful to have a PREFIX=$(pwd)/build or even
       | PREFIX=$HOME/.local as an installation destination. And have a
       | .env add PATH=$PREFIX/bin:PATH
        
       | fforflo wrote:
       | I'm not so sure with having bin/lib in the project dir.
       | 
       | I've found it more helpful to have a PREFIX=$(pwd)/build or even
       | PREFIX=$HOME/.local as an installation destination. And have a
       | .env add PATH=$PREFIX/bin:PATH
        
       | Arch-TK wrote:
       | The recommended Makefile has several issues, not least of which
       | being that it relies on dependency fulfilment order which can be
       | non-deterministic (e.g. if you pass -j).
       | 
       | The project structure includes flavour-of-the-week litter like
       | .devcontainer, .github, .vscode.
       | 
       | compile_commands.json should be getting generated, not stored.
       | 
       | Not sure if I would recommend anyone follow this information.
       | 
       | The build system situation in the C world is dire. And I should
       | know, given I write my own build systems for C.
       | 
       | I would say, while I've never actually used it, Meson seems the
       | least non-sensical out of the box experience.
       | 
       | Ideal in some ways but not in others is designing and
       | implementing your own build system because genuinely everything
       | else out there has some major shortcoming or other.
       | 
       | If you are insistent on make, stick to GNU make, read the GNU
       | make manual, understand make and then write something simple and
       | non-recursive.
       | 
       | This is the most recent Makefile I helped create:
       | CXX ?= clang++         CXXFLAGS += -std=c++20 -Wall -Wextra
       | -fsanitize=address -ggdb3 -MMD -MP         LDFLAGS +=
       | -fsanitize=address                  OBJ := program.o parse.o
       | all: program compile_flags.txt                  program: $(OBJ)
       | $(CXX) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@
       | $(OBJ): Makefile                  compile_flags.txt: Makefile
       | printf "%s\n" $(CXXFLAGS) >$@                  clean:
       | $(RM) *.o *.d program compile_flags.txt                  -include
       | $(OBJ:.o=.d)                  .PHONY: clean
       | 
       | If you want to do something like run a linter, code formatter,
       | etc, before building, then this should be performed by a script
       | which calls into make. Make is not designed to be used as a
       | switch statement for your bash snippets.
        
         | ReleaseCandidat wrote:
         | > The build system situation in the C world is dire.
         | 
         | Meta's Buck 2 (written is Rust) would be my choice, if I would
         | write "new" C or C++.
         | 
         | Examples using Vcpkg
         | 
         | https://github.com/Release-Candidate/Cxx-Buck2-vcpkg-Example...
         | 
         | And Conan
         | 
         | https://github.com/Release-Candidate/Cxx-Buck2-Conan-Example...
        
           | humanrebar wrote:
           | If you're writing C, you might not want tools written in
           | higher level languages required for your build environment.
           | 
           | This is why you still see a lot of important C project
           | support autotools even though it's a pretty awful developer
           | experience.
        
             | jpc0 wrote:
             | Calling rust a high level language gave me a chuckle. Thank
             | you sir and incase anyone wonders, I agree.
        
         | eska wrote:
         | It seems weird to me to write your own build system and give
         | advice on build systems when your basic makefile already
         | completely violates what the manual suggests.
        
           | Arch-TK wrote:
           | Unnecessarily vague and uncharitable comment but I'll bite.
           | 
           | While I recommend anyone who uses GNU make should read the
           | manual, the manual is full of outdated best practices which
           | can be safely ignored.
           | 
           | If I was going to use this makefile for my own project, I
           | would even drop the clean target entirely. phony targets are
           | mostly a misfeature. Although it looks like the makefile as
           | accepted by the project hasn't marked the all target as
           | phony.
           | 
           | But go on, tell me what it is that is not recommended
           | specifically. I am aware of many deviations from what the GNU
           | make manual claims is recommended. A large portion of
           | deviations is the lack of many of the standard targets it
           | thinks should be provided. Another deviation might be the
           | uppercase OBJS should be lowercase according to the manual,
           | but in this case I was trying to minimize the number of
           | immaterial changes from the original makefile I modified to
           | arrive at this point since it was not my project.
        
         | LabMechanic wrote:
         | You can consider using MinUnit and Clang-Tidy together with
         | Clang-Format as well, further, for C it should be something
         | more like this:                   CC = clang         CFLAGS =
         | -g -std=c2x -Weverything -fsanitize=undefined,address
         | all: app.exe                       app.exe: src/main.c
         | $(CC) $(CFLAGS) src/main.c                 clean:         rm
         | -rf /build
         | 
         | See my comment here with an example of an NMAKE Makefile.
         | Consider:
         | 
         | https://nullprogram.com/blog/2017/08/20/
        
         | UncleEntity wrote:
         | > ...not least of which being that it relies on dependency
         | fulfilment order which can be non-deterministic (e.g. if you
         | pass -j).
         | 
         | Didn't actually know that was a thing until I accidentally ran
         | into it yesterday when I ran the make command in the wrong
         | terminal window and the build was very, very unhappy.
         | 
         | At least it didn't bork my entire /home directory like that
         | time we don't talk about...
         | 
         | My general rule is Make for simple things and CMake if it
         | involves distro-packed library deps or I'm building python
         | modules with complicated enough builds that I don't wan't to
         | beat the dead horse of distutils into submission.
         | 
         | Honestly, if I can't tell what the build system is doing
         | without having to ask google then it's way too complicated.
        
       | ReleaseCandidat wrote:
       | Am I missing something, or are the examples both projects with
       | less than 20 C source files?
        
       | JonChesterfield wrote:
       | I write a lot of C. I'm somewhat prone to writing things in C
       | that shouldn't be. There are two somewhat unusual things that
       | make that more viable than it sounds.
       | 
       | One, write unit tests in the same source as the implementation.
       | Tests that run through the interface are good too and can be in
       | separate source files, maybe in another directory. Fine grain
       | poke at the internals of static functions really should be
       | written as well and those are best placed where they can call
       | said static functions directly. Make adding tests really low
       | friction and write lots of them.
       | 
       | Two, embrace code generators. I've seen it written that C++
       | solves problems by extending the language and C solves problems
       | by writing more C and that sounds about right. Make adding a code
       | generator as low friction as adding a C source file. It should
       | take seconds to convert an existing source file into a generator
       | that recreates said existing source file.
       | 
       | People love ceremony. All the source files are written in lists
       | in cmake files. All their build dependencies are written down in
       | the cmake files as well. All the tests are written in list of
       | tests to run. Every code generator is introduced as an
       | independent project with it's own documentation and careful
       | integration into the build systems.
       | 
       | I love writing C because I do none of that overhead. The files to
       | compile are whatever is on disk. The tests to run are all the
       | tests in the source, they aren't listed somewhere else as well.
       | Any file called *.lua.c is a lua program that writes the intended
       | contents of *.c to stdout. *.c.h will be compiled as C and then
       | run to write a C header on stdout.
       | 
       | Convention over configuration with a language that requires zero
       | cognitive overhead to write.
        
         | memoryperson10 wrote:
         | Wild, I was just thinking about how I wanted to do reflection
         | on C structs using LuaJIT FFI for serialization.
        
           | JonChesterfield wrote:
           | It's worth considering the other direction - write the struct
           | definition in json or xml, or dicts in python source or
           | whatever, and generate the C structs and the associated
           | functions and data from that.
           | 
           | Go slightly further and write out function types like that
           | and you get something equivalent to swig, where instead of
           | parsing C and trying to emit wrappers for other languages
           | from that framework, you have the data in native lua tables
           | that you emit code from.
        
         | bluetomcat wrote:
         | In procedural C land, unit testing is nowhere near as relevant
         | as it is in memory-safe OO lands. Assertions are far more
         | relevant. You want to detect illegal program states in
         | development as early as possible. In C11, you also have
         | static_assert for things like "is this static lookup table as
         | big as needed".
        
           | monsieurbanana wrote:
           | I get why assertions would be more important than in memory
           | safe languages, but why are unit tests less important in C
           | than in higher-level languages? Or you just mean relative to
           | assertions? (which to me don't really influence each other in
           | that way)
        
             | bluetomcat wrote:
             | Because the coding style is different. The basic unit of
             | code is the procedure, not abstract entities like
             | DateTimeCalculator or EmployeePayrollManager. Procedures
             | are sensitive to the context where they are called. A
             | procedure calling another procedure usually takes care to
             | provide valid input data. Robust error-checking must be in
             | place, or your program is unsafe.
             | 
             | A unit test wouldn't know the calling context of procedures
             | deep in the call graph, and cannot detect unsafe but
             | seemingly working code.
        
               | vkazanov wrote:
               | This is more of a culture surrounding the language than
               | anything else. Modern C avoids global state, prefers
               | directly specified context and even allocation is
               | external to the function's logic.
               | 
               | And testing functions is easy.
               | 
               | This is as opposed to procedures, I.e. functions that
               | operate on global state with fragile conext assumptions,
               | which almost impossible to replicate in test
               | environments.
        
               | lelanthran wrote:
               | > The basic unit of code is the procedure, not abstract
               | entities like DateTimeCalculator or
               | EmployeePayrollManager.
               | 
               | I agree, but in almost any non-trivial well-run C project
               | the basic unit is going to be datatypes, opaque pointers,
               | modules, etc.
               | 
               | In that case I generally throw in a `bool
               | MyObjectType_test(void)` into the module so, in some
               | sense, I'll have some basic sort of testing. Not as nice
               | as the OO languages provide, but enough to give myself
               | some confidence that changes don't introduce blatant
               | bugs.
        
               | monsieurbanana wrote:
               | I work mostly with functional programming languages where
               | the function is the basic unit of code, what you say is
               | true but is also true for "abstract entities". Not all
               | classes need robust error checking if you control how
               | they are being called.
               | 
               | > The basic unit of code is the procedure
               | 
               | I think ultimately this is where we think differently. If
               | you are okay testing a whole class in an OOP context and
               | calling that a unit test, then a test that calls multiple
               | procedures (because one of them creates the input for the
               | main procedure I want to test, for example) can also be
               | considered a unit test.
        
               | Gibbon1 wrote:
               | I also think because it's lower level what functions are
               | built on tend to be long term stable. Function was tested
               | at one point and then used in production for a couple
               | years. It works and will continue to work as long as no
               | one messes with it.
        
           | hgs3 wrote:
           | > In procedural C land, unit testing is nowhere near as
           | relevant as it is in memory-safe OO lands.
           | 
           | I'm not sure why you'd say this. Testing has more to do with
           | the developer than the language. The SQLite project is
           | written in C and has 590 times more test code than source
           | code [1].
           | 
           | [1] https://www.sqlite.org/testing.html
        
         | iainmerrick wrote:
         | _People love ceremony. All the source files are written in
         | lists in cmake files._ [...] _I love writing C because I do
         | none of that overhead. The files to compile are whatever is on
         | disk._
         | 
         | I couldn't agree more. I can't stand build tools that don't
         | take globs! I shouldn't need to list the same thing in multiple
         | places just to make it work. Almost all the time, the files on
         | disk should be the only list needed.
        
           | ndiddy wrote:
           | The reason why people write all the source files in a list in
           | a cmake file is that it means that when one developer adds a
           | new file, everyone else's build system automatically
           | regenerates the next time they try to build the project. When
           | you use globs (which cmake does support), it means that the
           | CMakeLists.txt file doesn't get modified when adding a new
           | file, so the cmake generated makefiles/ninjafiles/whatever
           | have no way of knowing that the build was modified. Instead
           | of having the build system regenerate and then the build go
           | through, they get a linker error and have to manually run
           | "cmake -B build" or whatever to fix it.
        
             | Filligree wrote:
             | Somehow this isn't a problem with Cargo.toml, however.
             | What's cmake missing?
        
               | staunton wrote:
               | > What's cmake missing?
               | 
               | Where to begin... Maybe let's start with "a build
               | system".
               | 
               | (I'm serious, CMake isn't a build system and doesn't have
               | one. It _supports_ a few...)
        
               | madduci wrote:
               | What are the alternatives?
        
             | humanrebar wrote:
             | CMake can detect when new source files are added and rerun
             | itself. You need to know how to spell that option when
             | globbing though.
        
               | ndiddy wrote:
               | From the cmake documentation:
               | 
               | > The CONFIGURE_DEPENDS flag may not work reliably on all
               | generators, or if a new generator is added in the future
               | that cannot support it, projects using it will be stuck.
               | Even if CONFIGURE_DEPENDS works reliably, there is still
               | a cost to perform the check on every rebuild.
        
             | xboxnolifes wrote:
             | Is this much of a problem if you just assume the mentality
             | of having to run cmake every time you pull code?
        
           | humanrebar wrote:
           | CMake supports globs. It just has notes in the reference docs
           | about why that can lead to slower build times or even
           | incorrect builds (like undetected new files) in some cases.
        
         | rwbt wrote:
         | I'm curious how your build process works with the codegen
         | approach. Do you have the codegen run a pass everytime you
         | build the project? Or is it a manual process?
         | 
         | I've always wanted to use Lua to generate boilerplate C code
         | (like typesafe vectors) instead of relying on macros, so might
         | actually start thinking in this direction going forward.
        
           | turtledragonfly wrote:
           | Not the person you asked, but this sort of thing is pretty
           | easy with templating languages, such as Perl's
           | Template::Toolkit[1] or Python's Jinja2[2].
           | 
           | The advantage of a system like that over a plain programming
           | language (such as Lua you mentioned) is that there's less
           | escaping and string.format(xxx) sorts of things. The output
           | text is the _primary_ thing and the templating language has
           | its own special syntax to  "activate" it when you need it,
           | rather than the other way around.
           | 
           | That being said, Lua's square-bracket strings are pretty good
           | for bulk text, too (:
           | 
           | [1] http://template-toolkit.org/about.html
           | 
           | [2] https://jinja.palletsprojects.com/en/3.1.x/templates/#syn
           | ops...
        
             | rwbt wrote:
             | Template-Toolkit seems like the right tool for the job.
             | Thanks for sharing.
        
           | JonChesterfield wrote:
           | Instead of code generation as a special case, let it be the
           | default. That kills most of the complexity.
           | 
           | src/ contains lots of code in lots of languages, all
           | describing code generators of varying complexity. src/foo.c
           | uses 'cp' as the code generator. src/bar.c.py uses 'python'.
           | 
           | gen/ contains the result of the ad hoc code generators. Same
           | tree layout as src, one to one map from those files. All of
           | that is C. Files that get #included and don't make sense
           | standalone I tend to suffix .data, e.g. src/table.data.lua
           | runs to create gen/table.data.
           | 
           | obj/ contains all the C compiled to object code or llvm
           | bitcode, depending on the toolchain.
           | 
           | I originally did that with the idea that I could distribute
           | gen/ as the source code for people who wanted to build the
           | project without dealing with the complexity of the build
           | system, strongly inspired by sqlite's amalgamation. Sqlite is
           | built from a chaotic collection of TCL and C code generators
           | but you don't see that as a user of sqlite.c.
           | 
           | It turns out that debugging the stuff under gen/ when things
           | go wrong is really easy. Valgrind / gdb show you the boring
           | code that the generators stamped out. So in practice I keep
           | that separation because it makes it easier to trace through
           | the system when it behaves unexpectedly.
           | 
           | Lua does that _really_ well. Good multiline string literal
           | support. A template string that you call gsub() on a few
           | times then dump to stdout is very quick to put together. You
           | 've got string format for more complicated things, maybe
           | using one of the string interpolation implementations found
           | on their wiki.
        
         | hgs3 wrote:
         | > embrace code generators.
         | 
         | You also don't need to limit yourself to the C pre-processor.
         | There are other dedicated pre-processing languages, like M4,
         | and templating languages you can use.
        
           | bch wrote:
           | Sort of happy to see m4 mentioned, but for the uninitiated:
           | m4 is the receiver of an awful lot of hate, because things
           | can get unwieldy quickly. Go in with your eyes open.
        
           | JonChesterfield wrote:
           | Definitely don't limit yourself to the C preprocessor. That
           | way lies a tarpit. M4 took several months off me, it can
           | definitely be used to do things but maybe shouldn't be. A
           | really bad play was generating C++ from cmake as a compromise
           | that delighted noone.
           | 
           | Python is good at generating C source. I changed to mostly
           | using lua five years ago as python climbed the complexity
           | curve. Sometimes the right thing is C itself, e.g. laying out
           | data based on sizes of various C types. Currently I'm trying
           | to use xslt to emit syntax trees that pretty print to get the
           | generated source in what is probably a dead end.
        
         | beamsen wrote:
         | > I love writing C because I do none of that overhead. The
         | files to compile are whatever is on disk. The tests to run are
         | all the tests in the source, they aren't listed somewhere else
         | as well. Any file called _.lua.c is a lua program that writes
         | the intended contents of_.c to stdout. *.c.h will be compiled
         | as C and then run to write a C header on stdout.
         | 
         | Do you have any sample projects that showcase both the tests
         | and the code generation? I'd love to take a look.
        
           | JonChesterfield wrote:
           | The tests look much like https://github.com/JonChesterfield/E
           | vilUnit/blob/master/evil.... That's from the self tests for
           | the test framework. It's syntactically modelled on catch2.
           | The name is because I built the thing out of preprocessor
           | macros to run on freestanding c89.
           | 
           | The projects using code generation in that fashion are
           | proprietary. There's a makefile at
           | https://github.com/JonChesterfield/boring-
           | makefile/blob/mast... set up to do the src/gen/obj codegen by
           | default structure.
           | 
           | Just made the ad hoc xml codegen hackery public, some
           | examples of tests written a couple of days ago at https://git
           | hub.com/JonChesterfield/xml/blob/main/tools/intse...
        
       | thesnide wrote:
       | I sense incoming bikeshedding in the comments.
       | 
       | Just have a look at how many C make replacement exists
        
       | whiterknight wrote:
       | Start by actually writing code. Use gcc command to compile. Put
       | the command in a shell script or make file when you start to get
       | more than a few arguments. Done.
        
         | LabMechanic wrote:
         | Use MinUnit.h
         | 
         | https://jera.com/techinfo/jtns/jtn002
         | 
         | Consider using Clang-Tidy with                 `-*,cert-*`
         | 
         | (Which disables all default options, and checks your code
         | according to the Secure coding standard (CERT) ruleset.)
         | 
         | Use Clang-Format as well. If you use Clang, consider
         | `-Weverything -fsanitize=address,undefined` and disable options
         | that you do not need. Consider using NMake or Make (or just
         | write a simple script to automate building the stuff).
         | 
         | Here's a Google-esque style guide for C:
         | 
         | https://gist.github.com/davidzchen/9187878
         | 
         | At least, this is my approach.
        
           | LabMechanic wrote:
           | Here's a simple NMAKE Makefile using MSVC to compile a Win32
           | application on Windows 11:                 CC = cl.exe
           | LD = link.exe       CFLAGS =  /Od /Zi /FAsu /std:c17
           | /permissive- /W4 /WX       LDFLAGS = /DEBUG /MACHINE:X64
           | /ENTRY:wWinMainCRTStartup /SUBSYSTEM:WINDOWS       LDLIBS =
           | user32.lib gdi32.lib       PREFIX = ../src/            all:
           | app.exe            app.exe: app.obj       $(LD) $(LDFLAGS)
           | /OUT:app.exe app.obj $(LDLIBS)            app.obj:
           | $(PREFIX)app.c       $(CC) /c $(CFLAGS) $(PREFIX)app.c
           | clean:        rmdir /q /s build
        
       | hashtag-til wrote:
       | Sounds like the perfect theme for a Cookiecutter template.
       | 
       | https://cookiecutter.readthedocs.io/en/stable/
        
       | TheCipster wrote:
       | In recent times I prefer Xmake[0] to CMake.
       | 
       | [0] https://xmake.io
        
       | Alifatisk wrote:
       | My only wish was that C had a package manager, and maybe a
       | dynamic analyzer to could catch bugs that a static analyzer would
       | miss.
       | 
       | Also, being able to cross-compile would be cool. But I guess
       | cosmopolitan solves this.
        
         | GuestHNUser wrote:
         | zig cc is a top notch cross compiler that you should check
         | out[0].
         | 
         | [0] https://andrewkelley.me/post/zig-cc-powerful-drop-in-
         | replace...
        
         | teunispeters wrote:
         | Ah satire. ;)
         | 
         | Package manager: dynamic libraries! Or if you want to get
         | really advanced, pkg-config and related. (or static libraries
         | even. Common enough in embedded space!)
         | 
         | Dynamic analyzer : valgrind, gdb, Visual Studio, xCode, ...
         | 
         | Cross compiling : more platforms supported by C compilers than
         | by any other build tool. Oh sure, you need to eventually watch
         | byte sizes (as 8 bit bytes are a fairly narrow set of
         | platforms), and know something about the platform's kernel API
         | (if any, lots of embedded platforms are just hardware
         | interactions!).
         | 
         | So I'll assume this is satire.
        
           | skydhash wrote:
           | That makes a while since I wrote C, but I was surprised by
           | GP's comment. I think C has the most tooling available
           | nowadays (they may not be easy to use). And with its
           | interoperability, can be used together with a lot of
           | languages.
        
         | jll29 wrote:
         | Nobody is held back from adding further components to the GNU
         | GCC or Clang/LLVM compiler suites.
         | 
         | Conan, gdb/DDD, Rational Purify etc. exist and can be
         | replaced/improved by better tools.
        
         | JonChesterfield wrote:
         | The C++ world is keenly interested in having a package manager.
         | I believe there are a few competing ones now.
         | 
         | All I've seen in practice is "vendoring", which amounts to copy
         | the source into your tree with some degree of review or
         | patching, and then go on as before. After being initially
         | indignant that we didn't use libboost from the OS that approach
         | has really grown on me.
         | 
         | Dependency management is a horrendous mess. Moving the logic
         | into package managers hides that mess from the day to day
         | development which is very nice. However, following a debugger
         | into the binaries that came from apt install is not a great
         | time. Patching the source to behave differently while trying to
         | work out what is going wrong is rather harder too.
         | 
         | If you do the vendored thing, you also get the interesting
         | feature where you can checkout the source tree as it was eight
         | months ago to correspond with whatever a customer is asking
         | about and it builds and runs, instead of telling you that the
         | dependencies no longer exist.
         | 
         | I'm now heading further in that direction. All the libraries
         | should be built from source in the same repo, but preferably by
         | tools that are also in that repo. I'm going to end up with a C
         | compiler checked in as well, much like the embedded toolchain
         | people are prone to doing.
        
       | danjl wrote:
       | Isn't this basically, "how to structure a large project". The
       | bits that are C-specific are small. This could easily be changed
       | to JavaScript, Rust, Java or any language with only small
       | modifications.
        
       | ww520 wrote:
       | Just this week I start looking into Zig. It's a joy to use the
       | language. The compiler and the build tool come in one executable.
       | Most of the needs to decide on the project structure are
       | eliminated. The project scaffold including the directory layout,
       | build files and initial source files is generated with the init
       | command. The build file is written in Zig itself, and have all
       | the directory layout configured. You can modify it but the
       | default structure is pretty sensible.
       | 
       | Zig really removes a lot of hurdles in the onboarding process and
       | let you jump into building software right the way.
        
       | jll29 wrote:
       | What's proposed as the "modular" approach is close to what I use,
       | perhaps with the following modifications:
       | 
       | 1. instead of folders for sub-systems at the top level, put them
       | all into src/.
       | 
       | 2. I use the name ext/ instead of lib/ for external libraries,
       | because in POSIX, the lib/ name indicates where the binaries of
       | static and dynamic/shared libraries reside (not their sources).
       | 
       | I use a simple sh script called mkprj <project-name> to create
       | that folder hierarchy for a new project.
        
       | o11c wrote:
       | Compiling with `-Werror=missing-declarations -Werror=redundant-
       | decls`, and having a test rule that verifies that every .h file
       | is included on exactly the first line of the corresponding .c
       | file, is very useful at enforcing proper headers. Non-`static`
       | declarations should be forbidden outside of headers. Violating
       | this rule is often the only thing needed to make a project hard
       | to understand. Easy-to-understand projects usually only have to
       | add a few `static`s on unintentionally-exported symbols.
       | 
       | For libraries, it is critical that a distinction be made between
       | "header files that will be installed" (which should use the
       | project name as a directory prefix!) and "header files used
       | internally". I put the latter in src/ but practice varies widely.
       | 
       | I argue that the flat layout is somewhat harmful (though at least
       | using src/ beats shoving everything in ./), because even small
       | projects eventually grow a distinction between "source files that
       | are part of the main installed project" and "source files used
       | solely for maintenance tooling".
       | 
       | Making a distinction between "code that needs to be cross-
       | compiled" and "code that needs to be native" is useful even if
       | you're not planning to do cross-compilation.
        
         | JonChesterfield wrote:
         | An alternative to the ritual include foo.h as the first line of
         | foo.c strategy is to compile each header individually, `clang
         | -x c -Wall foo.h -c -o /dev/null` or similar. That catches
         | roughly the same developer intent.
         | 
         | Similar to the cross compiled / native distinction, it's
         | probably a good idea to put platform specific things behind an
         | interface up front. Link time is a good point to swap out win32
         | for linux or similar and implementing the second target is much
         | easier if you drew the line up front. The platform layer might
         | also be one worth writing a mock for as part of testing.
        
       ___________________________________________________________________
       (page generated 2024-03-08 23:01 UTC)