Pull to refresh

Comments 37

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

Нет никаких причин не использовать его пока устраивает тулчейн VC++
Вот чем надо Микрософту заниматься для удобства разработчиков, а не окно создания проектов туда-сюда редизайнить. Спасибо!
А в чём тут, собственно, адище-то?
UFO landed and left these words 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