Search Unity

Conditional compiling, check if a namespace/plugin is available using #if #endif.c

Discussion in 'Scripting' started by MirkoSon, Jun 16, 2017.

  1. MirkoSon

    MirkoSon

    Joined:
    Oct 14, 2015
    Posts:
    24
    Is it possible to "#define" a condition that excludes a script from compiling based on the absence/presence of a specific plugin, let's say, Vuforia?

    I have a script that I would use in different projects but I need to call "using Vuforia;" only if that is loaded into that project.

    I thought that an #if and #endif structure might be the solution, but can I define that condition somehow?

    Thanks in advance.
     
  2. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,089
    As far as I'm aware that's not possible in C# in the same way as it is in C++. You can set up #defines in the Unity settings though (e.g. 'Build Settings->Player Settings->Other Settings->Scripting Define Symbols')

    You can also set up a custom unity editor script to automatically change those define symbols, you could set it up to check if a file exists in the project and automatically set the define up.
     
  3. MirkoSon

    MirkoSon

    Joined:
    Oct 14, 2015
    Posts:
    24
    Thank you for your input, but how am I supposed to define the symbol other than writing its name in the settings?
    I'm new to this, as I've never done it before, not in C# nor in C++.
     
  4. DaDonik

    DaDonik

    Joined:
    Jun 17, 2013
    Posts:
    258
    Unity is a bit special, as in it's necessary to add the define directives to the text field inside the editor settings.
    There are some methods to access and change it's contents from code, but it has to be done from within an editor script.

    I wrote two methods that make it a bit easier to manage adding and removing define directives:
    Code (CSharp):
    1.  
    2. using UnityEditor;
    3.  
    4. public static class EditorUtils
    5. {
    6.     public static void AddDefineIfNecessary(string _define, BuildTargetGroup _buildTargetGroup)
    7.     {
    8.         var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(_buildTargetGroup);
    9.  
    10.         if (defines == null) { defines = _define; }
    11.         else if (defines.Length == 0) { defines = _define; }
    12.         else { if (defines.IndexOf(_define, 0) < 0) { defines += ";" + _define; } }
    13.  
    14.         PlayerSettings.SetScriptingDefineSymbolsForGroup(_buildTargetGroup, defines);
    15.     }
    16.  
    17.     public static void RemoveDefineIfNecessary(string _define, BuildTargetGroup _buildTargetGroup)
    18.     {
    19.         var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(_buildTargetGroup);
    20.  
    21.         if (defines.StartsWith(_define + ";"))
    22.         {
    23.             // First of multiple defines.
    24.             defines = defines.Remove(0, _define.Length + 1);
    25.         }
    26.         else if (defines.StartsWith(_define))
    27.         {
    28.             // The only define.
    29.             defines = defines.Remove(0, _define.Length);
    30.         }
    31.         else if (defines.EndsWith(";" + _define))
    32.         {
    33.             // Last of multiple defines.
    34.             defines = defines.Remove(defines.Length - _define.Length - 1, _define.Length + 1);
    35.         }
    36.         else
    37.         {
    38.             // Somewhere in the middle or not defined.
    39.             var index = defines.IndexOf(_define, 0, System.StringComparison.Ordinal);
    40.             if (index >= 0) { defines = defines.Remove(index, _define.Length + 1); }
    41.         }
    42.  
    43.         PlayerSettings.SetScriptingDefineSymbolsForGroup(_buildTargetGroup, defines);
    44.     }
    45. }
    46.  
    To use those methods, add a folder named "Editor" to your project and in it create a C# file named "EditorUtils".
    Then just copy and paste the above code and you can use those methods in any editor script.
    I use them to enable/disable my own plugins, which is very convenient if you can do that by pressing a button.
     
    IlluBlack likes this.
  5. MirkoSon

    MirkoSon

    Joined:
    Oct 14, 2015
    Posts:
    24
    That's great, could you also provide an example of an editor script which uses these methods?
    Just to clarify how should I use them... (sorry, still very new to this side of Unity :) )
     
  6. DaDonik

    DaDonik

    Joined:
    Jun 17, 2013
    Posts:
    258
    Here is an absolute barebones example:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public class Definer : EditorWindow
    5. {
    6.     [MenuItem("Tools/Definer")]
    7.     private static void OpenWindow()
    8.     {
    9.         const float wndWidth = 200.0f;
    10.         const float wndHeight = 200.0f;
    11.         var pos = new Vector2(0.5f * (Screen.currentResolution.width - wndWidth),
    12.                               0.5f * (Screen.currentResolution.height - wndHeight));
    13.         var window = GetWindow<Definer>();
    14.         window.titleContent = new GUIContent("Definer");
    15.         window.position = new Rect(pos, new Vector2(wndWidth, wndHeight));
    16.     }
    17.  
    18.     private void OnGUI()
    19.     {
    20.         if (GUILayout.Button("Enable"))
    21.         {
    22.             EditorUtils.AddDefineIfNecessary("THE_DEFINE", BuildTargetGroup.Standalone);
    23.         }
    24.         if (GUILayout.Button("Disable"))
    25.         {
    26.             EditorUtils.RemoveDefineIfNecessary("THE_DEFINE", BuildTargetGroup.Standalone);
    27.         }
    28.     }
    29. }
    Just paste this into a script named "Definer" inside of an "Editor" folder. You can then access this little tool via the main menu Tools->Definer. Open the player settings where the defines are and enjoy the text being altered when you press the buttons.
     
    IlluBlack likes this.
  7. MirkoSon

    MirkoSon

    Joined:
    Oct 14, 2015
    Posts:
    24
    Oh I got it, your tool is pretty helpful, though what I wasn't getting was the very obvious usage of symbols.
    So if I put my script between #if MY_SYMBOL and #endif, it will only be compiled if that symbol is defined in the settings. I wonder why it took me so long to realize it :D
    Thanks a lot ^^
     
  8. DaDonik

    DaDonik

    Joined:
    Jun 17, 2013
    Posts:
    258
    It's worth noting that, in a case where you only want to do something when a certain define is set, you can do either of the following:
    Code (CSharp):
    1. #if THE_DEFINE
    2.     // Some code.
    3. #endif
    or:
    Code (CSharp):
    1. [System.Diagnoistics.Conditional("THE_DEFINE")]
    2. public void CallMeOnlyWhenDefined()
    3. {
    4.     // Some code.
    5. }
    The method in the second case is not allowed to have a return value, because it might not even be called, depending on whether THE_DEFINE is defined, or not.
    This does, of course, not work if you need something like:
    Code (CSharp):
    1. #if THE_DEFINE
    2.     // Some code.
    3. #else
    4.     // Some code for when THE_DEFINE is not defined.
    5. #endif
     
  9. amirebrahimi_unity

    amirebrahimi_unity

    Joined:
    Aug 12, 2015
    Posts:
    400
  10. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    @amirebrahimi_unity if I understand the source code correctly, this looks like a great way to detect 3rd party features and be able to write code that is compile-time dependent, but auto-compiled-out when they're missing.

    But I can't find any description of the UnityLabsUtils package that this comes from - anywhere on the web! - or what the correct way is of embedding/referencing/using them. It seems it's shipping as a package right now? Is there any official guide/info on using these classes? I noticed that some of the others are actively fixing / workingaround other bugs or missing features in core Unity APIs, and so I wonder if the utils package is fragile and expected to delete those over time once official Unity versions no longer need them?
     
  11. amirebrahimi_unity

    amirebrahimi_unity

    Joined:
    Aug 12, 2015
    Posts:
    400
    That post was from a while ago. Here is a more recent version:
    https://github.com/Unity-Technologies/conditionalcompilationutility

    There's an example there in the README, however, if you're looking to see how it is used in a project AutoLOD still makes use of it.

    BTW, this utility was really just a stop-gap. Utilizing packages and package dependencies with conditional defines is a better approach.
     
  12. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Great, thanks.

    Re: stop-gap - packages are useless to me until the AssetStore supports them fully - I'm still investigating this, but it seems like the support is now partial (last time I checked, we weren't even allowed to upload packages to asset store!) - in particular: (from my current - possibly incorrect! - understanding) things like package-based conditional defines don't work at all for AssetStore because every user has to re-do them locally themself.

    So if CCU works with AssetStore (which it seems it does) and works in situations like that one above (which - if my reading of the source + github readme suggests it does) then it's really the only viable solution for me right now :).

    (I love packages, but I've been waiting multiple years for Unity to allow non-Unity staff to use them fully. It was frustrating that the policy seemed to be "stop using assetstore if you want packages". (Unity without the AssetStore, and the vast amount of core + critical packages on there, which now make up a large part of the overall engine/platform, is an impossible idea to anyone doing game dev today :)). But - as noted - still investigating to see if that's finally changed)
     
  13. amirebrahimi_unity

    amirebrahimi_unity

    Joined:
    Aug 12, 2015
    Posts:
    400
    Sounds good. If you're going to include this in your AssetStore package, then I suggest a fork/copy & paste with your own namespace, so it doesn't conflict with anything else that might use CCU.

    And agreed on the AssetStore/Packages front -- I think we're working on a better solution for that, but don't hold me to it. Package Manager doesn't really replace Asset Store, especially from a discovery standpoint.
     
    a436t4ataf likes this.
  14. moonproxymode

    moonproxymode

    Joined:
    Feb 20, 2017
    Posts:
    10
    Hey @amirebrahimi_unity, AutoLOD doesn't seem to actually use the 'OptionalDependencyAttribute' anywhere - I'm having some trouble getting it to work correctly in my case.

    I'm trying to detect whether a developer is using SteamVR, OVR or XR in the project.

    Code (CSharp):
    1. [assembly: OptionalDependencyAttribute("OVR", "USE_OVR")]
    2. [assembly: OptionalDependencyAttribute("Valve.VR.InteractionSystem", "USE_STEAMVR")]
    For the definition of the attribute, I had to write a constructor for it to compile:

    Code (CSharp):
    1. using System;
    2. using System.Diagnostics;
    3. using UnityEngine;
    4.  
    5. [Conditional("UNITY_CCU")]                                    // | This is necessary for CCU to pick up the right attributes
    6. [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    7. public class OptionalDependencyAttribute : Attribute        // | Must derive from System.Attribute
    8. {
    9.     public string dependentClass;                           // | Required field specifying the fully qualified dependent class
    10.     public string define;                                   // | Required field specifying the define to add
    11.  
    12.     public OptionalDependencyAttribute(string dependentClass, string define)
    13.     {
    14.         this.dependentClass = dependentClass;
    15.         this.define = define;
    16.     }
    17. }
    Any help would be greatly appreciated.
    Thanks!
     
    loreidy likes this.
  15. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    UPDATE: Even for non-packages projects - plain, simple, default Unity projects - VersionDefines appear to be broken. Unity detects projects that use it, and wipes the data when you send to the Asset Store.

    Welcome to corrupt packages that worked correctly on upload, and now fail in bizarre ways for anyone who downloads them!

    My conclusion: CCU is currently the *only* way you can do conditional compilation in Unity.

    (I submitted a bug report a month ago, haven't had any response positive or negative. I've pinged QA again to see if there's anything else I can do to help with it)
     
    ArcAid likes this.
  16. moonproxymode

    moonproxymode

    Joined:
    Feb 20, 2017
    Posts:
    10
    @a436t4ataf Did you manage to get CCU working for your needs? Would you be able to give me a hand? Please see my previous post.
     
  17. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I tried it before, but didn't publish anything with CCU. I'm experimenting with it now, since VersionDefines are (seemingly) useless :(, and will try to publish something to asset store in the next few days.
     
  18. moonproxymode

    moonproxymode

    Joined:
    Feb 20, 2017
    Posts:
    10
    @a436t4ataf cool, let me know if you make some progress. I'm also trying to publish something to the asset store this weekend, I will let you know if I can get CCU to work.
     
  19. amirebrahimi_unity

    amirebrahimi_unity

    Joined:
    Aug 12, 2015
    Posts:
    400

    Hi there. I must've missed this somehow. AutoLOD has been updated to be a package now and as such, no longer includes CCU.

    To your question: if you were using an earlier version of AutoLOD and didn't pull from github w/ submodules intact, then it would be missing OptionalDependencyAttribute.
     
  20. amirebrahimi_unity

    amirebrahimi_unity

    Joined:
    Aug 12, 2015
    Posts:
    400
    However, the point here is that you could include your own OptionalDependencyAttribute within a custom namespace and decorate it with [Conditional("UNITY_CCU")], and then proceed to mark any optional dependencies in code with defines. I didn't want to have every dev to have to include the CCU in their project to enable optional dependencies. For the dev that pulls multiple projects in _and_ happens to have CCU, then everything should just work. Or, optionally, they could simply add the defines manually to their project settings and it would work fine that way, too.
     
  21. unity_T1WL1huamuCWSQ

    unity_T1WL1huamuCWSQ

    Joined:
    Jun 18, 2019
    Posts:
    10
  22. amirebrahimi_unity

    amirebrahimi_unity

    Joined:
    Aug 12, 2015
    Posts:
    400
  23. Fiedel

    Fiedel

    Joined:
    Mar 7, 2017
    Posts:
    4
    For the example to work you need to put the UNITY_CCU define into quotation marks, like this:

    Code (CSharp):
    1.     [Conditional("UNITY_CCU")]                     // | This is necessary for CCU to pick up the right attributes
    2.     public class OptionalDependencyAttribute : Attribute        // | Must derive from System.Attribute
    3.     {
    4.         public string dependentClass;                           // | Required field specifying the fully qualified dependent class
    5.         public string define;                                   // | Required field specifying the define to add
    6.  
    7.         public OptionalDependencyAttribute(string dependentClass, string define)
    8.         {
    9.             this.dependentClass = dependentClass;
    10.             this.define = define;
    11.         }
    12.     }
    This adds the define in the attribute you set in any of the usages of the attribute.
    Make sure to add the full class name to the attribute, including namespace.

    Note that your IDE might not compile the scripts correctly just after the define has been added.
    For instance, I had to reload the project in Rider to have the define be applied correctly
     
  24. ProfPivec

    ProfPivec

    Joined:
    Sep 21, 2012
    Posts:
    28
    I have this almost working, and if it does, it would help me tremendously. I am doing something similar to what
    moonproxymode above is doing, where I need to have this automatically detect which assets (namespace) are installed, and then magically update the Scripting Define Symbols. Sadly it is not.

    I have all my Defines in place and this works as intended, if I manually add to the player settings, scripting define symbols. However, I was of the understanding that this was supposed to do that automatically for me.

    And suggestions would be appreciated please.
     
  25. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    (update on the non-CCU way: eventually got Unity QA to accept it's a genuine bug, Unity dev team to state that it's broken-since-day-1 and cannot be fixed without correctly re-writing a lot of code, but that would be expensive so they're going to wait a few years until the entire build-system in Unity gets replaced, since they hope/plan to delete everything and just use MS's standard stuff instead. Sometime in 2025, perhaps)

    So, yeah. Gotta figure out how to use CCU.

    *or*: asset-store publishers I've spoken to have written scripts to (something like): edit the player-settings and similar, inserting (or removing) the defines. An incredibly ugly hack IMHO, but since Unity doesn't want to fix/replace the core broken/missing feature, and if it works 'it works'.