Переход с Visual Studio на VS Code

Если вы используете Microsoft Visual Studio в коммерческих целях, то вам нужно покупать лицензию. Фирма, в которой я работаю, такую лицензию имеет. Однако, почему бы не избавиться от необходимости платить, и не перейти на бесплатные средства разработки? Вот и стали мы переходить… Оказалось, что не так это сложно, в конце концов интегрированная среда разработки (ее платная часть) — это лишь графическая оболочка, которая для своей работы пользуется различными бесплатными утилитами командной строки. Графическую Visual Studio часть можно компенсировать бесплатным редактором исходного кода Visual Studio Code. Что требуется от редактора? Две вещи: IntelliSense и поддержка отладки исходного кода. В VS Code это есть. Но текстовый редактор — это не IDE, поэтому такие задачи как компиляция исходного кода и управление пакетами придется выполнять «своими руками», т. е. используя утилиты командной строки. В отсутствие IDE придется научиться этими утилитами пользоваться. Обо всех проблемах, связанных с переходом с Visual Studio на свободное ПО — далее в этой заметке.

Для справки: в Visual Studio я работаю над большим решением, которое включает порядка сотни проектов на .NET ⁄ C#. Далее в примерах командной строки будут использоваться следующие условные обозначения:
SolutionDir — папка, в которой находится решение (файл с расширением .sln)
ProjectDir — папка, в которой лежит проект (файл с расширением .csproj)

Управление пакетами — Nuget Package Manager

Nuget — это менеджер пакетов (package manager) — утилита, которая автоматизирует использование (в частности, скачивание, установку, удаление) стороннего кода. Подробнее о менеджерах пакетов вообще и о Nuget в частности читайте в статье Что такое Nuget и для чего он нужен. Nuget поставляется в составе Visual Studio. Существует целый зоопарк родных поддерживаемых Microsoft инструментов для управления пакетами, основанный на Nuget. Какие-то из них доступны только из Visual Studio, какие-то можно использовать сами по себе. Перечислю их в том порядке, в котором я их для себя открывал:

Инструмент Только в составе Visual Studio?
Package Manager UI да
Package Manager Console да
nuget.exe нет
dotnet.exe нет
PowerShell PackageManagement нет

packages.config и PackageReference

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

Вариант первый, который по-умолчанию имеет место в проектах для .NET Framework — это файл packages.config. Этот xml-файл создается отдельно для каждого проекта в решении. В файле перечисляются пакеты, от которых зависит данный проект. Для каждого пакета указывается его имя, версия и целевая платформа (target framework).

Второй вариант — это когда информация о пакетах для проекта хранится прямо в файле проекта (*.csproj) в xml-узлах под названием PackageReference.

Ну а теперь — к инструментам…

Package Manager UI и Package Manager Console

В Visual Studio в вашем распоряжении будет как консольный (Package Manager Console), так и графический (Package Manager UI) интерфейс для работы с Nuget. Открыть оба инструмента в Visual Studio можно при помощи меню Tools > NuGet Package Manager. Причем интерфейс командной строки в Visual Studio — это оболочка PowerShell с кучей cmdlet’ов типа Find-Package, Istall-Package, Update-Package и других.

nuget.exe

Консольная утилита nuget.exe — это, как мне кажется, самый простой и понятный инструмент, который можно использовать без Visual Studio. Но сначала его надо установить. Для этого надо скачать nuget.exe в какую-нибудь папку и добавить путь к этой папке в переменную окружения PATH (в Windows: Панель управления\Система и безопасность\Система\Дополнительные параметры системы\Переменные среды).

Установка пакетов для заданного проекта
1. В папке проекта (где лежит файл *.csproj) создайте файл packages.config и добавьте в него информацию о необходимых пакетах. Вот пример такого файла:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="AForge" version="2.2.5" targetFramework="net46" />
  <package id="AForge.Imaging" version="2.2.5" targetFramework="net46" />
  <package id="AForge.Math" version="2.2.5" targetFramework="net46" />
</packages>

2. Далее в командной строке (обычной cmd.exe) запускаем nuget. При этом в аргументах командной строки обязательно надо указать путь к файлу packages.config и путь к папке, в которую будут установлены пакеты (обычно это папка packages, которая лежит в папке решения):

SolutionDir\ProjectDir> nuget install packages.config -OutputDirectory SolutionDir\packages

Примечательно, что средства Visual Studio Package Manager Console и Package Manager UI а также утилита dotnet.exe при установке пакета добавляют ссылки на сборки этого пакета в файл проекта (*.csproj), а вот утилита nuget.exe не добавляет. Поэтому вам придется добавлять ссылки вручную (что необязательно плохо — я, например, люблю утилиты которые делают только узкую специфическую часть работы, а не все сразу).

Восстановление пакетов для всего решения
Переходим в корневую папку решения и вызываем:

SolutionDir> nuget restore

Когда нужно восстановить пакеты, nuget.exe ищет в папках проектов файлы packages.config и считывает из них информацию о том, какие именно пакеты надо установить.

dotnet.exe

Инструмент, который обеспечивает не только управление пакетами, но и возможность создания решений, проектов, сборки и пр. — это .NET Core command-line interface (CLI) tools, который является частью платформы .NET Core. Пользоваться этим самым .NET Core CLI можно при помощи утилиты dotnet.exe. С помощью этой утилиты вы можете управлять пакетами как в nuget.exe, но есть нюанс: dotnet.exe не поддерживает packages.config (см. выше), а поддерживает только PackageReference.

Установка пакетов для заданного проекта

SolutionDir\ProjectDir> dotnet add package <PACKAGE_NAME> --package-directory <PACKAGE_DIRECTORY>

Восстановление пакетов для всего решения

SolutionDir> dotnet restore --packages <PACKAGES_DIRECTORY>

PowerShell PackageManagement

И еще один инструмент — PowerShell PackageManagementмодуль PowerShell, который содержит такие же cmdlet’ы, как и интерфейс командной строки Visual Studio (Find-Package, Istall-Package и пр.) Но, оказывается, консольный интерфейс Visual Studio и интерфейс PowerShell PackageManagement в чем-то все-таки отличаются:

The commands listed here are specific to the Package Manager Console in Visual Studio, and differ from the Package Management module commands that are available in a general PowerShell environment.

Чтобы воспользоваться PowerShell PackageManagement, вы можете запустить Windows PowerShell или PowerShell ISE. При попытке что-то установить этим инструментом у меня возникли проблемы, описанные в топике Nuget Packages do not display on powershell Windows 10 на superuser.com. Например, пытаемся посмотреть доступные пакеты NuGet стандартным способом, который описывается в документации от Microsoft:

Find-Package -ProviderName NuGet

И не видим ничего. А делать надо так:

Find-Package -Source https://www.nuget.org/api/v2/

В общем, везде ссылайтесь не на -ProviderName NuGet а на -Source https://www.nuget.org/api/v2/ (именно v2, а не v3).

Поиск пакетов по имени
Допустим я хочу установить пакет Extended.Wpf… что-то там, не помню как называется.

Find-Package -Name Extended.Wpf* -Source https://www.nuget.org/api/v2/
Name                           Version          Source           Summary                                                                                                                      
----                           -------          ------           -------                                                                                                                      
Extended.Wpf.Toolkit           3.5.0            NuGet            Extended WPF Toolkit is the number one collection of WPF controls for creating next-gen Windows apps.

Ага, есть такой пакет, называется Extended.Wpf.Toolkit. Теперь я его устанавливаю:

Install-Package -Name Extended.Wpf.Toolkit -Source https://www.nuget.org/api/v2/ -Destination SolutionDir\packages

Естественно, никакие ссылки в проект не добавляются (т. е. в этом смысле PowerShell PackageManagement работает аналогично nuget.exe).
А теперь я хочу посмотреть список установленных пакетов:

