QEF home page

Chapter 4: The QEF Tutorials

The Guide's Table of Contents Previous Chapter Bottom Of Page Next Chapter

The three qef tutorials in this chapter cover building "Hello, world" with qef, the structure of a qeffile, adding files to a project, installing a project, building multiple directories, qvrs, qdsrv, and other processes. In addition to the tutorials, there are some miscellaneous notes at the end of the chapter that may be helpful.


This chapter describes three qef tutorials. Each tutorial introduces new concepts and approaches to the software construction problem. Work through all the tutorials in sequence.

The tutorials are:

  1. Tutorial 1: an introduction to qef, building the most basic application ("Hello, world") in place.
  2. Tutorial 2: an introduction to working trees, configuration files, and bulk file removal.
  3. Tutorial 3: a real-world application, including remote directories, version-control software, release numbering and distributions.
Note: www.qef.com contains a set of pages entitled "A Cook's Tour of the QEF Software Process Automation System" which presents an advanced tutorial about the QEF system, but in far more detail that can be managed without a gentler introduction.
Note:Throughout these tutorials we will use "~" to indicate the user's home directory. If appearing in an actual shell command, the "~" can be taken literally, however, in all other instances the "~" will be replaced by the user's home directory path.

4.1) Tutorial 1

In this first tutorial, we look at the actual construction of a program using the ubiquitous "Hello, world" program that is everyone's introduction to the C language. Our focus is on the directory it is in, the call to the C compiler, the link phase, and the resulting binary. Using this simple example, we will see how qef affects the process, and will introduce a few basic qef concepts and operations.

The first step is to install the tutorial. To do this, use the commands:

    % dirsetup -d ~/tut/tut1 qeftut1 # install the tutorial
    % cd ~/tut/tut1

The dirsetup tool creates the specified directories (tut and tut/tut1) and passes it the name of a script, qeftut1, which creates the three files comprising this tutorial: README contains a thumbnail description of this tutorial; hello.c the project source; and qeffile a simple qef configuration file for this project.

In this simple example there is only one configuration file for the project, qeffile. (There is a second configuration file involved, sysnm.vrs, that defines the settings for the platform; it is installed once on each platform.) Obviously, in more complex projects, there may be thousands of source files in hundreds of directories, as well as numerous configuration files.

The Qef Command

First, run qef and see what happens. The command is:

    % qef
    #{ @host <date> <time>	# header line
    # QEFHALTFILE: ~/%qef99999a.hlt # Location of the Qef HaltFile
    New:	hello.c	# new source file detected
    Warning: "qmkhist._" does not exist -- everything will be
	redone See warnMimk1(x-qmisc)
    + cc -c hello.c	# compile hello
    + cc hello.o -o hello	# link binary
    #} E0 host@~/tut/tut1 <date> <time>(<elapsed-time>)

The output is delimited by #{ and #}. qef writes the machine name, the start and completion times of the job, the elapsed time and finally the exit status of the job to the output stream. The header and trailer lines are generated at run-time and will vary.

The HaltFile, mentioned in the above example, exists as a way of gently halting qef, qsh, or mimk. Each process checks for its existence and writability after every forked process exits. If at any time it does not exist or it is not writable, then that job exits. By using the HaltFile instead of simply killing the process, various cleanup and exit operations can be performed. During a qef run, the HaltFile also contains a trace of the individual QEF processes, which is useful when running large project builds.

QEF examines the contents of the directory and finds a new source file, qef then invokes the C compiler twice, once to create the object file and again to create the binary.

Now remove the binary and run qef again:

    % rm hello
    % qef
    #{ @<machine name> <date> <time>
    # QEFHALTFILE: ~/%qef99999a.hlt
    + cc hello.o -o hello
    #} E0 host@~/tut/tut1 <date> <time>(#)

Like make with a standard script, qef determines that the object file hello.o is up to date, and does not bother recompiling hello.c. It simply invokes cc to link the binary.

In addition to the hello* files, a number of other administration files have been created:

incls._ A cache of the list of include files referenced in the source files; the dependency list for the source.
qmkhist._ The history file of mimk builds -- what was built, when and how (e.g. what compiler flags were used, the full source path name, etc.)
srclist._ The list of source files found in this directory.

These files are typically created each time qef is run, although their creation can be suppressed by changing some configuration options.

How did qef know to construct the binary? Before answering this question, let us examine the configuration file, qeffile.

Structure of a qeffile

The qeffile contains:

    # Section 1: qvrs settings
    set Project Tutorial1
    # Additional qvrs settings can go here
    # End Section 1

Begin
# Section 2: script to run
commands @argv
# Additional scripts to run
# End Section 2

Lines beginning with # are comments. As the comments indicate, a qeffile consists of 3 units:

  • qvrs settings: this section allows configuration information specific to this qeffile or directory to be specified before running the script. Any variable defined here is available to the other tools such as the script generator.
  • the Begin line: starts with the word "Begin" and usually names the script generator to run, plus any desired arguments to the script generator. In practice, the script generator is almost always either qsg or qefdirs. Actually most Begin lines do not have any arguments -- the default value, which is usually qsg --M, is used.
  • the input script: usually input to the script generator.

In this particular script, Section 1 consists of a comment followed by a single command, "set Project Tutorial1". The set command assigns the value "Tutorial1" to the variable Project. Incidentally, this set command is not required; it is there for illustration. Many qeffiles have nothing in Section 1.

Section 2 consists of a single line that invokes a routine called commands contained in the qsg library. This is one of the standard qsg library scripts included in the product. The @argv is the argument list passed to the script, which in this case (due to the --M flag) will be the list of source files for the directory. More detail about argv can be found by typing x-qsg argv.

The output of this invocation of qsg is a script, which when processed by the qef pre-processor will be a mimk input file. mimk is run to interpret the processed script, resulting in the product's construction.

Listing the Sub- Commands

qef runs a number of other tools as part of its processing. For example, incls is used to create the dependencies.

Normally, the listing of the sub-commands is suppressed, but you can override this with the --v (verbose) flag, as in:

    % qef -v
    #{ -v @host <date> <time>
    # qvrs -Bp -LQefArgs= -o/tmp/qv_12345a.tmp
    # QEFHALTFILE: ~/tut/tut1/%qef12345a.hlt
    # cwd=~/tut/tut1
    # sls -Ls
    # qsg -M
    # incls -MUS
    # mimk -f /tmp/qefa12345
    #} E0 host@~/tut/tut1 <date> <time>(#)

The commands run by qef in this directory are:

  • qvrs: establishes the QEF environment variables
  • sls: creates the list of source files to process (source list). In a multi-tree environment, this command also handles the viewpathing.
  • qsg: the principal script generator
  • incls: creates the dependency list for the source files.
  • mimk: executes the set of commands necessary to build the product.
Building Debugging Versions

To build the project with the compiler's debug flag turned on, run qef flag, --LDEBUGGING. Actually, this is just temporarily setting a qvrs variable called DEBUGGING to 1. Any qvrs variable can be set using the -L flag and the default value is 1.

    % qef -LDEBUGGING
    #{ -LDEBUGGING @host <date> <time>
    # QEFHALTFILE: ~/<filename>.hlt
    + cc -c -g hello.c
    + cc -g hello.o -o hello
    #} E0 host@~/tut/tut1 <date> <time>(#)

When the debugging flag is turned on, all object files compiled without debugging become obsolete. qef recognizes this, since the recipes have changed, and rebuilds the system completely.

Adding Files to the Project

In the life of a project, source files are added, modified and sometimes removed. Traditionally this has involved maintenance of the project's make scripts -- with all the attendant opportunities for error, such as dependency omitted in the script, dependency undeclared, omitted from the link statement, file moved, file renamed, wrong include file (<> vs. ""), and so on. qef eliminates hand-written make scripts, generating them instead. New source files (or moved files) usually require no extra script maintenance -- simply create the files or move them into place.

