Cons FAQ

Version 1.2
23 February 2001
Steven Knight knight@baldmt.com

Subject: 1. Overview

1.1. Introduction

This FAQ contains information about the Cons software construction utility. Much of the information in this FAQ is based on submissions to the Cons mailing list, cons-discuss@gnu.org. To join the mailing list, follow the subscription instructions at:

        http://mail.gnu.org/mailman/listinfo/cons-discuss
      

1.2. Current Version of This FAQ

The most recent and up-to-date version of this FAQ may always be found at:

        http://www.baldmt.com/cons-faq/current.html (HTML)
        http://www.baldmt.com/cons-faq/current.txt (text)
      

Please check to make sure your question isn't already answered in the latest version before submitting a new question (or an addition or correction to this FAQ).

1.3. Copyright

This document is copyright 1999-2001 by Steven Knight (knight@baldmt.com). Individual items taken from the Cons mailing list are incorporated with the permission of the credited individuals.

This document may be freely copied, redistributed or modified for any purpose and without fee, provided that this copyright notice is not removed or altered.

Individual items from this document may be excerpted and redistributed without inclusion of the copyright notice.

If you incorporate this FAQ in any commercial, salable, or for-profit collection or product, please be courteous and send a copy to the copyright holder.

1.4. Feedback

Any and all feedback on this FAQ is welcome: corrections to existing answers, suggested new questions, typographical errors, better organization of questions, etc. Contact the author at knight@baldmt.com.

1.5. Credits

Thanks to the following for their contributions to the Cons mailing list and this FAQ:

Ron Aaron, Chris Bartz, Todd J. Derr, Brad M. Garcia, Johan Holmberg, Anthony Kolarik, John Macdonald, Gary Oberbrunner, Jeff Rosenfeld, Jochen Schwarze, Bob Sidebotham, Alex Smith, and Rajesh Vaidheeswarran.


Subject: 2. Table of Contents

  1. Overview

    1.1. Introduction
    1.2. Current Version of This FAQ
    1.3. Copyright
    1.4. Feedback
    1.5. Credits

  2. Table of Contents

  3. General Information

    3.1. What is Cons?
    3.2. Do I need to understand Perl to use Cons?
    3.3. Where do I get Cons?
    3.4. Should I use the stable or development version of Cons?
    3.5. What's the difference between the cons and cons-test packages?
    3.6. Do I need to download the cons-test package to use Cons?
    3.7. Is there a Cons mailing list or newsgroup?
    3.8. Is the mailing list archived anywhere?
    3.9. On what operating systems does Cons run?
    3.10. Who wrote Cons?
    3.11. Who maintains Cons?
    3.12. Who owns the copyright to Cons?
    3.13. How is Cons licensed?

  4. Compatibility with make

    4.1. Is Cons compatible with make?
    4.2. Is there a Makefile-to-Cons or Cons-to-Makefile converter?
    4.3. What are the advantages or disadvantages of Cons vs. Make?
    4.4. How do I do something like 'make install' in Cons?
    4.5. Does Cons support building in parallel, like make's -j option?
    4.6. How do I do something like 'make test' in Cons?
    4.7. Does Cons support something like VPATH in make?

  5. Installation Problems

    5.1. Why can't Cons locate a loadable object for module MD5?
    5.2. Why can't Cons locate Config.pm?

  6. Execution Problems

    6.1. Why do I sometimes get an error message when I interrupt Cons?
    6.2. Why didn't Cons build anything when I ran it?
    6.3. Why can't Cons execute my compiler?
    6.4. Why doesn't Cons pass the user's environment to child processes?
    6.5. Why didn't Cons execute its Build directive when I expected?
    6.6. Why doesn't Cons detect changes to files outside the local build directory?
    6.7. Why didn't Cons build things in the order I expected?

  7. Using Cons: How do I...?

    7.1. Do I have to put a separate Conscript file in each subdirectory?
    7.2. How do I tell Cons that a single command builds multiple targets?
    7.3. How do I use different or multiple compilers?
    7.4. How do I build multiple software versions from a single source?
    7.5. How can I build or install files above the top-level Construct file?
    7.6. How do I link objects created in separate subdirectories?
    7.7. How do I modify an existing Cons environment?
    7.8. How do I use Import and Export to pass information between Construct and Conscript files?
    7.9. How do I glob filenames in Cons?
    7.10. How does Cons install files?
    7.11. How do I get Cons to install a file only if it doesn't exist at all?
    7.12. How do I replace individual modules in a library?
    7.13. How do I build shared libraries?
    7.14. How do I share files between builds for different architectures?
    7.15. How do I change the suffix for object files?
    7.16. When using Repositories, must I change third-party code to use #include <...> instead of #include "..."?
    7.17. How can I add another command to be executed after a target is built?

  8. Dependencies

    8.1. How does Cons determine build dependencies?
    8.2. Does Cons re-scan a file every time it calculates dependencies?
    8.3. How can I have Cons list the build dependencies?
    8.4. Will Cons use C preprocessor statements to determine dependencies?
    8.5. Shouldn't the platform be part of Cons' dependency analysis?
    8.6. Shouldn't the executable path be part of Cons' dependency analysis?

  9. Language Support

    9.1. Does Cons support C?
    9.2. Does Cons support C++?
    9.3. Does Cons support Java?
    9.4. Does Cons support Fortran?

  10. Windows NT

    10.1. Does Cons recognize Windows NT absolute path names?
    10.2. On Windows NT, how do I specify an absolute path name in CPPPATH?
    10.3. How does Cons deal with case insensitivity in Windows NT?
    10.4. How do I build a DLL using Cons?

  11. Extending Cons

    11.1. How do I add support for new file types (suffixes)?
    11.2. How do I submit a bugfix or a new feature to be added to Cons?

Subject: 3. General Information

3.1. What is Cons?

Cons is a software construction utility--that is, an alternative to "make". It is implemented as a Perl script, which gives it many powerful capabilities not found in other software construction systems.

3.2. Do I need to understand Perl to use Cons?

No. The file formats used by Cons are Perl-like, but do not require specific knowledge of Perl to be useful. Of course, you can use Cons more powerfully if you know Perl well enough to be able to use its features.

3.3. Where do I get Cons?