Get-Package -Destination SolutionDir\packages
Name                           Version          Source                               ProviderName                                                                                                
----                           -------          ------                               ------------                                                                                                
Extended.Wpf.Toolkit           3.5.0            SolutionDir\Extended.Wpf.Toolkit.... NuGet

Переход с Nuget на Paket

При безусловных преимуществах, которые дает использование Nuget, есть и некоторые мелкие неудобства. Нас с коллегами например напрягало, что при установке (или восстановлении) пакета средствами Visual Studio в проект всегда добавляются ссылки на сборки пакета с установленным свойством Copy Local = True, т. е. они будут копироваться в выходную папку проекта, а нас такое поведение не устраивало по некоторым причинам.

Поэтому мы решили рассмотреть переход на другой менеджер пакетов — Paket. Преимущества Paket (по мнению его авторов) по сравнению с Nuget описаны на страничке проекта на GiHub. Как и nuget.exe, Paket — это консольная утилита, которая для Windows поставляется в виде файла packet.exe. Процесс установки описан в Paket installation. Есть некоторые нюансы касательно установки:

1. Вам придется создать в корне вашего решения папку .paket, а поскольку имя папки начинается с точки, вы сможете сделать это только из командной строки:

mkdir .paket

2. Есть еще одна инструкция по установке, которая говорит: скачав файл paket.bootstrapper.exe в папку .paket, переименуйте его в paket.exe и закоммитьте (игнорировать его при помощи файла .gitignore, не надо). Зачем все это надо, написано в описании файла paket.bootstrapper.exe.

3. Для удобства пользования утилитой packet.exe существует расширение для Visual Studio под названием Paket for Visual Studio, которое обеспечивает некие плюшки, в частности подсветку синтаксиса и InteliSense для файлов paket.dependencies и paket.references. Но это расширение не устанавливается для Visual Studio 2019.

Установив paket.exe, подготавливаем наше решение (имеется в виду Visual Studio solution). Создаем в папке решения файл paket.dependencies. В нем перечисляются все пакеты для всех проектов решения, обычно, с указанием версий. Для одного из моих решений файл выглядит так:

// paket.dependencies file for paket package manager utility

// restrict target framework
framework: >= net46

source https://api.nuget.org/v3/index.json

// don't copy referenced assemblies to project's output directory
copy_local: false

// NuGet packages
nuget Accord 3.8.0
nuget Accord.Imaging 3.8.0
nuget Accord.MachineLearning 3.8.0
nuget Accord.Math 3.8.0
nuget Accord.Statistics 3.8.0
nuget Accord.Video 3.8.0
nuget Accord.Vision 3.8.0
nuget AForge 2.2.5
nuget AForge.Imaging 2.2.5
nuget AForge.Math 2.2.5
nuget AForge.Video 2.2.5
nuget AForge.Video.DirectShow 2.2.5
nuget AvalonEdit 5.0.4
nuget Extended.Wpf.Toolkit 2.5
nuget Mantin.Controls.Wpf.Notification 3.2.0.0
nuget MessagingToolkit.QRCode 1.3.0
nuget Microsoft.CodeDom.Providers.DotNetCompilerPlatform 2.0.1
nuget Ookii.Dialogs.Wpf 1.0.0
nuget System.ValueTuple 4.5.0
nuget System.Windows.Interactivity.WPF 2.0.20525

После того как файл paket.dependencies создан, можно попросить paket закачать пакеты:

SolutionDir> .paket\paket.exe install

Paket загружает пакеты в папку SolutionDir\packages — в ту же самую папку, в которую их загружает NuGet.

ВАЖНО: в первый раз когда я запустил команду paket.exe install, paket помимо нужных пакетов закачал еще огромную кучу какого-то хлама. Оказалось, что мне надо было ограничить версию целевой платформы, добавив в paket.dependencies строчку framework: >= net46. А хлам — это были пакеты-зависимости (от них зависели нужные пакеты, перечисленные в файле paket.dependencies) для всех возможных платформ. Добавив упомянутую строчку и перезапустив paket.exe install, получаем правильный результат, без лишних пакетов.

