Search code examples
makefilecompilationgnu-makeadagnat

Using Gnu Make to compile ada project requiring gnatprep and gnatchop


I have been trying to convert multiple large ada compilations from a script based approach to using a gnu make 3.82 makefile and could use some veteran knowledge.

Some background:

  • GNAT 4.8.5 on Red Hat Enterprise Linux version 7.9
  • Each compilation begins with a source list of ~1000 .ada files
  • Different versions use some of the same files, there are only ~7000 unique files out of ~14k total of number of files in each source list
  • Each file needs to be prepped, chopped, then compiled - binding and linking are done elsewhere

My approach:

  • Recipe 1: For each file, prep the files into src/foo/prepped, then chop into src/foo/chopped
  • Recipe 2: Copy each chopped file that was created in recipe 1 into the modules SRC/ folder
  • Recipe 3: Compile each chopped file from recipe 2 within SRC/ folder and place in OBJ/ folder

Issues:

  • The chopping phase causes issues because the file names are changed, and in some cases more files are created. To get around this, I attempt to wildcard each src/foo/prepped folder for its contents and copy them into the SRC/ folder for compilation. Since this list is unknown until the prep/chop phase, make needs to be invoked a second time to pick up these file names.
  • During the compile phase, I blindly run each file in the SRC/ folder to compile using a double colon rule because I am unsure of the files output during the compile phase (.ali or .o)
  • If a file has already been prepped/chopped from a previous compile, it will not do double work prepping/chopping, but does not copy the file into SRC/

Questions:

  • How can I account for the files that are output with gnatchop without already knowing this in advance?
  • During the compile phase, how do I write a rule that can create either a .o or .ali file from a .adb or .ads file?
FILE:= 'input'  #read input from file
OBJECTS:=$(shell cat ${FILE}) #need to do this, no file function in gnu make 3.82
FPOBJECTS:= $(addprefix ../../src/, $(OBJECTS)) # path + source name from pwd
OBJFILE:=$(notdir $(OBJECTS)) #just get out source file name
PDIR:=$(addprefix ../../src/, $(addsuffix prepped/ , $(dir $(OBJECTS)))) #just get out directory part of objects, add prepped/ suffix and ../../src to beginning
CDIR:= $(addprefix ../../src/, $(addsuffix chopped/ , $(dir $(OBJECTS))))# same as PDIR but with chopped/ instead of prepped/
CDIR2:= $(sort $(CDIR)) # unique directories in CDIR for folder creation
PDIR2:= $(sort $(PDIR)) # unique directories in PDIR for folder creation
DIRS:= OBJ TMP SRC #directories inside lib/version*
POBJECTS:= $(basename $(join $(PDIR), $(OBJFILE))) # join prepped directory with objects
COBJECTS = $(addprefix OBJ/, $(notdir $(wildcard SRC/*))) # this is an incorrect way to generate the objects needed, but does allow me to pass each source file for compilation
GNATOPTIONS:= -c -O1 -w -fPIC -gnatE -gnatv -LSRC/ # gcc flags
CDIRwld:=$(shell echo $(addsuffix *, $(CDIR2))) #append * to each unique chopped directory, to try and get names of each chopped file
.RECIPEPREFIX = >

#compile creates the COBJECTS in the pwd and moves them where they belong
compile : $(COBJECTS)
>mv *.ali OBJ/
>mv *.o OBJ/

$(COBJECTS)::
>gcc $(GNATOPTIONS) SRC/$(@F)

copy : $(CDIRwld)
>cp $? SRC/

prep : $(POBJECTS)

$(POBJECTS):$(FPOBJECTS) | $(DIRS) $(PDIR2) $(CDIR2)
>gnatprep -cru -Dlil $(addsuffix .ada, $(subst prepped/,,$@)) $(basename $@)
>gnatchop -rw $@ $(subst prepped/,chopped/,$(dir $@))

$(DIRS):
>mkdir -p $@

$(PDIR2):
>mkdir -p $@

$(CDIR2):
>mkdir -p $@

.PHONY: clean move

clean :
>rm -f -r $(DIRS)
>rm -f -r $(PDIR2)
>rm -f -r $(CDIR2)

move :
>mv *.ali OBJ/
>mv *.o OBJ/

Solution

  • There’s a one-to-one correspondence between files that go into being prepped and files that go into being chopped.

    It’d be a good idea if you can to leave it to GNAT to know when it needs to compile the spec, and to know about dependencies - especially non-obvious ones like inlining and generic bodies.

    A very simple makefile (works with Make 3.81) might be:

    SOURCES = foo.ada bar.ada
    DEFS = defs.def
    
    CHOPPED_STAMPS = $(addsuffix .chopped, $(SOURCES))
    
    compile: $(CHOPPED_STAMPS)
        cd obj; gnatmake -c ../chopped/*
    
    %.prepped: %
        gnatprep $< prepped/ $(DEFS)
        touch $@
    
    %.chopped: %.prepped
        gnatchop -w prepped/$(basename $<) chopped/
        touch $@
    

    It has the slight disadvantage of littering the current directory with .prepped, .chopped stamp files, but they could be put into a subdirectory.

    A worse problem is that the gnatprep stage depends on the definitions file, I haven’t worked out how to introduce that dependency into the implicit rule.

    I used gnatmake to do the compilation, on the whole it’d be better to use gprbuild, if you have it, with a library project. The GPRBuild RM is a bit of a challenge, and that link is to the current version, so some of the facilities will probably be missing.