|
Delphi tools: Compiler commands
Erik Stok (aka Baldo)
Delphi biedt voor het compileren van projecten in een project group
geen andere opdachten dan build all en build current
project. In Delphi 7 is eindelijk de optie build all
from current project erbij gekomen, maar daar hebben gebruikers
van eerdere Delphi versies zoals ik vrij weinig aan. Een reden om
weer eens een blik te werpen op de open tools api.
Het idee
Als je met Delphi aan het werk bent en je werkt met een project
group dan blijkt dat je nogal eens zit te wisselen tussen projecten
om het betreffende project gecompileerd te krijgen. Als je een wijziging
maakt in een unit die niet in je actieve project zit dan moet je
eerst het project van de betreffende unit actief maken, vervolgens
compileren, en daarna eventueel het vorige project weer actief maken
omdat dat degene is die je wilt starten met de optie run.
Ook mist de faciliteit om vanaf een bepaald project te kunnen bouwen.
Als een project group bijvoorbeeld afhankelijke packages bevat in
hiërarchische volgorde, dan zou het wel handig zijn als er
vanaf een bepaald package een build kan worden gedaan. Dit komt
zeker van pas bij dynamische
applicaties.
Het idee is dus om twee opties voor de compiler erbij te maken:
build project of current unit en build from current
project.
De OTA
De open tools api biedt via het IOTAProjectBuilder interface mogelijkheden
om de Delphi project builder aan het werk te zetten. Een referentie
naar deze project builder kan worden verkregen via het IOTAProject
interface van een project. Via het IOTAProjectGroup interface kan
toegang worden verkregen tot de verschillende projecten in de project
group. Al deze interfaces staan uitvoerig beschreven in de Delphi
help (zoek op één van de interface namen), dus op
de details van deze interfaces zal ik verder niet ingaan. Het zijn
wel de noodzakelijke ingrediënten voor de IDE uitbreiding die
we gaan maken, dus het gebruik ervan zal ik wel degelijk bespreken.
Build project of current unit
Om het project uit de project group te kunnen compileren waarin
de huidige unit zich bevindt zal eerst moeten worden bepaald welk
project dit is. Daarvoor zal door alle projecten van de project
group gelopen moeten worden om te bepalen in welk project de huidige
unit zich bevindt.
Zoals in het vorige open tools api
artikel al te lezen was, kent een project in de open tools api
geen units, alleen maar modules. Ik heb toen ook behandeld hoe een
referentie naar de huidige module verkregen kan worden. Van de huidige
module zal dus moeten worden bepaald in welk project deze zich bevindt.
De CurrentProjectGroup functie geeft een referentie terug
naar de project group en ziet er als volgt uit:
function GetProjectGroup: IOTAProjectGroup; var ModuleServices : IOTAModuleServices; i : Integer; begin // Standaard resultaat Result := nil;
// Verkrijg een referentie naar de moduloe services
ModuleServices := (BorlandIDEServices as IOTAModuleServices);
// Loop door de modules for i := 0 to ModuleServices.ModuleCount - 1 do begin
// Als een van de modules het IOTAProjectGroup interface implementeert, // dan is er een project group gevonden if (ModuleServices.Modules[i].QueryInterface(
IOTAProjectGroup, Result) = S_OK) then Break;
end;
// Laat referentie naar interface los ModuleServices := nil; end;
Zoals te zien is, is de project group ook gewoon een van de modules
die geopend is in de Delphi IDE. Door aan een module te vragen of
deze het IOTAProjectGroup interface implementeert, komen we te weten
of het de project group module is. Er is gelukkig altijd maar één
project group module en dat vereenvoudigd het zoeken aanzienlijk.
Om te bepalen of een module zich in een project uit de project
group bevindt, kan worden gekeken naar het IOTAProject interface
van elk project in de project group. Door gebruik te maken van de
GetModuleCount method van het IOTAProject interface kan het
aantal modules van een project worden bepaald. Door vervolgens gebruik
te maken van de GetModule method kan van iedere module een
interface referentie worden opgevraagd. Door de Filename
property van de huidige module en de module uit het project te vergelijken
kan worden bepaald of het dezelfde module betreft en dus of de module
in het betreffende project is opgenomen.
// Tot dusverre is er geen project gevonden waarin de huidige module // zich bevindt ModuleProjectIndex := -1;
// Loop door alle projecten van de projectgroup for p := 0 to ProjectGroup.ProjectCount - 1 do begin
// Loop door alle modules van het project for m := 0 to ProjectGroup.Projects[p].GetModuleCount - 1 do begin
// Als de filename van de module module overeenkomst met de filename // van de huidige module dan wordt het project als gevonden beschouwd if SameText(ProjectGroup.Projects[p].GetModule(m).FileName,
Module.FileName) then begin // Onthou index ModuleProjectIndex := p; // Stop loop Break; end;
end;
// Als er iets gevonden is, stop dan de loop if ModuleProjectIndex <> -1 then Break;
end;
Zodra er een project gevonden is waar de actieve module zich in
bevindt weten we dat dit het project is dat gecompileerd moet worden.
Er kan nu via de ProjectBuilder property van het project
interface opdracht gegeven worden om te builden. Er moet daarbij
wel rekening gehouden worden met het feit dat het package waarin
deze functionaliteit geïmplementeerd wordt zelf natuurlijk
niet gecompileerd wordt, want dat zou natuurlijk niet werken. Voor
deze vergelijking vind ik een controle op de bestandsnaam van het
package voldoende, hoewel het niet honderd procent waterdicht is.
De kans dat er andere packages die precies op idemenu.dpk
eindigen acht ik klein genoeg.
if Pos('IDEMENU.DPK',
UpperCase(
ExtractFileName(
ProjectGroup.Projects[ModuleProjectIndex].FileName))) = 0 then ProjectGroup.Projects[ModuleProjectIndex].ProjectBuilder.BuildProject(
cmOTAMake, False);
Build from current project
Volgens een zelfde strategie kan worden bepaald wat het huidige
project is en welke projects er dan gebuild moeten worden als er
vanaf het huidige project gebuild moet worden. Een referentie verkrijgen
naar de project group verloopt op dezelfde wijze als bij build
project of current unit. Aangezien de CurrentProjectGroup
functie dubbel is opgenomen is het wellicht handig om een aparte
unit met open tools api functies te beginnen waarin al dergelijke
functies zijn opgenomen. Om de eenvoud van distributie van de individuele
IDE menu uitbreidingen niet te verhogen laat ik dat hier achterwege.
Nadat een referentie naar de project group verkregen is moet het
huidige project bepaald worden. Daar heeft de project group gelukkig
een property voor, de ActiveProject property. We willen echter
niet het IOTAProject interface van het huidige project weten, maar
de index van het huidige project in de lijst van projecten in de
project group. Daar biedt het IOTAProjectGroup interface geen faciliteiten
voor, dus er zit niets anders op dan één voor één
door de projecten te lopen om het actieve project te vinden.
// Loop door alle projecten van de project group for p := 0 to ProjectGroup.ProjectCount - 1 do begin
// Vergelijk of het project dezelfde filename heeft als het actieve project if SameText(ProjectGroup.Projects[p].FileName,
ProjectGroup.ActiveProject.FileName) then begin // Zo ja dan is het huidige project gevonden. Onthou de index. CurrentProjectIndex := p; // Doorbreek de loop Break; end;
end;
Zodra de index van het actieve project bekend is, kan de lijst
van de projecten vanaf dat punt worden afgelopen om elk van de projecten
te builden. Omdat ik niet na het builden van ieder project op OK
wil klikken, geef ik alleen na het builden van het laatste project
een boodschap weer. Wederom geldt dat het IdeMenu package zelf niet
gecompileerd mag worden.
// Loop door de projecten van de project group for p := CurrentProjectIndex to ProjectGroup.ProjectCount - 1 do begin
// Compileer nooit het package waarin deze code zit. Geef alleen een // boodschap weer bij het laatste package. if Pos('IDEMENU.DPK',
UpperCase(ExtractFileName(ProjectGroup.Projects[p].FileName))) = 0 then
ProjectGroup.Projects[p].ProjectBuilder.BuildProject(
cmOTAMake, p = (ProjectGroup.ProjectCount - 1)
end;
De menu opties
Om de nieuwe compiler opdrachten beschikbaar te maken wordt weer
gebruik gemaakt van de IDE menu uitbreiding die in een
eerder artikel al beschreven staat. Wederom geldt dat het toevoegen
van unit uIdeMenuBuildFromCurrent
en unit uIdeMenuBuildUnitProject
aan de uses clause van unit uIdeMenuExpert en het aanmaken van een
instance van beide classes in de constructor van de expert voldoende
is. Natuurlijk is voor de mensen die liever niet zelf de code aanpassen
ook een aangepaste versie van het IdeMenu
package beschikbaar.
Conclusie
Delphi mist duidelijk een aantal manieren om de compiler aan te
roepen. Gelukkig is de open tools api echt open, zodat
we zelf in staat zijn de compiler opdrachten te geven. Door gebruik
te maken van wat de open tools api interfaces aanbieden en wat handige
mogelijkheden om het Delphi IDE menu uit te breiden is Delphi voorzien
van wederom een handige toevoeging.
|