Команда paket.exe install генерирует в папке решения файл paket.lock, который описывает какой пакет от какого пакета зависит. У меня он выглядит так:

RESTRICTION: >= net46
NUGET
  remote: https://api.nuget.org/v3/index.json
    Accord (3.8)
    Accord.Imaging (3.8)
      Accord (>= 3.8)
      Accord.Math (>= 3.8)
      Accord.Statistics (>= 3.8)
    Accord.MachineLearning (3.8)
      Accord (>= 3.8)
      Accord.Math (>= 3.8)
      Accord.Statistics (>= 3.8)
    Accord.Math (3.8)
      Accord (>= 3.8)
    Accord.Statistics (3.8)
      Accord (>= 3.8)
      Accord.Math (>= 3.8)
    Accord.Video (3.8)
      Accord (>= 3.8)
    Accord.Vision (3.8)
      Accord (>= 3.8)
      Accord.Imaging (>= 3.8)
      Accord.MachineLearning (>= 3.8)
      Accord.Math (>= 3.8)
      Accord.Statistics (>= 3.8)
      Accord.Video (>= 3.8)
    AForge (2.2.5)
    AForge.Imaging (2.2.5)
      AForge (>= 2.2.5)
      AForge.Math (>= 2.2.5)
    AForge.Math (2.2.5)
      AForge (>= 2.2.5)
    AForge.Video (2.2.5)
      AForge (>= 2.2.5)
    AForge.Video.DirectShow (2.2.5)
      AForge.Video (>= 2.2.5)
    AvalonEdit (5.0.4)
    Extended.Wpf.Toolkit (2.5)
    Mantin.Controls.Wpf.Notification (3.2)
    MessagingToolkit.QRCode (1.3)
    Microsoft.CodeDom.Providers.DotNetCompilerPlatform (2.0.1)
    Ookii.Dialogs.Wpf (1.0)
    System.ValueTuple (4.5)
    System.Windows.Interactivity.WPF (2.0.20525)

Далее надо для каждого проекта указать, какие пакеты этому проекту нужны. Для этого надо создать в папке каждого проекта файл paket.references. Этот файл очень простой — в нем просто перечисляются названия пакетов без указания версий. Вот как он выглядит для одного из проектов в моем решении:

Microsoft.CodeDom.Providers.DotNetCompilerPlatform
System.ValueTuple

Если теперь запустить paket.exe install, то нужные пакеты не только загрузятся в папку packages (если они уже не загружены), но и в файл проекта (.csproj) добавятся ссылки на сборки из этих пакетов. Я уже писал выше, что для меня было важно в ссылках на сборки контролировать параметр Copy Local и делать его равным False. В paket это достигается строчкой copy_local: false в файле paket.dependencies.

Вот собственно и весь процесс использования paket. Последнее, что нужно сказать: если вы в своем решении уже пользовались NuGet, и переходите на paket, то утилита paket может выполнить этот переход полностью автоматически (т. е. вам не придется делать все, что было описано выше, нужно будет только скачать paket.exe):

.paket\paket.exe convert-from-nuget --force

Создание проектов и решений без Visual Studio

Создание файлов проектов и решений вручную

