How to tell that compilation exceptions are resolved when no recompiling occurred?

I need my C# code to be aware if the C# files match what is built in the assembly. I.e. if there are compilation errors then obviously it doesn’t. If there is a method that can easily tell me if Assembly-CSharp.dll currently matches the code base, that should solve it. But I can’t find a solution.

ADDITIONAL NOTES:

I tried CompilationPipeline, using the AssemblyCompilationFinished. This reports each assembly it tries to build and any errors that stopped it.

  • On a failed compile, it reports failure. (EXCELLENT)
  • On a successful compile, it reports no failure, but still reports the compilation completed. (EXCELLENT)

However, if the code was in a failed state and my repair was to return the file to its previous state, (removing the “j” I accidentally typed, or undoing the repo changes) It won’t compile that. It seems to detect that a file changed is no longer different from the compiled version, and doesn’t try to recompile. possibly a CRC check?

This means that if I captured the failed compilation prior, my code believes its broken, it will continue to think its broken, even if my code is fixed in some cases.

Obviously Unity internally has a method to determine if any CS files don’t match what was last successfully compiled into an assembly. But I do not know how to determine the same.

Use of “CompilationPipeline.assemblyCompilationNotRequired” doesn’t report anything either if the no compilation happened, but the code was fixed. :frowning:

Here is the code I created:

using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;

[InitializeOnLoad]
public static class CompilationErrorMonitor
{
    public static bool LatestBuildBroken { get; private set; }

    static CompilationErrorMonitor()
    {
        LatestBuildBroken = EditorPrefs.GetBool("LatestBuildBrokenFlag", false);

        CompilationPipeline.assemblyCompilationFinished += (path, messages) =>
        {
            if (path != "Library/ScriptAssemblies/Assembly-CSharp.dll") return;

            LatestBuildBroken = false;
            foreach (var message in messages)
            {
                if (message.type == CompilerMessageType.Error)
                {
                    LatestBuildBroken = true;
                    break;
                }
            }

            if (LatestBuildBroken) Debug.Log($"Latest build has errors: {path}");
            EditorPrefs.SetBool("LatestBuildBrokenFlag", LatestBuildBroken);
        };
    }
}

Wut? :thinking:

Instantly this raises eyebrows just like “I’m trying to catch fish but my handgrenade ain’t working.”

So please tell us WHY you want to do that thing, and what alternatives you have considered, if any. I’m positively certain this is one of those “Don’t do it (that way)” cases. :wink:

You are aware of the Roslyn code generator?

If you change a script, any script, under /Assets or /Packages and “Auto-Refresh Assets” is enabled (default) and the editor is focused (again), then a compilation and domain reload will occur.

UNLESS your modification bypasses the AssetDatabase. For instance by modifying the script via System.IO.File operations and without the editor losing focus in between ie through an editor script. If you do that with an editor script, you have to call AssetDatabase.ImportAsset(pathToScriptFile); in order to make Unity aware of the change.

This will fail the very moment you start using Assembly Definitions. Or possibly, depending on why and what for you need this, even when using any code that uses asmdefs, ie any package and most store assets. But it also won’t catch editor scripts nor code under /Plugins. Unity projected compile to more than just one DLL.

2 Likes

I’m just as confused as CodeSmile :slight_smile: What exactly is the goal here? Also to address this point:

No, Unity has not. Script compilation is a one-way process. However Unity has its AssetDatabase which actually stores and keeps track of all source assets including the timestamp when it was last changed. When Unity scans for changed files, it compares the timestamps of the files to the ones in the database and if they haven’t changed, the file hasn’t changed.

Again, it’s not clear what your actual goal is. You can manually trigger a recompilation by using CompilationPipeline.RequestScriptCompilation. Note that you have the option to pass ClearBuildCache as an argument which should force a recompilation of all scripts.

ps: When using EditorPrefs, you really should use some custom prefix for your own preference names. The editorprefs are shared among all internal preferences as well as any package code you may import. You have to be careful to not get nameclashes with other code.