Adding a Header File

To demonstrate this, you will add an include file to hello.c, then rebuild the project.

Create the file hello.h, containing one text line:

    #define MSG_HELLO "Hello, world\n"

Modify hello.c, replacing the literal text with the MSG_HELLO, and including the new header file. Note, include the new header file as <hello.h>, not "hello.h":

    #include <stdio.h>
    #include <hello.h>	/* note: not "hello.h"*/

main() { printf( MSG_HELLO ) ; exit(0); }

Now rebuild the program:

    % qef -LDEBUGGING
    #{ -LDEBUGGING @host <date> <time>
    # QEFHALTFILE: ~/%qef19717a.hlt
    # qvrs -Bp -LDEBUGGING -LQefArgs= -o/tmp/qv_19717b.tmp
    # cwd=~/tut/tut1
    # sls -Ls	create source database
    # qsg -M	produce script
    # incls -MUS	produce dependency list
    # mimk -f /tmp/qefa00879	run the back end
    + cc -c -g hello.c
    hello.c:2:hello.h: No such file or directory
    *** Error code 1
    + unlink hello.o
    Warning: could not remove hello.o
    Fatal error - waiting for active jobs to complete.

Mimk: Fatal error during construction #} E1 host@~/tut/tut1 <date> <time>(#)

The problem here is that hello.h is not visible to the compiler, due to how it is referenced in hello.c.

This #include statement could have used quotation marks rather than angle brackets, but in practice, this style of reference leads to other problems -- see #includes(x-qmisc).

Extending Include Search Path

A better approach is to add the current directory to the include path. A convenient place to do this is in Section 1 of qeffile. Edit this file and add the following line to the qeffile before the line containing Begin:

    addpath InclPath @SrcPath

The addpath is a qvrs command, and InclPath holds a list of include directories. The line adds the value of SrcPath to the list in InclPath.

Save the file and run qef again:

    % qef -LDEBUGGING

Now the source compiles successfully and the binary is rebuilt. Note the --I flags added to the cc commands.

Querying the History File, qmkhist._

The file qmkhist._ records each build performed on the current project. This file contains information about what was built, when it was built and why. The qef system includes a tool, qhy, that allows you to query this information for constructed files.

    % qhy hello.o
    hello.o  2000/02/25 01:42:51 ->
        hello.o (2000/02/25 01:42:46)
            < ~/tut/tut1/hello.c (2000/02/25 01:42:48)
            < hello.c (2000/02/25 01:42:48)
            + cc -c hello.c

The output shows you the current timestamp for hello.o, its timestamp prior to being built, the timestamp of its prerequisite hello.c and finally the command that was used to construct the file.

Adding a New Source File

The second type of change frequently made to a construction is the addition of new sources. In the typical make environment, this involves changing all the relevant make scripts to include the new file(s). qef automates this process.

For simplicity's sake, instead of creating a new file simply copy hello.c:

    % cp hello.c newfile.c
    % qef -LDEBUGGING
    #{ -LDEBUGGING @host <date> <time> 
    # QEFHALTFILE: ~/tut/tut1/%qef19724b.hlt
    New:	~/tut/tut1/newfile.c
    + cc -c -g -I. newfile.c	compile newfile.c
    + cc -g newfile.o -o newfile	create newfile binary
    #} E1 host@~/tut/tut1 <date> <time>(#)

There are now two binaries, hello and newfile. qef processes the new source file automatically. The "commands @argv" line includes newfile.c. Since the hello product is up-to-date, it was not rebuilt.

This illustrates some important concepts behind qef:

  • Directories are assumed to contain relevant sources.
  • Usually scripts do not need to be altered when source files are added or removed.

In fact, not all files in the directory are treated as source, only files with specific extensions. The list of relevant extensions is specified by the qvrs variable @Suffixes or its default value as applied by sls.

The Commands Script

Of all qsg scripts, commands is the second most frequently used. Its generated targets include the program, its debugging, purify, profile, and quantify versions, and many other commonly required products for C, yacc, lex, shell, man and other languages. It can be extended to handle new files as required. (For a directory in which all of the files go into a single command, you would use the program script.)

Other Useful Commands

qef offers a number of other useful options, including the following:

Forcing Rebuild of a Target To force the rebuilding of a target one uses the --F flag as in:
    % qef -F hello
    #{ -F hello @host 2000/02/25 00:48:30
    # QEFHALTFILE: /g/dt/tut/tut1/%qef19802b.hlt
    + cc -c hello.c
    + cc -o hello hello.o
    #} E0 host@~/tut/tut1 2000/02/25 00:48:30(0)
This flag is passed to mimk which will force all prerequisites and targets to be assumed to be out of date.
Checking What Will Happen To check what will be done, but not do it, one can use the --n flag, as in:
    % rm hello ; qef -n # rm hello and check what is to be done
    #{ -n @host 2000/02/25 00:54:39
    # QEFHALTFILE: ~/tut/tut1/%qef19824b.hlt
    + cc -o hello hello.o
    #} E0 host@~/tut/tut1 2000/02/25 00:54:39(0)
Note that the recipe to rebuild hello is listed since hello does not exist. However, hello.o's recipe is not shown, since hello.o is up-to-date.
Showing Target's Recipe The --n and --F flags can be combined to show the recipes for the selected targets, as in:
    % qef -nF hello~I
    #{ -nF hello~I @host 2000/02/25 01:01:28
    # QEFHALTFILE: ~/tut/tut1/%qef19895b.hlt
    + cc -c hello.c
    + cc -o hello hello.o
    + instal -E -d @NotSet/bin/hello hello  # more later
    #} E0 host@~/tut/tut1 2000/02/25 01:01:28(0)
Given the --nF flags, mimk assumes that all targets and their prerequisites are out of date and shows what would be done, without actually doing it. Note the use of the pseudo target hello~I which is an alias for the installation of hello.
Generating Assembly Code

To generate the assembly code for a particular file, use a command of the form

    % qef hello.s
Creating a Dependency List

This process occurs automatically during a qef run, but on occasion you may wish to create a list of inclusions for some specific source file. You can do so with this command:

    % incls hello.c

To produce slightly different output, call the incls program with the --l option:

    % incls -l hello.c

To produce the dependency list in the format preferred by make, use the --m option:

    % incls -m hello.c

If you have Purify and/or Quantify use hello_p and hello_q to request the associated binaries.

Creating the Object Files

Because qef uses techniques which are more sophisticated than timestamps to keep track of whether the product is up-to-date, some tricks that work with make do not work with qef. For example, try removing the object file hello.o and then running qef again. With make, this would cause the product hello to be produced again. Not with qef:

    % rm hello.o
    % qef
    #{ @host <date> <time>header line
    #} E0 host@~/tut/tut1 <date> <time>(#)

The target hello is deemed up-to-date whether or not hello.o exists. hello.o will be built if requested explicitly or implicitly as part of the Objects target. To build the object files for the directory, use the target Objects:

    % qef Objects
    #{ Objects @host <date> <time>header line
    + cc -c newfile.c
    + cc -c hello.c
    #} E0 host@~/tut/tut1 <date> <time>(#)
Installing the Product

Now let us attempt to install the product of this project. The standard qef target for this is Install:

    % qef Install
    #{ Install @host <date> <time>
    + cc -c -I. hello.c 
    + cc -c -I. newfile.c 
    + cc newfile.o -o newfile 
    + cc hello.o -o hello 
    + instal -d @NotSet/bin/newfile newfile 
    + instal -d @NotSet/bin/hello hello
    instal: @NotSet/bin/newfile: will not install @NotSet files
     *** Error code 255
    instal: @NotSet/bin/hello: will not install @NotSet files
     *** Error code 255
    Fatal error - waiting for active jobs to complete.

mimk: Fatal error during construction #} E1 host@~/tut/tut1 <date> <time>(#)
Note:There are two error messages, because mimk ran the two instal commands in parallel. While this adds to efficiency it makes the job of debugging problems based solely on the output problematic because the output is mixed. In order to avoid this, the --m1 flag can be used. This will run the same jobs, but sequentially.

The instal program failed as it will not install a file whose pathname begins with "@NotSet".

The destination directory is specified by the qvrs variable _DestDir_. Given its special use, if _DestDir_ is not explicitly set, qvrs sets it to @NotSet.

To set _DestDir_ you could add the corrective line to qeffile, but in practice this is not a good idea. One of the goals of qef is to avoid changing configuration data just to perform some local twiddling or to add a variable definition. Rather than change qeffile, create a new file called leaf.vrs, consisting of the following line:

    cset _DestDir_ @(home)/tut/tut1
Note:The cset keyword sets the value of a qvrs variable, but only if the variable is not already set.

The effects of leaf.vrs are restricted to the current directory. It is intended as a temporary file to change the configuration in the current directory. For example, in a larger project, you may want to temporarily compile with the debugging option in only a portion of the project, or to add a path to the library path only for the current directory. You would never make permanent changes in leaf.vrs, and you would never set _DestDir_ there. We use it here just to demonstrate a qvrs setting. The correct way to set _DestDir_ is discussed in the next tutorial.

You can now install the project successfully:

    % qef Install

The installation proceeds correctly. The sub-directory ~/tut/tut1/bin is created, and the binaries hello and newfile are installed there. The default directory for programs is _DestDir_/_BinDir_ where _BinDir_ defaults to bin.

Viewing a Build Script

Sometimes you may wish to see the mimk script without actually running it. (Typically, you would do this to investigate the unexpected inclusion or exclusion of files.) To do so, run the command:

    % qef -Pscript
    #:Qef=qsg -M
    #srcmap /ph/dt/tut/tut1/hello.c
    #include mimkvars.qh
    #include deps.qh
    
#define _Main_ hello Objects:V: hello.o hello.o:P: _S_(hello.c) _Touch_(cc) _T_cc -c _F_cc _F_cc[hello.c] _F_cc_c _F_cc_c[hello.c] \ _Optimize_(hello.c) _Threads_(hello.c) \ _CcFlags_(hello.c) _D_cc _D_cc[hello.c] \ _InclFlags_() _A_(hello.c) ... balance of output elided

Note the script contains qef macros and variables: _S_(), _Touch_(), _T_cc, etc. See var(x-qefpp) for a description of any macro var. The names of development tools, as well as the arguments they accept, are set using such variables. Two special forms: _T_prog and _F_prog are of particular importance:

  • tools are defined by variables beginning with "_T_", such as _T_cc;
  • flags are defined by parallel variables beginning with "_F_", such as _F_cc.
  • flags specific to a particular file use an associative array, as in _F_cc[file.c]. This specifies flags to cc that are unique to file.c It should be noted that if there is a setting _F_cc[pat] in which pat matches file.c, that value would be used, unless _F_cc[file.c] is specified.
  • The macros _Optimize_(), _Treads_(), _CcFlags_(), etc. are additional controls that should be be used to specify global and file specific settings, however, a full explanation is beyond the scope of this tutorial. But as an example, consider the following qvrs settings:
        set _Optimize_index_ Max # probably in global
    		# settings file
        ...
        # In the hello.c's directory's qeffile
        if ! @(options DEBUGGING)
    	set _Optimize_index_(hello.c) Safe
        fi

    Given the above, unless _Optimize_index_ is redefined, the maximum optimization flags will be used for all C files, other than hello.c which will be compiled with the safe flags. The _Optimization_(), _CcFlags_(), _LdFlags_(), and _Threads_() are all macros for which classes of flags are defined and then selected by an index set for a specific file or set of files matched by a pattern, or globally. (I did warn you it was complicated -- but useful).

Targets

Targets are the possible products of a given construction, i.e., items you can ask the system to build for you. For a list of the possible targets, use the command:

    % qef -Ptargets
    Local:       do local constructions
    Install:     do installations
    RemoveInst:  remove objects installed into other directories
    RemoveObjs:  remove objects in local directories
    RemoveAll:   remove installed and local objects
                 Any of above can be followed by `List' which
                 suppresses removals and leaves list in place.
    Man:         install manual sections
    Objects:     produce all *.o files
    <module>.o:  produce object file for module
    <module>.i:  produce preprocessed C code for module.[cly]
    Sfiles:      produce *.s files
    <module>.s:  produce assembly code for module.[cly]
    <module>~I:  do Install actions for module

Individual selectable items:
hello hello~I newfile newfile~I

These are the targets offered by the commands script. The list covers all the typical outputs desired by a developer. To support new targets, you would revise the commands script and add the new targets, but that is beyond the scope of this tutorial and will be covered later.

Before going further, you may wish to experiment with qef, building the various targets and removing part(s) of the system.

Summary
You now know how to run qef and how to indicate which of the possible targets you wish to create.

You have examined the configuration file qeffile, and learned about its structure.

By applying the --v(erbose) flag, you have seen how qef calls its subordinate tools when performing the construction. So far, you have seen only a few of the subordinate tools, but they serve important purposes.

qvrs establishes the construction environment, which is not dependent upon simple shell variables, but instead maintains a database and uses it when creating scripts. Thus it steps around both the limitations of shell variables and the isolation of construction information embedded in make scripts. In practice, this means that all your development tools will have all the information they require when they require it.

sls is an ls-like program that understands the qef environment. It searches the source path defined in a project's configuration files, and can produce a listing of source files that spans the directories of the source search path.

qsg is one of the two script generators used in almost all cases. (The other is qefdirs.)

incls is used to generate a list of the included files required either for a specific file or for an entire project. It is exhaustive, walking the list of files recursively until all references have been resolved.

mimk is qef's version of make. It differs from typical make programs in two important ways. First, since its input scripts are generated, mimk has no need and therefore no facility for variables. Secondly, it uses much more advanced consistency checking mechanisms, much better than make's simplistic and insufficient "out-of-date" rule. It also maintains a record of all information used in commands it executes, including the reason for the last build.

Besides building the desired products, qef produces or maintains a variety of files:
incls._ A cache of the list of include files referenced in the source files; the dependency list for the source
qmkhist._ The history file of mimk builds
srclist._ The list of source files in the current directory
/.../incls.{dir,pag} These files are created if the qvrs variable InclsDbm is defined. They constitute kdbm database that contains a list of the symbolic includes for scanned files and the time stamp of the file when it was last scanned. This is used to eliminate scanning files for dependencies more than once within a build session no matter how large that project.

This concludes the first tutorial; the second tutorial builds on the basics covered here, extending them to more interesting cases.

4.2) Tutorial 2

In the second tutorial, you will use several more of qef's command options, and examine the contents of the various configuration (qvrs) files. The following concepts are introduced:

  • tree types
  • mirror trees
  • bulk removal of temporary files
  • controlling the construction environment with qvrs

To install the second tutorial, use the commands:

    % dirsetup -d ~/tut/tut2/src qeftut2
    - create ~/tut/tut2/src/README
    - create ~/tut/tut2/src/QefAdm/confvrs/confdefl.vrs
    - create ~/tut/tut2/src/hello.c
    - create ~/tut/tut2/src/qeffile
    - create ~/tut/tut2/src/root.vrs
    % cd ~/tut/tut2/src

This will create the appropriate directories and files and then change to the new source directory.

Exploring the Project

This tutorial's source is much the same as that for the first tutorial, with the addition of a directory QefAdm/confvrs and two files:

QefAdm/confvrs/confdefl.vrs
The prototype and default user configuration file
root.vrs
The configuration file that identifies the root of a project tree and contains configuration information about that tree. It also links sets of project trees together.
The other difference is that we have stored the source in a sub-directory called src -- we could have used any reasonable name (e.g., source if you like vowels).

Examining the root.vrs file, we get:

    % cat root.vrs
    cset Project tut2
    cset TreeType baseline
    cset Revision 1.1

The cset keyword sets a qvrs variable, but only if it is not set already. Now try building in this tree:

    % qef
    #{ @host <date> <time>
    # QEFHALTFILE: ~/tut/tut2/src/%qef00783b.hlt
    qef: Building in a tree with TreeType baseline prohibited
    #} E-1 host@~/tut/tut2/src <time>(0)
Trees and Tree Types

The error message above mentions a variable TreeType. This variable identifies the tree's type which, in turn, controls certain aspects of qef's behavior within that tree.

Each project in qef is a "forest" of trees. There are three main tree types: baseline, working, and object.

The baseline tree represents the read-only source base that is shared by all developers. A working tree contains the source used by a single developer. An object tree is used to contain the builds for a single configuration, thus a developer or build manager may have multiple object trees.
Note:The actual names "baseline" and "working" are not fixed, thus could be master and sandbox if you prefer, however, some programs provide short cuts if the former are used (e.g., see qds's --bwoms flags). The name object is fixed in that builds are limited to trees with a TreeType of object as described below.

One of the software construction principles supported by qef is the separation of source files from generated or object files. In order to protect the project's source trees from contamination, qef will not normally run if the tree's type is not object or undefined. You can override this restriction -- we did in the first tutorial -- but it is not recommended.

Where do you build the product, then? In a mirror (or parallel) tree called the object tree. Each hardware platform or unique configuration has its own object tree, generated from a common source tree.

It is worth noting that the product is not installed in a destination tree, but in a destination directory. The reason is that the baseline source tree should be organized according to the logical structure of the source. The working and object trees mirror that organization. But the logical structure of the source may have no relation to the placement of the final products. For example, the modules for two different libraries should be kept in separate directories because the libraries are separate components. But both finished libraries will probably be installed in the same destination directory, namely lib in the tree named by _DestDir_.

Creating an Object Tree

To create an object tree for the current source tree, use mkqtree as in:

    % mkqtree -o ../obj # create object tree in ~/tut/tut2/obj
    + mkdir ../obj
    + chdir ../obj
    + rootvrs -t object ~/tut/tut2/src
    # output to root.vrs
    addpath RootPath        ~/tut/tut2/src
    cset    RootAlias       @NotSet
    cset    Project         tut2
    cset    Revision        1.1
    cset    TreeType        object
    cset    TreeType[~/tut/tut2/src]    baseline
    cset    BuildHost       @NotSet
    cset    BuildSys        Linux-2.0.34-i686
    cset    QremoteEnv      @NotSet  # defaults to qremote
    cset    _DestDir_       @NotSet
    cset    PrereqList      tcl(7.4)
    cset    ConfigName      @NotSet
    cset    ConfVrs         @NotSet  # defaults to conf.vrs
    cset    SysVrs          @NotSet  # defaults to linux2.vrs
    cset    TreedirsList    @NotSet  # defaults to treedirs.lst
    cset    VCSys           @NotSet
    cset    VCRoot          @NotSet  # defaults to @LastRoot
    cset    VCBranch        @NotSet
    cset    OldRoot         @NotSet
    cset    EditBranches    @NotSet  # defaults to 0
    qdupd: Added: 12 tut2 1.1 object dt gobo - ~/tut/tut2/obj ...
    + confvrs -cn
    Copying ~/tut/tut2/src/QefAdm/confvrs/confdefl.vrs to conf.vrs
    + treedirs -s

The "+" lines in the above output indicates commands that are executed.

The above mkqtree created the new directory and invoked rootvrs (creates root.vrs), confvrs (copies the default configuration file to conf.vrs in the new directory), treedirs (creates directories in the new tree duplicating the directory structure of the source tree), and qdupd (adds new path to the database).

Two important variables are worth noting: TreeType is set to the value "object" and RootPath has the baseline source directory added to it.
Note: You'll be seeing a lot of "cset Variable @NotSet" statements in root.vrs and confdefl.vrs files in the future, so an explanation is in order. The read-only variable NotSet's value is `@NotSet'. When the argument to a set keyword is @NotSet, the set keyword actually unsets the named variable. Thus the following are equivalent:
    set Variable @NotSet
    unset Variable
The cset keyword treats a @NotSet value in a similar manner, except the variable will be unset only if the special condition associated with cset is met, that is, that the variable is not set already.

@NotSet is often used as the default value in confdefl.vrs and root.vrs files, as in:

    cset  Parameter  @NotSet # comment describing Parameter
Given the semantics of a cset keyword, the above command does nothing. If Parameter was previously defined it is left unchanged. If it was undefined, the cset keyword will do the set, but given the @NotSet value the effect is to leave Parameter unset.

Now change directory into the new tree and run sls to list the files in the project's source path.

    % qd -12 # using the newly created path
    Using 12 tut2 1.1 object dt host - ~/tut/tut2/obj
		 ~/tut/tut2/src
    % sls
    ~/tut/tut2/src/QefAdm
    ~/tut/tut2/src/README
    ~/tut/tut2/obj/conf.vrs
    ~/tut/tut2/src/hello.c
    ~/tut/tut2/src/qeffile
    ~/tut/tut2/obj/root.vrs

The project's configuration is now split among three qvrs files: root.vrs, containing tree specific settings; conf.vrs, containing the user configuration specific variables; and qeffile, containing the local configuration.

List the files in the newly created object tree:

    % ls
    conf.vrs  root.vrs
    % qvrs -f # list qvrs files
    ~/tut/tut2/obj/root.vrs
    ~/tut/tut2/obj/conf.vrs
    <qtree>/lib/sysvrs/linux2.vrs
    ~/tut/tut2/src/qeffile

With these project configuration variables established in these files, build the system:

    % qef
    #{ @host <date> <time>
    + cc -c ~/tut/tut2/src/hello.c
    + cc hello.o hello
    #} E0 host@~/tut/tut2/obj <date> <time>(#)

Note that the source files are accessed with their full paths. They could (and in production installations, typically do) reside on another machine, a file server somewhere on the network. qef constructs the system as expected, but this is the developer's build, not the final system. Now we'll install the final system:

    % qef Install
    #{ Install @host 2000/02/26 02:26:02
    # QEFHALTFILE: ~/tut/tut2/obj/%qef25682b.hlt
    + instal -E -d @NotSet/bin/hello hello
    instal: @NotSet/bin/hello: will not install @NotSet files
     *** Error code 255
    Fatal error - waiting for active jobs to complete.

    mimk: Fatal error during construction
    #} E1 host@~/tut/tut2/obj 2000/02/26 02:26:02(0)

In the last tutorial, you fixed this by setting the variable _DestDir_ in the leaf.vrs file. That is not really the appropriate place to set it. Given the importance of this variable, it is set in the root.vrs file.

Rather than edit the root.vrs file directly, use the rootvrs utility to set the destination directory:

    % rootvrs -d ../dest
    # output to root.vrs
    ... # unchanged lines not shown
    cset    _DestDir_       @(resolve @RootDir/../dest)
    qdupd: Unchanged: 12 tut2 1.1 object dt host - ~/tut/...

