Générateur de makefile

Description

Bonjour,

Ce script est un générateur simple de Makefile, une sorte de mini-autotool mais en moins bien. Il est utilisable dans le plupart des projets compilés, aussi bien pour des exécutables que pour des librairies, et ce dans pas mal de langage.

Problématique :
Lorsque les sources projet sont réparties dans plusieurs sous-dossiers il devient hardu de n'avoir qu'un seule Makefile pour compiler tout ceci. Les comportements antagonistes de make et gmake font que n'avoir qu'un seul Makefile pour son projet est une chose compliquée a obtenir et très peu efficace. Le meilleur moyen pour garder une compatibilité entre ces deux outils est d'avoir un Makefile par sous-dossier, chose complexe a mettre en place a la main.

Solution :
Ce script va générer de lui même chacun des Makefile dans tous les sous-dossiers. Pour ceci il se base sur un fichier de configuration en xml et sur un modèle de Makefile (template).

Utilisation :
cf man donné dans l'archive (attention il est en mauvais anglais :p). Cette dernière contient également des exemples de templates et de fichier de configuration (les observer aide énormément a comprendre).
Rappel : man -M ./ my_makefile

Spécificités :

Cet outil se base sur une configuration minimale du projet :
- Un dossier includes contenant les headers a inclure.
- Un dossier src contenant les sources du projet, ces dernières pouvant être réparties dans autant de sous-dossiers que souhaité.
- Le script et son fichier de configuration a la racine du projet.

Le choix du compilateur, du linker et de l'outil make est basé sur la variable d'environnement OSTYPE. J'utilise régulièrement ce procédé afin de pouvoir compiler sur une machine spécifique dont le PATH est mal configuré (je doit préciser a la main je chemin vers gcc). ceci entraine un gros point faible du script : il faut préciser le compilateur pour chaque OSTYPE. cependant, ce soucis peu être relativisé car tous les shells (en particulier le bash) n'ont pas OSTYPE dans leur environnement : il est possible de définir un compilateur pour ce genre de cas (chose recommandée).

Il est important de noter qu'une fois les Makefiles générés le script devient inutile si l'architecture du projet de change pas (ajout de fichiers etc) : c'est le Makefile qui s'adapte a l'OSTYPE et non le script, ce dernier étant indépendant de toute architecture. Ceci vous permet d'utiliser un modèle de Makefile définissant CC, LD et MAKE "en dur" si vous n'appréciez pas l'adaptation a l'OSTYPE.

Source / Exemple :


#!/usr/bin/php
<?php
// do_makefiles.php for my_makefile in /media/tycho/sources/php/my_makefile
// 
// Made by Rodolphe Breard
// Mail    <rodolphe.breard@laposte.net>
// 
// Started on  Thu Jan 29 21:12:19 2009 Rodolphe Breard
// Last update Wed Feb  4 23:07:26 2009 Rodolphe Breard
// 
// my_makefile Copyright (C) 2008 Rodolphe Breard
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

define('FILE_CONFIG', 'config.xml');
define('DIR_SRC', 'src');
define('DIR_INCLUDES', 'includes');
define('ERR_LOAD_FILE', "unable to load file.\n --> %s");
define('ERR_CNF_MISS', "missing configuration element.\n --> %s");
define('ERR_CNF_SYNTAX', "unknown configuration element.\n --> %s");
define('ERR_DIR_MISS', "missing directory.\n --> %s");
error_reporting(0);

// -----------------------------------
// --- XML configuration functions ---
// -----------------------------------

function	load_cnf()
{
  if (($cnf = simplexml_load_file(FILE_CONFIG)) === false)
    throw new Exception(sprintf(ERR_LOAD_FILE, FILE_CONFIG));
  return ($cnf);
}

function	cnf_get_name($cnf, $argc, $argv)
{
  if ($argc > 1)
    return ($argv[1]);
  if (($ret = (string)$cnf->name) == '')
    return (basename(getcwd()));
  return ($ret);
}

function	cnf_get_make($cnf)
{
  $ret = NULL;
  foreach($cnf->make->children() as $archi)
    $ret .= "\nMAKE_" . ($archi->getName() != 'unknown' ?
			 $archi->getName() : "\t") . "\t= " . $archi . "\n";
  if ($ret === NULL || $ret == '')
    throw new Exception(sprintf(ERR_CNF_MISS, '<make>'));
  return ($ret);
}

