gnulib-tool-py
[Top][All Lists]
Advanced

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

Re: [gnulib-tool-py] func_modules_transitive_closure


From: Bruno Haible
Subject: Re: [gnulib-tool-py] func_modules_transitive_closure
Date: Fri, 29 Jun 2012 21:28:29 +0200
User-agent: KMail/4.7.4 (Linux/3.1.10-1.9-desktop; KDE/4.7.4; x86_64; ; )

Hi Dmitriy,

> I'm a bit confused with func_modules_transitive_closure function,
> especially with its conditions part. Previous experience has shown that it
> would be better not to write the exact copy of the code, but do what is
> supposed to be done. Could you, please, tell what you expect in this
> section?

OK. Let me explain it in three steps, with increasing complexity:

1) Ignoring --with[out]-*tests options and conditional dependencies.
2) Ignoring conditional dependencies.
3) The full variant.

1) The input is a set L of modules, the output is the smallest set C
of modules that contains the input set and that is closed under
dependencies. "Closed under dependencies" means:
if m ∈ C and o ∈ dependencies(m), then o ∈ C.

In the original code, lists are used for representing the sets. In
Python, you can use hashed sets (unordered) if it fits better than lists.

The basic algorithm is to start with the set L, pick an element m
and one of its dependencies o, look whether o is already in the set, and
if not, add o to the set. Then repeat, repeat, repeat...

This algorithm is modified to cope with two problems:
  - You need to know when to stop.
  - Adding an element to a set over which you are currently iterating is
    normally a forbidden operation (or may produce undefined behaviour).
    Namely, the "add" operation may reorganize the storage of the set in
    such a way that the iterator does an out-of-bounds access or misses
    an element or similar.

The solution to these problems is to proceed in "rounds": At each moment,
you have three lists:
  - the list of modules whose dependencies have already been looked up,
    accumulated from previous rounds (called 'handledmodules' in the code),
  - the list being traversed (called 'inmodules' in the code),
  - the list of new dependencies being accumulated (called 'outmodules'
    in the code).
Once a round is done, the contents of 'inmodules' is put into
'handledmodules', the 'outmodules' becomes the 'inmodules' (modulo those
elements that have already been handled), and the 'outmodules' are
emptied.

You can convince yourself that with this algorithm,
  - the resulting set is closed under dependencies,
  - no module has been added other than because it was a direct or indirect
    dependency,
  - for every module, the dependencies list was processed only once (hence
    it has optimal performance).

2) Now, a variation that respects the --with[out]-*tests options.

It is the same algorithm, except that the computation of the dependencies
  a) includes the test module,
     If tests are included then we consider that m has a dependency on m-tests
     (if m-tests exists).
  b) depends on the options and on the status of the module. This code does it:

            # Determine whether to include the dependency or tests module.
            inc=true
            for word in `func_get_status $dep`; do
              case "$word" in
                obsolete)
                  test -n "$incobsolete" \
                    || inc=false
                  ;;
                c++-test)
                  test -z "$excl_cxx_tests" \
                    || inc=false
                  test -n "$fmtc_inc_all_tests" || test -n "$inc_cxx_tests" \
                    || inc=false
                  ;;
                longrunning-test)
                  test -z "$excl_longrunning_tests" \
                    || inc=false
                  test -n "$fmtc_inc_all_tests" || test -n 
"$inc_longrunning_tests" \
                    || inc=false
                  ;;
                privileged-test)
                  test -z "$excl_privileged_tests" \
                    || inc=false
                  test -n "$fmtc_inc_all_tests" || test -n 
"$inc_privileged_tests" \
                    || inc=false
                  ;;
                unportable-test)
                  test -z "$excl_unportable_tests" \
                    || inc=false
                  test -n "$fmtc_inc_all_tests" || test -n 
"$inc_unportable_tests" \
                    || inc=false
                  ;;
                *-test)
                  test -n "$fmtc_inc_all_tests" \
                    || inc=false
                  ;;
              esac
            done
            if $inc ...

      I hope this piece of code is clear: We start with the assumption that
      we have to include the dependency. But if the dependency is marked
      'obsolete' and obsolete tests are to be not included, then we don't
      include it. If the dependency is marked 'c++-test' and C++ tests
      are not to be included, then we don't include it. And so on.

      Note that this filtering applies only to the dependencies, not to
      modules that were listed among the set of input modules. If a module
      is explicitly listed as an element of the input set, it is always
      included, regardless whether it is obsolete or not, regardless whether
      it is a test or not, regardless of options. (The purpose is that the
      user can override some of the filtering.)

3) The extended variant also considers conditional dependencies. Same
algorithm, but it updates conddep_dependers, conddep_condition to keep
track of the condition under which a module is considered to be present.

Remember these conditions are strings in shell script syntax (they will
be executed as part of a 'configure' run).

              if test "$cond_dependencies" = true; then
                escaped_dep=`echo "$dep" | sed -e "$sed_escape_dependency"`
                sed_extract_condition1='/^ *'"$escaped_dep"' *$/{
                  s/^.*$/true/p
                }'

Among the dependencies, a module name that stands alone in a line is
unconditional.

                sed_extract_condition2='/^ *'"$escaped_dep"' *\[.*\] *$/{
                  s/^ *'"$escaped_dep"' *\[\(.*\)\] *$/\1/p
                }'

Otherwise the condition is noted in brackets. (See modules/openat for an
example.)

                condition=`func_get_dependencies $module | sed -n -e 
"$sed_extract_condition1" -e "$sed_extract_condition2"`
                if test "$condition" = true; then
                  condition=
                fi
                if test -n "$condition"; then
                  func_conddep_add_module "$module" "$dep" "$condition"
                else
                  if $conditional; then
                    func_conddep_add_module "$module" "$dep" true
                  else
                    func_uncond_add_module "$dep"
                  fi
                fi

This code adds the condition to the tables, so that the code that emits
the .m4 file can extract it later.

              fi


Bruno




reply via email to

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