Возникла проблема с копированием транзитивных зависимостей в выходную директорию при сборке посредством MSBuild.
Предположим, есть проект Core.csproj, который ссылается на библиотеку ThirdParty.dll;
есть проект Api.csproj, который ссылается на Core.csproj и не ссылается прямо на ThirdParty.dll;
есть проект Application.csproj, который ссылается на Api.csproj и не ссылается прямо на Core.csproj и ThirdParty.dll:
ThirdParty.dll ← Core.csproj ← Api.csproj ← Application.csproj
Для первых двух проектов выходной папкой установлена «$(SolutionDir)Bin\Common\$(Configuration)-$(Platform)\». Для последнего проекта установлена отдельная папка «$(SolutionDir)Bin\Application\$(Configuration)-$(Platform)\».
Ожидаемое поведение сборки: в выходную папку Application скопируются Application.exe, его явная зависимость Api.dll, неявные зависимости Core.dll и ThirdParty.dll.
Для тривиальных проектов всё происходит согласно ожиданиям. Но в каких-то более сложных случаях что-то нарушается, и в выходную директорию Application не копируются зависимости второго порядка. С чем это может быть связано, есть ли какие-то общие рекомендации, или это проблемы конкретных скриптов сборки?
Здравствуйте, hardcase, Вы писали:
Q>>С чем это может быть связано, есть ли какие-то общие рекомендации, или это проблемы конкретных скриптов сборки? H>Возможно в одном из проектов стоит что-то типа:
На Core идёт ссылка через ProjectReference.
Для ссылок на библиотеки указанное свойство private deployment'а не установлено (что соответствует Copy Local: true в IDE).
Здравствуйте, Qbit86, Вы писали:
Q>На Core идёт ссылка через ProjectReference. Q>Для ссылок на библиотеки указанное свойство private deployment'а не установлено (что соответствует Copy Local: true в IDE).
Значит остается лишь внимательное изучение /verbosity:diagnostic логов msbuild-а.
Q>Возникла проблема с копированием транзитивных зависимостей в выходную директорию при сборке посредством MSBuild.
Я кое-что понял.
Неясно, как MSBuild трактует понятие «выходные файлы проекта». Есть выходные файлы разных таргетов (в т.ч. пользовательских), но что из них копировать вместе со сборкой?
По всей видимости, информация о том, что копировать, берётся не только из билд-скриптов, но и из манифеста сборки. Видимо, этим занимается таск ResolveAssemblyReference, фигурирующий в билд-логе.
Иными словами, вместо копирования build-time зависимостей мы получаем копирование явных (известных в момент сборки) run-time зависимостей.
Почему в тестовом солюшене копирование транзитивных зависимостей происходило, а в рабочем — нет? Дело в том, что аналогом проекта Application.csproj у меня является WPF приложение, а Api.csproj и Core.csproj — библиотеки кастомных контролов. В шаблоне контрола из Api.csproj используется контрол из Core.csproj. Но такого рода зависимость не зафиксирована явно; несмотря на то, что эта зависимость отражена в build time как ссылка на проект, в манифест сборки Api.dll ссылка на Core.dll не попадает (последняя сборка должна быть найдена и загружена в рантайме динамически).
Проверить это легко. В моём тестовом солюшене, где копирование транзитивных зависимостей происходило якобы корректно, стоило убрать использование классов из Core.dll (не убирая ссылки на проект Core), как сборки Core.dll и ThirdParty.dll перестали копироваться в папку Application. IL Dasm подтвердил версию насчёт манифеста.
Интересен также пример поведения, в некотором смысле обратного.
Пусть в сборке Core.dll определён класс Core, в сборке Api.dll определён класс Api, наследующий Core, а в сборке Application.exe используется Api, и не используется явно Core. Тогда в манифесте сборки Application.exe будет указана сборка Api, и не будет указана сборка Core — для разрешения зависимостей в рантайме этого достаточно. А для компиляции — нет! То есть компилятор (вполне закономерно) потребует добавить ссылку на сборку Core.dll, даже если явного её использования нигде в коде нет (и в манифест она не попадёт).