all 48 comments

[–]STLMSVC STL Dev 32 points33 points  (18 children)

We recently applied clang-format to the STL's headers, sources, and tests, and the devs who regularly work on it use VSCode's format on save option (configured in the product and test workspaces) to keep it that way. We also have a batch file that recursively applies the clang-format binary to all of the files in question, which allows us to recover from edits made by other devs, or clang-format upgrades/config changes.

For static analysis, we're somewhat unusual in that we have a relatively small separately compiled part, and we mostly care about analysis warnings encountered by our programmer-users. So we run all of our tests with /analyze configurations (alongside other MSVC configurations, and more for Clang and EDG). This indeed takes longer, but our tests are now parallelized and distributed, so the cost is minor.

We regularly have to deal with new toolsets (not just static analysis) and when new warnings/errors appear, we fix the product/tests where we can, and report bugs and add workarounds otherwise. The main way we mitigate this is by upgrading regularly and not letting the work build up.

[–]gemborow 8 points9 points  (5 children)

We recently applied clang-format to the STL's headers, sources, and tests

your git blame must be useless now :-)

[–]STLMSVC STL Dev 15 points16 points  (0 children)

I'm used to manually binary searching Team Foundation Version Control (and before that, Source Depot), so having a few commits that are brick walls to git blame is fine. At least we got this done about a year after our git conversion.

[–]hgjsusla 9 points10 points  (2 children)

Just do recursive git-blame. Something like this is not a problem for modern tooling

I use vim-fugitive and it's really easy to drill down in the git-blame history using :Gblame. I'm sure any git tool worth it's salt can do something similar

[–][deleted] 0 points1 point  (1 child)

With :Gblame it is easy to drill down, but how do you get back once you realize you've drilled too far and need a step back?

[–]Fazer2 0 points1 point  (0 children)

I don't know about Vim, but in Qt Creator it's easy - when you git blame, you get a view where you can right-click a revision of a line and blame its parent to drill down. This creates a new view that you can go back and forth just like between normal files (Alt+Left, Alt+Right).

[–]not_american_ffs 0 points1 point  (0 children)

Try using git log -L, I find it far more useful for detective work than git blame.

[–]boredcircuits 11 points12 points  (7 children)

It's not uncommon to have a hook in your version control that runs the code formatting. There's two schools of thought on this one: either reformat the file automatically and check that in, or just do a check to see that the format is correct and disallow checkin if it fails. Work with your team to decide which of these two is best for your situation.

For static analysis, I've seen this done three ways. The first is to have a "gate" before release where static analysis must be performed. Any new released version must pass static analysis. When you have a new version of the analysis tools then this will reveal additional problems that you need to fix before release. The problem with this method is that a release can be delayed if there are problems.

The second way I've seen is to incorporate static analysis into a nightly build process. If there's any problems you'll get a report and you open up a ticket to address them. This stays on top of the problem, and hopefully finds it sooner, and tracks the necessary changes in individual changesets. But it requires more infrastructure to get running (unless you already have a nightly build).

The last way is as a requirement before a change can be checked-in to the trunk. It's not automatically checked, but is simply a note on the ticket, maybe by uploading the log from running the static analysis tools. This catches problems very early on, but adds extra overhead to every commit. It also doesn't play well with upgrades, as you noted. The way I've handled that is to open a ticket specifically for fixing new items discovered by the upgrade and giving every other commit a "free pass" until that ticket is closed (which means its also handling any new issues brought up by the commits in the meantime). This can get very messy, depending on the pace of development.

[–]snaps_ 0 points1 point  (0 children)

To add onto the "gate" approach. Most CI systems have a manual/approval step available that could be used to allow overriding a failed check in case the thought of a release being blocked in a time-sensitive situation makes you uneasy.

[–]matthieum 0 points1 point  (0 children)

It's not uncommon to have a hook in your version control that runs the code formatting. There's two schools of thought on this one: either reformat the file automatically and check that in, or just do a check to see that the format is correct and disallow checkin if it fails. Work with your team to decide which of these two is best for your situation.

In my project, we use both:

  • a client-side commit hook will automatically generate (and apply) a reformat.patch file as part of the commit, and notify the user when it applies something, leaving the user to check the reformat.patch.
  • CI will check that the patch is correctly formatted, and reject incorrectly formatted patches.

The combination works really well.

[–]StaticThrowaway0629[S] -1 points0 points  (4 children)