Файлы проектов (с расширениями .csproj, .vcxproj и другие) и решений (с расширением .sln) — это файлы, которые потребляет программа MSBuild. Это текстовые файлы формата xml. В файле проекта содержится информация о том, как его построить (какие файлы исходного кода принадлежат к проекту, какими инструментами их копилировать, на какие библиотеки ссылается проект и пр.). В файле решения перечисляются все проекты, которые принадлежат решению.
В документации к MSBuild приведен простой пример, как создавать файлы проектов вручную. Там же есть хорошее руководство по синтаксису файлов проектов. Прочитав это руководство, я разобрался например в том, как при помощи настроек в файле проекта MSBuild скопировать файлы сборок (*.dll) из папки OutputPath в некую другую папку DistributionFolder, которая задается в свойствах проекта:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
...
...
  <PropertyGroup>
    <!-- Property of the project that can be changed -->
    <DistributionFolder>$(SolutionDir)Distributive\$(ConfigurationName)</DistributionFolder >
  </PropertyGroup>

  <ItemGroup>
    <!-- Input to the tasks (see below) -->
    <OutputFiles Include="$(OutputPath)*.dll" />
  </ItemGroup>

  <!-- Target -->
  <Target Name="PostBuild" AfterTargets="Build">
    <!-- Task -->
    <Exec Command="echo POSTBUILD COPYING OUTPUT FILES" />

    <!-- Task -->
    <Copy SourceFiles="@(OutputFiles)"
         DestinationFolder="$(DistributionFolder)" />
  </Target>
</Project>

Во фрагменте файла проекта (.csproj) даны комментарии по терминологии (в MSBuild ключевую роль играют понятия: Target, Task, Property и Item). По принципам своей работы MSBuild очень похожа на утилиту make.

Небольшое отступление. Если речь не идет о создании нового файла проекта (.csproj), а лишь об изменении имеющегося, то даже необязательно редактировать файл проекта. Те изменения, которые вы планируете внести в файл проекта можно вынести в отдельные два файла Directory.Build.props и Directory.Build.targets, которые утилита MSBuild сама ищет в папке проекта и выше нее и импортирует в проект. Подробнее об этом читайте в статье Customize your build.

Утилита dotnet.exe

Утилита dotnet.exe умеет создавать проекты для .NET Core (и поддерживает ряд соответствующих шаблонов проектов), но не для .NET Framework. Например проект приложения WPF dotnet.exe вам не создаст — утилита не поддерживает такой шаблон проекта. Ну а ради интереса, вот как при помощи dotnet.exe создать решение, создать проект (здесь — шаблон Class Library), добавить проект в решение и построить решение:

dotnet new sln -n MySolution
dotnet new classlib -n MyProject
dotnet sln MySolution.sln add MyProject/MyProject.csproj
dotnet build

Здесь MySolution — имя решения (будет создан файл MySolution.sln), MyProject — имя проекта (в папке решения будет создана папка MyProject и в ней — файл MyProject.csproj).

Утилита CMake

В топике на Stack Overflow — Generate C# project using CMake и на страничке проекта cmake_csharp_example на GitHub объясняется как можно сгенерировать проект csproj при помощи CMake, но… CMake требует наличия на компьютере установленной Visual Studio.

Программная генерация файлов проектов и решений

Вот топике Making a solution file without using visual studio на Stack Overflow рассказывается что можно программно генерировать файлы решений и проектов при помощи MSBuild API, который входит в .NET Framework.

Резюме

Таким образом, я не нашел простого способа генерировать файлы решений и проектов без Visual Studio 🙁 Если выбирать из того что есть, то я склоняюсь к созданию файлов проектов вручную в текстовом редакторе. При этом создать вручную файл решения (sln) — это уже совсем геморрой, потому что в нем используются GUID’ы, но как раз файл решения, как мне кажется, можно создавать утилитой dotnet.exe.

Сборка решения

MSBuild

Без Visual Studio вы можете построить проект при помощи утилиты MSBuild.exe, которую можно скачать и использовать бесплатно. Например, чтобы построить release’ную версию программы, нужно находясь в папке решения, запустить в командной строке:

SolutionDir> MSBuild.exe -property:Configuration=Release

Чтобы программа MSBuild успешно запустилась из командной строки, вам нужно либо добавить путь к папке, где лежит msbuild.exe в переменную окружения PATH (у меня это C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin), либо запускать командную строку Visual Studio Command Prompt либо указывать полный путь к файлу MSBuild.exe.

Build Systems (автоматизация сборки решения)

