


       



       .














                                   Cook





                                 Tutorial







                            Aryeh M. Friedman

                         _a_r_y_e_h_@_m_-_n_e_t_._a_r_b_o_r_n_e_t_._o_r_g


































       .












       This document describes Cook version 2.32
       and was prepared 18 April 2013.






       This document describing the Cook program is
       Copyright (C) 2002 Aryeh M. Friedman

       Cook itself is
       Copyright  (C)  1988,  1989,  1990,  1991, 1992, 1993, 1994,
       1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,  2004,
       2005, 2006, 2007, 2008 Peter Miller

       This  program  is  free  software;  you  can redistribute it
       and/or modify it under the terms of the GNU  General  Public
       License as published by the Free Software Foundation; either
       version 3 of the License, or  (at  your  option)  any  later
       version.

       This  program  is  distributed  in  the hope that it will be
       useful, but WITHOUT ANY WARRANTY; without even  the  implied
       warranty  of  MERCHANTABILITY  or  FITNESS  FOR A PARTICULAR
       PURPOSE.  See  the  GNU  General  Public  License  for  more
       details.

       You  should  have  received a copy of the GNU General Public
       License   along   with   this   program.   If    not,    see
       <http://www.gnu.org/licenses/>.


















       Cook                                                Tutorial



       _1_.  _B_u_i_l_d_i_n_g _P_r_o_g_r_a_m_s

       If you write simple programs (a few hundred lines of code at
       most) compiling the program is often no more then  something
       like this:
            gcc foo.c -o foo
       If you have a few files in your program you just do:
            gcc foo.c ack.c -o foo
       But  what happens if some file that is being compiled is the
       output of an other program (like using yacc/lex to construct
       a  command  line  parser)?   Obviously  foo.c does not exist
       before foo.y is processed by yacc.  Thus you have to do:
            yacc foo.y
            cc foo.c ack.c -o foo
       What happens if say you  modify  ack.c  but  do  not  modify
       foo.y?   You  can  skip  the yacc step.  For a small program
       like the one above it is possible to remember what order you
       need  to  do stuff in and what needs to be done depending on
       what file you modify.

       Let's add one more complication let's say you have a library
       that  also  needs  to be "built" before the executable(s) is
       built.  You need to not only remember what steps are  needed
       to  construct  the  library object file but you also need to
       remember that it needs to be done you make your executables.
       Now  add  to  this  you also need to keep track of different
       versions  as  well  figuring  out  how  to  build  different
       versions  for  different platforms and/or customers (say you
       support Windows, Unix and have a Client, Server  and  trial,
       desktop  and  enterprise  versions  of  each and you need to
       produce any and  all  combination  of  things...  that's  24
       different  versions of the same set of executables).  It now
       becomes almost impossible to to  remember  how  each  on  is
       built.   On  top  all this if you build it differently every
       time you need to recompile the program there is no guarantee
       you  will not introduce bugs due to only the order stuff was
       built in.

       And the above example is for a "small"  applications  (maybe
       10  to  20 files) what happens if you have a medium or large
       project (100s or 1000s of files) and 10+ or 100+ executables
       with  each  one  having 10+ different configurations.  It is
       clearly the number of possible ways to make this  approaches
       infinity  very  rapidly (in algorithm designer terms _O_(_n_!_)).
       There has to be a easier  way!   Traditionally  people  have
       used  a tool called _m_a_k_e to handle this complexity, but make
       has some major flaws such  that  it  is  very  hard  if  not
       impossible  to  make  know  how  to build the entire project
       without some super nasty and flawed "hacks".   In  the  last
       few  years  a  program  called  Cook  has gained a small but
       growing popularity as a extremely "intelligent"  replacement
       for make.




       Aryeh M. Friedman                                     Page 1





       Cook                                                Tutorial



       _2_.  _D_e_p_e_n_d_e_n_c_y _G_r_a_p_h_s

       Clearly,  for any build process the build management utility
       (e.g. _c_o_o_k or _m_a_k_e) needs to know that for event Y to  occur
       event  X  has  to  happen first.  This knowledge is called a
       dependency.  In simple programs it is possible to just  tell
       the  build  manager  that  X  depends  on Y.  This has a few
       problems:

          +o You can not define generic dependencies for example you
            can not say that all .o files depend on .c files of the
            same name.

          +o Often there are intermediate files created  during  the
            build  process  for  example foo.y -> foo.c -> foo.o ->
            foo.  This means that each intermediate file  needs  to
            be made before the final program is built.

          +o In  almost  all  projects  there  is  no  single way of
            producing any given file type.  For example ack.c  does
            not  need  to  be created from the ack.y file but foo.c
            does need to be created from the foo.y file.

          +o Many times many things depend on event X but X can  not
            happen  until  Y  happens.   For example if you need to
            compile all the .c files into .o files before  you  can
            combine  them  into  a library then once the library is
            made  then  and  _o_n_l_y  then  can  you  build  all   the
            executables that need that library.

          +o Depending  on  what  variant  of  an executable you are
            building  you  may  have  a  total  different  set   of
            dependencies  for  that  executable.   For  example the
            Microsoft  version  of  your  program  may  be  totally
            different than the Unix one.
       Thus  one  of  the most fundamental things any build manager
       needs to know is create a "graph" of  all  the  dependencies
       (i.e.  what depends on what and what order stuff needs to be
       built in).

       Obviously if you modify only a file or two and  rebuild  the
       project you only need to recreate those files that depend on
       the ones you changed.  For example if I modify foo.y but not
       ack.c  then  ack.c  does not need to be recompiled but foo.c
       after it is recreated does.  All build managers know how  to
       do this.


       _3_.  _C_o_o_k _v_s_. _M_a_k_e

       Many  times the contents of entire directories depend on the
       building of  everything  in  other  directories.   Make  has
       traditionally  done  this with "recursive make".  There is a
       basic flaw with this method though: if  you  "blindly"  make


       Aryeh M. Friedman                                     Page 2





       Cook                                                Tutorial



       each directory in some preset order you are doing stuff that
       is either unneeded and/or may cause problems  in  the  build
       process down the road.  For a more complete explanation, see
       Recursive Make Considered Harmful1.

       Cook  takes  the  opposite  approach.   It  makes a _c_o_m_p_l_e_t_e
       dependency graph of your entire project then does the entire
       "cook" at the root directory of your project.


       _4_.  _T_e_a_c_h_i_n_g _C_o_o_k _a_b_o_u_t _D_e_p_e_n_d_e_n_c_i_e_s

       Each  _n_o_d_e  in  a dependency graph has two basic attributes.
       The first is what other nodes (if any) it  depends  on,  and
       the  second  is  a list of actions needed to be performed to
       bring the node _u_p _t_o _d_a_t_e (bring it to a state in which  any
       nodes that depend on it can use it's products safely).

       One  issue  we  have  right  off the bat is which node do we
       start at.  While by convention this node is  usually  called
       'all'  it does not have to be, as we will see later it might
       not even have a hard coded name at all.  Once we know  where
       to  start  we  need someway of linking nodes together in the
       dependency graph.

       In cook all this functionality is handled  by  _r_e_c_i_p_e_s.   In
       basic terms a recipe is:

          +o The name of the node so other nodes know how to link to
            it (this name can be dynamic).  This  name  is  usually
            the name of a file, but not always.

          +o A list of other recipes that need to be "cooked" before
            this recipe can be processed.  The best way to think of
            this  is  to  use  the  metaphor that cook is based on.
            That being in order to make meal at a  fine  restaurant
            you  need to make each dish.  For each dish you need to
            combine the ingredients in the right order at the right
            time.  You keep dividing up the task until you get to a
            task that does not depend on something else like seeing
            if   you  have  enough  eggs  to  make  the  bread.   A
            dependency graph for building  a  software  project  is
            almost identical except the _i_n_g_r_e_d_i_e_n_t_s are source code
            not food.

          +o A list of actions to perform once  all  the  ingredient
            are  ready.   Again using the cooking example, in order
            to make  a  French  cream  sauce  you  gather  all  the
            ingredients  (in  cook's  cases  the  output from other

       ____________________

       1. Miller, P.A. (1998).  _R_e_c_u_r_s_i_v_e _M_a_k_e _C_o_n_s_i_d_e_r_e_d  _H_a_r_m_f_u_l,
          AUUGN Journal of AUUG Inc., 19(1), pp. 14-25.
          http://aegis.sourceforge.net/auug97.pdf

       Aryeh M. Friedman                                     Page 3





       Cook                                                Tutorial



            recipes) and then and _o_n_l_y then put the butter  in  the
            pan  with  the  the flour and brown it, then slowly mix
            the milk in, and finally add in the cheese.
       So in summary we have the following parts of a recipe:

          +o The name of the recipe's node in the graph

          +o A list of ingredients needed to cook the recipe

          +o A list of steps performed to cook the recipe
       From the top level view in  order  to  make  a  hypothetical
       project we do the following recipes:

          +o We  repeatedly  process dependency graph nodes until we
            get  a  _l_e_a_f  node  (one  that  does   not   have   any
            ingredients).   Namely  we  go  from the general to the
            specific not the other way.

          +o Visit the all recipe which has program1 and program2 as
            its ingredients

          +o Visit  the  program1  node  which  has  program1.o  and
            libutils.a as its ingredients

          +o Visit program1.o which has program1.c and program1.h as
            its ingredients

          +o Visit  program1.c  to  discover that it is a leaf node,
            because the file already exists we need to  do  nothing
            to create it.

          +o Visit  program1.h  to  discover that it is a leaf node,
            because the file already exists we need to  do  nothing
            to create it.

          +o Now  that we have all the ingredients for program1.o we
            can cook it with a command something like
                 gcc -c program1.c \
                     -o program1.o

          +o Visit the libutils.a node which has lib1.o as its  only
            ingredient.

          +o Visit  lib1.c  to  discover  that  it  is  a leaf node,
            because the file already exists we need to  do  nothing
            to create it.

          +o Now  that we have all the ingredients for lib1.o we can
            cook it with a command something like
                 gcc -c lib1.c -o lib1.o

          +o Now that we have all the ingredients for libutils.a  we
            can cook it with a command something like
                 rm libutils.a


       Aryeh M. Friedman                                     Page 4





       Cook                                                Tutorial



                 ar cq libutils.a lib1.o

          +o Now  that  we  have all the ingredients for program1 we
            can cook it with a command something like
                 gcc program1.o libutils.a \
                     -o program1

          +o Visit  the  program2  node  which  has  program2.o  and
            libutils.a as its ingredients

          +o Visit program2.o which has program2.c and program1.h as
            its ingredients

          +o Visit program2.c to discover that it is  a  leaf  node,
            because  the  file already exists we need to do nothing
            to create it.

          +o Visit program2.h to discover that it is  a  leaf  node,
            because  the  file already exists we need to do nothing
            to create it.

          +o Now that we have all the ingredients for program2.o  we
            can cook it with a command something like
                 gcc -c program2.c \
                     -o program2.o

          +o There  is  no need to visit the libutils.a node, or any
            of its ingredient nodes, because  Cook  remembers  that
            they have been brought up to date already.

          +o Now  that  we  have all the ingredients for program2 we
            can cook it with a command something like
                 gcc program2.o libutils.a \
                     -o program2

          +o Return to the all recipe and find that we  have  cooked
            all  the ingredients and there are no other actions for
            it.  We are done and our entire project is built!
       Now what happens if I say modify program2.c all we  have  to
       do  is  walk  to  the entire graph from all and we find that
       program2.c has changed, and do any  node  which  depends  on
       program2.c  needs  to  be  brought up to date, and any nodes
       which depend on _t_h_e_m, and so  on.   In  this  example,  this
       would be program2.c -> program2.o -> program2 -> all.


       _5_.  _R_e_c_i_p_e _S_y_n_t_a_x

       All statements, recipes and otherwise, are in the form of
            _s_t_a_t_e_m_e_n_t;
       Note the terminating simicolon (;).  An example statement is
            echo aryeh;
       The  only  time  the  the  simicolon (;) is not needed is in
       compound statements surrounded by  {  curly  braces  }.   In


       Aryeh M. Friedman                                     Page 5





       Cook                                                Tutorial



       general  the  convention  is to follow the same general form
       that  C  uses,  as  it  is  with  most  modern   programming
       languages.   This  means  that  for  the  main  part  almost
       everything you have learned about writing  legal  statements
       works  just  fine  in  cook.   The  only exception are the [
       square brackets ] used instead of ( parentheses  )  in  most
       cases.

       The  general  form  of  a  recipe,  there  are some advanced
       options that do not fit well into this format, is:
            _n_a_m_e: _i_n_g_r_e_d_i_e_n_t_s
            {
                _a_c_t_i_o_n_s
            }
       Note: the actions and ingredients are optional.

       Here is a recipe from the above example:
            program1.o: program1.c program1.h
            {
                gcc -c program1.c
                    -o program1.o;
            }
       The only thing to remember here is  that  program1.c  either
       has  to  exist or Cook needs to know how to cook it.  If you
       reference an ingredient that Cook does not know how to  cook
       you get the following error:
            cook: program1: don't know how
            cook: cookfile: 1: "program1"
                not derived due to errors
                deriving "program1.o"
       All  this  says  is  there  is  no  algorithmic way to build
       example1.o that Cook can find.

       A _c_o_o_k_b_o_o_k file can contain zero or more recipes.  If  there
       is  no  _d_e_f_a_u_l_t  recipe (the first recipe whose name is hard
       coded) you get the following error:
            cook: no default target
       Most of the time this just means that Cook cannot figure out
       what  the  "concrete"  name  of  a recipe is based solely by
       reading  the  cookbook.   By  default  cook  looks  for  the
       cookbook in "Howto.cook" [note 1].


       _6_.  _A _S_a_m_p_l_e _P_r_o_j_e_c_t

       For  the  remainder  of  the  tutorial  we will be using the
       following sample project source tree:









       Aryeh M. Friedman                                     Page 6





       Cook                                                Tutorial



                    ++-
                    -----_P-_r-_o+_j+_e-_c-_t--
                           +++--Hl-oiwbto.cook
                           +------++---l-ib1.c
                           |      ++---l-ib2.c
                           |      ++---l-ib.h
                           +++--p-ro-g+1----
                           |  ----++---s-rc1.c
                           |      ++---s-rc2.c
                           |      ++---m-ain.c
                           +++--p-r-o+g+2----
                           |      ++---s-rc1.c
                           |      ++---s-rc2.c
                           +++    -+---m-ain.c
                           +----d-o-c+++
                                  -+---p-r-o-g+1-----
                                  +++ pro-g+2---m-anual
                                  --------+---m-a-nual
                                         -+----

       The final output of the build  process  will  be  completely
       working   and  installed  executables  of  prog1  and  prog2
       installed in  /usr/local/bin  and  the  documentation  being
       placed in /usr/local/share/doc/myproj.


       _7_.  _O_u_r _F_i_r_s_t _C_o_o_k_b_o_o_k

       The  first  step  in  making a cookbook is to sketch out the
       decencies in our sample project the graph would be:

       
                          lib1.c lib2.c  lib.h
                             +      +
                             | -+   |  -+
                          lib1.o lib2.o
                                    +
                                    |
                                 +-
                                lib/lib.a src2.y
                                             +
                                             |
                          mmaaiinn..ccsrscr1c.1c.cssrrcc22..cc
                             ++     + +     ++
                             ||-+   | |-+   ++
                          main.o -s+rc1.o |src2.o
                           main.o  s+rc+1.o+src2.o
                               +- | | +  |+
                                b+i+n/pro-g+1 |
                                 bin/prog2


       Now we know  enough  to  write  the  first  version  of  our
       cookbook.   The cookbook which follows doesn't actually cook

       
       Aryeh M. Friedman                                     Page 7





       Cook                                                Tutorial



       anything, because it contains ingredients  and  no  actions.
       We  will add the actions needed in a later section.  Here it
       is:
            /* top level target */
            all: /usr/local/bin/prog1
                /usr/local/bin/prog2
                /usr/local/share/doc/prog1/manual
                /usr/local/share/doc/prog2/manual
                ;
            /* where to install stuff */
            /usr/local/bin/prog1:
                bin/prog1 ;
            /usr/local/bin/prog2:
                bin/prog2 ;
            /usr/local/share/doc/prog1/manual:
                doc/prog1/manual ;
            /usr/local/share/doc/prog2/manual:
                doc/prog2/manual ;
            /* how to link each program */
            bin/prog1:
                prog1/main.o
                prog1/src1.o
                prog1/src2.o
                lib/liblib.a ;
            bin/prog2:
                prog2/main.o
                prog2/src1.o
                prog2/src2.o
                lib/liblib.a ;
            /* how to use yacc */
            prog2/src2.c: prog2/src2.y ;
            /* how to compile sources */
            prog1/main.o: prog1/main.c ;
            prog1/src1.o: prog1/src1.c ;
            prog1/src2.o: prog1/src2.c ;
            prog2/main.o: prog2/main.c ;
            prog2/src1.o: prog2/src1.c ;
            prog2/src2.o: prog2/src2.c ;
            lib/src1.o: lib/src1.c ;
            lib/src2.o: lib/src2.c ;
            /* include file dependencies */
            prog1/main.o: lib/lib.h ;
            prog1/src1.o: lib/lib.h ;
            prog1/src2.o: lib/lib.h ;
            prog2/main.o: lib/lib.h ;
            prog2/src1.o: lib/lib.h ;
            prog2/src2.o: lib/lib.h ;
            lib/src1.o: lib/lib.h ;
            lib/src2.o: lib/lib.h ;
            /* how to build the library */
            lib/liblib.a:
                lib/src1.o
                lib/src2.o ;
       In order to cook this cookbook just type the

       
       Aryeh M. Friedman                                     Page 8





       Cook                                                Tutorial



            cook
       command in the same directory as the cookbook is in.


       _8_.  _S_o_f_t _c_o_d_i_n_g _R_e_c_i_p_e_s

       One of the most glaring problems with this first version  of
       our  cookbook  is  it  hard  codes everything.  This has two
       problems:

          +o We have to be super verbose in how  we  describe  stuff
            since we have to specify every single recipe by hand.

          +o If we add new files (maybe we add a third executable to
            the project) we have to rewrite the cookbook for  _e_v_e_r_y
            file we add.
       Fortunately,  Cook  has  a  way of automating the build with
       implicit recipes.  It has a way of saying how to  move  from
       any arbitrary .c file to its .o file.

       Cook  provides  several  methods for being able to soft code
       these relationships.  This section discusses file "patterns"
       that  can  be  used to do pattern matching on what recipe to
       cook for a given file.

       Note on pattern matching notation used in this section:

       _[_s_t_r_i_n_g_] means the matched pattern.

       The first  thing  to  keep  in  mind  about  cook's  pattern
       matching  is once a pattern is matched it will have the same
       value for the remainder of the recipe.  So for example if we
       matched  prog/[src1].c  then  any  other  reference  to that
       pattern will also return src1.  For example:
            prog/_[_s_r_c_1_].o: prog/_[_s_r_c_1_].o ;
       if we matched _s_r_c_1 on the first match (prog1/_[_s_r_c_1_].o)  then
       we will always match _s_r_c_1 in this recipe (prog1/_[_s_r_c_1_].c).

       Cook uses the percent (%) character to denote matches of the
       relative file name (no path).  Thus the above  recipe  would
       be written:
            prog/%.o: prog/%.c ;
       Cook  also  lets you match the full path of a file, or parts
       of the path to a file.  This done with %_n where _n is a  part
       number.  For example
            /usr/local/bin/prog1
       could match the pattern
            /%1/%2/%3/%
       with the parts be assigned

                                %1   usr
                                %2   local
                                %3   bin


       
       Aryeh M. Friedman                                     Page 9





       Cook                                                Tutorial



                                 %   prog1
       Note that the final component of the path has no _n (there is
       no %4 for prog1).  If we want to reference the  whole  path,
       Cook uses %0 as a special pattern to do this.
            /usr/local/bin/prog1
       could match the pattern
            %0%
       with the parts be assigned

                           %0   /usr/local/bin/
                            %   prog1
       Patterns are connected together thus %0%.c will match any .c
       file in any pattern.

       Let's rewrite the cookbook  for  our  sample  project  using
       pattern matching.  The relevant portions of our cookbook are
       replaced by
            /* how to use yacc */
            %0%.c: %0%.y;
            /* include file dependencies */
            %0%.c: lib/lib.h;
            /* how to compile sources */
            %0%.o: %0%.c;
       When constructing the dependency graph Cook will  match  the
       the  first recipe it sees that meets all the requirements to
       meet a given  pattern.   I.e.  if  we  have  a  pattern  for
       prog1/%.c  and  one for %0%.o and it needs to find the right
       recipe for prog1/src.o it will match the  one  that  appears
       first in the cookbook.  So if the first one is %0%.c then it
       does that recipe even if we meant for it to match prog1/%.c.


       _9_.  _A_r_b_i_t_r_a_r_y _S_t_a_t_e_m_e_n_t_s _a_n_d _V_a_r_i_a_b_l_e_s

       Any statement that is not  a  recipe,  and  not  a  statment
       inseide  a  recipe,  is executed as soon as it is seen.  For
       example I can have a Howto.cook file that only contains  the
       following line:
            echo Aryeh;
       and when ever I ise the cook command it will print my name.

       This in and upon it self is quite pointless but it does give
       a clue about how we can set some cookbook-wide values.   Now
       the  question  is  how  do  we  symbolically represent those
       variables.

       Cook has only one type of variable and that  is  a  list  of
       string  literals,  i.e. "ack", "foo", "bar", _e_t_c.  There are
       no restrictions on how you name variables, except  they  can
       not   be  reserved  words,  this  is  pretty  close  to  the
       restrictions most programming languages have.  There is  one
       major  difference  though:  variables can start with numbers
       and contain punctuation characters.   Additionally  you  can
       vary  variable  names,  i.e. the name of the actual variable

       
       Aryeh M. Friedman                                    Page 10





       Cook                                                Tutorial



       can use a variable expression (this is hard to  explain  but
       easy to show which we will do in a few paragraphs).

       All variables, when queried for their value, are [ in square
       brackets ] for  example  if  the  "name"  variable  contains
       "Aryeh" then:
            echo [name];
       Has  exactly  the  same  result  as  the  previous  example.
       Variables are simply set by using var = value;  For example:
            name = Aryeh;
            echo [name];
       Let's say I need to have two  variables  called  'prog1_obj'
       and   'prog2_obj'   that  contain  a  list  of  all  the  .o
       ingredients in the prog1 and prog2 directories respectively.
       Obviously  the  same  operation  that  produces the value of
       prog1_obj is identical to the one  that  produces  prog2_obj
       except  it operates on a different directories.  So why then
       do we need two different operations to do  the  same  thing,
       this violates the principle of any given operation it should
       only occur in one place.  In reality all we need  to  do  is
       have some way of changing the just the variable name and not
       the values it produces.  In cook we do this  with  something
       like [[dir_name]_obj].  The actual procedure for getting the
       list of files will be covered in  the  "control  structures"
       section.

       Let's  revise some sections of our sample project's cookbook
       to take advantage of variables:
            /* where to install stuff */
            prefix = /usr/local;
            idoc_dir = [prefix]/share/doc;
            ibin_dir = [prefix]/bin;
            /* top level target */
            all:
                [ibin_dir]/prog1
                [ibin_dir]/prog2
                [idoc_dir]/prog1/manual
                [idoc_dir]/prog2/manual;
            /* where to install each program */
            [ibin_dir]/%: bin/% ;
            [idoc_dir]/%/manual: doc/%/manual ;
       As you can see we  didn't  make  the  cookbook  any  simpler
       because  we do not know how to intelligently set stuff based
       on what the actual file structure of our project.  The  only
       thing we gain here is the ability to change where we install
       stuff very quickly be just changing  install_dir.   We  also
       gain  a little flexibility in how we name the directories in
       our source tree.


       _1_0_.  _U_s_i_n_g _B_u_i_l_t_-_i_n _F_u_n_c_t_i_o_n_s

       If all you could do was set variables to static  values  and
       do  pattern  matching  cook  would  not be very useful, i.e.

       
       Aryeh M. Friedman                                    Page 11





       Cook                                                Tutorial



       every time we add a new source file to our project  we  need
       to rewrite the cookbook.  We need some way to extract useful
       data from variables and leave out what we do not want.   For
       example  if  we  want  to  know what all the .c files in the
       prog1 directory are we just ask for  all  files  that  match
       prog1/%.c.  We could use the match_mask built-in function to
       extract the needed sublist of files.  Built-in functions can
       do  many other manipulations of our source tree contents and
       how to process them.  In general I will  introduce  a  given
       built-in function as we encounter them.

       As  far  as  cook is concerned, for the most part, functions
       and variables are treated identically.  This means  anywhere
       where  you  would use a variable you can use a function.  In
       general a function is called like this:
            [func arg1 arg2 ... argN]
       For example:
            name = [foobar aryeh];


       _1_1_.  _S_o_u_r_c_e _T_r_e_e _S_c_a_n_n_i_n_g

       The first thing we need to do to  automate  the  process  of
       handling  new  files is to collect the list of source files.
       In order to do this we need to ask the operating  system  to
       give  us  a  list  of  all files in a directory and all it's
       subdirectories.  In Unix the best way to do this is with the
       find(1)  command.   Thus to get a complete list of all files
       in say the current directory we do:
            find . -print
       or any variation thereof.

       Great, now how do we get the output of find into a  variable
       so  cook  can use it.  Well, the collect function does this.
       We then just assign the results of  collect  to  a  list  of
       files,  build  experts  like  to call this the manifest.  So
       here is how we get the manifest:
            manifest = [stripdot
                [collect find . -print]];
       That is all nice and well but how do  we  get  the  list  of
       source  files  in  prog1  only,  for  example.    There is a
       function called match_mask that does this.   The  match_mask
       function  returns all "words" that match some pattern in our
       list.  For example to get a list of  all  .c  files  in  our
       project we do:
            src = [match_mask %0%.c
                [manifest]];
       It is fine to know what files are already in our source tree
       but what we really want to do is find the list of files that
       need  to  be cooked.  We use the fromto function to do this.
       The fromto function takes all the  words  in  our  list  and
       transforms  all  the  names  which match to some other name.
       For example to get a list of all the .o  files  we  need  to
       cook we do:

       
       Aryeh M. Friedman                                    Page 12





       Cook                                                Tutorial



            obj = [fromto %0%.c %0%.o
                   [src]];
       It  is  rare  that we need to know about the existence of .c
       files since in most cases,  unless  they  are  derived  from
       cooking  something  else,  they  either exist or they do not
       exist.  In the case of them not existing the .o  target  for
       that  source  should fail.  For this reason we really do not
       need a src variable at all.  Remember  I  mentioned  that  a
       function  call  can  be  used anywhere a variable can.  This
       means that we can do the match_mask call in  the  same  line
       that we do the fromto.  Thus the new statement is:
            obj = [fromto %0%.c %0%.o
                   [match_mask %0%.c
                    [manifest]]];
       Time  to  update  some  sections  of  our  sample  project's
       cookbook one more time:
            /* info about our files */
            manifest =
                [collect find . -print];
            obj = [fromto %0%.c %0%.o
                   [match_mask %0%.c
                    [manifest]]];
            /* how to build each program */
            prog1_obj = [match_mask
                prog1/%.o [obj]];
            prog2_obj = [match_mask
                prog2/%.o [obj]];
            bin/%: [%_obj] lib/lib.a;
            /* how to build the library */
            lib_obj = [match_mask lib/%.o
                [obj]];
            lib/lib.a: [lib_obj];
       The important thing to  observe  here  is  that  it  is  now
       possible  to  add  a  source  file  to one of the probram or
       library directories  and  Cook  will  automagically  notice,
       without  any need to modify the cookbook.  It doesn't matter
       whether there are 3 files or 300 in these  directories,  the
       cookbook is the same.


       _1_2_.  _F_l_o_w _C_o_n_t_r_o_l

       If  there  was  no conditional logic in programming would be
       rather pointless, who wants to write I program that can only
       do  something  once,  the same is true in cook.  Even though
       the stuff we need to conditional in a build  is  often  very
       trivial  as  far as conditional logic goes, namely there are
       if statements and the equivalent of while  loops  and  thats
       all.

       If  statements are pretty straight forward.  If you are used
       to C, C++, _e_t_c, the only surprise is the need for  the  then
       keyword.  Here is a example if statement:
            if [not [count [file]]] then

       
       Aryeh M. Friedman                                    Page 13





       Cook                                                Tutorial



                echo no file provided;
       The count function returns the number of words in the "file"
       list and the not function is true  if  the  argument  is  0.
       Other  then  that  the  if  statement works much the way you
       would expect it to.

       Cook has only one type of loop that being the loop statement
       and  it  takes  no  conditions.  A loop is terminated by the
       loopstop statement (like a C _b_r_e_a_k statement).   Other  then
       that  loops  pretty  much  work  the way you expect them to.
       Here is an example loop:
            /* set the loop "counter" */
            list = [kirk spock 7of9
                janeway worf];
            /* do the loop */
            loop word = [list]
            {
                /* print the word */
                echo [word];
            }


       _1_3_.  _S_p_e_c_i_a_l _V_a_r_i_a_b_l_e_s

       Like most scripting languages Cook has a set  of  predefined
       variables.   While  most of them are used internally by Cook
       and not by the user, one of them  deserves  special  mention
       and  that is target.  The target variable has no meaning out
       side of recipes but inside recipes it refers to the  current
       recipe's  target's  "real"  name,  i.e.  the  one  that Cook
       "thinks" it is currently building, not the soft  coded  name
       we  provided  in  the  cookbook.   For example in our sample
       project's cook book if we where  compiling  lib/src1.c  into
       lib/src.o  the %0%.o: %0%.c; recipe would, as far as Cook is
       concerned, actually be lib/src1.o: lib/src1.c;   The  recipe
       name, and thus the [target], of this is set to the lib/src.o
       string.

       There are other special variables described in the Cook User
       Guide.   You  may want to look them up and use them when you
       start writing more advanced cookbooks.


       _1_4_.  _S_u_p_e_r _S_o_f_t _c_o_d_i_n_g

       Now we know enough so we can make Cook  handle  building  an
       arbitrary  number  of  programs in our sample project.  Note
       the following example assumes that all  program  directories
       contain  a  main.c  file and no other directory contains it.
       The best way to understand what is needed it to look at  the
       sample  cookbook  for  this  line  by line.  So here are the
       rewritten sections of our sample cookbook:
            /* names of the programs */
            progs = [fromto %/main.c %

       
       Aryeh M. Friedman                                    Page 14





       Cook                                                Tutorial



                     [match_mask %/main.c
                      [manifest]]];
            /* top level target */
            all:
                [addprefix [ibin_dir]/
                    [progs]]
                [prepost [idoc_dir]/ /manual
                    [progs]];
            /* how to build each program */
            loop prog = [progs]
            {
                [prog]_obj = [match_mask
                    [prog]/%.o [obj]];
            }
            bin/%: [%_obj] lib/lib.a;
       The basic idea is that we use a loop to create the  list  of
       .o  files for all programs and then we use variable variable
       names to reference the right one in the recipe.


       _1_5_.  _S_c_a_n_n_i_n_g _f_o_r _H_i_d_d_e_n _D_e_c_e_n_c_i_e_s

       In most real programs most .c files have a different set  of
       #include  lines  in  them.   For  example prog1/src1.c might
       include prog1/hdr1.h but prog1/src2.c does not.  So  far  we
       have  conveniently  avoided this fact on the assumption that
       once made .h files don't change.  Any experience with a non-
       trivial  project  show  this  is  not  true.   So  how do we
       automatically scan for these  dependencies?   It  would  not
       only  defeat  the purpose of soft coding but would be a pain
       in the butt to have to encode this in the cookbook.

       One way of doing it is to scan each .c  for  #include  lines
       and  say any that are found represent "hidden" dependencies.
       It would be fairly trivial to create a shell script or small
       C  program that does this.  Cook though has been nice enough
       to include program that does this for us in most cases  that
       are  not insanely non-trivial.  There are several methods of
       using c_incl we will only cover the "trivial"  method  here,
       if you need higher performance refer to the Cook User Guide,
       it has a whole chapter on include dependencies.

       The  c_incl  program  essentially  just  prints  a  list  of
       #include  files  it  finds in its argument.  To do this just
       do:
            c_incl _p_r_o_g.c
       Now all we have to do is have Cook collect  this  output  on
       the  ingredients  list of our recipe and boom we have a list
       of our hidden dependencies.  Here is the  rewritten  portion
       of our sample cookbook for that:
            /* how to build each program and
               include file dependencies */
            %0%.o: %0%.c
                [collect c_incl -api %0%.c];

       
       Aryeh M. Friedman                                    Page 15





       Cook                                                Tutorial



       The c_incl -api option means if the file doesn't exist, just
       ignore it.


       _1_6_.  _R_e_c_i_p_e _A_c_t_i_o_n_s

       Now that we have all the decencies soft coded all we have to
       do  actually build our project is to tell each recipe how to
       actually cook the target from the ingredients.  This is done
       by adding actions to a recipe.  The actions are nothing more
       "simple" statements that are bound to  a  recipe.   This  is
       done by leaving off the trailing semicolon (;) on the recipe
       and putting the actions inside { curly braces  }.   This  is
       best  shown  by  example.  So here is our final cookbook for
       our sample project:
            /* where to install stuff */
            prefix = /usr/local;
            idoc_dir = [prefix]/share/doc;
            ibin_dir = [prefix]/bin;
            /* info about our files */
            manifest =
                [collect find . -print];
            obj = [fromto %0%.c %0%.o
                   [match_mask %0%.c
                    [manifest]]];
            /* names of the programs */
            progs = [fromto %/main.c %
                     [match_mask %/main.c
                      [manifest]]];
            /* top level target */
            all:
                [addprefix [ibin_dir]/
                    [progs]]
                [prepost [idoc_dir]/ /manual
                    [progs]];
            /* how to build each program */
            loop prog = [progs]
            {
                [prog]_obj = [match_mask
                    [prog]/%.o [obj]];
            }
            bin/%: [%_obj]
            {
                gcc [%_obj] -o [target];
            }
            /* how to build the library */
            lib_obj = [match_mask lib/%.o
                [obj]];
            lib/lib.a: [lib_obj]
            {
                rm [target];
                ar cq [target] [lib_obj];
            }
            /* how to "install" stuff */

       
       Aryeh M. Friedman                                    Page 16





       Cook                                                Tutorial



            [ibin_dir]/%: bin/%
            {
                cp bin/% [target];
            }
            [idoc_dir]/%/manual: doc/%/manual
            {
                cp doc/%/manual [target];
            }
            /* how to compile sources*/
            %0%.o: %0%.c
                [collect c_incl -api %0%.c]
            {
                gcc -c %0%.c -o [target];
            }


       _1_7_.  _A_d_v_a_n_c_e_d _F_e_a_t_u_r_e_s

       Even though the tutorial part of this document  is  done,  I
       feel  it is important to just mention some advanced features
       not covered in the tutorial.  Except for  just  stating  the
       basic  nature of these features I will not go into detail on
       any given one.

          +o Platform  polymorphism.   This  is   where   Cook   can
            automatically  detect  what  platform you are on and do
            some file juggling so that you build for that platform.

          +o Support for private work areas.   If  you  are  working
            within  a  change  management system, Cook knows how to
            query it for only the files you need to work on.   This
            includes  the  automatic  check-out  and  in of private
            copies of those files.

          +o Parallel builds.  For large projects it is possible  to
            spread the build over several processors or machines.

            Conditional  recipes.   It  is  possible  to  execute a
            recipe one way if certain conditions  are  met  and  an
            other way if they are not.
       Many  more  that  are not directly supported by Cook but can
       easily be integrated using shell scripts.


       _1_8_.  _C_o_n_t_a_c_t_s

       If you find any bugs in this  tutorial  please  send  a  bug
       report to Aryeh M. Friedman <aryeh@m-net.arbornet.org>.

       The  Cook  web site is http://www.canb.auug.org.au/~millerp-
       /cook/

       If you want to contact Cook's author, send  email  to  Peter
       Miller <millerp@canb.auug.org.au>.

       
       Aryeh M. Friedman                                    Page 17


