File: BuildCheck\Acquisition\BuildCheckAcquisitionModule.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Build.Experimental.BuildCheck.Infrastructure;
using Microsoft.Build.Framework;
#if FEATURE_ASSEMBLYLOADCONTEXT
using Microsoft.Build.Shared;
#endif
 
namespace Microsoft.Build.Experimental.BuildCheck.Acquisition;
 
internal class BuildCheckAcquisitionModule : IBuildCheckAcquisitionModule
{
#if FEATURE_ASSEMBLYLOADCONTEXT
    /// <summary>
    /// AssemblyContextLoader used to load DLLs outside of msbuild.exe directory.
    /// </summary>
    private static readonly CoreClrAssemblyLoader s_coreClrAssemblyLoader = new();
#endif
 
    /// <summary>
    /// Creates a list of factory delegates for building check rules instances from a given assembly path.
    /// </summary>
    public List<CheckFactory> CreateCheckFactories(
        CheckAcquisitionData checkAcquisitionData,
        ICheckContext checkContext)
    {
        var checksFactories = new List<CheckFactory>();
 
        try
        {
            Assembly? assembly = null;
#if FEATURE_ASSEMBLYLOADCONTEXT
            assembly = s_coreClrAssemblyLoader.LoadFromPath(checkAcquisitionData.AssemblyPath);
#else
            assembly = Assembly.LoadFrom(checkAcquisitionData.AssemblyPath);
#endif
 
            IList<Type> availableTypes = assembly.GetExportedTypes();
            IList<Type> checkTypes = availableTypes.Where(t => typeof(Check).IsAssignableFrom(t)).ToArray();
 
            foreach (Type checkCandidate in checkTypes)
            {
                checksFactories.Add(() => (Check)Activator.CreateInstance(checkCandidate)!);
                checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckRegistered", checkCandidate.Name, checkCandidate.Assembly);
            }
 
            if (availableTypes.Count != checkTypes.Count)
            {
                availableTypes.Except(checkTypes).ToList()
                    .ForEach(t => checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckBaseTypeNotAssignable", t.Name, t.Assembly));
            }
        }
        catch (ReflectionTypeLoadException ex) when (ex.LoaderExceptions.Length != 0)
        {
            foreach (Exception? unrolledEx in ex.LoaderExceptions.Where(e => e != null).Prepend(ex))
            {
                ReportLoadingError(unrolledEx!);
            }
        }
        catch (Exception ex)
        {
            ReportLoadingError(ex);
        }
 
        return checksFactories;
 
        void ReportLoadingError(Exception ex)
        {
            checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckFailedRuleLoading", ex.Message);
            checkContext.DispatchFailedAcquisitionTelemetry(System.IO.Path.GetFileName(checkAcquisitionData.AssemblyPath), ex);
        }
    }
}