The Cons home page is:

        http://www.dsmit.com/cons/
      

Mirrors are available at:

        http://www.baldmt.com/cons/
        http://members.home.com/garsh/cons/
      

The page contains information about downloading the latest versions of Cons. Cons is also available on CPAN:

        http://www.CPAN.org/authors/id/K/KN/KNIGHT/
      

3.4. Should I use the stable or development version of Cons?

Beginning with version 2.0, Cons follows the GNU/Linux numbering convention: stable versions have even minor version numbers (2.0.x, 2.2.x, etc.), and development versions have odd minor version numbers (2.1.x, 2.3.x, etc.).

Development versions of Cons are intended for more frequent release of new features and fixes. Most users should be fine using the stable version, unless they want to use a specific feature, or need a specific fix, found in a more recent development version.

Both stable and development versions of Cons are developed with an extensive regression test suite, so the actual stability of both stable and development versions is generally quite good.

3.5. What's the difference between the cons and cons-test packages?

Beginning with version 2.1.2, Cons has been delivered in two packages, each available in .tgz, .rpm, or .deb formats. These packages, "cons" and "cons-test", are available on the Cons web page(s).

The "cons" package contains the Cons script itself and its documentation.

The "cons-test" package contains the full set of regression tests used in Cons development, some supporting Perl modules, and a wrapper script for executing the tests. It is recommended for users who want to run the tests to verify that Cons performs correctly on their system, or who plan to modify or extend Cons and want to make sure their modification doesn't break any existing Cons features.

3.6. Do I need to download the cons-test package to use Cons?

No. The "cons" package available on Cons web page(s) contains everything you need to run Cons.

3.7. Is there a Cons mailing list or newsgroup?

The Cons mailing list is cons-discuss@gnu.org. To join the mailing list, follow the subscription instructions at:

        http://mail.gnu.org/mailman/listinfo/cons-discuss
      

There is no Cons newsgroup on Usenet. (Not yet, anyway.)

3.8. Is the mailing list archived anywhere?

Yes, at:

	http://mail.gnu.org/pipermail/cons-discuss/
      

3.9. On what operating systems does Cons run?

Because it is implemented as a Perl script, Cons could conceivably run on any system that supports Perl. Cons is known to be in production use on FreeBSD, GNU/Linux, Solaris, SunOS, HPUX, AIX, IRIX, and Windows NT.

3.10. Who wrote Cons?

Cons was originally written by Bob Sidebotham. Bob still participates on the mailing list from time to time, but is not the current maintainer.

3.11. Who maintains Cons?

Cons is currently maintained by Rajesh Vaidheeswarran. Development versions are released by Steven Knight. Questions about Cons should be directed to the cons-discuss@gnu.org mailing list, however.

3.12. Who owns the copyright to Cons?

Cons is currently copyright the Free Software Foundation. Prior to Cons version 2.0, Cons was copyright FORE Systems, Bob's employer while he was developing Cons.

3.13. How is Cons licensed?

Cons is distributed under the GNU Public License (GPL), so you can use it, modify it, or even redistribute it without charge (so long as you provide source code when you redistribute it). Prior to Cons version 2.0, FORE Systems made Cons available under a license similar to the BSD license.


Subject: 4. Compatibility with make

4.1. Is Cons compatible with make?

No. Cons uses a completely different format for its input files.

4.2. Is there a Makefile-to-Cons or Cons-to-Makefile converter?

No. It would probably be more work than it's worth to write a converter, especially since the Cons approach of building everything from a single process at the top of the directory tree differs radically from the standard recursive use of Make.

4.3. What are the advantages or disadvantages of Cons vs. Make?

Cons' advantages over Make are covered in detail in its documentation. The main advantage for many is that Cons has better dependency analysis integrated directly into its build engine, which means that you never have to do the equivalent of a 'make clean' to make sure that everything was rebuilt properly. It's also much faster than Make for typical builds of large directory trees.

The main disadvantage of Cons is that its Construct and Conscript files have some of Perl's syntax-heavy flavor, which can mean a steeper learning curve than some people will tolerate. It also currently lacks an extension mechanism for new file types that's as easy to use as Make's suffix rules.

4.4. How do I do something like 'make install' in Cons?

One of the biggest differences between make and Cons is that, in Cons, specified build targets are *only* files or subdirectories in your tree. Cons doesn't have a way to specify a dummy target of "install" in a way that means, "I don't really want you to build anything called install, I just want you to install a bunch of stuff elsewhere in the tree." (This is covered in the Cons documentation under the heading "No ``special'' targets.")

4.5. Does Cons support building in parallel, like make's -j option?

Not currently. Some experimental versions that support parallel builds are in various stages of development. A web page exists with information about these different versions at:

        http://www.baldmt.com/parallel-cons/
      

4.6. How do I do something like 'make test' in Cons?

One effective approach is to link a subdirectory named "test" to the "src" subdirectory (e.g.), and then Build a "Conscript.test" file therein:

	% cat Construct
		.
		.
		.
	Link "test" => "src";
	Build "test/Conscript.test";
		.
		.
		.
	%
      

Within the src subdirectory, the "Conscript" file handles normal build of the software:

	% cat src/Conscript
	Program $Env 'foo', 'foo.c';
	%
      

and the "Conscript.test" file handles execution of automated test scripts that live in the same directory. It does this by running another test-execution script to deposit the (successful) exit status of the test in a file with a .ES extension:

	% cat src/Conscript.test
	my @tests = qw(
		test1.pl
		test2.pl
		test3.pl
	);
	my @deps = qw( src/foo );
	foreach my $test (@tests) {
		$exit_status_file =~ s#\.pl#\.ES#;
		Command $Env $exit_status_file, "test/$test", @deps, qq(
			$^X execute-tests.pl -t test %>
		);
	}
	%
      

The "execute-tests.pl" script above is a simple wrapper that manipulates the arguments to run the test. If the test was successful, it writes the (successful) exit status to the output file, but if the test fails, it unlinks the exit status file. That last item is subtle, but crucial. It means that you can then say:

	% cons test
      

and have only the tests that have already failed be re-run. This helps a lot when you're debugging problems with the tests themselves, as opposed to in the software under test. Of course, because of the "@deps" list, above, whenever the software is updated, every test gets re-run.

