Mis à jour le 10 mars 2006
DM: Génération de scripts de configuration

DM: Génération de scripts de configuration

À rendre pour le lundi 6 mars, 18h00.

Ce DM est facultatif et portera sur les processus et la communication entre processus. En voici une version partielle n'utilisant que les parties du cours déjà vues.

Remise du DM

Sujet

Le but de ce Devoir Maison est de fournir une bibliothèque permettant d'écrire rapidement des scripts de configuration, i.e. des programmes qui génèrent des fichiers de configuration pour une application, et en particulier pour des applications OCaml, en fonction de la configuration de la machine (architecture, commandes disponibles, bibliothèques disponibles, versions installées sur la machine).

Cela demande, par exemple, de tester l'existence de certains appels systèmes et leur conformité à la norme POSIX, de vérifier la présence de certaines commandes, la version de OCaml ou d'autres applications, donc l'exécution de commandes, la récupération et l'analyse de leurs résultats, l'installation de certaines bibliothèques ou extensions etc., puis de générer en fonction de ces tests un fichier réglant les options de compilation pouvant être utilisée par OCaml ou par un Makefile (fichier indiquant quels fichiers compiler et dans quel ordre).

L'exercice consiste en l'écriture d'une bibliothèque dédiée Confutils et d'un script illustrant l'usage de cette bibliothèque. L'écriture de la bibliothèque constitue la partie la plus importante du travail. Elle peut se décomposer elle-même en des fonctions d'usage général telles que l'exécution d'une commande avec redirection, l'exécution de commandes en pipeline, etc. et des fonctions plus spécifiques comme la recherche de l'existence d'une commande dans un chemin d'exécution.

1  La bibliothèque Confutils

On pourra récupérer les fonctions souhaitées de la bibliothèque Misc du cours.
(corrigé)

1.1  Manipulation du chemin d'accès

On veut pourvoir vérifier l'existence de certains fichiers ayant certaines propriétés. Bien souvent, on recherche ces fichiers dans un ensemble de répertoires précis, par exemple déterminé par la variable d'environnement PATH.

Écrire une fonction list_of_path qui traduit une chaîne au format UNIX, i.e. dans laquelle les chemins sont représentés séparés par le caractère ':', vers une liste de chemins. Écrire la fonction inverse path_of_list—on devra produire une erreur si l'un des chemins contient le caractère ':' car cela donnerai au résultat une interprétation incorrecte. Écrire également une fonction get_path qui récupère la variable d'environnement PATH comme une liste de chemins. (On pourra si besoin utiliser la bibliothèque Str.)
      
  val list_of_path : string -> string list
  
val path_of_list : string list -> string
  
val get_path : unit -> string list
(corrigé)
Écrire une fonction find_in_path qui prend un prédicat de type string -> bool, une liste de chemins, un fichier et retourne la liste des chemins combinés avec le fichier qui satisfont le prédicat.
      
  val find_in_path : (string -> bool) -> string list -> string -> string list
(corrigé)

1.2  Tests sur l'existence et le caractère de fichiers

On veut généraliser la fonction access pour pouvoir tester également le type et les dates de modifications des fichiers. On souhaite pouvoir effectuer les test suivants:
      
  type filetest =
  
  | Fexists (* file exists *)
  
  | Freadable (* file exists and is readable *)
  
  | Fwritable (* file exists and is writable *)
  
  | Fexecutable (* file exists and is executable *)
  
  | Fdir (* file exists and is a directory *)
  
  | Freg (* file exists and is a regular file *)
  
  | Flnk (* file exists and is a symbolic link *)
  
  | Fnonempty (* file exists and is non empty *)
  
  | Fnewer of string (* files exists and is newer than this other file *)
  
  | Folder of string (* files exists and is older than this other file *)
  
  | Fequal of string (* files is identical (same st_ino and st_dev) to this other file *)
On implémentera une fonction testfile qui prend une liste d'éléments de type filetest et retourne un booléen indiquant si le fichier satisfait tous les tests. Si la liste est vide, on se contentera de tester Fexists.

La fonction testfile attrape donc les exceptions qui déterminent la réponse au calcul demandé, mais elle devra laisser passer les autres qui indiquent une erreur pendant ce calcul (par exemple une erreur d'entrée-sortie, l'absence de mémoire suffisante). On indiquera les exceptions qui peuvent être retournées.
      
  val testfile : filetest list -> string -> bool
(corrigé)

1.3  Chaînes et fichiers

On veut être capable de lire ou écrire rapidement des fichiers, i.e. écrire des fonctions string_of_file et file_of_string qui lisent un fichier dans une chaîne de caractères et écrivent une chaîne de caractères dans un fichier.

En fait, on commence par écrire des fonctions plus générales string_from_descr and descr_from_string qui font la même chose mais dans des descripteurs de fichiers. On veut que ces fonctions puissent être utilisées dans un contexte assez large, en particulier en présence de signaux et pour des descripteurs de fichiers variés. Ces fonctions pourront lever les mêmes exceptions que les appels système read et write sauf EINTR. Le descripteur passé à ces fonctions est supposé ouvert à l'appel et laissé ouvert en sortie.
      
  val string_from_descr : Unix.file_descr -> string
  
val descr_from_string : string -> Unix.file_descr -> unit
(corrigé)
En déduire les fonctions string_of_file and file_of_string (on donnera les droits 0o640 aux fichiers créés) et on écrasera le fichier s'il existe déjà.
      
  val string_of_file : string -> string
  
