find_uses - John COLIBRI. | - mots clé:utilitaire de programmation et documentation - analyse lexicale
- logiciel utilisé: Windows 98, Delphi 5.0
- matériel utilisé: Pentium 500Mhz, 128 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal et Delphi
- uses: u_types_constants, u_c_display, u_c_file_name,
u_c_search_pathes, u_c_uses_list, u_c_path_segments,
u_c_find_uses, (u_c_basic_object, u_c_log, u_loaded,
u_strings, u_dir, u_c_text_file, u_c_basic_file,
u_c_line, u_display_hex)
- plan:
1 - Introduction Cet utilitaire permet de dresser la liste des unités utilisées par un projet ou une unité: en fournisant le nom du projet (ou d'une unité), le programme
fournit la liste des couples (chemin, nom_d_unité). 2 - Utilisation 2.1 Un exemple simple Pour utiliser le programme: - tapez dans la boîte d'édition le chemin du .DPR
- tapez dans la seconde boite le nom du fichier
- cliquez "test_uses_"
Il suffit alors de copier et coller cette liste. A titre d'exemple, en fournissant le nom du programme lui-même, nous obtenons:
u_test_find_uses | 0 | 1 | c:\programs\colibri_utilities\programs\find_uses\ |
u_types_constants | 1 | 5 | c:\programs\colibri_helpers\units\ | u_c_log | 1 | 4 | c:\programs\colibri_helpers\classes\ | u_c_display | 1 |
12 | c:\programs\colibri_helpers\classes\ | u_c_file_name | 1 | 5 | c:\programs\colibri_helpers\classes\ | u_c_search_pathes | 1 | 1 |
c:\programs\colibri_utilities\programs\find_uses\ | u_c_uses_list | 1 | 2 | c:\programs\colibri_utilities\programs\find_uses\ | u_c_path_segments | 1 | 1 |
c:\programs\colibri_helpers\classes\ | u_c_find_uses | 1 | 1 | c:\programs\colibri_utilities\programs\find_uses\ | u_c_basic_file | 3 | 2 |
c:\programs\colibri_helpers\classes\ | u_loaded | 3 | 7 | c:\programs\colibri_helpers\units\ | u_c_basic_object | 4 | 7 | c:\programs\colibri_helpers\classes\
| u_strings | 4 | 2 | c:\programs\colibri_helpers\units\ | u_dir | 5 | 2 | c:\programs\colibri_helpers\units\ | u_c_text_file | 9
| 1 | c:\programs\colibri_helpers\classes\ | u_c_line | 15 | 1 | c:\programs\colibri_helpers\classes\ | u_display_hex | 15 | 1 |
c:\programs\colibri_helpers\units\ | | Si le fichier analysé est un projet ou une librairie, le fichier .DOF est analysé pour trouver les chemins de recherche des unités.
Si le fichier est une unité (.PAS), le programme n'a aucun moyen de trouver seul les chemins des unités appelées par cette unité. Il faut alors fournir ces chemins, en les ajoutant par:
// -- add here additional search pathes
g_c_search_pathes.add_search_path('c:\programs\'); |
2.2 - Répertoires et Directives de Compilation L'unité est prévue pour être placée dans: C: programs colibri_utilities programs
find_uses Vous pouvez naturellement changer cette organisation par Projet | Options | Directories Les directives de compilation sont: - R+ (vérification des intervalles)
- S+ (vérification de la pile)
- pas d'optimisation
3 - Programmation 3.1 - Organisation Générale L'organisation est la suivante:
- le projet contient uniquement la tForm, les valeurs initiales à titre d'exemple, et un appel à f_c_find_uses.
- cette fonction qui retourne actuellement la liste des noms d'unités se charge:
- d'initialliser la liste des chemins de recherche c_search_pathes
- d'initialiser la pile des segments de chemin c_path_segments
- d'initialiser la liste de gestion des noms / chemins d'unité c_uses_list
- lance la recherche récursive handle_one_file, qui appelle l'analyseur
lexical c_find_uses pour chaque fichier
L'essentiel du contrôle du programme est donc effectué dans u_find_usesqui utilise les autres classes et contient
l'algorithme récursif. 3.2 - c_search_pathes: les chemins de recherche Lorsque l'analyseur rencontrera un nom dans une clause USES, ce nom ne comportera en général pas le chemin.
Les premières versions de Turbo Pascal n'avaient pas les UNITés, et lorsque Borland les a ajoutées avec Turbo Pascal 4, le chemin ne pouvait être précisé dans le texte de l'unité. Il a fallu attendre Delphi pour qu'apparaisse la
clause IN: program p_test_find_uses;
uses Forms,
u_test_find_uses in 'u_test_find_uses.pas' {Form1}; |
Dans cette partie générée automatiquement par Delphi, seul le nom de fichier est spécifié, sans chemin. Si nous ajoutons des unités par "Project | Add", ces noms seront ajoutés à cette clause, avec, au besoin, les chemins relatifs (par
rapport au .DPR). A titre d'exemple, nous avons ajouté u_find_file et u_c_log:
program p_test_find_uses; uses Forms,
u_test_find_uses in 'u_test_find_uses.pas' {Form1},
u_find_uses in 'u_find_uses.pas',
u_c_log in '..\..\..\colibri_helpers\classes\u_c_log.pas'; |
Mentionnons que ce sont ces noms qui apparaissent dans le gestionnaire de projets. Mais Delphi utilise aussi deux autres source de répertoires à explorer - les répertoire que vous avez ajouté par "Project | Options |
Répertoires-conditions | chemin_de_recherche"
- les répertoires des librairies "Outil | Option d'environnement | Bibliothèque"
Les premiers sont stockés dans le fichier .DOF de notre projet.
Ce fichier est un fichier ASCII au format .INI. Voici un exemple de .DOF: Voici un exemple partiel de fichier .DOF (fichier partiel, parties "..." correspondant à des suppressions):
[Compiler] A=1 B=0 ... Z=1 ShowHints=1 ShowWarnings=1 UnitAliases=WinTypes=Windows;WinProcs=Windows; ... [Linker] MinStackSize=16384
MaxStackSize=1048576 ImageBase=4194304 ... [Directories] OutputDir=..\..\exe UnitOutputDir=..\..\exe\dcu PackageDLLOutputDir= PackageDCPOutputDir= SearchPath=..\..\..\colibri_helpers\units; ..\..\..\colibri_helpers\classes
Packages=VCL50;VCLX50;... [Parameters] [Language] [Version Info] [Version Info Keys] [History...] | La section qui nous intéresse est la section [Directories] et l'entrée
contenant les chemins de recherche a la clé "SearchPath". Dans notre cas, les chemins sont: ..\..\..\colibri_helpers\units; ..\..\..\colibri_helpers\classes
En général, lorsque nous utilisons le dialogue permettant de sélectionner un chemin (l'ellipse "..." à droite de "Chemins de Recherche", les chemins sont absolus. C'est volontairement que nous prenons la peine de taper à la mains des
chemins relatifs, pour que les projets correspondant à nos articles puissent s'exécuter dans n'importe quel répertoire. A titre d'exemple, prenons l'arborescence utilisée par ce projet: - l'arborescence est la suivante:
C: programs colibri_helpers classes u_c_log.pas etc.
colibri_utilities exe p_test_find_uses.exe
dcu u_find_uses.dcu
programs find_uses
p_test_find_uses.dpr u_find_uses.pas
- le compilateur utilise donc par défaut le répertoire du fichier .DPR, donc:
c\programs\colibri_utilities\programs\find_uses\ - pour pouvoir supprimer facilement les .EXE et les .DCU, et pour trouver
rapidement un .EXE, je regroupe les exécutables et les .DCU dans des répertoires séparés du projet.
- Pour que le compilateur accepte de placer l'exécutable dans un fichier séparé du .DPR. il faut le spécifier dans la clause "Projet | Options |
Directories | Destination". Dans notre cas, le chemin absolu serait:
c\programs\colibri_utilities\exe\ Et le chemin relatif est: ..\..\exe\
- de même pour que le compilateur trouve u_c_log, nous lui fournissons:
..\..\..\colibri_helpers\classes\
L'analyse du fichier .DOF est réalisée par la classe c_search_pathes. La
classe c_search_path lit donc dans le fichier .INI la clé contenant les chemins de recherche, et les place dans une tStringList. La seule raisons pour laquelle nous avons utilisé une classe est pour
encapsuler la lecture du fichier .INI. L'interface de cette classe est:
type c_search_pathes= class(c_basic_object)
public
m_c_path_list: tStringList;
Constructor create_search_pathes(p_name: String); Virtual;
procedure read_path_from_dof(p_dof_name: String);
procedure add_search_path(p_search_path: String);
procedure insert_search_path(p_index: Integer; p_search_path: String);
function f_search_path(p_path_index: Integer): String;
function f_search_path_count: Integer;
Destructor Destroy; Override;
end; |
Pour les données: - m_c_path_list: la liste des chemins
et pour les méthodes: - create_search_pathes: crée la tStringList
- read_path_from_dof: LA procédure utile de cette classe. Le fichier .INI est ouvert, la clé "SearchPath" de [Directories] est récupérée, puis une simple boucle débite les noms séparés de point-virgules pour les placer dans la
tStringList
- add_search_path: ajout manuel d'un chemin
- insert_search_path: insére un chemin
- f_search_path: le nombre de chemins
- f_search_path_count: Integer;
read_path_from_dof charge les noms tels qu'ils sont dans le .DOF. Si nous souhaitons les convertire en chemin absolu, il faut: - utiliser le nom du chemin de l'application
- convertir les remontées "..\..\" en parties de chemins. C'est le rôle de c_path_segments
3.3 - c_path_segments
La classe c_path_segments se charge simplement de débiter un chemin en segments (avec éventuellement le nom du lecteur en tête).
Pour convertir nos noms de chemins en chemins absolus, il nous faut: - partir d'un chemin absolu
- remplacer les remontées "..\" des chemins partiels par des noms de segment
Nous calculons le chemin absolu de la façon suivante:
- si le nom de chemin commence par "X:", nous considérons que le chemin est absolu
- sinon nous concaténons:
- le chemin de Application.ExeName (le chemin où se trouve, à l'exécution p_test_find_uses):
c\programs\colibri_utilities\exe\ - et le chemin relatif par rapport à l'exécutable de p_test_find_file:
..\programs\find_uses\ - le résultat est:
c\programs\colibri_utilities\exe\..\programs\find_uses\ - dans cette chaîne, "exe\..\" est replacé par la chaîne vide (par la fonction f_remove_middle_dot_dot, pour obtenir:
c\programs\colibri_utilities\programs\find_uses\ Naturellement, il était bien plus simple de fournir directement le chemin absolu. Mais comme je souhaite pour ceux qui me lisent que tout fonctionne
n'importe où, cela nécessite un peu de triturations de chemins. 3.4 c_uses_list: la gestion des noms d'unités La classe c_uses_list encapsule la liste des noms d'unités. Elle est utilisée
pour résoudre deux problèmes: - stocker le nom de l'unité (sans l'extension, car celle-la ne figure pas, par défaut, dans les clauses USES), et les attributs de cette unité (essentiellement le chemin où elle se trouve)
- vérifier que nous n'analysons pas plusieurs fois une unité. En effet une unité peut être appelée par plusieurs unités, et il peut même y avoir des références croisées (voir plus bas)
Mentionnons que cette unité est un exemple type d'encapsulation d'une tStringList (cf l'article sur l'encapsulation des tList ou
l'encapsulation des tStringList) 3.5 - c_find_uses: l'analyseur lexical
Pour chaque fichier .DPR ou .PAS, nous analysons le texte à la recherche des noms figurant dans les clauses USES. L'analyseur c_find_uses a l'interface suivante:
type c_find_uses= class(c_text_file)
public
constructor create_find_uses(p_name, p_file_name: String); Virtual;
procedure parse(p_unit_name: String; p_level: Integer; p_c_uses_list: c_uses_list);
destructor Destroy; Override;
end;
function f_is_custom_unit(p_name: String): Boolean;
function f_is_project(p_name: String): Boolean;
function f_is_dpr(p_name: String): Boolean; |
La classe hérite de c_text_file qui permet de charger un fichier ASCII dans un tampon mémoire et l'analyser.
c_find_uses a les méthodes suivantes: - create_find_uses: constructeur
- parse: LA procédure essentielle. Elle reçoit le nom de l'unité et la liste des unités à laquelle nous ajouterons les nouvelles unitée trouvées
L'unité contient aussi trois fonctions qui testent le type de fichier: - f_is_custom_unit: détermine si une unité est spécifique. Nous ne souhaitons
pas analyser Classes ou Windows (encore que jadis Midas et par conséquent Db, Variants etc étaient passés par cette moulinette).
Nous utilisons TOUJOURS des noms d'unités commençant par "u_". C'est donc la
présence de ce "u_" que nous utilisons. Une autre solution serait de proposer une liste de noms, ou de n'analyser que les unités dont nous trouvons le source (ce qui exclurait les unités dont le chemin ne figure pas dans le .DOF)
- f_is_project(p_name: String): un nom débutant par "p_"
- f_is_dpr: un nom se terminant par ".DPR"
La recherche des noms d'unités se fait de la façon suivante:
- si le fichier est un .DPR, une seule clause USES est recherchée
- dans le cas contraire, les USES sont recherché à la fois après INTERFACE et après IMPLEMENTATION
L'analyse elle-même est sans doûte plus poussés que nécessaire. Une première solutions serait de rechercher USES brutalement (par Pos en transformant le
tampon en String). Mais il pourrait y avoir de USES dans des commentaires ou des chaînes littérale. Nous avons donc utilisé une analyse lexicale traditionnelle, qui pourrait sans doûte pouvoir être simplifiée (l'analyse des
valeurs numériques est ici sans intérêt). En fait j'ai recyclé un analyseur lexical minimal qui a été utilisé dans de nombreuses autres circonstances. L'analyse se fait de la façon suivante: - les symboles suivants sont définis
- read_symbol lit le prochain symbole. Pour cela, nous analysons le caractère courant. Sa valeur permet de calculer le type et la valeur du symbole:
- 'a'..'z', 'A'..'Z' débute un identificateur
- '0'..'9' débute une valeur numérique littérale
- ' débute une chaîne littérale
- {, // et (* débutent un commentaire
Un CASE permet alors de lancer la procédure adéquate
- quelques procédures ou fonctions annexes telles que skip_until_id et f_next_id permettent de nous concentrer sur les identificateurs, les autres symboles ne nous concernant pas ici
- add_a_file_name centralise l'ajout d'une nouvelle unité en vérifiant que c'est une unité qui nous intéresse
- l'analyse d'un projet ou d'une unité se fait donc en sautant tout jusqu'à USES, et en ajoutant les mots jusqu'au ";"
3.6 - u_find_uses A part l'analyseur syntaxique, l'autre pièce maîtresse est le lancement récursif des appels.
- les classes annexes (c_path_segments, c_search_pathes et c_uses_list) sont initialisées
- le .DPR est analysé. Il nous fournit une liste d'unités que nous ajoutons à c_uses_list.
- nous devons alors analyser chacune de ces unités à son tour:
- nous la recherchons tout d'abord dans le répertoire de l'unité principale
- sinon nous la recherchons dans chacun des répertoires du .DOF (ou dans
les répertoires que nous aurions ajoutés "à la main")
Chaque fois qu'une unité est analysée, elle est marquée comme m_analyzed. Ceci évite d'analyser plusieurs fois la même unité, ou même de les analyser sans fin
lorsqu'elles s'appellent mutuellement. 3.7 - Historique Cet utilitaire a été utilisé à deux occasions: - trouver la hiérarchie des unités lorsque d'explore un nouveau projet que
j'ai récupéré (ou les unités des nouvelles versions Delphi ou Kylix)
- vérifier qu'un article présenté sur le site contient bien les références vers toutes les unités nécessaires pour compiler le projet.
4 - Améliorations Parmi les améliorations, mentionnons: - actuellement nous ne tenons aucun compte des attribut IN. Ce serait assez
simple d'analyser cette partie. Pour le moment nos programmes ne modifient pas la partie générée par Delphi dans le .DPR, et notre analyse du .DOF semble suffire. Allez, pour la prochaine version...
- notre mécanique s'appuie sur des conventions de nom de fichier:
- nous n'analysons que les unités dont le nom commence par la lettre "u". D'autre mécanismes de filtrage pourraient être utilisés
- de même, pour être analysé, le projet doit avoir un nom commençant par "p"
- la version actuelle suppose essentiellement que le premier fichier fourni est le .DPR (ceci nous permet de récupérer le .DOF). Pour analyser des
unités seules, il faudrait ajouter à l'interface un tMemo permettant de saisir les chemins à utiliser.
- le résultat pourrait être mis en forme (tri, formattage, classement par répertoire...)
- plus épineux, et plus intéressant: le traitement des opérateurs de compilation conditionnelle. Rien n'est fait actuellent. Il faudrait:
- mettre en batterie une sorte d'évaluateur d'opération sur les booléens
- à priori il faudrait faire l'analyse de toutes les combinaisons possibles (encore que des $IFDEF emboîtés peuvent éliminer certaines combinaisons).
A ce jour rien n'est fait
5 - Télécharger le source Pour télécharger, vous pouvez: - télécharger les fichiers séparément:
- télécharger le projet de test complet test_find_uses.zip (31 K)
Le zip comporte: - les unités précédentes
- les unités annexes qu'elles appellent
- le projet de test
Comme d'habitude: - nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections
qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même
les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en
cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site,
ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.
L'auteur
John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |