@@ -128,12 +128,35 @@ class PackageProps {
128
128
if ($ciArtifactResult ) {
129
129
$this.ArtifactDetails = [Hashtable ]$ciArtifactResult.ArtifactConfig
130
130
131
+ $repoRoot = Resolve-Path (Join-Path $PSScriptRoot " .." " .." " .." )
132
+ $ciYamlPath = (Resolve-Path - Path $ciArtifactResult.Location - Relative - RelativeBasePath $repoRoot ).TrimStart(" ." ).Replace(" `\" , " /" )
133
+ $relRoot = [System.IO.Path ]::GetDirectoryName($ciYamlPath ).Replace(" `\" , " /" )
134
+
131
135
if (-not $this.ArtifactDetails [" triggeringPaths" ]) {
132
136
$this.ArtifactDetails [" triggeringPaths" ] = @ ()
133
137
}
134
- $RepoRoot = Resolve-Path (Join-Path $PSScriptRoot " .." " .." " .." )
135
- $relativePath = (Resolve-Path - Path $ciArtifactResult.Location - Relative - RelativeBasePath $RepoRoot ).TrimStart(" ." ).Replace(" `\" , " /" )
136
- $this.ArtifactDetails [" triggeringPaths" ] += $relativePath
138
+ else {
139
+ $adjustedPaths = @ ()
140
+
141
+ # we need to convert relative references to absolute references within the repo
142
+ # this will make it extremely easy to compare triggering paths to files in the deleted+changed file list.
143
+ for ($i = 0 ; $i -lt $this.ArtifactDetails [" triggeringPaths" ].Count; $i ++ ) {
144
+ $currentPath = $this.ArtifactDetails [" triggeringPaths" ][$i ]
145
+ $newPath = Join-Path $repoRoot $currentPath
146
+ if (! $currentPath.StartsWith (" /" )) {
147
+ $newPath = Join-Path $repoRoot $relRoot $currentPath
148
+ }
149
+ # it is a possibility that users may have a triggerPath dependency on a file that no longer exists.
150
+ # before we resolve it to get rid of possible relative references, we should check if the file exists
151
+ # if it doesn't, we should just leave it as is. Otherwise we would _crash_ here when a user accidentally
152
+ # left a triggeringPath on a file that had been deleted
153
+ if (Test-Path $newPath ) {
154
+ $adjustedPaths += (Resolve-Path - Path $newPath - Relative - RelativeBasePath $repoRoot ).TrimStart(" ." ).Replace(" `\" , " /" )
155
+ }
156
+ }
157
+ $this.ArtifactDetails [" triggeringPaths" ] = $adjustedPaths
158
+ }
159
+ $this.ArtifactDetails [" triggeringPaths" ] += $ciYamlPath
137
160
138
161
$this.CIParameters [" CIMatrixConfigs" ] = @ ()
139
162
@@ -180,8 +203,86 @@ function Get-PkgProperties {
180
203
return $null
181
204
}
182
205
206
+
207
+ function Get-TriggerPaths ([PSCustomObject ]$AllPackageProperties ) {
208
+ $existingTriggeringPaths = @ ()
209
+ $AllPackageProperties | ForEach-Object {
210
+ if ($_.ArtifactDetails ) {
211
+ $pathsForArtifact = $_.ArtifactDetails [" triggeringPaths" ]
212
+ foreach ($triggerPath in $pathsForArtifact ){
213
+ # we only care about triggering paths that are actual files, not directories
214
+ # go by by the assumption that if the triggerPath has an extension, it's a file :)
215
+ if ([System.IO.Path ]::HasExtension($triggerPath )) {
216
+ $existingTriggeringPaths += $triggerPath
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ return ($existingTriggeringPaths | Select-Object - Unique)
223
+ }
224
+
225
+ function Update-TargetedFilesForTriggerPaths ([string []]$TargetedFiles , [string []]$TriggeringPaths ) {
226
+ # now we simply loop through the files a single time, keeping all the files that are a triggeringPath
227
+ # for the rest of the files, simply group by what directory they belong to
228
+ # the new TargetedFiles array will contain only the changed directories + the files that actually aligned to a triggeringPath
229
+ $processedFiles = @ ()
230
+ $Triggers = [System.Collections.ArrayList ]$TriggeringPaths
231
+ $i = 0
232
+ foreach ($file in $TargetedFiles ) {
233
+ $isExistingTriggerPath = $false
234
+
235
+ for ($i = 0 ; $i -lt $Triggers.Count ; $i ++ ) {
236
+ $triggerPath = $Triggers [$i ]
237
+ if ($triggerPath -and $file -eq " $triggerPath " ) {
238
+ $isExistingTriggerPath = $true
239
+ break
240
+ }
241
+ }
242
+
243
+ if ($isExistingTriggerPath ) {
244
+ # we know that we should have a valid $i that we can use to remove the triggerPath from the list
245
+ # so that it gets smaller as we find items
246
+ $Triggers.RemoveAt ($i )
247
+ $processedFiles += $file
248
+ }
249
+ else {
250
+ # Get directory path by removing the filename
251
+ $directoryPath = Split-Path - Path $file - Parent
252
+ if ($directoryPath ) {
253
+ $processedFiles += $directoryPath
254
+ } else {
255
+ # In case there's no parent directory (root file), keep the original
256
+ $processedFiles += $file
257
+ }
258
+ }
259
+ }
260
+
261
+ return ($processedFiles | Select-Object - Unique)
262
+ }
263
+
264
+ function Update-TargetedFilesForExclude ([string []]$TargetedFiles , [string []]$ExcludePaths ) {
265
+ $files = @ ()
266
+ foreach ($file in $TargetedFiles ) {
267
+ $shouldExclude = $false
268
+ foreach ($exclude in $ExcludePaths ) {
269
+ if (! $file.StartsWith ($exclude , ' CurrentCultureIgnoreCase' )) {
270
+ $shouldExclude = $true
271
+ break
272
+ }
273
+ }
274
+ if (! $shouldExclude ) {
275
+ $files += $file
276
+ }
277
+ }
278
+ return , $files
279
+ }
280
+
183
281
function Get-PrPkgProperties ([string ]$InputDiffJson ) {
184
282
$packagesWithChanges = @ ()
283
+ $additionalValidationPackages = @ ()
284
+ $lookup = @ {}
285
+ $directoryIndex = @ {}
185
286
186
287
$allPackageProperties = Get-AllPkgProperties
187
288
$diff = Get-Content $InputDiffJson | ConvertFrom-Json
@@ -194,10 +295,9 @@ function Get-PrPkgProperties([string]$InputDiffJson) {
194
295
$targetedFiles += $diff.DeletedFiles
195
296
}
196
297
197
- $excludePaths = $diff.ExcludePaths
198
-
199
- $additionalValidationPackages = @ ()
200
- $lookup = @ {}
298
+ $existingTriggeringPaths = Get-TriggerPaths $allPackageProperties
299
+ $targetedFiles = Update-TargetedFilesForExclude $targetedFiles $diff.ExcludePaths
300
+ $targetedFiles = Update-TargetedFilesForTriggerPaths $targetedFiles $existingTriggeringPaths
201
301
202
302
# Sort so that we very quickly find any directly changed packages before hitting service level changes.
203
303
# This is important because due to the way we traverse the changed files, the instant we evaluate a pkg
@@ -206,9 +306,11 @@ function Get-PrPkgProperties([string]$InputDiffJson) {
206
306
# To avoid this without wonky changes to the detection algorithm, we simply sort our files by their depth, so we will always
207
307
# detect direct package changes first!
208
308
$targetedFiles = $targetedFiles | Sort-Object { ($_.Split (" /" ).Count) } - Descending
309
+ $pkgCounter = 1
209
310
210
311
# this is the primary loop that identifies the packages that have changes
211
312
foreach ($pkg in $allPackageProperties ) {
313
+ Write-Host " Processing changed files against $ ( $pkg.Name ) . $pkgCounter of $ ( $allPackageProperties.Count ) ."
212
314
$pkgDirectory = Resolve-Path " $ ( $pkg.DirectoryPath ) "
213
315
$lookupKey = ($pkg.DirectoryPath ).Replace($RepoRoot , " " ).TrimStart(' \/' )
214
316
$lookup [$lookupKey ] = $pkg
@@ -224,73 +326,71 @@ function Get-PrPkgProperties([string]$InputDiffJson) {
224
326
}
225
327
226
328
foreach ($file in $targetedFiles ) {
227
- $pathComponents = $file -split " /"
228
- $shouldExclude = $false
229
- foreach ($exclude in $excludePaths ) {
230
- if ($file.StartsWith ($exclude , ' CurrentCultureIgnoreCase' )) {
231
- $shouldExclude = $true
232
- break
233
- }
234
- }
235
- if ($shouldExclude ) {
236
- continue
237
- }
238
329
$filePath = (Join-Path $RepoRoot $file )
239
330
240
331
# handle direct changes to packages
241
- $shouldInclude = $filePath -like (Join-Path " $pkgDirectory " " *" )
242
-
243
- # handle changes to files that are RELATED to each package
244
- foreach ($triggerPath in $triggeringPaths ) {
245
- $resolvedRelativePath = (Join-Path $RepoRoot $triggerPath )
246
- if (! $triggerPath.StartsWith (" /" )){
247
- $resolvedRelativePath = (Join-Path $RepoRoot " sdk" " $ ( $pkg.ServiceDirectory ) " $triggerPath )
248
- }
249
-
250
- # if we are including this package due to one of its additional trigger paths, we need
251
- # to ensure we're counting it as included for validation, not as an actual package change
252
- if ($resolvedRelativePath ) {
253
- $includedForValidation = $filePath -like (Join-Path " $resolvedRelativePath " " *" )
332
+ $shouldInclude = $filePath -eq $pkgDirectory -or $filePath -like (Join-Path " $pkgDirectory " " *" )
333
+
334
+ # we only need to do additional work for indirect packages if we haven't already decided
335
+ # to include this package due to this file
336
+ if (-not $shouldInclude ) {
337
+ # handle changes to files that are RELATED to each package
338
+ foreach ($triggerPath in $triggeringPaths ) {
339
+ $resolvedRelativePath = (Join-Path $RepoRoot $triggerPath )
340
+ # triggerPaths can be direct files, so we need to check both startswith and direct equality
341
+ $includedForValidation = ($filePath -like (Join-Path " $resolvedRelativePath " " *" ) -or $filePath -eq $resolvedRelativePath )
254
342
$shouldInclude = $shouldInclude -or $includedForValidation
255
343
if ($includedForValidation ) {
256
344
$pkg.IncludedForValidation = $true
257
345
}
258
346
break
259
347
}
260
- }
261
348
262
- # handle service-level changes to the ci.yml files
263
- # we are using the ci.yml file being added automatically to each artifactdetails as the input
264
- # for this task. This is because we can resolve a service directory from the ci.yml, and if
265
- # there is a single ci.yml in that directory, we can assume that any file change in that directory
266
- # will apply to all packages that exist in that directory.
267
- $triggeringCIYmls = $triggeringPaths | Where-Object { $_ -like " *ci*.yml" }
268
-
269
- foreach ($yml in $triggeringCIYmls ) {
270
- # given that this path is coming from the populated triggering paths in the artifact,
271
- # we can assume that the path to the ci.yml will successfully resolve.
272
- $ciYml = Join-Path $RepoRoot $yml
273
- # ensure we terminate the service directory with a /
274
- $directory = [System.IO.Path ]::GetDirectoryName($ciYml ).Replace(" `\" , " /" )
275
- $soleCIYml = (Get-ChildItem - Path $directory - Filter " ci*.yml" - File).Count -eq 1
276
-
277
- # we should only continue with this check if the file being changed is "in the service directory"
278
- $serviceDirectoryChange = (Split-Path $filePath - Parent).Replace(" `\" , " /" ) -eq $directory
279
- if (! $serviceDirectoryChange ) {
280
- break
281
- }
349
+ # handle service-level changes to the ci.yml files
350
+ # we are using the ci.yml file being added automatically to each artifactdetails as the input
351
+ # for this task. This is because we can resolve a service directory from the ci.yml, and if
352
+ # there is a single ci.yml in that directory, we can assume that any file change in that directory
353
+ # will apply to all packages that exist in that directory.
354
+ $triggeringCIYmls = $triggeringPaths | Where-Object { $_ -like " *ci*.yml" }
355
+
356
+ foreach ($yml in $triggeringCIYmls ) {
357
+ # given that this path is coming from the populated triggering paths in the artifact,
358
+ # we can assume that the path to the ci.yml will successfully resolve.
359
+ $ciYml = Join-Path $RepoRoot $yml
360
+ # ensure we terminate the service directory with a /
361
+ $directory = [System.IO.Path ]::GetDirectoryName($ciYml ).Replace(" `\" , " /" )
362
+
363
+ # we should only continue with this check if the file being changed is "in the service directory"
364
+ # files that are directly included in triggerPaths will kept in full form, but otherwise we pre-process the targetedFiles to the
365
+ # directory containing the change. Given that pre-process, we should check both direct equality (when not triggeringPath) and parent directory
366
+ # for the case where the full form of the file has been left behind (because it was a triggeringPath)
367
+ $serviceDirectoryChange = (Split-Path $filePath - Parent).Replace(" `\" , " /" ) -eq $directory -or $filePath.Replace (" `\" , " /" ) -eq $directory
368
+ if (! $serviceDirectoryChange ) {
369
+ break
370
+ }
282
371
283
- if ($soleCIYml -and $filePath.Replace (" `\" , " /" ).StartsWith($directory )) {
284
- if (-not $shouldInclude ) {
285
- $pkg.IncludedForValidation = $true
286
- $shouldInclude = $true
372
+ # this GCI is very expensive, so we want to cache the result
373
+ $soleCIYml = $true
374
+ if ($directoryIndex [$directory ]) {
375
+ $soleCIYml = $directoryIndex [$directory ]
376
+ }
377
+ else {
378
+ $soleCIYml = (Get-ChildItem - Path $directory - Filter " ci*.yml" - File).Count -eq 1
379
+ $directoryIndex [$directory ] = $soleCIYml
380
+ }
381
+
382
+ if ($soleCIYml -and $filePath.Replace (" `\" , " /" ).StartsWith($directory )) {
383
+ if (-not $shouldInclude ) {
384
+ $pkg.IncludedForValidation = $true
385
+ $shouldInclude = $true
386
+ }
387
+ break
388
+ }
389
+ else {
390
+ # if the ci.yml is not the only file in the directory, we cannot assume that any file changed within the directory that isn't the ci.yml
391
+ # should trigger this package
392
+ Write-Host " Skipping adding package for file `" $file `" because the ci yml `" $yml `" is not the only file in the service directory `" $directory `" "
287
393
}
288
- break
289
- }
290
- else {
291
- # if the ci.yml is not the only file in the directory, we cannot assume that any file changed within the directory that isn't the ci.yml
292
- # should trigger this package
293
- Write-Host " Skipping adding package for file `" $file `" because the ci yml `" $yml `" is not the only file in the service directory `" $directory `" "
294
394
}
295
395
}
296
396
@@ -305,6 +405,8 @@ function Get-PrPkgProperties([string]$InputDiffJson) {
305
405
break
306
406
}
307
407
}
408
+
409
+ $pkgCounter ++
308
410
}
309
411
310
412
# add all of the packages that were added purely for validation purposes
0 commit comments