- PVSM.RU - https://www.pvsm.ru -

Автоматизированное создание NuGet-пакетов

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

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

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

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

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

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

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

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

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

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

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

  • Взять чужой пример [2] и исправить.
  • Сгенерировать утилитой NuGet.exe [3] (команда «NuGet.exe spec»).
  • Создать новый пакет или открыть существующий чужой пакет GUI-утилитой NuGet Package Explorer [4], исправить и сохранить командой «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-Net45ICSSoft.STORMNET.DataObject.dll" target="libnet45ICSSoft.STORMNET.DataObject.dll" />
    <file src="Debug-Net45ICSSoft.STORMNET.DataObject.xml" target="libnet45ICSSoft.STORMNET.DataObject.xml" />
	<!-- ... -->
  </files>
</package>

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

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

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

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

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

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

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

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

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

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

Автоматизированное создание NuGet-пакетов - 2

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

Автоматизированное создание NuGet-пакетов - 3

Если локальный источник пакетов находится где-то рядом с пользователями, например, в одной локальной сети, то рекомендуется закачать в него все пакеты с зависимостями – это сократит время скачивания пакетов для новых пользователей. Найти 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 [15]. Вот раздел скрипта, посвящённый автодокументации:

Выдержки из 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.Автоматизированное создание NuGet-пакетов - 4

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

Выдержки из .csproj-файла

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug-Net35|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>binDebug-Net35</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
    <DocumentationFile>binDebug-Net35LogService.XML</DocumentationFile>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release-Net35|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>binRelease-Net35</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <DocumentationFile>binRelease-Net35LogService.XML</DocumentationFile>
    <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug-Net40|AnyCPU'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>binDebug-Net40</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DocumentationFile>binDebug-Net40LogService.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>binDebug-Net45</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DocumentationFile>binDebug-Net45LogService.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>binRelease-Net40</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <DocumentationFile>binRelease-Net40LogService.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>binRelease-Net45</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <DocumentationFile>binRelease-Net45LogService.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-Net35LogService.dll" target="libnet35LogService.dll" />
    <file src="Debug-Net35LogService.XML" target="libnet35LogService.XML" />
    <file src="Debug-Net40LogService.dll" target="libnet40LogService.dll" />
    <file src="Debug-Net40LogService.XML" target="libnet40LogService.XML" />
    <file src="Debug-Net45LogService.dll" target="libnet45LogService.dll" />
    <file src="Debug-Net45LogService.XML" target="libnet45LogService.XML" />
  </files>

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

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

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

Результат

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

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

Автор: seregamatin

Источник [21]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/net/107634

Ссылки в тексте:

[1] доступна: http://docs.nuget.org/Create/Creating-and-Publishing-a-Package

[2] NuSpec-файла: http://docs.nuget.org/Create/NuSpec-Reference

[3] NuGet.exe: https://github.com/NuGet/NuGet.Client/releases

[4] NuGet Package Explorer: http://docs.nuget.org/Create/using-a-gui-to-build-packages

[5] указывает: http://www.nuget.org/packages/NewPlatform.Flexberry.ORM/

[6] название компании: http://www.nuget.org/packages/Microsoft.AspNet.Mvc

[7] кто-то: http://www.nuget.org/packages/Unity/

[8] заморачивается: http://www.nuget.org/packages/Npgsql/

[9] семантического версионирования: http://semver.org/lang/ru/

[10] хитро: http://docs.nuget.org/Create/versioning#Specifying-Version-Ranges-in-.nuspec-Files

[11] соглашением: http://docs.nuget.org/Create/NuSpec-Reference#specifying-files-to-include-in-the-package

[12] https://nuget.org: https://nuget.org/

[13] движок: https://github.com/NuGet/NuGetGallery

[14] https://nuget.org: https://nuget.org

[15] Doxygen: http://www.stack.nl/~dimitri/doxygen/

[16] https://www.nuget.org/packages/NuGetReferenceHintPathRewrite: https://www.nuget.org/packages/NuGetReferenceHintPathRewrite

[17] .tfignore: https://msdn.microsoft.com/en-us/library/ms245454.aspx

[18] Наши пакеты: https://www.nuget.org/profiles/Flexberry

[19] Что такое NuGet: http://docs.nuget.org/consume/overview

[20] Top 10 NuGet (Anti-) Patterns: https://msdn.microsoft.com/en-us/magazine/jj851071.aspx

[21] Источник: http://habrahabr.ru/post/274283/