Was aiming to get started on a small idea of a psv program to help parse markdown tables into json output. Studied how jq a popular json data handling tool structure it build system and found it is using autotools.

To better use this means I first need to create a minimum autotool setup. To which i tend to prefer a scriptable approach to make the process a bit clearer to me (Since it ends up becoming a bit like a runbook).


One thing to add is I quite like how jq approaches version strings in that they captured from the git tag the version name and use that to inform the autotool system the current version of this program. If you are interested, you would have a look at this script https://github.com/jqlang/jq/blob/master/scripts/version and then add this line m4_define([program_version], m4_esyscmd_s([scripts/version]))) to replace in configure.ac this line m4_define([program_version], [0.1]).

Here's the script, just copy it to a file and chmod +x <script name> in any linux based computer and it will be ready to use. Just note that I also added a .gitignore as I typically use git version control.

Also this script contains a trivial unit test example (to ensure ease of grokkability of the build system) so you may want to check out these other unit test frameworks for it and integrate it in manually.

  • Unity Test - This is a unit test that I have often used for embedded programming and is relatively easy to integrate in most build systems including Make and CMake.
  • µnit - Single source/header file and is trivial to integrate into build systems.
  • RK Test A demake of google test targeting C99 by Warwolt for those who don't like the idea of writing C++ just for C code. Its a ~1k single header lib. Warwolt comment for reference
  • Google Test (Recommended by Warwolt as it is a straight forward library and is apparently easy to setup and write, but test codes are written in C++)

By the way I highly recommend watching Introduction to the Autotools by David A. Wheeler which would explain how autotools works at a basic level. You are also recommended to read GNU Autotools: a tutorial (Embedded Linux Conference 2016) IA Mirror as Thomas Petazzoni's diagrams is pretty good.

A copy of this script is also located here https://gist.github.com/mofosyne/21fca0eb74fbc788983c1068af92bc83 for those who prefer github gist.

#!/bin/bash

# INITIALIZE MINIMAL AUTOTOOLS BASED C PROGRAM (Brian Khuu 2024)
# https://briankhuu.com/blog/2024/04/11/initialise-minimal-autotools-script/

set -euo pipefail

# Function to display usage
show_usage() {
  echo "Usage: $0 [PACKAGE_NAME]"
  exit 1
}

# Parse command-line arguments
if [ "$#" -eq 0 ]; then
  read -p "Enter project name: " PACKAGE_NAME
elif [ "$#" -eq 1 ]; then
  PACKAGE_NAME="$1"
else
  show_usage
fi

# Create project directory if it doesn't exist
if [ ! -d "$PACKAGE_NAME" ]; then
  mkdir "$PACKAGE_NAME"
fi

cd "$PACKAGE_NAME" || exit 1

echo "== GENERATING FILES =="

# Create README.md
cat <<EOF >README.md
# $PACKAGE_NAME

A minimal Autotools-based C project.