4.7. Does Cons support something like VPATH in make?

Yes. The Repository feature and -R option provide functionality very similar to VPATH, although without some inconsistencies that make VPATH somewhat difficult to use. See the Cons documentation for details.


Subject: 5. Installation Problems

5.1. Why can't Cons locate a loadable object for module MD5?

After installation, Cons might generate an error message similar to the following:

          $ cons -V
          Can't locate loadable object for module MD5 in @INC (@INC contains: 
          /opt/perl/lib/sun4-solaris/5.00404 /opt/perl/lib 
          /opt/perl/lib/site_perl/sun4-solaris /opt/perl/lib/site_perl .) at cons line XYZZY
          BEGIN failed--compilation aborted at cons line XYZZY.
      

This is generally because the Perl MD5 module has not been properly installed; just the MD5.pm file alone is not enough. The "loadable object" Perl wants is a shared library generated by xsubpp, and may be generated by a doing a full installation (perl makefile.PL; make install) of the MD5 module.

5.2. Why can't Cons locate Config.pm?

After installation, Cons might generate an error message similar to the following:

          $ cons -x
          Can't locate Config.pm in @INC at /usr/swlocal/lib/perl5/DynaLoader.pm line 18.
          BEGIN failed--compilation aborted at /usr/swlocal/bin/cons line 1964.
      

This is because the Perl binary you're using does not have the MD5 module installed at all. The MD5 module should be installed as above.

If there are multiple versions of Perl installed on your system, 'which perl' will give the perl binary that is being used.


Subject: 6. Execution Problems

6.1. Why do I sometimes get an error message when I interrupt Cons?

Q:     

Why, when I kill Cons with ^C, does it print the following msg before exiting?

                cons: error in file "docs-spark/Conscript" (Undefined subroutine &sig::hash::END called at ./cons line XYZZY.)
        
[Gary Oberbrunner , 16 April 1998]
A:     

Perl. This comes up on p5p every once in a while.

As I understand it, the problem is this: a signal can arrive between any two instructions, some signals must be at least partially dealt with at once. If perl was in the middle of an action such as malloc, it might not be safe to interrupt in the middle and then evaluate more perl code before returning to that original position. There are ideas floating around for dealing with it and lots of desire to have it dealt with, but it is not easy (not completely and correctly anyhow).

[John Macdonald , 16 April 1998]

6.2. Why didn't Cons build anything when I ran it?

A:     

You have to give it a target, usually a directory. Try

                perl cons.pl .
        

