How to prevent #include from loading same procedure twice?

Hello,

Some of my procedures are using the same basic functions, which are saved in separate procedures. So my procedures start with an #include-statement to load these basic procedures, to be sure they are available. However, if multiple procedures include the same basic procedure I get an error while compiling that the function name is already existing, because the same procedure is loaded multiple times.

I already tried

If(!Exists(FunctionName))
#include ":BasicProcedure"
Endif


but this seems to have no effect. I guess the "If" does not affect anything while compiling. The manual states options for #include, but none is suitable.

So how can I turn off the #include under certain conditions, or kill all except one of the instances when a collision between procedures appears?

Greeting,
Mathias

#include shouldn't load the same file twice. It's more likely that you have the same function name in two different files that are being loaded, or that you have two copies of the same file, one loaded manually and the other by #include, or something like that.

In reply to by tony

Thank you for this hint, you are completely right. When more than one procedure want to include the exact same procedure file, then #include only loads this file once. But when you have the following folder structure:

  • my Procedures
    • Procedure1.ipf
    • BasicProcedure.ipf
  • shared Procedures
    • Procedure2.ipf
    • BasicProcedure.ipf

and both Procedure1.ipf and Procedure2.ipf contain

#include ":BasicProcedure"

then the error happens. Igor does not recognize that both files are the same (by name and checksum) and loads both.

So the solution is to have only one BasicProcedure.ipf in one folder, and let all relative paths point to this one file.

 

Then a new problem arises: The folder "shared Procedures" has subversions inside. Subfolders are tags, trunk, branch with different folder depths. So it is necessary to have BasicProcedure.ipf inside each folder. E.g. letting Procedure1.ipf in "my Procedures" point to Basicprocedure.ipf in "shared Procedures/trunk" will result in multiple loaded files when using "Procedure2.ipf" in "shared Procedures/tags/1.0".

Is there a way to solve this problem inside Igor? Or maybe I just need help to use subversion in a clean way.

In reply to by Mathias

you should be aware that

#include "procedure"

