Sunday 26 August 2012

List makefile targets

This is a program that lists the targets in a makefile (using GNU make).

Hopefully it's fairly clear. There is some complexity for performance (all filtering is done by sed).

A version of this script is now included in bash completion from debian although it might not be released as of this writing. That version doesn't output the full target name, it stops at directory separators to make the tab completion feature more comfortable but this version simply outputs all targets in full, once each.

It doesn't output implicit targets nor any targets listed in the .INTERMEDIATE rule. It *does* output generated targets in complex makefiles.

If your makefile includes others that are non-existent targets themselves then they will be generated before this script finishes running. You can avoid that if your makefile is written in an appropriate manner.

#!/bin/bash

SCRIPT='
  /^# Make data base/,/^# Files/d             # skip until files section
  /^# Not a target/,+1          d             # following target isnt
  /^\.PHONY:/                   d             # special target
  /^\.SUFFIXES:/                d             # special target
  /^\.DEFAULT:/                 d             # special target
  /^\.PRECIOUS:/                d             # special target
  /^\.INTERMEDIATE:/            d             # special target
  /^\.SECONDARY:/               d             # special target
  /^\.SECONDEXPANSION:/         d             # special target
  /^\.DELETE_ON_ERROR:/         d             # special target
  /^\.IGNORE:/                  d             # special target
  /^\.LOW_RESOLUTION_TIME:/     d             # special target
  /^\.SILENT:/                  d             # special target
  /^\.EXPORT_ALL_VARIABLES:/    d             # special target
  /^\.NOTPARALLEL:/             d             # special target
  /^\.ONESHELL:/                d             # special target
  /^\.POSIX:/                   d             # special target
  /^\.NOEXPORT:/                d             # special target
  /^\.MAKE:/                    d             # special target

# The stuff above here describes lines that are not
#  explicit targets or not targets other than special ones
# The stuff below here decides whether an explicit target
#  should be output.

  /^[^#\t:=%]+:([^=]|$)/ {                    # found target block
    h                                         # hold target
    d                                         # delete line
  }
  /^# File is an intermediate prerequisite/ { # nope
    s/^.*$//;x                                # unhold target
    d                                         # delete line
  }
  /^([^#]|$)/ {                               # end of target block
    s/^.*$//;x                                # unhold target
    s/:.*$//p                                 # write current target
    d                                         # hide any bugs
  }
'

make -npq .DEFAULT 2>/dev/null | sed -n -r "$SCRIPT" \
  | sort | uniq


This Makefile demonstrates the relative completeness of the above script

As you add .cc files to a subfolder called "test", eg "test/a.cc" targets are dynamically defined for "test/a.success", "test/a.result", "test/a.bin".

A target "test/a.o" is also defined but that is incidental for automatic dependencies and for setting custom variables for files in the test folder. test/a.o is dynamically listed as an intermediate target so the above script does not list it.

.PHONY: all test/%.success
.PRECIOUS: test/%.bin test/%.d
.DEFAULT:

TOP := $(shell pwd)
CXXFLAGS = -std=gnu++0x -ggdb -O0

TESTS:=$(patsubst test/%.cc,%,$(wildcard test/*.cc))
TESTSUCCESSES:=$(patsubst %,test/%.success,$(TESTS))

all: $(TESTSUCCESSES)

$(foreach SUCCESS,$(TESTSUCCESSES),$(eval $(SUCCESS):))
$(foreach RESULT,$(patsubst %.success,%.result,$(TESTSUCCESSES)),$(eval $(RESULT):))
$(foreach BIN,$(patsubst %.success,%.bin,$(TESTSUCCESSES)),$(eval $(BIN):))

.INTERMEDIATE: $(patsubst %.success,%.o,$(TESTSUCCESSES))

test/%.o: CXXFLAGS += -I test -I.

test/%.o: test/%.cc
 g++ -c $(CXXFLAGS) -o $@ $<
 @g++ -MM $(CXXFLAGS) $< | sed -e '1s/:/: \\\n/' | sed -e '1s:^:test/:' > test/$*.d

test/%.bin: test/%.o
 g++ $(CXXFLAGS) -o $@ $^

test/%.result: test/%.bin
 $< > $@

test/%.success: test/%.result
 @test `grep -c ^fail $<` -eq 0 || { cat $< 1>&2; false; }
 
clean:
 rm -f *.o test/*.o test/*.s test/*.bin test/*.result test.result test/*.d *.d test/*.pretest

include $(patsubst %.o,%.d,$(wildcard test/*.o))