Pull to refresh

Comments 37

Познавательно. Но для большинства проектов — бесполезно, т.к. MSBuild файлы генерируются, а не являются основополагающим форматом.
Статья однозначно хорошая, не спорю.
А вы используете MSBuild файлы в качестве основных?
У нас много кода в Студии написано и сугубо в ней же живет. Весь этот код (сотни проектов, миллионы LOC) собирается в родном для Студии MSBuild. Все работает, кстати, на удивление хорошо и стабильно.
Я нигде не ставил под сомнение то что все это можно использовать правильно и все будет стабильно) Ну и по статье понятно, что у вас это основная система.
Вопрос был все же к комментатору прежде всего, чем ему это полезно.
А можно в двух словах… почему не CMake?
Мы не ищем простых путей :)
Если серьезно, то на MSBuild живет много чистых windows-проектов которым не нужна кросс-платформенность. Да и интеграция GUI в Студии с MSBuild остается по-прежнему лучше.
CMake это не только кроссплатформенность, а простые конфиги + готовые решения + даже есть пакетные менеджеры. Если я пишу только под linux, то это не значит, что мне стоит использовать Makefile.
MSBuild тоже дает простой конфиг (особенно в версии Core), готовые решения и даже есть пакетный менеджер (NuGet)

Нет никаких причин не использовать его пока устраивает тулчейн VC++
Вот чем надо Микрософту заниматься для удобства разработчиков, а не окно создания проектов туда-сюда редизайнить. Спасибо!
А в чём тут, собственно, адище-то?
UFO just landed and posted this here
Как насчёт premake?
Вообще, лично я перестал использовать студию для генерации проектов. У меня все эти *.sln, *.vcxproj, etc — попадают в игнор и удаляются во время clean. Хоть и разработка идёт только под винду, всё равно намного удобнее сгенерить файлы проектов под кокретную версию VS.

А в чём проблема прописать Condition="'$(Configuration)' == 'Debug'"?

ну да, действительно, можно, если в тексте непосредственно конфигурировать, а я всё больше через IDE и макросами…

Так вроде же через IDE настраиваемая конфигурация выбирается сверху-слева в диалоге?

для .props(страницы свойств) — нет. конфигурация и платформа не выбирается.
а вручную сработало:
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <Link>
      <AdditionalDependencies>zlibstatic.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>

Некрокоммент, но может кому-то будет полезно.


1) Если не хочется заморачиваться с импортами своих .props и .targets файлов в каждый проект, то можно создать файл Directory.Build.props и/или Directory.Build.targets, которые импортируются автоматически.


2) Лучше избегать $(SolutionDir), потому что он существует только если солюшен строится из Студии. На билд-сервере ваши проекты будут строиться голым MSBuild, который использует .sln только чтобы получить список проектов и билд-завсимостей, после чего каждый проект строится изолировано от солюшена. Если надо привязаться к стабильному пути, то лучше использовать $(MSBuildThisFileDirectory), который равен пути к самому props/targets-файлу. Если такой файл лежит рядом c .sln, то это даст надёжный переносимый эквивалент $(SolutionDir).


3) Бывает так, что солюшен не плоский (все проекты в одной папке), а иерархическй, т.е. проекты разбиты по поддиректориям (например, пачка проектов в поддиректории Core, другая пака в поддиректории Client, третья — в поддиректории Server и т.д), и в таком случае на каждом уровне иерархии могут появляться собственные настройки, в дополнение к общим. Например, все проекты в Client подключают один набор заголовочный файлов и библиотек и используют один набор параметров, все проекты в Server — другой набор, и при этом все проекты вообще должны использовать общий solution-wide набор. В таком сценарии можно использовать комбо из приёмов 1 и 2, положив на каждом уровне иерархии по файлику Directory.Build.* примерно такого вида:


<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!--  Импортируем вышележащий Directory.Build.props, если таковой есть. В самом верхнем файле этого можно не добавлять. -->
  <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

  <!-- Для удобства делаем короткий алиас для $(MSBuildThisFileDirectory)  -->
  <PropertyGroup>
    <_ThisDir>$(MSBuildThisFileDirectory)</_ThisDir>
  </PropertyGroup>

  <!-- Добавляем параметры, общие для всех подпроектов в данной иерархии -->
  <ItemDefinitionGroup>
    <ClCompile>
      <AdditionalIncludeDirectories>$(_ThisDir)Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <ResourceCompile>
      <AdditionalIncludeDirectories>$(_ThisDir)Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ResourceCompile>

    ...

  </ItemDefinitionGroup>

</Project>

