Delphi conditional compilation in uses clause
Asked Answered
S

4

11

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
Symmetry answered 1/10, 2014 at 23:31 Comment(7)
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.Endbrain
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 ?Symmetry
Also want to note that Delphi 2010 IDE is fine with conditional statements in the interface uses. My only problem is in XE7 IDE.Symmetry
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.Endbrain
I was about to clarify that then Ken did :-) Additionally, I've also had experience with the IDE playing with all the code in the project's main file. Creating a new unit for the application initialization solved that, with only the core used units and a single call to my procedure RunApp. The IDE won't play with the source there, because it's not tied to a form or any other component.Genvieve
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.Endbrain
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.)Endbrain
N
14

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.

Nominal answered 2/10, 2014 at 0:25 Comment(12)
However, unit aliases work just fine. And are probably simpler.Sims
I am confused a little here - @Nominal seems to be saying Aliases DO NOT work- 'you cannot even employ a Unit Alias', David says you can. Deltics, am I reading you right ?Divided
Ho Hugh, David is quite right. My observation re aliases not working referred to the fact that if you have an alias for a unit that the IDE wants to add, then the IDE will still add that unit even if you already have an alias for that unit in the uses list. I thought that the dodge of aliasing to another existing unit would cause a duplicate unit error. I should have tested it. The compiler is happy with duplicate units, it's the parser that complains about a duplicate IDENTIFIER if you have a duplicate unit name. The alias keeps the parser happy, and the compiler never cared anyway.Nominal
I've updated the answer to make this distinction and to re-direct readers to David's answer as this is far more straightforward than faffing around with placeholder units. :)Nominal
I think this dupe handling was needed to support the classic aliases: WinTypes=Windows,WinProcs=WindowsSims
One more thing you could do with the placeholder unit is add a {$IF CompilerVersion >= 24} {$Message Error 'Do not include this unit in new versions of Delphi'} {$IFEND} to force a compiler error if it is inadvertently included in a project for a more recent version.Mithraism
@Craig, this rather defeats the object of having a single unit of source which will compile in all versions of Delphi (avoiding the different IDE versions munging a form interface uses clause). Having said that, it might be useful to emit a hint or a warning, rather than an error. But using the more elegant "alias to System unit" approach renders the point moot. :)Nominal
The point is that you should never include the placeholder unit in a newer version's project file. Doing so would lead to extremely strange error messages. Undeclared identifier ... But how can that be?? I'm using the unit!! So if you do make the mistake of incorrectly including the placeholder unit in the wrong project file - the first error you'll get is one telling you it doesn't belong in that particular file. Far less confusion to fix that way.Mithraism
As for the alias... I would like that solution if not for: - The buggy .dproj (I mainly use D2007). - And glut of environment specific information it saves that it absolutely shouldn't! (Run parameters??? Really?!!) ... As things stand, given the choice between adding a file to the project vs changing other .dproj settings: I prefer the former.Mithraism
I see now what you mean, but we are talking about units that are added to the form uses clause by the IDE rather than ever added explicitly either to the form OR to the DPR uses list. So whilst you should never add a placeholder unit to a newer versions project DPR, equally you should never be adding placeholder - or other VCL units - to ANY project DPR uses, except in cases where you have specifically updated/replaced a VCL unit (e.g. to fix a bug in the original VCL unit) in which case you have bigger fish to fry in terms of isolating version specific VCL replacement units.Nominal
There's a difference between adding to uses of another unit (which just identifes the unit name) and adding to dpr/dproj (which can indentify a physical file corresponding to a particular unit name). The exact same technique of forcing a compiler error can be used if you have a version specific VCL fix. E.g. If Printers.pas has a bug only in the D2007 version, use a fixed version of Printers.pas, but use conditional compilation to force a clear error if the fixed version is compiled compiled using a different version of Delphi. Like version specific folders, it's a helpful technique.Mithraism
There's an echo in here. ;)Nominal
S
9

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.

Sims answered 2/10, 2014 at 7:8 Comment(4)
Judging by the OP question Delphi XE7 has no Actnlist unit, so it is unclear why XE7 IDE will be happy with the presence of absent unit in the uses clause.Toothache
@user246408 XE7 certainly has Actnlist: docwiki.embarcadero.com/Libraries/XE7/en/Vcl.ActnListSims
Ok, then your's and Deltic's answers answer slightly different questions.Toothache
@user246408 I don't think so. The user can use ActnList or not as they please. The question is all about dealing with the XE7 IDE forcing the Actions into uses clauses. A problem that I have encountered myself. Deltics provides another way to arrange for old versions, whose VCL do not have an Actions unit, to accept the presence of the Actions unit in the uses clause. I think you are misunderstanding something.Sims
M
1

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.

Mountie answered 2/10, 2014 at 0:40 Comment(1)
Yes. :-) This has the exact same issue - it's not going to work in the interface uses clause for units related to components dropped on the form at design-time. The IDE owns that uses clause, and any fight with it is lost before it starts. (Read the comments posted to the original question.) Reversing the conditional (and the units affected) won't change that fact.Endbrain
R
1

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

Ragan answered 14/12, 2017 at 14:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.