Skip to content

Commit aada76d

Browse files
committed
main
1 parent 1aa069f commit aada76d

File tree

1 file changed

+227
-38
lines changed

1 file changed

+227
-38
lines changed

docs/document/Modern CSharp/docs/Understanding MSBuild.md

Lines changed: 227 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,10 @@
22

33
## File Types
44

5-
- `.*proj`: project file for each project
6-
- `.targets`: shared config for project within a same solution, can be imported to another.
7-
- `.props`:
8-
- `*.rsp`: msbuild response file,
9-
10-
### Recommended Usage
11-
12-
- `*.targets`
13-
- set dependent properties
14-
- override properties
5+
- `.*proj`: project identifier file for each project
6+
- `.props`: shared config to be imported at the beginning of the project file
7+
- `.targets`: shared config to be imported at the end of the project file
8+
- `*.rsp`: [msbuild response file](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-response-files?view=vs-2022), default options for msbuild cli so you don't have to repeat them on each build
159

1610
## File Structure
1711

@@ -26,6 +20,7 @@
2620
- children with the same tag name are within a same source(they're just declared separately)
2721
- use `@(itemType)` to retrieve a list of items from *existing* `<ItemGroup>`
2822
- has some builtin item types preserved
23+
- `<ItemDefinitionGroup>`: define a new shape of item with default metadata
2924
- `<Target>`: section to **wrap sub-procedures** and to be invoked during build
3025
- name it like a function
3126
- use `Name` attribute to specify a name for it
@@ -37,19 +32,48 @@
3732
## Project Attributes
3833

3934
- `SDK`: sepcify a sdk so that msbuild can prepare dedicated builtin *targets* and *tasks* for corresponding type of project.
40-
> a project with specified `SDK` is sometimes referred as *SDK-style project*
35+
> [!NOTE]
36+
> - a project with specified `SDK` is referred as *SDK-style project*
37+
> - a `<Project>` with `SDK` would auto import standard `*.targets` and `*.props`
38+
> - you could find standard config files from `${DOTNET_ROOT}/sdk/<dotnet_version>/Sdks/<sdk_name>/`
39+
40+
- `InitialTargets`: a list of targets should run first on build
41+
- `DefaultTargets`: a list of targets should run after `InitialTargets` **when no any target specified from msbuild cli**
4142

4243
> [!NOTE]
4344
> [Available SDKs](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview#available-sdks)
4445
4546
## Property Section
4647

47-
- reserved properties: pre-defined and cannot be overridden
48+
Two kinds of properties:
49+
50+
- reserved properties: pre-defined and cannot be overridden,
4851
- well-known properties: pre-defined and can be overridden
52+
- properties set by specified sdk, all other sdk inherits from `Microsoft.NET.Sdk`, see: [.NET project SDKs](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview?view=aspnetcore-9.0)
4953

5054
> [!NOTE]
5155
> [Reserved & Well-Known Properties](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-reserved-and-well-known-properties?view=vs-2022#reserved-and-well-known-properties)
5256
57+
### Property Functions
58+
59+
> [!NOTE]
60+
> [Property Functions](https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions?view=vs-2022)
61+
62+
### Merge Properties
63+
64+
```xml
65+
<PropertyGroup>
66+
<Foo>foo;bar</Foo>
67+
<!-- override self with interpolating itself -->
68+
<Foo>$(Foo);baz</Foo>
69+
</PropertyGroup>
70+
71+
<Target>
72+
<!-- foo;bar;baz -->
73+
<Message Text="$(Foo)" Importance="high"/>
74+
</Target>
75+
```
76+
5377
## Item Section
5478

5579
`<ItemGroup>` section is generally for specifying files to be included on build.
@@ -201,7 +225,40 @@ Each item has pre-defined metadata can be accessed.
201225
> [!NOTE]
202226
> If a item does not represent a file, the most of intrinsic metadata would be empty.
203227

228+
### Common Items
229+
230+
- `Reference`: reference to dll files
231+
- `PackageReference`: reference packages to be restored by nuget
232+
- `ProjectReference`: reference project by project file, builds projects cascading
233+
- `Using`: extra global using by specifying namespaces
234+
```xml
235+
<ItemGroup>
236+
<Using Include="System.IO.Pipes" />
237+
</ItemGroup>
238+
```
239+
240+
## ItemDefinitionGroup Section
241+
242+
```xml
243+
<ItemDefinitionGroup>
244+
<Foo>
245+
<!-- metadata Bar has default value bar -->
246+
<Bar>bar</Bar>
247+
</Foo>
248+
</ItemDefinitionGroup>
204249

250+
<ItemGroup>
251+
<Foo Include="foo"></Foo>
252+
</ItemGroup>
253+
254+
<Target Name="Hello">
255+
<!-- bar -->
256+
<Message Text="%(Foo.Bar)" Importance="high"/>
257+
</Target>
258+
```
259+
260+
> [!NOTE]
261+
> [ItemDefinitionGroup](https://learn.microsoft.com/en-us/visualstudio/msbuild/item-definitions?view=vs-2022)
205262
206263
## Expression Syntax
207264

@@ -224,10 +281,11 @@ Expression syntax in msbuild has some flavor of Command Prompt and PowerShell.
224281

225282
<Target Name="Hello">
226283
<!-- Collected metadata: Program.cs; ConsoleApp.csproj -->
227-
<Message Text="Collected metadata: @(MyFile->'%(FileName)%(Extension)')" <!-- [!code highlight] -->
228-
Importance="high" /> <!-- [!code highlight] -->
229-
<Message Text="Exists: @(MyFile->Count())" <!-- 5 --> <!-- [!code highlight] -->
230-
Importance="high" /> <!-- [!code highlight] -->
284+
<Message Text="Collected metadata: @(MyFile->'%(FileName)%(Extension)')"
285+
Importance="high" />
286+
<!-- 5 -->
287+
<Message Text="Exists: @(MyFile->Count())"
288+
Importance="high" />
231289
</Target>
232290
```
233291

@@ -248,23 +306,21 @@ For [special characters](https://learn.microsoft.com/en-us/visualstudio/msbuild/
248306
So one could include dedicated part of the config for different build scenarios.
249307

250308
```xml
251-
<ItemGroup Condition="'$(DotNetBuildSourceOnly)' == 'true'"> <!-- [!code highlight] -->
309+
<ItemGroup Condition="'$(DotNetBuildSourceOnly)' == 'true'">
252310
<PackageVersion Include="Microsoft.Build" Version="17.3.4" />
253311
<PackageVersion Include="Microsoft.Build.Framework" Version="17.3.4" />
254312
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="17.3.4" />
255313
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.3.4" />
256314
</ItemGroup>
257315

258-
<ItemGroup Condition="'$(DotNetBuildSourceOnly)' != 'true' and '$(TargetFramework)' != 'net472'"> <!-- [!code highlight] -->
316+
<ItemGroup Condition="'$(DotNetBuildSourceOnly)' != 'true' and '$(TargetFramework)' != 'net472'">
259317
<PackageVersion Include="Microsoft.Build" Version="17.7.2" />
260318
<PackageVersion Include="Microsoft.Build.Framework" Version="17.7.2" />
261319
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="17.7.2" />
262320
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.7.2" />
263321
</ItemGroup>
264322
```
265323

266-
## Builtin Variables
267-
268324
## Target Section
269325

270326
A task is a pre-defined procedure to be executed in `<Target>` section in their declared order.
@@ -273,6 +329,7 @@ MSBuild ships with some builtin task to be used out of box.
273329

274330
```xml
275331
<Target>
332+
<!-- Csc is a builtin task from sdk -->
276333
<Csc
277334
Sources="@(Compile)"
278335
OutputAssembly="$(AppName).exe"
@@ -283,9 +340,22 @@ MSBuild ships with some builtin task to be used out of box.
283340
> [!NOTE]
284341
> See: [MSBuild Task Reference](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-task-reference?view=vs-2022#in-this-section)
285342
286-
### Task Hooks
343+
### Builtin Targets
287344

288-
- `DependsOnTargets`
345+
MSBuild has some pre-defined targets to perform common actions like `Build`, `Clean`...
346+
Targets starts with `After` and `Before` are preserved to be overridden as a hook on specific target, such as `AfterBuild` and `BeforeBuild` are hooks on `Build`.
347+
348+
> [!NOTE]
349+
> Use `dotnet msbuild -targets|-ts [proj]` to check existing targets(including builtin) from a project file.
350+
>```ps1
351+
># filter out internal targets starts with _
352+
>dotnet msbuild -targets | where { $_ -notmatch '^_' }
353+
>```
354+
355+
### Target Hooks
356+
357+
- `DependsOnTargets`: this target must be executed after the specified target
358+
- `BeforeTarget`
289359
290360
### Custom Task
291361
@@ -308,41 +378,117 @@ Such approach seems to only allow updating on all existing items since `Include`
308378
309379
<Target Name="Hello">
310380
<ItemGroup>
311-
<FooList> <!-- no Include here --> <!-- [!code highlight] -->
381+
<FooList> <!-- no Include here -->
312382
<!-- update all existing items from FooList -->
313-
<FooMetaData>this is a bar metadata now!</FooMetaData> <!-- [!code highlight] -->
383+
<FooMetaData>this is a bar metadata now!</FooMetaData>
314384
</FooList>
315385
</ItemGroup>
316386
317-
<Message Text="%(FooList.Identity): %(FooList.FooMetaData)" Importance="high"/> <!-- [!code highlight] -->
387+
<Message Text="%(FooList.Identity): %(FooList.FooMetaData)" Importance="high"/>
318388
<!-- foo: this is a bar metadata now!
319389
bar: this is a bar metadata now!
320390
baz: this is a bar metadata now! -->
321391
</Target>
322392
```
323393
324-
## Importing
394+
### Emit Property and Item from Task
325395

326-
- `*.props` should be imported in early stage
327-
- `*.targets`: should be imported in build stage
396+
- one task could emit multiple items and properties(use multiple `<Output>`)
397+
- the task supports output parameter(specific value for `TaskParameter`)
398+
- search `output parameter` to check available `TaskParameter` on documentation of a task
399+
- use `PropertyName` or `ItemName` attribute to specify the name to emit
328400

329-
## Evaluation Order
401+
```xml
402+
<ItemGroup>
403+
<NewDir Include="foo;bar" />
404+
</ItemGroup>
330405

331-
MSBuild files are order sensitive, order matters when properties and items may dependent on each other.
406+
<Target>
407+
<MakeDir Directories="@(NewDir)">
408+
emit DirCreated property with the value of DirectoriesCreated
409+
<Output TaskParameter="DirectoriesCreated" ItemName="DirCreated"/>
410+
</MakeDir>
411+
<!-- log out new property -->
412+
<Message Text="@(DirCreated)" Importance="high"/>
413+
</Target>
414+
```
332415

416+
## Import & Evaluation Order
417+
418+
The only file msbuild cares for build is the `*.*proj` file, all other files are just imports within it.
419+
The evaluation happens upon the project file, and splits the file into a process with priorities for different section.
420+
421+
### SDK Style
422+
423+
Aforementioned SDK-style project has implicit `<Import>` for standard `props` and `targets`.
424+
425+
- standard `props` were imported
426+
427+
### Evaluation
428+
429+
> [!NOTE]
430+
> [Evaluation Phase](https://learn.microsoft.com/en-us/visualstudio/msbuild/build-process-overview?view=vs-2022#evaluation-phase)
431+
432+
MSBuild files are order sensitive, order matters when properties and items may dependent on each other.
433+
MSBuild merges imported config files and proj file into one object, and then evaluate all values of different parts.
333434

334435
1. environment variables: they're stored as properties
335436
2. imports & properties: evaluated by their declaration order
336-
- so, the order between imports and properties matters if you inter-reference them on expression
437+
- when evaluating a import, the **evaluation process** applies to it **recursively**.
438+
- the order between imports and properties matters if you inter-reference them on expression
439+
- never reference items on properties not within a target
337440
3. definition of items: how should item were filtered out and captured.
338441
4. items: execute the process to fetch items and their metadata
339-
5. inline tasks
442+
5. inline tasks(`<UsingTask>`)
340443
6. targets: as well as items inside targets
341444

445+
> [!IMPORTANT]
446+
> Expressions are lazily evaluated until the identifier been referenced got evaluated.
447+
> So any expression to be evaluated on evaluation time(*not within a target*) would be like a template.
448+
> That is to say, the order doesn't matter when you inter-reference items and properties.
449+
> See: [Subtle effects of the evaluation order](https://learn.microsoft.com/en-us/visualstudio/msbuild/comparing-properties-and-items?view=vs-2022#subtle-effects-of-the-evaluation-order)
450+
>```xml
451+
> <PropertyGroup>
452+
> <!-- reference before FooList were evaluated -->
453+
> <Foo>@(FooList)</Foo>
454+
> </PropertyGroup>
455+
>
456+
> <ItemGroup>
457+
> <FooList Include="foo;bar"/>
458+
> </ItemGroup>
459+
>
460+
> <Target Name="Hello">
461+
> <!-- foo;bar -->
462+
> <Message Text="$(Foo)" Importance="high"></Message>
463+
> </Target>
464+
>```
465+
466+
342467
## Folder-Specific Config
343468
344-
`Directory.Build.props` and `Directory.Build.targets` are special config files that could be auto-imported by msbuild.
345-
Such file is a **template** rather than a static store for values, so the containing properties and tasks **could vary for each project**.
469+
Folder-Specific config files includes:
470+
471+
- `Directory.Build.props`: content to be imported before standard config automatically for current or child folders
472+
- `Directory.Build.targets`: content to be imported after content of project file automatically for current or child folders
473+
- `Directory.Build.rsp`: default options for msbuild cli to be passed automatically on each build
474+
```
475+
# inside rsp file
476+
-v:diag -t:Clean
477+
```
478+
- `Directory.Packages.props`: for central package management, fixed version by `<PackageVersion>`
479+
```xml
480+
<PropertyGroup>
481+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
482+
</PropertyGroup>
483+
<ItemGroup>
484+
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
485+
</ItemGroup>
486+
```
487+
488+
> [!NOTE]
489+
> [Introducing Central Package Management](https://devblogs.microsoft.com/nuget/introducing-central-package-management/)
490+
491+
All these kind of config are special config files that would be auto-imported by msbuild at certain phase.
346492
MSBuild would search upward for each aforementioned config file **until one was found**, it doesn't stop on solution root until it reaches the **drive root**
347493
348494
```xml
@@ -356,16 +502,59 @@ MSBuild would search upward for each aforementioned config file **until one was
356502
</Project>
357503
```
358504
359-
`Directory.Build.props` is imported early in `Microsoft.Common.props` which is the default config for sdk-style projects, so properties in `Directory.Build.props` should not be dependent on other
505+
### Best Practice
506+
507+
- `Directory.Build.props`: imported before standard musbuild config
508+
- to set **independent** properties
509+
- to set conditional items
510+
511+
- `Directory.Build.targets`: imported after standard
512+
- to override properties
513+
- to set **dependent** properties
514+
- to set targets
515+
516+
- `$(MSBuildProjectFullPath).user`: extra config specific to local machine, should not be included in source control
360517

361518
> [!TIP]
362519
> - use `dotnet new buildprops` to create `Directory.Build.props`
363520
> - use `dotnet new buildtargets` to create `Directory.Build.targets`
364-
521+
> - use `dotnet new packagesprops` to create `Directory.Packages.props`
365522
366523
## MSBuild CLI
367524

368-
MSBuild cli was wrapped as `dotnet msbuild [-options]`
525+
> [!NOTE]
526+
> [MSBuild CLI Reference](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference?view=vs-2022#switches)
527+
528+
MSBuild cli was wrapped as `dotnet msbuild [-options]` within `dotnet` cli.
369529

370530
- `-p:<prop>=<value>`: override a property value(namely global property)
371-
- `-t:<target>`: trigger a target(and its dependent hooks)
531+
- `-t:<target>[;..]`: trigger a target(and its dependent hooks)
532+
- `-r`: `Restore` before building
533+
534+
> [!NOTE]
535+
> `dotnet` cli essentially wrapped `msbuild` cli and has some shorthand for common usage of `msbuild`.
536+
> All build and restore related command from `dotnet` are wrapper of a `msbuild` command.
537+
> Such targets like `Build` are pre-defined targets.
538+
>```sh
539+
># they're equivalent
540+
>dotnet build
541+
>dotnet msbuild -t:Build
542+
>dotnet msbuild # Build would be the default target if no other targets were specified
543+
>
544+
>dotnet pack
545+
>dotnet msbuild -t:Pack
546+
>
547+
>dotnet restore -p:Foo=foo
548+
>dotnet msbuild -t:Restore -p:Foo=foo #
549+
>```
550+
551+
### Target Execution Order
552+
553+
Targets would be executed by the order they were specified(each target triggers and waits its dependent targets)
554+
555+
```sh
556+
dotnet msbuild -t:Clean,Build # Clean first then Build
557+
```
558+
559+
> [!NOTE]
560+
> [Target Order](https://learn.microsoft.com/en-us/visualstudio/msbuild/target-build-order?view=vs-2022#determine-the-target-build-order)

0 commit comments

Comments
 (0)