(that's a dot at the end of the line).

[Gary Oberbrunner , 12 May 1998]
A:     

Alternatively, you can use the 'Default' function to specify one or more default targets to build when no targets are specified on the command line. The following in a Construct file:

                Default '.';
        

will mimic make's behavior of building the world by default.

6.3. Why can't Cons execute my compiler?

Q:     

I tried to run the 'hello' example. My Construct is:

            $env = new cons(
                CC => 'gcc',
            );

            Program $env 'hello', 'hello.c'
        

When I type 'cons hello', I get this output:

            gcc -c hello.c -o hello.o
            gcc: installation problem, cannot exec `cpp': Invalid argument
            cons: *** [hello.o] Error 256
            cons: errors constructing hello.o
        

Which is strange, since I can *manually* type:

            gcc -c hello.c hello.o
        

and it works just fine. I am guessing my PATH or GCC_EXEC_PREFIX is getting munged by 'cons', because :

            perl -e "system 'gcc -c hello.c hello.o'"
        

works just fine too!!!

[Ron Aaron , 13 May 1999]
A:     

Cons does not pass the user's environment to the child processes that it forks to build the software. Anything you need or want to pass in from the user's environment must be done so explicitly. In practice, this isn't hard at all:

            $Env = new cons(
                ENV => {
                    PATH => $ENV{PATH} . ":/bin:/usr/bin",
                    USER => $ENV{USER},
                }
            );
        

Create any other, subsidiary Cons environments using $Env->clone, and everything else in your build gets the PATH that you want.

[Steven Knight , 13 May 1999]

6.4. Why doesn't Cons pass the user's environment to child processes?

A:     

This is for purposes of repeatability, so the build won't work for me because I have some weird thing in my path (or some other weird env variable that might alter compiler behavior) and yet fail for everyone else. Cons wants you to explicity set the path to what you need it to be.

Most folks use Make this way too, for the same reason.

[Gary Oberbrunner , 13 May 1999]

6.5. Why didn't Cons execute its Build directive when I expected?

Q:     

Yet another mechanism that I wanted to implement in CONS but failed was a build script for templates coded in different languages (locales). I have this directory structure:

            templates/
            templates/en
            templates/fr
	

where templates/ are my locale-independent templates and templates/Conscript says 'for each locale, build a locale-specific template' (but do not Install it). The individual locale's Conscript contains the Install directive.

templates/Conscript:

            @Locales = ('en','fr');
            @Sources = ('index.html');
            foreach $Locale (@Locales) {
             Command $CONS qq($Locale), join(' ',@Sources), q(localize %<);
             Build qq($Locale/Conscript);
            }
        

templates/en/Conscript:

            foreach $Source(<*.html>) {
             Install $CONS qq($BIN), qq($Source); 
            }
        

I thought that CONS will first look at templates/Conscript, run the above Command which will generate a set of files in templates/$Locale, and then Build templates/$Locale/Conscript which will Install the generated files into $BIN. This didn't work. Is there a correct/better way to accomplish this?

[Alex Smith , 26 March 1999]
A:     

Cons directives (Install, Build, Command) can be moderately counter-intuitive, because they're not "executed" at the time they're processed by perl. ("Build" is especially problematic, because it doesn't actually *perform* a build of anything, it just reads up the specified subsidiary Conscript files containing build instructions...)

When the Cons directives are processed, they just set up the appropriate dependency tree and store the proper build commands for all the targets in all the Construct+Conscript files. It's not until Cons is actually trying to build the targets specified on its command line (or in the Default method) that it does any "real work" of building or installing files.

Fixing your example to generate an index.html file from an index.in file for each locale:

templates/Conscript:

            @Locales = qw(en fr);
            foreach $Locale (@Locales) {
                Build qq($Locale/Conscript);
            }

        templates/en/Conscript:

            @Inputs = qw(index.in);
            foreach $Source (@Inputs) {
                ($target = $Source) =~ s/\.in$/.html/; # index.in => index.html
                Command $CONS qq($target), qq($Source), q(localize %<);
                Install $CONS qq($BIN), qq($target);
            }
        
[Steven Knight , 27 March 1999]

6.6. Why doesn't Cons detect changes to files outside the local build directory?

A:     

Cons will use MD5 signatures for files outside the local build directory. The catch is that Cons does not, by default, search the standard directories for adding a file to its signature calculation. The search directories must be explicitly specified via CPPPATH or LIBPATH.

With CPPPATH explicitly specified like so:

	  $env = new cons ( CPPPATH => '/usr/include:.' );
	

You get the expected behavior:

	  % cons .
	  cc -I/usr/include -I. -c foo.c -o foo.o
	  cc -o foo foo.o
	  % cons .
	  cons: "." is up-to-date.
	  % su
	  Password:
	  # vi /usr/include/stdio.h
	  # exit
	  % cons .
	  cc -I/usr/include -I. -c foo.c -o foo.o
	  cc -o foo foo.o
	  %
	
[Steven Knight , 17 April 2000]

6.7. Why didn't Cons build things in the order I expected?

This is perfectly normal and nothing to be worried about. Cons orders the build based on its own recursive descent of the targets and their dependencies. Sometimes this looks arbitrary or counterintuitive, but it will build everything it needs to in a proper order, given proper specification of the dependencies. If you need to figure out what dependencies are causing Cons to build things in a specific order, use the -d or -pw options to get more information.


Subject: 7. Using Cons: How do I...?

7.1. Do I have to put a separate Conscript file in each subdirectory?

A:     

No. You could put all of the information about your entire directory tree in one top-level Construct file that uses relative path names for all file references. In practice, though, most people find it a lot more convenient to list the source files and build rules for a given subdirectory's constructions in that subdirectory. Your mileage may vary.

[Steven Knight , 7 April 1999]

7.2. How do I tell Cons that a single command builds multiple targets?

A:     

Make the target of the Command be a reference to an array containing a list of the targets being built. This is most commonly done by enclosing the list in [square brackets].

In order, for a common example, to build target foo.class and bar.class files from a single invocation of the Java compiler on the input foo.java and bar.java files:

            @sources = qw(foo.java bar.java);
            @targets = qw(foo.class bar.class);
            Command $CONS [@targets], @sources, q(javac %<);
        

The Command directive can take a reference to an array in this way in all versions of Cons back to 1.3.1. Beginning with version 2.2.0, the Depends directive can also take references to an array in this way.

A:     

Or, if you have more targets produced from a given set of sources... say, foo.java and bar.java producing foo.class bar.class and baz.class... then this would work for you...

            @sources = qw(foo bar);
            @targets = qw(foo bar baz);

            CompileJava $CONS [@targets], [@sources];

            sub cons::CompileJava {
               my($cons, $tgt, $src) = @_;
               $cons->Command([map(("$_.class"), @$tgt)], map(("$_.java"), @$src),
                   q(javac %< ));
            }
        
[Rajesh Vaidheeswarran , 29 March 1999]

7.3. How do I use different or multiple compilers?

A:     

You need different environments specifying what CC is for each compiler:

            $CONS = new cons();

            # GNU C build environment
            $CONS_GCC = clone $CONS(
                    CC               => "gcc",
            );

            # Microsoft Visual C build environment
            $CONS_MSVC = clone $CONS(
                    CC               => "cl",
            );

            Library $CONS_GCC "mylib.a", qw(a.c b.c c.c);
            Library $CONS_MSVC "mylib.a", qw(x.c y.c z.c);
        

7.4. How do I build multiple software versions from a single source?

Q:     

During one call to cons, we would like to build two versions of a single set of source. We need to direct one source directory to two destinations, since we have to have different object files. We found it a real hassle having to update two sets of very similar sources in separate directories and decided to fold them together and use preprocessor directives to control the compilation.

I didn't find a simple way to do that. Instead, I just created a second set of source files (.cpp) in the same directory as the first, with different names. These source files simply #include their counterpart.

Any cleaner solutions?

[Anthony Kolarik , 3 May 1999]
A:     

Well, in one of the projects at FORE, we build an embedded OS and the boot monitor (bootrom) mostly from the same set of sources.

In another project, we build the exact same sources for a variety of platforms like sunos5, irix6, and other flavors of unix.

In the first case, we need to build both as cross-compiled targets on the same host platform.

In the second case, we will always compile the sources only for the target platform on itself.

The second case is easier compared to the first, so I'll give you an idea of how we approached the first one.

I can't cut and paste the Conscripts here due to the copyrights in them, but I can given you an idea of what you can do.

           # Trees
           $src = "#src";
           $build = "#build";

           $variant1 = "$build/var1";
           $variant2 = "$build/var2";

           Link $build => $src;

           # cons object for variant1
           $cons1 = new cons(CFLAGS => '-DVARIANT1');

           # cons object for variant2
           $cons2 = new cons(CFLAGS => '-DVARIANT2');

           # common sources
           @srcs = qw(a.c b.c c.c)

           # variant1 sources
           @var1src = qw(d.c)

           # variant2 sources
           @var2src = qw(e.c)

           # First Variant of Program `prog' 

           Install $cons1 $variant1, map("$src/$_", @srcs, @var1src);
           Program $cons1 "$variant1/prog", map("$variant1/$_", @srcs, @var1src);

           # Second Variant of Program `prog' 

           Install $cons2 $variant2, map("$src/$_", @srcs, @var2src);
           Program $cons2 "$variant2/prog", map("$variant2/$_", @srcs, @var2src);
        

So, you'll find the final program in variant1 style in build/variant1/prog and in variant2 style in build/variant2/prog

The second case is much easier since you need to just change your build tree to be dependent on the OS.

             chomp ($ostype = `uname`);

             $build = "#build/$ostype";

             Link $build => $src;
        
[Rajesh Vaidheeswarran , 3 May 1999]
A:     

The Link directive can do exactly this when used with multiple build environments. One way is to define multiple Cons environments with different CFLAGS, and then Link+Build the subsidiary program with each environment in turn:

            $ cat Construct
            my $build1 = new cons ( CFLAGS  => '-DVERSION=1' );

            my $build2 = new cons ( CFLAGS  => '-DVERSION=2' );

            $Env = $build1;
            Export qw( Env );
            Link 'build1' => 'src';
            Build 'build1/Conscript';

            $Env = $build2;
            Export qw( Env );
            Link 'build2' => 'src';
            Build 'build2/Conscript';
            $
        

You need to assign each build environment to $Env in turn because that's the environment that the subsidiary 'src' directory's Conscript file is expecting to Import:

            $ cat src/Conscript
            # src/Conscript

            Import qw( Env );

            Program  $Env 'foo', 'foo.c';
            $
        

Because the src directory is Linked twice, once each with the separate build environments, it gets "called" with a different -DVERSION option in each subdirectory. Then, the common .c file uses normal #if/#ifdef to figure out which VERSION is needed:

            $ cat src/foo.c
            /*
             * src/foo.c
             */

            main()
            {
            #if VERSION == 1
                    printf("This is version 1\n");
            #elif VERSION == 2
                    printf("This is version 2\n");
            #else
            #error Must -DVERSION=1 or -DVERSION=2 when compiling!
            #endif
            }
        

Put it all together, and the build looks like this:

            $ cons . 
            cc -DVERSION=1 -c build1/foo.c -o build1/foo.o
            cc -o build1/foo build1/foo.o
            cc -DVERSION=2 -c build2/foo.c -o build2/foo.o
            cc -o build2/foo build2/foo.o
            $ build1/foo
            This is version 1
            $ build2/foo
            This is version 2
            $ 
        

Another variation on this theme would have separate build1/Conscript and build2/Conscript files, each defining their own environment and Linking to the top-level src directory. This is easier to manage if there is a large number of different build environment (or if the environments themselves are complicated).

[Steven Knight , 3 May 1999]

7.5. How can I build or install files above the top-level Construct file?

A:     

Upgrade to Cons 2.0 or later and use "../" to ascend the directory tree as you'd expect. (Prior to version 2.0, Cons didn't know how to build things above its top-level directory without some messy workarounds.)