You can now install the system:

    % qef Install
    #{ Install @host 2000/02/26 02:34:26
    # QEFHALTFILE: ~/tut/tut2/obj/%qef25756b.hlt
    + instal -E -d ~/tut/tut2/dest/bin/hello hello
    #} E0 host@~/tut/tut2/obj 2000/02/26 02:34:26(0)

qef builds the system, then does the installation, placing the binary in the destination directory.

Configuration (Qvrs) Files

qef looks at a group of files called qvrs files for construction information. These files are arranged in a hierarchy, and their scope operates according to the hierarchy. For a list of the qvrs files that apply to the current directory, type:

    % qvrs -f
    ~/tut/tut2/obj/root.vrs
    ~/tut/tut2/obj/conf.vrs
    <qtree>/lib/sysvrs/linux2.vrs
    ~/tut/tut2/src/qeffile

Depending on their location and name, the qvrs files exercise varying degrees of control over the behavior of qef. Some files affect the entire system (the system defaults file, sysnm.vrs, where sysnm is replaced by the type of the system); some affect a given directory and its children (root.vrs, conf.vrs); and some affect only the directory in which they reside (qeffile).

qvrs is one of the key tools in the qef toolbox. As you work with qef, you will find it useful for inspecting the construction variables affecting a project, branch or directory. To list all the configuration variables currently defined, run qvrs with no arguments:

    % qvrs
    AsSuffix    s
    BeginLine   qsg --M
    Branch      .
    BuildPath   <qtree-home>/bin /bin /usr/bin
    BuildSys    Linux-2.0.34-i686
    CcInclDirs  GNUDIR/include /usr/include
    CcLibDirs   GNUDIR /lib /usr/lib
    ConfVrsFile ~/tut/tut2/obj/conf.vrs
    ConfVrsPath ~/tut/tut2/obj ~/tut/tut2/src/QefAdm/confvrs
    CurDir      ~/tut/tut2/obj
    # rest elided

Use x-qvrs <variable> to learn more about a specific variable, as in:

    % x-qvrs CurDir
    CurDir : current working directory
    
    Prior to processing its first input file, qvrs sets CurDir to
    the full pathname of the current directory.
    
    See also: RootDir Branch SubDir Dots RootAlias

You can also run qvrs to find the value of a particular variable, using the @ sign to indicate the variable of interest. For example:

    % qvrs @_DestDir_
    ~/tut/tut2/dest
    % qvrs -VD # a short cut for the above
    ~/tut/tut2/dest
    % qvrs -V? # show list of other variable short cuts
    List of -V arguments

    -V      output version number and exit
    -V?     output this list and exit
    -VBH    @BuildHost
    ...

Note that the _DestDir_ path has been resolved. This is an important concept -- project specifics are abstracted into symbolic representations, so that no matter how complex they are, references to them remain simple. Change one variable in one place, and you make that change throughout the whole system or any desired part.

The qvrs --W flag is used to find out where a variable is set, as in:

    % qvrs -W _DestDir_
    ~/tut/tut2/obj/root.vrs:10: set _DestDir_ ~/tut/tut2/dest
Removing Files

To remove installed and/or object files, qef provides three special targets: RemoveInst, RemoveObjs, and RemoveAll:

    % qef RemoveInst
    #{ RemoveInst @host 2000/02/26 03:43:25
    # QEFHALTFILE: ~/tut/tut2/obj/%qef26177b.hlt
    - rm ~/tut/tut2/dest/bin/hello
    #} E0 host@~/tut/tut2/obj 2000/02/26 03:43:25(0)

This command removes all installed files that qef knows how to build, wherever they may be and no matter how many there are. It does not remove files qef does not know how to build.

The RemoveInst, RemoveObjs, and RemoveAll options actually traverse the directories of the build, constructing a list of all the files to be removed. Once all the directories have been visited, the files in the list are removed. The removals are not done until all directories have been processed to deal with those situations in which tools that would be removed are used in later directories in script generation.

Qef Options

The --LDEBUGGING option used earlier is one of more than a dozen qef (and qvrs) variables used as options by various qef utilities. The following table is a list of some of the most commonly used options. You are free to create new options, either globally or on a project-specific level.

 

Option Description
DEBUGGING compile and link with debugging turned on
MKSOLIB indicate that shared libraries can be constructed
NO_MAN manual pages are not to be installed
PROFILING produce binaries with profiling information
STAND_ALONE option indicating building stand-alone product

Usually such options are set in the user's conf.vrs file at the root of the tree.

Standard Construction Methods

One problem experienced by many software production groups is the absence of a standard construction method. qef addresses this issue.

The options in the table above are close to universal: virtually every project you build should support them. While you may not have certain development tools such as Purify and Quantify, you will almost certainly want to build debugging and profiling versions, produce manual pages, create shared libraries, and so on.

Using qef, all of these options are built into every software construction process. Moreover, every developer will be able to select the same options on every project, on every platform.
Note:Most uses of these variables just check to see if the variable is defined. If an option is defined using the "options" keyword, its value is set to 1.

To find out more about any particular option, use x-qvrs, as in:

    % x-qvrs DEBUGGING
    ...

To find out the options supported within most projects, use the command:

    % x-qvrs opt-vars
    ...

4.3) Tutorial 3

The third tutorial deals with problems in a real-world project, with application code and two libraries, one of which depends upon the other.

This section presents several new concepts and components:

  • the workspace -- developer's working directory
  • shadowed trees and files
  • the QEF directory server
  • partial constructions

To install the tutorial, use the following commands:

    % dirsetup -d ~/tut/tut3/src qeftut3
    - mkdir ~/tut/tut3/src/QefAdm/confvrs/RCS
    - mkdir ~/tut/tut3/src/QefAdm/relinfo/RCS
    ...
    % cd ~/tut/tut3/src

This creates the ~/tut/tut3/src directory and within it several sub-directories.

Outline of Tutorial

This tutorial goes through the typical steps followed by a developer working on an ongoing project. Some of the initial steps are executed only once, such as setting the configuration name. Other steps, such as copying files to and from the baseline source and the work directory, are repeated frequently. A typical use of qef will resemble the following steps:

  1. Add the source tree to the server database (qdupd)
  2. Create the working tree (mkqtree --w)
  3. Create the object tree (mkqtree --o)
  4. List the relevant configuration files (qvrs --f)
  5. Use rootvrs to set configuration options and settings
  6. Build the entire project (qef); this creates an install log
  7. View the install log
To illustrate other QEF facilities, this tutorial will also:
  1. Copy a header file to the work directory
  2. Build the project and note what is built due to the new header file
  3. Remove the header file from the work directory
  4. Rebuild and note what is rebuilt and why
qdsrv: The QEF Directory Server

For this tutorial, qdsrv, the QEF directory server, must be running. To determine if it is, issue the command:

    % qdmgt -v
    # if qdsrv is running ...
    Qdsrv   9.1(162) - 2000/01/19 01:08:00 - Linux-2.0.34-i686
	    pid=455; uid=0; host=host; port=6631
	    Database: <qtree>/data/qdsrv.db

    # If qdsrv is not running ...
    qdmgt: connect failed -- Connection refused
	    check that qdsrv is running -- See errQdsrv1(x-qmisc)
If the qdsrv is not running, it needs to be started using:
    % qdsrv
    qdsrv started: errors will be logged in
	<qtree>/data/qdsrv.log
The above might fail for a variety of reasons. For a more detailed description of starting the qdsrv, see Chapter 6.

One of the qdsrv client programs qds show entries in database. qds's --d flag, it outputs a directory name that can be used as an argument to cd. Rather than constantly typing cd $(qds -d <query-options>), you should define an alias or function qd as described in Chapter 3.1: define qd.