Они подключатся автоматически, и все параметры протянутся по иерархии сверху вниз, от корня к каждому подпроекту.


4) Лучше не использовать дефолтные $(OutDir) и $(IntDir), которые по умолчанию гадят прямо в папку проекта (in-source build), и приучиться всегда строить во внешнюю папку (out-of-source build), которая находится за пределами солюшена, и может даже за пределами репозитория.
Во-первых, меньше шансов случайно закоммитить мусор в source control; проще настроить .gitignore.
Во-вторых, облегчается поиск по исходникам без открытия студии (из Far-а, например) — не нужен фильтр для исключеня .exe;.obj;.pch;....
В-третьих, можно быстро сделать clean и освободить кучу места на диске, не открывая студии — достаточно удалить одну-единственную директорию, а не лазить по всему дереву проекта, удаляя папки /Debug, /Release, /obj, /bin, /generated и т.п.
В-четвёртых, на билд-сервере проекты часто билдаются именно out-of-source, валя всё в одну папку, и иногда это тихо ломает ваш in-source билд. Например, два проекта зависят от одной DLL, копируя их каждый в свой output. Но вот версии незаметно разбежались. Пока строим локально из студии, каждый проект получает свою версию, и работает нормально, разработчик считает, что всё отлично. Но когда делатся "сводный" билд в общую папку на билд-сервере, то одна версия может затереть другую, недетерминированным способом (в зависимости от порядка и времени билда каждого проекта), и это может долго оставаться незамеченным. Если делать "сводные" билды сразу, локально, то больше шансов заметить, что разные файлы с одинаковыми именами перетирают друг друга.


Настраивать $(OutDir) тоже удобно одним общим props-файликом вроде такого:


<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup Label="Output folders">
    <BuildDirSuffix>$(Configuration).$(Platform)\</BuildDirSuffix>
    <IntDir>$(SolutionDir)~output\~intermediate\$(BuildDirSuffix)\</IntDir>
    <OutDir>$(SolutionDir)~output\$(BuildDirSuffix)\</OutDir>
    <OutputPath>$(OutDir)</OutputPath>
  </PropertyGroup>

Здесь использутся $(SolutionDir) потому что эта настройка нужна только для Студии, на билд-сервере путь задаётся билд-конфигурацией. Нужно только удалить $(OutDir) и $(IntDir) из каждого .vcxproj-файла.


Если кому-то всё же неудобно, что все файлы валятся в одну папку, то достаточно изменить BuildDirSuffix на $(Configuration).$(Platform)\$(MSBuildProjectName).


Если же кому-то неудобно искать бинарники вдалеке от сорцов, то в Студии легко создать тул, чтобы открывать целевую папку: "Tools > External Tools > Add", Title="Open target director&y", Command="C:\Windows\explorer.exe", Arguments="$(BinDir)", после чего комбинация "Alt+T-Y" в студии будет мгновенно открывать папку с построенными бинарниками.

Не надо так делать: <OutDir>$(SolutionDir)~output\$(BuildDirSuffix)\</OutDir>. Это убивает возможность перегрузить OutDir "снаружи" при дальнейшей настройке сборки.


Такие вещи надо писать в OutputPath, а OutDir сформируется на основе OutputPath средствами SDK.

Насколько я помню, это справедливо для проектов C#, но у C++ похоже собственная гордость. Если я убираю <OutDir> и оставляю только <OutputPath>$(SolutionDir)~output\$(BuildDirSuffix)\</OutputPath>, то .vcxproj начинает кидать всё в $(SolutionDir)$(Platform)\$(Configuration), т.е. например в \x64\Release\ на уровне солюшена.


Под "перегрузить снаружи" вы имеете в виду передать через командную строку msbuild.exe /p:OutDir="...\\"? Вроде командная строка имеет приоритет над пропсами? Я не сталкивался с проблемами тут, можно пояснить детали?

Спасибо за статью, пригодилось.

Замечу что при формировании списков файлов CopyFilesToFolder можно использовать wildcards.

Оказалось, что wildcards не поддерживаются VisualStudio.

Так что, для копирования директории приходится по-прежнему использовать xcopy в PostBuildEvent.

Причём тут вообще студия? Собирает-то проект не она.

Собирает-то проект не она

Студия загружает проект. А собирает его потом с помощью MsBuild.

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

Конечно, можно не загружать проект в студию, а собирать его из командной строки. Но зачем тогда Студия?

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


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


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

Если я правильно помню то они не показываются правильно в проекте, но сборка msbuild при этом работает. Однако xcopy имхо тут худшее решение - и не показывается и работает не так хорошо как msbuild.