function	cnf_get_cc($cnf)
{
  $ret = NULL;
  foreach($cnf->cc->children() as $archi)
    $ret .= "\nCC_" . ($archi->getName() != 'unknown' ?
		       $archi->getName() : "\t") . "\t= " . $archi . "\n";
  if ($ret === NULL || $ret == '')
    throw new Exception(sprintf(ERR_CNF_MISS, '<cc>'));
  return ($ret);
}

function	cnf_get_ld($cnf)
{
  $ret = NULL;
  foreach($cnf->ld->children() as $archi)
    $ret .= "\nLD_" . ($archi->getName() != 'unknown' ?
		       $archi->getName() : "\t") . "\t= " . $archi . "\n";
  if ($ret === NULL || $ret == '')
    throw new Exception(sprintf(ERR_CNF_MISS, '<ld>'));
  return ($ret);
}

function	cnf_get_cflags($cnf, $deb=false)
{
  $ret = NULL;
  foreach($cnf->cflags->children() as $flag)
    {
      if (($flag->getName() == 'flag') ||
	  ($flag->getName() == 'optflag' && !$deb) ||
	  ($flag->getName() == 'debflag' && $deb))
	$ret .= ($ret === NULL ? '-' : "\\\n\t\t-") . $flag;
      elseif ($flag->getName() != 'optflag' && $flag->getName() != 'debflag')
	throw new Exception(sprintf(ERR_CNF_SYNTAX, $flag));
    }
  return ($ret);
}

function	cnf_get_ldflags($cnf)
{
  $ret = NULL;
  foreach($cnf->ldflags->children() as $flag)
    {
      if (($flag->getName() == 'lib'))
	{
	  if ($flag->name == false)
	    throw new Exception(sprintf(ERR_CNF_MISS, '<name>'));
	  if ($flag->dir != false)
	    $tmp = 'L' . $flag->dir . ' -l';
	  else
	    $tmp = 'l';
	  $ret .= ($ret === NULL ? '-' : "\\\n\t\t-") . $tmp . $flag->name;
	}
      elseif ($flag->getName() != 'optflag' && $flag->getName() != 'debflag')
	throw new Exception(sprintf(ERR_CNF_SYNTAX, $flag->getName()));
    }
  return ($ret);
}

function	cnf_get_files($cnf, $elem)
{
  $ret = NULL;
  if ($elem != 'tpl_main' && $elem != 'tpl_sub' &&
      $elem != 'src' && $elem != 'obj')
    throw new Exception(sprintf(ERR_CNF_SYNTAX, $elem));
  $ret = (string)$cnf->files->{$elem};
  if ($ret === NULL || $ret == '')
    throw new Exception(sprintf(ERR_CNF_MISS, '<' . $elem . '>'));
  return ($ret);
}

// ------------------------------
// --- Verification functions ---
// ------------------------------

function	check_files()
{
  $dir_list = array(DIR_SRC, DIR_INCLUDES);
  foreach ($dir_list as $to_check)
    {
      if (!is_dir($to_check))
	throw new Exception(sprintf(ERR_DIR_MISS, $to_check));
    }
}

// -----------------------------------
// --- Makefile creation functions ---
// -----------------------------------

function	get_includes($lvl)
{
  $ret = '-I';
  while ($lvl-- > 0)
    $ret .= '../';
  return ($ret . DIR_INCLUDES);
}

function	get_submake($dir, $opt = NULL)
{
  foreach (scandir($dir) as $rep)
    if ($rep != '.' && $rep != '..' && is_dir($dir . '/' . $rep))
      $ret .= "\t" . '$(MAKE) ' . $rep . ($opt != NULL ? ' ' . $opt : '')."\n";
  return ($ret);
}

function	get_local_src($dir)
{
  $ret = NULL;
  foreach (scandir($dir) as $src)
    {
      if (!is_dir($src) && strstr($dir . '/' . $src, '.c') == '.c')
	$ret .= ($ret !== NULL ? " \\\n\t\t" : '') . $src;
    }
  return ($ret);
}