Quickstart Tip: Just run \`./bootstrap.sh && ./build.sh && ./test.sh\` to build and test.
Your executable will be located at \`./$PACKAGE_NAME\`.
Use \`./clean.sh\ && ./bootstrap.sh\` to reset to a clean slate.
EOF

# Create M4 folder
mkdir -p m4
cat <<EOF >m4/readme.md
# Autotools M4 Macro Directory
This directory contains custom or third-party macros used by Autotools.
## Purpose
These macros are included in the Autotools build process via the \`AC_CONFIG_MACRO_DIRS([m4])\` directive in the \`configure.ac\` file.
It is also accessible within  \`configure.ac\` as well as m4 does a double pass as well.
## Usage
Ensure to include \`-I m4\` in the \`ACLOCAL_AMFLAGS\` variable within the \`Makefile.am\` file to instruct \`aclocal\` to search for macros in this directory.
EOF

# Create configure.ac
cat <<EOF >configure.ac
m4_define([program_version], [0.1])
AC_INIT([$PACKAGE_NAME], [program_version])
AC_PREREQ([2.65]) # Minimum version number for autoconf build system
AC_CONFIG_SRCDIR([src/main.c]) # Ensure the main source file exists
AC_CONFIG_HEADERS([src/config.h:config_h.in]) # Generate config header
AC_CONFIG_AUX_DIR([build-aux]) # Put autotools auxiliary files into a seperate dir to reduce clutter
AC_CONFIG_MACRO_DIRS([m4]) # Put autotools M4 macros files into a seperate dir to reduce clutter
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) # Initialize Automake with options
AC_CONFIG_FILES([Makefile]) # Specify Makefile generation for main directory and src directory
AC_PROG_CC # Find and set up C compiler
AC_OUTPUT # Generate output files
EOF

# Create Makefile.am
cat <<EOF >Makefile.am
bin_PROGRAMS = $PACKAGE_NAME
${PACKAGE_NAME}_SOURCES = src/main.c src/hello.h

check_PROGRAMS = unit_test
unit_test_SOURCES = tests/unit_test.c src/hello.h

# ACLOCAL_AMFLAGS specifies additional flags for aclocal.
# -I m4: Include the m4 directory for additional macros.
# --install: Automatically install missing macros into the system-wide directory.
ACLOCAL_AMFLAGS = -I m4 --install
EOF

# Create source files
mkdir -p src
cat <<'EOF' >src/main.c
#include <stdio.h>
#include "config.h"
#include "hello.h"
int main() {
  printf("%s! (version: %s)\n", HELLO_MESSAGE, PACKAGE_VERSION);
  return 0;
}
EOF

# Create header file src/hello.h
cat <<'EOF' >src/hello.h
#ifndef HELLO_H
#define HELLO_H
#define HELLO_MESSAGE "Hello, world"
#endif /* HELLO_H */
EOF

# Create test source files
mkdir -p tests
cat <<'EOF' >tests/unit_test.c
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "hello.h"
int main() {
  if(strcmp(HELLO_MESSAGE, "Hello, world") != 0) {
    printf("Unit test failed: Message does not match expected output.\n");
    return 1;
  }
  printf("All Unit Test Passed\n");
  return 0;
}
EOF

# Create bootstrap.sh
cat <<EOF >bootstrap.sh
#!/bin/bash
# Setup Autotools Enviroment
set -euo pipefail
echo "== BOOTSTRAPPING =="

# Run autoreconf to generate configure script and Makefile.in files
echo "Running autoreconf..."
[ -e configure ] || autoreconf -vim

# Configure and build the project
echo "Configuring and building the project..."
[ -e Makefile ]  || ( ./configure && make )
EOF
chmod +x bootstrap.sh

# Create build.sh
cat <<EOF >build.sh
#!/bin/bash
# Run the main program
set -euo pipefail
echo "== BUILD AND RUN $PACKAGE_NAME =="
make
./$PACKAGE_NAME
if [ $? -eq 0 ]; then
    echo "build completed."
else
    echo "build failed."
    exit 1
fi
EOF
chmod +x build.sh

# Create test.sh
cat <<EOF >test.sh
#!/bin/bash
# Run the unit test program
set -euo pipefail
echo "== BUILD AND RUN UNIT TEST =="
make check
./unit_test
if [ $? -eq 0 ]; then
    echo "Unit tests passed successfully."
else
    echo "Unit tests failed."
    exit 1
fi
EOF
chmod +x test.sh

# Create clean.sh
cat <<EOF >clean.sh
#!/bin/bash
# Clean out everything as much as possible
set -euo pipefail
echo "== CLEANING =="
make clean
make maintainer-clean
# Extra cleaning is possible if inside a git repo
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
  echo "Using .gitignore in an active git repo to clean out autotool files and directories"
  git clean -Xdf
fi
EOF
chmod +x clean.sh

# Create .gitignore
cat <<EOF >.gitignore
# http://www.gnu.org/software/automake

Makefile.in
/ar-lib
/mdate-sh
/py-compile
/test-driver
/ylwrap
.deps/
.dirstamp

# http://www.gnu.org/software/autoconf

autom4te.cache
/autoscan.log
/autoscan-*.log
/aclocal.m4
/compile
/config.cache
/config.guess
/config.h.in
/config.log
/config.status
/config.sub
/configure
/configure.scan
/depcomp
/install-sh
/missing
/stamp-h1

# https://www.gnu.org/software/libtool/

/ltmain.sh

# http://www.gnu.org/software/texinfo

/texinfo.tex

# http://www.gnu.org/software/m4/

m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4

# Generated Makefile
# (meta build system like autotools,
# can automatically generate from config.status script
# (which is called by configure script))
Makefile

# Generated from configure.ac by autoheader
config_h.in
src/config.h
src/stamp-h1

# Generated by autoreconf -i because of AC_CONFIG_AUX_DIR()
build-aux/
EOF

# Check overall compilation flow
./bootstrap.sh && ./build.sh && ./test.sh && ./clean.sh

echo "== COMPLETE =="
echo "Project '$PACKAGE_NAME' initialized. Run './bootstrap.sh' to set up the Autotools project."

Autotools Cleanup Note

After running the provided script to set up a project using Autotools, some generated files such as aclocal.m4, configure, Makefile.in, and others may remain in the project directory even after cleaning with make clean and make maintainer-clean. These files are essential for the Autotools build system and are typically preserved as they may contain custom configurations or manual modifications made by developers.

I've added a check that would remove it if it's a git based project via the .gitignore file, however you can delete it from the script if you think you would be hand editing it as well.

  • aclocal.m4 typically contains macros and definitions used during the configuration process. Developers often customize this file to include project-specific macros or to modify existing ones to fit their requirements.
  • configure is a script generated by autoconf that is used to configure the build environment. It's often customized by developers to include specific configuration options or settings relevant to their project. Regenerating configure from scratch can be time-consuming and may discard customizations, so it's typically preserved during clean-up operations.
  • Makefile.in files are templates generated by automake from Makefile.am files. While Makefile.in files are usually generated automatically, developers may make manual adjustments or additions to them to fine-tune the build process or include custom rules. Preserving these files ensures that these manual modifications are not lost.
  • Additionally, files such as compile, depcomp, install-sh, and missing are auxiliary scripts and templates used by Autotools during the build process. Although they are generated by Autotools, developers may need to customize or modify them to accommodate specific project requirements. Therefore, they are also preserved after cleaning to ensure that any manual modifications are retained.

By preserving these files, developers retain control over their project's configuration and build process, allowing for customization and flexibility.