lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] autotools versus makefiles


From: Greg Chicares
Subject: Re: [lmi] autotools versus makefiles
Date: Sat, 11 Feb 2006 07:32:42 +0000
User-agent: Mozilla Thunderbird 1.0.2 (Windows/20050317)

On 2006-1-23 1:48 UTC, Vadim Zeitlin wrote:
> On Sat, 21 Jan 2006 16:48:12 +0000 Greg Chicares <address@hidden> wrote:
> 
> GC> I'd very much like to support more compilers.
> 
>  Let me make it clear that I'm perfectly aware that you can support
> multiple compilers with manually written makefiles. However using autoconf
> gives an advantage of providing standard (this is the word I'm using a lot
> when speaking about autoconf -- but only because it is, after all, one of
> its main advantages) procedures for dealing with the issues which arise
> with them. I.e. you may/will still have to write different compiler flags
> for different compilers but with autoconf this is centralized in a single
> place and better organized and, although this could be subjective, easier
> to understand.

That's what autoconf aspires to, not what it achieves today. I have three
of the nine compilers (mwcw, gcc, intel, hp, sun, como, msvc, borland, dmc)
that boost regression-tests. I ran boost's ./configure with those three,
and it worked only with gcc. Presumably it wouldn't work with the intel
compiler, either, which is based on EDG (as is como). Probably it would
work with the *nix compilers--gcc on GNU/Linux, hp, and sun. But all of
our users are on msw.

> GC> How would autotools make this easier? I find a thread starting here
> GC>   http://sources.redhat.com/ml/automake/2001-05/msg00359.html
> GC> that seems to suggest that it's difficult to use automake with a compiler
> GC> other than gcc.
> 
>  The reply in this thread makes it quite clear that the goal of autoconf is
> to support all compiler but that, unfortunately, sometimes some GNUisms
> slip in.

The incompatibilities are more severe than you expected.

I thought it might be helpful to use boost's autoconf stuff, which
is geared toward C++. There are problems with these msw compilers:

borland:
  PATH=$PATH:/c/Borland/BCC55/Bin/
  export CC=bcc32
  export CPPFLAGS=-I"c:\Borland\Bcc55\include"
  export LDFLAGS=-L"c:\Borland\Bcc55\lib"
  ./configure
config.log says:
  configure:1595: bcc32 -o conftest.exe -Ic:\Borland\Bcc55\include [...]
  Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
  Error E2194: Could not find file 'conftest.exe'
  conftest.cc:
  configure:1598: $? = 1
  configure:1616: error: cannot compute suffix of executables: cannot compile 
and link
With this compiler, '-o' names an object file, not the executable.

como:
  checking for C++ compiler default output file name
  configure:1550: result: b.out
  configure:1555: checking whether the C++ compiler works
  configure:1561: ./b.out
  /c/boost/boost_1_31_0/libs/config/configure: ./b.out: No such file or 
directory
With this compiler, the default output file is 'aout.exe', not 'b.out'.

> And automake goes to great lengths to not require the use of GNU
> make as otherwise many, many things would have been much simpler (although,
> of course, automake does work excellently with gmake and it does require
> VPATH support from the make program which some BSD makes don't have).

VPATH isn't supported by borland's make, or ms's as I understand it.
Workarounds for other *nix make implementations don't help us on msw:
we have to require GNU make anyway.

>  From my personal experience, I've never had any problems due to not using
> gcc with autoconf itself. I did have many problems with configure scripts
> which assumed gcc (obvious example is putting gcc-specific flags in CFLAGS)
> but this can hardly be blamed on autoconf. Also, I didn't use autoconf with
> neither bcc nor como so far but I did use it with just about all standard
> Unix C/C++ compilers I know about (Sun CC, HP-UX aCC, SGI mipsPro, AIX xlC)
> without problems.

Let's try using autotools with MSYS to build gnu 'hello'
  http://ftp.gnu.org/gnu/hello/hello-2.1.1.tar.gz
whose 'README' says "The GNU hello package serves as an example of
GNU package distribution and code." But this exemplary package
doesn't build with gcc in MSYS:

  /c/tmp/hello-2.1.1[0]$./configure && make

  localename.c: In function `_nl_locale_name':
  localename.c:630: error: `LANG_SORBIAN' undeclared (first use in this 
function)

I've seen this problem with several other packages--often enough to have
learned to that `LANG_SORBIAN' diagnostics suggest the following workaround.
I imagine autoconf could be enhanced to do this automatically, but today we
have to do it manually:

  /c/tmp/hello-2.1.1[0]$./configure --disable-nls && make

  checking for stdlib.h... yes
  checking for string.h... yes
  ...
  checking stddef.h usability... yes
  checking stddef.h presence... yes
  ...
  hello.c:34:17: pwd.h: No such file or directory
  hello.c: In function `main':
  hello.c:257: warning: initialization makes pointer from integer without a cast
  hello.c:263: error: dereferencing pointer to incomplete type