I’m writing code that bakes other code. For background, I helped write the Expert Programmer certification, i.e. this is not a whim. At the moment I’m working on a low-level memory management tool, that based on the selection of an unmanaged struct, will automatically write and import custom code tuned to what will be most optimal in managing a chunk memory system with selected look up tables/mappings. Specifically, I’m replacing Unity physics with a physics probe system, and adding an AI probe system, both will require a highly tuned memory management custom to this game (and others). However, before baking code into the project and depending on it, I need to be aware if the current source code matches the assembly that is currently loaded. Otherwise, the post domain reload won’t have any of the new baked code to react to, because the only reason it wouldn’t match was because it could not compile (hence why it didn’t match in the first place.

This is an invalid statement (and how I might have responded to this post 3 days ago). You can verify it with the code in the original post. Anytime I change the code it does trigger a compilation, which triggers the event/method above, EXCEPT, when the code goes from a broken state, back to the state that matches what compiled the current assembly. It would seem they do a CRC check on the file to detect differences (in addition to file change monitoring), or keep a cached copy in the library or something.

However, I may be incorrect here. Perhaps in this case it does still compile but just does not trigger this event. However, it seems far more likely they would have put in a CRC check to skip irrelevant compilation steps and win back some cycles.

You can validate this, by importing that class into your own project. (when first importing something like this, it reports errors on almost everything, 1 time, as it hooks into the compilation changes. But you can ignore them. I had 114 in my test project and that only had 2 assemblies and 3 classes.) Then just make a change to any script in your code, including CompilationErrorMonitor. It will trigger the compilation step, and on complete, it will report in the debug log if the compilation succeeded or failed. EXCEPT in the case where you return it to its previously compiled state. Example, take functional code that is current, Add a “j” to it where it doesn’t belong. The tool will announce the compilation attempt and failure. Then remove the “j” returning the code to the exact state it was in prior. This event is no longer called. As an additional note, simply adding a space will trigger it to recompile.

I believed this as well until now, when I’m facing a test that shows me otherwise and has an obvious explanation. It may have used time stamp in the past, but now it appears to use CRC (inference) Which would cause the state above where it does not compile despite a code change.

See my other previous response to CodeSmile. It gives some depth to what I’m doing and the issue.

However, I agree with the forced recompile, that it may be the best option currently. If I trigger it on purpose as a validation step prior to baking, then that should give me a clear state that I don’t need to retain/manage. I’m hoping for a slightly faster step. Recompiling takes < 1 second right now in my test project but I’ve been on other projects where the compilation time takes upwards of 5 minutes. I may have to go this route, but I’m hoping to make this lighter weight when possible.

Thank you! That is a good idea that I will apply from now on. :smiley:

I had another realization of something I might be able to use. Console panel reports compilation exceptions. However, when those are fixed, it removes the associated log entries.

And it is able to associate when I restore an uncompilable file to its former compilable version to remove the log. I was worried that this was still isolated to internals in Unity, but I downloaded a 3rd party debugger replacement, and that also is able to capture compiler exceptions, and get rid of them. This means it is possible without having internals in what appears to be an efficient method that does not require recompiling to validate.

I’m now researching how the log systems are able to detect the removal of compiler exceptions when no compilation has been triggered.

This doesn’t explain why you need to do the “scripts match compiled assembly” step.

It sounds a lot like you need (!) to be using Roslyn source code generators in order to inject your custom code the very moment the code gets compiled and use the current compilation state as basis to make your decisions which parts of your code need to be injected or what code needs optimizing.

In that case, the script and assembly still match. The erroneous script did not compile, hence the assembly wasn’t updated. When you return that script to its original, already-compiled state then why recompile when there was no actual change made to the output?

Any good compiler will notice that and not recompile or link the output. I can’t tell you how it’s done but it’s most likely not Unity doing that but the C# compiler toolchain.

Try doing that with any C# command line program in your IDE and you’ll likely notice the same behaviour.

Nevertheless, this is actually besides the point. I’m still convinced that you got your head stuck in a solution that is inadequate. Experiment with Roslyn source generators and you may be baffled that you can do all of your changes WHILE the code is compiling. :wink:

Unity uses these code generators for a number of things, eg Entities to add boilerplate code based on the user’s code, or to generate Unity Mathematics methods which are effectively often doing the same thing over again for a different type, or the same struct with varying number of fields (float2, float3, float4, float4x4).

My code uses reflection to determine what types it can use. On Domain reload, this loops through all types of all connected assemblies to catalog any types that are classified as unmanaged.

If at any point, the source code was changed and unable to compile. I’m A) Not going to see any new unmanaged types that have shown up, and B) Any code I try to bake will assume all types found in reflection will be in the source code for the next time it tries to compile. But if I deleted/renamed a struct, and had a compilation issue, but then tried to bake the code that depends on it. I then have created more bugs in the system in a setup that may be confusing to correct.

