Delphi tools - Compiler commandsGeplaatst door Baldo op 17-04-03Erik 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.
|