But we're still left with a fatal error not solved by autotools.

This seems to be geared toward platforms that
 - have nls support, though that's not in the C standard, but
 - may lack required C headers standardized in 1989, yet
 - always have 'pwd.h', which is not standard C.
There's even code to make it work with compilers that lack 'const'.
Here, autotoolization works around the absence of standard C features,
but doesn't help with C features that are required but nonstandard.
It bridges the gap among obsolete C dialects more than a quarter of a
century old, for a family of platforms that doesn't include msw.

But lmi's requirements are the opposite. I want to
 - require standard C++;
 - push nonstandard features into libraries, or at least segregate them;
 - require four standard C99 functions, or use tested workarounds.
Sure, autoconf can test for those four C99 functions, but if they're not
present, the linker tells you, just as hello's 'configure' tells you
(more cryptically) that nls is absent. In that case, it's OK to fail and
force the porter to find a workaround that passes lmi's unit tests.

Automatically using a workaround that may not pass the tests isn't very
valuable: it gives a system that runs, but may give incorrect results.
In our intensely regulated industry, we need a system that's correct:
in production, a system that runs but gives incorrect results is worse
than one that doesn't run at all. The problems that matter for lmi
aren't the problems solved by autotools.

And gnu 'hello' works even less well with the other msw compilers I have,
e.g.:

  make distclean
  PATH=$PATH:/c/Borland/BCC55/Bin/
  export CXX=bcc32
  export CXXFLAGS=-I"c:\Borland\Bcc55\include"
  export LDFLAGS=-L"c:\Borland\Bcc55\lib"
  ./configure --disable-nls && make

As with boost, above, this results in

  cannot compute suffix of executables: cannot compile and link

so it's not that boost wrote their 'configure' incorrectly.

Clearly, the msw platform and some of its well-known compilers are at
a real disadvantage with autotools.

> GC> >  A few other things:
> GC> > 
> GC> > - tests for functions can be written manually,
> GC> 
> GC> I believe that only these four C99 functions have proved to be 
> problematic:
> GC>   expm1(), log1p(), snprintf(), strtold()
> 
>  So far, yes.

I would not assume that we'll necessarily use others.

> GC> but they're all used only in very isolated cases. We have macros like
> GC>   LMI_COMPILER_PROVIDES_EXPM1, LMI_COMPILER_PROVIDES_STRTOLD
> GC> and 'config_*.hpp' files to define them. We need another such file for 
> each
> GC> new compiler. We can write it by hand, or autoconf can write it.
> 
>  I'd rather say that we can write N different error-prone (because they
> will involve testing the compiler version and you don't always know for
> sure when was the particular function added/removed and then there are also
> strange but occuring in practice cases of using old compiler with newer
> runtime libraries) tests for every compiler or write 1 autoconf test.

Autoconf can determine whether snprintf() is provided, but so can any
linker. Neither, AFAICT, determines whether it's implemented correctly.
The lmi unit test for snprintf() tests the implementation. I've worked
around defects in two implementations; autoconf doesn't automate that.

The problem I'm interested in here isn't a problem that autoconf solves.
I don't want lmi used with a system that lacks, say, snprintf(), because
of the security risks of sprintf(). If a system doesn't have snprintf(),
then I don't mind saying: find a aystem-specific workaround that passes
the unit tests, or don't use that system at all. If a new system has
snprintf() and I haven't detected that correctly, then I don't mind
saying: change the lmi configuration header and run the unit tests.

Readers with less experience than you might wonder: why care about
snprintf() if it seems to raise portability issues? Avoiding sprintf();
preferring snprintf() to iostreams, in exactly one line of production
code, based on actual timings; testing the snprintf() implementation
carefully--these aren't personal idiosyncrasies. Rather, I'm following
this advice:
  http://www.gotw.ca/publications/mill19.htm
from a ms architect who chairs the C++ standards committee, and the
lmi unit test for snprintf() guards against the error he points out in
his own company's implementation--which is used by MinGW as well as,
of course, msvc.

