Rickard's Projects

This site hosts my projects.

Projects

Recent events

2026-01-08 22:03 Rickard pushed to blog

commit 683dd4718637b477e66c05b8c3859d2a0e04b65c
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Thu Jan 8 22:03:01 2026 +0100

    Write

diff --git a/posts/2026/01/08/how-to-release-software/post.md b/posts/2026/01/08/how-to-release-software/post.md
index fe95e55..0e32823 100644
--- a/posts/2026/01/08/how-to-release-software/post.md
+++ b/posts/2026/01/08/how-to-release-software/post.md
@@ -66,7 +66,8 @@ implemented. Otherwise, what is the point of upgrading. It can only get worse.
   > We keep our latest code ready to release.
 
   > The ultimate goal of continuous integration is to make releasing a business
-  > decision, not a technical decision.
+  > decision, not a technical decision. When on-site customers are ready to
+  > release, you push a button and release.
 
   Book:
 

2026-01-08 21:58 Rickard pushed to blog

commit fbfe37399f9ed192290fba279014fa52b0d38c81
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Thu Jan 8 21:57:55 2026 +0100

    Don't commit rlworkbench

diff --git a/.gitignore b/.gitignore
index f588e35..1c54782 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 /Dockerfile.ci.files
 /html/
+/out/
diff --git a/Dockerfile.ci b/Dockerfile.ci
index 337e2cc..7f0c365 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -1,5 +1,5 @@
 FROM fedora:42
 
-RUN dnf install -y python3-markdown SDL3 SDL3_ttf
+RUN dnf install -y python3-markdown
 
-CMD ["./blog.py", "build"]
+CMD ["./build.sh"]
diff --git a/blog.py b/blog.py
index 75a1e3c..a589bb4 100755
--- a/blog.py
+++ b/blog.py
@@ -519,7 +519,7 @@ class Post:
     def render(self, body):
         if self.is_markup:
             return subprocess.check_output(
-                ["./workbench_sdl", "run", "--language", "markup"],
+                ["rlworkbench_cli", "run", "--language", "markup"],
                 input=body,
                 text=True,
             )
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..338a6f9
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env sh
+
+set -e
+
+set -x
+
+MARKUP2WEBSITE_VERSION=1.0.0
+MARKUP2WEBSITE_FOLDER_NAME=markup2website-$MARKUP2WEBSITE_VERSION
+MARKUP2WEBSITE_ARCHIVE=https://projects.rickardlindberg.me/artifacts/markup2website/$MARKUP2WEBSITE_VERSION/$MARKUP2WEBSITE_FOLDER_NAME.tgz
+
+rm -rf out
+mkdir out
+
+curl $MARKUP2WEBSITE_ARCHIVE | tar xzvv -C out
+
+PATH="out/$MARKUP2WEBSITE_FOLDER_NAME:$PATH"
+
+./blog.py build
diff --git a/workbench_sdl b/workbench_sdl
deleted file mode 100755
index 9eeb0b5..0000000
Binary files a/workbench_sdl and /dev/null differ

2026-01-08 21:51 Rickard pushed to blog

commit 5bed5f0f46158d355bd47b19d547ba7c873fa61a
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Thu Jan 8 21:51:35 2026 +0100

    Write

