


       



       .














                                   Cook





                                 Tutorial







                            Aryeh M. Friedman

                         [4maryeh@m-net.arbornet.org[0m


































       .












       This document describes Cook version 2.26
       and was prepared 11 July 2025.






       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 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 2 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, write to  the  Free
       Software  Foundation,  Inc.,  59  Temple  Place,  Suite 330,
       Boston, MA 02111, USA.

















       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


       Aryeh M. Friedman                                     Page 2





       Cook                                                Tutorial



       basic  flaw  with  this method though: if you "blindly" make
       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

       ____________________

       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



            ingredients (in cook's  cases  the  output  from  other
            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



       Aryeh M. Friedman                                     Page 4





       Cook                                                Tutorial



          +o Now  that we have all the ingredients for libutils.a we
            can cook it with a command something like
                 rm libutils.a
                 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;


       Aryeh M. Friedman                                     Page 5





       Cook                                                Tutorial



       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
       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


       Aryeh M. Friedman                                     Page 6





       Cook                                                Tutorial



       following sample project source tree:
                            +++
                            ++_P+_r|_o_j_e_c_t
                             ++++Hloiwbto.cook
                             |++ +|lib1.c
                             | + +|lib2.c
                             | + +|lib.h
                             ++++p+rog1
                             | + +|src1.c
                             | + +|src2.c
                             | + +|main.c
                             ++++p+rog2
                             | + +|src1.c
                             | + +|src2.c
                             |+++-main.c
                             ++++d+o+c
                               |++p+r|og1
                               ++++p-rmoagn2ual
                                ++ +|manual
                                   -


       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 src1.o  src2.o
                           main.o  s+rc1.o src2.o
                                      +
                                bin/prog1
                                 bin/prog2



       
       Aryeh M. Friedman                                     Page 7





       Cook                                                Tutorial



       Now we know  enough  to  write  the  first  version  of  our
       cookbook.   The cookbook which follows doesn't actually cook
       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

       
       Aryeh M. Friedman                                     Page 8





       Cook                                                Tutorial



                lib/src2.o ;

       In order to cook this cookbook just type the
            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

       
       Aryeh M. Friedman                                     Page 9





       Cook                                                Tutorial



                                %1   usr
                                %2   local
                                %3   bin
                                 %   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

       
       Aryeh M. Friedman                                    Page 10





       Cook                                                Tutorial



       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
       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

       
       Aryeh M. Friedman                                    Page 11





       Cook                                                Tutorial



       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.
       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:

       
       Aryeh M. Friedman                                    Page 12





       Cook                                                Tutorial



            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:
            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

       
       Aryeh M. Friedman                                    Page 13





       Cook                                                Tutorial



       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
                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.




       
       Aryeh M. Friedman                                    Page 14





       Cook                                                Tutorial



       _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 %
                     [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.


       
       Aryeh M. Friedman                                    Page 15





       Cook                                                Tutorial



       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];

       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]];

       
       Aryeh M. Friedman                                    Page 16





       Cook                                                Tutorial



            }
            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 */
            [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.

       
       Aryeh M. Friedman                                    Page 17





       Cook                                                Tutorial



       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 18