function	get_glob_src($dir)
{
  static	$start = 0;

  foreach (scandir($dir) as $src)
    {
      if (!is_dir($dir .'/'. $src) && strstr($dir .'/'. $src, '.c') == '.c')
	{
	  $ret .= ($start != 0 ? " \\\n\t\t" : '') . $dir . '/' . $src;
	  $start = 1;
	}
      elseif (is_dir($dir .'/'. $src) && $src != '.' && $src != '..')
	{
	  $ret .= get_glob_src($dir .'/'. $src, $start);
	}
    }
  return ($ret);
}

function	get_makefile($tpl, $changes, $rep = '.')
{
  $changes['{SUB_MAKE_ALL}'] = get_submake($rep);
  $changes['{SUB_MAKE_DEBUG}'] = get_submake($rep, 'debug');
  $changes['{SUB_MAKE_CLEAN}'] = get_submake($rep, 'clean');
  $changes['{SOURCES}'] = ($rep != '.' ? get_local_src($rep) :
			   get_glob_src(DIR_SRC));
  if (!($make = file_get_contents($tpl)))
    throw new Exception(sprintf(ERR_LOAD_FILE, $tpl));
  return (strtr((string)$make, $changes));
}

function	create_submakefile($cnf, $dir, $lvl, $changes)
{
  if ($changes['{CFLAGS}'] != '')
    $changes['{CFLAGS}'] .= "\\\n\t\t";
  $changes['{CFLAGS}'] .= get_includes($lvl);
  if ($changes['{DEBUGFLAGS}'] != '')
    $changes['{DEBUGFLAGS}'] .= "\\\n\t\t";
  $changes['{DEBUGFLAGS}'] .= get_includes($lvl);
  file_put_contents($dir . '/Makefile',
		    get_makefile(cnf_get_files($cnf, 'tpl_sub'),
				 $changes, $dir));
  echo $dir, '/Makefile', "\n";
  foreach (scandir($dir) as $rep)
    {
      if ($rep != '.' && $rep != '..' && is_dir($dir . '/' . $rep))
	create_submakefile($cnf, $dir . '/' . $rep, $lvl + 1, $changes);
    }
}

// --------------------------
// --- Main (entry point) ---
// --------------------------

try
{
  check_files();
  $cnf = load_cnf();
  $changes = array('{NAME}' => cnf_get_name($cnf, $argc, $argv),
		   '{MAKE_OS}' => cnf_get_make($cnf),
		   '{CC_OS}' => cnf_get_cc($cnf),
		   '{LD_OS}' => cnf_get_ld($cnf),
		   '{CFLAGS}' => cnf_get_cflags($cnf),
		   '{DEBUGFLAGS}' => cnf_get_cflags($cnf, true),
		   '{LDFLAGS}' => cnf_get_ldflags($cnf),
		   '{FILES_SRC}' => cnf_get_files($cnf, 'src'),
		   '{FILES_OBJ}' => cnf_get_files($cnf, 'obj'),
		   '{SOURCES}' => NULL,
		   '{SUB_MAKE_ALL}' => NULL,
		   '{SUB_MAKE_DEBUG}' => NULL,
		   '{SUB_MAKE_CLEAN}' => NULL);
  echo 'Creating main Makefile ...', "\n";
  file_put_contents('Makefile',
		    get_makefile(cnf_get_files($cnf, 'tpl_main'), $changes));
  echo 'Creating sub-Makefile ...', "\n";
  create_submakefile($cnf, DIR_SRC, 1, $changes);
}
catch (Exception $e)
{
  fprintf(STDERR, "Error : %s\n", $e->getMessage());
  exit (1);
}

?>

Conclusion :


Comme beaucoup d'outils libres, ce script a été créé a la base pour un usage personnel. Lorsque j'ai écrit ce script j'ai fait face a des problèmes particuliers que je me posais et non a des problèmes trop génériques, ce qui en fait un outil très critiquable car ne répondant pas forcément au besoin général, et ce d'autant plus qu'il existe déjà des outils bien plus performants dans le domaine. Je suis tout a fait conscient de ce fait, il est inutile de me le faire remarquer a nouveau.

Ceci dit, je vous laisse regarder le code suis impatient de voir vos critiques a son sujet afin de pouvoir l'améliorer.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.