make, while being one of the most widely used build tools, has a very operational semantics: it looks at time stamps and, depending on which file is newer, runs commands. Those commands do whatever they do, creating whatever side effect on the file system.
Modern build tools understand a build as computing a function from sources to final artifacts, composed of individual steps ("actions") with well-defined inputs (including command line and environment) and outputs, assumed to behave like a mathematical function.
Therefore, an intersting way to compare these approaches to builds is to translate Makefiles into a functional build description. Of course, this requires some heuristics (and we need to allow the user to override the default heuristics), but with rbemake I have a prototype (using justbuild (packages) as underlying modern build tool) that works well enough to demonstrate the difference on simple examples.
Let's consider the following Makefile.
PREFIX ?= /usr
BINDIR = ${PREFIX}/bin
CC ?= cc
.SUFFIXES: .c .o
.c.o:
${CC} ${CFLAGS} -c -o $@ $<
all: programs/foo programs/bar
programs/%:
mkdir -p programs
${CC} ${CFLAGS} -o $@ $^
programs/foo: foo.o common.o
programs/bar: bar.o common.o
SRCS = $(wildcard *.c)
${SRCS:.c=.o}: $(wildcard *.h)
install: all
mkdir -p ${DESTDIR}${BINDIR}
install programs/foo programs/bar ${DESTDIR}${BINDIR}
clean:
rm -rf programs *.o
$ make programs/foo cc -c -o foo.o foo.c cc -c -o common.o common.c mkdir -p programs cc -o programs/foo foo.o common.o $ CFLAGS=-g make programs/bar cc -g -c -o bar.o bar.c mkdir -p programs cc -g -o programs/bar bar.o common.o $
$ rbemake -v programs/foo
Wrote targets file '/home/aehlig/.cache/rbemake/targets/deb9b4991c2a6beef38597d83d0936f4e12d686b_TARGETS'
DEBUG: Roots for "", total of 0:
INFO: Requested target is [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
INFO: Analysed target [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
DEBUG: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching
INFO: Discovered 3 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}].
DEBUG (action:8e1dbdad0ec83f2b6cfe3f43a707d5241e29a281c0e636dc61c6c52c5b602ad0):
["sh","-c","cc -c -o foo.o foo.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
DEBUG (action:2b1f944f1ea88dcea8d83469e273ec3c9fdce5c27bf8b98e380e3fa99a459c1a):
["sh","-c","cc -c -o common.o common.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
DEBUG (action:c44165ead15daee77ed9565b7617c9e0da3e5854a9b0259d1b9171a7ed4a091f):
["sh","-c","set -e\nmkdir -p programs\ncc -o programs/foo foo.o common.o"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
INFO: Processed 3 actions, 0 cache hits.
INFO: Artifacts built, logical paths are:
programs/foo [65fb9c02920a2598c36f17ff08ce3d2c5943a13a:15960:x]
DEBUG: Finished backing up artifacts of export targets
$ CFLAGS=-g rbemake -v programs/bar
Wrote targets file '/home/aehlig/.cache/rbemake/targets/8c403264e58391af6f6a132af80256f1a764db65_TARGETS'
DEBUG: Roots for "", total of 0:
INFO: Requested target is [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
INFO: Analysed target [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
DEBUG: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching
INFO: Discovered 3 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}].
DEBUG (action:5ef43fdd171d5104134d3c6e9eb833629150e5a621c13f9b83efaa4327d96cf5):
["sh","-c","cc -g -c -o bar.o bar.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
DEBUG (action:0f7d371ef94927d17f5b580cd12bb1867f50af9e012467759713f5e1a4c4f1ad):
["sh","-c","cc -g -c -o common.o common.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
DEBUG (action:acd3537559be16c93dca78071bbcde99058c938f93a46b6a5eac837aa50253f9):
["sh","-c","set -e\nmkdir -p programs\ncc -g -o programs/bar bar.o common.o"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
INFO: Processed 3 actions, 0 cache hits.
INFO: Artifacts built, logical paths are:
programs/bar [e9e77e8b9b1a6b2dc68208ba07b28aba38d51f5f:18112:x]
DEBUG: Finished backing up artifacts of export targets
$
$ make clean # recover to get a proper debug build of programs/bar rm -rf programs *.o $ CFLAGS=-g make programs/bar cc -g -c -o bar.o bar.c cc -g -c -o common.o common.c mkdir -p programs cc -g -o programs/bar bar.o common.o $ vi bar.c # fix bug $ DESTDIR=/tmp/dest make install cc -c -o foo.o foo.c mkdir -p programs cc -o programs/foo foo.o common.o cc -c -o bar.o bar.c mkdir -p programs cc -o programs/bar bar.o common.o mkdir -p /tmp/dest/usr/bin install programs/foo programs/bar /tmp/dest/usr/bin $
$ vi bar.c # fix bug
$ rbemake -v -o /tmp/dest install
Wrote targets file '/home/aehlig/.cache/rbemake/targets/9bac42fa189ff2772bc36e56a9c661ca527397c0_TARGETS'
DEBUG: Roots for "", total of 0:
INFO: Requested target is [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
INFO: Analysed target [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
DEBUG: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching
INFO: Discovered 6 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}].
DEBUG (action:2b1f944f1ea88dcea8d83469e273ec3c9fdce5c27bf8b98e380e3fa99a459c1a):
["sh","-c","cc -c -o common.o common.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
DEBUG (action:8e1dbdad0ec83f2b6cfe3f43a707d5241e29a281c0e636dc61c6c52c5b602ad0):
["sh","-c","cc -c -o foo.o foo.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
DEBUG (action:c44165ead15daee77ed9565b7617c9e0da3e5854a9b0259d1b9171a7ed4a091f):
["sh","-c","set -e\nmkdir -p programs\ncc -o programs/foo foo.o common.o"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
DEBUG (action:6286ea860ca97c5cbf5e8a6bcbf627860493f4bb93dddd926f2b71dd7e82bbdd):
["sh","-c","cc -c -o bar.o bar.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
DEBUG (action:c355956b55fea9d8b6e7e9c89a33bca72f6762609ed0ef433da0154ee030e9bc):
["sh","-c","set -e\nmkdir -p programs\ncc -o programs/bar bar.o common.o"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
DEBUG (action:57fc70f0f4f4fe69619e04c206c889ce75c5ae9ebbe34e8a6f869ef0cc4dbf69):
["sh","-c","export DESTDIR=\"`pwd`/destdir\"\n\ncd work\nset -e\nmkdir -p ${DESTDIR}/usr/bin\ninstall programs/foo programs/bar ${DESTDIR}/usr/bin"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
INFO: Processed 6 actions, 3 cache hits.
INFO: Artifacts can be found in:
/tmp/dest/. [c66e522f85c2209856eecaadb6081f4417a74f11:30:t]
DEBUG: Finished backing up artifacts of export targets
$
But it is not only that make forgets to redo build steps. Being based on timestamps, it can also do too many. Let's consider a cleanly built state and we add a comment to common.c. As both programs transitively depend on that file, both a relinked.
$ make clean rm -rf programs *.o $ DESTDIR=/tmp/dest make install cc -c -o foo.o foo.c cc -c -o common.o common.c mkdir -p programs cc -o programs/foo foo.o common.o cc -c -o bar.o bar.c mkdir -p programs cc -o programs/bar bar.o common.o mkdir -p /tmp/dest/usr/bin install programs/foo programs/bar /tmp/dest/usr/bin $ echo '/* comment! */' >> common.c $ DESTDIR=/tmp/dest make install cc -c -o common.o common.c mkdir -p programs cc -o programs/foo foo.o common.o mkdir -p programs cc -o programs/bar bar.o common.o mkdir -p /tmp/dest/usr/bin install programs/foo programs/bar /tmp/dest/usr/bin $
$ echo '/* comment! */' >> common.c
$ rbemake -v install
Wrote targets file '/home/aehlig/.cache/rbemake/targets/9bac42fa189ff2772bc36e56a9c661ca527397c0_TARGETS'
DEBUG: Roots for "", total of 0:
INFO: Requested target is [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
INFO: Analysed target [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
DEBUG: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching
INFO: Discovered 6 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}].
DEBUG (action:8e1dbdad0ec83f2b6cfe3f43a707d5241e29a281c0e636dc61c6c52c5b602ad0):
["sh","-c","cc -c -o foo.o foo.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
DEBUG (action:6286ea860ca97c5cbf5e8a6bcbf627860493f4bb93dddd926f2b71dd7e82bbdd):
["sh","-c","cc -c -o bar.o bar.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
DEBUG (action:2b1f944f1ea88dcea8d83469e273ec3c9fdce5c27bf8b98e380e3fa99a459c1a):
["sh","-c","cc -c -o common.o common.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
DEBUG (action:c355956b55fea9d8b6e7e9c89a33bca72f6762609ed0ef433da0154ee030e9bc):
["sh","-c","set -e\nmkdir -p programs\ncc -o programs/bar bar.o common.o"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
DEBUG (action:c44165ead15daee77ed9565b7617c9e0da3e5854a9b0259d1b9171a7ed4a091f):
["sh","-c","set -e\nmkdir -p programs\ncc -o programs/foo foo.o common.o"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
DEBUG (action:57fc70f0f4f4fe69619e04c206c889ce75c5ae9ebbe34e8a6f869ef0cc4dbf69):
["sh","-c","export DESTDIR=\"`pwd`/destdir\"\n\ncd work\nset -e\nmkdir -p ${DESTDIR}/usr/bin\ninstall programs/foo programs/bar ${DESTDIR}/usr/bin"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"} (cached)
INFO: Processed 6 actions, 5 cache hits.
INFO: Artifacts built, logical paths are:
. [c66e522f85c2209856eecaadb6081f4417a74f11:30:t]
DEBUG: Finished backing up artifacts of export targets
$
The functional approach is also good in discovering incorrect build descriptions. Say, we forgot the dependency declaration on the header files. make will happily run the commands and, as the header files are there, the build will succeed; however, changes of the header file will not trigger a rebuild. In the functional world, on the other hand, even with the very weak isolation of local builds, a build error will be discovered.
$ vi Makefile # pretend we forgot the dependency declaration of the header files
$ cat Makefile
PREFIX ?= /usr
BINDIR = ${PREFIX}/bin
CC ?= cc
.SUFFIXES: .c .o
.c.o:
${CC} ${CFLAGS} -c -o $@ $<
all: programs/foo programs/bar
programs/%:
mkdir -p programs
${CC} ${CFLAGS} -o $@ $^
programs/foo: foo.o common.o
programs/bar: bar.o common.o
SRCS = $(wildcard *.c)
# ${SRCS:.c=.o}: $(wildcard *.h)
install: all
mkdir -p ${DESTDIR}${BINDIR}
install programs/foo programs/bar ${DESTDIR}${BINDIR}
clean:
rm -rf programs *.o
$ rbemake -v
Wrote targets file '/home/aehlig/.cache/rbemake/targets/aee3ce08a2d3407e0be2cdbdc648723cbad9c249_TARGETS'
DEBUG: Roots for "", total of 0:
INFO: Requested target is [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
INFO: Analysed target [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]
DEBUG: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching
INFO: Discovered 5 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}].
ERROR (LocalExecution): expected known type at foo.o
ERROR (LocalExecution): could not collect output file or symlink foo.o
ERROR (LocalExecution): expected known type at bar.o
ERROR (LocalExecution): could not collect output file or symlink bar.o
INFO (action:e7cb03e431d4d00378ea98f9c79136f9fbae577f10aec1bc8afa85a821d1cd83):
Stderr of command ["sh","-c","cc -c -o foo.o foo.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
foo.c:1:10: fatal error: 'common.h' file not found
1 | #include "common.h"
| ^~~~~~~~~~
1 error generated.
ERROR (action:e7cb03e431d4d00378ea98f9c79136f9fbae577f10aec1bc8afa85a821d1cd83): action returned non-zero exit code 1
ERROR (action:e7cb03e431d4d00378ea98f9c79136f9fbae577f10aec1bc8afa85a821d1cd83):
Failed to execute command ["sh","-c","cc -c -o foo.o foo.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
requested by
- [["@","","","foo.o"],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]#0
INFO (action:0e0736d17193d6d17abbda5c4a26bb192f03f5fd679c87c81991c51714ee8a54):
Stderr of command ["sh","-c","cc -c -o bar.o bar.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
bar.c:1:10: fatal error: 'common.h' file not found
1 | #include "common.h"
| ^~~~~~~~~~
1 error generated.
ERROR (action:0e0736d17193d6d17abbda5c4a26bb192f03f5fd679c87c81991c51714ee8a54): action returned non-zero exit code 1
ERROR (action:0e0736d17193d6d17abbda5c4a26bb192f03f5fd679c87c81991c51714ee8a54):
Failed to execute command ["sh","-c","cc -c -o bar.o bar.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
requested by
- [["@","","","bar.o"],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]#0
ERROR (LocalExecution): expected known type at common.o
ERROR (LocalExecution): could not collect output file or symlink common.o
INFO (action:667cd307615a4d668f9c0b11e276aa4b1b6fa161f24c85b958788651dd52be70):
Stderr of command ["sh","-c","cc -c -o common.o common.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
common.c:1:10: fatal error: 'common.h' file not found
1 | #include "common.h"
| ^~~~~~~~~~
1 error generated.
ERROR (action:667cd307615a4d668f9c0b11e276aa4b1b6fa161f24c85b958788651dd52be70): action returned non-zero exit code 1
ERROR (action:667cd307615a4d668f9c0b11e276aa4b1b6fa161f24c85b958788651dd52be70):
Failed to execute command ["sh","-c","cc -c -o common.o common.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin"}
requested by
- [["@","","","common.o"],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin"}}]#0
ERROR: Build failed.
$
Inputs can also go through worflow targets. Consider the following test example. Here, the file .done_data indicates that the generated header files (in the directory data) declaring data structures are up to date. In this case, the inputs to build actions are trees.
$ cat Makefile
all: main
CC ?= cc
HEADERS = $(wildcard *.h)
SRCS = $(wildcard *.c)
%.o: %.c ${HEADERS} .done_data
${CC} ${CFLAGS} -I . -c -o $@ $<
.done_data: gen-hdrs.py data.json
./gen-hdrs.py data.json data
touch $@
main: ${SRCS:.c=.o}
${CC} -lm -o $@ $^
clean:
rm -rf data *.o main .done_*
$ rbemake -v
Wrote targets file '/home/aehlig/.cache/rbemake/targets/7dd27296783312950491b8e31e7fe7e8e80e94a9_TARGETS'
DEBUG: Roots for "", total of 0:
INFO: Requested target is [["@","","",""],{"MAKE_ENV":{"PATH":"/home/aehlig/.nix-profile/bin:/home/aehlig/Projects/ISILMAR/unlock/bin:/home/aehlig/bin:/run/wrappers/bin:/nix/profile/bin:/home/aehlig/.local/state/nix/profile/bin:/etc/profiles/per-user/aehlig/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"}}]
INFO: Analysed target [["@","","",""],{"MAKE_ENV":{"PATH":*}}]
DEBUG: Export targets found: 0 cached, 0 uncached, 0 not eligible for caching
INFO: Discovered 5 actions, 3 tree overlays, 6 trees, 0 blobs
INFO: Building [["@","","",""],{"MAKE_ENV":{"PATH":*}}].
DEBUG (action:c84340ff7f6f659ff9e0c2ad2dc6b5d870680e877d99e291b385a5c174ca9a95):
["sh","-c","set -e\n./gen-hdrs.py data.json data\ntouch .done_data"] in environment {"PATH":"/home/aehlig/.nix-profile/bin:/home/aehlig/Projects/ISILMAR/unlock/bin:/home/aehlig/bin:/run/wrappers/bin:/nix/profile/bin:/home/aehlig/.local/state/nix/profile/bin:/etc/profiles/per-user/aehlig/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"}
DEBUG (action:6e4a95f2b98b5473625f86337efb52b5b812c9b25844490d6aaad081de2de96e):
Requested tree-overlay action is 6e4a95f2b98b5473625f86337efb52b5b812c9b25844490d6aaad081de2de96e
Trees to overlay:
- 0ef495ee038be22889b401bae76a9b7490b63146:34:t
- 158a7e28e40287b3704521633483feeaa759355e:38:t
- 22f5ccd65374636ce5ba07eee83f80b708931e19:43:t
- 3a4cbc10eebffde33717b934eeb47416bc0defd5:145:t
DEBUG (action:b989404a9a8e099ce27887cabf0dd80dd4689cc983e7f11164cbd10624e8a049):
Requested tree-overlay action is b989404a9a8e099ce27887cabf0dd80dd4689cc983e7f11164cbd10624e8a049
Trees to overlay:
- 1a1e51443969612cea07a8bbe7761f9af2efc2aa:43:t
- 158a7e28e40287b3704521633483feeaa759355e:38:t
- 22f5ccd65374636ce5ba07eee83f80b708931e19:43:t
- 3a4cbc10eebffde33717b934eeb47416bc0defd5:145:t
DEBUG (action:cc6b891f85b4ce21543864e63bd7c2c39a05cfca4508e071569d40e9f3403351):
Requested tree-overlay action is cc6b891f85b4ce21543864e63bd7c2c39a05cfca4508e071569d40e9f3403351
Trees to overlay:
- faefce200b19bcc9daff9b23a87e8ad7e864c643:38:t
- 158a7e28e40287b3704521633483feeaa759355e:38:t
- 22f5ccd65374636ce5ba07eee83f80b708931e19:43:t
- 3a4cbc10eebffde33717b934eeb47416bc0defd5:145:t
DEBUG (action:2e5d10bfdd44505b4d9b6b5e5114f985abd435de5ee5c42fc97ecb7e617b2f8f):
["sh","-c","cc -I . -c -o circumference.o circumference.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin:/home/aehlig/Projects/ISILMAR/unlock/bin:/home/aehlig/bin:/run/wrappers/bin:/nix/profile/bin:/home/aehlig/.local/state/nix/profile/bin:/etc/profiles/per-user/aehlig/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"}
DEBUG (action:ddadda55d7b9dbaaf8f40db25ab151699c2c0f46217bb7afa496ff2203e57583):
["sh","-c","cc -I . -c -o main.o main.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin:/home/aehlig/Projects/ISILMAR/unlock/bin:/home/aehlig/bin:/run/wrappers/bin:/nix/profile/bin:/home/aehlig/.local/state/nix/profile/bin:/etc/profiles/per-user/aehlig/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"}
DEBUG (action:24601fda0db4d288990168aa75bda520d7bf6cb89e0143a029c3c792dfd35afd):
["sh","-c","cc -I . -c -o distance.o distance.c"] in environment {"PATH":"/home/aehlig/.nix-profile/bin:/home/aehlig/Projects/ISILMAR/unlock/bin:/home/aehlig/bin:/run/wrappers/bin:/nix/profile/bin:/home/aehlig/.local/state/nix/profile/bin:/etc/profiles/per-user/aehlig/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"}
DEBUG (action:e3a3f319eaca1eb2d38ad1e7cf58857cc8e2b977f430f29984d9ca6dfe0e5293):
["sh","-c","cc -lm -o main main.o distance.o circumference.o"] in environment {"PATH":"/home/aehlig/.nix-profile/bin:/home/aehlig/Projects/ISILMAR/unlock/bin:/home/aehlig/bin:/run/wrappers/bin:/nix/profile/bin:/home/aehlig/.local/state/nix/profile/bin:/etc/profiles/per-user/aehlig/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"}
INFO: Processed 5 actions, 0 cache hits.
INFO: Artifacts built, logical paths are:
main [9f124f43ef5c54d8144dcde5cbe64f4789522c6a:16136:x]
DEBUG: Finished backing up artifacts of export targets
$ rbemake --install-cas 3a4cbc10eebffde33717b934eeb47416bc0defd5:145:t
{
".done_data": "[e69de29bb2d1d6434b8b29ae775ad8c2e48c5391::f]",
"data": "[7593d86f190908c11ba4a7d355dea28935e16176::t]",
"data.json": "[59873f2882fdd7c74ad753ae6631fc87c6476160::f]",
"gen-hdrs.py": "[1c7bddaec94d4fc372c67a581288ff236cac1366::x]"
}
$ rbemake --install-cas 3a4cbc10eebffde33717b934eeb47416bc0defd5:145:t -P data
{
"point.h": "[f8184114da6c541240bdb5494330c4eaa20651db::f]",
"triangle.h": "[e8d01fd0ea81cd8688a74d027f242e24d32fe2b9::f]"
}
$