For static code analysis tools, it is necessary to know all effective source paths for a given Delphi project, which are defined on project level and in the global IDE configuration.
Is there a Delphi library which can collect this kind of project information?
As far as I know, the registry settings for the Delphi IDE can be in different places, to support multiple configurations. But for a given combination of the IDE registry location and a project file, it should be possible to collect the source paths.
Edit: Another solution is to use the --depends switch. This will cause dcc32.exe to write a ".d" file with all dcu file names of the project (and all dependencies), including the path names. However, the file list includes units which are compiled already, so it is not a correct solution for the original problem.
You can use OpenTools API to get the active project's search path (merged from active configuration and option set) and the IDE's global library path. Here is a unit from my quick test design package:
unit Unit1;
interface
uses
Windows, SysUtils, Classes,
ToolsAPI;
type
TTestWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
private
{ IOTAWizard }
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute;
{ IOTAMenuWizard }
function GetMenuText: string;
private
function AddLibraryPaths(Strings: TStrings): Integer;
function AddProjectSearchPaths(Strings: TStrings): Integer;
end;
procedure Register;
implementation
uses
Dialogs,
DCCStrs, TypInfo;
var
WizardIndex: Integer = -1;
procedure GetEnvironmentVariables(Strings: TStrings);
var
P: PChar;
begin
P := nil;
Strings.BeginUpdate;
try
Strings.Clear;
P := GetEnvironmentStrings;
repeat
Strings.Add(P);
P := StrEnd(P);
Inc(P);
until P^ = #0;
finally
if Assigned(P) then
FreeEnvironmentStrings(P);
Strings.EndUpdate;
end;
end;
function EvaluateEnvironmentVariables(const S: string): string;
var
Strings: TStringList;
I: Integer;
begin
Result := S;
Strings := TStringList.Create;
try
GetEnvironmentVariables(Strings);
for I := 0 to Strings.Count - 1 do
Result := StringReplace(Result, Format('$(%s)', [Strings.Names[I]]), Strings.ValueFromIndex[I],
[rfReplaceAll, rfIgnoreCase]);
finally
Strings.Free;
end;
end;
procedure Register;
begin
WizardIndex := (BorlandIDEServices as IOTAWizardServices).AddWizard(TTestWizard.Create);
end;
{ TTestWizard private: IOTAWizard }
function TTestWizard.GetIDString: string;
begin
Result := 'TOndrej.TestWizard';
end;
function TTestWizard.GetName: string;
begin
Result := 'TestWizard';
end;
function TTestWizard.GetState: TWizardState;
begin
Result := [wsEnabled];
end;
procedure TTestWizard.Execute;
var
Paths: TStrings;
begin
Paths := TStringList.Create;
try
AddProjectSearchPaths(Paths);
AddLibraryPaths(Paths);
ShowMessage(EvaluateEnvironmentVariables(Paths.Text));
finally
Paths.Free;
end;
end;
{ TTestWizard private: IOTAMenuWizard }
function TTestWizard.GetMenuText: string;
begin
Result := GetIDString;
end;
function TTestWizard.AddLibraryPaths(Strings: TStrings): Integer;
var
Paths: TStringList;
EnvironmentOptions: IOTAEnvironmentOptions;
begin
Paths := TStringList.Create;
try
Paths.Delimiter := ';';
Paths.StrictDelimiter := True;
EnvironmentOptions := (BorlandIDEServices as IOTAServices).GetEnvironmentOptions;
Paths.DelimitedText := EnvironmentOptions.Values['LibraryPath'];
Strings.AddStrings(Paths);
Result := Paths.Count;
finally
Paths.Free;
end;
end;
function TTestWizard.AddProjectSearchPaths(Strings: TStrings): Integer;
var
ActiveProject: IOTAProject;
Configurations: IOTAProjectOptionsConfigurations;
Configuration: IOTABuildConfiguration;
Paths: TStringList;
begin
Result := -1;
ActiveProject := GetActiveProject;
if not Assigned(ActiveProject) then
Exit;
Configurations := ActiveProject.ProjectOptions as IOTAProjectOptionsConfigurations;
Configuration := Configurations.ActiveConfiguration;
if not Assigned(Configuration) then
Exit;
Paths := TStringList.Create;
try
Configuration.GetValues(sUnitSearchPath, Paths, True);
Strings.AddStrings(Paths);
Result := Paths.Count;
finally
Paths.Free;
end;
end;
initialization
finalization
if WizardIndex <> -1 then
(BorlandIDEServices as IOTAWizardServices).RemoveWizard(WizardIndex);
end.
Just found another solution:
if I launch the RAD Studio command prompt and run
msbuild /t:Rebuild
in the project directory, msbuild will show the full command line to invoke dcc32, including all path settings. Redirecting the build log to a file (or replacing dcc32.exe with a self made version which only captures the parameters) and parsing the output seems to be a lot easier than to parse dproj files.
Another advantage is that it can be used in automated builds / continuous integration.