@@ -128,12 +128,35 @@ class PackageProps {
128128 if ($ciArtifactResult ) {
129129 $this.ArtifactDetails = [Hashtable ]$ciArtifactResult.ArtifactConfig
130130
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+
131135 if (-not $this.ArtifactDetails [" triggeringPaths" ]) {
132136 $this.ArtifactDetails [" triggeringPaths" ] = @ ()
133137 }
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
137160
138161 $this.CIParameters [" CIMatrixConfigs" ] = @ ()
139162
@@ -180,8 +203,86 @@ function Get-PkgProperties {
180203 return $null
181204}
182205
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+
183281function Get-PrPkgProperties ([string ]$InputDiffJson ) {
184282 $packagesWithChanges = @ ()
283+ $additionalValidationPackages = @ ()
284+ $lookup = @ {}
285+ $directoryIndex = @ {}
185286
186287 $allPackageProperties = Get-AllPkgProperties
187288 $diff = Get-Content $InputDiffJson | ConvertFrom-Json
@@ -194,10 +295,9 @@ function Get-PrPkgProperties([string]$InputDiffJson) {
194295 $targetedFiles += $diff.DeletedFiles
195296 }
196297
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
201301
202302 # Sort so that we very quickly find any directly changed packages before hitting service level changes.
203303 # 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) {
206306 # To avoid this without wonky changes to the detection algorithm, we simply sort our files by their depth, so we will always
207307 # detect direct package changes first!
208308 $targetedFiles = $targetedFiles | Sort-Object { ($_.Split (" /" ).Count) } - Descending
309+ $pkgCounter = 1
209310
210311 # this is the primary loop that identifies the packages that have changes
211312 foreach ($pkg in $allPackageProperties ) {
313+ Write-Host " Processing changed files against $ ( $pkg.Name ) . $pkgCounter of $ ( $allPackageProperties.Count ) ."
212314 $pkgDirectory = Resolve-Path " $ ( $pkg.DirectoryPath ) "
213315 $lookupKey = ($pkg.DirectoryPath ).Replace($RepoRoot , " " ).TrimStart(' \/' )
214316 $lookup [$lookupKey ] = $pkg
@@ -224,73 +326,71 @@ function Get-PrPkgProperties([string]$InputDiffJson) {
224326 }
225327
226328 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- }
238329 $filePath = (Join-Path $RepoRoot $file )
239330
240331 # 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 )
254342 $shouldInclude = $shouldInclude -or $includedForValidation
255343 if ($includedForValidation ) {
256344 $pkg.IncludedForValidation = $true
257345 }
258346 break
259347 }
260- }
261348
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+ }
282371
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 `" "
287393 }
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 `" "
294394 }
295395 }
296396
@@ -305,6 +405,8 @@ function Get-PrPkgProperties([string]$InputDiffJson) {
305405 break
306406 }
307407 }
408+
409+ $pkgCounter ++
308410 }
309411
310412 # add all of the packages that were added purely for validation purposes
0 commit comments