diff --git a/posts/2026/01/08/how-to-release-software/post.md b/posts/2026/01/08/how-to-release-software/post.md
index f12f8bf..fe95e55 100644
--- a/posts/2026/01/08/how-to-release-software/post.md
+++ b/posts/2026/01/08/how-to-release-software/post.md
@@ -61,6 +61,24 @@ implemented. Otherwise, what is the point of upgrading. It can only get worse.
 * Branch to stabalize for release? [Version Control that doesn't suck | Nuno
   Afonso](https://www.youtube.com/watch?v=Te2gZc_mOMA&t=4618s)
 
+* https://www.jamesshore.com/v2/books/aoad2/continuous_integration
+
+  > We keep our latest code ready to release.
+
+  > The ultimate goal of continuous integration is to make releasing a business
+  > decision, not a technical decision.
+
+  Book:
+
+  CI: Do it as real as possible. "If it's a desktop app, build an install
+  package." But how to release that at will later? What should the version
+  number be?
+
+  Deployng vs Releasing. (Release means no feature flag is hiding it.)
+  This talks about releasing functionality. Not the noun "release".
+
+  CD deploy to production
+
 ## TODO
 
 * Convert posts/2023/04/06/what-should-a-ci-server-do/post.md

commit d80af3f386faf9311951aeca9f87a0de1bce4c35
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Thu Jan 8 21:24:54 2026 +0100

    Draft new post how-to-release-software

diff --git a/posts/2026/01/01/newsletter-january/post.md b/posts/2026/01/01/newsletter-january/post.md
index 725bea7..d73cf21 100644
--- a/posts/2026/01/01/newsletter-january/post.md
+++ b/posts/2026/01/01/newsletter-january/post.md
@@ -72,7 +72,4 @@ month:
       are easier when looping over characters. (Nov 17)
 * Write blog post about how to parse offside in markup language
 * How to release software?
-  * Changelog?
-  * Binaries?
-  * Branch to stabalize for release? [Version Control that doesn't suck | Nuno
-    Afonso](https://www.youtube.com/watch?v=Te2gZc_mOMA&t=4618s)
+  posts/2026/01/08/how-to-release-software/post.md
diff --git a/posts/2026/01/08/how-to-release-software/post.md b/posts/2026/01/08/how-to-release-software/post.md
new file mode 100644
index 0000000..f12f8bf
--- /dev/null
+++ b/posts/2026/01/08/how-to-release-software/post.md
@@ -0,0 +1,66 @@
+---
+date: 2026-01-08 20:06:45
+title: How to Release Software?
+tags: hidden, agile
+---
+
+My thoughts on software development is highly influenced by agile ideas. When I
+think about how software evolves, I think about small incremental changes that
+are applied to a code repository. Each change goes through a continuous
+integration pipeline, and at every stage of that pipeline we gain confidence
+that the change is good through various automated tests. If the change makes it
+all the way to the end of the pipeline, we can choose to deploy that version to
+production or release it to customers.
+
+```
+           | Build | Test | Deploy Test
+version 1  |  ok   >  ok  >    fail
+version 2  |  ok   >  ok  >     ok      > Deploy Pro?
+version 3  |  ok   >  ok  >     ok      > Deploy Pro?
+```
+
+In this model, the concept of a release doesn't make much sense. Or
+alternatively, every version that passes the pipeline is a release. But what is
+a release?
+
+When I think about a release, I think about a particular version of a piece of
+software. That version is usually a set of numbers separated by dot like
+`1.2.0`. There might also be different builds of that version. There might be a
+source archive that represents the state of the repository at that time. There
+might be a windows exe. There might be an RPM for use in some Linux
+distributions.
+
+Furthermore, I expect a higher version number to be released later in time. I
+also expect bigger changes if the numbers to the left are incremented. For
+example, I expect there to be fewer changes between `1.2.0` and `1.2.1` than
+between `1.2.1` and `1.3.0`. And I might also expect to find a changelog so
+that I can see what has changed in each release.
+
+I expect the changelog to be more carefully written than just a list of commit
+messages. I expect the changelog to include only changes that are relevant for
+the users of the software. In an agile way of working, refactoring is a common
+task. That means to improve the code without changing its external behavior.
+The users of the software should never be able to observe that a refactoring
+has taken place. In case they do, for example if the refactoring introduced a
+bug, it was not a refactoring. In any case, those types of changes are not
+relevant to mention in a changelog.
+
+## Pace
+
+The agile mindset is that more frequent releases are better.
+
+However, as a consumer of a piece of software, you probably don't like spending
+all your time upgrading. You probably favour more stability. And perhaps that
+the bugs that affect you gets fixed. And that the features that you lack get
+implemented. Otherwise, what is the point of upgrading. It can only get worse.
+
+## Notes
+
+* Changelog?
+* Binaries?
+* Branch to stabalize for release? [Version Control that doesn't suck | Nuno
+  Afonso](https://www.youtube.com/watch?v=Te2gZc_mOMA&t=4618s)
+
+## TODO
+
+* Convert posts/2023/04/06/what-should-a-ci-server-do/post.md

commit e7372c082ab0c5c25b08c8d31c615b980b88f8df
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Thu Jan 8 20:46:57 2026 +0100

    Import what should a ci server do

diff --git a/posts/2023/04/06/what-should-a-ci-server-do/post.md b/posts/2023/04/06/what-should-a-ci-server-do/post.md
new file mode 100644
index 0000000..065f2d6
--- /dev/null
+++ b/posts/2023/04/06/what-should-a-ci-server-do/post.md
@@ -0,0 +1,386 @@
+---
+date: 2023-04-06 02:00:00
+title: What should a Continuous Integration (CI) server do?
+tags: agile
+alternativeUri0: https://archive.rickardlindberg.me/writing/what-should-a-ci-server-do/
+alternativeUri1: https://rickardlindberg.me/2023/04/06/what-should-a.html
+---
+
+*After drafting this article, I asked for feedback on [James'
+Discord](https://discord.com/channels/897648912851173408/897648913799077930/1076788077353246760).
+[Emily](https://coding-is-like-cooking.info/)
+[wrote](https://discord.com/channels/897648912851173408/897648913799077930/1078285384527650877)
+back and said that this sounded a lot like pre-tested integration that she had
+written about ([here](https://www.eficode.com/blog/pre-tested-integration) and
+[here](https://www.eficode.com/blog/pre-tested-integration2)) earlier. She
+describes almost the exact same workflow as I imagine with this CI server, and
+there is also a Jenkins plugin to support that workflow. I encourage you the
+check out her writing as well.*
+
+I think I have figured out what a Continuous Integration (CI) server *should*
+do. It is very simple. Yet common tools used for CI, like Jenkins, make it hard
+or near impossible.
+
+## What is CI?
+
+CI probably means different things to different people.
+
+I've tried to find the root of the practice, and a lot of my thoughts here are
+based on James Shore's descriptions in
+[AOAD2](https://www.jamesshore.com/v2/books/aoad2/continuous_integration).
+
+So with that in mind, CI to me is about two things:
+
+1. Integrate often.
+2. Promise to keep the main branch working at all times.
+
+## What does integrate mean?
+
+Integrate means to merge your changes into the main branch. This branch is
+commonly also referred to as master or trunk.
+
+## How often should you integrate?
+
+From what I've read, the consensus seems to be that you should integrate at
+least once a day. If you do it less frequently, you are not doing *continuous*
+integration.
+
+## How to keep the main branch working?
+
+Every time you integrate, you have to make sure that the main branch is
+still working. This is the second aspect of CI.
+
+How can you do that?
+
+The only way to do that, and still integrate often, is with an automatic test
+suite.
+
+When you integrate your code, you want to run the test suite to make sure that
+everything still works.
+
+**The test suite should give you confidence that when it's time to deploy to
+production, it will just work.**
+
+I'm using the term test suite here to include everything you need to gain that
+confidence, so it includes compiling, linting, static analysis, unit tests,
+deploy to test environment, smoke test... *everything*.
+
+## Attitude, not a tool
+
+James Shore writes that [Continuous Integration is an Attitude, Not a
+Tool](https://www.jamesshore.com/v2/blog/2005/continuous-integration-is-an-attitude)
+and points out that [you can do CI without a
+tool](https://www.jamesshore.com/v2/blog/2006/continuous-integration-on-a-dollar-a-day).
+
+No tool can choose to integrate your changes often. You have to change your way
+of working so that you *can* integrate more often and also do so. This requires
+practice.
+
+No tool can enforce that your main branch is always working. You
+have to have a mindset of working like that. This requires practice.
+
+However, there are some things that a tool can help with. To make it easier to
+work in this way.
+
+## CI server functionality
+
+**A CI server should merge changes to the main branch in a "safe" way.**
+
+### Basic workflow
+
+Here is pseudo code for how a CI server should integrate changes from a branch
+in a Git repo:
+
+```python
+def integrate(repo, branch):
+    with lock(repo):
+        sh("git clone {repo}")
+        sh("git merge origin/{branch}")
+        sh("<command to run test suite>")
+        sh("git push")
+```
+
+The lock step ensures that only one integration can happen at a time. If you
+have two branches that want to integrate, one has to wait for the other to be
+integrated first.
+
+The branch is then integrated by performing a `git merge`.
+
+To make sure the new main branch works, a test suite is then run. This test
+suite should be defined in the repo.
+
+If the test suite passes, a `git push` is performed to "publish" the new main
+branch.
+
+This workflow ensures that every change that is merged into the main branch
+works. Where "works" is defined as passing the test suite.
+
+That is the basic function that I think a CI server should perform. Let's look
+at some directions where this design can be evolved to make a more full fledged
+CI server.
+
+### Clean environments
+
+One thing that a dedicated CI server helps prevent is the problem that code
+works on one developer's machine, but not on another's. Perhaps it is due to a
+dependency missing on one developer's machine.
+
+With a CI server, the one true environment is the CI server's environment.
+
+Preferably, this should also be set up in the exact same way before every test
+run so that two test runs have the exact same clean environment.
+
+Clean environments make test runs more predictable and helps make integrations
+safe.
+
+Setting up a clean environment looks different in different contexts. One
+option would be to use Docker containers. In the Python world, virtual
+environments could be set up for each test run.
+
+Any function that a CI server can perform to help set up a clean environment is
+useful.
+
+### Multiple environments
+
+Another advantage of a dedicated CI server is that you can make sure that your
+code works in an environment that you don't have access to on your development
+machine.
+
+You might write Python code that should work on both Windows and Linux, but
+your laptop only runs Windows.
+
+A CI server should have functionality to run the test suite in different
+environments.
+
+### Pipeline language
+
+To take full advantage of the CI server, the "command to run the test suite"
+should be written in a "pipeline language" that the CI server understands.
+
+Consider this pseudo example:
+
+    step('compile') {
+        sh('make')
+    }
+    parallel {
+        step('test unix') {
+            environment('unix') {
+                sh('./test')
+            }
+        }
+        step('test windows') {
+            environment('windows') {
+                sh('test.exe')
+            }
+        }
+    }
+
+This script could not have been written as a Bash script for example, because
+then it could not have taken advantage of the CI server functionality to run
+commands in different environments.
+
+### Objection!?
+
+When I asked for feedback on this article, I got some objections about a CI
+server being responsible for environments and a pipeline language.
+
+One person wrote
+[this](https://discord.com/channels/897648912851173408/897648913799077930/1077685040311435314)
+and
+[this](https://discord.com/channels/897648912851173408/897648913799077930/1078738880653693060):
+
+> ... having a pipeline script that works *only* with the ci software seems
+> like a huge lockin and risk
+
+> I feel that the moment I say I can't do this locally and I need a
+> pre-configured build server, I am violating the basic principles of
+> development.
+
+I partly agree with those objections.
+
+It would be better if you could run your whole pipeline locally and have it set
+up all the clean environments for you. With virtualisation technology, this is
+becoming more and more possible.
+
+If you manage to get this setup, then the CI server only functions as a single
+integration point that everyone has to go through.
+
+I still think that a pipeline language would be useful for programming your
+pipeline. However, it could be used outside the CI server as well. That way you
+could also debug your pipeline locally without involving the CI server. If a
+pipeline step requires a specific environment that you can't get locally, that
+step could be skipped when run locally.
+
+### Communication
+
+Another aspect of continuous integration is communication.
+
+For example, when you integrate, you want to tell your team members about the
+change so that they can pull your changes and test their code against it.
+
+A CI server can help communicate. It can for example do the following:
+
+* Notify the team on a successful integration.
+* Show today's integrations in a dashboard to visualize what's
+  happening.
+* Show success rate of integrations to give an idea of how the team is doing.
+* Present clear errors when an integration fails.
+* Present a clear view of a pipeline and what steps were run.
+
+### Multiple test suites
+
+The lock step in the basic workflow ensures that only one integration can
+happen at a time.
+
+In some situations you might have a longer running test suite that you don't
+want to block further integrations.
+
+A CI server could support that something like this:
+
+```python
+def integrate(repo, branch):
+    with lock(repo):
+        sh("git clone {repo}")
+        sh("git merge origin/{branch}")
+        sh("<command to run fast test suite>")
+        sh("git push")
+    sh("<command to run slow test suite>")
+```
+
+Of course, when you do this, you risk breaking the main branch since all tests
+are not run before the change is integrated.
+
+One scenario where this could be useful is if you have a slow running test
+suite today that you can't make instantly faster. You can start using this
+pattern with the goal of making all your slow tests fast. As a rule of thumb,
+the fast test suite should not take more than 10 minutes. If it takes longer for
+an integration to complete, chances are that you start multitasking because you
+don't want to wait for it.
+
+Some tests might also be impossible to run in less than 10 minutes. In that
+case, this pattern is also good. But make sure that all basic functionality is
+tested in the fast test suite.
+
+## Common "CI" workflows and their problems
+
+When it comes to tools commonly used for CI, I primarily have experience with
+Jenkins. And the two most common patterns in Jenkins, which a believe are not
+unique to Jenkins, prevent you from doing continuous integration. Let's have a
+look.
+
+### Run pipeline after commit
+
+This pattern runs a pipeline only after you have merged your changes to the
+main branch.
+
+If the test suite fails, your main branch is broken, and everyone who pulls
+your changes will base their work on something broken.
+
+If you are serious about continuous integration, you fix this problem
+immediately. Either by reverting the change or merging a fix. It might not be
+too big a problem.
+
+If you are not serious about continuous integration, you might leave the main
+branch broken and hope that someone else fixes it.
+
+With a CI server I describe in this article, it is simply impossible to merge
+something broken. (Given that your test suite will catch the broken things.)
+
+### Run pipeline on branch, then again after merge
+
+This patterns runs a pipeline on every branch so that you know that your
+changes work before you merge them. And when you merge them, the pipeline is
+run again.
+
+This is a slight improvement over the previous pattern, but it still has a
+flaw. Consider this scenario:
+
+    0---0
+         \
+          \---A
+           \
+            \---B
+
+`A` and `B` are two branches that both have passing test suites, so they both
+go ahead and merge, resulting in this:
+
+    0---0-------A'---B'
+         \     /    /
+          \---A    /
+           \      /
+            \----B
+
+`A'` has already been tested on the branch, but `B'` has never been tested.
+That is, the combination of `A`'s and `B`'s changes has never been tested,
+until they are both merged.
+
+With a CI server I describe in this article, this problem is solved with the
+lock where multiple integrations have to wait for each other.
+
+If you use the multiple test suites pattern, you still have this problem. At
+least for functionality only covered by the slow test suite. But then it's a
+choice you make. You decide if the trade off is worth it for you or not.
+
+## Why don't tools for CI work like this?
+
+I think that tools for CI should help you do CI well. Why don't they?
+
+I have two speculations.
+
+First, if your team is committed to doing continuous integration, a broken main
+branch might not be too big a deal since everyone is committed to fixing it
+fast.
+
+Second, back in the day of using SVN (which was my fist version control
+system), branching was expensive. The default way to share changes was to push
+directly to the main branch. Having a CI tool do the actual integration was
+probably technically more difficult. However, now with Git, that is no longer
+true.
+
+Do you know why tools for CI don't work like I describe in this article? Please
+let me know.
+
+Emily
+[responded](https://discord.com/channels/897648912851173408/897648913799077930/1078571337989234780)
+the following to that question:
+
+> I think it's hard to tell at this distance, but I suspect the people building
+> the tools weren't always the same people who really understood what CI is,
+> and there was a communication gap. The tools that ended up becoming popular
+> were perhaps the easiest to adopt and had the best marketing?
+
+That sounds reasonable to me.
+
+Another person
+[responded](https://discord.com/channels/897648912851173408/897648913799077930/1077685040311435314)
+with this:
+
+> i think most [build servers] can be configured that way [proper CI]. many
+> users do not want to because they don't understand the ci process. instead
+> they regard the build server as some central platform on which development is
+> done.
+
+So people find value in build servers even though they are not designed
+explicitly for CI. That also makes sense.
+
+So perhaps the reason why we don't have better tools for CI is that people
+don't understand the value of CI or don't want to adopt it?
+
+## What about pull requests?
+
+Pull requests are a common way of working, but they don't play nicely together
+with CI.
+
+First of all, when working with pull requests, you integrate your code by
+pressing a button that will perform the merge. With a CI tool like the one I
+describe in this article, the CI tool performs the merge. With the former, no
+tool can prevent broken code on the main branch. (The best they can do is test
+the branch, then test again after merge.)
+
+Second of all, pull requests, at least blocking ones, add delay to the
+process of integrating code, making it difficult to integrate often.
+
+Pull requests are often used to review changes before they are merged. In a CI
+server that I describe in this article, there is nothing preventing you from
+having a manual review step before the CI server is allowed to merge. However,
+a manual review step adds delays and makes it difficult to integrate often.
diff --git a/posts/2023/04/06/what-should-a/post.md b/posts/2023/04/06/what-should-a/post.md
deleted file mode 100644
index e23c168..0000000
--- a/posts/2023/04/06/what-should-a/post.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-date: 2023-04-06 02:00:00
-title: What should a Continuous Integration (CI) server do?
-tags: agile
----
-
-
-This post has not yet been imported to my new blog.
-
-In the meantime, you can read it here: [http://archive.rickardlindberg.me/writing/what-should-a-ci-server-do/](http://archive.rickardlindberg.me/writing/what-should-a-ci-server-do/).

2026-01-08 21:23 Rickard pushed to rlworkbench

fatal: Invalid revision range 6af9ed786982417dfc20c0695f2465dd047c0bb1..00ec7a55018669d076cd6c9ab5c977e7300834dc

2026-01-07 22:19 Rickard pushed to blog

commit 0393a7643b0d063eaf960ea3d8236264dc609656
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Wed Jan 7 22:18:55 2026 +0100

    Add note for newsletter

diff --git a/posts/2026/01/01/newsletter-january/post.md b/posts/2026/01/01/newsletter-january/post.md
index a773094..725bea7 100644
--- a/posts/2026/01/01/newsletter-january/post.md
+++ b/posts/2026/01/01/newsletter-january/post.md
@@ -40,6 +40,8 @@ December: posts/2025/12/01/newsletter-december/post.md
 ## Blog
 
 * [](post:2026/01/06/can-gcc-warn-about-functions-that-are-not-called/post.md)
+  I just wanted to document my findings and think a little deeper about this
+  problem by writing about it.
 
 ## TODO
 

commit ccad5a1ca55d3fe92078befa7f10a2485933539b
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Tue Jan 6 21:54:32 2026 +0100

    Fix syntax

diff --git a/posts/2026/01/01/newsletter-january/post.md b/posts/2026/01/01/newsletter-january/post.md
index eda11b5..a773094 100644
--- a/posts/2026/01/01/newsletter-january/post.md
+++ b/posts/2026/01/01/newsletter-january/post.md
@@ -73,4 +73,4 @@ month:
   * Changelog?
   * Binaries?
   * Branch to stabalize for release? [Version Control that doesn't suck | Nuno
-    Afonso](https://www.youtube.com/watch?v=Te2gZc_mOMA&t=4618s
+    Afonso](https://www.youtube.com/watch?v=Te2gZc_mOMA&t=4618s)

commit 98f8e8db4db43da522cdbcebda65aa9f6e5f6843
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Tue Jan 6 21:49:57 2026 +0100

    Fix syntax

diff --git a/posts/2026/01/01/newsletter-january/post.md b/posts/2026/01/01/newsletter-january/post.md
index dc6547e..eda11b5 100644
--- a/posts/2026/01/01/newsletter-january/post.md
+++ b/posts/2026/01/01/newsletter-january/post.md
@@ -39,8 +39,7 @@ December: posts/2025/12/01/newsletter-december/post.md
 
 ## Blog
 
-* [Can gcc warn about functions that are not
-  called?](posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/post.md)
+* [](post:2026/01/06/can-gcc-warn-about-functions-that-are-not-called/post.md)
 
 ## TODO
 
@@ -75,4 +74,3 @@ month:
   * Binaries?
   * Branch to stabalize for release? [Version Control that doesn't suck | Nuno
     Afonso](https://www.youtube.com/watch?v=Te2gZc_mOMA&t=4618s
-

2026-01-06 21:47 Rickard pushed to blog

commit 96cf4f9a41d892b3adb3d8e6795880c0c11aac82
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Tue Jan 6 21:47:30 2026 +0100

    New blog post can-gcc-warn-about-functions-that-are-not-called

diff --git a/posts/2026/01/01/newsletter-january/post.md b/posts/2026/01/01/newsletter-january/post.md
index 06d7795..dc6547e 100644
--- a/posts/2026/01/01/newsletter-january/post.md
+++ b/posts/2026/01/01/newsletter-january/post.md
@@ -37,6 +37,11 @@ December: posts/2025/12/01/newsletter-december/post.md
 * Publish and document maze project?
   https://projects.rickardlindberg.me/maze/
 
+## Blog
+
+* [Can gcc warn about functions that are not
+  called?](posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/post.md)
+
 ## TODO
 
 Here are the things that I'm currently most interested in working on next
@@ -65,10 +70,9 @@ month:
     * Looping over characters is fast in C but slow in Python. Some algorithms
       are easier when looping over characters. (Nov 17)
 * Write blog post about how to parse offside in markup language
-* Blog about "Can gcc warn about functions that are not called?"
-  * If they are static
 * How to release software?
   * Changelog?
   * Binaries?
   * Branch to stabalize for release? [Version Control that doesn't suck | Nuno
     Afonso](https://www.youtube.com/watch?v=Te2gZc_mOMA&t=4618s
+
diff --git a/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/a.out b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/a.out
new file mode 100755
index 0000000..e27a8a0
Binary files /dev/null and b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/a.out differ
diff --git a/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/make.sh b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/make.sh
new file mode 100755
index 0000000..c045f8b
--- /dev/null
+++ b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/make.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+
+set -e
+
+set -x
+
+gcc -Wall -Werror test.c -ffunction-sections -Wl,--gc-sections,--print-gc-sections
diff --git a/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/post.md b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/post.md
new file mode 100644
index 0000000..d815b70
--- /dev/null
+++ b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/post.md
@@ -0,0 +1,98 @@
+---
+date: 2026-01-06 21:46:00
+title: Can GCC warn about functions that are not called?
+tags: c
+markup: markup
+---
+
+I was wondering if GCC can warn about functions that are not called.
+
+Here is a sample program where the function `bar` is not called. Can GCC detect
+that and warn about it?
+
+```c
+#include <stdio.h>
+
+void foo() {
+    printf("foo\n");
+}
+
+void bar() {
+    printf("bar\n");
+}
+
+int main() {
+    foo();
+}
+```
+
+Running GCC with all warnings enabled produces no warnings:
+
+```
+$ gcc -Wall -Werror test.c
+```
+
+Let's try changing the functions to static:
+
+```c
+#include <stdio.h>
+
+static void foo() {
+    printf("foo\n");
+}
+
+static void bar() {
+    printf("bar\n");
+}
+
+int main() {
+    foo();
+}
+```
+
+Now GCC detects the unused function:
+
+```
+$ gcc -Wall -Werror test.c
+test.c:7:13: error: ‘bar’ defined but not used [-Werror=unused-function]
+    7 | static void bar() {
+      |             ^~~
+cc1: all warnings being treated as errors
+```
+
+That makes sense. In the first example, the unused function `bar` might be
+called by some other translation unit, and only at link time can it be detected
+that it is never called. (Unless you are building a library in which case you
+can still not know.)
+
+In the Stack Overflow post [Is there a way to get warned about unused
+functions?](https://stackoverflow.com/questions/9091397/is-there-a-way-to-get-warned-about-unused-functions)
+there are some suggestions how to detect unused functions at link time. I tried
+the following options:
+
+```
+$ gcc -Wall -Werror test.c -ffunction-sections -Wl,--gc-sections,--print-gc-sections
+/usr/bin/ld: removing unused section '.rodata.cst4' in file '/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o'
+/usr/bin/ld: removing unused section '.data' in file '/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o'
+/usr/bin/ld: removing unused section '.rodata' in file '/usr/lib/gcc/x86_64-redhat-linux/14/crtbegin.o'
+/usr/bin/ld: removing unused section '.text.bar' in file '/tmp/ccO5vFLs.o'
+```
+
+As I understand it, these options modify the binary to not include unused
+code. Not exactly what I wanted. But we can see in the output above that
+`.text.bar` is removed. So it detected that it could be removed.
+
+In [rlworkbench](https://projects.rickardlindberg.me/rlworkbench/), I use a
+[jumbo build](https://austinmorlan.com/posts/unity_jumbo_build/), so I can
+probably turn all functions into static functions and be warned about all
+unused functions.
+
+However, I have for example `string.c` that I include in multiple
+binaries/jumbo builds. And the different binaries might use different subsets
+of `string.c`.  In that case, I can not make the functions static because
+binary `a` might say that functions `x` is not called, while function `x` is
+actually called from binary `b`.
+
+So the quickest solution might be to temporarily build with the link options to
+remove unused code, grep for all functions in the output, and see if they are
+never called or not.
diff --git a/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/test.c b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/test.c
new file mode 100644
index 0000000..712de98
--- /dev/null
+++ b/posts/2026/01/06/can-gcc-warn-about-functions-that-are-not-called/test.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+
+void foo() {
+    printf("foo\n");
+}
+
+void bar() {
+    printf("bar\n");
+}
+
+int main() {
+    foo();
+}

2026-01-06 20:41 Rickard pushed to markup2website

fatal: Invalid revision range b74a7c9c067ae1f0fa208ef91d4187dabad71ed2..1afaeb3b6468f39f784d352ce7009f7aa9021f44

2026-01-06 14:08 Rickard pushed to blog

commit 5a512b7f46426ef7e2aa59e3208f7eb2b8bc96f3
Author: Rickard Lindberg <rickard@rickardlindberg.me>
Date:   Tue Jan 6 14:08:42 2026 +0100

    Add notes for newsletter

diff --git a/posts/2026/01/01/newsletter-january/post.md b/posts/2026/01/01/newsletter-january/post.md
index 96fccc0..06d7795 100644
--- a/posts/2026/01/01/newsletter-january/post.md
+++ b/posts/2026/01/01/newsletter-january/post.md
@@ -20,8 +20,11 @@ December: posts/2025/12/01/newsletter-december/post.md
 
 ## projects2
 
+[projects2](https://projects.rickardlindberg.me/projects2/)
+
 * `GIT_HASH` and `GIT_MESSAGE` for use in CI scripts
 * Allow pushing Git tags
+* New clone for every Dockerfile.ci to simplify build?
 
 ## markup2website
 
@@ -32,6 +35,7 @@ December: posts/2025/12/01/newsletter-december/post.md
 ## maze
 
 * Publish and document maze project?
+  https://projects.rickardlindberg.me/maze/
 
 ## TODO
 

2026-01-06 14:05 Rickard pushed to rlworkbench

fatal: Invalid revision range 5aef393cfd57756c7b5c1e23422bc8ba87728ddc..6af9ed786982417dfc20c0695f2465dd047c0bb1

2026-01-06 13:59 Rickard pushed to hgtest

changeset:   2:96b3da072e90
tag:         tip
user:        Rickard Lindberg <rickard@rickardlindberg.me>
date:        Tue Jan 06 13:59:56 2026 +0100
summary:     Test

diff -r e46aa333f461 -r 96b3da072e90 README.md
--- a/README.md Tue Jan 06 13:38:41 2026 +0100
+++ b/README.md Tue Jan 06 13:59:56 2026 +0100
@@ -1,1 +1,2 @@
 Test
+Test