A tour of JCons
---------------

Basics
++++++

Suppose we have a program consisting of two modules ``main.c`` and
``mod1.c``, and a header file ``mod1.h``:

.. code-block:: c

    /* main.c */
    #include <stdio.h>
    #include "mod1.h"
    int main() {
        printf("%s from main\n", GREETING);
        mod1_greeting();
        return 0;
    }

.. code-block:: c

    /* mod1.c */
    #include <stdio.h>
    #include "mod1.h"
    void mod1_greeting() {
        printf("%s from mod1\n", GREETING);
    }

.. code-block:: c

    /* mod1.h */
    #define GREETING "hello"
    extern void mod1_greeting();

To build it with JCons we could create the following file:

.. code-block:: python

    # construct.py
    e = Cons()
    e.program("prog", "main.c", "mod1.c")

When JCons is invoked, the program ``prog`` will be built in the
following manner::

    $ jcons
    gcc -c main.c -o main.o
    gcc -c mod1.c -o mod1.o
    gcc -o prog main.o mod1.o
    $ jcons
    jcons: up-to-date: .
    $ ./prog
    hello from main
    hello from mod1

The second time everything is "up to date", so nothing is done. If any
of the involved files changes, JCons will detect that an re-build the
necessary files::

    $ echo "void a_change() {}" >> main.c     # change source file
    $ jcons
    gcc -c main.c -o main.o
    gcc -o prog main.o mod1.o

    $ rm main.o                               # remove object file
    $ jcons
    gcc -c main.c -o main.o

    $ echo GARBAGE > mod1.o                   # destroy content of object file
    $ jcons
    gcc -c mod1.c -o mod1.o

    $ rm prog                                 # remove program
    $ jcons
    gcc -o prog main.o mod1.o

In some of the examples above only an object file is rebuild. Since
the newly created object file has the same content as before, no
re-linking is needed. For users of Make this might seem odd, but this
is one of the features of tools like JCons. JCons tracks changes to
the **contents** of the files. The only function of timestamps is as
an indicator of change (a file *might* have changed if its timestamp has
changed). If we only change the timestamps of files, nothing will be re-built::

    $ touch main.c
    $ jcons
    jcons: up-to-date: .

    $ touch mod1.o
    $ jcons
    jcons: up-to-date: .

    $ touch prog
    $ jcons
    jcons: up-to-date: .

The module ``mod1.c`` has a header file ``mod1.h`` included by the
C file itself and by ``main.c``. JCons will detect changes to
that header file::

    $ jcons                                   # up-to-date before change
    jcons: up-to-date: .

    $ perl -i -pe 's/hello/goodbye/' mod1.h   # change header file
    $ jcons
    gcc -c main.c -o main.o
    gcc -c mod1.c -o mod1.o
    gcc -o prog main.o mod1.o
    $ ./prog
    goodbye from main
    goodbye from mod1

This works because JCons looks for ``#include`` lines in the source
files and automatically adds dependencies for the files it finds.
If we would like to build the program in a debug flavor, we need to
change the ``construct.py`` file:

.. code-block:: python

    # construct.py
    e = Cons(CFLAGS = "-DDEBUG -g", LINKFLAGS = "-g")
    e.program("prog", "main.c", "mod1.c")

::

    $ jcons
    gcc -DDEBUG -g -c main.c -o main.o
    gcc -DDEBUG -g -c mod1.c -o mod1.o
    gcc -o prog -g main.o mod1.o

As can be seen, the configuration variable ``CFLAGS`` affects how a C
program is built.  Variables like ``CFLAGS`` can also be set
temporarily on the command line::

    $ jcons CFLAGS="-O2"                     # command line override
    gcc -O2 -c main.c -o main.o
    gcc -O2 -c mod1.c -o mod1.o
    gcc -o prog -g main.o mod1.o

    $ jcons CFLAGS="-Wall"                   # another override
    gcc -Wall -c main.c -o main.o
    gcc -Wall -c mod1.c -o mod1.o
    gcc -o prog -g main.o mod1.o

    $ jcons CFLAGS="-Wall"                   # same setting again
    jcons: up-to-date: .

    $ jcons                                  # back to normal
    gcc -DDEBUG -g -c main.c -o main.o
    gcc -DDEBUG -g -c mod1.c -o mod1.o
    gcc -o prog -g main.o mod1.o

As can be seen in the examples, a change in command line triggers a
rebuild. JCons includes the command line in the dependency calculation.
JCons knows which files are "generated files" and can remove them if
asked to do that (with the ``-r`` option)::

    $ jcons -r                               # remove generated files
    Removed main.o
    Removed mod1.o
    Removed prog

