jam

Jam Object rule and directories


I suspect that the manual is actually saying what I'm doing wrong, but I can't really see a solution; the problem occurs when the .c file and the .o file to be build are not in the same directory, and the .c file has an automatic dependency on a .h file which has to be generated on the fly. The problem can be probably be solved by manually setting dependencies between the .c and .h file, but I would like to avoid that.

I have the following directory structure:

weird/
    Jamfile
    b.c
    src/
        a.c
        c.c

The src/a.c file is like this:

#include "src/a.h"

int main(int argc, char *argv[])
{
    return 0;
}

The b.c file is like this:

#include "src/b.h"

int main(int argc, char *argv[])
{
    return 0;
}

The src/c.c file is like this:

#include "c.h"

int main(int argc, char *argv[])
{
    return 0;
}

The Jamfile is:

rule CreateHeader
{
    Clean clean : $(1) ;
}

actions CreateHeader
{
    echo "int x = 10;" > $(1)
}

Object a.o : src/a.c ;
Object b.o : b.c ;
Object c.o : src/c.c ;

CreateHeader src/a.h ;
CreateHeader src/b.h ;
CreateHeader src/c.h ;

The following command correctly creates b.o and src/b.h:

jam b.o

The following command creates src/a.h, but then GCC fails to create a.o; the reason is quite obviously that the #include in a.c mentions src/a.h while in fact should simply refer to a.h:

jam a.o

The following command fails completely, and does not even create c.h; the reason is probably that when Jam analyzes c.c it generates a dependency on c.h instead of src/c.h, and in the Jamfile there are no rules for generating c.h:

jam c.o

This command compiles properly if I explicitly ask to generate src/c.h before asking for c.o:

jam src/c.h
jam c.o

In my opinion the jam src/c.h should not be necessary. What's wrong here? Check the Jam manual for more information, particularly under the section Header File Scanning.

Added after I accepted the answer

I kept experimenting a little bit with the constructs suggested by the author of the accepted answer, and I'll post here the results. In this setting you can type:

jam app

And the application will be linked under bin/app. Unfortunately I had to use a UNIX path when setting LOCATE_TARGET, and my understanding is that this is not exactly a good practice.

Directory Structure:

project/
    Jamfile
    src/
        main.c
    gen/
    bin/
        obj/

File Jamfile:

SubDir TOP ;

rule CreateHeader
{
    MakeLocate $(1) : $(LOCATE_SOURCE) ;
    Clean clean : $(1) ;
}

actions CreateHeader
{
    BUILD_DATE=`date`
    echo "char build_date[] = \"$BUILD_DATE\";" > $(1)
}

SEARCH_SOURCE = src ;
LOCATE_TARGET = bin/obj ;
SubDirHdrs gen ;
Object main.o : main.c ;

LOCATE_TARGET = bin ;
MainFromObjects app : main.o ;

LOCATE_SOURCE = gen ;
CreateHeader info.h ;

File src/main.c

src/main.c
#include <stdio.h>
#include "info.h"

int main(int argc, char *argv[])
{
    printf("Program built with Jam on %s.\n", build_date);

    return 0;
}

Solution

  • Changing all #include directives to omit the path (i.e. '#include "a.h' etc.) and changing the Jamfile to the following will solve your issues:

    SubDir TOP ;
    
    SEARCH_SOURCE += [ FDirName $(SUBDIR) src ] ;
    LOCATE_SOURCE = [ FDirName $(SUBDIR) src ] ;
    
    rule CreateHeader
    {
        MakeLocate $(1) : $(LOCATE_SOURCE) ;
        Clean clean : $(1) ;
    }
    
    actions CreateHeader
    {
        echo "int x = 10;" > $(1)
    }
    
    Object a.o : a.c ;
    Object b.o : b.c ;
    Object c.o : c.c ;
    
    CreateHeader a.h ;
    CreateHeader b.h ;
    CreateHeader c.h ;
    

    Here're the details:

    So the general thrust of these changes is to omit the "src/" part from target names used in the Jamfile. It is generally recommended to omit directory components in Jamfiles (and use grist for disambiguation instead). It is important to note that the way Jam works targets with the names "src/a.h" and "a.h" are different targets, even if the former is considered to be located in "." and the latter in "./src" (e.g. by means of the on-target SEARCH or LOCATE variables). With the files you gave Jam's header scanning results in the following include dependencies (those are target names):

    src/a.c : src/a.h
    b.c     : src/b.h
    src/c.c : c.h
    

    This makes obvious why jamming "c.o" fails: The target "c.h" is unknown, since the target name of the header you declare to be generated is "src/c.h". Hence jam ignores the include dependency. The reason for the failing "jam a.o" is the one you suspected.

    The change I suggest requires adjusting the #include directives in the source files, which may not be desirable/possible in your actual use case. The situation would still be salvageable. E.g. you can change the Jamfile as suggested, but extend the CreateHeader rule:

    rule CreateHeader
    {
        MakeLocate $(1) : $(LOCATE_SOURCE) ;
        Clean clean : $(1) ;
        Depends src/$(1) : $(1) ;
        NotFile src/$(1) ;
    }
    

    This is obviously a bit of a hack. It defines the targets "src/a.h" and friends as pseudo targets, each depending on the corresponding actual target ("a.h" etc.). This way Jam's header scanning will result in a known target regardless of whether it has the "src/" prefix or not.

    The less hacky solution is to explicitly declare the include relations, though:

    Includes a.c : a.h ;
    Includes b.c : b.h ;