----------------------------------------------------------------------- Pre Make Kit tutorial file Document revision: $Id: tutorial.txt 1161 2004-08-26 20:03:08Z xsa $ ----------------------------------------------------------------------- PMK tutorial 1) Introduction ------------ As make is using a makefile, the pmk binary is using a pmkfile. This file contains a set of commands that perform checks of source dependencies. Following to that, template files are processed to generate a final file after tags substitutions. In addition to this tutorial, we highly recommend to read the pmkfile(5) manpage. 2) The settings ------------ The first command you will find in a pmkfile is named SETTINGS. Its function is to define global settings. To begin we are going to use only one template for the makefile. See the following example: SETTINGS { TARGET = ("Makefile.in") } The TARGET option is taking a list as value. The list is delimited by parenthesis. As you can see Makefile.in will be the template name. 3) Defining some variables ----------------------- It can be useful to define some basic variables such as package name and version. For this, we will use the DEFINE command as following: DEFINE { PACKAGE = "hello" VERSION = "0.1" } If the target(s) file(s) contain(s) the @PACKAGE@ and @VERSION@ tags then they will be replaced by the variables values. 4) First check ----------- Lets say that we have a hello.c file that needs the string header. For that, we use the CHECK_HEADER command like following: CHECK_HEADER(header_string) { NAME = "string.h" } The NAME option contains the header to check. The string into brackets is the label of the command. If the check succeeds, this label is set as true else it is set as false. We'll see later how to use this label state into conditional command or as dependency with the DEPEND option. Now let's see what our actual pmkfile contains. First we create the template with the following commands: echo "PACKAGE=@PACKAGE@" > Makefile.in echo "VERSION=@VERSION@" >> Makefile.in You can now type 'pmk'. The resulting output should look like this: __[OUTPUT]____________________________________________________[BEGIN]__ Processing commands : * Parsing settings Collecting targets : Added 'Makefile.in'. Total 1 target(s) added. * Parsing define defined 'PACKAGE' variable. defined 'VERSION' variable. * Checking header [header_string] Use C language with CC compiler. Store compiler flags in 'CFLAGS'. Found header 'string.h' : yes. Process templates : Created '/home/mips/tmp/Makefile'. __[OUTPUT]______________________________________________________[END]__ And the generated Makefile should look like: __[FILE]______________________________________________________[BEGIN]__ PACKAGE=hello VERSION=0.1 __[FILE]________________________________________________________[END]__ 5) Using external libraries ------------------------ Our hello program also needs the curses library. To check if this library is present we will use the following command: CHECK_LIB(lib_curses) { NAME = "curses" } Which gives as result: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking library [lib_curses] Use C language with CC compiler. Store library flags in 'LIBS'. Found library 'curses' : yes. __[OUTPUT]______________________________________________________[END]__ We could also check for a specific function in the library: CHECK_LIB(lib_curses) { NAME = "curses" FUNCTION = "getwin" } So you'll get the following: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking library [lib_curses] Use C language with CC compiler. Store library flags in 'LIBS'. Found function 'getwin' in 'curses' : yes. __[OUTPUT]______________________________________________________[END]__ The linker flags are automatically set in the default LIBS variable. We'll see later how to use an alternate variable. 6) Playing with pkg-config support ------------------------------- Let's say that our package has the gtkhello.c source that builds a gtk version of our hello program. That said, we need to check if gtk is present on the system. We have two possibilities to do that: first by using pkg-config or the second one by checking the library. We are going to learn how to check gtk via pkgconfig support: CHECK_PKG_CONFIG(pc_gtk) { NAME = "gtk+" } If gtk is present you should get the following output when running pmk: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking pkg-config module [pc_gtk] Found package 'gtk+' : yes. Stored compiler flags in 'CFLAGS'. Stored library flags in 'LIBS'. __[OUTPUT]______________________________________________________[END]__ We could also check for a minimal version of gtk needed by the program. For example we could look for at least 1.2 version of gtk: CHECK_PKG_CONFIG(pc_gtk) { NAME = "gtk+" VERSION = "1.2" } And the related output: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking pkg-config module [pc_gtk] Found package 'gtk+' : yes. Found version >= 1.2 : yes (1.2.10). Stored compiler flags in 'CFLAGS'. Stored library flags in 'LIBS'. __[OUTPUT]______________________________________________________[END]__ As you can see the compiler and linker flags are automatically stored in the default CFLAGS and LIBS variables. You can provide alternate variables with the CFLAGS and LIBS options like following: CHECK_PKG_CONFIG(pc_gtk) { NAME = "gtk+" VERSION = "1.2" CFLAGS = "GTK_CFLAGS" LIBS = "GTK_LIBS" } And you'll get: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking pkg-config module [pc_gtk] Found package 'gtk+' : yes. Found version >= 1.2 : yes (1.2.10). Stored compiler flags in 'GTK_CFLAGS'. Stored library flags in 'GTK_LIBS'. __[OUTPUT]______________________________________________________[END]__ Notice that the storage variables have changed in the output. 7) Using switches -------------- Now we are able to detect gtk we could use a switch to specify if we want the gtk version or not. For this we will use the SWITCHES command and add it between SETTINGS AND DEFINE: SWITCHES { build_gtk_version = FALSE } By default we don't want the gtk version to be build so it's set to FALSE. A switch can be modified by using -e (enable) and -d (disable) options, see pmk(1). For example we could use the following to enable the build of the gtk program: pmk -e build_gtk_version We are going to see the two methods of using switchs. First using the DEPEND option: CHECK_PKG_CONFIG(pc_gtk) { NAME = "gtk+" DEPEND = ("build_gtk_version") VERSION = "1.2" CFLAGS = "GTK_CFLAGS" LIBS = "GTK_LIBS" } As you can see we added 'build_gtk_version' in the dependency list of the DEPEND option. A dependency can be a command label or a switch. If all the dependencies in the list are true then the command is executed. Else if the test is required pmk fill fail like this: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking pkg-config module [pc_gtk] Required 'build_gtk_version' dependency failed. __[OUTPUT]______________________________________________________[END]__ Another method to use switches is the IF conditional command. This will look like the following: IF(build_gtk_version) { CHECK_PKG_CONFIG(pc_gtk) { NAME = "gtk+" VERSION = "1.2" CFLAGS = "GTK_CFLAGS" LIBS = "GTK_LIBS" } } Of course you can put as many commands as you want into the body. The ouput looks like the following: __[OUTPUT]____________________________________________________[BEGIN]__ < Begin of condition [build_gtk_version] * Checking pkg-config module [pc_gtk] Found package 'gtk+' : yes. Found version >= 1.2 : yes (1.2.10). Stored compiler flags in 'GTK_CFLAGS'. Stored library flags in 'GTK_LIBS'. > End of condition [build_gtk_version] __[OUTPUT]______________________________________________________[END]__ It's also possible to negate a value by appending a '!' above the dependency like this: IF(!build_gtk_version) { [commands] } or like this: DEPEND = ("!build_gtk_version") 8) Checking types -------------- As an example we will check for the ISO C99 boolean type: _Bool. See the following: CHECK_TYPE(type_bool_isoc) { NAME = "_Bool" } As my compiler is not fully ISO C99 compliant i get an error: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking type [type_bool_isoc] Use C language with CC compiler. Found type '_Bool' : no. Error : failed to find type '_Bool'. __[OUTPUT]______________________________________________________[END]__ This test could be useful to detect compilers that are ISO C99 compliant. For those which are not we could include an external definition of this type. That said, pmk is exiting with an error by default if a check fails. To avoid this we'll use the REQUIRED option: CHECK_TYPE(type_bool_isoc) { REQUIRED = FALSE NAME = "_Bool" } And it gives the following: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking type [type_bool_isoc] Use C language with CC compiler. Found type '_Bool' : no. __[OUTPUT]______________________________________________________[END]__ Now let's see how to use this test. First we must create an header that will contains the tags. Here is isoc_compat.h.in: __[FILE]______________________________________________________[BEGIN]__ /* ISO C compatibility header */ /* _Bool type */ @DEF___BOOL@ __[FILE]________________________________________________________[END]__ Now add this file in the target list: SETTINGS { TARGET = ("Makefile.in","isoc_compat.h.in") } The definition tag is easy to build, it's "DEF__" followed by the string of the type in uppercase: _Bool gives _BOOL. Now see the result: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking type [type_bool_isoc] Use C language with CC compiler. Found type '_Bool' : no. Process templates : Created '/home/mips/tmp/pmk_tutor/Makefile'. Created '/home/mips/tmp/pmk_tutor/isoc_compat.h'. End of log __[OUTPUT]______________________________________________________[END]__ And the isoc_compat.h file: __[FILE]______________________________________________________[BEGIN]__ /* ISO C compatibility header */ /* _Bool type */ #undef HAVE__BOOL __[FILE]________________________________________________________[END]__ The @DEF___BOOL@ has been replaced by "#undef HAVE__BOOL". Now we can add the code to include the definition of _Bool type if it does not exist. __[FILE]______________________________________________________[BEGIN]__ /* ISO C compatibility header */ /* _Bool type */ @DEF___BOOL@ #ifndef HAVE__BOOL typedef unsigned char _Bool; #endif __[FILE]________________________________________________________[END]__ Which results in the following on my system: __[FILE]______________________________________________________[BEGIN]__ /* ISO C compatibility header */ /* _Bool type */ #undef HAVE__BOOL #ifndef HAVE__BOOL typedef unsigned char _Bool; #endif __[FILE]________________________________________________________[END]__ Mission succeeded ! 9) Looking for a binary ? ---------------------- Easy ! Need to know if strip program is available ? Use the following: CHECK_BINARY(bin_strip) { NAME = "strip" VARIABLE = "STRIP_PATH" } and add the tag in the Makefile.in template: __[FILE]______________________________________________________[BEGIN]__ PACKAGE=@PACKAGE@ VERSION=@VERSION@ STRIP_PATH=@STRIP_PATH@ __[FILE]________________________________________________________[END]__ Now run pmk: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking binary [bin_strip] Found binary 'strip' : yes. __[OUTPUT]______________________________________________________[END]__ And look at Makefile: __[FILE]______________________________________________________[BEGIN]__ PACKAGE=hello VERSION=0.1 STRIP_PATH=/usr/bin/strip __[FILE]________________________________________________________[END]__ Well we got it :) 10) Checking a variable ------------------- As an example we'll take the case of a program that will run only on 'little endian' byte ordered CPUs. For that matter we'll check the HW_BYTEORDER variable set by pmksetup: CHECK_VARIABLE(var_endian) { NAME = "HW_BYTEORDER" VALUE = "LITTLE_ENDIAN" } On big-endian machines the check will fail with an error: __[OUTPUT]____________________________________________________[BEGIN]__ * Checking variable [var_endian] Found variable 'HW_BYTEORDER' : yes. Variable match value 'LITTLE_ENDIAN' : no. Error : variable value does not match ('LITTLE_ENDIAN' != 'BIG_ENDIAN'). __[OUTPUT]______________________________________________________[END]__ 11) Shared library support ---------------------- The first step is to detect the compiler used. We do that with the DETECT option of the SETTINGS command: SETTINGS { TARGET = ("Makefile.in","isoc_compat.h.in") DETECT = ("CC") } In this example we detect the C compiler but we could have detected the C++ compiler by using DETECT=("CXX"). You can also detect both compilers with DETECT=("CC","CXX"). The output should look almost like the following: __[OUTPUT]____________________________________________________[BEGIN]__ * Parsing settings Collecting targets : Added 'Makefile.in'. Added 'isoc_compat.h.in'. Total 2 target(s) added. Detecting compilers : Gathering data for compiler detection. Detecting 'CC' : GNU gcc (version 295). Setting SLCFLAGS to '-fPIC' Setting SLLDFLAGS to '-shared' __[OUTPUT]______________________________________________________[END]__ In this example the compiler used is gcc (version 2.95). As you can see the shared library flags (-fpic and -shared) are set in the reserved variables. Now we're going to see how to build the name of the shared library using the BUILD_SHLIB_NAME command: BUILD_SHLIB_NAME { NAME = "hello" MAJOR = "2" MINOR = "1" VERSION_NONE = "LIBNAME" VERSION_MAJ = "LIBNAME_MAJ" VERSION_FULL = "LIBNAME_FULL" } This will give the following output: __[OUTPUT]____________________________________________________[BEGIN]__ * Building shared library name Shared library support : yes. Setting LIBNAME to 'libhello.so' Setting LIBNAME_FULL to 'libhello.so.2.1' Setting LIBNAME_MAJ to 'libhello.so.2' __[OUTPUT]______________________________________________________[END]__ To use all this stuff we could use a template of makefile like following: __[FILE]______________________________________________________[BEGIN]__ PACKAGE=@PACKAGE@ VERSION=@VERSION@ STRIP_PATH=@STRIP_PATH@ CFLAGS=@CFLAGS@ LIBS=@LIBS@ SLCFLAGS=@SLCFLAGS@ SLLDFLAGS=@SLLDFLAGS@ LIBNAME=@LIBNAME@ LIBNAME_MAJ=@LIBNAME_MAJ@ LIBNAME_FULL=@LIBNAME_FULL@ libhello.o: $(CC) $(CFLAGS) $(SLCFLAGS) -c libhello.c $(LIBNAME): libhello.o $(CC) $(SLLDFLAGS) -o $@ libhello.o cp $(LIBNAME) $(LIBNAME_MAJ) cp $(LIBNAME) $(LIBNAME_FULL) __[FILE]________________________________________________________[END]__ And after running pmk we should obtain a makefile like this one: __[FILE]______________________________________________________[BEGIN]__ PACKAGE=hello VERSION=0.1 STRIP_PATH=/usr/bin/strip CFLAGS=-O2 -I/usr/include LIBS=-lcurses SLCFLAGS=-fPIC SLLDFLAGS=-shared LIBNAME=libhello.so LIBNAME_MAJ=libhello.so.2 LIBNAME_FULL=libhello.so.2.1 libhello.o: $(CC) $(CFLAGS) $(SLCFLAGS) -c libhello.c $(LIBNAME): libhello.o $(CC) $(SLLDFLAGS) -o $@ libhello.o cp $(LIBNAME) $(LIBNAME_MAJ) cp $(LIBNAME) $(LIBNAME_FULL) __[FILE]________________________________________________________[END]__ TO BE CONTINUED ...