It is also possible to force a full rebuild::

    $ jcons                                  # normal build
    gcc -DDEBUG -g -c main.c -o main.o
    gcc -DDEBUG -g -c mod1.c -o mod1.o
    gcc -o prog -g main.o mod1.o

    $ jcons                                  # nothing to do
    jcons: up-to-date: .

    $ jcons  --always-make                   # forced build
    gcc -DDEBUG -g -c main.c -o main.o
    gcc -DDEBUG -g -c mod1.c -o mod1.o
    gcc -o prog -g main.o mod1.o

Several output files
++++++++++++++++++++

A command can have several output files. The method
:py:meth:`Cons.command` can handle this situation:

.. code-block:: python

    e = Cons()
    e.command(["parse.tab.c", "parse.tab.h"], "parse.y", "bison -d parse.y")

Here the output argument is an array of files produced by the
command. If any of those files doesn't exit or is out-of-date the
command has to be run. Both the input- and output-argument to
:py:meth:`Cons.command` can be arrays instead of single values. JCons
understands how to deal with those cases too.

C++ programs
++++++++++++

If the some of the source files had been C++ rather than C files, the
``construct.py`` would look almost the same:

.. code-block:: python

    # construct.py
    e = Cons()
    e.program("prog", "main.cpp", "mod1.c")

.. FILE:

    /* main.cpp */
    extern "C" int status ; int main() { return status; }

.. FILE:

    /* mod1.c */
    int status  = 123;

.. COMMAND: REMOVE .jcons

When running JCons we then get::

    $ jcons
    g++ -c main.cpp -o main.o
    gcc -c mod1.c -o mod1.o
    g++ -o prog main.o mod1.o

Note that ``g++`` is used to compile the C++ file, and also used when linking.

Include files
+++++++++++++

The include path to a C/C++ compiler is typically given by ``-I``
options on the command line. For JCons to be able to calculate the
#include dependencies, the directories have to be specified via a variable ``CPPPATH``:

.. code-block:: python

    # construct.py
    e = Cons(CPPPATH = ["dir1", "dir2"])
    e.program("prog", "main.c")

When JCons is executed the values in ``CPPPATH`` are translated into
``-I `` options on the command line. Suppose the file ``main.c`` looks like:

.. code-block:: c

   /* main.c */
   #include <main.h>
   int main() { return EXIT_CODE; }

Running JCons we get::

    $ mkdir -p dir1 dir2
    $ echo "#define EXIT_CODE 2" > dir2/main.h
    $ jcons
    gcc -I dir1 -I dir2 -c main.c -o main.o
    gcc -o prog main.o

    $ echo "#define EXIT_CODE 1" > dir1/main.h
    $ jcons
    gcc -I dir1 -I dir2 -c main.c -o main.o
    gcc -o prog main.o

The second time JCons realizes that the ``main.h`` located in ``dir1`` is going to be used, and recompiles ``main.c``.


Libraries
+++++++++

TODO: write this section ....

.. _variant-builds:

Variant builds
++++++++++++++

Often a program should be built in several "flavours", e.g. a debug
and and a release version. Then the object files and executables need
to be stored in different places for each flavour. JCons makes it easy
to change where the output is placed in several ways:

1) in a separate build directory tree (by using ``BUILD_TOP``)

2) in separate sub-directories (by using ``BUILD_SUBDIR``)

3) with different filename suffixes (by using ``BUILD_SUFFIX``)

If ``BUILD_TOP`` is used, we get:

.. code-block:: python

    # construct.py
    e = Cons(BUILD_TOP = "build/release")
    e.program("prog", "main.c", "lib/mod1.c")

.. FILE:

    /* main.c */
    extern int status ; int main() { return status; }

.. FILE:

    /* lib/mod1.c */
    int status  = 123;

.. COMMAND: REMOVE .jcons

::

    $ jcons
    gcc -c lib/mod1.c -o build/release/lib/mod1.o
    gcc -c main.c -o build/release/main.o
    gcc -o build/release/prog build/release/main.o build/release/lib/mod1.o

To build both a release and a debug version, we can use normal Python_ scripting:

.. code-block:: python

    # construct.py
    flavors = [
        ["release", "-O2 -DNDEBUG"],
        ["debug",   "-DDEBUG"],
    ]
    for flavor, cflags in flavors:
        e = Cons(BUILD_TOP = "build/" + flavor, CFLAGS = cflags)
        e.program("prog", "main.c", "lib/mod1.c")

.. CALL: save_file construct.py