> GC> I think we should treat expm1() like this (untested):
> GC> 
> GC>   #if !defined LMI_COMPILER_PROVIDES_EXPM1
> GC>   inline double expm1(double x)
> GC>       {
> GC>       global_untrustworthy_flag = true;
> GC>       return std::exp(x) - 1.0;
> GC>       }
> GC>   #endif // !defined LMI_COMPILER_PROVIDES_STRTOLD
> GC> 
> GC>   int main()
> GC>       {
> GC>       if(global_untrustworthy_flag)
> GC>           warn("Results may be invalid.");
> GC> 
> GC> and the other three C99 functions similarly.
> 
>  If it's safe to assume that expm1() is called in all calculations (isn't
> it?) then it could be simpler to write it as
> 
>       int main()
>       {
>       #if !defined(HAVE_EXPM1) || !defined(HAVE_LOG1P)
>               warn("Results may be incorrect.");
>       #endif
>       }

Yes, thanks, that's much better. I'll do that.

> GC> > but configure may also do
> GC> >   other compile-time checks, e.g. verify whether the correct version of
> GC> >   wxWidgets is available (and built with correct options),
> 
>  Before continuing with wx-config, let me emphasize once again that
> autoconf can be used to test all build settings dynamically instead of
> hardcoding them statically into makefile. It's reminiscent of using
> hardcoded constants to run-time variables in the code. And the advantages
> of autoconf are comparable: easier maintenance, more clarity in the
> makefiles.

I'm interested in using wx-config to make the lmi makefiles clearer
and easier to maintain.

> GC> IIRC, building wx with autotools creates 'wx-config', a script that makes
> GC> it easy to retrieve the options wx was built with, so that we wouldn't
> GC> need to write, e.g.,
> GC>   -DWXUSINGDLL
> GC>   -D__WXDEBUG__
> GC>   -DNO_GCC_PRAGMA
> GC> in the lmi makefiles (and manually keep them coordinated with the wx build
> GC> options we're using, which may change over time).
> 
>  Exactly.
> 
> GC> But this can be done in the makefiles, too, can't it?
> 
>  It certainly can. What makefile can't do is to test whether wx-config
> exists and is correct (e.g. doesn't correspond to Unicode build of wx which
> is not suitable for lmi). Of course you'd still get an error sooner or
> later but it risks to be much less clear.

I don't understand. I can test a file's existence with [ -e filename ],
but of course you know that already. If I'm not sure where it resides,
I could use 'find', though that risks finding a variety of different
builds. If options aren't suitable, then messages like

  #if wxUSE_THREADS
  #   error Disable wxUSE_THREADS in wx setup.
  #endif // wxUSE_THREADS

in 'wx_checks.cpp' seem clear enough to me, and I think wx backs that
up with comprehensible warnings of its own.

> GC> > - we gain a few very nice features for free when using autotools:
> GC> >    . standard configure arguments such as --prefix or --enable-debug:
> GC> >      it's really convenient to be able to specify them in a usual way
> GC> >      instead of having to modify the makefiles or pass the flags on
> GC> >      command line
> GC> 
> GC> Is this only the difference between
> GC>   configure --enable-debug --prefix='/foo/bar' && make
> GC> and
> GC>   make CXXFLAGS='-g' prefix='/foo/bar'
> GC> ?
> 
>  There are a few differences here:
> 
> 1. "make --help" won't give you possible values for CXXFLAGS but configure
>    will give you --enable-debug

OTOH, reading the makefile will give you, for example,
  build_type=mpatrol
but configure won't.

And even '--enable-debug' doesn't work with gnu 'hello', which is
supposed to be a "model for all of the GNU coding standards":

  /c/tmp/hello-2.1.1[0]$./configure --help |grep debug || echo 'debug not found'
  debug not found

> 2. "-g" is not going to work for msvc (it needs "-Zi") and --enable-debug
>    could hide it

But autoconf won't know that borland needs both '-v' in CXXFLAGS and '/v'
in LDFLAGS.

> 3. you have to specify make parameters every time and this is painful if
>    you recompile repeatedly -- with configure you normally just run it once
>    and then just do "make"

To compile repeatedly, I just use command history. Of course, you can
also set options in a trivial script. I changed 'GNUmakefile' on
2005-06-26 to let you set a variety of parameters in the environment.
I wouldn't mind including a 'user_configuration.make' file, if found,
which would offer another way of setting persistent options; actually,
that seems much better than testing for a fixed list of options in
'GNUmakefile', so I think I'll do that instead.

We could write a script to provide semantic sugar:

  $cat user_options
  #!/bin/sh
  echo "# User options" > user_options.make
  for z in "$@"; do echo "$z" >> user_options.make; done

and write "-include user_options.make" in 'GNUmakefile'.

  $cat test_user_options.make
  -include user_options.make
  .PHONY: all
  all:
        @echo CXXFLAGS is $(CXXFLAGS)

and it works:

  $sh user_options CXXFLAGS='-O0 -ggdb -x cpp'
  $make -f test_user_options.make
  CXXFLAGS is -O0 -ggdb -x cpp

I find that useful myself, so I'll commit it to cvs.

> GC> >    . possibility to build in another directory: this is much more tidy 
> than
> GC> >      building in the source directory
> GC> 
> GC> The lmi makefiles do that already.
> 
>  Sorry, I should have been more clear. What I meant was to build in _any_
> other directory. E.g. a common situation is to have sources on an NFS
> volume and then you really don't want to build on it so typically you'd
> build in /tmp/foo using sources from ~/src/foo.

Then do this:
  make build_directory='/tmp/foo'

> GC> >    . make targets such as install, dist, ... are implemented 
> automatically
> GC> >      (this is done by automake and not autoconf but it doesn't matter)
> GC> 
> GC> We have an 'install' target, and it uses 'prefix' and 'exec_prefix'
> GC> as prescribed here, AFAIK:
> GC>   http://www.gnu.org/software/make/manual/html_chapter/make_14.html#SEC131
> GC> We have a 'test' target; maybe I should name it 'check' to conform to
> GC> typical GNU practice. We have 'clean', 'distclean', 'mostlyclean', and
> GC> 'maintainer-clean' just to conform to GNU standards, even though they all
> GC> happen to do the same thing for now.
> 
>  Yes, but you still need to maintain these targets. Automake takes care of
> them transparently.

Let's compare two approaches, and judge transparency and suitability
for msw based on objective facts.

I tested 'make clean' with gnu 'make-3.81beta4'; it doesn't remove
 - borland '.obj' files
 - EDG '.ii' and '.ti' prelinker files
 - '.bak' backup files created by many msw tools
 - '.lib' files created by msvc
 - '.dll' files created by any msw toolset
That's not reasonable behavior for msw.

Now, lmi's 'workhorse.make' has a clean target like this:

  files_normally_created_by_running_make := \
    $(edg_prelinker_files) \
    $(wildcard lib*.a) \
    [...]
  @-$(RM) --force $(files_normally_created_by_running_make)

and it's easy to make it do whatever else we may need.

OTOH, gnu 'make-3.81beta4' has the following rule, which doesn't do what
we need for msw, and I don't think it would be trivial to fix it--for me
at least, the clean target above is transparent, but this one is opaque:

mostlyclean-recursive clean-recursive distclean-recursive \
maintainer-clean-recursive:
        @failcom='exit 1'; \
        for f in x $$MAKEFLAGS; do \
          case $$f in \
            *=* | --[!k]*);; \
            *k*) failcom='fail=yes';; \
          esac; \
        done; \
        dot_seen=no; \
        case "$@" in \
          distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
          *) list='$(SUBDIRS)' ;; \
        esac; \
        rev=''; for subdir in $$list; do \
          if test "$$subdir" = "."; then :; else \
            rev="$$subdir $$rev"; \
          fi; \
        done; \
        rev="$$rev ."; \
        target=`echo $@ | sed s/-recursive//`; \
        for subdir in $$rev; do \
          echo "Making $$target in $$subdir"; \
          if test "$$subdir" = "."; then \
            local_target="$$target-am"; \
          else \
            local_target="$$target"; \
          fi; \
          (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
          || eval $$failcom; \
        done && test -z "$$fail"

I looked in 'glob/Makefile', and saw only

  CONFIG_CLEAN_FILES =
  distclean-generic:
        -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)

