Коль захотел ты сборки передать
И с ними пламенный привет
Нугетом не забудь запаковать
В пакет!


Сразу оговоримся, что в этой статье речь пойдёт о стеке технологий Microsoft .NET.

Часто так бывает, что какое-то подмножество проектов начинает использоваться в разных решениях.

Как правило, программисты, разглядев в соседнем проекте что-то полезное, первое время не заморачиваются — создают папку lib (dll, assemblies и т.п.) и складывают туда скомпилированные сборки из оригинального решения. Со временем становится понятно, что это не самый удобный вариант и вот почему:

  • оригинальное решение начинает развиваться в свою собственную сторону, без учёта «потребителей»: добавляются новые зависимости, обновляются версии .net и т.п. «приколы»;
  • если даже о «потребителях» задумываются, то забывают обновить сборки у них, когда выходит критическое обновление или просто новая версия, а потом всё становится ещё хуже, когда сборок становится больше одной и между ними возникают некоторые зависимости — обновляя одну сборку, получаем проблемы в момент исполнения, т.к. другая сборка может оказаться не той версии;
  • оригинальное решение перестаёт дальше разрабатываться.

Ответом на все эти неприятности может служить вынесение проектов в отдельное решение и создание NuGet-пакета, включающего общие сборки, и смена парадигмы развития этих сборок. По большому счёту, всё это можно сделать и без NuGet, но удовольствия в этом гораздо меньше.Как сделать так, чтобы NuGet-пакет собирался сам автоматически вместе с компиляцией проекта на сервере построения и включал все необходимые свистелки и гуделки — об этом и будет наш рассказ.

Изготовление NuGet-пакетов


Процесс изготовления NuGet-пакетов довольно прост. Вся общая теоретическая часть доступна и, в целом, понятна. В пакеты можно упаковывать различный контент, не только скомпилированные сборки, но и отладочные символы, картинки и т.п. ресурсы, и даже исходный код.

В данном описании мы ограничимся наиболее насущным вопросом упаковки скомпилированных сборок.

Подготовка первого NuGet-пакета


Для того, чтобы наладить автоматизированное создание NuGet-пакетов на сервере построения, надо «состряпать» первую версию пакета. Самый простой и понятный способ создания пакета – это использование NuSpec-файла, который описывает, что это будет за пакет. Получить данный NuSpec-файл можно разными способами:

  • Взять чужой пример и исправить.
  • Сгенерировать утилитой NuGet.exe (команда «NuGet.exe spec»).
  • Создать новый пакет или открыть существующий чужой пакет GUI-утилитой NuGet Package Explorer, исправить и сохранить командой «Save Metadata As…».

В принципе, можно полностью всё создание NuSpec-файла выполнить в GUI, но понимать то, как устроен NuSpec, всё же будет полезно.

Для примера, один из наших NuSpec-файлов с сокращениями выглядит как-то так:

Содержимое NuSpec-файла
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <id>NewPlatform.Flexberry.ORM</id>
    <version>2.1.0-alpha1</version>
    <title>Flexberry ORM</title>
    <authors>New Platform Ltd</authors>
	<!-- ... -->
    <description>Flexberry ORM package.</description>
    <releaseNotes>
      ...
    </releaseNotes>
    <copyright>Copyright New Platform Ltd 2015</copyright>
    <tags>Flexberry ORM</tags>
    <dependencies>
        <dependency id="NewPlatform.Flexberry.LogService" version="1.0.2" />
		<!-- ... -->
        <dependency id="SharpZipLib" version="0.86.0" />
    </dependencies>
  </metadata>
  <files>
	<!-- ... -->
    <file src="Debug-Net45\ICSSoft.STORMNET.DataObject.dll" target="lib\net45\ICSSoft.STORMNET.DataObject.dll" />
    <file src="Debug-Net45\ICSSoft.STORMNET.DataObject.xml" target="lib\net45\ICSSoft.STORMNET.DataObject.xml" />
	<!-- ... -->
  </files>
</package>


