Orphan Targets

tmake maintains a cache of all targets that have previously been built. This allows tmake to "know" when rules change such that a previous target is no longer a target, and thus the old file can be removed. By removing previous targets, tmake ensures that these files do not interfere with the new build.

Consider the build.spec:

$ cat build.spec
Executable prog main.c a.c b.c
$ tmake 
    Cc           a.o
    Cc           b.o
    Cc           main.o
    Link         prog
Built 4 of 4 target(s) in 0.25 seconds

Now assume that b.c is no longer required:

$ cat build.spec
Executable prog main.c a.c
$ tmake 
    Clean        removing 1 orphan target(s)
    Link         prog
Built 1 of 3 target(s) in 0.12 seconds

Note that the orphan target b.o is deleted as it an no longer be generated by any rule. And now change the name of the program.

$ cat build.spec
Executable newprog main.c a.c
$ tmake 
    Clean        removing 1 orphan target(s)
    Link         newprog
Built 1 of 3 target(s) in 0.09 seconds

Now prog is deleted and newprog is created in it's place.

rulebase.default orphan handling

By default, the tmake core will simply delete orphan targets. However it is possible that an accidental rule change could cause files to be deleted unintentionally. For example, if a generated file is replaced with a source file and the source is misidentified as an orphan.

To address this situation rulebase.default overrides delete-orphan-files to instead move orphans to .trash/. The deleted orphan can be manually reinstated from .trash/, until tmake clean is run, at which point all files from .trash/ are deleted.

From rulebase.default:

# This callback is invoked to delete orphans
# This version replaces the builtin version.
# Instead of deleting files immediately, orphans are moved into
# the .trash directory. This directory is only removed via the
# 'clean', 'distclean' or 'clean-orphans' targets.
proc delete-orphan-files {args} {
        # Can't create .trash if it doesn't exist and we are root
        file-mkdir -rooterr .trash
        foreach file $args {
                if {[file-type $file] in {file link}} {
                        set trashfile .trash/[string map {/ _} $file]
                        # Can't simple use file rename -force in case the files are hard links
                        file delete $trashfile
                        file rename $file $trashfile
                }
        }
}

An example of the importance of removing orphans

Consider the situation where a header file is being generated and used.

$ cat build.spec
Generate test.h {} {} {
	writefile $target {static const char TEST[] = __FILE__;}
	
}
PublishIncludes test.h

Executable --test tester test.c
$ cat test.c
#include <stdio.h>
#include <test.h>

int main(void)
{
	printf("TEST is %s\n", TEST);
	return 0;
}
$ tmake test
    Generate     test.h
    Publish      publish/include/test.h
    Cc           test.o
    Link         tester
    Test         objdir/tester
TEST is objdir/publish/include/test.h
Built 4 of 4 target(s) in 0.41 seconds

Now imagine that we decide that test.h is no longer to be generated but will be a source file.

$ echo "static const char TEST[] = __FILE__;" >test.h
$ cat build.spec
Executable --test tester test.c
$ tmake -v -v test
    Clean        removing 2 orphan target(s)
    Cc           test.o
gcc -Iobjdir/publish/include -I. -Iobjdir   -c test.c -o objdir/test.o
    Link         tester
gcc   -o objdir/tester objdir/test.o
    Test         objdir/tester
objdir/tester >@stdout
TEST is ./test.h
Built 2 of 2 target(s) in 0.39 seconds

Note that because -Iobjdir/publish/include comes before -I., if test.h had not been removed, the compiler would have used the old, generated test.h instead -- which would would have led to unintended consequences. By removing files build outputs that are no longer targets, tmake ensures that this situation cannot occur.

This situation is particularly likely to occur when using ifconfig as targets are modified dynamically without any changes to the build description. If instead we have:

$ cat build.spec
ifconfig GENTEST {
	Generate test.h {} {} {
		writefile $target {static const char TEST[] = __FILE__;}
		
	}
	PublishIncludes test.h
}

Executable --test tester test.c

Then we can dynamically choose where test.h comes from and tmake will ensure that a leftover generated file will not compromise the integrity of the build.

$ tmake GENTEST=1 test
    Generate     test.h
    Publish      publish/include/test.h
    Cc           test.o
    Link         tester
    Test         objdir/tester
TEST is ./test.h
Built 4 of 4 target(s) in 0.16 seconds
$ tmake test
    Clean        removing 2 orphan target(s)
    Cc           test.o
    Link         tester
    Test         objdir/tester
TEST is ./test.h
Built 2 of 2 target(s) in 0.15 seconds

The results of the search are