7.6. How do I link objects created in separate subdirectories?

Q:     

If I build object files spread out in multiple directories, each generated by a separate Conscript file, how can I link them all together into another relocatable module?

A:     

In each Conscript file, include a LinkedModule directive, similar to:

            LinkedModule $ENV "#lib/combined.o", qw (
                source1.c
                source2.c
                object1.o
            );
        

The list of files will be different in each Conscript file, but cons will basically combine all of these lists together to create the final lib/combined.o file.

[Brad M. Garcia , 19 August 1999]

7.7. How do I modify an existing Cons environment?

Q:     

It seems that modifying an environment in cons is a no-no. Any other environments copied or cloned from the original won't have the modifications.

	  $e1 = new cons(VAR => 'abc');
	  $e1->{VAR} = "xyz";
	  print $e1->{VAR}.", ";
	  $e2 = $e1->clone();
	  print $e2->{VAR}."\n";
	

This prints xyz, abc.

I'd like to, when setting up my environments, set up the base env to 'approximately' the right settings, then tweak it depending on various conditions. This says I can't do that.

[Gary Oberbrunner , 22 March 2000]
A:     

Construction environment variables are "frozen" when the environment is created. The way to create a modified environment from a template would be to supply the desired modifications as arguments to the clone() method:

	  $e1 = new cons(VAR => 'abc');
	  $e2 = $e1->clone(VAR => 'xyz');
	

7.8. How do I use Import and Export to pass information between Construct and Conscript files?

A:     

Import and Export arrange for Perl scalar values to be shared between Construct and Conscript files. There are several ways you can use them to pass information.

First, you could put the desired values directly in Perl scalars that can each be Exported/Imported independently:

	  Construct:
	      $CONS = new cons;
	      $INCLUDE = $ENV{INCLUDE} || '/usr/include';
	      Export qw(CONS INCLUDE);
	      Build 'Conscript';
	  Conscript:
	      Import qw(CONS INCLUDE);
	      Depends $CONS 'output', "$INCLUDE/file1", "$INCLUDE/file2";
	

This approach is straightforward but has two drawbacks:

1) Any time you want to pass more information, you have to add more variables to the Import and Export lists in the various Construct and Conscript files.

2) $INCLUDE is a normal Perl scalar and must be expanded to generate the proper path names. Consequently, you can't use Perl operators like qw() which don't expand variables (no "qw($INCLUDE/file1 $INCLUDE/file2)").

A more natural Cons approach would be to put these variables in the Cons environment, and then use Cons' construction-variable expansion (with a % prefix) to extract the values from the construction environment:

	  Construct:
	      $CONS = new cons(INCLUDE => $ENV{INCLUDE} || '/usr/include');
	      Export qw(CONS);
	      Build 'Conscript';
	  Conscript:
	      Import qw(CONS);
	      Depends $CONS 'output', qw(%INCLUDE/file1 %INCLUDE/file2);
	

This eliminates the need to pass additional scalars directly through Export and Import.

7.9. How do I glob filenames in Cons?

In general, globbing file names isn't recommended because it makes the build dependent on the local environment and potentially non-reproducible. This causes problems, especially for large projects: developer A's build will succeed because his build globs a local file that he to forgot checkin, then it fails for everyone else. Explicitly listing all source files avoids these sorts of problems.

As of Cons 2.1.2, you can have Cons chdir to the directory when reading each Conscript file by putting the following anywhere in any Construct or Conscript file in your tree:

	  Conscript_chdir 1;
	

This will allow you to use the normal Perl file-globbing syntax to generate a list of file names:

	  Library $env 'libfoo.a', <*.c>;	# this works in 2.1.2
	

Prior to version 2.1.2, Cons had no way of changing to the Conscript file's directory, so you had to work around it by hand, surrounding any globbing code in a Conscript file as follows:

	  # only necessary prior to Cons 2.1.2!
	  use Cwd;
	  $save_cwd = cwd;
	  chdir(DirPath('.'));
	  # GLOBBING CODE
	  chdir($save_cwd);
        

