Fix unset ZapRelocationType for fixup (#18589)
[platform/upstream/coreclr.git] / Tools / CloudTest.Helix.targets
1 <Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2
3   <PropertyGroup>
4     <!-- For the separate package scenario (not BuildTools) we need to pick up the right version of the assembly.  -->
5     <!-- This will only work on versions of MSBuild that supply MSBuildRuntimeType (defaults to core in this case. -->
6     <BuildToolsTaskDir Condition="'$(BuildToolsTaskDir)'=='' And '$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)</BuildToolsTaskDir>
7     <BuildToolsTaskDir Condition="'$(BuildToolsTaskDir)'=='' And '$(MSBuildRuntimeType)' == 'Full'">$(MSBuildThisFileDirectory)\desktop\</BuildToolsTaskDir>
8   </PropertyGroup>
9
10   <UsingTask TaskName="CreateAzureContainer"             AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
11   <UsingTask TaskName="RemoveItemMetadata"               AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
12   <UsingTask TaskName="SendJobsToHelix"                  AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
13   <UsingTask TaskName="UploadToAzure"                    AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
14   <UsingTask TaskName="ValidateWorkItems"                AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
15   <UsingTask TaskName="WriteItemsToJson"                 AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
16   <UsingTask TaskName="WriteTestBuildStatsJson"          AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
17   <UsingTask TaskName="ZipFileCreateFromDirectory"       AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
18   <UsingTask TaskName="ZipFileCreateFromDependencyLists" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
19
20   <!-- See Documentation\Samples\CloudTest.Helix.targets.sampleproject for a sample project.
21
22        Some helpful Helix environment variables: (Use appropriate-for-OS means to access, i.e. %WINDOWS% or $Linux, $OSX )
23        **********************************************************************************************************************************
24         HELIX_WORKITEM_PAYLOAD      - Execution folder of helix workitem, current directory when using relative path.
25         HELIX_CORRELATION_ID        - GUID identifier for a helix run.
26         HELIX_PYTHONPATH            - Path to python executable
27         HELIX_CORRELATION_PAYLOAD   - Correlation payload folder;  root of where all correlation payloads are unzipped.
28         HELIX_WORKITEM_FRIENDLYNAME - Friendly name of work item
29        **********************************************************************************************************************************
30
31        Variables that CloudTest.Helix.Targets cares about:
32        **********************************************************************************************************************************
33        Required Properties:
34        **********************************************************************************************************************************
35        HelixJobType                 - Job Type formatting string, used for sorting and display of jobs.
36        HelixSource                  - Job Source formatting string, used for sorting and display of jobs.
37        TargetQueues                 - Queue(s) to send Jobs to. (TODO: Need discoverability for users)
38        BuildMoniker                 - Identifying name for build when sending to Helix.  Used for labelling runs.
39        CloudDropConnectionString    - Azure Storage account connection string used for payloads.
40        CloudResultsConnectionString - Azure Storage account connection string used for results.
41        ArchivesRoot                 - Relative path for work item payloads to use.  Blob Uris will be made relative to this.
42        
43        **********************************************************************************************************************************
44        Optional Properties:
45        **********************************************************************************************************************************
46        TestListFilename  - File name to use for test list sent to Helix.                      Default: TestList.json
47        OverwriteOnUpload - Whether to overwrite blobs if they already exist on upload.        Default: False
48        ContainerName     - Container name for uploaded files.                                 Default: build-<Random GUID>. 
49        IsOfficial        - Boolean flag for display on Mission Control.                       Default: False:
50        HelixApiEndpoint  - Endpoint to send jobs to.                                          Default: https://helix.dot.net/api/2016-06-28/jobs
51        CloudResultsReadTokenValidDays  - Days that result URLs produced will be readble       Default: 30
52        CloudResultsWriteTokenValidDays - Days that result URLs produced will be writeable     Default: 4
53        SupplementalPayloadDir - Temp (deletable) dir for assembling supplemental payloads     Default: Random folder in %TEMP%
54        HelixApiAccessKey            - GitHub API Access Key for sending jobs to Helix.  Get from https://helix.dot.net/UserProfile/Index/
55        HelixLogFolder    - Folder for storing JSON files describing job start and other info  Default: <Blank>
56        HelixCorrelationInfoFileName - JSON file containing info on started jobs               Default: Helix-correlation-infos.json
57        HelixJobProperties- Must be JSON.  String describing Helix MC-specific metadata.       Default: <Blank>
58        HelixArchLabel    - If HelixJobProperties is not set, we'll use this to fill it out    Default: <Blank>
59        HelixConfigLabel  - If HelixJobProperties is not set, we'll use this to fill it out    Default: <Blank>
60        MaxRetryCount     - Max automatic retry of workitems which do not return 0             Default: 0 (no retry)
61        HelixAttempt      - A lexically monotonic increasing string distinguishing             Default: <Blank>
62                            jobs whose results should override previous executions.
63
64        **********************************************************************************************************************************
65        Re-queuing Properties:
66        **********************************************************************************************************************************
67        UseContinuationRunner  - Use runner script that is capable of sending to another queue when finished  Default: False
68        SecondaryQueue         - When submitting multi-stage tests, queue for secondary jobs                  Default: <Blank>
69        SecondarySasValidHours - SAS Valid time for token provided for multi-stage tests.                     Default: 15.0
70        SecondaryPayloadDir    - Folder to make into workitem payload for continued execution                 Default: <Blank>
71
72        **********************************************************************************************************************************
73        Required ITaskItems:
74        **********************************************************************************************************************************
75        HelixWorkItem         - Zip files to be uploaded as payloads. 
76        
77        **********************************************************************************************************************************
78        Optional ITaskItems:
79        **********************************************************************************************************************************
80        HelixCorrelationPayloadFile - Zip files to be uploaded as correlation payloads (shared by all work items, cached where possible)       
81     -->
82
83   <!-- Main entry point -->
84   <Target Name="HelixCloudBuild" DependsOnTargets="VerifyInputs;PreCloudBuild;ValidateWorkItems;UploadContent;CreateTestListJson" />
85
86   <PropertyGroup>
87     <OverwriteOnUpload Condition="'$(OverwriteOnUpload)' == ''">false</OverwriteOnUpload>
88     <ContainerName Condition="'$(ContainerName)'== ''">build-$([System.Guid]::NewGuid().ToString("N"))</ContainerName>
89     <ContainerName>$(ContainerName.ToLower())</ContainerName>
90     <TestListFilename Condition="'$(TestListFilename)'==''">TestList.json</TestListFilename>
91     <SupplementalPayloadDir Condition="'$(SupplementalPayloadDir)' == ''">$([System.IO.Path]::GetTempPath())$([System.IO.Path]::GetRandomFileName())/SupplementalPayload/</SupplementalPayloadDir>
92     <SupplementalPayloadFilename>SupplementalPayload.zip</SupplementalPayloadFilename>
93     <SupplementalPayloadFile>$(ArchivesRoot)$(SupplementalPayloadFilename)</SupplementalPayloadFile>
94     <IsOfficial Condition="'$(IsOfficial)'!=''">false</IsOfficial>
95     <HelixApiEndpoint Condition="'$(HelixApiEndpoint)'==''">https://helix.dot.net/api/2016-06-28/jobs</HelixApiEndpoint>
96     <MaxRetryCount Condition="'$(MaxRetryCount)' == ''">0</MaxRetryCount>
97   </PropertyGroup>
98
99   <!-- Set Helix environment vars based on target platform -->
100   <!-- This is only used in the case where property 'UseScriptRunner' is true.-->
101   <PropertyGroup Condition="'$(TargetsWindows)' == 'true' AND '$(UseScriptRunner)' == 'true' ">
102     <HelixPythonPath>%HELIX_PYTHONPATH%</HelixPythonPath>
103     <HelixScriptRoot>%HELIX_SCRIPT_ROOT%\</HelixScriptRoot>
104     <RunnerScript>%HELIX_CORRELATION_PAYLOAD%\RunnerScripts\scriptrunner\scriptrunner.py</RunnerScript>
105   </PropertyGroup>
106   <PropertyGroup Condition="'$(TargetsWindows)' == 'true' AND '$(UseContinuationRunner)' == 'true' ">
107     <RunnerScript>%HELIX_CORRELATION_PAYLOAD%\RunnerScripts\scriptrunner\continuationrunner.py --next_queue $(SecondaryQueue) --next_payload_dir $(SecondaryPayloadDir)</RunnerScript>
108     <SecondarySasValidHours Condition="'$(SecondarySasValidHours)' == ''">15.0</SecondarySasValidHours>
109   </PropertyGroup>
110   <PropertyGroup Condition="'$(TargetsWindows)' != 'true' AND '$(UseScriptRunner)' == 'true' ">
111     <HelixPythonPath>$HELIX_PYTHONPATH</HelixPythonPath>
112     <HelixScriptRoot>$HELIX_SCRIPT_ROOT/</HelixScriptRoot>
113     <RunnerScript>$HELIX_CORRELATION_PAYLOAD/RunnerScripts/scriptrunner/scriptrunner.py</RunnerScript>
114   </PropertyGroup>
115
116   <Target Name="VerifyInputs">
117     <!-- Verify all required properties have been specified.  Update the comment above if you update this list! -->
118     <Error Condition="'$(ArchivesRoot)' == ''"                                         Text="Missing required property ArchivesRoot." />
119     <Error Condition="'$(HelixJobType)' == ''"                                         Text="Missing required property HelixJobType." />
120     <Error Condition="'$(HelixSource)' == ''"                                          Text="Missing required property HelixSource." />
121     <Error Condition="'$(TargetQueues)' == ''"                                         Text="Missing required property TargetQueues." />
122     <Error Condition="'$(BuildMoniker)' == ''"                                         Text="Missing required property BuildMoniker." />
123     <Error Condition="'$(CloudDropConnectionString)' == ''"                            Text="Missing required property CloudDropConnectionString." />
124     <Error Condition="'$(CloudResultsConnectionString)' == ''"                         Text="Missing required property CloudResultsConnectionString." />
125     <Error Condition="'$(HelixJobProperties)' == '' and ('$(HelixArchLabel)' == '' or '$(HelixConfigLabel)' == '')"
126                                                                                        Text="HelixJobProperties (JSON), or HelixArchLabel and HelixConfigLabel (string) must be set to start Helix jobs" />
127   </Target>
128
129   <!-- Provided as an extensibility point for targets to run before the real work begins -->
130   <Target Name="PreCloudBuild">
131
132     <!-- Copy runner scripts so they can be uploaded as supplemental payload -->
133     <ItemGroup>
134       <RunnerScripts Include="$(ToolsDir)RunnerScripts/**/*.py" />
135       <RunnerScripts Include="$(ToolsDir)RunnerScripts/**/*.sh" />
136       <RunnerScripts Include="$(ToolsDir)RunnerScripts/**/*.txt" />
137     </ItemGroup>
138
139     <!-- Split up Target Queues list 
140          In order to support all delimiters (commas, plus and semicolons), I do a little hoop-jumping to do a replace first
141          Note that queues will never have ',', '+' or ';' in their name, and this greatly simplifies providing a queue list in *nix.
142     -->
143     <PropertyGroup>
144       <ProcessedTargetQueues>$([System.String]::Copy('$(TargetQueues)').Replace(',',';').Replace('+',';'))</ProcessedTargetQueues>  
145     </PropertyGroup>
146
147     <ItemGroup>
148       <!-- This Split() is needed since the semicolon in ProcessedTargetQueues is now a literal -->
149       <TargetQueue Include="$(ProcessedTargetQueues.Split(';'))" />
150     </ItemGroup>
151     <Message Text="Will Enqueue to @(TargetQueue->Count()) Queue(s) : @(TargetQueue)" />
152
153     <!-- Compress the supplemental payload directory for upload -->
154     <!-- We might not have RunnerScripts, so skip the tasks in that case -->
155     <MakeDir Directories="$(SupplementalPayloadDir)"/>
156     <Copy Condition="'@(RunnerScripts->Count())'!='0'"
157           SourceFiles="@(RunnerScripts)"
158           DestinationFiles="@(RunnerScripts->'$(SupplementalPayloadDir)RunnerScripts/%(RecursiveDir)%(Filename)%(Extension)')"
159           SkipUnchangedFiles="true" />
160     <ZipFileCreateFromDirectory Condition="'@(RunnerScripts->Count())'!='0'"
161         SourceDirectory="$(SupplementalPayloadDir)"
162         DestinationArchive="$(SupplementalPayloadFile)"
163         OverwriteDestination="true" />
164     <RemoveDir Directories="$(SupplementalPayloadDir)"/>
165     <ItemGroup>
166       <SupplementalPayload Condition="'@(RunnerScripts->Count())'!='0'" Include="$(SupplementalPayloadFile)">
167         <RelativeBlobPath>$(SupplementalPayloadFilename)</RelativeBlobPath>
168       </SupplementalPayload>
169     </ItemGroup>
170   </Target>
171
172   <!-- Make sure the work items included contain all the required fields, calculate relative blob paths -->
173   <Target Name="ValidateWorkItems">
174     <ValidateWorkItems WorkItems="@(HelixWorkItem)" WorkItemArchiveRoot="$(ArchivesRoot)">
175       <Output TaskParameter="ProcessedWorkItems" ItemName="HelixProcessedWorkItem"/>
176     </ValidateWorkItems>
177   </Target>
178
179   <!-- Create Azure containers and file shares -->
180   <Target Name="CreateAzureStorage">
181     <CreateAzureContainer
182       ConnectionString="$(CloudDropConnectionString)"
183       ContainerName="$(ContainerName)"
184       ReadOnlyTokenDaysValid="30">
185       <Output TaskParameter="StorageUri" PropertyName="DropUri" />
186       <Output TaskParameter="ReadOnlyToken" PropertyName="DropUriReadOnlyToken" />
187     </CreateAzureContainer>
188
189     <PropertyGroup>
190       <CloudResultsReadTokenValidDays  Condition="'$(CloudResultsReadTokenValidDays)' == ''">30</CloudResultsReadTokenValidDays>
191       <CloudResultsWriteTokenValidDays Condition="'$(CloudResultsWriteTokenValidDays)' == ''">4</CloudResultsWriteTokenValidDays>
192     </PropertyGroup>
193
194     <CreateAzureContainer
195       ConnectionString="$(CloudResultsConnectionString)"
196       ContainerName="$(ContainerName)"
197       ReadOnlyTokenDaysValid ="$(CloudResultsReadTokenValidDays)"
198       WriteOnlyTokenDaysValid="$(CloudResultsWriteTokenValidDays)">
199
200       <Output TaskParameter="StorageUri"     PropertyName="ResultsUri" />
201       <Output TaskParameter="ReadOnlyToken"  PropertyName="ResultsReadOnlyToken" />
202       <Output TaskParameter="WriteOnlyToken" PropertyName="ResultsWriteOnlyToken" />
203     </CreateAzureContainer>
204   </Target>
205
206   <!-- Upload content to Azure (Everything except test list) -->
207   <Target Name="UploadContent" DependsOnTargets="CreateAzureStorage">
208     <ItemGroup>
209       <LocalFileForUpload Include="@(HelixProcessedWorkItem->Metadata('PayloadFile'))">
210         <RelativeBlobPath>%(RelativeBlobPath)</RelativeBlobPath>
211       </LocalFileForUpload>
212       <!-- For now assume these all go in the root of the container -->
213       <LocalPayloadFileForUpload Include="@(HelixCorrelationPayloadFile)">
214         <RelativeBlobPath>%(FileName)%(Extension)</RelativeBlobPath>
215       </LocalPayloadFileForUpload>
216     </ItemGroup>
217
218     <!-- Debug output of work items, and a warning if there are none. -->
219     <Message Text="Files for upload :: @(LocalFileForUpload)" Importance="Low" />
220
221     <!-- Verify the test archives were created -->
222     <Warning Condition="'@(LocalFileForUpload->Count())' == '0'" Text="Didn't find any archives in supplied HelixWorkItem(s)." />
223
224     <!-- Work Item payloads -->
225     <UploadToAzure
226       Condition="'@(LocalFileForUpload->Count())' != '0'"
227       ConnectionString="$(CloudDropConnectionString)"
228       ContainerName="$(ContainerName)"
229       Items="@(LocalFileForUpload->Distinct())"
230       Overwrite="$(OverwriteOnUpload)" />
231
232     <!-- Correlation payload(s) -->
233     <UploadToAzure
234       Condition="'@(LocalPayloadFileForUpload->Count())' != '0'"
235       ConnectionString="$(CloudDropConnectionString)"
236       ContainerName="$(ContainerName)"
237       Items="@(LocalPayloadFileForUpload->Distinct())"
238       Overwrite="$(OverwriteOnUpload)" />
239
240     <!-- Supplemental payload.  TODO: This could be combined with Correlation Payload items, there's not much(any?) difference -->
241     <UploadToAzure
242       ConnectionString="$(CloudDropConnectionString)"
243       ContainerName="$(ContainerName)"
244       Items="@(SupplementalPayload)"
245       Overwrite="$(OverwriteOnUpload)"
246       Condition="'@(SupplementalPayload)' != ''" />
247
248   </Target>
249
250   <Target Name="CreateTestListJson">
251     <!-- We always bring the supplemental payload folder.  
252          This contains whatever's in src\Microsoft.DotNet.Build.CloudTestTasks\RunnerScripts,
253          but can also contain various other stuff.  
254          -->
255     <ItemGroup>
256       <CorrelationPayloadUri Include="@(SupplementalPayload->'$(DropUri)%(RelativeBlobPath)$(DropUriReadOnlyToken)')" />
257       <CorrelationPayloadUri Include="@(LocalPayloadFileForUpload->'$(DropUri)%(RelativeBlobPath)$(DropUriReadOnlyToken)')" />
258     </ItemGroup>
259
260     <PropertyGroup>
261       <!-- Flatten it into a property as msbuild chokes on @(CorrelationPayloadUri) in CorrelationPayloadUris -->
262       <CorrelationPayloadUris>@(CorrelationPayloadUri)</CorrelationPayloadUris>
263       <SecondaryQueuesJson Condition="'$(SecondaryQueue)' != ''">{ &quot;QueueId&quot;:&quot;$(SecondaryQueue)&quot;,&quot;SasValidHours&quot;: $(SecondarySasValidHours), &quot;EnqueueSAS&quot; : null, &quot;DropContainerUri&quot; : null,&quot;DropContainerRsas&quot; : null,&quot;DropContainerWsas&quot; : null }</SecondaryQueuesJson>
264     </PropertyGroup>
265
266     <ItemGroup>
267       <HelixProcessedWorkItem>
268         <Command Condition="'$(UseScriptRunner)'!='true'">%(Command)</Command>
269         <!-- When UseScriptRunner is set, we'll wrap commands provided with special Helix-y goo for telemetry-->
270         <Command Condition="'$(UseScriptRunner)'=='true' AND '$(TargetsWindows)' == 'true'">$(HelixPythonPath) $(RunnerScript) --script %(Command)</Command>
271         <Command Condition="'$(UseScriptRunner)'=='true' AND '$(TargetsWindows)' != 'true'">chmod +x $HELIX_WORKITEM_PAYLOAD/*.sh &amp;&amp; $(HelixPythonPath) $(RunnerScript) --script %(Command)</Command>
272         <CorrelationPayloadUris>[$(CorrelationPayloadUris)]</CorrelationPayloadUris>
273         <PayloadUri>$(DropUri)%(RelativeBlobPath)$(DropUriReadOnlyToken)</PayloadUri>
274         <WorkItemId>%(WorkItemId)</WorkItemId>
275         <TimeoutInSeconds>%(TimeoutInSeconds)</TimeoutInSeconds>
276         <SecondaryQueues Condition="'$(SecondaryQueue)' != ''">[$(SecondaryQueuesJson)]</SecondaryQueues>               
277       </HelixProcessedWorkItem>
278     </ItemGroup>
279
280     <!-- I'd love a built-in way to do this, but I'm compromising here since this lets us use 
281          arbitrary extra metadata in the future to compose work items, then strip out extra stuff used along the way.
282          It lets us use a single item to bring along everything we might possibly need in the future -->
283     <RemoveItemMetadata Items="@(HelixProcessedWorkItem)" FieldsToRemove="RelativeBlobPath;PayloadFile">
284       <Output TaskParameter="ProcessedItems" ItemName="HelixProcessedWorkItemForList"/>
285     </RemoveItemMetadata>
286
287     <WriteItemsToJson JsonFileName="$(HelixLogFolder)$(TestListFilename)" Items="@(HelixProcessedWorkItemForList)" ForceJsonArray="true" />
288     <ItemGroup>
289       <HelixWorkItemList Include="$(HelixLogFolder)$(TestListFilename)">
290         <RelativeBlobPath>$(TestListFilename)</RelativeBlobPath>
291         <BuildCompleteJson>$(HelixLogFolder)BuildComplete.json</BuildCompleteJson>
292         <OfficialBuildJson>$(HelixLogFolder)OfficialBuild.json</OfficialBuildJson>
293         <HelixJobUploadCompletePath>$(HelixLogFolder)helixjobuploadcomplete.sem</HelixJobUploadCompletePath>
294       </HelixWorkItemList>
295     </ItemGroup>
296
297     <UploadToAzure
298      ConnectionString="$(CloudDropConnectionString)"
299      ContainerName="$(ContainerName)"
300      Items="@(HelixWorkItemList)"
301      Overwrite="$(OverwriteOnUpload)" />
302   </Target>
303
304   <!-- Write event hub notification JSON files -->
305   <Target Name="WriteCompletionEvent"
306           AfterTargets="CreateTestListJson"
307           Inputs="%(HelixWorkItemList.Identity)"
308           Outputs="%(HelixWorkItemList.BuildCompleteJson)">
309
310     <CreateItem Include="@(TargetQueue)" AdditionalMetadata="ResultsUri=$(ResultsUri)%(TargetQueue.Identity)/;QueueId=%(TargetQueue.Identity)">
311       <Output TaskParameter="Include" ItemName="BuildCompleteTemplateV2" />
312     </CreateItem>
313
314     <!-- If the user didn't provide HelixJobProperties, generate them from HelixArchLabel, HelixConfigLabel, and QueueId -->
315     <ItemGroup Condition="'$(HelixJobProperties)' == ''">
316       <BuildCompleteTemplateV2>
317         <Properties>{ &quot;architecture&quot; : &quot;$(HelixArchLabel)&quot;, &quot;configuration&quot;: &quot;$(HelixConfigLabel)&quot;, &quot;operatingSystem&quot; : &quot;%(QueueId)&quot; }</Properties>
318       </BuildCompleteTemplateV2>
319     </ItemGroup>
320     <ItemGroup Condition="'$(HelixJobProperties)' != ''">
321       <BuildCompleteTemplateV2>
322         <Properties>$(HelixJobProperties)</Properties>
323       </BuildCompleteTemplateV2>
324     </ItemGroup>
325
326     <ItemGroup Condition="'$(HelixCreator)' != ''">
327       <BuildCompleteTemplateV2>
328         <Creator>$(HelixCreator)</Creator>
329       </BuildCompleteTemplateV2>
330     </ItemGroup>
331
332     <ItemGroup Condition="'$(HelixPullRequestId)' != ''">
333       <BuildCompleteTemplateV2>
334         <PullRequestId>$(HelixPullRequestId)</PullRequestId>
335       </BuildCompleteTemplateV2>
336     </ItemGroup>
337
338     <ItemGroup>
339       <!-- V1 is long since deprecated, and not supported here.  
340            Keeping the name for clarity.                       -->
341       <BuildCompleteTemplateV2>
342         <DropContainerSAS>$(DropUriReadOnlyToken)</DropContainerSAS>
343         <ListUri>$(DropUri)%(HelixWorkItemList.Filename)%(HelixWorkItemList.Extension)$(DropUriReadOnlyToken)</ListUri>
344         <ResultsUriRSAS>$(ResultsReadOnlyToken)</ResultsUriRSAS>
345         <ResultsUriWSAS>$(ResultsWriteOnlyToken)</ResultsUriWSAS>
346         <Build>$(BuildMoniker)</Build>
347         <Type>$(HelixJobType)</Type>
348         <Source>$(HelixSource)</Source>
349         <MaxRetryCount>$(MaxRetryCount)</MaxRetryCount>
350         <Attempt>$(HelixAttempt)</Attempt>
351       </BuildCompleteTemplateV2>
352       <BuildComplete Include="@(BuildCompleteTemplateV2)"/>
353     </ItemGroup>
354
355     <WriteItemsToJson JsonFileName="%(HelixWorkItemList.BuildCompleteJson)" Items="@(BuildComplete)" />
356     <Message Text="Wrote job-start (build complete) JSON for @(BuildComplete->Count()) Queues." />
357     <Message Condition="$(MaxRetryCount) &gt; 1" Text="Work Items will automatically retry on non-zero exit code up to $(MaxRetryCount) times." />
358     
359     <CreateItem Include="%(HelixWorkItemList.BuildCompleteJson)" AdditionalMetadata="RelativeBlobPath=JobStartJsonMessages.json">
360       <Output TaskParameter="Include" ItemName="JobStartJsons" />
361     </CreateItem>
362     
363     <UploadToAzure
364      ConnectionString="$(CloudDropConnectionString)"
365      ContainerName="$(ContainerName)"
366      Items="@(JobStartJsons)"
367      Overwrite="$(OverwriteOnUpload)" />
368     <Message Text="Uploaded job-start JSON files to $(ContainerName).   These can be used for resending the same job for debugging purposes." />
369
370   </Target>
371
372   <!-- Send completion event to Helix API -->
373   <Target Name="SendCompletionEvent"
374           AfterTargets="WriteCompletionEvent"
375           Inputs="%(HelixWorkItemList.BuildCompleteJson)"
376           Outputs="%(HelixWorkItemList.HelixJobUploadCompletePath)"
377           Condition="'$(SkipSendToHelix)' != 'true'">
378     <SendJobsToHelix
379       AccessToken="$(HelixApiAccessKey)"
380       ApiEndpoint="$(HelixApiEndpoint)"
381       EventDataPath="%(HelixWorkItemList.BuildCompleteJson)">
382       <Output TaskParameter="JobIds" ItemName="GeneratedCorrelationId" />
383     </SendJobsToHelix>
384
385     <!-- Upload the Correlation Ids generated to the test drop container for tracking purposes-->
386     <PropertyGroup>
387       <HelixCorrelationInfoFileName Condition="'$(HelixCorrelationInfoFileName)'==''">Helix-correlation-infos.json</HelixCorrelationInfoFileName>
388     </PropertyGroup>
389
390     <Message Text="Writing correlation info to: $(HelixLogFolder)$(HelixCorrelationInfoFileName) " />
391     <WriteItemsToJson JsonFileName="$(HelixLogFolder)$(HelixCorrelationInfoFileName)" Items="@(GeneratedCorrelationId)" ForceJsonArray="true" />
392     <ItemGroup>
393       <CorrelationFile Include="$(HelixLogFolder)$(HelixCorrelationInfoFileName)">
394         <RelativeBlobPath>Tracking/$(HelixCorrelationInfoFileName)</RelativeBlobPath>
395       </CorrelationFile>
396     </ItemGroup>
397
398     <UploadToAzure
399         ConnectionString="$(CloudDropConnectionString)"
400         ContainerName="$(ContainerName)"
401         Items="@(CorrelationFile)"
402         Overwrite="true" />
403
404   </Target>
405
406 </Project>