Build system — это некий софт, который выполняет сборку программы из кучи файлов (исходников и ресурсов), учитывая зависимости этих файлов друг от друга. Яркими представителями такого софта (из известных мне) являются утилиты GNU make, CMake, MSBuild. Когда вы используете такую утилиту для сборки своей программы, вы всегда создаете некий текстовый файл, в котором так или иначе описывается процесс сборки (в GNU make это makefile, в MSBuild это файл проекта).

При построении большого решения, о котором я рассказывал в прошлой заметке, у нас возникают проблемы формирования дистрибутива (т. е. копирования файлов), которые стандартными средствами Visual Studio (читай — средствами MSBuild) не очень легко решаются — приходится писать скрипты например для PowerShell. Связана эта проблема с тем, что дистрибутив у нас — это не одна папка, в которую сваливаются все сборки (dll-ки) а целая структура папок. А структура папок нам нужна а) чтобы не запутаться в огромной куче файлов б) потому что программа у нас модульная, поддерживает плагины, и каждый плагин — это папка с одной или несколькими сбороками внутри; плагин устанавливается путем копирования такой папки в дистрибутив, а удаляется соответственно путем удаления этой папки из дистрибутива.

При отказе от Visual Studio возникают дополнительные проблемы — приходится «вручную» вызывать package manager для восстановления пакетов и MSBuild для сборки проектов. Все это приводит к необходимости написать такой скрипт, который бы выполнял все наши задачи:

  1. Очистка решения и ⁄ или дистрибутива
  2. Установка ⁄ восстановление ⁄ обновление пакетов
  3. Построение проектов
  4. Формирование дистрибутива (копирование всяких файлов в папку дистрибутива)

Для выполнения этих задач применительно к проекту на платформе .NET мы рассматривали build system’ы, перечисленные в табличке ниже. Причем предпочтение мы отдавали системам, использующим в качестве языка программирования C#.

Build System Programming Language
Nuke C#
Cake С#
Psake PowerShell
Fake F#
Rake Ruby

В итоге остановились на системе Nuke.

Nuke

Установка Nuke на компьютер:

dotnet tool install Nuke.GlobalTool --global

Установка Nuke в решение Visual Studio:

SolutionDir> nuke :setup

Далее у меня состоялся с утилитой следующий диалог:

How should the build project be named?
┐  nuke_build_project
Where should the build project be located?
┐  nuke_build_project
Which NUKE version should be used?
┐  0.20.1 (latest release)
Which solution should be the default?
┐  MySuperBigSolution.sln
Do you need help getting started with a basic build?
┐  Yes, get me started!
Restore, compile, pack using ...
┐  Neither
Source files are located in ...
┐  ./src
Move packages to ...
┐  Neither
Where do test projects go?
┐  ./tests
Do you use GitVersion?
┐  Yes, just not setup yet
Creating directory 'SolutionDir\nuke_build_project'...
Creating directory 'SolutionDir\tests'...

Утилита создает в корне решения несколько файлов (из которых нас интересует только build.ps1) и проект — консольное приложение на C#, которое будет строить наше решение.

Пример очень простого кода Build.cs:

class Build : NukeBuild
{
    public static int Main () => Execute<Build>(x => x.Compile);

    [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
    readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;

    AbsolutePath SourceDirectory => RootDirectory / "src";

    // This describes a utility tool - package manager paket.exe
    [LocalExecutable("./.paket/paket.exe")]
    readonly Tool Paket;

    // Example of command line:
    // ./build.ps1 -target clean
    Target Clean => _ => _
        .Before(Restore)
        .Executes(() =>
        {
            SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory);
        });

    // Example of command line:
    // ./build.ps1 -target restore
    Target Restore => _ => _
        .Executes(() =>
        {
             // Here we use the tool paket.exe we described previously
             Paket("install");
        });