(Modulo proper error checking and robustness...)

7.10. How does Cons install files?

Cons will try to create a hard link to the file first. If that fails (because the installation directory is on a separate partition or physical drive), then Cons will copy the file.

7.11. How do I get Cons to install a file only if it doesn't exist at all?

Q:     

I have the following command:

            Command $env ("$BIN/license.txt", "license.txt",
                          "test -e %> || cp %< %>");
        

The intended result is "replace this file only if it doesn't exist at all in $BIN." But of course it doesn't work, because cons deletes the target before it runs my command, so it always does the copy. Bummer.

Is there a way, short of a fake target that always runs, to do this?

(BTW, what I *really* want is for it only to do the cp if %< is newer than %>, any idea how to do that? Anyway, the existence test is good enough for my application.)

[Gary Oberbrunner , 6 October 1998]
A:     

Assuming that license.txt is not a derived file, then would this do?

            system("cp license.txt $BIN") unless -e "$BIN/license.txt";
        

If license.txt is derived, then this won't do, since it will be done too soon (before the derivation of the file).

[Bob Sidebotham , 6 October 1998]

7.12. How do I replace individual modules in a library?

Q:     

I'm looking for a way to replace files in an existing library using cons. i.e. I have some library 'libfoo.a' containing 'bar.o' and 'baz.o'. I want to build my own version of 'bar.o' from 'bar.c' and then build a new 'libfoo.a' with the new 'bar.o' and the old 'baz.o'.

Is there any easy way to accomplish this? The only way I've come up with so far (I haven't tried it, and it will be a hack regardless) is to extract all of the objects from the existing library and then repackage the whole thing, because cons wants to delete the existing library before it rebuilds it rather than just replacing the files.

[Todd J. Derr , 29 October 1998]
A:     

As of Cons 2.0, you can prevent the library (target file) from being removed before being rebuilt by using the Precious method:

	  Library $CONS "libfoo.a", @objects;
	  Precious "libfoo.a";
	
A:     

On most systems (Solaris, Irix at least) it is faster (sometimes by a lot) to rebuild the archive from the .o files than to replace ones in the middle.

[Gary Oberbrunner , 29 October 1998]

7.13. How do I build shared libraries?

Q:     

The Library method is hard-wired to generate non-shared libraries (.a files). How can I build shared libraries (.sl files)?

A:     

As a quick-and-dirty approach, you can use the Program method to build a shared library directly by setting appropriate values for CFLAGS, LINK and LDFLAGS:

            $slenv = $env->clone(CFLAGS => '-fPIC',
                                 LINK => 'gcc',
                                 LDFLAGS => '-shared'); 

            Program $slenv "mydll.sl" "file1.o", "file2.o", ...;
        
A:     

Try adding the function below to your Construct. Use just like Library. You must do the following to use it:

        - Set LINK,LDFLAGS, etc. in your slenv environment to include
          '-shared' as above.
        - Set SUFSHLIB to '.sl'
        - Set SUFLIBS to '.sl:.a' so the shared lib will be found as a
          dependency by your program.  This will solve the problem you are
          having, assuming you are using -lmydll to link with your lib.

        ########################################################################
        # SharedLibrary
        ########################################################################
        # Usage: just like Library, but builds a shared lib.  User must set
        # CFLAGS,LINK,LDFLAGS etc. to proper options for building a shared lib.
        sub cons::SharedLibrary {
            my($env) = shift;
            my($lib) = $dir::cwd->lookup(file::addsuffix(shift, $env->{SUFSHLIB}));
            my($libenv) = $env->_resolve($lib);
            $lib->bind(find build::command::link($libenv, $libenv->{LINKCOM}),
                       $env->_Objects(map($dir::cwd->lookup($_), @_)));
        }
        
[Gary Oberbrunner , 5 March 1999]

7.14. How do I share files between builds for different architectures?

Q:     

I try to set up a build environment where several architectures are built in parallel, but certain targets (for example C files generated by yacc) should be shared between all architectures.

When I try to do this, however, cons always rebuilds the shared targets. I found the reason is that cons includes the build command's time stamp into the target file signature, i.e. if you have a target like

            Command $CONS "y.tab.c", "a.y", "yacc a.y";
        

The signature of y.tab.c contains the command name "yacc" (without path) and the modification time of /usr/bin/yacc, /usr/ccs/bin/yacc or wherever yacc resides. In general, this is probably an advantage, because it forces a rebuild whenever new compiler versions are installed.

Any ideas how to address this problem? One could wrap a simple perl or shell script around yacc. With the script belonging to the source code, the modification time of the script would be the same for all archi- tectures. But this is not exceptionally elegant ...

[Jochen Schwarze , 8 May 1998]
A:     
            Ignore "/yacc\$";
        

But if you ever upgrade yacc, y.tab.c will not get rebuilt automatically.

You might be better off by simply rebuilding y.tab.c for each platform, rather than trying to share it among all platforms.

Another possibility is to create "fake" .consign files in the directories where yacc lives, and give every copy of yacc the exact same checksum.

[Brad M. Garcia , 8 May 1998]

7.15. How do I change the suffix for object files?

Q:     

I'm working on a Construct file which I am trying to set up for multiple compilers and environments.

One of the compilers assumes that objects have a '.obj' ending, not '.o', and it tries to compile '.o' files. Needless to say, this is undesirable :-)

Is there some way to inform cons that the default object extension should be obj?

[Ron Aaron , 13 September 1999]
A:     

Within the cons environment, re-define SUFOBJ:

            $ENV = new cons(
                ...
                SUFOBJ       => '.o',
                ...
            );
        
[Brad M. Garcia , 13 September 1999]

7.16. When using Repositories, must I change third-party code to use #include <...> instead of #include "..."?

A:     

When using third-party stuff from a repository, you only need to change the #include "..." lines if you're going to make your own modifications to any included (.h) file. If you're going to leave it shrink-wrapped, then you can leave the quotes alone and it should pick up everything from the repository just fine.

This loses only if you want to (or must) use a more-recently-modified local .h file instead of what's in the repository. The C preprocessor will see the #include "..." and always use the repository copy, no matter what Cons does with -I flags to try to get it to use the local one.

7.17. How can I add another command to be executed after a target is built?