The qd alias is used in this tutorial; you may want to define it now, using:

    % eval `qfunc qd` # if using sh
    % eval `qfunc -c qd` # if using csh
The Source Tree

This tutorial uses the following source tree:

    src/
	QefAdm/
	    confvrs/
		RCS/
		    confdefl.vrs,v # other RCS files not listed
		confdefl.vrs
	    relinfo/
		qeffile
	    touchdir/
		qeffile
	README
	appl/
	    appl/
		appl.h
		qeffile
	    cmd/
		appl.c
		qeffile
	    lib/
		qeffile
		rtn.c
	    man/
		appl.1
		qeffile
	    qeffile
	    tree.vrs
	cmd/
	    hello.c
	    qeffile
	qeffile
	root.vrs
	tree.vrs

This tree demonstrates most of the configuration files and structures used for a large project, other than the FSIC package described in Chapter 9. Only appl/ and cmd/ contain source.

appl
Contains the source for an application; it is subdivided into a command, a library, and UNIX "man" directories.
cmd
Contains more commands.
QefAdm/
Contains QEF administration packages.
QefAdm/confvrs/
Contains the default conf.vrs file, called confdefl.vrs.
QefAdm/relinfo/
contains a qeffile to create release information files and build audit trail records.
QefAdm/touchdir/
Files related to the touchfiles mechanism, a technique for forcing recompiles or constructions.
Adding the Baseline to the Database

Now change directory to the source tree, and add the tree to the directory server's database:

    % cd ~/tut/tut3/src
    % qdupd
    qdupd: Added: 3 tut3 1.1 baseline you host - ~/tut/tut3/src

An entry has been added to the server database marking this as a baseline tree. This is normally done as soon as the project is created. For future use note the record index (e.g., "3") that appears immediately after "Added:" in the qdupd output.

Creating the Developer's Workspace

In this tutorial, the src directory is the baseline. In practice, no developer would make changes in the baseline code -- the baseline tree is intended to be the approved, ready-for-production code, changed by the project librarian or manager.

To perform maintenance on the project, the first thing the developer does is create a personal work directory or workspace. With the root of the source tree as your current directory, create a working source directory named "work":

    % mkqtree -w ../work
    + mkdir ../work
    + chdir ../work
    + rootvrs -t working ~/tut/tut3/src
    # output to root.vrs
    addpath	RootPath	~/tut/tut3/src
    cset	Project		tut3
    cset	Revision	1.1
    cset	TreeType	working
    cset	TreeType[~/tut/tut3/src]	baseline
    cset	QefAdm		QefAdm
    qdupd: Added: 9 tut3 1.1 working you host - ~/tut/tut3/work
	~/tut/tut3/src
    # confvrs not done as TreeType not `object'
    + treedirs -s
    + mkdir QefAdm
    + mkdir QefAdm/relinfo
    + mkdir QefAdm/touchdir
    + mkdir appl
    + mkdir appl/appl
    + mkdir appl/cmd
    + mkdir appl/lib
    + mkdir appl/man
    + mkdir cmd

The --w is actually a short form for --tworking. This command duplicates the relevant part of the src hierarchy into a directory called work, The work directory hierarchy contains no files yet, other than the root.vrs file, which establishes the link to the baseline tree.

Instead of copying or linking all the baseline source files, you create working copies of the files you will change, storing them remotely from the originals and including the copies rather than the originals in the project construction. All your working copies mask their counterparts in the source tree. Ideally, you should be able to copy any file(s) to your workspace at any time, then build the project using these files without making any other adjustments.

Since the workspace is before the baseline source directory in the source path list, any file residing there will be found and used before a file of the same name in the baseline source tree. Similarly, moving a completed file out of the workspace and back to the source tree also requires no changes in the construction scripts. The move will be noticed, and documented by QEF during the build, so the developer is always certain which files were used to build the product

Creating the Object Tree

Next, you need an object tree to receive all the object files created during the construction. Chdir to the work tree and run mkqtree as follows:

    % cd ../work
    % mkqtree -o ../obj
    + mkdir ../obj
    + chdir ../obj
    + rootvrs -t object ~/tut/tut3/work
    # output to root.vrs
    addpath	RootPath	~/tut/tut3/work ~/tut/tut3/src
    cset	Project		tut3
    cset	Revision	1.1
    cset	TreeType	object
    cset	TreeType[~/tut/tut3/work]	working
    cset	TreeType[~/tut/tut3/src]	baseline
    cset	BuildSys	Linux-2.0.34-i686
    cset	QefAdm		QefAdm
    qdupd: Added: 12 tut3 1.1 object you host - ~/tut/tut3/obj
	~/tut/tut3/work ~/tut/tut3/src
    + confvrs -cn
    Copying ~/tut/tut3/src/QefAdm/confvrs/confdefl.vrs to conf.vrs
    + treedirs -s
    + mkdir QefAdm
    + mkdir ...
Listing the Configuration Files

To see which configuration files affect builds in the current directory, list the active configuration files with qvrs --f. Chdir to the object tree and list the configuration files:

    % cd ../obj
    % qvrs -f
    ~/tut/tut3/obj/root.vrs
    ~/tut/tut3/obj/conf.vrs
    ~/tut/tut3/src/tree.vrs
    <qtree>/lib/sysvrs/sysdefl.vrs
    ~/tut/tut3/src/qeffile

You can run qvrs with multiple arguments, including variable names and even expressions. For example, to display the assignments of the variables RootPath and SrcPath:

    % qvrs "RootPath = @RootPath" "SrcPath = @SrcPath"
    RootPath = ~/tut/tut3/obj ~/tut/tut3/work ~/tut/tut3/src
    SrcPath = . ~/tut/tut3/work ~/tut/tut3/src

Viewing the configuration files can teach you more about their relative purpose in the scheme of the project, You may wish to examine them from time to time as you do the builds.

_DestDir_ and ConfigName

At this point you must set the destination directory (the variable _DestDir_), You should also define the ConfigName variable. ConfigName is a one-word name to describe this particular platform or project. It is usually included in the version strings and appears in the qdsrv database paths. In this case, set it to the default for the current system. The rootvrs command can be used to set both these variables, as in:

    % rootvrs -d ../dest -c .
    ...
    cset  _DestDir_  @(resolve @RootDir/../dest)
    cset  ConfigName  linux2_0i
    ...

qdupd: Changed: 12 tut3 1.1 object you host - ~/... qdupd: .....to: 12 tut3 1.1 object you host linux2_0i ~/...
Note:rootvrs automatically invokes qdupd to update the qdsrv project database.

Setup is now complete. You are ready to build the entire project, but before we continue, let us explore the use of the qdsrv database to navigate back and forth from tree to tree.

Using qd to change trees

For a list of the currently relevant trees use qds. This will list the tree records for the current project, host and user.

    % qds
    2 tut3 1.1 baseline you host -
	~/tut3/src
    9 tut3 1.1 working you host -
	~/tut3/work
	~/tut3/src 
    12 tut3 1.1 object you host linux2_0i
	~/tut3/obj
	~/tut3/work
	~/tut3/src