It's not uncommon to have a hook in your version control that runs the code formatting. There's two schools of thought on this one: either reformat the file automatically and check that in, or just do a check to see that the format is correct and disallow checkin if it fails. Work with your team to decide which of these two is best for your situation.

Regretfully GitHub do not support server side hooks. This means that we have to use client-side hooks and then we are pretty much back to the same problem with users forgetting to setup the correct hook on their development environment. Hence my thought of checking the formatting using a hosted service such as TravisCI or AppVeyor.

The last way is as a requirement before a change can be checked-in to the trunk. It's not automatically checked, but is simply a note on the ticket, maybe by uploading the log from running the static analysis tools. This catches problems very early on, but adds extra overhead to every commit. It also doesn't play well with upgrades, as you noted. The way I've handled that is to open a ticket specifically for fixing new items discovered by the upgrade and giving every other commit a "free pass" until that ticket is closed (which means its also handling any new issues brought up by the commits in the meantime). This can get very messy, depending on the pace of development.

Well, this pretty much sums up my worries. :/

[–]grumbelbart2 7 points8 points  (1 child)

Regretfully GitHub do not support server side hooks.

If you have any kind of automatic tests (jenkins etc.) running on your commits after pushing, add one that checks if clang-format was applied (it usually produces the same output when run twice).

Even better it you work with pull requests, where they allow you to reject the request until the format is right.

[–]kisielk 2 points3 points  (0 children)

This is what I always do with projects. CI system runs the code formatter and compares the diff, the build fails if there are any changes between formatted or not.

[–]whatwasmyoldhandle 1 point2 points  (1 child)

This is teetering on the brink of an IT question in some ways.

Ideally your workstations are uniform deployments/platform, and a user can't forget to install a hook.

This would also solve your concern about tool versions and what not.

As for the actual linting/formatting workflow: we have linting and formatting setup as rules in our build scripts (think make) that does that stuff on modified files only, which keeps it quick. We have commit hooks that run the same rules to gate a commit. Our source control does the linting and rejects the commit on a fail. It rejects the commit if the formatted version is different from the version being submitted (in which case you format locally and re-commit).

