


       



       .














                                   Cook





                                 Tutorial







                            Aryeh M. Friedman

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


































       .












       This document describes Cook version 2.25
       and was prepared 16 July 2012.






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



       [4m1.[24m  [4mBuilding[24m [4mPrograms[0m

       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  [4mO(n!)[24m).
       There  has  to  be  a easier way!  Traditionally people have
       used a tool called [4mmake[24m 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



       [4m2.[24m  [4mDependency[24m [4mGraphs[0m

       Clearly, for any build process the build management  utility
       (e.g.  [4mcook[24m or [4mmake[24m) 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  [4monly[24m  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.


       [4m3.[24m  [4mCook[24m [4mvs.[24m [4mMake[0m

       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  [4mcomplete[0m
       dependency graph of your entire project then does the entire
       "cook" at the root directory of your project.


       [4m4.[24m  [4mTeaching[24m [4mCook[24m [4mabout[24m [4mDependencies[0m

       Each [4mnode[24m 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 [4mup[24m [4mto[24m [4mdate[24m (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 [4mrecipes[24m.  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 [4mingredients[24m 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).  [4mRecursive[24m [4mMake[24m [4mConsidered[24m [4mHarmful[24m,
          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 [4monly[24m 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   [4mleaf[24m   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  [4mthem[24m,  and  so on.  In this example, this
       would be program2.c -> program2.o -> program2 -> all.


       [4m5.[24m  [4mRecipe[24m [4mSyntax[0m

       All statements, recipes and otherwise, are in the form of
            [4mstatement[24m;


       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:
            [4mname[24m: [4mingredients[0m
            {
                [4mactions[0m
            }

       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  [4mcookbook[24m file can contain zero or more recipes.  If there
       is no [4mdefault[24m 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].


       [4m6.[24m  [4mA[24m [4mSample[24m [4mProject[0m

       For the remainder of the  tutorial  we  will  be  using  the


       Aryeh M. Friedman                                     Page 6





       Cook                                                Tutorial



       following sample project source tree:
                    ++-
                    -----[4mP[24m-[4mr[24m-[4mo[24m+[4mj[24m+[4me[24m-[4mc[24m-[4mt[24m--
                           +++--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.


       [4m7.[24m  [4mOur[24m [4mFirst[24m [4mCookbook[0m

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

       [40m[47m[40m[47m[40m[47m[40m[47m[40m[47m[40m[0m
                          lib1.c lib2.c  lib.h
                             [40m+      +[0m
                             [40m| -+   |  -+[0m
                          lib1.o [40mlib2.o[0m
                                    [40m+[0m
                                    [40m|[0m
                                 [40m+-[0m
                                [40mlib/lib.a [47msrc2.y[0m
                                             [40m+[0m
                                             [40m|[0m
                          [47mmmaaiinn..ccsrscr1c.1c.cssrrcc22..cc[0m
                             [40m++     + +     ++[0m
                             [40m||-+   | |-+   ++[0m
                          [47mmain.o [40m-[47ms[40m+[47mrc1.o [40m|[47msrc2.o[0m
                           [47mmain.o  s[40m+[47mrc[40m+[47m1.o[40m+[47msrc2.o[0m
                               [40m+- | | +  |+[0m
                                [47mb[40m+[47mi[40m+[47mn/pro[40m-[47mg[40m+[47m1 [40m|[0m
                                 [47mbin/prog2[0m



       
       Aryeh M. Friedman                                     Page 7





       Cook                                                Tutorial



       [40mNow  we  know  enough  to  write  the  first  version of our[0m
       [40mcookbook.  The cookbook which follows doesn't actually  cook[0m
       [40manything,  because  it  contains ingredients and no actions.[0m
       [40mWe will add the actions needed in a later section.  Here  it[0m
       [40mis:[0m
            [40m/* top level target */[0m
            [40mall: /usr/local/bin/prog1[0m
                [40m/usr/local/bin/prog2[0m
                [40m/usr/local/share/doc/prog1/manual[0m
                [40m/usr/local/share/doc/prog2/manual[0m
                [40m;[0m
            [40m/* where to install stuff */[0m
            [40m/usr/local/bin/prog1:[0m
                [40mbin/prog1 ;[0m
            [40m/usr/local/bin/prog2:[0m
                [40mbin/prog2 ;[0m
            [40m/usr/local/share/doc/prog1/manual:[0m
                [40mdoc/prog1/manual ;[0m
            [40m/usr/local/share/doc/prog2/manual:[0m
                [40mdoc/prog2/manual ;[0m
            [40m/* how to link each program */[0m
            [40mbin/prog1:[0m
                [40mprog1/main.o[0m
                [40mprog1/src1.o[0m
                [40mprog1/src2.o[0m
                [40mlib/liblib.a ;[0m
            [40mbin/prog2:[0m
                [40mprog2/main.o[0m
                [40mprog2/src1.o[0m
                [40mprog2/src2.o[0m
                [40mlib/liblib.a ;[0m
            [40m/* how to use yacc */[0m
            [40mprog2/src2.c: prog2/src2.y ;[0m
            [40m/* how to compile sources */[0m
            [40mprog1/main.o: prog1/main.c ;[0m
            [40mprog1/src1.o: prog1/src1.c ;[0m
            [40mprog1/src2.o: prog1/src2.c ;[0m
            [40mprog2/main.o: prog2/main.c ;[0m
            [40mprog2/src1.o: prog2/src1.c ;[0m
            [40mprog2/src2.o: prog2/src2.c ;[0m
            [40mlib/src1.o: lib/src1.c ;[0m
            [40mlib/src2.o: lib/src2.c ;[0m
            [40m/* include file dependencies */[0m
            [40mprog1/main.o: lib/lib.h ;[0m
            [40mprog1/src1.o: lib/lib.h ;[0m
            [40mprog1/src2.o: lib/lib.h ;[0m
            [40mprog2/main.o: lib/lib.h ;[0m
            [40mprog2/src1.o: lib/lib.h ;[0m
            [40mprog2/src2.o: lib/lib.h ;[0m
            [40mlib/src1.o: lib/lib.h ;[0m
            [40mlib/src2.o: lib/lib.h ;[0m
            [40m/* how to build the library */[0m
            [40mlib/liblib.a:[0m
                [40mlib/src1.o[0m

       
       Aryeh M. Friedman                                     Page 8





       Cook                                                Tutorial



       [40m         lib/src2.o ;[0m

       [40mIn order to cook this cookbook just type the[0m
            [40mcook[0m
       [40mcommand in the same directory as the cookbook is in.[0m


       [4m[40m8.[24m  [4mSoft[24m [4mcoding[24m [4mRecipes[0m

       [40mOne  of the most glaring problems with this first version of[0m
       [40mour cookbook is it hard  codes  everything.   This  has  two[0m
       [40mproblems:[0m

          [40m+o We  have  to  be super verbose in how we describe stuff[0m
            [40msince we have to specify every single recipe by hand.[0m

          [40m+o If we add new files (maybe we add a third executable to[0m
            [40mthe  project) we have to rewrite the cookbook for [4mevery[0m
            [40mfile we add.[0m

       [40mFortunately, Cook has a way of  automating  the  build  with[0m
       [40mimplicit  recipes.   It has a way of saying how to move from[0m
       [40many arbitrary .c file to its .o file.[0m

       [40mCook provides several methods for being able  to  soft  code[0m
       [40mthese relationships.  This section discusses file "patterns"[0m
       [40mthat can be used to do pattern matching on  what  recipe  to[0m
       [40mcook for a given file.[0m

       [40mNote on pattern matching notation used in this section:[0m

       [4m[40m[string][24m means the matched pattern.[0m

       [40mThe  first  thing  to  keep  in  mind  about  cook's pattern[0m
       [40mmatching is once a pattern is matched it will have the  same[0m
       [40mvalue for the remainder of the recipe.  So for example if we[0m
       [40mmatched prog/[src1].c  then  any  other  reference  to  that[0m
       [40mpattern will also return src1.  For example:[0m
            [40mprog/[4m[src1][24m.o: prog/[4m[src1][24m.o ;[0m
       [40mif  we matched [4msrc1[24m on the first match (prog1/[4m[src1][24m.o) then[0m
       [40mwe will always match [4msrc1[24m in this recipe (prog1/[4m[src1][24m.c).[0m

       [40mCook uses the percent (%) character to denote matches of the[0m
       [40mrelative  file  name (no path).  Thus the above recipe would[0m
       [40mbe written:[0m
            [40mprog/%.o: prog/%.c ;[0m

       [40mCook also lets you match the full path of a file,  or  parts[0m
       [40mof  the path to a file.  This done with %[4mn[24m where [4mn[24m is a part[0m
       [40mnumber.  For example[0m
            [40m/usr/local/bin/prog1[0m
       [40mcould match the pattern[0m
            [40m/%1/%2/%3/%[0m
       [40mwith the parts be assigned[0m

       
       Aryeh M. Friedman                                     Page 9





       Cook                                                Tutorial



       [40m                         %1   usr[0m
                                [40m%2   local[0m
                                [40m%3   bin[0m
                                 [40m%   prog1[0m

       [40mNote that the final component of the path has no [4mn[24m (there is[0m
       [40mno  %4  for prog1).  If we want to reference the whole path,[0m
       [40mCook uses %0 as a special pattern to do this.[0m
            [40m/usr/local/bin/prog1[0m
       [40mcould match the pattern[0m
            [40m%0%[0m
       [40mwith the parts be assigned[0m

                           [40m%0   /usr/local/bin/[0m
                            [40m%   prog1[0m

       [40mPatterns are connected together thus %0%.c will match any .c[0m
       [40mfile in any pattern.[0m

       [40mLet's  rewrite  the  cookbook  for  our sample project using[0m
       [40mpattern matching.  The relevant portions of our cookbook are[0m
       [40mreplaced by[0m
            [40m/* how to use yacc */[0m
            [40m%0%.c: %0%.y;[0m
            [40m/* include file dependencies */[0m
            [40m%0%.c: lib/lib.h;[0m
            [40m/* how to compile sources */[0m
            [40m%0%.o: %0%.c;[0m

       [40mWhen  constructing  the dependency graph Cook will match the[0m
       [40mthe first recipe it sees that meets all the requirements  to[0m
       [40mmeet  a  given  pattern.   I.e.  if  we  have  a pattern for[0m
       [40mprog1/%.c and one for %0%.o and it needs to find  the  right[0m
       [40mrecipe  for  prog1/src.o  it will match the one that appears[0m
       [40mfirst in the cookbook.  So if the first one is %0%.c then it[0m
       [40mdoes that recipe even if we meant for it to match prog1/%.c.[0m


       [4m[40m9.[24m  [4mArbitrary[24m [4mStatements[24m [4mand[24m [4mVariables[0m

       [40mAny  statement  that  is  not  a  recipe, and not a statment[0m
       [40minseide a recipe, is executed as soon as it  is  seen.   For[0m
       [40mexample  I can have a Howto.cook file that only contains the[0m
       [40mfollowing line:[0m
            [40mecho Aryeh;[0m
       [40mand when ever I ise the cook command it will print my name.[0m

       [40mThis in and upon it self is quite pointless but it does give[0m
       [40ma  clue about how we can set some cookbook-wide values.  Now[0m
       [40mthe question is  how  do  we  symbolically  represent  those[0m
       [40mvariables.[0m

       [40mCook  has  only  one  type of variable and that is a list of[0m
       [40mstring literals, i.e. "ack", "foo", "bar", [4metc[24m.   There  are[0m

       
       Aryeh M. Friedman                                    Page 10





       Cook                                                Tutorial



       [40mno  restrictions  on how you name variables, except they can[0m
       [40mnot  be  reserved  words,  this  is  pretty  close  to   the[0m
       [40mrestrictions  most programming languages have.  There is one[0m
       [40mmajor difference though: variables can  start  with  numbers[0m
       [40mand  contain  punctuation  characters.  Additionally you can[0m
       [40mvary variable names, i.e. the name of  the  actual  variable[0m
       [40mcan  use  a variable expression (this is hard to explain but[0m
       [40measy to show which we will do in a few paragraphs).[0m

       [40mAll variables, when queried for their value, are [ in square[0m
       [40mbrackets  ]  for  example  if  the  "name" variable contains[0m
       [40m"Aryeh" then:[0m
            [40mecho [name];[0m
       [40mHas  exactly  the  same  result  as  the  previous  example.[0m
       [40mVariables are simply set by using var = value;  For example:[0m
            [40mname = Aryeh;[0m
            [40mecho [name];[0m
       [40mLet's  say  I  need to have two variables called 'prog1_obj'[0m
       [40mand  'prog2_obj'  that  contain  a  list  of  all   the   .o[0m
       [40mingredients in the prog1 and prog2 directories respectively.[0m
       [40mObviously the same operation  that  produces  the  value  of[0m
       [40mprog1_obj  is  identical  to the one that produces prog2_obj[0m
       [40mexcept it operates on a different directories.  So why  then[0m
       [40mdo  we  need  two different operations to do the same thing,[0m
       [40mthis violates the principle of any given operation it should[0m
       [40monly  occur  in  one place.  In reality all we need to do is[0m
       [40mhave some way of changing the just the variable name and not[0m
       [40mthe  values  it produces.  In cook we do this with something[0m
       [40mlike [[dir_name]_obj].  The actual procedure for getting the[0m
       [40mlist  of  files  will be covered in the "control structures"[0m
       [40msection.[0m

       [40mLet's revise some sections of our sample project's  cookbook[0m
       [40mto take advantage of variables:[0m
            [40m/* where to install stuff */[0m
            [40mprefix = /usr/local;[0m
            [40midoc_dir = [prefix]/share/doc;[0m
            [40mibin_dir = [prefix]/bin;[0m
            [40m/* top level target */[0m
            [40mall:[0m
                [40m[ibin_dir]/prog1[0m
                [40m[ibin_dir]/prog2[0m
                [40m[idoc_dir]/prog1/manual[0m
                [40m[idoc_dir]/prog2/manual;[0m
            [40m/* where to install each program */[0m
            [40m[ibin_dir]/%: bin/% ;[0m
            [40m[idoc_dir]/%/manual: doc/%/manual ;[0m

       [40mAs  you  can  see  we  didn't  make the cookbook any simpler[0m
       [40mbecause we do not know how to intelligently set stuff  based[0m
       [40mon  what the actual file structure of our project.  The only[0m
       [40mthing we gain here is the ability to change where we install[0m
       [40mstuff  very  quickly  be just changing install_dir.  We also[0m
       [40mgain a little flexibility in how we name the directories  in[0m

       
       Aryeh M. Friedman                                    Page 11





       Cook                                                Tutorial



       [40mour source tree.[0m


       [4m[40m10.[24m  [4mUsing[24m [4mBuilt-in[24m [4mFunctions[0m

       [40mIf  all  you could do was set variables to static values and[0m
       [40mdo pattern matching cook would  not  be  very  useful,  i.e.[0m
       [40mevery  time  we add a new source file to our project we need[0m
       [40mto rewrite the cookbook.  We need some way to extract useful[0m
       [40mdata  from variables and leave out what we do not want.  For[0m
       [40mexample if we want to know what all  the  .c  files  in  the[0m
       [40mprog1  directory  are  we  just ask for all files that match[0m
       [40mprog1/%.c.  We could use the match_mask built-in function to[0m
       [40mextract the needed sublist of files.  Built-in functions can[0m
       [40mdo many other manipulations of our source tree contents  and[0m
       [40mhow  to  process  them.  In general I will introduce a given[0m
       [40mbuilt-in function as we encounter them.[0m

       [40mAs far as cook is concerned, for the  most  part,  functions[0m
       [40mand  variables are treated identically.  This means anywhere[0m
       [40mwhere you would use a variable you can use a  function.   In[0m
       [40mgeneral a function is called like this:[0m
            [40m[func arg1 arg2 ... argN][0m

       [40mFor example:[0m
            [40mname = [foobar aryeh];[0m


       [4m[40m11.[24m  [4mSource[24m [4mTree[24m [4mScanning[0m

       [40mThe  first  thing  we  need to do to automate the process of[0m
       [40mhandling new files is to collect the list of  source  files.[0m
       [40mIn  order  to do this we need to ask the operating system to[0m
       [40mgive us a list of all files in  a  directory  and  all  it's[0m
       [40msubdirectories.  In Unix the best way to do this is with the[0m
       [40mfind(1) command.  Thus to get a complete list of  all  files[0m
       [40min say the current directory we do:[0m
            [40mfind . -print[0m
       [40mor any variation thereof.[0m

       [40mGreat,  now how do we get the output of find into a variable[0m
       [40mso cook can use it.  Well, the collect function  does  this.[0m
       [40mWe  then  just  assign  the  results of collect to a list of[0m
       [40mfiles, build experts like to call  this  the  manifest.   So[0m
       [40mhere is how we get the manifest:[0m
            [40mmanifest = [stripdot[0m
                [40m[collect find . -print]];[0m

       [40mThat  is  all  nice  and  well but how do we get the list of[0m
       [40msource files in  prog1  only,  for  example.    There  is  a[0m
       [40mfunction  called  match_mask that does this.  The match_mask[0m
       [40mfunction returns all "words" that match some pattern in  our[0m
       [40mlist.   For  example  to  get  a list of all .c files in our[0m
       [40mproject we do:[0m

       
       Aryeh M. Friedman                                    Page 12





       Cook                                                Tutorial



       [40m     src = [match_mask %0%.c[0m
                [40m[manifest]];[0m
       [40mIt is fine to know what files are already in our source tree[0m
       [40mbut what we really want to do is find the list of files that[0m
       [40mneed to be cooked.  We use the fromto function to  do  this.[0m
       [40mThe  fromto  function  takes  all  the words in our list and[0m
       [40mtransforms all the names which match  to  some  other  name.[0m
       [40mFor  example  to  get  a list of all the .o files we need to[0m
       [40mcook we do:[0m
            [40mobj = [fromto %0%.c %0%.o[0m
                   [40m[src]];[0m
       [40mIt is rare that we need to know about the  existence  of  .c[0m
       [40mfiles  since  in  most  cases,  unless they are derived from[0m
       [40mcooking something else, they either exist  or  they  do  not[0m
       [40mexist.   In  the case of them not existing the .o target for[0m
       [40mthat source should fail.  For this reason we really  do  not[0m
       [40mneed  a  src  variable  at all.  Remember I mentioned that a[0m
       [40mfunction call can be used anywhere  a  variable  can.   This[0m
       [40mmeans  that  we  can do the match_mask call in the same line[0m
       [40mthat we do the fromto.  Thus the new statement is:[0m
            [40mobj = [fromto %0%.c %0%.o[0m
                   [40m[match_mask %0%.c[0m
                    [40m[manifest]]];[0m
       [40mTime  to  update  some  sections  of  our  sample  project's[0m
       [40mcookbook one more time:[0m
            [40m/* info about our files */[0m
            [40mmanifest =[0m
                [40m[collect find . -print];[0m
            [40mobj = [fromto %0%.c %0%.o[0m
                   [40m[match_mask %0%.c[0m
                    [40m[manifest]]];[0m
            [40m/* how to build each program */[0m
            [40mprog1_obj = [match_mask[0m
                [40mprog1/%.o [obj]];[0m
            [40mprog2_obj = [match_mask[0m
                [40mprog2/%.o [obj]];[0m
            [40mbin/%: [%_obj] lib/lib.a;[0m
            [40m/* how to build the library */[0m
            [40mlib_obj = [match_mask lib/%.o[0m
                [40m[obj]];[0m
            [40mlib/lib.a: [lib_obj];[0m

       [40mThe  important  thing  to  observe  here  is  that it is now[0m
       [40mpossible to add a source file  to  one  of  the  probram  or[0m
       [40mlibrary  directories  and  Cook  will  automagically notice,[0m
       [40mwithout any need to modify the cookbook.  It doesn't  matter[0m
       [40mwhether  there  are 3 files or 300 in these directories, the[0m
       [40mcookbook is the same.[0m


       [4m[40m12.[24m  [4mFlow[24m [4mControl[0m

       [40mIf there was no conditional logic in  programming  would  be[0m
       [40mrather pointless, who wants to write I program that can only[0m

       
       Aryeh M. Friedman                                    Page 13





       Cook                                                Tutorial



       [40mdo something once, the same is true in  cook.   Even  though[0m
       [40mthe  stuff  we  need to conditional in a build is often very[0m
       [40mtrivial as far as conditional logic goes, namely  there  are[0m
       [40mif  statements  and  the equivalent of while loops and thats[0m
       [40mall.[0m

       [40mIf statements are pretty straight forward.  If you are  used[0m
       [40mto  C,  C++, [4metc[24m, the only surprise is the need for the then[0m
       [40mkeyword.  Here is a example if statement:[0m
            [40mif [not [count [file]]] then[0m
                [40mecho no file provided;[0m
       [40mThe count function returns the number of words in the "file"[0m
       [40mlist  and  the  not  function  is true if the argument is 0.[0m
       [40mOther then that the if statement  works  much  the  way  you[0m
       [40mwould expect it to.[0m

       [40mCook has only one type of loop that being the loop statement[0m
       [40mand it takes no conditions.  A loop  is  terminated  by  the[0m
       [40mloopstop  statement  (like a C [4mbreak[24m statement).  Other then[0m
       [40mthat loops pretty much work the  way  you  expect  them  to.[0m
       [40mHere is an example loop:[0m
            [40m/* set the loop "counter" */[0m
            [40mlist = [kirk spock 7of9[0m
                [40mjaneway worf];[0m
            [40m/* do the loop */[0m
            [40mloop word = [list][0m
            [40m{[0m
                [40m/* print the word */[0m
                [40mecho [word];[0m
            [40m}[0m


       [4m[40m13.[24m  [4mSpecial[24m [4mVariables[0m

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

       [40mThere are other special variables described in the Cook User[0m
       [40mGuide.  You may want to look them up and use them  when  you[0m
       [40mstart writing more advanced cookbooks.[0m




       
       Aryeh M. Friedman                                    Page 14





       Cook                                                Tutorial



       [40m[4m14.[24m  [4mSuper[24m [4mSoft[24m [4mcoding[0m

       [40mNow  we  know  enough so we can make Cook handle building an[0m
       [40marbitrary number of programs in our  sample  project.   Note[0m
       [40mthe  following  example assumes that all program directories[0m
       [40mcontain a main.c file and no other  directory  contains  it.[0m
       [40mThe  best way to understand what is needed it to look at the[0m
       [40msample cookbook for this line by  line.   So  here  are  the[0m
       [40mrewritten sections of our sample cookbook:[0m
            [40m/* names of the programs */[0m
            [40mprogs = [fromto %/main.c %[0m
                     [40m[match_mask %/main.c[0m
                      [40m[manifest]]];[0m
            [40m/* top level target */[0m
            [40mall:[0m
                [40m[addprefix [ibin_dir]/[0m
                    [40m[progs]][0m
                [40m[prepost [idoc_dir]/ /manual[0m
                    [40m[progs]];[0m
            [40m/* how to build each program */[0m
            [40mloop prog = [progs][0m
            [40m{[0m
                [40m[prog]_obj = [match_mask[0m
                    [40m[prog]/%.o [obj]];[0m
            [40m}[0m
            [40mbin/%: [%_obj] lib/lib.a;[0m

       [40mThe  basic  idea is that we use a loop to create the list of[0m
       [40m.o files for all programs and then we use variable  variable[0m
       [40mnames to reference the right one in the recipe.[0m


       [4m[40m15.[24m  [4mScanning[24m [4mfor[24m [4mHidden[24m [4mDecencies[0m

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

       [40mOne  way  of  doing it is to scan each .c for #include lines[0m
       [40mand say any that are found represent "hidden"  dependencies.[0m
       [40mIt would be fairly trivial to create a shell script or small[0m
       [40mC program that does this.  Cook though has been nice  enough[0m
       [40mto  include program that does this for us in most cases that[0m
       [40mare not insanely non-trivial.  There are several methods  of[0m
       [40musing  c_incl  we will only cover the "trivial" method here,[0m
       [40mif you need higher performance refer to the Cook User Guide,[0m
       [40mit has a whole chapter on include dependencies.[0m


       
       Aryeh M. Friedman                                    Page 15





       Cook                                                Tutorial



       [40mThe  c_incl  program  essentially  just  prints  a  list  of[0m
       [40m#include files it finds in its argument.  To  do  this  just[0m
       [40mdo:[0m
            [40mc_incl [4mprog[24m.c[0m

       [40mNow  all  we  have to do is have Cook collect this output on[0m
       [40mthe ingredients list of our recipe and boom we have  a  list[0m
       [40mof  our  hidden dependencies.  Here is the rewritten portion[0m
       [40mof our sample cookbook for that:[0m
            [40m/* how to build each program and[0m
               [40minclude file dependencies */[0m
            [40m%0%.o: %0%.c[0m
                [40m[collect c_incl -api %0%.c];[0m

       [40mThe c_incl -api option means if the file doesn't exist, just[0m
       [40mignore it.[0m


       [4m[40m16.[24m  [4mRecipe[24m [4mActions[0m

       [40mNow that we have all the decencies soft coded all we have to[0m
       [40mdo actually build our project is to tell each recipe how  to[0m
       [40mactually cook the target from the ingredients.  This is done[0m
       [40mby adding actions to a recipe.  The actions are nothing more[0m
       [40m"simple"  statements  that  are  bound to a recipe.  This is[0m
       [40mdone by leaving off the trailing semicolon (;) on the recipe[0m
       [40mand  putting  the  actions inside { curly braces }.  This is[0m
       [40mbest shown by example.  So here is our  final  cookbook  for[0m
       [40mour sample project:[0m
            [40m/* where to install stuff */[0m
            [40mprefix = /usr/local;[0m
            [40midoc_dir = [prefix]/share/doc;[0m
            [40mibin_dir = [prefix]/bin;[0m
            [40m/* info about our files */[0m
            [40mmanifest =[0m
                [40m[collect find . -print];[0m
            [40mobj = [fromto %0%.c %0%.o[0m
                   [40m[match_mask %0%.c[0m
                    [40m[manifest]]];[0m
            [40m/* names of the programs */[0m
            [40mprogs = [fromto %/main.c %[0m
                     [40m[match_mask %/main.c[0m
                      [40m[manifest]]];[0m
            [40m/* top level target */[0m
            [40mall:[0m
                [40m[addprefix [ibin_dir]/[0m
                    [40m[progs]][0m
                [40m[prepost [idoc_dir]/ /manual[0m
                    [40m[progs]];[0m
            [40m/* how to build each program */[0m
            [40mloop prog = [progs][0m
            [40m{[0m
                [40m[prog]_obj = [match_mask[0m
                    [40m[prog]/%.o [obj]];[0m

       
       Aryeh M. Friedman                                    Page 16





       Cook                                                Tutorial



       [40m     }[0m
            [40mbin/%: [%_obj][0m
            [40m{[0m
                [40mgcc [%_obj] -o [target];[0m
            [40m}[0m
            [40m/* how to build the library */[0m
            [40mlib_obj = [match_mask lib/%.o[0m
                [40m[obj]];[0m
            [40mlib/lib.a: [lib_obj][0m
            [40m{[0m
                [40mrm [target];[0m
                [40mar cq [target] [lib_obj];[0m
            [40m}[0m
            [40m/* how to "install" stuff */[0m
            [40m[ibin_dir]/%: bin/%[0m
            [40m{[0m
                [40mcp bin/% [target];[0m
            [40m}[0m
            [40m[idoc_dir]/%/manual: doc/%/manual[0m
            [40m{[0m
                [40mcp doc/%/manual [target];[0m
            [40m}[0m
            [40m/* how to compile sources*/[0m
            [40m%0%.o: %0%.c[0m
                [40m[collect c_incl -api %0%.c][0m
            [40m{[0m
                [40mgcc -c %0%.c -o [target];[0m
            [40m}[0m


       [4m[40m17.[24m  [4mAdvanced[24m [4mFeatures[0m

       [40mEven  though  the  tutorial part of this document is done, I[0m
       [40mfeel it is important to just mention some advanced  features[0m
       [40mnot  covered  in  the tutorial.  Except for just stating the[0m
       [40mbasic nature of these features I will not go into detail  on[0m
       [40many given one.[0m

          [40m+o Platform   polymorphism.    This   is  where  Cook  can[0m
            [40mautomatically detect what platform you are  on  and  do[0m
            [40msome file juggling so that you build for that platform.[0m

          [40m+o Support  for  private  work  areas.  If you are working[0m
            [40mwithin a change management system, Cook  knows  how  to[0m
            [40mquery  it for only the files you need to work on.  This[0m
            [40mincludes the automatic  check-out  and  in  of  private[0m
            [40mcopies of those files.[0m

          [40m+o Parallel  builds.  For large projects it is possible to[0m
            [40mspread the build over several processors or machines.[0m

            [40mConditional recipes.   It  is  possible  to  execute  a[0m
            [40mrecipe  one  way  if  certain conditions are met and an[0m
            [40mother way if they are not.[0m

       
       Aryeh M. Friedman                                    Page 17





       Cook                                                Tutorial



       [40mMany more that are not directly supported by  Cook  but  can[0m
       [40measily be integrated using shell scripts.[0m


       [4m[40m18.[24m  [4mContacts[0m

       [40mIf  you  find  any  bugs  in this tutorial please send a bug[0m
       [40mreport to Aryeh M. Friedman <aryeh@m-net.arbornet.org>.[0m

       [40mThe Cook web site  is  http://www.canb.auug.org.au/~millerp-[0m
       [40m/cook/[0m

       [40mIf  you  want  to contact Cook's author, send email to Peter[0m
       [40mMiller <millerp@canb.auug.org.au>.[0m









































       
       Aryeh M. Friedman                                    Page 18


