summaryrefslogtreecommitdiff
path: root/Tools/Symbols.targets
blob: 06ee7466b1150761e7acc38b9fa77c2831d47bc9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="AddItemIndices" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.Tasks.dll"/>
  <UsingTask TaskName="ConvertPortablePdbsToWindowsPdbs" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.Tasks.dll"/>
  <UsingTask TaskName="DownloadFilesFromUrl" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.Tasks.dll"/>
  <UsingTask TaskName="ExecWithRetries" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.Tasks.dll"/>
  <UsingTask TaskName="ZipFileExtractToDirectory" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.Tasks.dll"/>
  <UsingTask TaskName="ZipFileGetEntries" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.Tasks.dll"/>
  <UsingTask TaskName="ZipFileInjectFile" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.Tasks.dll"/>

  <PropertyGroup>
    <SymbolsRequestIntermediateDir Condition="'$(SymbolsRequestIntermediateDir)'==''">$(BaseIntermediateOutputPath)SymbolsRequest\</SymbolsRequestIntermediateDir>
  </PropertyGroup>

  <!--
    Submits a request to index or archive symbols with the Microsoft symbol server. Depends on
    targets which assemble the request specification file.
  -->
  <Target Name="SubmitSymbolsRequest"
          DependsOnTargets="CreateSymbolsRequestIni">
    <PropertyGroup>
      <CreateRequestCommandLocation>\\symbols\tools\CreateRequest.cmd</CreateRequestCommandLocation>
      <SymbolsRequestLogDir>$(SymbolsRequestIntermediateDir)Logs\</SymbolsRequestLogDir>

      <SubmissionArg Condition="'$(IndexSymbols)'=='true'">-s</SubmissionArg>
      <SubmissionArg Condition="'$(ArchiveSymbols)'=='true'">-a</SubmissionArg>

      <RequestCommand>$(CreateRequestCommandLocation)</RequestCommand>
      <RequestCommand>$(RequestCommand) -i $(SymbolsRequestIniPath)</RequestCommand>
      <RequestCommand>$(RequestCommand) -d $(SymbolsRequestLogDir)</RequestCommand>
      <RequestCommand>$(RequestCommand) -c</RequestCommand>
      <RequestCommand Condition="'$(SymbolsRequestDryRun)'!='true'">$(RequestCommand) $(SubmissionArg)</RequestCommand>
    </PropertyGroup>

    <Message Text="[$([System.DateTime]::Now.ToString('HH:mm:ss.ff'))] Running request command..." Importance="High"/>

    <Exec Command="$(RequestCommand)" />

    <Message Text="[$([System.DateTime]::Now.ToString('HH:mm:ss.ff'))] Running request command... Done." Importance="High"/>
  </Target>

  <!--
    Creates a Request.ini file that can be used as an input to the symbol server CreateRequest tool.
  -->
  <Target Name="CreateSymbolsRequestIni"
          DependsOnTargets="AddRequestProperties;
                            AddArchiveRequestProperties;
                            CreateSymbolsFileList">
    <PropertyGroup>
      <SymbolsRequestIniPath>$(SymbolsRequestIntermediateDir)request-$(SymbolsBuildId)-$(SymbolsBuildRemark).ini</SymbolsRequestIniPath>
    </PropertyGroup>

    <WriteLinesToFile File="$(SymbolsRequestIniPath)"
                      Lines="@(RequestProperty)"
                      Overwrite="true" />
  </Target>

  <!--
    Creates RequestProperty items that apply to all requests.
  -->
  <Target Name="AddRequestProperties">
    <PropertyGroup>
      <SymbolsBuildId Condition="'$(SymbolsBuildId)'==''">$(BUILD_DEFINITIONNAME)</SymbolsBuildId>

      <SymbolsBuildRemark Condition="'$(SymbolsBuildRemark)'=='' and
                                     '$(BUILD_BUILDNUMBER)'!='' and
                                     '$(BUILD_BUILDID)'!=''"
                          >$(BUILD_BUILDNUMBER)-$(BUILD_BUILDID)</SymbolsBuildRemark>

      <SymbolsErrorMailOnly Condition="'$(SymbolsErrorMailOnly)'==''">Yes</SymbolsErrorMailOnly>
    </PropertyGroup>

    <Error Text="'SymbolsProject' must be defined." Condition="'$(SymbolsProject)'==''" />
    <Error Text="'SymbolsStatusMail' must be defined." Condition="'$(SymbolsStatusMail)'==''" />
    <Error Text="'SymbolsUserName' must be defined." Condition="'$(SymbolsUserName)'==''" />

    <Error Text="'SymbolsBuildId' must be defined, or fallback 'BUILD_DEFINITIONNAME'" Condition="'$(SymbolsBuildId)'==''" />
    <Error Text="'SymbolsBuildRemark' must be defined, or fallbacks 'BUILD_BUILDNUMBER' and 'BUILD_BUILDID'" Condition="'$(SymbolsBuildRemark)'==''" />

    <ItemGroup>
      <RequestProperty Include="BuildId=$(SymbolsBuildId)" />
      <RequestProperty Include="BuildRemark=$(SymbolsBuildRemark)" />
      <RequestProperty Include="ErrorMailOnly=$(SymbolsErrorMailOnly)" />
      <RequestProperty Include="Project=$(SymbolsProject)" />
      <RequestProperty Include="StatusMail=$(SymbolsStatusMail)" />
      <RequestProperty Include="UserName=$(SymbolsUserName)" />
    </ItemGroup>
  </Target>

  <!--
    Creates RequestProperty items that apply to archive requests.
  -->
  <Target Name="AddArchiveRequestProperties"
          Condition="'$(ArchiveSymbols)'=='true'">
    <PropertyGroup>
      <SymbolsBuild Condition="'$(SymbolsBuild)'==''">$(BUILD_BUILDNUMBER)</SymbolsBuild>
    </PropertyGroup>

    <Error Text="'SymbolsRelease' must be defined." Condition="'$(SymbolsRelease)'==''" />
    <Error Text="'SymbolsProductGroup' must be defined." Condition="'$(SymbolsProductGroup)'==''" />
    <Error Text="'SymbolsProductName' must be defined." Condition="'$(SymbolsProductName)'==''" />

    <Error Text="'SymbolsBuild' must be defined for a symbol archive request, or fallback 'BUILD_BUILDNUMBER'" Condition="'$(SymbolsBuild)'==''" />

    <ItemGroup>
      <RequestProperty Include="Build=$(SymbolsBuild)" />
      <RequestProperty Include="Release=$(SymbolsRelease)" />
      <RequestProperty Include="ProductGroup=$(SymbolsProductGroup)" />
      <RequestProperty Include="ProductName=$(SymbolsProductName)" />
      <RequestProperty Include="SubmitToArchive=ALL" />
      <RequestProperty Include="SubmitToInternet=Yes" />
    </ItemGroup>
  </Target>

  <!--
    Creates the list of every symbol file to submit to the symbol server.

    Creates RequestProperties:
    * FileList: the newline-separated list of paths on disk or in a share to submit.
    * PrefixToStrip: specifies the path prefix of all paths in the request. The symbol server
        replaces 'PrefixToStrip' with 'UNCPath' when calculating the share location of each file.
  -->
  <Target Name="CreateSymbolsFileList"
          DependsOnTargets="GetAllSymbolFilesToPublish;
                            PublishSymbolFilesToFileShare">
    <PropertyGroup>
      <SymbolFileListPath>$(SymbolsRequestIntermediateDir)SymbolFileList.txt</SymbolFileListPath>
    </PropertyGroup>

    <ItemGroup>
      <RequestProperty Include="FileList=$(SymbolFileListPath)" />
      <RequestProperty Include="PrefixToStrip=$(SymbolPackageExtractDir)" />
    </ItemGroup>

    <MakeDir Directories="$(SymbolsRequestIntermediateDir)"
             Condition="!Exists('$(SymbolsRequestIntermediateDir)')" />

    <WriteLinesToFile File="$(SymbolFileListPath)"
                      Lines="@(SymbolFileToPublish -> '%(FullPath)')"
                      Overwrite="true" />
  </Target>

  <!--
    Gets every symbol file to publish onto disk and creates SymbolFileToPublish items.
  -->
  <Target Name="GetAllSymbolFilesToPublish"
          DependsOnTargets="UnzipSymbolPackagesForPublish;
                            GenerateAdditionalSymbolsForArchive" />

  <!--
    Copies symbols to a directory based on the given search glob path. The target should be a file
    share that the symbol server can access when servicing the request.

    Creates RequestProperty:
    * UNCPath: path of the file share root.
  -->
  <Target Name="PublishSymbolFilesToFileShare"
          DependsOnTargets="UnzipSymbolPackagesForPublish">
    <Error Text="'SymbolPublishDestinationDir' must be defined." Condition="'$(SymbolPublishDestinationDir)'==''" />

    <ItemGroup>
      <RequestProperty Include="UNCPath=$(SymbolPublishDestinationDir)" />
    </ItemGroup>

    <PropertyGroup>
      <SymbolPublishCopyRetries Condition="'$(SymbolPublishCopyRetries)'==''">5</SymbolPublishCopyRetries>
    </PropertyGroup>

    <ItemGroup Condition="'$(SymbolFileSearchGlob)'!=''">
      <SymbolFileToPublish Include="$(SymbolFileSearchGlob)" />
    </ItemGroup>

    <ItemGroup>
      <SymbolFileToPublish>
        <DestinationPath>$(SymbolPublishDestinationDir)%(RecursiveDir)%(Filename)%(Extension)</DestinationPath>
      </SymbolFileToPublish>
    </ItemGroup>

    <Error Text="No symbol files found to publish. No existing 'PublishedSymbolsFile' items, and glob '$(SymbolFileSearchGlob)' found no items."
           Condition="'@(SymbolFileToPublish)'==''" />

    <Message Text="Publishing files to '$(SymbolPublishDestinationDir)'."
             Importance="low" />

    <MakeDir Directories="$(SymbolPublishDestinationDir)"
             Condition="!Exists('$(SymbolPublishDestinationDir)')" />

    <Message Text="[$([System.DateTime]::Now.ToString('HH:mm:ss.ff'))] Copying symbols to publish dir..." Importance="High"/>

    <Copy SourceFiles="@(SymbolFileToPublish)"
          DestinationFiles="@(SymbolFileToPublish -> '%(DestinationPath)')"
          Retries="$(SymbolPublishCopyRetries)">
      <Output TaskParameter="CopiedFiles" ItemName="PublishedSymbolFile"/>
    </Copy>

    <Message Text="[$([System.DateTime]::Now.ToString('HH:mm:ss.ff'))] Copying symbols to publish dir... Done." Importance="High"/>
  </Target>

  <!--
    Unzips a set of symbol packages so the symbols inside can be archived/indexed.
    
    Depends on:
    * AddRequestProperties: uses SymbolsBuildId and SymbolsBuildRemark to create a distinct extract
        folder for each request.
  -->
  <Target Name="UnzipSymbolPackagesForPublish"
          DependsOnTargets="AddRequestProperties"
          Condition="'$(SymbolPackagesToPublishGlob)'!=''">
    <PropertyGroup>
      <SymbolPackageExtractDir>$(SymbolsRequestIntermediateDir)ExtractedPackages\</SymbolPackageExtractDir>
    </PropertyGroup>

    <ItemGroup>
      <SymbolPackageFile Include="$(SymbolPackagesToPublishGlob)" />
    </ItemGroup>

    <!-- Extract to the index of the symbol package, not file name, to avoid exceeding max path. -->
    <AddItemIndices Input="@(SymbolPackageFile)">
      <Output TaskParameter="Output" ItemName="SymbolPackageFileWithIndex" />
    </AddItemIndices>

    <RemoveDir Directories="$(SymbolPackageExtractDir)"
               Condition="Exists('$(SymbolPackageExtractDir)')" />

    <Message Text="[$([System.DateTime]::Now.ToString('HH:mm:ss.ff'))] Extracting symbol packages..." Importance="High"/>

    <ZipFileExtractToDirectory SourceArchive="%(Identity)"
                               DestinationDirectory="@(SymbolPackageFileWithIndex -> '$(SymbolPackageExtractDir)%(Index)')"
                               OverwriteDestination="true" />

    <Message Text="[$([System.DateTime]::Now.ToString('HH:mm:ss.ff'))] Extracting symbol packages... Done." Importance="High"/>

    <ItemGroup>
      <IndexedExtensions Include=".dll;.pdb;.exe" Condition="'@(IndexedExtensions)'==''" />
      <SymbolFileToPublish Include="$(SymbolPackageExtractDir)**\*%(IndexedExtensions.Identity)" />
    </ItemGroup>
  </Target>

  <!-- Generate any extra symbols (e.g. Windows PDBs) that also need to be archived. -->
  <Target Name="GenerateAdditionalSymbolsForArchive"
          DependsOnTargets="SetupCreateWindowsPdbsFromPortablePdbs;
                            CreateWindowsPdbsFromPortablePdbs;
                            AddConvertedWindowsPdbsToPublishList" />

  <!--
    Set up properties for CreateWindowsPdbsFromPortablePdbs that will generate Windows PDBs for
    files in the unzipped symbol packages.
  -->
  <Target Name="SetupCreateWindowsPdbsFromPortablePdbs">
    <PropertyGroup>
      <PortablePdbToConvertGlob>$(SymbolPackageExtractDir)**\*.pdb</PortablePdbToConvertGlob>
      <WindowsPdbConversionTargetPath>$(SymbolPackageExtractDir)WindowsPDB\</WindowsPdbConversionTargetPath>
    </PropertyGroup>
  </Target>

  <!--
    Add Windows PDBs created in CreateWindowsPdbsFromPortablePdbs to the list of symbol files to
    publish. We need to wait until after the conversion: only Portable PDBs will be converted, and
    we can't tell beforehand which PDBs are portable.
  -->
  <Target Name="AddConvertedWindowsPdbsToPublishList">
    <ItemGroup>
      <SymbolFileToPublish Include="$(WindowsPdbConversionTargetPath)**\*.pdb" />
    </ItemGroup>
  </Target>

  <!-- This will be overridden if we're building with MicroBuild. -->
  <Target Name="SignFiles">
    <Message Text="Fake sign target.  Would sign: @(FilesToSign)" />
  </Target>

  <!--
    Entry point: inject signed symbol catalogs into all specified symbol packages.
  -->
  <Target Name="InjectSignedSymbolCatalogIntoSymbolPackages"
          DependsOnTargets="GenerateSymbolCatalogs;
                            SignFiles">
    <MSBuild Targets="InjectSignedSymbolCatalogsForPackage"
             Projects="$(MSBuildProjectFile)"
             Properties="SymbolPackageFilePath=%(SymbolPackageFileWithIndex.Identity);
                         SymbolPackageIndex=%(SymbolPackageFileWithIndex.Index);
                         SymbolCatalogIntermediateDir=$(SymbolCatalogIntermediateDir);"
             Condition="'@(SymbolPackageFileWithIndex)'!=''" />
  </Target>

  <!--
    Generate symbol catalogs for all symbol packages specified. Creates FilesToSign items for each
    catalog so they can be sigend.
  -->
  <Target Name="GenerateSymbolCatalogs">
    <PropertyGroup>
      <SymbolCatalogIntermediateDir Condition="'$(SymbolCatalogIntermediateDir)' == ''">$(BaseIntermediateOutputPath)SymbolsCatalog\</SymbolCatalogIntermediateDir>
      <ExtensionsToCatalog Condition="'$(ExtensionsToCatalog)'==''">.dll;.pdb;.a;.so;.dbg;.dylib;.dwarf</ExtensionsToCatalog>
      <MakeCatCommand Condition="'$(MakeCatCommand)'==''">makecat -v</MakeCatCommand>
    </PropertyGroup>

    <ItemGroup>
      <SymbolPackageFile Include="$(SymbolPackagesToPublishGlob)" />
    </ItemGroup>

    <!-- Extract to the index of the symbol package, not file name, to avoid exceeding max path. -->
    <AddItemIndices Input="@(SymbolPackageFile)">
      <Output TaskParameter="Output" ItemName="SymbolPackageFileWithIndex" />
    </AddItemIndices>

    <RemoveDir Directories="$(SymbolCatalogIntermediateDir)"
               Condition="Exists('$(SymbolCatalogIntermediateDir)')" />

    <MSBuild Targets="GenerateSymbolCatalogsForPackage"
             Projects="$(MSBuildProjectFile)"
             Properties="SymbolPackageFilePath=%(SymbolPackageFileWithIndex.Identity);
                         SymbolPackageIndex=%(SymbolPackageFileWithIndex.Index);
                         ExtensionsToCatalog=$(ExtensionsToCatalog);
                         SymbolCatalogIntermediateDir=$(SymbolCatalogIntermediateDir);
                         MakeCatCommand=$(MakeCatCommand)" />

    <!-- Find the catalogs to sign them. -->
    <Error Text="'SymbolCatalogCertificateId' must be defined."
           Condition="'$(SymbolCatalogCertificateId)'==''" />

    <ItemGroup>
      <FilesToSign Include="$(SymbolCatalogIntermediateDir)**\*.cat">
        <Authenticode>$(SymbolCatalogCertificateId)</Authenticode>
      </FilesToSign>
    </ItemGroup>

    <PropertyGroup>
      <!--
        The OutDir and IntermediateOutputPath properties are required by MicroBuild. MicroBuild only
        signs files that are under these paths.
      -->
      <OutDir Condition="'$(OutDir)'==''">$(BinDir)</OutDir>
      <!-- always overwrite IntermediateOutputPath so we know we can sign the catalogs.
           some repos do weird things with BaseIntermediateOutputPath -->
      <IntermediateOutputPath>$(SymbolCatalogIntermediateDir)</IntermediateOutputPath>
    </PropertyGroup>
  </Target>

  <!--
    Generates any catalogs needed to sign a specific symbol package.
  -->
  <Target Name="GenerateSymbolCatalogsForPackage">
    <ItemGroup>
      <!-- Create a single item for the symbol package to get metadata about it. -->
      <SymbolPackageFile Include="$(SymbolPackageFilePath)" />
      <ExtensionToCatalog Include="$(ExtensionsToCatalog)" />
    </ItemGroup>

    <PropertyGroup>
      <ExtractBaseDir>$(SymbolCatalogIntermediateDir)$(SymbolPackageIndex)\</ExtractBaseDir>
    </PropertyGroup>

    <ZipFileGetEntries TargetArchive="$(SymbolPackageFilePath)">
      <Output TaskParameter="Entries" ItemName="SymbolPackageEntry" />
    </ZipFileGetEntries>

    <ItemGroup>
      <!-- files like _.pdb and _._ are empty and makecat doesn't handle empty files -->
      <FilteredSymbolPackageEntries Include="@(SymbolPackageEntry)" Condition="'%(Filename)' != '_'" />
      <SymbolPackageEntryCrossExtensionsToCatalog Include="@(FilteredSymbolPackageEntries)">
        <AttemptMatchExtension>%(ExtensionToCatalog.Identity)</AttemptMatchExtension>
      </SymbolPackageEntryCrossExtensionsToCatalog>

      <SymbolFileToCatalog Include="@(SymbolPackageEntryCrossExtensionsToCatalog)"
                           Condition="'%(Extension)'=='%(AttemptMatchExtension)'" />
    </ItemGroup>


    <ZipFileExtractToDirectory SourceArchive="$(SymbolPackageFilePath)"
                               Include="@(SymbolFileToCatalog)"
                               DestinationDirectory="$(ExtractBaseDir)"
                               Condition="'@(SymbolFileToCatalog)'!=''" />

    <!-- Leave a marker so a package can be associated with its numbered dir. -->
    <WriteLinesToFile File="$(SymbolCatalogIntermediateDir)%(SymbolPackageFile.Filename)"
                      Lines="$(SymbolPackageIndex)"
                      Overwrite="true"
                      Condition="'@(SymbolFileToCatalog)'!=''" />

    <!-- Create the catalog file to feed into makecat. -->
    <PropertyGroup>
      <CatalogFileHeaderLines>
        [CatalogHeader];
        Name=signatures.cat;
        ResultDir=.\;
        PublicVersion=2;
        CatalogVersion=2;
        HashAlgorithms=SHA256;
        PageHashes=false;
        EncodingType=0x00010001;
        CATATTR1=0x10010001:OSAttr:2:6.4;
        [CatalogFiles]
      </CatalogFileHeaderLines>
    </PropertyGroup>

    <ItemGroup>
      <CatalogFile Include="$(ExtractBaseDir)signatures.cat.cdf"
                   Condition="'@(SymbolFileToCatalog)'!=''">
        <ContentLines>$(CatalogFileHeaderLines);@(SymbolFileToCatalog -> '&lt;HASH&gt;%(Identity)=%(Identity);&lt;HASH&gt;%(Identity)ATTR1=0x11010001:Filename:%(Identity);')</ContentLines>
      </CatalogFile>
    </ItemGroup>

    <Message Text="@(CatalogFile)" />

    <WriteLinesToFile File="@(CatalogFile)"
                      Lines="%(ContentLines)"
                      Condition="'@(CatalogFile)'!=''" />

    <!-- Create an easily readable list of files that were cataloged. -->
    <WriteLinesToFile File="$(ExtractBaseDir)cataloged.txt"
                      Lines="@(SymbolFileToCatalog)"
                      Condition="'@(CatalogFile)'!=''" />

    <!--
      Don't include cdf path because it may be too long for makecat.
      See https://stackoverflow.com/a/18682676
    -->
    <Exec Command="$(MakeCatCommand) %(CatalogFile.Filename)%(CatalogFile.Extension)"
          WorkingDirectory="%(CatalogFile.RootDir)%(CatalogFile.Directory)"
          Condition="'@(CatalogFile)'!=''" />
  </Target>

  <!--
    Inject all symbol catalogs found in the extracted symbol package dir into a new copy of the
    symbol package in the intermediate dir.
  -->
  <Target Name="InjectSignedSymbolCatalogsForPackage">
    <ItemGroup>
      <CatalogFile Include="$(SymbolCatalogIntermediateDir)$(SymbolPackageIndex)\**\*.cat" />
      <CatalogFile>
        <ArchivePath>%(RecursiveDir)%(Filename)%(Extension)</ArchivePath>
      </CatalogFile>

      <CatalogedFileManifest Include="$(SymbolCatalogIntermediateDir)$(SymbolPackageIndex)\cataloged.txt">
        <ArchivePath>cataloged.txt</ArchivePath>
      </CatalogedFileManifest>
    </ItemGroup>

    <ZipFileInjectFile TargetArchive="$(SymbolPackageFilePath)"
                       InjectFiles="@(CatalogFile);@(CatalogedFileManifest)"
                       Condition="'@(CatalogFile)'!=''" />
  </Target>

  <!--
    Generates Windows PDBs from Portable PDBs. PDBs passed that are not Portable are skipped.

    [In]
    $(PortablePdbToConvertGlob)
      * A path glob that matches all portable PDBs to convert. Conversion requires the DLL, and this
        option assumes that the DLL is in the same directory with the same filename.
    $(WindowsPdbConversionTargetPath)
      * Location to place converted PDBs when using PortablePdbToConvertGlob. Recursive directory
        is preserved, if one is present in the glob.
    @(ConversionOptions) [optional]
      * ItemSpec: An entry in the Microsoft.DiaSymReader.Tools.PdbConversionOptions flags enum to
        use for all conversions performed.
  -->
  <Target Name="CreateWindowsPdbsFromPortablePdbs"
          Condition="'$(SkipCreateWindowsPdbsFromPortablePdbs)'!='true'">

    <!-- Early exit for unsupported scenario. See https://github.com/dotnet/buildtools/issues/1607 -->
    <Error Text="BuildTools does not support Portable PDB conversion to Windows PDB in .NET Core. Run msbuild using the desktop framework."
           Condition="'$(MSBuildRuntimeType)'=='core'" />

    <!-- Find DLL/PDB pairs based on the optional path glob given. -->
    <ItemGroup Condition="'$(PortablePdbToConvertGlob)'!=''">
      <_PdbCandidate Include="$(PortablePdbToConvertGlob)" />

      <_ToPublishDllCandidate Include="@(_PdbCandidate -> '%(RootDir)%(Directory)%(Filename).dll')">
        <PdbPath>%(Identity)</PdbPath>
        <PdbRecursiveDir>%(RecursiveDir)</PdbRecursiveDir>
      </_ToPublishDllCandidate>

      <!--
        If this target becomes reliable, we could start calling directly in msbuild and define these
        items as parameters:
        
        @(PortableFileToConvert)
          * ItemSpec: The DLL file associated with a Portable PDB to convert.
          * PdbPath: The path to the Portable PDB file to convert.
          * TargetPath: The output path for the generated Windows PDB. Full filename.
      -->
      <PortableFileToConvert Include="@(_ToPublishDllCandidate)"
                             Condition="Exists('%(Identity)')">
        <TargetPath>$(WindowsPdbConversionTargetPath)%(PdbRecursiveDir)%(Filename).pdb</TargetPath>
      </PortableFileToConvert>
    </ItemGroup>

    <ConvertPortablePdbsToWindowsPdbs Files="@(PortableFileToConvert)"
                                      ConversionOptions="@(ConversionOptions)" />
  </Target>

  <!--
    Downloads and unzip symbol packages passed in as an ItemGroup

    [In]
    @(SymbolPackagesToDownload)
      * An ItemGroup with the following characteristics.
        * ItemSpec: The name of the package. (Not used within the target)
        * Url [Required]: The url endpoint to download the package from.
        * DestinationFile [Required]: The file name where the source will be downloaded to, must be a zip. i.e NugetId.symbols.nupkg.zip
        * UnzipDestinationDir [Required]: This is the directory where the package contents will be extracted to.
    $(SymbolPackagesDir) [optional]
      * Location where the packages are going to be downloaded to as a cache. If empty, then will be the working directory.

    [Out]
    @(SymbolPackagesDownloaded)
      * ItemSpec: The full path to the downloaded file.
      Includes the same metadata that was passed in @(SymbolPackagesToDownload)
      If this itemgroup is empty the task failed to download the files.
  -->
  <Target Name="DownloadAndUnzipSymbolPackage"
          Condition="'@(SymbolPackagesToDownload)' != ''">

      <DownloadFilesFromUrl Items="@(SymbolPackagesToDownload)"
                           DestinationDir="$(SymbolPackagesDir)"
                           TreatErrorsAsWarnings="true">
        <Output TaskParameter="FilesCreated" ItemName="SymbolPackagesDownloaded" />
      </DownloadFilesFromUrl>

      <ZipFileExtractToDirectory SourceArchive="%(SymbolPackagesDownloaded.Identity)"
                                 DestinationDirectory="%(SymbolPackagesDownloaded.UnzipDestinationDir)"
                                 OverwriteDestination="true"
                                 Condition="'@(SymbolPackagesDownloaded)' != ''" />
  </Target>

</Project>