loads the first instance of procedure.ipf found the the User Procedures folder (doesn't seem to be affected by location of the procedure file containing the include statement).

Also that independent modules, named modules and static functions can help avoid conflicts.

Maybe if you avoid specifying the path that will do the trick? If you're sure that the files will always be identical you can probably get away with it.

Also, I strongly recommend storing procedure files in the User Procedures folder, rather than in Wavemetrics Procedures, as implied by your use of #include < > rather than #include " ".

BTW, I think that in the help for #include may be a bit misleading: 'If file spec is the name of or a partial path to the file, Igor interprets it as relative to the "User Procedures" folder'. It seems to be relative to the calling procedure file, not User Procedures.

In reply to by tony

First of all, I had a typo. I am using #include ":BasicProcedure" instead of the version with the <...>. Sorry for that.

And thank you for pointing towards modules and static functions. It seems like I have to put all procedures (including BasicProcedure.ipf) within "shared Procedures" into one regular module to avoid the name conflict. Furthermore I should put the version of BasicProcedure.ipf of the other folder into my User Procedure folder, so there is only one file at one position.

By doing so I should be able to solve the problem of name conflicts. Furthermore the procedures in "Shared Procedures" can be even better shared with coworkers, so that they just use the folder and have all procedures available, without name conflicts because they have used the same function name.

I will test it in the next days, if it works I will hopefully not forget to post the solution underneath for other people.

In reply to by Mathias

In your example if you simply switch #include ":BasicProcedure" to #include "BasicProcedure" the conflict should go away. No need for modules. Should be okay if all copies of BasicProcedure.ipf are identical.

I want to emphasize what Tony just said- that colon in #include ":BasicProcedure" is really important, and the likely source of the original problem. For all the gory details on the various formats for #include, execute this Igor command: DisplayHelpTopic "The Include Statement"

When the two files are identical ...

One better approach is to put BasicProcedure in to the Igor Procedures folder. It will then load automatically every time Igor is opened.

The other better approach is to put BasicProcedure into its own "User Procedures: Common Procedures" folder. Then, when any procedure calls to #include "BasicProcedure", the file will only be loaded at the first call. All subsequent calls will be aware the procedure file is already loaded.

In reply to by jjweimer

Regarding the remark with the colon: Just #include "BasicProcedure" looks only for procedures within the User Procedures folder. Removing the colon results in Igor not finding the local procedure.

And I will put BasicProcedure.ipf in the User Procedures folder, this folder is an aspect of Igor that I ignored for too long.

But the problem is that all procedures in "Shared Procedures" should be easily accessible by others and run out-of-the-box. So my coworkers can just download the folder, compile one procedure consisting of many #include ":...", and everything will run on its own. Not moving anything in the User Procedures folder.

To accomplish this goal for "Shared Procedures" without conflicts a module seems to be the best solution.

In reply to by Mathias

I see several possible scenarios:

  1. You are the developer and you have some users who will download a standalone package. The conflict problem arises in your developing environment, but not on the user side.
  2. Users also end up with conflicts because they have multiple copies of the package loaded at the same time.
  3. You are developing as a team and your versioning setup is causing conflicts in Igor.

Depending on which of these best describes the problem the solution may differ. In general, developing outside of User Procedures may make sense, but your life will be much easier if you can persuade your users to store code in the User Procedures folder.

Don't forget that the help menu has a 'Show Igor Pro User Files' option.

 

In reply to by Mathias

Mathias wrote:

... But the problem is that all procedures in "Shared Procedures" should be easily accessible by others and run out-of-the-box. So my coworkers can just download the folder, compile one procedure consisting of many #include ":...", and everything will run on its own. Not moving anything in the User Procedures folder.

To accomplish this goal for "Shared Procedures" without conflicts a module seems to be the best solution.

The best solution is to manage this non-standard approach properly up-front at your end. Independent Modules are one way to go but they are really thought of as a means to solve a different set of problems than what you face.

In the end though, insisting that procedures must be stored in User Procedures can save you more development headaches even when it means you have to spend a bit more time to educate your co-workers.

BTW, when you are running on macOS, I believe that you can handle storing files external to User Procedures by putting a symlink (not an alias) of your external (Shared Procedures) folder directly in the User Procedures.

@Mathias: Could you add a MWE so that we have something to play around with?

Also I'm not getting how modules should help here, naming the modules differently is an option but that is of course a maintenance nightmare.

In reply to by thomas_braun

thomas_braun wrote:

@Mathias: Could you add a MWE so that we have something to play around with?

Here is a MWE that is working with Independent Modules. It worked once, as I am having some problems with Igor under Wine right now I cannot check anymore.

// in folder "Shared Procedures":
// Basic.ipf
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3     // Use modern global access method and strict wave access.
#pragma IndependentModule=Test1


Function TestBasic()
    print "Clear"
End


// Test1.ipf
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3     // Use modern global access method and strict wave access.
#pragma IndependentModule=Test1

#include ":Basic"

Function MyFunctionA()
    TestBasic()
End


// in folder "My Procedures":
// Basic.ipf
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3     // Use modern global access method and strict wave access.
#pragma IndependentModule=Test2


Function TestBasic()
    print "Clear"
End


// Test2.ipf
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3     // Use modern global access method and strict wave access.
#pragma IndependentModule=Test2

#include ":Basic"

Function MyFunctionB()
    TestBasic()
End

So this works as I want, the big downside is that you need to write

Test1#MyFunctionA()

So this means not only changing the header of every procedure but every call of a function. Yes, this is a big pain to change everything. But still, I think putting everything in "Shared Procedures" in one Independent Module would be a clean solution if you want it to work out of the box for everyone.

And in the last year there was only one coworker who wanted to use these procedures, he was even too lazy to understand SVN and how to download from a repository. In the end I gave him the files on a flash drive. He was not willing to do this work to get 200kb of working source code with GUIs. That is why I do not think putting the files in the User Procedures folder will work.

I might consider this structure ...

Shared Procedures
- Common
-- Basic.ipf

- Test1 Folder
- Test2 Folder
- ...
- TestN Folder

The Basic.ipf procedure is only stored in ONE PLACE. In your TestN procedures, you should then need only to #include "::Common:Basic.ipf" (this should be the right syntax based on going up and then down again).

Otherwise, SVN can be a bit of a pain to appreciate. Even I get befuddled by remembering where I put what at times. When you are doing this distribution only internally, the easier option might be to use a "more transparent" (for those who are less inclined to get in to deep technology) cloud distribution source such as Dropbox or GDrive or even simply sftp. And the idea then might be to say ... Download this ZIP archive from here, put it in the User Procedures folder here, unZIP it there, and viola!

Using a name like

Basic.ipf

is not a good idea as that can collide with other peoples code.

I've attached  a MWE using an approach with file name prefixes and normal includes. This works out of the box. If you go that route you have to be sure that the common functions work with all your different projects. Ensuring that can be done with e.g. some automated testing, I'm using https://github.com/byte-physics/igor-unit-testing-framework (disclaimer: I wrote most of it) for that.

MWE.zip

In reply to by jjweimer

jjweimer wrote:

The Basic.ipf procedure is only stored in ONE PLACE. In your TestN procedures, you should then need only to #include "::Common:Basic.ipf" (this should be the right syntax based on going up and then down again).

Of course this would be a clean solution, but the problem is that the folder depth varies as written in the second comment.

And regarding the MWE of thomas_braun: "Basic.ipf" is a name chosen for clarity in this forum. The real name is "ShirleyBKG.ipf", which is widely used in our group. And then the main problem arises when someone wants to use the procedures in "Shared Procedures" out of the box. That ShirleyBKG.ipf was already loaded from another directory and that there is a collision.

Educating everyone to avoid this collision by putting ShirleyBKG.ipf in everyones' User Procedures folder as suggested in the third and seventh comment would not work. When there is a compilation error many colleagues simply give up.

In reply to by thomas_braun

thomas_braun wrote:

@Mathias: I think I'm lost here. What is missing from the example MWE I provided?

The folder structure is more difficult than just two folders. It is basically:

  • Shared Procedures
    • Tags
      • 1.0
      • 1.1
    • Trunk
  • My Procedures

And in every folder (that does not have subfolders inside) there is a copy of Basic.ipf. So the problem is, having one version e.g. in Shared Procedures means that when I copy the files from trunk to tag/1.2, the relative path is not working anymore because the files are now 1 layer further away.

So the only clean solution where there is only one Basic.ipf would be the User Procedures folder, but that would mean that I would have to educate my coworkers. No longer working out-of-the-box.

A more tedious attempt (that I will try) is to put all procedures within Shared Procedures into one independent module. But I do not have the time to try this right now, as it means to change every function call from "function(...)" to "modulename#function(...)".

I hope this sums up the current point of the argumentation.

I'm not sure that this will solve your problem, but I would recommend checking out either trunk or one of the specific tags instead of checking out the top level folder of the repository. You can use the SVN switch command to switch your local Shared Procedures directory to point to svn://xyz/repository/tags/1.0/ or svn://xyz/repository/trunk/. That should reduce the number of Basic.ipf copies within your hierarchy.

aclight is totally right. You don't want to include muliple versions from your version control system.