11
votes

I am trying to modify my Delphi 2010 code to compile in XE7 (and want to retain the ability to compile it in 2010). So in the unit that houses my mainform I added conditional directives. The following works fine in 2010

uses 
  {$IF CompilerVersion >= 24}System.Actions, {$ELSE}Actnlist,{$IFEND}
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,  Dialogs;

But XE7 automatically adds a System.Actions at the end to create a uses clause that now has System.Actions declared twice (see below), and gives an error message [dcc32 Error] MyForm.pas(10): E2004 Identifier redeclared: 'System.Actions'. Why is XE7 not accepting the unit from within the conditional directive ?

uses 
  {$IF CompilerVersion >= 24}System.Actions, {$ELSE}Actnlist,{$IFEND}
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,  Dialogs,
  System.Actions; // <- automatically added
4
Because the interface uses clause in a form-related unit belongs to the IDE itself, and it will add whatever is needed in the current version for any components placed on a form. The only way to overcome this is to delete the components from the form, place the declarations in the private section instead of the default published one, and create the components at runtime. You're fighting the IDE itself, and you won't win that battle.Ken White
But I already have all the needed units,don't I ? Delphi is processing the {$IF CompilerVersion >= 24}System.Actions{$IFEND} section. If I make a "typo" like (`{$IF CompilerVersion >= 24}System.Action*s{$IFEND} it complains. So, it is aware of the existence of System.Actions within the interface uses. Could it be that IDE and Compiler are not in sync ?ss2006
The IDE doesn't know about the conditional defines. It just knows that you have a certain component on the form, and it can't find that unit in the uses clause, so it adds it. As I said, the interface uses clause belongs to the IDE, not you. If the component is on the form, the IDE will insure that the unit is directly included in the uses clause. I'll repeat it a third time for clarity: The interface uses clause in a form-related unit belongs to the IDE with regard to any component dropped on a form.Ken White
OK. You asked, I provided information. I won't debate it with you, but I've been dealing with this issue since Delphi 2 was released and I needed to support conditional defines between 16 and 32 bit versions of components. :-) You're fighting a losing battle against the IDE - it always wins.Ken White
BTW, I think the part you're missing here is that the IDE does not compile the code to determine what is (or isn't) in the uses clause for components. It simply parses that uses clause to see if it has all of the needed units for components that are on the form, which is why it can't see what's wrapped in the conditionals. (The conditionals are for the compiler, and the IDE doesn't compile for this purpose.)Ken White

4 Answers

14
votes

As Ken says, the interface uses clause will be modified by the IDE and the processes by which this is achieved are somewhat less than sophisticated (as you have discovered). The same problem affects the project uses clause. Unfortunately this is much harder to avoid in the case of Form/DataModule uses clauses.

You could use a Unit Alias (see David Heffernan's answer) but need to be aware that if you create an alias for a unit that the IDE wishes to add, then the IDE will still add a reference to the required unit since it does not recognise the alias as identifying that required unit. Aliasing to the System unit will avoid this since it is already (implicitly) used by every unit.

Another alternative is to remove all such conditionals from your uses list and instead create place-holder units as required so that the different compilers you wish to use on the project can each be satisfied by the single uses list combined from the list that each IDE insists is required (the IDE won't remove unused units from the uses list, something that is often a complaint but in this case actually helps solve your problem).

In this case, in your Delphi 2010 project create an empty Actions unit:

 unit Actions;
 interface
 implementation
 end.

You will of course need to ensure that this unit is not in the project path for your XE7 version of the project.

One way to achieve that would be ensure that the empty Actions.pas unit is not explicitly listed in the DPR uses list, but is placed in a subfolder of your project source (e.g. 'placeholders'). You can then add this subfolder to the project search path for the Delphi 2010 version but not the XE7 version:

 \Project Folder

     project2010.dpr
     project2010.dproj
     projectXE7.dpr
     projectXE7.dproj

     \placeholders
          Actions.pas

If you find that you need placeholders for each of the different versions then you will need separate placeholder folders. You might create further version specific subfolders, for example:

     \placeholders
          \2010
               Actions.pas
          \XE7
               D2010UnitNotPresentInXE7.pas

This sort of structure might be advisable simply from the point of view of creating an auto/self documenting organisation.

Note that this is only required for dealing with unit references in the uses clause of the interface section of Forms (or Frames etc). In non-visual units or in the implementation section, the IDE does not interfere so conditional compilation directives should present no issues there.

9
votes

The easiest way to fix this is to add a unit alias to your Delphi 2010 project. You'll need to use different .dproj files for your different Delphi versions, but you need to do that anyway.

In the unit aliases settings for the Delphi 2010 project add this:

Actions=System

I'm using System as the alias target because the System unit is automatically included in every Delphi unit and so aliased inclusions are benign. It's the simplest way that I can think of to make the compiler effectively ignore an entry in a uses clause.

Then you can declare your uses clause like this:

uses 
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
  Actions, Actnlist;

This will compile fine in Delphi 2010, because the alias processing will map Actions onto System. In XE7 you are also fine because there is no alias, and the IDE is satisfied by the presence of the Actions unit and so feels no compulsion to modify the uses clause.

1
votes

Would there be something wrong with

{$IF CompilerVersion < 24}Actnlist,{$IFEND}

or is this an academic argument?

Addendum...

Then add a dummy System.Actions.dcu containing nothing into your 2010 compile-path.

I'd theorise that the IDE would then insist on inserting uses ... System.Actions, 2010 has what it wants, XE7 has what it wants.

But I don't have XE7 so I can't test it out.

1
votes

we had the same issue... The easiest way is to do it that way:

{$IF CompilerVersion < 24}{$ELSE}System.Actions,{$IFEND}
{$IF CompilerVersion >= 24}{$ELSE}Actnlist,{$IFEND}

If you open the file in old IDE's, than you may see an error, which says "unit X" not found, but it will compile fine and no automatic adding is performed. It looks not so nice, but it works quite well...

Kind regards,

Bernd