Укажите расположение выходных данных сборки для зависимостей DLL PackageReference - PullRequest
1 голос
/ 25 мая 2020

У меня есть проект с некоторыми зависимостями NuGet, использующий PackageReference:

<ItemGroup>
    <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" />
</ItemGroup>

Я не хочу, чтобы зависимости устанавливались непосредственно в папку bin\${buildConfiguration}\${framework} (файлы в скобках ( )):

bin
    Debug
        net472
            (MyLibrary.dll)
            (Microsoft.CSharp.dll)
            (Microsoft.Xaml.Behaviors.Wpf.dll)

Скорее, я хочу, чтобы зависимости каждой сборки находились в более глубокой подпапке, например:

bin
    Debug
        net472
            (MyLibrary.dll)
            MyLibrary
                (Microsoft.CSharp.dll)
                (Microsoft.Xaml.Behaviors.Wpf.dll)

Я знаю, что могу использовать файл nuget.config для управления куда должны быть загружены пакеты решения, но результат сборки зависимостей NuGet остается прежним - папка bin / release / framework.

Обратите внимание, что я хочу больше, чем просто переместить файлы, которые могут будет выполняться на этапе после сборки и не будет очень полезным. Мне нужно, чтобы ссылка на зависимую DLL изменялась во время сборки, чтобы ссылаться на подпапку вместо папки root; поэтому я могу скопировать все содержимое папки root целиком в другое место, и при этом оно будет работать.

MyLibrary.dll создается с использованием проекта формата SDK, который использует PackageReference ; это может быть. NET Framework,. NET Core или. NET Standard.

Как я могу это сделать?


Немного фона

Я создал визуализатор отладки Visual Studio для выражений . Визуализаторы отладки - это отдельные библиотеки DLL, которые копируются вручную - вместе со своими зависимостями - в определенную подпапку c в разделе «Документы», например, Visual Studio 2019\Visualizers, или в подпапку Visualizers установочной папки VS.

Если есть два визуализатора, которые зависят от разных версий одной и той же библиотеки, один может сломаться. Удаление визуализатора - это случайный случай удаления ненужных зависимостей.

Это усугубляется необходимостью создания нескольких DLL при написании визуализатора для. NET Core или. NET Standard; каждая из этих DLL может иметь свои собственные зависимости.

Если бы можно было вывести зависимости в подпапку с тем же именем, это было бы шагом в правильном направлении.

( Запрос сообщества разработчиков и (сейчас закрыт) * ​​1044 * запрос на документирование лучших решений этой проблемы )

Ответы [ 2 ]

1 голос
/ 25 мая 2020

Я тестировал это только в самом базовом сценарии c, поэтому, возможно, для проекта с множественным таргетингом потребуется его модификация, и, возможно, проекты в стиле, отличном от sdk, работают иначе, чем проекты в стиле sdk, но:

исследование

Единственная важная вещь, которую вам нужно знать при исследовании чего-либо MSBuild, - это двоичный вывод журнала , который просматривается с помощью MSBuild Structed Log Viewer .

Итак, я запустил dotnet new console и dotnet add package NuGet.Versioning, потому что мне действительно нужно выполнять сравнения SemVer2 в консольном приложении. Теперь я запускаю dotnet build -bl и start msbuild.binlog.

В средстве просмотра журналов MSBuild ищу имя сборки из пакета и слово copy. В моем случае я искал «скопировать nuget.versioning.dll» и нашел один результат. Нажав на нее, я вижу, что сообщение было выведено задачей с именем «Копировать», которая выполнялась в целевом объекте с именем «_CopyFilesMarkedCopyLocal». Щелкнув Task Copy в дереве, он открывает текстовое представление Microsoft.Common.CurrentVersion.targets в строке, которая запускает задачу Copy, и я вижу следующее:

    <Copy
        SourceFiles="@(ReferenceCopyLocalPaths)"
        DestinationFiles="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')"
        SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
        OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
        Retries="$(CopyRetryCount)"
        RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
        UseHardlinksIfPossible="$(CreateHardLinksForCopyLocalIfPossible)"
        UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyLocalIfPossible)"
        Condition="'$(UseCommonOutputDirectory)' != 'true'"
            >