The qd alias or function that we defined previously can now be used to move back and forth between the trees. qds only not serves to help navigate the forest of project trees, but it also helps navigate the local woods, as represented by the three trees that we have now. The following is a series of qdsrv commands that will illustrate some of the more important facilities.

    % qd -ptut3 -tobject / # cd to your tut3 object tree
    # the slash indicates to go to the root
    % qd -ptut3 -o # - -o equivalent to -tobject
    % qd -w # cd to the working tree
    % qd -b # cd to the baseline tree
    % qd -o # cd back to the object tree
    % qd -n # chdir to next tree (i.e., working)
    % qd -n # chdir to next tree (i.e., baseline)
    % qd -n # nowhere to go, so we stay where we are
    % qd -w appl/cmd # chdir to appl/cmd in working tree
    % qd -o # back to appl/cmd in object tree
    % qd -w .. # back to working tree but up one
    % qds -R # print current Root
    % qd -R /cmd # cmd directory in current root
    % qd -o # get back to the object tree
    % qdid -s # bind user identifier to current tree
    % cd /etc # go wandering
    % qd -i # return to bound tree
    % cd /usr/lib # go wandering again
    % qd -i1 # return to dir1 of path (the working tree)
    % qd -0 # path 0 is always your home
    % qd -ptut3 # Ambiguous ... you will be shown numbered list
    % qd -<num> # chdir to path <num>

That should be enough for now. Be sure to end up in the root of the object tree before trying to build the system, i.e.:

    % qd -i /
Building the Entire Project

Building the entire project at this point has two purposes: it confirms that everything is running smoothly, and it initializes the build log. Type the command:

    % qd -i / # chdir to the root of the project
    % go qef All # go will redirect output to ,.g
    % g -f # tail the qef output; ^C to interrupt
    ...

You should be able to examine the output in ,.g and get a general sense of what is happening in the build.

    % g # cat the ,.g file
    #{ All @host 2000/02/27 04:03:51
    # QEFHALTFILE: ~/tut/tut3/obj/%qef00527b.hlt
    #-{ -d appl Install @host 2000/02/27 04:03:52
    #--{ -d appl Install @host 2000/02/27 04:03:52
    ...
    % g -t # tail the ,.g file
    ...
    #-} E0 host@~/tut/tut3/obj/appl 2000/02/27 04:03:59(1)
    #-{ -d QefAdm/relinfo Post @host 2000/02/27 04:03:59
    + mkvernum -o relinfo -0Tut3 -1"" -l relinfo.vf release
    + instal -S ~/tut/tut3/dest/relinfo relinfo
    #-} E0 host@~/tut/tut3/obj/QefAdm/relinfo 2000/02/27
	04:03:59(0)
    #} E0 host@~/tut/tut3/obj 2000/02/27 04:03:59(8)
Now look at the files in the current directory.
    % l -f # list files in directory
    ,.g        conf.vrs   instal.log root.vrs
The instal.log file is a record of all the installations done by instal.
Multi- Directory Builds and qefdirs

This is an appropriate time to look at the qeffile that builds the project.

    % cat `sls qeffile` # sls searches SrcPath directories
			# for argument
    # A qefdirs qeffile

    set _DefaultArgs_ All

    Begin   qefdirs

    # Note: `qefdirs -X' outputs a describe syntax and semantics

    P = Post	# add Post construction key P; qefdirs already
		# provides I=Install, L=Local, and M=Man

    All = Install Man Post

    # add directories and operations after this line

    appl  ILM
    cmd   IL  appl  # cmd requires appl's installation

    + a new level

    QefAdm/relinfo  P
This is the first qeffile we have examined that uses qefdirs as its script generator. As a rule, qefdirs is used in nodes of the tree hierarchy, and qsg is used in leaves of the tree.

The simplest qeffile using qefdirs is the Begin line and a list of directories. When a qef construction is specified in the current directory, the same construction is triggered in the listed directories that support that construction.

The first statement defines a new construction key, Post. The second statement redefines All so it is a combination of Install, Man, and Post. (Normally it is a combination of Install and Man.)

The rest of the script defines the directories, their constructions, and the relationships:

  • The appl directory supports the Install, Local build, and Man targets
  • The cmd directory supports the Install and Local build targets, and it depends upon appl.

The + defines a new level. The levels mechanism allows you to split the directory list into groups that can be selected or suppressed. In this case, the done directory represents another level. You can invoke a construction on either or both levels. qefdirs provides a flag that shows the directories and the constructions they support:

    % qefdirs -L # L for levels
    Install 1	appl cmd
    Man     1	appl
    Post    2	QefAdm/relinfo
The first column names the construction, the second the level number, and the third the directories supporting the construction in that level. The sub-directory appl also has a qefdirs qeffile:
    % cd appl
    % cat `sls qeffile`
    Begin   qefdirs
    
    appl    I
    lib     IL
    +
    cmd     IL      -lib
    man     m       # m == Man, but only if @NO_MAN not set
    % qefdirs -L
    Install 1       appl lib
            2       cmd
    Man     2       man
Modifying Source Files in the Work Tree

To protect the baseline source files, developers copy files they wish to edit to their workspace, and work on them there. Files located anywhere in the workspace take precedence over the corresponding files in the source tree.

To illustrate, check out a file:

    % qd -o /appl/cmd # chdir to obj/appl/cmd
    % sls -c # list the files corresponding to this directory
    ~/tut/tut3/obj/appl/cmd:
    appl       appl.o     incls._    qmkhist._  srclist._
    
    ~/tut/tut3/src/appl/cmd:
    RCS     appl.c  qeffile
    % sls appl.c # determine the location of a specific file
    ~/tut/tut3/src/appl/cmd/appl.c
    % vci co -e appl.c # check out appl..c for editing
    ~/tut/tut3/src/appl/cmd/RCS/appl.c,v  -->  appl.c
    revision 1.1 (locked)
    done
    % sls appl.c # visible appl.c now in working tree
    ~/tut/tut3/work/appl/cmd/appl.c
    % sls -a appl.c # list all the appl.c files in the path
    ~/tut/tut3/work/appl/cmd/appl.c
    ~/tut/tut3/src/appl/cmd/appl.c
    % vci -X # list supported commands
    changed	tell what files have been changed (RCS)
    ci		check in the argument files (RCS)
    co		check out the argument files (RCS)
    diff	run rcsdiff on the g-file and the v-file (RCS)
    ...
    tell	tell what files are being edited (RCS)
    ...
    % vci tell # list locked files
    ~/tut/tut3/src/appl/cmd/appl.c

Now, build the project again:

    % qef
    #{ @host <date> <time>
    Moved:   ~/tut/tut3/src/appl/cmd/appl.c
		~/tut/tut3/work/appl/cmd/appl.c
    ...
    #} E0 host@~/tut/tut3/obj/appl/cmd <date> <time>(1) 

Since this version of appl.c is new, this portion of the system is rebuilt.

Now make some minor changes to appl.c

    % vi `sls appl.c` # replace vi by whatever you want
    # make some changes such as adding, deleting lines
    % vci diff appl.c # view changes using diff
    ...
    % vci vdiff appl.c # view changes using qgdiff
    # see Cook's Tour - Page 8
Undoing a Change

In this project, there are no new source files. Everything in the working tree is a copy of a file in the baseline tree. Thus you are free to experiment. From time to time, you may wish to undo a particular change to the working copy of a project. With QEF, just "unget" the copy from the work tree and rebuild the project:

    % vci unget appl.c
    % qef
    #{ @host <date> <start time>
    Moved:   ~/tut/tut3/work/appl/appl.c
		~/tut/tut3/src/appl/appl.c
    ...
    #} E0 host@~/tut/tut3/obj <date> <end time>(1)
Building the Complete System

You have already seen how easily qef can build any portion of a system: simply go into the tree to the directory of interest and run qef from there. You can also perform a system-wide build (of the files that need to be rebuilt):

    % cd ~/tut/tut3/obj # root of the object tree
    % qef All
    #{ All @host <date> <start time>
    # QEFHALTFILE: ~/tut/tut3/obj/%qef01195b.hlt
    #-{ -d appl Install @host <date> <start time>
    ...
    #-} E0 host@~/tut/tut3/obj/done <end.time>(1)
    #} E0 host@~/tut/tut3/obj <date> <end time>(9)
Specifying Libraries