With this file the build would look like::

    $ jcons --always-make
    gcc -DDEBUG -c lib/mod1.c -o build/debug/lib/mod1.o
    gcc -DDEBUG -c main.c -o build/debug/main.o
    gcc -o build/debug/prog build/debug/main.o build/debug/lib/mod1.o
    gcc -O2 -DNDEBUG -c lib/mod1.c -o build/release/lib/mod1.o
    gcc -O2 -DNDEBUG -c main.c -o build/release/main.o
    gcc -o build/release/prog build/release/main.o build/release/lib/mod1.o


If ``BUILD_SUBDIR`` was used instead of ``BUILD_TOP``, we would get:

.. code-block:: python

    # construct.py
    e = Cons(BUILD_SUBDIR = "release")
    e.program("prog", "main.c", "lib/mod1.c")

::

    $ jcons
    gcc -c lib/mod1.c -o lib/release/mod1.o
    gcc -c main.c -o release/main.o
    gcc -o release/prog release/main.o lib/release/mod1.o

or if ``BUILD_SUFFIX`` was used:

.. code-block:: python

    # construct.py
    e = Cons(BUILD_SUFFIX = "release")
    e.program("prog", "main.c", "lib/mod1.c")

::

    $ jcons
    gcc -c lib/mod1.c -o lib/mod1-release.o
    gcc -c main.c -o main-release.o
    gcc -o prog-release main-release.o lib/mod1-release.o


Note that only methods producing object files or executables
(e.g. :py:meth:`Cons.program` or :py:meth:`Cons.static_library`), are affected by the
``BUILD_*`` variables.  :py:meth:`Cons.command` does not look at those
variables.

Cache of build results
++++++++++++++++++++++

JCons can cache the generated files in a special directory. If the same
file is about to built again later, JCons can replace the actual
command with a copy operation from the cache directory. This is much
faster, and avoids needlessly re-executing the same command with the
same input several times.

TODO: add example

The construct.py file
+++++++++++++++++++++

JCons reads a file ``construct.py`` (or another file specified with an
``-f`` option). This file is a normal Python_ file where methods of the
:py:class:`Cons` class can be called. The purpose of the file is to tell
JCons what there is to build (i.e. help build the directed acyclic graph
(DAG) describing the dependencies).  It is of course possible (but pointless) to do
something entirely different in the script, but then JCons would not know what
to do:

.. code-block:: python

    # construct.py
    print("hello world")         # pointless use of JCons
    exit(0)

::

    $ jcons
    hello world

The first thing to do in a ``construct.py`` file is to create an object of the :py:class:`Cons` class.
Then methods can be called on that object to tell JCons what there is to build.
There are different methods for different needs (e.g. :py:meth:`Cons.command`, :py:meth:`Cons.object`, :py:meth:`Cons.objects`,
:py:meth:`Cons.static_library`, :py:meth:`Cons.program`). The following example will demonstrate:

.. code-block:: python

    e = Cons()
  
    # C program with two modules
    e.program("foo", "foo.c", "bar.c")

    # generic command
    e.command("bar.pdf", "bar.ps", "ps2pdf %INPUT %OUTPUT")

    # a library
    e.static_library("mylib", "x.cpp", "y.cpp", "z.cpp")

The constructor of the :py:class:`Cons` class can take optional named arguments.
For example:

.. code-block:: python

    e1 = Cons(CFLAGS = "-g -Wall")
    e1.program("foo", "foo1.cpp", "foo2.c")

    e2 = Cons(CFLAGS = "-O2 -Wall", CXXFLAGS = "-O2")
    e2.program("bar", "bar1.cpp", "bar2.c")


conscript.py files
++++++++++++++++++

A larger application will be spread over a number of directories. Each
directory may produce a program or a library used by some program.  To
handle this situation, the main ``construct.py`` file can include other
subsidiary files (typically called ``conscript.py`` in the tradition
from Cons_):

.. code-block:: python

    # construct.py
    e = Cons()
    e.program("prog", "main.c", "util/util.a")
    Cons.include("util/conscript.py")

.. code-block:: python

    # util/conscript.py
    e = Cons()
    e.static_library("util", "util.c")

.. FILE:

    /* main.c */
    extern int status; int main() { return status; }

.. FILE:

    /* util/util.c */
    int status = 17;

With these files we get::

    $ jcons
    gcc -c main.c -o main.o
    gcc -c util/util.c -o util/util.o
    ar rc util/util.a util/util.o
    gcc -o prog main.o util/util.a

JCons maintains *one* global DAG of all dependencies. All included
``conscript.py`` file contribute to this dependency graph.


.. include:: pointers.txt