    // Example of command line:
    // ./build.ps1 -target compile -configuration release
    Target Compile => _ => _
        .DependsOn(Restore)
        .Executes(() =>
        {
            MSBuildTasks.MSBuild(msbuild_settings => msbuild_settings
                .SetConfiguration(Configuration));
        });

    // Example of command line:
    // ./build.ps1 -target rebuild -configuration release
    Target Rebuild => _ => _
        .DependsOn(Clean)
        .DependsOn(Compile)
        .Executes(() =>
        {
        });
}

Nuke — это, помимо всего прочего, API, который позволяет анализировать файлы проектов (.csproj) и решений (.sln). Например, следующий учебный кусок кода проходит по всем проектам в решении и выводит в консоль название папки решения (Solution Folder), в которой находится проект и свойство OutputPath проекта:

foreach (var project in Solution.AllProjects)
{
    // output the solution folder name for the project
    Logger.Normal(project.SolutionFolder.Name);
    // output the project's output path
    Logger.Normal(project.GetMSBuildProject(Configuration).GetPropertyValue("OutputPath"));
}

Тут надо отметить метод Project GetMSBuildProject(this Project project, string configuration = null, string targetFramework = null);. Дело в том, что в файлах проекта активно используются условные конструкции — они создаются при помощи атрибута Condition. Это напоминает директивы условной компиляции в C/С++. Поэтому, чтобы узнать истинное значение, скажем, свойства OutputPath, которое оно будет иметь во время построения утилитой MSBuild, надо выполнить (evaluate) все эти условные конструкции. Вот это-то и делает метод GetMSBuildProject.

Итак, у нас есть проект программы, которая выполняет сборку решения. Теперь чтобы запустить процесс сборки решения, надо выполнить в командной строке powershell скрипт build.ps1:

SolutionDir> .\build.ps1 -target compile -configuration release

Расширения для Visual Studio

Command Task Runner

Мы с вами уже увидели, что для того чтобы выпонять различные задачи, например построить ⁄ очистить решение или восстановить пакеты, надо вызывать разные утилиты из командной строки. Это можно делать как при помощи обычной windows’овской командной строки cmd.exe или оболочки PowerShell, так и при помощи командной строки Visual Studio — Package Manager Console. Неудобство заключается в том, что 1) надо держать в памяти все команды, которые могут понадобиться в жизни 2) надо печатать их вручную в командной строке. Для облегчения жизни программиста в Visual Studio существует компонент под названием Task Runner Explorer (View > Other Windows > Task Runner Explorer), в котором отображается список задач, любую из которых можно запустить одним кликом. Сами задачи берутся из файла commands.json, который надо создавать и редактировать вручную. В описании каждой задачи в файле commands.json указывается утилита, которую надо запустить, и аргументы командной строки. В свою очередь, чтобы не создавать и не редактировать его вручную, существует расширение Command Task Runner.

Вот пример файла commands.json с двумя задачами (BuildRelease и Clean), которые выполняются путем запуска скрипта build.ps1 (скрипт PowerShell, который появился при установке Nuke):

{
  "commands": {
    "Build Release": {
      "fileName": "powershell.exe",
      "workingDirectory": ".",
      "arguments": "-ExecutionPolicy Bypass -NonInteractive -File build.ps1 -target compile -configuration release"
    },

    "Clean": {
      "fileName": "powershell.exe",
      "workingDirectory": ".",
      "arguments": "-ExecutionPolicy Bypass -NonInteractive -File build.ps1 -target clean"
    }
  }
}

Обратил внимание на такую неприятную особенность Task Runner Explorer: когда запускаешь задачу, графический интерфейс Visual Studio становится недоступным. Видимо задача запускается в потоке обработки сообщений (windows message loop).

NUKE Support

Расширение NUKE Support автоматически добавляет nuke’овские задачи в commands.json. Правда у меня это расширение почему-то не заработало. Судя по исходникам, расширение запускает build.ps1 --help и добывает из вывода информацию о target’ах.

Как пользоваться VS Code

