Package: src/packages/flx_pkgconfig.fdoc

The configuration database manager flx_pkgconfig.

key file
flx_pkg.flx share/lib/std/felix/flx_pkg.flx
flx_pkgconfig.flx share/lib/std/felix/flx_pkgconfig.flx
flx_pkgconfig_core.flx share/lib/std/felix/flx_pkgconfig_core.flx
flx_pkgconfig_export.flx share/lib/std/felix/flx_pkgconfig_export.flx
flx_pkgconfig_query.flx share/lib/std/felix/flx_pkgconfig_query.flx
flx_pkgconfig_query_interface.flx share/lib/std/felix/flx_pkgconfig_query_interface.flx
flx_pkgconfig_tool.flx $PWD/src/tools/flx_pkgconfig.flx

Command Line Interface

Provides function to handle generic shell style call with parsed arguments, supporting calls from either inside ordinary Felix code or from a stub execuatble which just parses the command line.

This code always returns, it cannot fail EXCEPT if –help is given.

//[flx_pkgconfig_core.flx]
class FlxPkgConfig_core
{
  open Lexer;

  gen flx_pkgconfig (args:list[string]) : int * list[string] =
  {
    proc print_help {
      println$ "flx_pkgconfig [options] pkg pkg ...";
      println$ "  returns code 1 if any packages are missing unless --noerror is specified";
      println$ "  prints package or field list to standard output on one line";
      println$ "options: (follows GNU conventions)";
      println$ "  --path=dirname        set database directory name";
      println$ "  --path+=dirname       append database directory name";
      println$ "  --extension=fpc       set resource descriptor extensions,";
      println$ "                          default 'fpc' use 'pc' for pkgconfig databases";
      println$ "  -h";
      println$ "  --hide                only process first package in path with a given name";
      println$ "                          default, process all occurences";
      println$ "  --list                list available packages from specified set";
      println$ "  --missing             list missing packages from specified set";
      println$ "  --noerror             do not return 1 because of missing packages";
      println$ "  -r";
      println$ "  --rec                 form transitive closure of specified set based on Requires field";
      println$ "  --rec=field           form transitive closure of specified set based on specified field";
      println$ "  -b";
      println$ "  --backwards           process specified packages in reverse order";
      println$ "  --field=field         collate values of field in package set";
      println$ "  --keepleftmost        remove duplicate values in output keeping only leftmost occurrence";
      println$ "  --keeprightmost       remove duplicate values in output keeping only rightmost occurrence";
      println$ "  --keepall             keep duplicate values in output";
      println$ "  @filename             Replace with arguments from filename, one line per argument";
    }

    proc pre_incr:&lex_iterator = "++*$1;";

    variant token_t = Str of string | Cmd of string | Eos;

    fun lexit(ini:lex_iterator, finish:lex_iterator): lex_iterator * token_t=
    {
      //println$ "lexit input='" + string_between(ini,finish)+"'";

      var start = ini;

      // already at end
      if start == finish do
        return start, Eos;

      // eat white space
      elif *start == char(' ') do
        ++start;
        while start != finish and *start == char(' ') do ++start; done;
        return start,Eos;

      // double quoted string
      elif *start == char('"') do
        ++start;
        p1 := start;
        while start != finish and *start != char('"') do ++start; done;
        if start == finish do
          return start,Str (string_between(p1,start));
        else
          return start+1,Str (string_between(p1, start));
        done;

      // single quoted string
      elif *start == char("'") do
        ++start;
        p2 := start;
        while start != finish and *start != char("'") do ++start; done;
        if start == finish do
          return start,Str (string_between(p2,start));
        else
          return start+1,Str (string_between(p2, start));
        done;

      elif *start == char("`") do
        ++start;
        p3 := start;
        while start != finish and *start != char("`") do ++start; done;
        if start == finish do
          return start,Cmd (string_between(p3,start));
        else
          return start+1,Cmd (string_between(p3, start));
        done;


      done;
      // identifier
      p4 := start;
      while start != finish and *start != char(" ")  do ++start; done;
      return start,Str (string_between(p4,start));
    }

    fun lexstr(s':string): list[string] =
    {
      var s = s';
      val first = start_iterator s;
      val finish = end_iterator s;
      var current = first;
      var words = Empty[string];
      while current != finish do
        match lexit(current,finish) with
        | next,token=>
          {
            current = next;
            match token with
            | Eos => ;
            | Str lexeme => if lexeme != "" perform words = Cons(lexeme,words);
            | Cmd cmd =>
              var res,s = System::get_stdout cmd;
              var recres = lexstr s.strip;
              words = rev recres + words;
            endmatch;
          }
        endmatch;
      done
      //println$ "Words='" + str(rev words)+"'";
      return rev words;
    }

    macro val streq = eq of (string * string);

    var path=Env::getenv("PKG_CONFIG_PATH");

    // parse arguments
    var fields = Empty[string];
    var pkgs = Empty[string];

    var hide = false; // only find first file in path
    var require_pkg_exists = true; // fail if file not found
    var missing = false; // report missing packages
    var require_field_exists = false; // fail if file doesn't contain field
    var recfields = Empty[string];
    var dolist = false;
    var listkeys = false;
    var return_code = 0;
    var backwards = false;
    enum keep_t {keepall, keepleftmost, keeprightmost};
    var keep= keepleftmost;
    var extension = "fpc";

    fun is_prefix_of(p:string,w:string)=> p == w.[to len p];

    fun xfind(flags: string, c: string) =>
     match find(flags, c) with
     | #None => false
     | Some _ => true
     endmatch
    ;

    proc parse_args(args:list[string])
    {
      match args with
      | #Empty => {}
      | Cons (arg,tail) =>
        {
          fun prefix(x:string)=>is_prefix_of(x,arg);

          if prefix("--hide") do hide = true;
          elif prefix("--backwards") do backwards = true;
          elif prefix("--list") do dolist = true;
          elif prefix("--missing") do missing = true;
          elif prefix("--noerror") do require_pkg_exists = false;
          elif prefix("--keeprightmost") do keep = keeprightmost;
          elif prefix("--keepleftmost") do keep = keepleftmost;
          elif prefix("--keepall") do keep = keepall;

          elif "--field" == arg.[0 to 7] do
            fields = fields + arg.[8 to];

          elif "--extension" == arg.[0 to 11] do
            extension = arg.[12 to];

          elif "-" == arg.[0 to 1] and "-" != arg.[1 to 2] do
            flags := arg.[1 to];
            if xfind(flags, "r") do
              recfields = append_unique streq recfields "Requires";
            done;

            if xfind(flags,"h") do hide = true; done;
            if xfind(flags,"b") do backwards = true; done;
            if xfind(flags,"l") do dolist = true; done;

          elif "--rec" == arg.[0 to 5] do
            var fld = arg.[6 to];
            fld = if fld == "" then "Requires" else fld endif;
            recfields = append_unique streq recfields fld;

          // add to path
          elif "--path+" == arg.[0 to 7] do
            val x = arg.[8 to];
            if path != "" do
              path= path + ":" + x;
            else
              path= x;
            done;

          // set path
          elif "--path" == arg.[0 to 6] do
            path= arg.[7 to];

          elif "--help" == arg do
            print_help;
            // FIXME
            System::exit(0);

          elif "@" == arg.[0 to 1] do
            val data = load$ strip arg.[1 to];
            parse_args$ split(data,c" \n\r\t,");

          // ignore unknown options
          elif "-" == arg.[0 to 1] do ;

          // ignore empty arguments
          elif "" == arg do ;

          // package name
          else
            pkgs = pkgs + arg;
          done;
          parse_args(tail);
        }
      endmatch;
    }

    parse_args(args);

    //print$ "Fields   = " + str fields; endl;
    //print$ "Packages = " + str pkgs; endl;

    fun reattach_drive_letters : list[string] -> list[string] =
      | Cons (a, Cons (b, tail)) =>
          if (len(a) == size 1 and isalpha(a.[0]) and b.startswith('\\')) then
            Cons (a+':'+b, reattach_drive_letters tail)
          else
            Cons (a, reattach_drive_letters (Cons (b, tail)))
          endif
      | other => other // 1 or 0 elements left
    ;

    val dirs=reattach_drive_letters(split(path, char ':'));

    // print$ "Path = " + str dirs; endl;

    var result = Empty[string];

    fun check_id (s:string) = {
      var acc=true;
      for elt in s do acc = acc and isalphanum elt; done
      return acc;
    }

    fun get_field(line:string):string * string =>
        match find (line,char ':') with
        | #None => "",""
        | Some n =>
            strip line.[to n],
            strip line.[n+1 to]
        endmatch
      ;


    fun get_variable(line:string):string * string =>
        match find (line,char '=') with
        | #None => "",""
        | Some n =>
            let name = strip line.[to n] in
            let value = strip line.[n+1 to] in
            if check_id name then name,value else "",""
        endmatch
      ;

    proc add_val(v:string){
     result = insert_unique streq result v;
    //  result = rev$ Cons(v, rev result);
    }

    proc tail_val(v:string){
       result = append_unique streq result v;
    //  result = Cons(v, result);
    }

    proc keep_val (v:string){
      result = result + v;
    }

    proc handle_pkg (pkg:string, trace:list[string]){
//eprintln$ "Handle_pkg pkg= " + pkg + " trace= " + trace.str;
       var variables = Empty[string * string];

       if mem streq trace pkg return;
       var found = false;
       iter(proc (dir:string){
         val filename =
           if dir=="" then "." else dir endif + #Filename::sep + pkg + "."+extension
         ;
         //print filename; endl;

         // examine line of one file
         file := fopen_input filename;
         if valid file do
           if dolist do
             match keep with
             | #keepleftmost => add_val pkg;
             | #keeprightmost => tail_val pkg;
             | #keepall => keep_val pkg;
             endmatch;
           done
           var lines = Empty[string];
           var line = readln file;
           while line != "" do
             line = line.strip;
             if line != "" and line.[0] != char "#" do
               lines = Cons(line,lines);
             done
             line = readln file;
           done
           if not backwards do lines = rev lines; done;

           iter (proc (line:string)
           {
             //print line;
             def var variable, var vval = get_variable(line);
             if variable != "" do
               var bdy = search_and_replace variables vval;
               variables = Cons ( ("${"+variable+"}",bdy), variables);
             else
               def var key, var value = get_field(line);
               if listkeys call add_val key;
               var values = lexstr(value);
               values = map (search_and_replace variables) values;
               if mem streq fields key do
                 match keep with
                 | #keepleftmost => { iter add_val values; }
                 | #keeprightmost => { iter tail_val values; }
                 | #keepall => { iter keep_val values; }
                 endmatch;
               done;
//eprintln$ "Chase dependent packages key = " + key + " recfields = " + recfields.str;
               // chase dependent packages
               if mem streq recfields key do
//eprintln$ "FOUND";
                 iter (proc (s:string){
                   handle_pkg$ s,Cons(pkg,trace);
                 })
                 values;
               done
//eprintln$ "DONE  dependent packages key = " + key + " recfields = " + recfields.str;

             done
           })
           lines
           ;
           fclose file;
           found = true;
           if hide return; // only find first file in path
         done;
       })
       dirs;
       if not found do
         //eprintln$ "package not found: " + pkg;
         if require_pkg_exists do return_code = 1; done;
         if missing call add_val(pkg);
       done;
    }

    var original_pkgs = pkgs;
//eprintln$ "+++++++++++++++++++++++++";
//eprintln$ "TOP LEVEL HANDLING PACKAGES " + original_pkgs.str;
    while not is_empty pkgs do
      match pkgs with
      | #Empty => {}
      | Cons (pkg,tail) =>
        {
//eprintln$ "TOP LEVEL HANDLE ONE PACKAGE " + pkg.str;
          pkgs = tail;
          handle_pkg(pkg,Empty[string]);
//eprintln$ "DONE: TOP LEVEL HANDLE ONE PACKAGE " + pkg.str;
        }
      endmatch;
    done;
//eprintln$ "DONE: TOP LEVEL HANDLING PACKAGES " + original_pkgs.str;
//eprintln$ " ************************";

    return return_code, result;
  }
}

Tool executable.

This the actual command line tool. It depends on only the flx_pkgconfig function. It exits with the return code that function returns.

//[flx_pkgconfig_tool.flx]
include "std/felix/flx_pkgconfig";

header flx_pkgconfig_header =
"""
#include <iostream>
#include "flx_ioutil.hpp"
#include "flx_strutil.hpp"
#include "flx_rtl.hpp"
#include "flx_gc.hpp"
""";

// This KLUDGE does two independent things:
//
// (1) It stops problems with the GC preventing
// building Felix in a core build.
//
// (2) It injects the header includes required by flx_pkgconfig
// directly into flx_pkgconfig so the executable can be built
// without flx or flx_pkgconfig.
//
// The latter is essential during the Python based bootstrap
// build process. That process uses the flx_pkgconfig executable
// to translate the flx.resh file produced by compiling flx.flx
// with flxg into actual package requirements, and thence
// into the required header file.
//

proc kludge : 1 = "PTF gcp->allow_collection_anywhere=false;" requires flx_pkgconfig_header;
kludge();

// strip any trailing space off to ease bash scripting
var return_code, result = FlxPkgConfig::flx_pkgconfig (tail #System::args);

print$ strip$ cat ' ' result; endl;
System::exit return_code;

Database query object.

This code provides an object wrapper around the flx_pkgconfig CLI interface function to allow low level queries about specific fields of specific packages.

Database query object interface.

//[flx_pkgconfig_query_interface.flx]
class FlxPkgConfigQuery_interface
{
  interface FlxPkgConfigQuery_t {
    query:           list[string] -> int * list[string];
    getpkgfield:     (1->0) -> string * string -> list[string];
    getpkgfield1:    (1->0) -> string * string -> string;
    getpkgfieldopt:  (1->0) -> string * string -> opt[string];
    getpkgfielddflt: (1->0) -> string * string ->  string;
    getclosure:      (1->0) -> string -> list[string];
   }
}

Database query object implementation.

Depends on on the CLI function interface.

//[flx_pkgconfig_query.flx]
include "std/felix/flx_pkgconfig_core";
include "std/felix/flx_pkgconfig_query_interface";

class FlxPkgConfig_query
{
  object FlxPkgConfigQuery (path:list[string]) implements FlxPkgConfigQuery_interface::FlxPkgConfigQuery_t =
  {
    var paths =
      match path with
      | #Empty => Empty[string]
      | Cons (h,t) =>
        let
          fun aux (lst:list[string]) (out:list[string]) =>
          match lst with
          | #Empty => rev out
          | Cons (h,t) => aux t (("--path+="+h)!out)
          endmatch
        in
        ("--path="+h) ! aux t Empty[string]
    ;
    match path with | #Empty => assert false; | _ => ; endmatch;


    method gen query (args:list[string]) =>
      FlxPkgConfig_core::flx_pkgconfig (paths + args)
    ;

    // Get all the values of a field in a particular package
    method gen getpkgfield (ehandler:1->0) (pkg:string, field:string) : list[string] = {
      var result,values = query$ list$ ("--field="+field, pkg);
      if result != 0 do
        println$ "[FlxPkgConfigQuery: getpkgfield] Can't find package " + pkg;
        println$ "Searching in paths:";
        for path in paths do
          println$ "  " + path;
        done
        // FIXME
        // System::exit(1);
        throw_continuation ehandler;
      done
      return values;
    }

    // Get the single value of a field in a particular package.
    // Bug out if missing or multiple values.
    method gen getpkgfield1 (ehandler:1->0) (pkg:string, field:string) : string = {
      var values = getpkgfield ehandler (pkg,field);
      match values with
      | Cons (h,#Empty) => return h;
      | #Empty =>
        println$ "[FlxPkgConfigQuery: getpkgfield1] Required field " + field + " not found in package "+pkg;
        throw_continuation ehandler;

      | _ =>
        println$ "[FlxPkgConfigQuery: getpkgfield1] Multiple values for field " + field + " in " + pkg + " not allowed, got" + str values;
        throw_continuation ehandler;
      endmatch;
    }

    // Get the single value of a field in a particular package.
    // Bug out if multiple values.
    method gen getpkgfieldopt (ehandler:1->0) (pkg:string, field:string) : opt[string] = {
      var result,values = query$ list$ ("--field="+field, pkg);
      if result !=0 return None[string]; // package or field missing
      match values with
      | Cons (h,#Empty) => return Some h;
      | #Empty => return None[string];
      | _ =>
        println$ "[FlxPkgConfigQuery: getpkgfieldopt ]Multiple values for field " + field + " in " + pkg + " not allowed, got" + str values;
        throw_continuation ehandler;
      endmatch;
    }

    method gen getpkgfielddflt (ehandler:1->0) (pkg:string, field:string) : string =>
      match getpkgfieldopt ehandler (pkg, field) with
      | Some h => h
      | #None => ""
      endmatch
    ;

    //$ Get Requires closure.
    //$ Result is topologically sorted with each package listed
    //$ after ones it requires.
    method gen getclosure (ehandler:1->0) (pkg:string) : list[string] = {
      var result,values = FlxPkgConfig_core::flx_pkgconfig $ paths +
        "--keeprightmost" + "--rec" + "--list" +  pkg
      ;
      if result != 0 do
        println$ "[GetPkgConfigQuery: getclosure] missing package for closure of " + pkg;
        // FIXME
        // System::exit(1);
        throw_continuation ehandler;
      done
      return rev values;
    }
  }
}

Database Manager Library

Export thunks to support separate compilation of the flx_pkgconfig database query library. This allows the code to be dynamically loaded or statically linked against as if a foreign C library.

The support does not reach the level of a plugin, however.

Compendium class.

This class provides both of the core CLI function and the object based query wrapper, wrapped in a single include file and with a single wrapping namespace.

//[flx_pkgconfig.flx]
include "std/felix/flx_pkgconfig_core";
include "std/felix/flx_pkgconfig_query_interface";
include "std/felix/flx_pkgconfig_query";

class FlxPkgConfig
{
  inherit FlxPkgConfig_core;
  inherit FlxPkgConfigQuery_interface;
  inherit FlxPkgConfig_query;
}

Separate compilation wrapper.

This file provides the separate compilation wrapper. We provide a struct wrapper around the underlying record type, because it is a nominal type and can be exported.

//[flx_pkgconfig_export.flx]
include "std/felix/flx_pkgconfig";

export FlxPkgConfig::flx_pkgconfig of (list[string]) as "flx_pkgconfig";

export struct FlxPkgConfigQuery_struct
{
    query:           list[string] -> int * list[string];
    getpkgfield:     (1->0) -> string * string -> list[string];
    getpkgfield1:    (1->0) -> string * string -> string;
    getpkgfieldopt:  (1->0) -> string * string -> opt[string];
    getpkgfielddflt: (1->0) -> string * string ->  string;
    getclosure:      (1->0) -> string -> list[string];
}

gen mk_pkgconfig_query (a:FlxPkgConfigQuery_struct) =>
  FlxPkgConfig::FlxPkgConfigQuery (
    query=a.query,
    getpkgfield=a.getpkgfield,
    getpkgfield1=a.getpkgfield1,
    getpkgfieldopt=a.getpkgfieldopt,
    getpkgfielddflt=a.getpkgfielddflt,
    getclosure=a.getclosure
  )
;

export mk_pkgconfig_query
  of (FlxPkgConfigQuery_struct)
  as "flx_pkgconfig_query"
;

Pkg config

This code provides a class wrapper around the command line function flx_pkgconfig AND the object based query system, designed solely to simplify access from the flx tool.

This code can do a System::exit which also exits the flx process using it.

This has to be FIXED so flx can run in non-stop mode.

//[flx_pkg.flx]
include "std/felix/flx_pkgconfig";

class FlxPkg
{
  typedef pkgconfig_inspec_t = (
    FLX_CONFIG_DIRS: list[string],
    FLX_TARGET_DIR:string,
    EXT_EXE: string,
    EXT_STATIC_OBJ: string,
    EXT_DYNAMIC_OBJ: string,
    STATIC: int,
    LINKEXE: int,
    SLINK_STRINGS: list[string],
    DLINK_STRINGS: list[string],
    LINKER_SWITCHES: list[string],
    EXTRA_PACKAGES: list[string],
    cpp_filebase : string
  );

  instance Str[pkgconfig_inspec_t] {
    fun str (spec: pkgconfig_inspec_t) => spec._strr;
  }

  typedef pkgconfig_outspec_t = (
    CFLAGS: list[string],
    INCLUDE_FILES: list[string],
    DRIVER_EXE: string,
    DRIVER_OBJS: list[string],
    LINK_STRINGS: list[string]
  );

  fun fix2word_flags (fs: list[string]) = {
    //println$ "Fix2word, input=" + fs.str;
    var output =  fold_left
     (fun (acc:list[string]) (elt:string) =>
       if prefix (elt, "---") then acc + (split (elt.[2 to], char "="))
       else acc + elt
       endif
      )
      Empty[string]
      fs
    ;
    //println$ "Fix2word, output=" + output.str;
    return output;
  }


  // Model:
  // Static link exe: return the object files required, no driver exe
  // Dynamic link exe: the same
  // DLL: return the executable (flx_run) required to run the DLL
  //
  // We provide instructions to link the target binary and how to run it.

/*
  proc ehandler () {
    eprintln$ "Flx_pkgconfig: map_package_requirements: failed, temporary ehandler invoked";
    System::exit 1;
  }
*/
  gen map_package_requirements (ehandler:1->0) (spec:pkgconfig_inspec_t) : pkgconfig_outspec_t =
  {
    fun / (a:string, b:string) => Filename::join (a,b);

// println$ "MAP PACKAGE REQUIREMENTS: " + spec.str;

    var PKGCONFIG_PATH=unbox (map
       (fun (s:string) => "--path+="+s)
       spec.FLX_CONFIG_DIRS)
    ;

    // to hook any extra packages found by the compiler
    var RESH = "@"+spec.cpp_filebase+".resh";

    // find all the compiler or switches
    // args are the args to flx_pkgconfig
    gen pkgconfl(args:list[string]) : list[string] =
    {
      if spec.EXTRA_PACKAGES != Empty[string] call
         eprintln$ "calpackages, EXTRA_PACKAGES = " + str spec.EXTRA_PACKAGES
      ;
      var allargs = PKGCONFIG_PATH+args+spec.EXTRA_PACKAGES + RESH;
      var ret,s = FlxPkgConfig::flx_pkgconfig(allargs);
      if ret != 0 do
        eprintln$ "[FlxPkg:map_package_requirements] Error " + str ret + " executing flx_pkgconfig, args=" + str allargs;
        // FIXME
        //System::exit (1);
        throw_continuation ehandler;
      done
      return s;
    }
    // convert list of switches to a single string
    gen pkgconfs(args:list[string]) : string => cat ' ' $ pkgconfl(args);

    var e = Empty[string];

    // find all include directories
    var CFLAGS=pkgconfl(e+'--field=cflags'+'--keepleftmost');

    // find all include files
    var INCLUDE_FILES=pkgconfl(e+'--field=includes'+'--keepleftmost');


    // find the driver package
    // not useful for C++ only
    var DRIVER_PKG=pkgconfs(e+'--field=flx_requires_driver');
    if DRIVER_PKG == "" do DRIVER_PKG="flx_run"; done

    // find the driver entity
    // not useful for C++ only
    if spec.STATIC == 0 do
      // dynamic linkage: the driver executable
      if spec.LINKEXE == 0 do
        var DRIVER_EXE= spec.FLX_TARGET_DIR/ "bin"/ DRIVER_PKG+spec.EXT_EXE;
        var DRIVER_OBJS = Empty[string];
      else
      // dynamic linkage: the object files for executable with DLL support
        DRIVER_OBJS =([
            spec.FLX_TARGET_DIR / "lib" / "rtl" / (DRIVER_PKG+"_lib_static"+ spec.EXT_DYNAMIC_OBJ),
            spec.FLX_TARGET_DIR / "lib" / "rtl" / (DRIVER_PKG+"_main"+spec.EXT_DYNAMIC_OBJ)
        ]);
        DRIVER_EXE = "";
      done
    else
      // static linkage: the object files for full static link
      DRIVER_OBJS = ([
          spec.FLX_TARGET_DIR/ "lib"/ "rtl"/ (DRIVER_PKG+"_lib_static"+ spec.EXT_STATIC_OBJ),
          spec.FLX_TARGET_DIR/ "lib"/ "rtl"/ (DRIVER_PKG+"_main"+spec.EXT_STATIC_OBJ)
      ]);
      DRIVER_EXE = "";
    done

    if spec.STATIC == 0 do
      if spec.LINKEXE == 0 do
        // Linking a DLL
        var LINK_STRINGS =
          spec.DLINK_STRINGS+
          spec.LINKER_SWITCHES+
          pkgconfl(e+'-r'+'--keeprightmost'+'--field=provides_dlib'+'--field=requires_dlibs'+DRIVER_PKG);
      else
        // Linking an EXE (with DLL support)
        LINK_STRINGS =
          spec.DLINK_STRINGS +
          spec.LINKER_SWITCHES+
          pkgconfl(e+'-r'+'--keepleftmost'+'--field=provides_dlib'+'--field=requires_dlibs'+DRIVER_PKG);
      done
    else
      // static linkage: all the libraries required by the application and driver
      // This has to be recursive to find the closure.
      // Linking an EXE (fully static)
      LINK_STRINGS =
        spec.SLINK_STRINGS+
        spec.LINKER_SWITCHES+
        pkgconfl(e+'-r'+'--keeprightmost'+'--field=provides_slib'+'--field=requires_slibs'+DRIVER_PKG);
    done
    LINK_STRINGS = fix2word_flags LINK_STRINGS;

    var result = (
      CFLAGS = CFLAGS,
      INCLUDE_FILES = INCLUDE_FILES,
      DRIVER_EXE = DRIVER_EXE,
      DRIVER_OBJS = DRIVER_OBJS,
      LINK_STRINGS = LINK_STRINGS
    );
    //println$ "Mapped requirements = " + result._strr;
    return result;
  }

  proc write_include_file(path:string, INCLUDE_FILES:list[string]) {
    var f = fopen_output(path+".includes");
    List::iter
      (proc (i:string) { writeln$ f, "#include " + i; })
      INCLUDE_FILES
    ;
    fclose f;
  }

}