Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom MSBuild Task locks assembly

At first glance, my problem seemed to be a common one: I have a custom MS Build task in some of my projects. Once I compile the projects, I cannot compile the build task any more - the build task assembly is locked by Visual Studio.

I found a lot of posts here saying 'Just inherit from AppDomainIsolatedTask'.

My task already does. The assembly contains nothing else then this task. The AppDomain seems to be unloaded, at least the DomainUnload event is fired. And, dependent assemblies are unloaded correctly.

However, the assembly containing the build task itself is locked by devenv.exe (which I double checked by ProcessExplorer).

I found another post saying 'Set the GenerateResourceNeverLockTypeAssemblies property true', which sounded promising but didn't help either.

So, I wonder what else may go wrong. The behavior is the same no matter if I use VS2008 or 2010.

like image 719
user544722 Avatar asked Dec 16 '10 12:12

user544722


1 Answers

This kind of thing can be very tricky to get right. There is a lot to consider LoaderOptimization, ShadowCopy etc.

AppDomainIsolatedTask does not mean "do not pollute the build domain". It means "run my code in another domain" - the task type itself is still loaded into the build engine domain. This is unfortunate as I feel most people interpret it as the former. For this specialized task type to work you must also decoarate your task with [LoadInSeparateAppDomain]. This still will not solve your problem.

ProcessExplorer has this handy .NET assemblies view which you can use to identify which domain has a handle to your assembly. As you already know devenv.exe has it locked.

Useful feature

Since you load the DLL that has the task into the domain that VS uses for building stuff you are going to run into this problem.

What you need to do is introduce another layer of indirection.

Options

  1. You can convert your task to an inline code task.
  2. You can use an inline code task to load your static task and delegate to it.

I have provided an example for approach #2 This screenshot validates the results that my custom library is not present in the MSBuild appdomain.

Define a inline task like so

<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">  
  <UsingTask  
    TaskName="RunOtherTask"  
    TaskFactory="CodeTaskFactory"  AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >  
    <ParameterGroup />  
    <Task>
      <Using Namespace="MyTasks" />  
      <Code Type="Class" Language="cs">  
<![CDATA[ 
using System;  
using System.Reflection;
using Microsoft.Build.Framework;  
using Microsoft.Build.Utilities;  

namespace MyTasks {      
    public class RunOtherTask : Task {  
        public override bool Execute() { 
           var remoteTask = CreateInstanceInSeparateDomain();      
           remoteTask.BuildEngine = this.BuildEngine;
           remoteTask.Execute();

           return true;  
        }

        private ITask CreateInstanceInSeparateDomain() {
            var thisAssembly = Assembly.GetExecutingAssembly();

            var setup = new AppDomainSetup {
                ShadowCopyFiles = true.ToString(),              
                LoaderOptimization = LoaderOptimization.MultiDomainHost                 
            };

            var domain = AppDomain.CreateDomain("MyDomain", null, setup);
            var handle = domain.CreateInstanceFrom(@"C:\ClassLibrary1.dll", "ClassLibrary1.SimpleTask");

            return (ITask)handle.Unwrap();
        }
    }
} 
 ]]>
      </Code>  
    </Task>  
  </UsingTask>  

  <Target Name="RunTask">  
    <RunOtherTask /> 
  </Target>

</Project>  

As long as your task implements ITask (which it must) it will work. You need to set the engine property on the newly instantiated task for everything to work correctly.

Class Library1 is not loaded

like image 104
Michael Baker Avatar answered Sep 18 '22 10:09

Michael Baker