В окне VS Code слева находится панелька EXPLORER. Там вы увидите кнопку Открыть папку (Open Folder). Вы щелкаете по этой кнопке и в диалоге выбора папки выбираете папку вашего решения (SolutionDir). После этого VS Code начинает загружать решение (Не знаю, что именно делает VS Code, но какая-то обработка файлов проектов происходит — ее ход отображается в панели OUTPUT в нижней части окна VS Code). Далее нам нужны две вещи: 1) одним кликом запускать сборку решения; 2) научиться отлаживать программу.

Command Palette

В VS Code есть точно такая же штука как Task Runner Explorer в Visual Studio — запускатель задач. Найти его можно в меню View > Command Palette.... Там, насколько я понимаю, есть какие-то встроенные задачи по-умолчанию. Но помимо них вы можете добавлять свои собственные. Если вы выберете View > Command Palette... > Tasks: Run Task, то VS Code предложит вам создать файл SolutionDir\.vscode\tasks.json, в котором можно описать набор задач. Пример содержимого файла tasks.json, который по смыслу аналогичен файлу commands.json для Visual Studio, приведенному выше:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build release",
            "command": "powershell",
            "type": "process",
            "args": [
                "${workspaceFolder}/build.ps1",
                "-target compile",
                "-configuration release"
            ],
            "problemMatcher": "$tsc"
        },
        {
            "label": "clean",
            "command": "powershell",
            "type": "process",
            "args": [
                "${workspaceFolder}/build.ps1",
                "-target clean"
            ],
            "problemMatcher": "$tsc"
        }
    ]
}

Отладка в VS Code

Во-первых, чтобы писать и отлаживать код на C# в VS Code рекомендуют установить расширение C# for Visual Studio Code.

Если вы попробуете запустить отладку (например выбрав меню Debug > Start Debugging, то VS Code создаст файл SolutionDir\.vscode\launch.json, который используется для конфигурирования отладчика. Вот такой файлик создался у меня по-умолчанию (я только поменял значение свойства «program» на путь к исполняемому файлу моей программы):

{
   // Use IntelliSense to find out which attributes exist for C# debugging
   // Use hover for the description of the existing attributes
   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
   "version": "0.2.0",
   "configurations": [

        {
            "name": ".NET Core Launch (console)",
            "type": "coreclr",
            "request": "launch",
            // If you have changed target frameworks, make sure to update the program path.
            "program": "${workspaceFolder}/Distributive/Debug/MySuperAwesomeApp.exe",
            "args": [],
            "cwd": "${workspaceFolder}/Distributive/Debug",
            // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
            "console": "internalConsole",
            "stopAtEntry": false
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        }
    ]
}

В VS Code почему-то можно отлаживать только 64-разрядные программы, поэтому вам придется специально построить 64-разрядную версию своей программы. Для этого при компиляции вы должны передать утилите MSBuild параметр командной строки /p:Prefer32Bit=False. Если мы запускаем MSBuild из проекта nuke, то можно добавить такую tagret в исходный код проекта nuke:

// Build.cs

class Build : NukeBuild
{
...
...
...
    // Example of command line:
    // ./build.ps1 -target CompileDebugX64
    Target CompileDebugX64 => _ => _
        .DependsOn(Restore)
        .Executes(() =>
        {
            MSBuildTasks.MSBuild(msbuild_settings => msbuild_settings
                .SetConfiguration(Configuration.Debug)
                .SetProperty("Prefer32Bit", "False"));
        });
...
...
...
}

Благодарности

Автор благодарит своего коллегу Сашу Новикова. Этот нехороший человек раньше меня разобрался во всех этих nuget’ах, paket’ах, msbuild’ах, fake’ах и прочих vscod’ах и заставил меня разбираться в них тоже, а также помогал консультацией, да еще и эту заметку прочитал и высказал ценные замечания. Спасибо, Саша!

Один комментарий к “Переход с Visual Studio на VS Code”

Добавить комментарий для Денис Отменить ответ

Ваш адрес email не будет опубликован.