The purpose of my statement of detecting if the scripts match the compilation, is because unity will automatically try to capture and compile any new changes automatically. There are multiple issues that convene on this point. I don’t want to generate new code on currently broken code. I don’t actually care what the source code looks like. I just don’t want it broken.

I’m well aware of roslyn source code. I’ve used it plenty of times, but it has no purpose here. I don’t need to actually see the existing code. I would be taking an existing type, as determined via reflection, and then writing a custom structure for managing it memory. I can easily tell if this class has already been generated, but either way, I would just overwrite it. There is no issue generating or validating the code. I could use a compiler and attempt to compile the code manually, to check if it is in a compilable state. but that would be expensive. Roslyn is not needed for this code. I don’t need to analyze the existing state in a logical structure or parse anything. I just need to write a file with some C# code that is largely templated out, with some variance based on the configurations. Roslyn serves no real value.

The only thing I need, that should have a low cost solution, is to determine if the assembly is up to date with the source code.

I appreciate all the help but my challange does not match this solution. Unless you know a way I can use rosyln in a low cost method to determine if it matches a compiled assembly.

I beg to differ. :wink:

First, this sounds an awful lot like a performance bottleneck which may be on par with whatever overhead Roslyn has:

loops through all types of all connected assemblies to catalog any types

Note that Roslyn source generators run during a Unity compilation. From what I understand your source generators would merely use the data that Roslyn has already available anyway.

Then, upon compiling, you will write out a script file that … Unity has to compile again as it is new code. So your solution will actually require compiling and reloading domains twice! This will also complicate your code due to the compilation step being reentrant, ie you’d have to safeguard your code against a second, unnecessary run (which I think may be part of the reason why you opened that thread).

With Roslyn, you can generate the code within the single compilation step. You can also wholly ignore the fact that sometimes compilation may fail.

Roslyn may also make it easier and/or faster to identify the potential unmanaged candidates for rewriting. From what I heard, inspection with Roslyn is a lot faster than traversing all (!) loaded Assemblies respectively reflection.

Unity uses Roslyn source generators for multiple packages already, as far as I know these are Entities, Netcode, Mathematics and likely more and more as they’ve fully adopted the benefits of Roslyn. I keep seeing hints regarding its use here and there, it just goes largely unnoticed unless you dive deep into package code or IL decompiled code (thanks to Rider).

1 Like

@CodeSmile I still don’t have a solution, and this is getting farther from my request. I truly appreciate the effort you have put in helping, but I have 0 interest in traversing the existing code, using a tool to write code, building an assembly directly in memory, with Roslyn or any other method. I believe you may be hung up on the concept that we need to look at source code to achieve what I asked for, but we do not. Interrupting the compilation steps would not even be possible from a UX standpoint, as the user must decide to bake this from within the Unity IDE based on the assemblies at that moment. I already have code that successfully bakes new code, recompiles, reloads the assemblies and then runs additional post domain reload processing steps. I just need a simple Boolean check to see if Unity believes the current source code has been compiled into the current assemblies or not, to deal with one edge case in my current detection system (based on using the compilation API’s).

Reflection for type traversal is also faster than Roslyn parsing text code and I don’t even have source code for all the assembly’s unity uses, Unity itself, or all the 3rd party assemblies a customer might have. Any unmanaged type can be used here. VEctor3, float3, System.Numerics.Vector3, or even a custom struct without a string or byte array (unless its unsafe/fixed). These are all unmanaged types and can work for my needs. The rest of the code already functions without issue. This is literally just a bug that causes a false negative. I.e. Baking is disabled, because it thinks the current source code had a compilation exception.

