Разработчики в борьбе за эффективность программиста, команды, команд

    Всем привет.

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

    Речь пойдет, конечно, о коде.

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

    Да и давайте признаемся — все мы любим писать “фреймворки”, которыми пользуемся потом сами и которыми пользуются другие. Это добавляет немного куража нашей повседневной деятельности.

    И тут автор ловит себя на том, что поступает неправильно, начиная описывать ситуации — все и так понимают, о чем речь. А описать нужно технологии и процесс. Поэтому давайте просто остановимся на абстрактной мысли, что внезапно нашлось много кода, который может быть полезен коллегам и его хорошо бы сделать легкодоступным. Также примем во внимание вторую, куда менее приятную, мысль, что в “зрелых” проектах можно найти большое количество copy-paste кода, значительно понижающего сопровождаемость. Да и работу над такими проектами приятной назвать трудно. И с этим надо что-то делать.

    И мы решили с этим что-то делать.


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

    Ну да хватит предисловий.

    Итак, сегодня мы в деталях расскажем:
    1. Как развернуть корпоративный nuget-сервер (основы nuget мы опустим, полагая, что большинство читателей знает, что это такое).
    2. Как развернуть корпоративный symbol-сервер, интегрированный с nuget.
    3. Почему одного nuget-сервера недостаточно для построения эффективного процесса.
    4. Также заранее обозначим темы для второй части, которую мы с удовольствием изготовим, если данная тема найдет своих читателей:
    5. Что такое SandCastle и с чем его едят.
    6. Как настроить continuous integration, чтобы автоматизировать всё нами содеянное.
    7. Несколько мыслей по поводу (само)организации.


    Разворачиваем корпоративный nuget-сервер.

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

    Если речь идет о развертывании корпоративного сервера, то вариантов установки есть два:
    1. Простое развертывание ленты без UI и плюшек. Для этого варианта достаточно создать MVC application и установить сервер nuget, скачав nuget-пакет (такая вот рекурсия).
    2. Второй вариант создан для чуть менее ленивых, но зато он с UI и плюшками. Для него необходимо скачать исходники и собрать их согласно инструкции с той же страницы. После этого, нажав Ctrl+F5, мы должны увидеть (почти) полный аналог официальной ленты.




    Собственно, на этом и всё — процесс чрезвычайно прост. Однако есть несколько моментов, не описанных в документации, которые проявились у нас и которые могут быть полезны другим:

    1. IIS express. Вариант работы на IIS express с использованием localdb трудно рассматривать как production ready по целому ряду причин. Поэтому мы перенесли нашего новорожденного на полнофункциональный сервер IIS. Благо, сложностей с этим никаких не возникает. Просто идем во вкладку properties проекта “Website” и снимаем галку “Use IIS Express” в разделе “Web”. Visual Studio моментально среагирует на такие перемены — предложит создать Virtual Directory и сменить Connection Strings в web.config с localdb на SQL express.



    2. Database Migrations. Nuget использует для доступа к данным Entity Framework и активно пользуется механизмом Database Migrations. В руководстве к развертыванию есть шаг № 4 — создание БД при помощи танцев с саблями вокруг порядка открывания вкладок в студии. На самом деле всё проще. Достаточно просто указать имя проекта, в котором хранятся migrations, при обновлении через консоль. И никаких танцев:



    3. Настройки безопасности. В руководстве не описаны моменты, касающиеся настроек безопасности (хотя они были в устаревшем руководстве). Видимо, предполагается что ставит всё безоговорочный админ и крутиться всё будет под его же учеткой. Если это не так, то нам нужно:

    • a. Дать права на запись в папку “AppData” приложения. Туда будут складываться nuget-пакеты. Даем права аккаунту, под которым крутится AppPool-приложения. На случай, если в настройках IIS стоит ApplicationPoolIdentity, а не конкретная учетная запись, даем права группе IIS_IUSRS.
    • b. По тем же принципам даём права на доступ к БД в SQL.
    • c. Можно после регистрации дать себе же роль админа. Для этого после стандартной регистрации через UI вставляем запись в таблицу dbo.UserRoles, поглядев Id роли в таблице dbo.Roles. После этого по адресу “<путь к серверу>/admin” вам станет доступна админка cо всякими плюшками и букмарклетом glimpse.




    4. GalleryOwner. Есть смысл поменять на реальные настройки пункта “Gallery.GalleryOwner” в web.config. От него будут рассылаться письма при регистрации. А можно и вовсе отключить эту рассылку. И обязательно пропишите актуальный SiteRoot.

    <appSettings>
      <add key="Gallery.LuceneIndexLocation" value="AppData" /> 
      <add key="Gallery.Environment" value="Development" /> 
      <add key="Gallery.SiteRoot" value="http://your-server-name/" /> 
      <add key="Gallery.GalleryOwner" value="%username% <%usermail%>" /> 
      <add key="Gallery.ConfirmEmailAddresses" value="true" /> 
      <add key="Gallery.HasWorker" value="false" /> 
    </appSettings>
    


    5. В файле stats.js есть функция getStats. На момент написания данной статьи ее вызов приводил к ошибке на главной странице.



    Причина кроется в том, что в локальной версии nuget статистика отключена. На данный момент она предназначена только для облачной версии.

    Вы можете создать свою реализацию IStatisticsService или просто отключить данный вызов. Мы выбрали второй путь.

    На этом, собственно, всё. Теперь у нас есть nuget-сервер с галереей и пакетами.

    Но что, если в наш пакет (dll-ку из него) закрался баг? Или разработчик просто хочет понять, что делает наша функция “DoStuff”? Очевидно, библиотеку нужно дебажить. Но не так-то это просто, когда у нас в распоряжении есть только dll. Именно поэтому nuget умеет собирать не только пакеты библиотек, но и symbol-пакеты.

    А мы идем учиться собирать свой собственный symbol server

    В качестве решения для поддержки отладки мы решили использовать SymbolSource, поскольку он настолько хорошо дружит с nuget, что тот его поддерживает как symbol-сервер по умолчанию.

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

    При создании nuget-пакета через команду nuget pack мы можем указать флаг “–Symbols”. В результате nuget создает, помимо стандартного пакета с библиотечкой, ещё пакет <имя_пакета>.symbols.nupkg, который будет содержать в себе pdb-файл и исходники. Потом этот пакет можно разместить на сервере SymbolSource, который проиндексирует pdb-файл и разместит у себя во внутреннем каталоге исходники проекта. Далее в Visual Studio настраивается доступ к symbol серверу, и при необходимости она загрузит с сервера pdb-файл и исходники. Так и становится возможной отладка.

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

    И тут начались приключения…

    По задумке авторов, развернуть свой symbol сервер задача почти такая же простая, как и развернуть свой nuget feed — создаем MVC-приложение, качаем nuget-пакет, даём права на папки “App_Data” и “Data” — всё работает.

    Ах да, есть одно дополнительное требование — необходимо поставить Debugging Tools for Windows и прописать путь к ним в web.config:

    <appSettings>
      <add key="SrcSrvPath" value="C:\Program Files\Debugging Tools for Windows (x64)\srcsrv" /> 
    </appSettings>
    


    Собственно, так мы и сделали — загрузили версию 1.3.2 SymbolServer-а, поставили, настроили, увидели волшебный экран.



    Диагностику запустили — всё хорошо. Настроили Visual Studio в точности по инструкции.

    Но Debug не работает…

    Проблема нашлась достаточно быстро – сервис не сохранял в каталог “Data” исходники при загрузке symbol-пакета. Повозившись какое-то время с настройками IIS, мы поняли, что проблема в другом, и занялись отладкой.

    Именно. Отладка сервера SymbolSource при помощи сервера SymbolSource.

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

    Ну что поделаешь… Посмеялись, форкнули исходники, поправили, собрались делать pull request. И тут мы обнаруживаем, что аналогичный request уже есть, причём создан достаточно давно. Да и пакет версии 1.3.2 выпущен уже два месяца как, а вот версия пакета с фиксом не выпущена до сих пор. Вот это уже было не так смешно. Точнее, совсем не смешно.

    На какое-то время мы даже задумались, стоит ли вообще использовать этот сервер или лучше поискать другой. Но положительный предыдущий опыт использования… И разработчики nuget сделали его сервером по умолчанию, значит доверяют. И пакет версии 1.3.1 отлично работает. В итоге мы решили запустить всё на поправленных исходниках. Для этого дополнительно скопировали настройку из “Web.config.transform” в “Web.config”, поскольку при локальной сборке transform не выполняется:

    <location path="WinDbg/pdbsrc">
      <system.webServer>
        <handlers>
          <clear />
          <add name="Deny" verb="*" path="*.config" type="System.Web.HttpForbiddenHandler" />
          <add name="Allow" verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler" />
        </handlers>
        <security>
          <requestFiltering>
            <fileExtensions allowUnlisted="true">
              <clear />
            </fileExtensions>
          </requestFiltering>
        </security>
      </system.webServer>
    </location>
    


    Теперь готово и еще и работает как надо. И тут настало время подумать о том,

    Почему одного nuget-сервера недостаточно для построения эффективного процесса (beta).

    А недостаточно его по очень простой причине: nuget, как он есть, это доступный всей компании сервер.

    Значит, в него нельзя класть пакеты, которые могут оказаться нестабильными. Но когда разработка конечных проектов потребовала изменения сборок, размещаемых в nuget, то внести изменения так, чтобы заработало сразу, задача из разряда “пишем код без багов”.

    Различных приемов и приседаний, чтобы как-то работать с одним сервером, можно найти множество. Например, подменять ручками dll-ки в папке “packages” конечного проекта, а в nuget публиковать только когда уже всё готово. Но мы же делаем это всё, чтобы жизнь стала проще, а не наоборот. Поэтому, мы решили подойти к вопросу более обстоятельно. Немного подумав, мы придумали нечто.

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

    Итак, мы решили, что у нас будет три nuget-ленты.
    1. Local feed. На самом деле это просто сетевая папка (в Visual Studio при настройке источников можно указать не только адрес сервера, но и UNC путь к папке). В эту ленту пакеты собираются на каждый commit в VCS. Никто кроме разработчиков reusable кода source к этой ленте не настраивает.
    2. Test feed. Эта роль вполне логично ложится на сервер SymbolSource, у которого есть своя лента (именно лента, не галерея) для пакетов, куда автоматически размещается обычный nuget-пакет при публикации symbol-пакета. Публикация в эту ленту выполняется, когда закончено внесение изменений в базовый код и можно приступать к его использованию. Source к этой ленте есть у всех разработчиков компании и у build-машин, собирающих тестовые версии конечных продуктов.
    3. Сервер release-пакетов. Этим сервером стал тот, который мы развернули ещё в начале статьи — красивый, с UI и прочими плюшками. Самая важная плюшка — требование ApiKey. Локальная лента его не требует, как и лента SymbolSource (точнее SymbolSource требует, но не верифицирует). И там они и не нужны. Здесь же всё серьезнее, поэтому имеет смысл сделать так, чтобы публиковать могли не все. А если кто-то смог, было бы понятно кто это. Публикация на этот сервер идет только тогда, когда базовый код протестирован и сам по себе, и в работе с конечными продуктами. Source на данный сервер настроен у всех разработчиков, build-машин, собирающих тестовые версии конечных продуктов, build-машин, собирающих release версии конечных продуктов.

    Получается, с одной стороны, что build-машины, собирающие release-версии конечных продуктов, никогда не соберут проект, если он ссылается на тестовую версию пакета.

    С другой стороны, порядок того, как указаны сервера в настройках Package Sources у Visual Studio (Tools->Library Package Manager->Package Manager Settings) задает приоритет загрузки пакетов. Поэтому, например, developer-ы базового кода имеют доступ ко всем трем лентам и порядок в настройках выставлен как release feed, test feed, local feed.

    В Visual Studio это выглядит это примерно вот так:



    Кстати, именно тут достаточно весомым становится аргумент в пользу локального развёртывания серверов — независимость от доступа в internet и бОльшая скорость при работе в локальной сети.

    “А что, если один из серверов упал?” — спросите вы. Ну, так не беда. Ведь build process “фреймворка” один на все ленты. То есть, если пакет версии 1.2.3.4 объявляется релизным, то он лежит во всех трех лентах. И доступ к нему, например, у разработчика будет всегда.

    С одной стороны, такой процесс не очень прост, но с другой стороны он получился очень естественным, поэтому о его сложности задумываться придётся не часто — он просто ложится на стандартный поток работ.

    Ну что же, продумали. Теперь можно вернуться к технологиям и подумать про Continuous Integration, который автоматизирует весь этот процесс.

    Но статья уже затянулась.


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

    Спасибо за ваше внимание.

    UPD: как оно обычно и бывает, ровно на следующий день после того, как статья была дописана, вышло обновление пакета SymbolSource.Server.Basic (версия 1.3.3), в которой исправили баг, упомянутый в разделе про развертывание symbol-сервера.
    True Engineering
    Лаборатория технологических инноваций

    Комментарии 14

      0
      можно просто сделать шару и на неё класть пакеты — nuget это дело вполне понимает. Ну а права разрулить шарой уже не представляет труда)
        0
        Мы сначало то же так делали, но потом пришли к тому, что за каждым пакетом прикреплен свой мэинтэйнер, который за него отвечает и переехали на галерею. Теперь понятно кто и когда что менял и обновлял).
          0
          Проще надо быть, проще: сборка и выкладка пакета в репо может быть осуществлена только CI сервером, версии (соответственно) видны в исходниках, равно как и все изменения… В целом, я конечно, не говорю что делать сацтец с RSS и прочими это неправильно, но стоит подобную технику тоже незабывать…
            0
            Ну тут еще вопрос ведения истории встал) очень важно что бы пакеты случайно кто-то не перетер и не подменил. Поэтому у всех доступ закрыт нафиг. А публикацию делает не CI сервер, а AddIn для студии, где указывается api ключик. Если у вас билд номер увеличивается с каждым билдом — очень ыбстро надоест обновлять пакеты))
              0
              Добрый вечер. В общем-то мы так и сделали — кладет пакеты в ленты только build-машина.
              Что касательно папок или сайтов — все по потребностям. Для полноценной поддержки процесса нам понадобилось три ленты, и самое главное — symbol-server. Он был необходим как воздух.
              Дело в том, что количество кода, который не можно, а нужно положить в ленту у нас действительно большое. Вот и требования к инфраструктуре соответствующие.
          0
          А не смотрели как в последних версиях сделан переключатель Stable Only/Include Prerelease?
            0
            Да, смотрели.
            Вообще это было одной из тем для второй статьи.
            Если идти по стандарту semantic versioning по которому мы как раз и пошли, и по которому идет nuget, то получается что весьма вероятная ситуация, когда мы внесли изменение, не повлекшее изменения API, поэтому меняем в версии формата Major.Minor.Patch только Patch.
            При этом, получается, что пакет, допустим, версии 1.2 был релизный, и тут вдруг опять появляется его beta.
            То есть prerelease это фича хорошая, но использовать ее в day-to-day разработке нам показалось не очень логично.
            Мы решили оставить эту штуку на другие случаи.
            Например, когда обстоятельства вынуждают нас сделать быстрый релиз чего-либо нового и не протестированного и это явный сигнал всем, что команде не стоит обновляться до этой версии, если эта версия не конкретно для нее.

              0
              А ок, мы используем… Всегда меняем номер версии при публикации, т.е не бывает ситуации, когда что-то поменялось, а версия осталась старая. Для каждой версии продукта есть четкий список зависимостей. Ну и свой AddIn написали для публикаций) Пока руки не доходят добить Stable\Prerelease. Так уже около года живем и все довольны, хотя поначалу тяжело было приучать народ не референсить бинарники в лоб и оформлять зависимости как пакеты).
              Буду ждать вторую статью) Заранее спасибо.
                0
                А вы не могли бы поделиться, зачем именно AddIn и чем он лучше continuous build-а? Или одно дополняет другое?
                  0
                  Билды собираются постоянно, но каждый раз обновлять пакет — нецелесообразно, так как у нас меняется номер билда. Если каждый раз выкладывать — будет не очень красиво) Хабрапарсер скушал схему версионности. Так же есть компоненты, которые используются для интеграции, и у них своя нумерация. AddIn позволяет четко контролировать, что и когда выкладывается, добавлять релизноты в Description и тд. Допустим продукт имеет версию 2.3.4.5000, но его SDK имеет версию 1.2.3.4000, и при изменении версии продукта, если изменения в SDK не вносились — версия SDK не будет изменена. Плюс как я уже писал выше — за каждым пакетом закреплен свой мэинтэйнер, и только он может обновлять его. Ну и огребать соответственно).
                    0
                    Зачем билды собираются постоянно? Билды собираются, если на то есть причина — обычно это изменение в исходных кодах, что должно влечь за собой изменения версии. Если вы не меняли sdk, то и сборки её не будет.
                      0
                      Ну не раз в неделю чекиним. И версия то как раз меняется, но независимо.
            0
            Отличная статья, сам планировал подобную.

            Насчет сборки решения и публикации пакетов — мы поступили несколько иначе — подняли два билд-сервера.
            На первом continious build для решения, на втором — сборка и публикация пакетов в корпоративную галерею.
            Плюс у разработчиков есть скрипт для локальной сборки пакетов для тестирования без публикации.
            Для того, чтобы не заморачиваться с формированием файла спецификаций, основную информацию мы собираем из атрибутов сборки (AssemblyCompany, AssemblyProduct, AssemblyVersion и т.д.), а зависимости — из packages.config и файла проекта (ProjectReferences).
            Версия пакета формируется с использованием Major и Minor версии, в качестве Build используется номер чейнджсета в локальном хранилище кода. Если пакет собирается для отладки, то добавляется Revision, равный номеру дня в месяце + количество секунд от полуночи поделенное на 4. Таким образом пакет в релизе имеет версию, например 1.5.2608, в отладке 1.5.2608.2721276, причем после завершения отладки и чекина, релизный пакет имеет версию, например 1.5.2609. Это упрощает отладку пакета в прикладном решении, так как не нужно руками откатывать версию пакета — после чекина достаточно просто сделать update.
            Перед публикацией мы запрашиваем список пакетов из галереи и отсекаем существующие дабы не тормозить на попытках опубликовать пакет, который уже присутствует на сервере. Это помогает нам не публиковать отладочные пакеты в общую галерею и, вместе с тем, позволяет нормально отлаживать пакеты в прикладном решении.

            На текущий момент для сборки и публикации пакетов достаточно вызвать билд из веб-интерфейса билд-сервера, что сильно экономит время.
              0
              Получается, во многом у нас вышли похожие решения по части версионирования.
              По поводу файлов спецификации — мы добавили шаблоны *.nuspec с replacement tokens прямо в проекты и собираем пакеты на их основе. Это позволяет добиться еще одного приятного момента — не приходится управлять тем, из каких проектов собирать пакеты, а из каких нет. У кого есть nuspec — из того и собирается. А всякие тестовые проекты и те, работа над которыми еще только-только началась и до релиза им еще далеко просто проходят этап билда и прогона тестов.
              В ближайшее время мы выпустим вторую часть статьи, там расскажем в деталях как это все сделано у нас.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое