Clone in a terminal:
#! /usr/bin/env python

import os, sys, imp
from waflib import Context, Options, Configure, Utils, Logs, TaskGen, Task
import waflib.Tools.c

"""
Compile main.c and dependent object files into a single target (program/shlib/stlib or just object files)

- no build directory and no script files
- just a c4che directory for the configuration files
- configure, clean or build

Uses the task signatures and the dependency calculation results to avoid
rescanning/rebuilding the files all the time
"""

def options(opt):
	opt.add_option('--type', action='store', default='program', help='type: program, shlib, stlib, objects', dest='progtype')
	opt.add_option('--source', action='store', default='main.c', help='space-separated list of source files', dest='source')
	opt.add_option('--app', action='store', default='app', help='name of the binary file to create', dest='app')
	opt.load('compiler_c')

def configure(conf):
	conf.options = Options.options
	conf.load('compiler_c')

def build(bld):
	tp = Options.options.progtype
	features = 'c cprogram'
	if tp == 'shlib':
		features = 'c cshlib'
	elif tp == 'stlib':
		features = 'c cstlib'
	elif tp == 'objects':
		features = 'c'

	source = Options.options.source
	app = Options.options.app
	bld(features=features, source=source, target=app)

def recurse_rep(x, y):
	f = getattr(Context.g_module, x.cmd or x.fun, Utils.nada)
	return f(x)

def start(cwd, version, wafdir):
	# this is the entry point of our small build system
	# no script file here
	Logs.init_log()
	Context.waf_dir = wafdir
	Context.out_dir = Context.top_dir = Context.run_dir = cwd
	Context.g_module = imp.new_module('wscript')
	Context.g_module.root_path = cwd
	Context.Context.recurse = recurse_rep

	Context.g_module.configure = configure
	Context.g_module.build = build
	Context.g_module.options = options
	Context.g_module.top = Context.g_module.out = '.'

	Options.OptionsContext().execute()

	do_config = 'configure' in sys.argv
	try:
		os.stat(cwd + os.sep + 'c4che')
	except:
		do_config = True
	if do_config:
		Context.create_context('configure').execute()

	if 'clean' in sys.argv:
		Context.create_context('clean').execute()

	if 'build' in sys.argv:
		Context.create_context('build').execute()


class c2(waflib.Tools.c.c):
	# Make a subclass of the default c task, and bind the .c extension to it

	def runnable_status(self):
		ret = super(waflib.Tools.c.c, self).runnable_status()
		self.more_tasks = []

		# use a cache to avoid creating the same tasks
		# for example, truc.cpp might be compiled twice
		try:
			shared = self.generator.bld.shared_tasks
		except AttributeError:
			shared = self.generator.bld.shared_tasks = {}

		if ret != Task.ASK_LATER:
			for x in self.generator.bld.node_deps[self.uid()]:
				node = x.parent.get_src().find_resource(x.name.replace('.h', '.c'))
				if node:
					try:
						tsk = shared[node]
					except:
						tsk = shared[node] = self.generator.c_hook(node)

						self.more_tasks.append(tsk)

					# add the node created to the link task outputs
					try:
						link = self.generator.link_task
					except AttributeError:
						pass
					else:
						if not tsk.outputs[0] in link.inputs:
							link.inputs.append(tsk.outputs[0])
							link.set_run_after(tsk)

							# any change in the order of the input nodes may cause a recompilation
							link.inputs.sort(key=lambda x: x.abspath())

			# if you want to modify some flags
			# you *must* have the task recompute the signature
			self.env.append_value('CXXFLAGS', '-O2')
			delattr(self, 'cache_sig')
			return super(waflib.Tools.c.c, self).runnable_status()

		return ret

@TaskGen.extension('.c')
def c_hook(self, node):
	# re-bind the extension to this new class
	return self.create_compiled_task('c2', node)

# modify the existing class to output the targets in the same directory as the original files
Task.update_outputs(c2)
Task.update_outputs(waflib.Tools.c.cprogram)
Task.update_outputs(waflib.Tools.c.cshlib)
Task.update_outputs(waflib.Tools.c.cstlib)

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112