- Home /
Is there a way to have debug code compiled out for a final build?
Is there an equivalent to the C standard way of having debug code that is disabled or compiled out for a final release build?
In C you would do something like
#if !RELEASEBUILD
... debug code ...
#endif
I expect this to be a bit of a problem with Unity/Mono but thought i'd ask. If it isn't possible then i'll just write some awk scripts that i can run which will allow for the same kind of behavior by manually running it on my script files before doing a final build.
Cheers
As of Unity 4.1 you can now add your own project-wide defines, see my answer below.
Answer by jashan · Dec 22, 2009 at 08:54 AM
As Peter pointed out: Currently, Unity doesn't support project-wide #defines, which in my opinion is a very unfortunate limitation. One thing you can do to get this more into reach is vote up the entry on feedback.unity3d.com: Editor: Interface for listing preprocessor macros (#define) in build settings. With that feature, the issue (and a lot more issues) would be solved.
One thing you could do under "the right circumstances" is compiling DLLs with Visual Studio that you use in Unity. That way, you could use the defines from Visual Studio. Unfortunately, those "right circumstances" are probably quite rare (usually, you'll want to keep most code directly in the Unity project because it simplifies most things).
So, until I get those defines in Unity, what I'm doing is this: I have a couple of defines in every class file where I need them, and I also have a script that runs through all the classes and does a little bit of code replacement to either "activate" or "deactivate" those code sections (I comment them in/out via script). That way, I have "kind of the same functionality" as I would have with proper project wide defines. It's just a hack and somewhat more cumbersome ... but it works for me in a rather complex project (server/client, different client versions etc.)
The whole code that I'm using for this is a bit too much and too complex (and too unpolished) to post but I can give you a few bits that should be helpful to set something like that up for yourself. So, the code I'm posting here is not "easy copy'n'paste code" but it should get you start it - and feel to ask questions (I can still edit this to make things more clear).
Just a word of warning: Replacing code in your source files can ruin your project if you're not careful. I also had a lot of Unity crashes when I developed this (but that was with a much older version of Unity). So either use version control (like the Asset Server) or always keep backup copies. And, speaking of version control: doing these replacements will obviously change your code; and usually, that's not a change you'd usually want to record in your version control - so in most cases, you'll probably just do this for a build and then revert the changes.
That said:
// Activate / deactivate the debug symbol (somewhere in an editor script or console app) public void ConfigureDebugCode(bool isDebug) { ConfigurationHelper helper = new ConfigurationHelper(); helper.RegisterIncludeSymbol("DEBUG", isProduction); helper.ReplaceInAllScriptAssets(); }
// I have this in a separate class; this has a "CONSOLEAPP"-define public class ConfigurationHelper {
private string fileFilter = "*.cs";
private List<string[]> currentReplacements = new List<string[]>(9);
public void RegisterIncludeSymbol(string symbol, bool activate) {
string a = string.Format("#define {0} //A", symbol);
string b = string.Format("//#define {0} //I", symbol);
RegisterReplaceAwithB(a, b, activate);
}
public void RegisterReplaceAwithB(string a, string b, bool revert) {
if (revert) {
currentReplacements.Add(new string[] { b, a });
} else {
currentReplacements.Add(new string[] { a, b });
}
}
// the following is what I called "unpolished" ;-)
/// <summary>
/// Executes the replacements that were previously registered.
/// </summary>
public void ReplaceInAllScriptAssets() {
if (currentReplacements.Count == 0) {
if CONSOLEAPP
Console.WriteLine("ReplaceInAllScriptAssets without registering replacements, not replacing anything!");
else
Debug.LogWarning("ReplaceInAllScriptAssets without registering replacements, not replacing anything!");
endif
return;
}
//Debug.Log("Will be replacing:");
//foreach (string[] replace in currentReplacements) {
// Debug.Log(string.Format("{0} / {1}", replace[0], replace[1]));
//}
if CONSOLEAPP
DirectoryInfo assets = new DirectoryInfo(ProjectPath + "Assets/");
else
DirectoryInfo assets = new DirectoryInfo("Assets/");
endif
List<FileInfo> changedFiles = new List<FileInfo>();
FileInfo[] scripts = assets.GetFiles(fileFilter, SearchOption.AllDirectories);
int index = assets.FullName.Length - "Assets/".Length;
for (int i=0; i < scripts.Length; i++) {
FileInfo script = scripts[i];
if !CONSOLEAPP
EditorUtility.DisplayProgressBar("Replacing keys in files",
string.Format("File: {0}", script.FullName.Substring(index)),
((float) i) / ((float) scripts.Length));
endif
if (script.FullName.Contains("Editor")) {
// we don't want to mess with editor scripts (like this one ;-) )
//Debug.Log(string.Format("Ignoring editor script: {0}", script.Name));
} else if (script.FullName.Contains("Standard Assets")) {
// we also don't want to mess with scripts from (Pro) Standard Assets
//Debug.Log(string.Format("Ignoring (Pro) Standard Assets script: {0}", script.Name));
} else { // now here's the gold ;-)
//Debug.Log(string.Format("Processing file: {0}", script.FullName.Substring(index)));
StreamReader reader = script.OpenText();
string code = reader.ReadToEnd();
reader.Close();
string newCode = code;
foreach (string[] replace in currentReplacements) {
newCode = newCode.Replace(replace[0], replace[1]);
}
if (!code.Equals(newCode) /* code.GetHashCode() != newCode.GetHashCode()*/) {
script.Refresh();
changedFiles.Add(script);
StreamWriter writer = new StreamWriter(script.Open(FileMode.Create), Encoding.UTF8); // "overwrite"
writer.Write(newCode);
writer.Flush();
writer.Close();
}
}
}
//allReplacements.AddRange(currentReplacements);
currentReplacements.Clear();
if !CONSOLEAPP
EditorUtility.ClearProgressBar();
StringBuilder filesWithReplacements = new StringBuilder("Replacing codes in all scripts DONE!\n");
AssetDatabase.StartAssetEditing();
foreach (FileInfo script in changedFiles) {
MonoScript scriptObject = (MonoScript)AssetDatabase.LoadAssetAtPath(script.FullName.Substring(index), typeof(MonoScript));
if (scriptObject != null) {
// this should make sure this is recompiled before build...
EditorUtility.SetDirty(scriptObject);
// is that really needed? I left it in here just to be sure...
AssetDatabase.ImportAsset(script.FullName.Substring(index), ImportAssetOptions.Default);
//AssetDatabase.ImportAsset(script.FullName.Substring(index), ImportAssetOptions.ForceSynchronousImport);
//Thread.Sleep(100);
filesWithReplacements.AppendLine(script.FullName.Substring(index));
} else {
Debug.LogWarning(string.Format("Could not open {0} as asset!", script.FullName.Substring(index)));
}
}
AssetDatabase.StopAssetEditing();
Debug.Log(filesWithReplacements.ToString());
endif
}
Thanks for this. Very detailed. I wasn't aware that even #if was allowed syntax. That helps a little.
I may have a go at writing a simple awk script as i don't foresee needing too much flexibility yet, I was thinking a similar thing though, to comment out the blocks i don't want.
I'll happily vote up this request, looks like i need a new account again, and unlike unity answers you can't authenticate via the unity forums it seems.
Cheers
Originally, I had blocks commented in/out - but that turned out to be quite a bit more cumbersome than the approach using commenting in/out just the defines. So, I converted all my "special syntax comments" to actual #ifs and now I'm almost happy ;-) Oh ... an regarding feedback.unity3d.com: I think you can also use OpenID with the forums (like here).
Please edit your link to read "feedback.unity3d.com" .. with "unity" spelled correctly, currently your unit3d link points us to a nasty Trojan site :/ otherwise, thanks for the help! :D
Answer by Peter Alexander · Dec 22, 2009 at 01:20 AM
That isn't strictly possible with Unity as it doesn't support project-wide #define
s like Visual Studio does.
You can, of course, do something like the following:
if (MyGameSettings.Debug)
{
...
}
and if Debug
is declared as const
and is false then the inner code will not become part of the binary.
That works for most cases, but if you need to strip out entire classes then you'll need another solution. You could easily write a script to process all your source files to handle the task.
I can certainly get by without being able to do entire classes for now. So can we prove what you are saying here that it WILL be compiled out if its const and false? That does sound like all i need.
I may write a script anyways to be absolutely sure, but i'd be interested if anyone could prove what you are saying by looking at the binary sizes.
Cheers
It has been proven before. You can look at the disassembled CIL instructions using ILDAS$$anonymous$$ to see that the enclosed instructions are missing. It is very important that you declare the Debug
variable as const, otherwise the compiler cannot reliable perform this optimisation. If everything goes well, you should also receive a 'Found unreachable code' warning in the Unity console.
Answer by yoyo · Oct 02, 2012 at 03:51 PM
[EDIT]
As of Unity 4, it is possible to create your own project-wide (and platform-specific) preprocessor defines in the Player Settings, as documented here.
[OLD ANSWER]
Note that you can check Debug.isDebugBuild to see if you are currently running a development build (based on the corresponding checkbox in the build settings). This is a runtime check, so code does not get compiled out as the OP requested.
You can also mark classes or methods with the ConditionalAttribute, which tells the compiler to remove code (and calls to it) unless a specific pre-processor define is present. Here's what it looks like:
using System.Diagnostics;
#define DEBUG
[Conditional("DEBUG")]
class MyConditionalClass
{
...
}
This would be much more useful if Unity provided more control over pre-processor defines. (Note that Profiler.BeginSample uses this mechanism to strip profiling hooks from non-development builds. Internally they are using the ENABLE_PROFILER #define, which is perhaps a way to tell if you are in a development build. Probably works for Unity Pro only.)
Answer by codestage · Jul 22, 2015 at 03:28 PM
Sorry for bumping so outdated question, but for any future visitors: you may use DEVELOPMENT_BUILD conditional flag, just like this for example:
#if DEVELOPMENT_BUILD
private void OnGUI()
{
// your debug GUI here
}
#endif
Answer by ian.du · Feb 17, 2010 at 06:15 PM
What does Unity / Mono do in the slightly less obvious case:
if(InputState.RUN_STATE_MACHINE && InputState.SHOW_DEBUG_MESSAGE){
....
}
when InputState.RUN_STATE_MACHINE
is const FALSE and InputState.SHOW_DEBUG_MESSAGE
in not const ???
To a human this is clearly always going to evaluate to FALSE, and so the compiler should strip the entire code block out. However, from my experience, it is almost always possible to trick a compiler into evaluating a statement repeatedly, even if the statement is always FALSE.