A:     

Derive a new $env with the correct added line. Newlines separate commands in cons so this should work. Here's a working Construct:

	  $env = new cons();
	  $regenv = $env->clone(LINKCOM => $env->{LINKCOM} . "\necho done!!!\n");
	  Program $regenv 'hello', 'hello.c';
	

It outputs this:

	  % cons hello
	  cc -c hello.c -o hello.o
	  cc -o hello hello.o
	  echo done!!!
	  done!!!
	
[Gary Oberbrunner , 1 September 2000]

Subject: 8. Dependencies

8.1. How does Cons determine build dependencies?

Unlike Make, which requires that dependencies be listed explicitly in the Makefiles, Cons scans source files directly to determine the dependent files. This requires that Cons understand the dependency rules for a given language that it is building. For C and C++, Cons has efficient built-in scanning rules that use Perl's regular expressions to search for #include lines. There is no native Cons support for other languages, but Cons offers a QuickScan feature that allows you to write a new dependency-scanning function and associated it with source files that require it. Consult the documentation for details on how to use QuickScan.

8.2. Does Cons re-scan a file every time it calculates dependencies?

Not within a single invocation. If multiple source files all #include a given .h file, then Cons only scans that .h file (and any subsidiary .h files that it includes) once, and remembers the result for later files that also #include that .h file.

Every time Cons is invoked, however, it does re-scan each .h file once.

8.3. How can I have Cons list the build dependencies?