val file_of_string : string -> string -> unit
(corrigé)

1.4  Redirections

Écrire une fonction execvp_to_list qui prend une commande cmd et un tableau d'argument argv, et lance la commande avec ses arguments (en ajoutant la commande elle-même comme premier argument) dans un autre processus et recueille le résultat (sortie standard) de l'exécution dans une liste de chaîne de caractères (une par ligne sans le '\n' final). La fonction devra se comporter comme la fonction system vis à vis de la protection des signaux pendant l'exécution de la commande.
      
  val execvp_to_list : string -> string array -> string list
(corrigé)
Écrire une fonction execvp_redirect qui prend en argument une liste de redirections à effectuer, une commande, un tableau d'arguments et exécute la commande dans un nouveau processus après avoir effectué les redirections demandées. Ici, on ne changera pas le comportement vis-à-vis des signaux pendant l'exécution.

Les redirections possibles sont définies par le type suivant:
      
  type redirection =
  
    In_from_file of string        (* < file *)
  
  | Out_to_file of string         (* > file *)
  
  | Err_to_file of string         (* 2> file *)
  
  | Out_append_to_file of string  (* >> file *)
  
  | Err_to_out                    (* 2>&1 *)
  
  | In_from_string of string      (* <<END *)
  
  | Err_null                      (* >/dev/null  *)
  
  | Out_null                      (* 2>/dev/null *)
  
  | Silent                        (* >/dev/null 2>&1 *)
  

  
val execvp_redirect :
  
    redirection list -> string -> string array -> Unix.process_status
(corrigé)

2  L'interface publique

On fera en sorte que les fonctions suivantes échouent fatalement au cas où une exception Unix_error serait levée sans bonne raison. On fera également attention à ce qu'elles effacent systématiquement les fichiers temporaires créés.

Écrire une fonction ocaml_output qui prend une liste de substitutions et une liste de paires de fichiers (fichier d'origine, et fichier généré), et effectue les substitutions. Les substitutions sont des paires de chaînes, la première étant celle devant être remplacée, la seconde celle la remplaçant. On pourra utiliser la fonction Str.global_replace après avoir appliqué la fonction Str.regexp sur chaque chaîne à remplacer.
      
  val ocaml_output : (string * stringlist -> (string * stringlist -> unit
Écrire une fonction ocaml_prog, qui teste si un programme existe dans le chemin spécifié par la variable PATH et s'il est exécutable, avant de retourner le nom complet du fichier trouvé.
      
  val ocaml_prog : string -> string
Écrire une fonction ocaml_defined, qui teste si une valeur Ocaml (List.map par exemple) est définie, i.e. si un petit programme contenant cette valeur peut être compilé par ocamlc -c.
      
  val ocaml_defined : string -> bool
Écrire une fonction ocaml_value_has_type, qui teste si une valeur Ocaml est définie avec le type spécifié. On pourra utiliser le fait que l'expression (x : t) vérifie que x a le type t avant de retourner la valeur de x.
      
  val ocaml_value_has_type : string -> string -> bool
Écrire une fonction ocaml_version, qui prend en argument le nom du fichier exécutable correspondant à ocamlc, et retourne la version d'Ocaml, c'est-à-dire la première ligne de la sortie de ocamlc -v.
      
  val ocaml_version : string -> string
(corrigé)

3  Un script

Un exemple de script pour tester le DM:
      
  open Unix;;
  
open Confutils;;
  
open Printf;;
  

  
let ocamlrun = ocaml_prog "ocamlrun"
  
let ocamlc = ocaml_prog "ocamlc"
  

  
let ocaml_version = ocaml_version ocamlc
  

  
let _ =
  
  printf "OCAMLC=%s\nOCAMLRUN=%s\nOCAMLVERSION=%s\n%!"
  
    ocamlrun ocamlc ocaml_version
  

  
let _ =
  
  let check v = Printf.printf "%s %s\n" v
  
      (if ocaml_defined v then "defined" else "is not defined"in
  
  check "succ";
  
  check "scuc";
  
  let check v t = Printf.printf "%s %s %s\n" v
  
      (if ocaml_value_has_type v t then "is:" else "is not:"t in
  
  check "succ" "int -> int";
  
  check "succ" "int -> bool";;
  

  
let _ =
  
  ocaml_output [
  
  "@CONFIG_OCAMLC@"ocamlc;
  
  "@CONFIG_OCAMLRUN@"ocamlrun;
  
  "@CONFIG_OCAMLVERSION@"ocaml_version;
  
]
  
    [ "Makefile.config.in""Makefile.config"]
Makefile.config.in a le contenu suivant:
      
  OCAMLC=@CONFIG_OCAMLC@
  
OCAMLRUN=@CONFIG_OCAMLRUN@
  
OCAMLVERSION=@CONFIG_OCAMLVERSION@
  

  
configuremisc.ml confutils.ml configure.ml
  
        $(OCAMLRUN$(OCAMLC) -c misc.ml
  
        $(OCAMLRUN$(OCAMLC) -c confutils.ml
  
        $(OCAMLRUN$(OCAMLC) -c configure.ml
  
        $(OCAMLRUN$(OCAMLC) -o configure str.cma unix.cma misc.cmo \
  
                 confutils.cmo configure.cmo
On pourra vérifier l'état du fichier Makefile.config après exécution du script.


Ce document a été traduit de LATEX par HEVEA