


       



       .














                                   Cook





                                 Tutorial







                            Aryeh M. Friedman

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


































       .












       This document describes Cook version 2.30
       and was prepared 27 November 2021.






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

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

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

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

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


















       Cook                                                Tutorial



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