Если я правильно помню то они не показываются правильно в проекте, но сборка msbuild при этом работает.

Студия просто не загружает проект если в нем есть CopyFilesToFolder с wildcard. Сообщает об ошибке.

Однако xcopy имхо тут худшее решение - и не показывается и работает не так хорошо как msbuild.

Команда xcopy нормально показывается в настройках, в разделе "PostBuildEvent".
А при сборке в окне "Output" выводятся имена копируемых файлов и сообщение "11 File(s) copied"

Однако xcopy имхо тут худшее решение

А какое лучшее решение?

Задача такая: В исходниках есть папки "debug" и "release", каждая содержит по десятку dll. При сборке из нужно скопировать dll-файлы из папки, соответствующей конфигурации, в OutDir.

Я сейчас специально перепроверил - 2022я студия загружает проект с wildcards. Пишет предупреждение при загрузке что при попытке изменить такой проект все может сломаться и упасть, но загружает. Начиная с 2019й Студии там даже добавили пару параметров специально для подобных проектов включающие возможность видеть данные из wildcards в solution или сделать проект read-only чтобы исключить проблемы с сохранением.

https://learn.microsoft.com/en-us/cpp/build/reference/vcxproj-files-and-wildcards

Проблема с xcopy в том что Студия не знает надо ли копировать файлы или нет и каждый раз при сборке выполняет xcopy. Это неудобно, особенно в больших солюшнах где проектов много и между ними куча dependency.

Да, сорри, был неправ. Wildcards поддерживаются.

CopyFilesToFolder работает. Но, к сожалению, работает молча: ничего не выводится в окно output при уровне Minimal. Можно это как-то исправить?


Да, но для ClCompile-items при Minimal-output выводится имя файла. И видно, что сборщик что-то делает.

А CopyFilesToFolder-items - полная тишина. Просто появляются файлы.
Можно ли как-то повысить информативность? Или добавить свои сообщения?

Вообще, CopyFilesToFolder как-то совсем плохо документированы.

Мой опыт с копированием файлов:

Item CopyFileToFolders:
Из коробки работают Rebuild, Build (Incremental), Clean. Но, при уровне Minimal-output, все это делается совершенно молча. Даже Rebuild.
Отсутствует документация.

Пользовательский Item и таргет с Copy-task:
Clean пришлось делать самостоятельно.
А Build не могу заставить работать. Если удалить часть файлов, то он этого не замечает, говорит, что все "up to date". Похоже, MsBuild проверяет файл *.lastbuildstate, и, если он актуальный, то игнорирует все таргеты и их Inputs и Outputs и завершает работу.

Вообще, странно. Вроде, простая и обычная задача: скопировать файл в выходную директорию, а нормального решения нет. А ведь нынешнему MsBuild-у уже 14 лет.

PS.
В статье есть опечатка в слове "CopyFileToFolders". Поправьте, пожалуйста.
Такая же опечатка есть в моих комментариях (я копипастил из статьи). Там, похоже, уже не исправить.

Если удалить часть файлов, то он этого не замечает, говорит, что все "up to date"

А .tlog файлы заполняете как описано в статье?

В статье есть опечатка в слове "CopyFileToFolders"

Спасибо за инфу про опечатку! Поправил

А .tlog файлы заполняете как описано в статье?

Да, действительно, я это упустил. И в результате, U2DCheck не запускал MSBuild.
Спасибо!
Поправил, заработало.

Вопросы:
Какой смысл несет имя tlog файла? В вашем примере это protobuf.read.1.tlog
Первая часть - что угодно?
Вторая - read/write.
Третья - какой-то номер?

Если удалять целевые файлы и вызывать Build, то они восстанавливаются. Но tlog-файлы при этом неограниченно растут - в них дописывается инфа о восстановленных файлах. Это нормально?
При Clean или Rebuild они очищаются.

По поводу получения детальных отчетов от U2DCheck. Не все так просто.

В указанной статье рекомендуют поставить флаг:
[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0\General] "U2DCheckVerbosity"=dword:00000001
Но это, наверное, для 12 (2013) версии Студии. А для 2022 там, наверное, должно быть 17.0
У меня в реестре оказалось аж четыре таких раздела:

Я добавил U2DCheckVerbosity в каждый из четырех 17.0 разделов. Не заработало.
Еще в UI (Tools/Options) похожая настройка для SDK-style проектов. Тоже не помогло

Вроде, U2DCheckVerbosity работает, но только для C# проектов.

Sign up to leave a comment.

Articles