Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

CompilationPipeline.assemblyCompilationFinished and weaving

Discussion in '2017.3 Beta' started by pkbis, Nov 20, 2017.

  1. pkbis

    pkbis

    Joined:
    Aug 1, 2017
    Posts:
    18
    Hello,

    I'm researching the new APIs in CompilerPipeline, especially the event that notifies the editor that the compilation has finished - assemblyCompilationFinished.

    We would like to weave code into our assemblies using Mono.Cecil after they finished compiling, and this seems to be the only reasonable/legit way to do it as an asset post-process step; however, it seems that the file by the time the event is fired, Unity already loads the assembly and I am getting an error when saving the modified assembly, because of "IOException: Sharing violation on path [...]".

    Is my assumption about the assembly being loaded by Unity correct? If it is, is there a way to unload the assembly temporarily, or would it be possible to add an event to the API that would fire before Unity loads the assembly?

    Kind regards
    Pavel Kouril
     
  2. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    Hi

    The way compilation of scripts works in Unity today, is that we start new C# compiler processes and then in each update in the editor we check whether the compilers have finished running. Once we detect a compiler is finished, we invoke assemblyCompilationFinished immediately after. We invoke assemblyCompilationFinished for each assembly before we reload any assemblies. As we only reload all the assemblies once they have all compiled successfully.

    My guess for what is happening for you is that you are on Windows and some application like anti-virus or similar is opening the file just after it has been written to disk. Not sure what the best approach is to address this, maybe making a copy of the assembly, modifying the copy and then try to replace the original with the modified copy using File.Replace?
     
  3. pkbis

    pkbis

    Joined:
    Aug 1, 2017
    Posts:
    18
    Hello,

    ok, so, I tested it more, and found out one thing -- the event is triggered before the DLL is actually placed in the path returned by the event, which makes the event unusable for weaving. Is this an expected behaviour?

    I'm attaching a simple repro project where you can test this by clearing up Library\ScriptAssemblies and then changing Test.cs and refocusing Unity to make it recompile the script; you will see that the file doesn't exist.

    If it already exists, you would weave the current DLL, and the the new one would replace it, after the event handler completes.
     

    Attached Files:

  4. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    I see. The event is triggered as the first thing after compilation finishes, before we do any kind of post-processing and copy the assembly, since there is a possibility that any of these steps can fail and then the event won't trigger.

    We always compile assemblies to "Temp/" and the copy them if everything goes well. All Unity versions do this.

    What do you think about adding a CompilationPipeline.assemblyTempDirectory property that will basically return "Temp" and you can use this to find the temporary location of the assemblies before they are copied?
     
  5. pkbis

    pkbis

    Joined:
    Aug 1, 2017
    Posts:
    18
    Hello,

    the CompilationPipeline.assemblyTempDirectory would be nice; but is it really needed, since it would basically return Temp anyways? Also, is the assembly be guaranteed to always be at assemblyTempDirectory/[AssemblyName]? It would need us to parse the assembly name from the event's path parameter, but that shouldn't be a problem.

    However - what about CompilationPipeline.assemblyTempDirectory and CompilationPipeline.assemblyLibraryDirectory which would return "Temp" and "Library\ScriptAssemblies" respectively, and change the "path" parameter to just assembly name (including or not including the .dll extension), if changing the API is realistic now (and wanted).

    And one more thing, a kind-of related question - for builds (both Windows and platforms), does the mono/il2cpp take the assemblies from Library folder, or compiles them once more? The question I'm asking is if we would need to redo the weaving separately, or this event would take care of that as well. :)
     
  6. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    I think your suggestion about just passing the assembly name makes sense and then having 2 properties for temp directory and final output directory. I will see if I can get away with breaking this API ;-) But for now you can just assume that we always output to Temp/.

    This event will be called every compilation of assemblies in Unity, also for players. Currently, when building the players, we switch the compile settings and then also compile to Temp first and then copy to Library/ScriptAssemblies afterwards. Then once we are done processing the player assemblies during player build, we switch back to editor settings for compilation and recompile them assemblies again.
     
    pkbis likes this.
  7. pkbis

    pkbis

    Joined:
    Aug 1, 2017
    Posts:
    18
    Based on the b11 changelog I assume the API wasn't changed? Is there still a possibility of it being changed in the RC versions?
     
  8. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    I had a look at changing this and there is a reason why the full path is passed.

    The reason is the new AssemblyBuilder API:
    https://docs.unity3d.com/ScriptReference/Compilation.AssemblyBuilder.html

    The assemblyCompilationStarted/ assemblyCompilationFinished events will also be invoked when compiling assemblies through this AP. In this case, we do not compile to a temp folder and then move the assembly. The assembly is outputted directly to the path specified in AssemblyBuilder constructor.

    I will fix this by invoking assemblyCompilationFinished as now when there are compile errors and the assembly won't exist. If there are no compile errors, the event will only be invoked after the assembly copy has taken place, so the assembly path string matches the outputted assembly file path on disk. Putting this change into the category of bugfixes and not breaking changes :)
     
    Last edited: Nov 29, 2017
  9. pkbis

    pkbis

    Joined:
    Aug 1, 2017
    Posts:
    18
    Ok, thanks a lot for the update!

    The change sounds reasonable. Just to be sure - the event will now be fired after the copy, but before any loading happens, right? :)
     
  10. lukaszunity

    lukaszunity

    Administrator

    Joined:
    Jun 11, 2014
    Posts:
    461
    Yes, the event will be fired immediately after the copy and before any loading.