which would appear to do nothing. Maybe I'm missing something, but I
don't think I should spend the time to figure it out when I can write
a clean target that's straightforward, flexible, and maintainable.

> GC> >    . without speaking of automatic dependency tracking
> GC> 
> GC> For gcc and perhaps msvc. What about borland? What about como? Anyway, we
> GC> already use the autodependency method Tom Tromey invented for automake.
> 
>  While I don't think automake supports neither borland nor como
> dependencies tracking, we can hope that it will continue to develop and
> support more compilers.

I join you in the hope that the autotools project will one day become
suitable for msw, bridging the gaps between compilers and between OS
variations there as well as it has for gcc and *nix.

However, business decisions must be made on the basis of today's cold,
hard, objective facts, not future hopes. Today, for the msw platform
that is crucial to lmi, autotoolization offers little useful benefit,
not nearly enough to justify its complexity and the extra burden it
imposes on me and my coworkers. Thank you for this discussion, from
which I've learned much. Now, I have enough facts to make a decision,
and that decision must be: the lmi makefiles remain the primary means
of building the system, and all work on lmi has to be fully supported
by that method in order to meet the business needs of the organization
that's funding lmi development.

I'd like to keep the autotools implementation, as a strictly secondary
build system, provided we can find ways to simplify its maintenance,
as in the final part of our discussion that I've snipped here. I'll
follow up on those ideas as time permits.




reply via email to

[Prev in Thread] Current Thread [Next in Thread]