Обратите внимание на пункт назначения DestinationFiles="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')". Хорошо, DestinationSubDirectory звучит многообещающе. Проверяя параметры задачи копирования, элемент DestinationFiles не имеет установленного DestinationSubDirectory, поэтому, похоже, я могу просто установить его на любой относительный путь, который я хочу.

Давайте поищем, где находятся элементы ReferenceCopyLocalPaths определены. Я вижу два результата поиска для «AddItem ReferenceCopyLocalPaths», но, проверяя «стек вызовов» обоих из них, я вижу, что они оба находятся под целью с именем «ResolveReferences».

Последнее, так как этот вопрос Что касается сборок, поступающих из PackageReference, я хочу быть особенно осторожным, поэтому я смотрю на элемент ReferenceCopyLocalPaths и замечаю, что у него есть элемент метаданных с именем NuGetPackageId.

Итак, теперь:

  • Я хочу запустить свою собственную цель после цели ResolveReferences
  • Я хочу, чтобы она обновляла элементы ReferenceCopyLocalPaths
  • , где определены метаданные NuGetPackageId
  • установить DestinationSubDirectory на некоторый путь

решение

Добавьте эту цель в свой csproj:

  <Target Name="CopyPackageAssembliesToSubFolder" AfterTargets="ResolveReferences">
    <ItemGroup>
      <ReferenceCopyLocalPaths Condition=" '%(ReferenceCopyLocalPaths.NuGetPackageId)' != '' "
        Update="%(ReferenceCopyLocalPaths)"
        DestinationSubDirectory="libs\" />
    </ItemGroup>
  </Target>

Теперь, когда я запускаю dotnet clean ; dotnet build, я вижу, что в каталоге bin есть папка libs/ с NuGet. Versioning.dll.

0 голосов
/ 10 июня 2020

Для этого есть две части:

  1. Во время сборки поместите зависимости во вложенную папку; метод, описанный @zivkan в , этот ответ работает очень хорошо.
  2. Основная сборка должна найти сборки зависимостей либо во время компиляции, либо во время выполнения. Я не уверен, что это возможно сделать во время компиляции, но я решил сделать это во время выполнения, используя следующий код (частично взят из этого ответа :
//using System;
//using System.Linq;
//using System.Reflection;
//using static System.IO.Path;
//using static System.StringComparison;
//using static System.Reflection.Assembly;
//using System.IO;

public static class SubfolderAssemblyResolver {
    public static void Hook(string subfolderKey) {
        if (string.IsNullOrWhiteSpace(subfolderKey)) { return; }
        if (!string.IsNullOrWhiteSpace(subfolderPath)) { return; }

        subfolderPath = Combine(
            GetDirectoryName(GetCallingAssembly().Location),
            subfolderKey
        );
        basePath = GetDirectoryName(subfolderPath);

        AppDomain.CurrentDomain.AssemblyResolve += resolver;
    }

    private static bool EndsWithAny(this string s, StringComparison comparisonType, params string[] testStrings) => testStrings.Any(x => s.EndsWith(x, comparisonType));

    private static readonly string[] exclusions = new[] { ".xmlserializers", ".resources" };
    private static readonly string[] patterns = new[] { "*.dll", "*.exe" };

    private static string? basePath;
    private static string? subfolderPath;

    private static Assembly? resolver(object sender, ResolveEventArgs e) {
        var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == e.Name);
        if (loadedAssembly is { }) { return loadedAssembly; }

        var n = new AssemblyName(e.Name);
        if (n.Name.EndsWithAny(OrdinalIgnoreCase, exclusions)) { return null; }

        // search in basePath first, as it's probably the better dependency
        var assemblyPath =
            resolveFromFolder(basePath!) ??
            resolveFromFolder(subfolderPath!) ??
            null;

        if (assemblyPath is null) { return null; }

        return LoadFrom(assemblyPath);

        string resolveFromFolder(string folder) =>
            patterns
                .SelectMany(pattern => Directory.EnumerateFiles(folder, pattern))
                .FirstOrDefault(filePath => {
                    try {
                        return n.Name.Equals(AssemblyName.GetAssemblyName(filePath).Name, OrdinalIgnoreCase);
                    } catch {
                        return false;
                    }
                });
    }
}

и называется он так:

SubfolderAssemblyResolver.Hook("SubfolderName");

Этот код работает на. NET Framework,. NET Core (проверено на 3.1) и. NET Standard 2.0.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...