[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)