The qef tools support several idioms for specifying the libraries to be linked with program modules.

The most convenient way when writing code is to include a LIBS comment in the main source file. A LIBS comment should be placed in the first 1000 characters of source and should have the following form:

    /* LIBS: -lX11 -lm
     */

This example specifies the X11 and the math libraries. The argument libraries are automatically extracted from the source and included in the build script.

If you do not want to edit all of your source code, you may also specify the libraries using qvrs variables. Set the qvrs variable LIBS[file.c] to the symbolic library list.

Review of the Project Config Files

The next several sections document this project's configuration files. By reviewing these, you should arrive at an understanding of how the project's components are configured.

obj/root.vrs
This file roots the object tree, and sets configuration variables that apply to the whole tree. The first command, addpath, attaches the source tree to the root path.
obj/conf.vrs
Copied from the default configuration file, this file establishes the build options, and sets compiler names and flags.
src/tree.vrs
This file specifies tree-wide settings for the tree. It declares the directories housing the Time Check, Install Log and Version database. It conditionally adds paths to LibPath and InclPath, depending on the value of _DestDir_ and the option STAND_ALONE. Finally, it adds a directory to the Touch path.
$QTREE/lib/sysvrs/sysnm.vrs
This file establishes system-wide settings. The contents vary from system to system. sysnm is replaced by the current system's name (e.g., linux2, sun4_1).
src/qeffile
This file names qefdirs as the script generator to run. The qefdirs script generator specifies relationships between directories. This script names two targets, Install and Man, and equates All with them. Finally, it lists the directories to process and the operations to perform on them.
The QEF Version Control Interface

Many software shops use a version control tool such as SCCS or RCS to monitor changes to the source. qef provides a standard interface to version control tools. The version system control interface (vci) runs the version control commands with full knowledge of qef's viewpathing facilities.

The baseline source is the source under version control. Instead of copying a file to be changed from the baseline source, the developer gives the command vci co <file> (for "version control interface checkout"). To submit the changed source, the developer gives the command vci ci <file> (for "version control interface checkin").

Depending upon how your qef system is configured, submitting the changed source may incorporate it into the baseline source, or it may move the source into a mirror tree which contains "unpublished" or unapproved sources. Someone (the project manager, the code librarian, the quality assurance staff) then authorizes the code to be incorporated into the baseline source.

Summary

In this tutorial, you have examined and worked with a real-world project whose files are organized as a tree of directories. You learned a number of important lessons about QEF, including:

  • creating a work area for doing maintenance on the system
  • creating an object tree for that work area
  • controlling how much of the construction is performed by choosing the directory in which you run qef
  • creating a destination directory and installing the project software
  • using variables stored in qef files to modify your construction environment, and how to select which targets are built
  • using the vci interface

As stated at the beginning of this chapter, the reader should look at "A Cook's Tour of the QEF", as it contains a more comprehensive coverage of the QEF system.

4.4) Miscellaneous Notes

This section has a vague title because we expect it to grow as we discover new things that the user needs or wants to know early in their QEF use.
Shell, traits, qvrs, & qefpp variables
The QEF system uses four different types of variables in configuring or parameterizing processes. The purpose of this note is to bring to the reader's attention how these variables work together. The types of variables are:
shell environment variables such as $PATH or $QTREE. Shell variables are typically set by the user's login process or user-invoked processes such as envset. Shell variables are commonly used to establish the user's environment and to store information about the user and the user's session. The QEF system supports the manipulation and use of shell variables, but avoids using them as much as is possible due to their poor control. Other than $QTREE, QEF uses only the standard variables (e.g., PATH, TERM, TZ, and HOME), plus variables to override the default file names for various tools, and settings to pass information to child processes. For a list of the environment variables used by QEF see *env(x-qvrs).
traits variables traits are typically used to store characteristics of the host system such as special directories or tool names and capabilities that cannot be determined by system type alone. traits are set via the system <qtree>/lib/traits.vrs and the local <qtree>/data/traits.ext files, which are converted into a host specific traits binary database by mktraits.
Note:Shell variables can be used to override the value of a trait, but that is not recommended.
qvrs variables The qvrs system is described in great detail in the x_db database x-qvrs, the qvrs(1) man page, and in Chapter 8 of this guide. In many ways qvrs is the single most important program in the system as it establishes the environment in which all other tools run. qvrs primary role is to find the tree, project, user, system, directory, and project/system specific configuration files, parse them, and deliver to other programs the settings as a variable/value tuple database. The variables specified via qvrs variables encompass every aspect of the system, from the names of tools, search paths, semantic options, library mappings and search mechanisms, user and system environment information, to flags for the processing of specific files by specific tools.
qefpp macros The qef pre-processor uses macros similar to those of the C pre-processor in C, but with very different restrictions. Their names must be a least three characters and begin with a `_' or an upper case letter. In fact, due to their use within text files that do not have a rigourous syntax, all internal macros and nearly all macros in provided scripts begin and end with an `_' to minimize the likelihood of the name being the same as that of a file.

 
Type Assoc.
Arrays
Num List
Cmmd
Usual Syntax/Semantics
Shellfalse <40qenv Usually all upper case. An explicitly requested trait will take the shell variable of the same name's value. qvrs & qefpp can create new shell variables.
traitstrue 50-75traits Usually mixed case. Can be overridden by Shell Variable. Variables beginning with "_T_", "_F_", or "_D_" automatically imported by qvrs. See notes below.
qvrstrue 125+qvrs Usually mixed case. _*_, _[TDF]_*, and --q variables automatically imported by qefpp. Can set environment variables.
qefpptrue 40+qef -P
defines
Majority are imported qvrs variables. Can export Shell vars. There are 30 additional built-ins.

These variables are used throughout the QEF system to control the way tools behave and the environment in which they function. The preservation and management of the variables is crucial to the health of your software. The following are notes on the care and management of your variables and some explanations of the naming conventions.

  1. The strange looking _Var_ variables are named that way to minimize the probability of have a file with the same name as a variable in the qefpp processing. Any new traits, qvrs, or qefpp variables that need to be created should take that form and provisions should be made to ensure that they are exported or imported appropriately.
  2. _T_* variables are used to name tools such as _T_cc. The default value for a _T_* variable is the name itself minus the _T_ prefix. Such variables are usually best named initially in the traits database. qvrs files or even qefpp scripts can override the values as necessary.
  3. _F_* variables are similar to _T_*, but their default value is the empty string. _F_X specifies the flags to be used by tool X. _F_X[f*.c] specifies the additional flags to be used when tool X processes a file whose basename matches "f*.c". Two special forms of _F_ variables, namely _F_cc_c and _F_cc_o are used to specify flags that apply to the compile and link passes of the C compiler respectively. When setting a _F_* variable, ensure that any previous setting that might be significant will not be lost. This is best done by appending the new settings or using a _F_X[*] form.
  4. _D_* are very similar to _F_* variables. Their default value is the empty string. _D_* variable are largely limited to c and cxx (i.e., c++) and are used to specify defines and undefines.
  5. Local site and platform configuration parameters that are non-deterministic (e.g., GNU path) should be initialized in the traits database and imported as necessary into the qvrs space.
  6. Minimize your use or dependence on Shell variables as they are not well controlled or controllable. If shell variables are required by tools needed in constructions, set them as qvrs variables and export them in the required directories. This way their control and setting is recorded and well controlled in source files or under strict centralized user control. The QEF tool envset is used extensively to define and invoke sets of Shell variables. The tools cush, josh, and qremote all take --E flags or use a qvrs specified setting to select an envset set to be put in place for their argument processes.

c020.qh - 9.4 - 03/10/27 QEF Home The Guide's Table of Contents Previous Chapter Top Of Page Next Chapter