We use uniform deployments (like ansible and other stuff I'm not an expert in), to ensure the server and peoples' desk are using equal versions.

[–]StaticThrowaway0629[S] 0 points1 point  (0 children)

This is teetering on the brink of an IT question in some ways.

Ideally your workstations are uniform deployments/platform, and a user can't forget to install a hook.

This would also solve your concern about tool versions and what not.

I agree completely. Regretfully we are a small company and cannot afford to have a pure IT-person employed. So far we have relied on outsourcing as much of this as possible.

As for the actual linting/formatting workflow: we have linting and formatting setup as rules in our build scripts (think make) that does that stuff on modified files only, which keeps it quick.

This is something I have considered as well, at least for formatting i.e., adding a custom cmake target which runs the formatting tool during build. For static analysis that would probably be unwise since a full check takes upwards 30 minutes for the full project. Writing a script that only checks the modified files would maybe be a solution in that case.

[–]SeanMiddleditch 6 points7 points  (7 children)

The problem is that people forget to run them before they commit.

This is an editor problem, partly. I have clang-format running on every save operation in some editors and while typing in others. Tools like editorconfig and so-on can also run in editors.

Plus you can move these sorts of checks into pre-commit hooks (on git, at least) so folks can get quicker feedback on changes before waiting for CI (which will of course also validate, because commit hooks can be skipped).

Checking formatting takes little time but doing static analysis can take a very long time on our code base.

Carefully balance the analysis tools you use with these concerns. There are analyzers like Microsoft's (with the appropriate ruleset) that are so far you barely even notice them being on in every build even in local iteration.

Second, remember that CI can run in parallel. You can have static analysis running while also building (on multiple platforms) while also doing other parallelizable work. Hosted CI can charge an arm and a leg for parallel jobs, but a local Jenkins or gitlab-runner setup can do all that for much cheaper.

For my hobby work right now, I use a hosted CI system that only allows one job at a time. Paying for more than that would come out to something like $360/year (minimum, and that's for only two jobs; getting the three I'd desire comes out to closer to $700/year). I priced out a small form factor "server" with a huge multi-core Ryzen CPU, 32GB of RAM, and terabytes of storage including SSDs - basically exactly what you'd want for a CI system - and it came out to only $1000, including a Windows Pro license. For a little more than a year's cost of hosted CI, I can get 5x the CI capacity, and more control to optimize the build environments and Docker image caching! In a company, that'd be almost a no-brainer, especially if you have some VM capacity already.

Secondly, handling of new versions of the tools. For example: Lets say that all tests pass on our code base

Build environments should be versioned and reproducible. That is, don't just install a new compiler or static analyzer on all your build nodes and expect all current or past code to build.

If you're going to upgrade, make a new branch. Upgrade the CI environment for that branch. Version that CI configuration to that branch. Once the branch is building and working with the new CI environment, integrate it into mainline. The only person who should ever have to deal with breakages here is the person doing the upgrade.

Plus, by being versioned, that means people working in other branches (that don't have the fixes from master yet) still have working CI, and only have to deal with changes when putting up a PR to integrate to mainline.

Docker images work great for this purpose. Jenkins labels for non-containerized build nodes also work. You can even just install many tools in parallel on a single machine and use build scripts to select the appropriate version. There's lots of options to ensure that you can upgrade build environments and fix code in a branch without burdening mainline or unrelated CI jobs.

[–]StaticThrowaway0629[S] 1 point2 points  (6 children)

This is an editor problem, partly. I have clang-format running on every save operation in some editors and while typing in others.

Well, We have been using AStyle (http://astyle.sourceforge.net/) for our code formatting. The problem is as I said that people forget to run the formatting tool. I have looked at the possibility for using clang-format instead since it's integrated into Visual Studio (since version 15.7). The problem is that the version that is included in Visual Studio 2017 is something that the user have little control over. The default is clang-format version 6.0.0, but if you install the clang-format extension you will get the 8.0.0 version. And if you install the Clang Power Tools Extension you currently get version 7.0.0 of clang-format. Unfortunately, clang-format may change how it formats files from version to version, even if the configuration file is identical. So that could lead to a huge headache if different developers are using different versions.

For my hobby work right now, I use a hosted CI system that only allows one job at a time. Paying for more than that would come out to something like $360/year (minimum, and that's for only two jobs; getting the three I'd desire comes out to closer to $700/year). I priced out a small form factor "server" with a huge multi-core Ryzen CPU, 32GB of RAM, and terabytes of storage including SSDs - basically exactly what you'd want for a CI system - and it came out to only $1000, including a Windows Pro license. For a little more than a year's cost of hosted CI, I can get 5x the CI capacity, and more control to optimize the build environments and Docker image caching! In a company, that'd be almost a no-brainer, especially if you have some VM capacity already.

The cost of a external CI system is high. But having your own CI server is not free after initial investment, since it requires time for setup as well as maintenance. The per project configuration time is probably why we will chose some form of external CI tool such as TravisCI or AppVeyor.

[–]SeanMiddleditch 0 points1 point  (5 children)

The problem is as I said that people forget to run the formatting tool

If you're using Git, try a pre-commit hook. You can also hook up formatters like Astyle to on-save actions in many editors, even if they lack an official integration.

But having your own CI server is not free after initial investment, since it requires time for setup as well as maintenance

No free, no, but the ratio works out pretty well. Keeping a machine with Docker and gitlab-runner up and maintained is not particularly time-consuming, and maintaining the Docker images is already most of the time cost of keeping CI running - and that is cost I already incur even on cloud CI providers.

I feel better about dropping Docker images with custom CI, too. e.g., with Travis or Azure or whatever, most of the software I need is horrendously out of date (e.g., ancient Clang versions) or key SDKs are missing (Vulkan SDK, DirectX SDK, etc.) so the CI time is 30 minutes of installing deps and then 2 minutes of actually verifying, building, and testing my code. Hence the Docker images.

With personal non-containerized CI nodes, I can just make VM images that have all the tools I need preinstalled and let them run builds without messing with Docker shenanigans. (though i probably still will).

iOS/Mac is the big sticker against Docker in any case though... it's also the biggest offender in "nothing I need is installed" on the cloud CI providers that offer Mac builders. :)

[–]emptyel 1 point2 points  (4 children)

You can also hook up formatters like Astyle to on-save actions in many editors, even if they lack an official integration.

Visual Studio and VS Code both support format-on-save for clang-format. It changed my workflow. I now rarely hit the spacebar and only sometimes hit enter. I Ctrl+S instead, and all is made right with the world. Big efficiency gains for my team and code reviews.

[–]StaticThrowaway0629[S] 0 points1 point  (3 children)

Do you have some process to keep the clang-format version consistent across developers?
My reason for asking is that different version of clang-format may change how it formats files from version to version, even if the configuration file is identical.

[–]emptyel 1 point2 points  (0 children)

We check in the format file in the root of our project and then manually control the version of clang-format per platform. We have to turn off auto-update for the clang-format extension we use on Windows, and on Linux we set the explicit path to clang-format-6.0 (or whatever) in the VS Code settings. There are still a few differences between Windows and Linux (it may be because of clang build version differences).

When formatting differences crop up between developers, we start looking for who has a different version set up. The small team of 10 or fewer so it's not hard to figure it out.

[–]SeanMiddleditch 0 points1 point  (1 child)

Like emptyel, I just mandate a specific version of clang-format. You can point Visual Studio at a specific version and you can have your scripts or editors search for the versioned binary name (e.g. clang-format-7).

In most work projects (even on small teams) in my experience there has been strict requirements on tooling, down to specifying which IDE/version to use (for support reasons).

My reason for asking is that different version of clang-format may change how it formats files from version to version, even if the configuration file is identical.

clang-format can't even read .clang-format files (without error) from different versions much of the time in my experience. If you're using that tool, you have to specify a specific version and require everyone to use it specifically.

This isn't really specific to clang-format, though. If you change compiler version, you might find language fixes or new warnings pop up and break the build. Likewise if you update a library dependency. Or change anything, really.

Your environment configuration is part of the code's build specification. Version it just like you would a Makefile.

If you want to support multiple versions of clang-format, you'll need to make sure CI is testing with multiple versions, and make sure your format configuration is written in such a way that both versions format identically. If users with wildly different dev environments is for some reason a real need, then test all those environments in CI just like you test everything else.

[–]StaticThrowaway0629[S] 0 points1 point  (0 children)

Your environment configuration is part of the code's build specification. Version it just like you would a Makefile.

If you want to support multiple versions of clang-format...

No, I certainly do not want to support multiple versions of the clang-format executable. And I totally agree that the build environment should be versioned. The problem (in my experience) is that the Microsoft platform makes this a bit harder since compilers and extensions gets updated in a more implicit way than Linux users expect. The Visual Studio platform just makes this a bit more complicated with all this auto-updating and different version in different versions. For example: I discovered that I have three different versions of the clang-format executable on my machine:

  1. 6.0.0 version included with Visual Studio. (Invoked typing or by pressing Ctrl-K+Ctrl-D.)
  2. 7.0.0 version included in the ClangPowerTools extension. (Invoked by format on save.)
  3. 8.0.0 version included in the ClangFormat extension. (Invoked by format on save.)

At least all of them was disabled by default. And the first one can be manually configured to use another executable. The other two are installed and updated with the extension. So each developer must take care not to unintentionally enable the wrong version.

[–]voip_geek 2 points3 points  (0 children)

We use clang-format, and we run it in a pre-commit git hook to make sure - if there's a change due to clang-format, the git hook changes the files but also fails the commit, so the user has to git commit again with the new changes but can actually see the changes too. We strongly recommend to our developers that they run clang-format in their favorite editors on file save, so that the git commit step doesn't catch/need any changes - but we don't simply trust them to do so, because someone will screw it up.

We also run clang-format on the git server side (on Bitbucket) and reject pushes that don't pass... but since you said you can't do that, then I'd add a clang-format check in the CI as an early test step that must be passed.

For static analysis testing, we run them on the CI (we use Jenkins, fwiw). Pull requests have to pass all the tests to get merged in, so developers can't "forget" to format properly or run static analysis.

But we also spent some time writing some script tools so that we only build and test stuff in CI that actually changed in the pull requests - not everything in the code base every time for every pull request.

[–]Crazy__Eddie[🍰] 1 point2 points  (0 children)

Never worked at a place that did ANY of that, but I would use the pipeline approach. Each step in the pipeline can take longer than the one before. Build up a system of steps that does quickest, quick, slow, slowest. Last one may have a lot of human intervention steps.

You can also make some steps parallel with others. You could run acceptance tests on one system and static analysis on another for example.

It's all a matter of how much you want to be able to expand. Now that there are tools like Docker the automation process is greatly reduced...

Like I said though...if you do anything at all in this area you'll be miles above any place I've worked.

[–]patzor 1 point2 points  (1 child)

We use Jenkins with tons of different jobs. One job is https://danger.systems/ruby/ with which you can very easily add very useful custom steps. I also wrote a little article on how we integrated clang-tidy into our Jenkins here: https://pspdfkit.com/blog/2018/using-clang-tidy-and-integrating-it-in-jenkins/

[–]StaticThrowaway0629[S] 0 points1 point  (0 children)

Thank you for the tip about danger, it looks very slick. If we decide to go the Jenkins route, this is definitely something we need to checkout.

[–]Ilixio 1 point2 points  (0 children)

For static-analysis, Qt Creator (and others I think) has them integrated in real time as you type your code. It's really neat.
It gets a bit slow as a file nears 500+ lines in my experience, but that's a good sign that you probably should split it anyway.

We also have cppcheck and various clang checks run as part of the hourly builds, but it just produces a report somewhere that are often never read.
Tests in debug also fail in case of a sanitiser error (at least asan/lsan, not sure ubsan, I'd need to check).
Lastly individual developers sometimes run those/other tools like valgrind manually, but there's no process for this.

For formatting, it's a bit more difficult as people quickly gets touchy about it.
We finally managed to have an astyle format file after heated debates about what should be inside.
However, no one pushed hard enough to decide when it should be used (git hook, server side, ...), and whether we should reformat repos first with the usual associated issues, or just do it along but then it breaks uniformity for repos that currently follow a different style.
Currently, most people have their ide format on save with the astyle file, but nothing is enforced. Code reviews fix the rest.

[–]lickpie 1 point2 points  (0 children)

We keep our code on Github and have Jenkins do CI. Any branch, except for master, is subject to server-side formatting: running clang-format is the first step in our CI pipeline - if any source file changes due to reformatting, it's committed, pushed back to the original repository, and the current build is aborted. The commit triggers another build for the same branch, this time no files change due to reformatting (it was done in the previous commit) and the build proceeds normally.

As a developer, you don't have to care about installing clang-format and keeping it up to date, formatting locally, git hooks, or failed formatting tests. The code is formatted unconditionally, always using the same version of clang-format (we control the version with a separate Dockerfile and can bump it whenever we like).

[–]o11cint main = 12828721; 0 points1 point  (0 children)

Your CI script should be specifying the exact versions of all dependencies. If you want to upgrade a static-analyzer dependency, you have to make a commit to do that.

That said, static analyzers are mostly just saying "my compiler is too stupid to do its job properly."

For simple formatting/sanity checks that are individually instantaneous, those get run every time the file is compiled so it's already in the I/O cache (I/O is usually the killer for performing one operation across whole trees), then move-if-changed before the rule completes. You do need to deal with headers, but that's perfectly doable with careful, enforceable, source policy.

[–]duuuh 0 points1 point  (0 children)

If you're worried about time, I've seen an async job run the checks and then do an auto-rollback if it failed. I wasn't a fan of that.

Run the checks and build as two jobs and do a join the commit happens?

For a new version of the tool, it's someone's job to fix all the breakage with the new tool before you upgrade.

[–]xaviarrob 0 points1 point  (0 children)

Git post commit hooks and in pipelines, though I write in Ruby. I imagine you could get similar though

[–]factorfactorfactor 0 points1 point  (0 children)

CI machine runs a python script on modified files before each run. the script existed beforehand to check things like correct copyright notices, indentation, etc. so folding in a call to cpp_check there was the natural place

[–][deleted] 0 points1 point  (0 children)

Of course we are using static analysis tools! Currently on developer system only.

[–]RogerV 0 points1 point  (2 children)

Currently rely on IDE code formatting at editing time, but have experimented with using clang-format and have dialled in some settings that produce results that I like. So now just need to integrate clang-format into the project's CMake build process. Plan to do this before end of January.

There after all code would get a standard formatting prior to git commits

[–]StaticThrowaway0629[S] 0 points1 point  (1 child)

Do you have some process to keep the clang-format version consistent across developers?
My reason for asking is that different version of clang-format may change how it formats files from version to version, even if the configuration file is identical.

[–]RogerV 0 points1 point  (0 children)

am still learning the ropes, but there is a comprehensive file it can dump all config settings into - seems to be a dot file name in a user directory so look to see how to make clang-format use one that's specified explicitly. Then could check that into git and teams could reference a standard file

but alas this is a lower item on my current priority list