The -d option does this. (Note that -d performs a build at the same time; there isn't currently a way to only list dependencies without building.)

8.4. Will Cons use C preprocessor statements to determine dependencies?

No. Any change to a file, regardless of whether it's surrounded by a #ifdef, will change the file's MD5 signature and cause a rebuild of all targets that depend on that file. So cons may believe that a file depends upon more than it actually does. This shouldn't hurt anything. If cons can't actually find one of the files that it believes should be a dependency, it simply ignores it.

8.5. Shouldn't the platform be part of Cons' dependency analysis?

Q:     

I'm trying to use Cons to build programs on several different platforms (Solaris, HP-UX, Linux, NT at least).

I was somewhat surprised that that the following could happen:

            hpux% cons hello
            cc -c hello.c -o hello.o
            cc -o hello hello.o
            hpux%
            hpux% rlogin solaris
            solaris%
            solaris% cons hello
            cons: "hello" is up-to-date.
        

Shouldn't the "platform" be part of the signatures in some way?

[Johan Holmberg , 16 August 1999]
A:     

I would prefer that it isn't. We do cross-compiles for the same target machine on several different host machine architectures. Having the platform NOT be part of the signature means we can start a build under Solaris and re-compile just the changes on a Linux box without rebuilding the whole system.

[Brad M. Garcia , 16 August 1999]
Q:     

Is there some simple way I can accomplish this myself?

[Johan Holmberg , 16 August 1999]
A:     

You can accomplish this yourself by adding a "Salt" statement to your Construct file, similar to the following:

            $host_type = `uname -s`;
            Salt($host_type);
        

You can use whatever you want to use to distinguish your build platforms as an argument to Salt.

[Brad M. Garcia , 16 August 1999]

8.6. Shouldn't the executable path be part of Cons' dependency analysis?

Q:     

I'm trying to use Cons to build programs on several different platforms (Solaris, HP-UX, Linux, NT at least).

I was somewhat surprised that that the following could happen:

On HPUX I have two different compilers named "cc", one in /usr/bin and one in /usr/ccs/bin. If I change my Construct file to

            %env = new cons()->copy();
            $env{CC} = "cc";
            $env{ENV}{PATH} = "/usr/local/bin:/bin:/usr/bin";
            $env = new cons(%env);
        

then build hello, and then later change Construct to

            %env = new cons()->copy();
            $env{CC} = "cc";
            $env{ENV}{PATH} = "/usr/ccs/bin:/usr/local/bin:/bin:/usr/bin";
            $env = new cons(%env);
        

then cons still thinks hello is up-to-date.

Shouldn't the actual path to "cc" used in the command be part of the signature, insead of just "cc" as I suspect it is now?

[Johan Holmberg , 16 August 1999]
A:     

Perhaps. The dependency analysis for ".o" files could be modified so that it also depends upon the compiler executable file as well as the source files. I would rather have it implemented in this manner, rather than simply going by the file path.

For now, you could simply use $env{ENV}{PATH} as part of the argument to the Salt command.

[Brad M. Garcia , 16 August 1999]

Subject: 9. Language Support

9.1. Does Cons support C?

Yes. Cons was originally written to do a better job than Make at compiling C projects, largely by integrating C dependency analysis into the build tool.

9.2. Does Cons support C++?

Yes. Cons build environments have a SUFMAP by which Cons understands that C++ file suffixes (.cc, .cxx, .cpp) can be built using its internal C compiler object (i.e., using the same compiler used for C). The suffixes are part of the default environment, so you don't need to modify the SUFMAP to get C++ support.

By default, Cons will use the same compiler for C and C++. If you need to use different compilers for C and C++, set the 'CXX' environment variable to the desired C++ compiler.

9.3. Does Cons support Java?

Using Cons to build Java projects is possible, but not straightforward. Java has a number of characteristics that don't fit well with the Cons (and Make) model of creating a dependency tree to determine what needs rebuilding:

        --  Java dependency analysis is more complex than the simple
            #include scanning of C and C++, arising from the classes
            used by the code in addition to the import statements.
        --  As a result of the class dependencies present, the Java
            compiler may create or update multiple output class files
            and directories.
        --  Java may automatically rebuild class files, even if not
            explicitly given as an argument to the compiler.
        --  Java may read possibly obsolete class files from previous
            compiler runs.
      

From the point of view of an outside build tool, the Java compiler is essentially unpredictable, creating or updating whatever files it decides need it. This makes it extremely difficult for a build tool such as Cons to decide what needs updating, or has been updated, by Java.

One approach that has been suggested would extend cons to scan the output of the Java -depend -verbose command to build dependency lists. Some additional mechanism would need to be invented to store Cons signatures on classes that are stored in archives (jar or zip files). This solution does not exist today.

An alternative approach that is already in use by Jochen Schwarze (Jochen.Schwarze@orthogon.de) is to use an intermediate script that passes a list of Java files to the java compiler, and puts the output class files in a new temporary directory. The script then calls jar to pack the newly-generated class files into a single jar file, essentially sidestepping the dependency issue. A second script is then used to merge the multiple jar files generated by the first script into a single file. This approach fits Cons more readily because the first script acts somewhat like a meta-compiler, creating a single object from Java source files, and the second acts like a linker, creating a single executable from multiple "objects."

The advantage to this approach is that it makes the builds very reliable, because there's no chance that an obsolete but unremoved class file will be incorporated into a build by the javac compiler. The disadvantage, however, is that there's no way to incrementally update a portion of a jar file, since from Cons' point of view, each is created atomically by a single call to the "compile" script.

Jochen has offered in the past to make his scripts available, but he is very busy, so be patient and courteous if you ask him for them.

9.4. Does Cons support Fortran?

Not natively. As distributed, Cons can only parse C/C++ files for include dependencies. You would need to use QuickScan to add Fortran-scanning capability to your own Construct/Conscript files.


Subject: 10. Windows NT

10.1. Does Cons recognize Windows NT absolute path names?

A:     

Yes, as of Cons 2.0, Windows NT absolute path names can be specified either with volume letters (c:\msvc42\include) or as a UNC path (\\share\include).

(Prior to version 2.0, Cons required a counter-intuitive work-around of beginning volume-letter absolute path names with a '#' to avoid some internal processing.)

10.2. On Windows NT, how do I specify an absolute path name in CPPPATH?

Q:     

How can you specify an absolute path name in NT in CPPPATH. I want CPPPATH to have something like v:\msvc42\include in it.

I can't put the colon in there because that is the delimiter that CONS uses in the CPPPATH. But if I don't put it in there then CONS does something like

                cl /I\v\msvc42\include
        

which doesn't work either.

[Chris Bartz , 29 September 1998]
A:     

In Cons version 1.4a2, you can make CPPPATH be an array reference:

                CPPPATH => [ "#v:\msvc42\include", "another directory", ... ]
        

This is now the preferred way to specify a path; the old way is deprecated.

[Bob Sidebotham , 29 September 1998]

10.3. How does Cons deal with case insensitivity in Windows NT?

Q:     

I have a problem in NT because of the odd case-insensitivity of NT. If I have one .c file that '#includes "abc.h"' and another that '#include "ABC.H"', they are really including the same file (assuming the same directory or include path, etc.). NT remembers the case that was used to create the file so the filename might be "abc.H", or "Abc.h", whatever, but the name is case insensitive because no matter what case you use to open the file, it will work.

But cons treats them as different. The real problem is where "abc.h" is generated or "Installed" into the location where the compiler will find it. The file is not created in time and/or it is deleted (i.e. unlinked from the Link directory).

When scanning .c files for #includes, if I change all the include file names to lower case, then things behave as expected.

[Chris Bartz , 19 October 1998]
A:     

It seems that some sort of change to recognize case insensitivity would be a useful change for NT.

It'd be best, however, to change the directory and file handling modules to generically convert all file names to lower case.

[Bob Sidebotham , 19 October 1998]

10.4. How do I build a DLL using Cons?

TO BE WRITTEN. (There is some mailing list discussion of this topic that needs editing, in case anyone cares to volunteer.)


Subject: 11. Extending Cons

11.1. How do I add support for new file types (suffixes)?

Q:     

Cons has a lot of hard-wired knowledge about building linking object files and executable files from C code. Are there suffix rules for different kinds of source files, like make does?

A:     

As long as you're willing to explicitly list all the targets to which your suffix rule will apply, you can use the following in your Construct:

                sub cons::SufRule
                {
                  my($env, $frsuf, $tosuf, @srcs) = @_;
                  my($rule) = pop @srcs;
                  foreach (@srcs) {
                    s/$frsuf$// || next;
                    Command $env "$_$tosuf", "$_$frsuf", $rule;
                  }
                }
        

Then your Conscript can do things like so:

                $CONS = new cons;

                SufRule $CONS ".pl" => ".c", @INIT, '%PERL %< > %>';
        

which allows a bunch of C files to be generated from perl scripts, or

                SufRule {$CONS->clone(CFLAGS => "-dD -E")} ".c" => ".i", @SRCS,
                        '%CCCOM';
        

which builds preprocessed versions of C files when I ask for them.

[Jeff Rosenfeld , 17 April 1998]

11.2. How do I submit a bugfix or a new feature to be added to Cons?

Short answer: Submit your patch to the mailing list (diff -c or diff -u output). After discussion, and barring objections, your patch may be incorporated into a future development release, and then into a stable release some time later.

Long answer: A number of things have to be done to make a patch ready for release. The more of these you do yourself before submitting your patch, the easier it is to incorporate, and the quicker and more likely the patch goes in. For example, if you don't document your new feature yourself, the patch will have to wait until someone else has time to document it...

An optimal patch will include:

Working code.

Cons and its regression tests work for Perl versions all the way back to 5.2. Consequently, Cons intentionally doesn't use some modern Perl idioms (like "foreach my $variable", which wasn't introduced until Perl 5.4). This means you should avoid "helpful" syntax cleanups unless you know for certain (preferably through actual testing) that your change works on all targeted Perl versions and platforms.

Updated documentation.

The POD documentation for Cons is at the end of the Cons script itself.

A new or updated regression test.

The test should demonstrate the new feature or fixed bug. That is, the current (non-patched) version of Cons should fail the test as expected, and your patched version should pass the test. (If the current version of Cons doesn't fail the test, then either the test doesn't really check for the bug you're fixing, or Cons already does what you want it to!)

Copyright assignment to the Free Software Foundation.

The Free Software Foundation requires that anyone contributing code to an FSF project (other than small two- or three-line patches) sign a document that makes the FSF the owner of any contributed work. This is to avoid any possibility of a future legal challenge to the FSF's right to distribute Cons freely.

This assignment document must be physically signed and snail-mailed to the FSF to keep the lawyers happy. The Cons maintainers (contact the FAQ author) can supply you with the appropriate document and instructions for printing it out and mailing it in.

You only have to sign this document once, and then any future patches you contribute to Cons are covered.