Search code examples
delphipathcomponentslibrary-path

How best to modify / install a component library path into the Delphi IDE without doing it manually?


I am preparing an installer (Inno Setup) to install my component package into Delphi XE without having to manually fiddle in the IDE.

I need to modify the Delphi library path, for example to delete part of it (for example, xxx;MyOldPath;yyy) and insert a new path, xxxx;MyNewPath;yyyy. Is there a preferred way of doing this or will I have to write a utility to do it?

Thanks


Solution

  • Modifying the path is basic string manipulation: you read the current path from the registry, manipulate it to suit your needs, write it back.

    You can probably write an Inno Setup script function so you don't have any external dependencies. Or write a Delphi DLL that's used from Inno Setup's script so it'll be easier to debug.


    Edit

    Here's a modified version of a routine I'm actually using in production. It'll read the whole list of paths from either the Search Path registry value or the Browsing Path (any other path for that matter), potentially remove some paths, and add some paths if they don't already exist.

    procedure UpdateDelphiPaths(const RegistryKey, RegistryValue: string; PathsToRemove, PathsToAdd: TStrings);
    var R:TRegistry;
          SKeys:TStringList;
          Found:Boolean;
          Updated:Boolean;
          i,j:Integer;
          s:string;
          R_Globals:TRegistry;
    
      // This function normalises paths in comparasions
      function PrepPathForComparasion(const Path:string):string;
      begin
        if Path = '' then Result := '\'
        else
          if Path[Length(Path)] = '\' then
            Result := LowerCase(Path)
          else
            Result := LowerCase(Path) + '\';
      end;
    
      function PathMatchesRemoveCriteria(const Path:string): Boolean;
      var i:Integer;
      begin
        // This needs to be addapted to match your criteria!
        for i:=0 to PathsToRemove.Count-1 do
          if AnsiPos(PathsToRemove[i], Path) <> 0 then
            Exit(True);
        Result := False;
      end;
    
    begin
      R := TRegistry.Create;
      try
        R.RootKey := HKEY_CURRENT_USER;
        if R.OpenKey(RegistryKey + '\Library', False) then
          if R.ValueExists(RegistryValue) then
          begin
            SKeys := TStringList.Create;
            try
              SKeys.Delimiter := ';';
              SKeys.StrictDelimiter := True;
              SKeys.DelimitedText := R.ReadString(RegistryValue);
    
              Updated := False;
    
              // Look at all the paths in the PathsToAdd list, if any one's missing add it to the list and mark
              // "Updated".
              for i:=0 to PathsToAdd.Count-1 do
              begin
                Found := False;
                for j:=0 to SKeys.Count-1 do
                  if LowerCase(Trim(SKeys[j])) = LowerCase(Trim(PathsToAdd[i])) then
                    Found := True;
                if not Found then
                begin
                  SKeys.Add(PathsToAdd[i]);
                  Updated := True;
                end;
              end;
    
              // Look at every single path in the current list, if it's not in the "PathsToAdd" and it matches
              // a name in "PathsToRemove", drop it and mark "Updated"
              i := 0;
              while i < SKeys.Count do
              begin
                if PathMatchesRemoveCriteria(SKeys[i]) then
                  begin
                    // Path matches remove criteria! It only gets removed if it's not actually present in
                    // PathsToAdd
                    Found := False;
                    for j:=0 to PathsToAdd.Count-1 do
                    begin
                      if PrepPathForComparasion(SKeys[i]) = PrepPathForComparasion(PathsToAdd[j]) then
                        Found := True;
                    end;
                    if not Found then
                      begin
                        SKeys.Delete(i);
                        Updated := True;
                      end
                    else
                      Inc(i);
                  end
                else
                  Inc(i);
              end;
    
              // If I've updated the SKeys in any way, push changes back to registry and force updates
              if Updated then
              begin
                s := SKeys[0];
                for i:=1 to SKeys.Count-1 do
                  if SKeys[i] <> '' then
                  begin
                    s := s + ';' + SKeys[i];
                  end;
                R.WriteString(RegistryValue, s);
    
                // Force delphi to re-load it's paths.
                R_Globals := TRegistry.Create;
                try
                  R_Globals.OpenKey(RegistryKey + '\Globals', True);
                  R_Globals.WriteString('ForceEnvOptionsUpdate', '1');
                finally R_Globals.Free;
                end;
    
              end;
    
            finally SKeys.Free;
            end;
          end;
      finally R.Free;
      end;
    end;
    

    From Delphi code I can call the routine like this, to make sure the latest search path to a given library is installed:

    var ToRemove, ToAdd: TStringList;
    begin
      ToRemove := TStringList.Create;
      try
        ToAdd := TStringList.Create;
        try
          ToRemove.Add('LibraryName\Source');
          ToAdd.Add('C:\LibraryName\Source');
          UpdateDelphiPaths('Software\CodeGear\BDS\7.0', 'Test Path', ToRemove, ToAdd);
        finally ToAdd.Free;
        end;
      finally ToRemove.Free;
      end;
    end;
    

    Notice both the ToRemove and ToAdd. I can safely specify a search path in both Remove and Add lists: Paths are only removed if they match "Remove" criteria but aren't also in the "ToAdd" list. Also note the PathMatchesRemoveCriteria function.

    You can probably modify the code to work straight from the InnoScript itself, or you can put the code in a DLL and use the DLL from the installer. The DLL variant has the merit of being easily debugged in Delphi and very easy on Inno itself; The Inno variant has the merit of not having external dependencies, but the code would need to be adapted and debugged.