Вот небольшие пояснения, касающиеся некоторых секций:

  • Id должен быть уникальным в рамках общего пространства имён всех пакетов, чтобы не допускать коллизий. Кто-то указывает в названии пакета название компании, потом название проекта и конкретного продукта, а кто-то не заморачивается.
  • По поводу версий: хорошей практикой считается использование принципов семантического версионирования. Небольшое правило, которое мы выработали у себя в команде – все пререлизные версии (у которых кроме 3-х чисел есть ещё что-то в конце, например, alpha1) мы публикуем со сборками, собранными в Debug-конфигурации, а релизы, соответственно, в Release.
  • Заметки к релизу (releaseNotes) – очень полезная вещь, обязательно пишите там, что поменялось с прошлой версии. Пользователи должны понимать, что они получают с каждым обновлением.
  • Зависимости (dependencies). При описании зависимостей надо думать о том, как ваш пакет будет устанавливаться: если пользователю достаточно будет только вашего пакета и ничего больше, значит, никаких зависимостей нет. Если же ваши сборки будут работать только при наличии другого пакета, например, SharpZipLib, то обязательно надо прописать эту зависимость. Важно понимать, что SharpZipLib, в свою очередь, может иметь свои зависимости, и они тоже «прилетят» пользователю при установке, даже если вы их не указываете у себя.
    Установка происходит рекурсивно, так что пользователь в одной из гипотетических ситуаций может начать устанавливать один пакет, а ему установится больше сотни – как раз через зависимости. Во время установки пакетов выбор версии зависимого пакета устроена весьма хитро. Если номер версии не указать, то будет устанавливаться последняя релизная версия, иначе та, которая явно указана в зависимости. Кстати, если вы используете несколько не связанных между собой пакетов из раза в раз, то вы можете создать пустой пакет с зависимостями от нужных вам пакетов и устанавливать этот свой пакет – остальные установятся вслед за ним сами.
  • Описание файлов может включать указание конкретных имён или масок. Крайне рекомендуем соблюдать правильную структуру пакетов, когда в target пишется тип контента, версия .net framework и другие вещи, в соответствии с соглашением. Важно понимать, что в атрибуте src при указании пути до файла надо отталкиваться от текущего каталога, в контексте которого будет выполняться команда упаковки пакета.

После того, как NuSpec-файл готов, можно приступить к пробному созданию пакета. Для этого выполняется простая команда утилиты NuGet.exe: nuget pack MyAssembly.nuspec.

Таким образом мы должны получить заветный «первый пакет», или «опытный образец пакета», то есть nupkg-файл, который можно использовать для установки в проекты через NuGet Package Manager или через NuGet.exe.

Выставка готовых пакетов


Итак, у нас есть пакет, который надо как-то доставлять пользователям через какой-нибудь «канал сбыта пакетов». Считаем, что большинство пользователей будут устанавливать пакеты через Visual Studio. Встроенный в неё NuGet Package Manager понимает два варианта размещения пакетов:

  • Галерея пакетов, доступная через сеть;
  • Папка Windows (локальная либо сетевая).

В настройках можно добавлять собственные источники пакетов, они будут перебираться по очереди при установке или восстановлении пакетов, пока нужный id не будет найден. Вариант, когда один и тот же одинаковый(!) пакет лежит в нескольких источниках – вполне приемлем.

Самый простой вариант для распространения пакетов – создать сетевую папку и складывать пакеты туда.

Стоит отметить, что NuGet позволяет работать не только с общей галереей пакетов https://nuget.org, но и создавать собственные галереи, для этого можно развернуть где-то у себя тот же движок, что используется на https://nuget.org. Наша команда предпочитает этот вариант, поскольку в этом случае появляется возможность отслеживания статистики загрузок, управление полномочиями через сайт, в конце концов, это просто красиво.



Установка галереи может потребовать небольших танцев с бубном, как минимум, в вопросе авторизации, но ничего сложного в этом нет. Публикация пакетов происходит точно так же, как и на NuGet.org, важно при обновлении сайта галереи не потерять архив с уже загруженными пакетами – они хранятся в каталоге узла. Настройка NuGet Package Manager для пользователей в этом случае будет выглядеть как-то так:



Если локальный источник пакетов находится где-то рядом с пользователями, например, в одной локальной сети, то рекомендуется закачать в него все пакеты с зависимостями – это сократит время скачивания пакетов для новых пользователей. Найти nupkg-файлы от зависимых пакетов очень легко – они всегда есть в папке packages, в которую устанавливаются эти самые пакеты (обычно в каталоге с sln-файлом). Также в окне настроек источников пакетов важен порядок – студия будет перебирать источники в случае восстановления пакетов в том порядке, который указан в настройках. Следовательно, если ваш пакет доступен только локально, то первым поставьте свой источник, чтобы не было лишних запросов на nuget.org.

Фабрика по производству NuGet-пакетов


После того, как «опытный образец пакета» сделан и «канал сбыта пакетов» налажен, можно приступать к автоматизации сборки пакетов, чтобы по первому же щелчку мышки мы могли получить горячий и самый свежий NuGet-пакет.

Рассмотрим, как это делается в случае с Team Foundation Server 2013/2015. Для других подобных CI-систем процесс будет похожим.
В свойствах Build Definition (XAML) можно указать PowerShell-скрипт, который выполнится в случае успешного выполнения построения. Именно в этом скрипте и будем вызывать наш «упаковщик», передавая в качестве параметра путь до NuSpec-файла.

Есть несколько моментов, которые следует прояснить для себя: где будет лежать сам NuGet.exe и все необходимые ему файлы (как минимум, конфигурационный файл), где будет находиться NuSpec-файл? С одной стороны, можно положиться на то, что на сервере построения будет в определённом месте расположен NuGet.exe, но если серверов построения несколько и их администрированием заниматься нет желания, то проще всего положить NuGet.exe в Source Control и добавить каталог с его расположением в Workspace, с которым будет выполняться построение. Что касается NuSpec, то его удобно держать рядом с sln-файлом и даже включить в Solution Items для быстрого доступа к нему через Solution Explorer.

Если имеется несколько солюшенов и планируется создавать несколько пакетов, то рекомендуется реализовать один общий PowerShell-скрипт, который будет в качестве параметра получать путь до NuSpec-файла.

Ниже представлены выдержки из такого скрипта:

Выдержки из PowerShell-скрипта
# Create NuGet Package after successfully server build.

# Enable -Verbose option for this script call.
[CmdletBinding()]