All I care about is if the code is compiled into assemblies already. If not, then there are changes that have not made it in, which can easily mean differences between the reflected types/fields once it is compiled. As I have pointed out, I cannot find a reasonable way to detect this right now, as the typically accepted methods have gaps. (see original message)

I need code that will get added directly into the project source code and can then be altered if need be. Sure, I could use roslyn to generate this code through logical steps of creating scopes (namespaces, classed, methods, nests and all the logic that sits inside) and writing tools that place all the code in. Or I could just use an existing C# file that already looks like C#, with a txt extention and a few parsing symbols worked in. Considering this is 10x easier to modify, expecially for larger code bases, or when larger feature changes are pushed, just to use normal C# as text. Roslyn is an option, but actually costs more to develop with in this case. And it also doesn’t solve the problem I’m asking about. I’m not even sure how to get Roslyn and Burst Compiler to work together. Burst Compiler is a necessity, but again none of this is what I’m asking to solve.

“Are there changes that exist in the source code, that are not able to compile into the current assemblies.” In my project, the method to bake shows in the inspector, and I don’t want its decision to enable the “Bake” button to have to parse though all the source code available every single validation tick. I don’t think that is how Console detects that compilation exceptions have disappeared. I’d be happy to loop through console messages from the console if I could, to check for Compilation Exceptions, but that’s locked behind internals.

Since unity largely handles this automatically, it seems the compilation APIs or something else should have a way to detect/provide this, but I cannot find it.

So if you run your code on a successfull compile, and don’t run your code on a non-successful compile, you should be good, right? Why run it on a failed compile?

If the code compiles, you generate your structs. If it doesn’t compile, you generate nothing, and set no state. Then, if you go back to compiling again without a AssemblyCompilationFinished message, you know that the previous version of your structs is valid, since you know that you just did an undo back to the old working code.

1 Like

I need the C# running in the IDE to be aware of this. The C# assembly doesn’t know if its a failed compile or not. AssemblyCompilationFinished doesn’t get called, when the compilation doesn’t happen. The compilation doesn’t happen, if the code was returned to the exact file hash as was compiled successfully last time.

  1. If I make a change, and the code recompiles, this calls the AssemblyCompilationFinished event. And I use that to report that it compiled successfully. Setting a static “WasLastCompileSuccessful” to true.

  2. If I then make a change, and the code fails to recompile because of a compilation exception, this calls AssemblyCompilationFinished event again, but reports the exception along the way.

  3. So now, I have a boolean “WasLastCompileSuccessful” set to false. (because of the compilation exception)

  4. Now I undo my changes, returning the cs file to its original file hash. Unity now knows the Assembly is already good. It does not attempt to recompile, and it does not trigger the AssemblyCompilationFinished event. So I was unable to detect that the code is in perfect order. I still believe it is suffering from a compilation exception. That “WasLastCompileSuccessful” still = false.

I am unaware of what I can tap into to detect this change from compilation-exception to compilation-exception-resolved-without-recompiling.

I cannot find any form of detection for “compilation exception resolved without recompiling” - That might make a better title for this thread.

I guess you could hook into Unity - Scripting API: AssemblyReloadEvents.afterAssemblyReload, as I believe that should happen even if the compilation was for an undo. Of course that would fire one exit and enter play mode if your users hasn’t turned that off, but why on earth would anyone not have that turned off?

Why? And why the WasLastCompileSuccessful?

Not trying to argue that you’re doing something wrong, just trying to understand. If your code just generates the correct artifacts when the code compiles successfully, and then does nothing if it doesn’t, then you should be good, no?

Or is this code that needs to run continously unless the code is compiled correctly?

If it’s code that’s run as a side-effect of a user button press in their IDE, I’d just say that if the user is silly enough to ask to generate import code for code that’s not compiling, then that’s a user error, and not something you should build a bunch of scaffolding to prevent.

1 Like

There is no compilation though. :frowning: Thats the problem. However, I just validated the domain reload does happen. :slight_smile: I hadn’t taken that into account. Even though there wasn’t a compilation, and the assemblies didn’t change, domain reload still fired. This might be something I can use.

But it feels kind of nebulous. I’m not sure what to try during post domain reload. I guess it’s time to make some tables. Thank you for that suggestion.