Param(
    # Disable parameter.
    # Convenience option so you can debug this script or disable it in 
    # your build definition without having to remove it from
    # the 'Post-build script path' build process parameter.
    [switch] $Disable,

    # This script used NuGet.exe from current directory by default.
    # You can change this path to meet your needs.
    [String] $NuGetExecutablePath = (Get-Item -Path ".\" -Verbose).FullName + "\NuGet.exe",

    $BinariesDirectoryPostfixes = @("\Debug", "\Release"),

    # Path to the nuspec file. Path relative TFS project root directory.
    [Parameter(Mandatory=$True)]
    [String] $NuspecFilePath,
    
    # Disable Doxygen.
    [switch] $NoDoxygen
    # ...
    # Go, go, go!
    $nugetOutputLines = & $NuGetExecutablePath pack $realNuspecFilePath -BasePath $basePath
        -OutputDirectory $outputDirectory -NonInteractive;
    ForEach ($outputLine in $nugetOutputLines) {
        Write-Verbose $outputLine;
    }
    # ...


В скрипте выполняются операции по преобразованию относительных путей в абсолютные (можно без труда найти описание доступных переменных, которые означаются CI-системой при запуске скрипта). В некоторых случаях требуется модификация NuSpec-файла в этом скрипте. Например, таким образом можно обработать создание пакетов для различных конфигураций (Any CPU, x86).

На этом, собственно, настройка автоматического механизма создания NuGet-пакетов заканчивается. Запускаем сборку на сервере построения, проверяем, что всё сработало. Для получения отладочной информации, если что-то пошло не так, не забываем писать –Verbose в параметрах скрипта в настройках определения построения. Готовые пакеты заливаем в общий ресурс или галерею и приглашаем первых пользователей.

Тонкости процесса


Как говорится, «главная задача программиста – убить в себе перфекциониста». Если внутренний перфекционист ещё не сдался, то ему должны пригодиться следующие пункты.

Кроме возможностей по созданию NuGet-пакетов, скрипт для сервера построения для каждого из пакетов может запускать утилиту генерации автодокументации на основе XML-комментариев в коде. Данная возможность удобна в том плане, что для каждой версии пакета у нас появляется своя версия автодокументации, это удобно, если пользователи применяют разные версии NuGet-пакетов. Для генерации автодокументации у нас применяется Doxygen. Вот раздел скрипта, посвящённый автодокументации:

Выдержки из PowerShell-скрипта для генерации автодокументации
if($NoDoxygen)
{
    Write-Verbose "Doxygen option is disabled. Skip generation of the project documentation.";
}
else
{
    Write-Verbose "Doxygen option is enabled. Start documentation generation.";

    # Copy doxygen config file.
    $doxyConfigSourcePath = Join-Path -Path $toolsFolderPath -ChildPath "DoxyConfig" -Resolve;
    $doxyConfigDestinationPath = Join-Path -Path $Env:TF_BUILD_BINARIESDIRECTORY -ChildPath "DoxyConfig";

    # Modify doxigen config file according with given nuspec.
    $nuspecXml = [xml](Get-Content $NuspecFilePath);
    $doxyConfig = Get-Content -Path $doxyConfigSourcePath;
    
    $projectName = $nuspecXml.GetElementsByTagName("title").Item(0).InnerText + " " +
    $nuspecXml.GetElementsByTagName("version").Item(0).InnerText;
    $doxyConfig = $doxyConfig -replace "FlexberryProjectName", $projectName;
    
    $projectLogoPath = Join-Path -Path $toolsFolderPath -ChildPath "logo.png" -Resolve;
    $doxyConfig = $doxyConfig -replace "FlexberryProjectLogo", $projectLogoPath -replace "\\", "/";

    $doxyConfig = $doxyConfig -replace "FlexberryOutputDirectory", $Env:TF_BUILD_BINARIESDIRECTORY -replace "\\", "/";

    $doxyConfig = $doxyConfig -replace "FlexberryInputDirectory", $Env:TF_BUILD_SOURCESDIRECTORY -replace "\\", "/";

    $doxyWarnLogFilePath = Join-Path -Path $Env:TF_BUILD_BINARIESDIRECTORY -ChildPath "doxygen_log.txt";
    $doxyConfig = $doxyConfig -replace "FlexberryWarnLogFile", $doxyWarnLogFilePath -replace "\\", "/";

    $doxyConfig | Out-File $doxyConfigDestinationPath default;
  
    # Run doxygen.
    $doxygenExecutablePath = Join-Path -Path $toolsFolderPath -ChildPath "doxygen.exe" -Resolve;
    $doxygenOutputLines = & $doxygenExecutablePath $doxyConfigDestinationPath

    ForEach ($outputLine in $doxygenOutputLines) {
        Write-Verbose $outputLine;
    }

    Write-Verbose "Documentation generation done. Packing to the archive.";

    # Do archive.
    $archiveSourceFolder = Join-Path -Path $Env:TF_BUILD_BINARIESDIRECTORY -ChildPath "html" -Resolve;
    $archiveFileName = $nuspecXml.GetElementsByTagName("id").Item(0).InnerText + "." +
    $nuspecXml.GetElementsByTagName("version").Item(0).InnerText;
    $archiveDestinationFolder = Join-Path -Path $Env:TF_BUILD_BINARIESDIRECTORY -ChildPath ($archiveFileName + ".zip");

    Add-Type -assembly "system.io.compression.filesystem";
    [io.compression.zipfile]::CreateFromDirectory($archiveSourceFolder, $archiveDestinationFolder);

    # Remove html documentation files.
    Remove-Item $archiveSourceFolder -recurse;

    Write-Verbose "Done.";
}


Второй пункт будет касаться сборки проекта в случае, если в один пакет упаковываются разные версии сборок под разные версии .net framework.

Хитрости начинаются с того, чтобы заставить сервер построений собирать сборки под разные версии .net framework. Рассмотрим, проекты, которые будут собираться, в формате csproj, а не новым json-форматом файла проекта (ASP.NET5). В Visual Studio поддерживается механизм конфигурации сборок. Обычно применяется 2 конфигурации – Debug и Release, но этот же механизм позволяет настроить переключение версий .net.

Можно создавать свои конфигурации, что мы и делаем. К сожалению, чтобы выполнить «тонкую» настройку всех необходимых параметров, придётся открыть csproj-файл и, как минимум, прописать там TargetFrameworkVersion в каждой из секций конфигурации.
Выдержки из .csproj-файла
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug-Net35|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug-Net35\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
    <DocumentationFile>bin\Debug-Net35\LogService.XML</DocumentationFile>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release-Net35|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release-Net35\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <DocumentationFile>bin\Release-Net35\LogService.XML</DocumentationFile>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug-Net40|AnyCPU'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\Debug-Net40\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DocumentationFile>bin\Debug-Net40\LogService.XML</DocumentationFile>
    <DebugType>full</DebugType>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug-Net45|AnyCPU'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\Debug-Net45\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DocumentationFile>bin\Debug-Net45\LogService.XML</DocumentationFile>
    <DebugType>full</DebugType>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Net40|AnyCPU'">
    <OutputPath>bin\Release-Net40\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <DocumentationFile>bin\Release-Net40\LogService.XML</DocumentationFile>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Net45|AnyCPU'">
    <OutputPath>bin\Release-Net45\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <DocumentationFile>bin\Release-Net45\LogService.XML</DocumentationFile>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>


Конфигурации в Visual Studio переключаются в основном тулбаре, в определении сборки на сервере можно выбрать одновременно несколько конфигураций, которые будут компилироваться последовательно.

Стоит отметить, если у вас код под разные версии .net framework начинает различаться, то это можно обрабатывать при помощи директив:

        #if NETFX_35
            for (int i = 0; i < resValueLength; i++) 
        #else
            System.Threading.Tasks.Parallel.For(0, resValueLength, i =>
        #endif

При этом константы должны быть определены в соответствующей секции csproj-файла:

<DefineConstants>DEBUG;TRACE;NETFX_35</DefineConstants>

Когда у нас есть готовые скомпилированные сборки, давайте разберёмся, как правильно настроить nuspec. В nuspec задаются специальные каталоги под конкретные версии .net framework.

Пример секции files в NuSpec-файле:

  <files>
    <file src="Debug-Net35\LogService.dll" target="lib\net35\LogService.dll" />
    <file src="Debug-Net35\LogService.XML" target="lib\net35\LogService.XML" />
    <file src="Debug-Net40\LogService.dll" target="lib\net40\LogService.dll" />
    <file src="Debug-Net40\LogService.XML" target="lib\net40\LogService.XML" />
    <file src="Debug-Net45\LogService.dll" target="lib\net45\LogService.dll" />
    <file src="Debug-Net45\LogService.XML" target="lib\net45\LogService.XML" />
  </files>

Ещё одна проблема, с которой можно часто столкнуться при использовании (даже не при создании) NuGet-пакетов — проблема подключения одного проекта в несколько солюшенов. Дело в том, что в csproj-файле ссылки на сборки проставляются вплоть до конкретных dll, которые по умолчанию восстанавливаются Visual Studio в папку packages рядом с sln-файлом. Отсюда возникает проблема, когда один и тот же проект включён в несколько солюшенов, располагающихся в разных папках. Для решения этой проблемы можно воспользоваться NuGet-пакетом, который включает в себя специальный Target, который переписывает ссылки перед билдом: https://www.nuget.org/packages/NuGetReferenceHintPathRewrite.

Ещё одной особенностью использования NuGet-пакетов является тема восстановление пакетов при сборке. Дело в том, что до некоторых пор Visual Studio не имела встроенных средств восстановления пакетов, поэтому в csproj дописывался специальный Target, который отвечал за восстановление. В современных Visual Studio (2013+) это уже не актуально, следите за чистотой своих csproj-файлов, никаких Target-ов для восстановления NuGet-пакетов больше не требуется.

Ну и напоследок можно рассказать о том, что при использовании TFS папка packages по умолчанию лезет в Source Control и кто-нибудь периодически может проморгать и всё-таки зачекинить все сборки в TFS. Чтобы такого не случилось (мы уверены, что для тех, кто чекинит сборки в TFS в аду должен быть отдельный котёл), можно использовать файл .tfignore, который должен спасти от этой напасти.

Результат


Итак, выполнив всё, что описано в предложенной нами инструкции, вы можете получить готовый механизм упаковки пакетов, который работает без участия человека. Наши пакеты собираются именно так. Разве что, сама публикация требует некоторого внимания.

Полезные ссылки:



Комментарии (18)


  1. Oxoron
    29.12.2015 17:54
    +6

    Как вы вовремя статью подогнали, как раз скоро пакеты создавать. Спасибо.
    В дополнение к статье, на недавней конференции от Luxoft рассказывали про проблемы с транзитивными зависимостями. Не поделитесь, на какие еще грабли можно наступить при работе с NuGet пакетами?


    1. seregamatin
      30.12.2015 14:05
      +2

      Статья все-таки посвящена в большей степени автоматизации процесса сборки пакетов, а не работе с ними.
      Тем не менее, в статье описаны пара проблем с которыми мы столкнулись:
      1. Проблема использования одного проекта в нескольких солюшенах, которая связанная с тем, что после выполнения билда в VS, ссылки на сборки (в csproj-файле) будут указывать на тот каталог packages, который лежит рядом с запущенным солюшеном, и если эти изменения послать в TFS, то при билде другого солюшена, использующего проект, билд-сервер не сможет найти сборки, и будет ругаться.
      2. Если требуется собирать пакет под разные версии .NET Framework, то придется потанцевать с бубном, настраивая конфигурации проектов, и даже поправить csproj-фалы ручками.

      Как мы справляемся с этими проблемами в статье описано.

      Из того, чего нет в статье, припоминаю некоторые проблемы с трансформациями конфигов приложений, в которые устанавливаются пакеты. Иногда при установке пакета перетирались уже существующие в конфиге секции. Но при решении таких проблем бубен не понадобится, нужно просто внимательно писать трансформации.

      Других сколько-нибудь серьезных проблем пока не припоминаю.


  1. alexstz
    30.12.2015 09:32
    +2

    Оффтоп. Мне интересно, введут ли на nuget.org премодерацию или сделают когда-нибудь компиляцию только из исходных кодов? Я так понимаю, что абстрактный пакет VasyaPupkin.MvcExtensions может сделать из моего сервера послушного зомби, а удобного способа быстро посмотреть исходники нет. Остаётся надеяться и верить в лучшее, но в эпоху скандалов информационной безопасности это как-то неправильно.


    1. Gorily
      30.12.2015 10:43
      +2

      Интересная тема. Тем более, что средства разработки обычно работают под админом. WCF вообще не от админа сложно отлаживать.
      Я обычно ищу пакет, затем внимательно изучаю автора — его сайт, комментарии к проекту, ищу статьи по использованию библиотеки. Конечно, в первую очередь это даёт представление о том, насколько его вообще удобно использовать и насколько он активно поддерживается. Но и некий элемент безопасности в этом присутствует.
      На первый взгляд логично обязать выкладывать исходники и давать на них ссылку, но это не всегда возможно.
      Вторая мысль — валидация самих разработчиков, подтверждение электронной почты, телефона, привязывание профиля OpenID. И выпиливание всех его разработок при обнаружении проблем.
      Или может разделить источники на доверенные и не доверенные?


      1. return_true
        30.12.2015 12:30
        +2

        Кроме странных билбиотек, которые ловко линкуются к проекту, существуют ещё Install.ps1 скрипты, которые автоматически запускаются при установке пакета. По сути, студия скачивает какой-то скрипт из интернета и запускает его под админом.


    1. redmanmale
      30.12.2015 14:17
      +1

      Всегда можно декомпилировать исходники из либы dotPeek'ом или сразу решарпером.


      1. traaance
        30.12.2015 15:26
        +1

        Действительно, очень удобно.


  1. Melz
    30.12.2015 18:50

    Как правило, программисты, разглядев в соседнем проекте что-то полезное, первое время не заморачиваются — создают папку lib (dll, assemblies и т.п.) и складывают туда скомпилированные сборки из оригинального решения.

    По поим наблюдением это делают когда владельцы библиотеки на нее малость подзабили и люди делают быстрый патч/пишут нужную фичу.
    Или наоборот, в зависимую либу откатили в нугете, а часть нужной функциональности уже используется.
    Потом просто люди забывают это удалить т.к. все работает. Могу даже пример дать :)

    2. А почему не делаете пакет автоматически через AppVeyor при билде мастера?
    Как-то так:
    https://www.appveyor.com/docs/nuget#automatic-publishing-of-nuget-projects


    1. Razaz
      31.12.2015 13:32

      А когда нагет разрешил откатывать пакеты? Там только из поиска их удялять по дефолту разрешено. Физическое удаление пакета крайняя мера.


      1. Melz
        31.12.2015 14:11

        Не знаю как это делают на практике может действительно удаляют из поиска, но видел что допустим 1.0.0.8 была с багом и ее убирали из нагета, оставляя 1.0.0.7.


  1. Nanako
    05.01.2016 11:17

    А зачем отдельный PS скрипт если можно и pack и push в репозиторий сделать по Release AfterBuild прямо из MSBuild? Мне просто интересно с какой проблемой вы столкнулись. Мы, например, запихнули pdb прямо в пакет и убрали солюшен референсы полностью. Теперь солюшены при сборке деплоят пакеты и обновляют их с репозитория через тот же NuGet. В номер версию включен билдтайм, машины синхронизированы по общему NTP.


  1. jakobz
    05.01.2016 15:31

    У меня вот несколько проектов используют один общий. Я выношу сейчас все в отдельный солюшн, со своим git-репозиторием. Затем из него собирается билд-машиной nuget-пакет, публикуется. Ну и потом обновляется версия в использующих пакет проектах.

    Но одно меня гложет — очень неудобно будет в этот общий пакет добавлять функционал, проверяя его как-то отдельно. Хотелось бы иметь по-необходимости возможность работать с этим проектом как с обычным локальным — чтобы он был в солюшне, по нему работал дебаг, можно было бы менять что-то и проверять не прогоняя цикл push->publish nuget->update package.

    Ничего по этой теме не подскажете? Т.е. хочется по необходимости получать упрощенный цикл разработки — как он был бы если это был просто проект в солюшне, но при этом иметь отдельный репозиторий и nuget для случаев когда такое не нужно.

    Я пока думаю временно подключать проект в студию, и настраивать ему output прямо в packages/myproject. Должно сработать, но как-то кривовато.


    1. return_true
      05.01.2016 23:27

      Из вашего описания не ясно, зачем вам NuGet пакет. Я правильно понял, у вас есть один Solution, в котором несколько проектов (Projects) и выхотите один из них хранить отдельно? Он где-нибудь кроме этого solution используется? Из этого солюшена вы собираете несколько приложений или одно?


      1. jakobz
        06.01.2016 14:15

        Сейчас есть несколько солюшнов, в каждом из которых есть проект с инфраструктурными штуками, которые нужны всем солюшнам. Доселе изменения в нем переносились туда-сюда копированием. Сейчас я этот проект выношу в nuget-пакет.

        И мне вот интересно как при этом не потерять возможность удобно что-то допиливать в этом общем проекте в контексте одного из солюшнов.

        Ну, скажем, хочу я условный хелпер ToColoredHTMLString написать. Мне было бы удобнее как-то подключить на время этот общий проект в один из солюшнов, написать там этот хелпер, сразу его попробовать использовать — допилить где-то, отладиться, посмотреть как оно на UI выглядит. И уже потом — залить общий проект в git, запаблишить в nuget, обновить nuget пакет в солюшне, и влить этот солюшн в другой git. А потом как-нибудь обновить пакет в других солюшнах.

        Без подключения в солюшн на время, я буду ограничен разработкой на тестах, что не всегда достаточно.


        1. return_true
          06.01.2016 21:21

          Ага, теперь понятнее. На самом деле, у вас не так уж и много вариантов. Мы у себя подобные вещи помечаем в коде, как кандидаты на перенос в общую библиотеку. Дальше, если код стабилен, то вливаем его в репозиторий из которого билдится nuget пакет. Не так уж часто одинаковый код нужен сразу сейчас и везде. Обычно есть что-то, что уже работает в одном проекте и хочется переиспользовать в другом.
          В торой варинт — это иметь тестовое приложение в солюшне с общей библиотекой.


          1. jakobz
            07.01.2016 16:47

            Да, эти два варианта тоже планирую использовать:
            — заведу в каждом солюшне проект типа Xyz.Common.Staging, код из которого будет перетекать в nuget-пакет
            — в репозитории для nuget уже есть тесты, но видимо придется создать и тестовый веб-проект — не все можно нормально проверить и отладить на тестах

            По-идее этого должно хватить. На крайняк — остается вариант временно настроить outDir общего проекта прямо в packages конкретного.


  1. igoriok
    06.01.2016 23:16

    Добавлю что в рамках одной компании очень удобно использовать TeamCity как приватный Nuget Feed и Symbol Server. Так что после удачной сборки, ваши пакеты будут автоматически обновлены.


    1. return_true
      07.01.2016 13:35

      Я бы рекомендовал развернуть приватный NuGet сервер (http://inedo.com/proget например). Если проектов много и обновляются они часто, то не стоит перегружать TC этим.