Skip to content

Commit

Permalink
Set azure pipelines variables based on changed paths (dotnet#748)
Browse files Browse the repository at this point in the history
* Set variables based on changed paths

* PR Feedback

* PR feedback
  • Loading branch information
safern authored Dec 12, 2019
1 parent 2aeab34 commit fe3f975
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 1 deletion.
42 changes: 41 additions & 1 deletion eng/pipelines/common/checkout-job.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
### Check out job creating a git bundle and publishing it
### into an Azure artifact for reuse by the subsequent build and test execution phases.
### If paths is specified, we will create a job using evaluate-changed-paths.yml template
### for each path specified.

parameters:
# Object containing subset include and exclude paths in an array form.
# Scenarios:
# 1. exclude paths are specified
# Will include all paths except the ones in the exclude list.
# 2. include paths are specified
# Will only include paths specified in the list.
# 3. exclude + include:
# 1st we evaluate changes for all paths except ones in excluded list. If we can't find
# any applicable changes like that, then we evaluate changes for incldued paths
# if any of these two finds changes, then a variable will be set to true.
# In order to consume this variable you need to reference it via: $[ dependencies.checkout.outputs['SetPathVars_<subset>.containschange'] ]
#
# Array form example
# paths:
# - subset: coreclr
# include:
# - src/libraries/System.Private.CoreLib/*
# exclude:
# - src/libraries/*
#
# This example will include ALL path changes except the ones under src/libraries/*!System.Private.CoreLib/*
paths: []

jobs:
- job: checkout
displayName: Checkout
Expand All @@ -10,7 +37,7 @@ jobs:

${{ if ne(variables['System.TeamProject'], 'public') }}:
name: Hosted Mac Internal

steps:
- checkout: self
clean: true
Expand All @@ -22,3 +49,16 @@ jobs:
- publish: $(Build.StagingDirectory)/Checkout.bundle
artifact: Checkout_bundle
displayName: Upload Checkout.bundle

- ${{ if and(ne(parameters.paths[0], ''), eq(variables['Build.Reason'], 'PullRequest')) }}:
- ${{ each path in parameters.paths }}:
- template: evaluate-changed-paths.yml
parameters:
subsetName: ${{ path.subset }}
arguments:
- --difftarget origin/$(System.PullRequest.TargetBranch)
- --subset ${{ path.subset }}
- ${{ if ne(path.include[0], '') }}:
- --includepaths '${{ join('+', path.include) }}'
- ${{ if ne(path.exclude[0], '') }}:
- --excludepaths '${{ join('+', path.exclude) }}'
19 changes: 19 additions & 0 deletions eng/pipelines/common/evaluate-changed-paths.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# This step template evaluates git changes using git based on a include/exclude path filter.
# For more information on how the path evaluation works look at evaluate-changed-paths.sh docs
# at the beginning of that file.

parameters:
# Name for the subset that we're evaluating changes for.
# It is required to name the step correctly and so the variable created can be consumable.
subsetName: ''
# Array containing the arguments that are to be passed down to evaluate-changed-paths.sh
# Note that --azurevariable is always set to containschange, no need to pass it down.
arguments: []

steps:
- ${{ if ne(parameters.arguments[0], '') }}:
- script: eng/pipelines/evaluate-changed-paths.sh
--azurevariable containsChange
${{ join(' ', parameters.arguments) }}
displayName: Evaluate paths for ${{ parameters.subsetName }}
name: ${{ format('SetPathVars_{0}', parameters.subsetName) }} # need a name to access output variable
7 changes: 7 additions & 0 deletions eng/pipelines/common/xplat-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ jobs:
- template: ${{ coalesce(parameters.helixQueuesTemplate, parameters.jobTemplate) }}
parameters:
variables:
- name: _containsCoreClrChange
value: $[ dependencies.checkout.outputs['SetPathVars_coreclr.containsChange'] ]
- name: _containsLibrariesChange
value: $[ dependencies.checkout.outputs['SetPathVars_libraries.containsChange'] ]
- name: _containsInstallerChange
value: $[ dependencies.checkout.outputs['SetPathVars_installer.containsChange'] ]

- ${{ if eq(parameters.osGroup, 'Windows_NT') }}:
- name: archiveExtension
value: '.zip'
Expand Down
185 changes: 185 additions & 0 deletions eng/pipelines/evaluate-changed-paths.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
: '
Scenarios:
1. exclude paths are specified
Will include all paths except the ones in the exclude list.
2. include paths are specified
Will only include paths specified in the list.
3. exclude + include:
1st we evaluate changes for all paths except ones in excluded list. If we can not find
any applicable changes like that, then we evaluate changes for incldued paths
if any of these two finds changes, then a variable will be set to true.
In order to consume this variable in a yaml pipeline, reference it via: $[ dependencies.<JobName>.outputs["<StepName>_<subset>.containschange"] ]
Example:
-difftarget ''HEAD^1'' -subset coreclr -includepaths ''src/libraries/System.Private.CoreLib/*'' -excludepaths ''src/libraries/*+src/installer/*''
This example will include ALL path changes except the ones under src/libraries/*!System.Private.CoreLib/*
'

# Disable globbing in this bash script since we iterate over path patterns
set -f

# Stop script if unbound variable found (use ${var:-} if intentional)
set -u

# Stop script if command returns non-zero exit code.
# Prevents hidden errors caused by missing error code propagation.
set -e

usage()
{
echo "Script that evaluates changed paths and emits an azure devops variable if the changes contained in the current HEAD against the difftarget meet the includepahts/excludepaths filters:"
echo " --difftarget <value> SHA or branch to diff against. (i.e: HEAD^1, origin/master, 0f4hd36, etc.)"
echo " --excludepaths <value> Escaped list of paths to exclude from diff separated by '+'. (i.e: 'src/libraries/*+'src/installer/*')"
echo " --includepaths <value> Escaped list of paths to include on diff separated by '+'. (i.e: 'src/libraries/System.Private.CoreLib/*')"
echo " --subset Subset name for which we're evaluating in order to include it in logs"
echo " --azurevariable Name of azure devops variable to create if change meets filter criteria"
echo ""

echo "Arguments can also be passed in with a single hyphen."
}

source="${BASH_SOURCE[0]}"

# resolve $source until the file is no longer a symlink
while [[ -h "$source" ]]; do
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
source="$(readlink "$source")"
# if $source was a relative symlink, we need to resolve it relative to the path where the
# symlink file was located
[[ $source != /* ]] && source="$scriptroot/$source"
done

scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
eng_root=`cd -P "$scriptroot/.." && pwd`

exclude_paths=()
include_paths=()
subset_name=''
azure_variable=''
diff_target=''

while [[ $# > 0 ]]; do
opt="$(echo "${1/#--/-}" | awk '{print tolower($0)}')"
case "$opt" in
-help|-h)
usage
exit 0
;;
-difftarget)
diff_target=$2
shift
;;
-excludepaths)
IFS='+' read -r -a tmp <<< $2
exclude_paths+=($tmp)
shift
;;
-includepaths)
IFS='+' read -r -a tmp <<< $2
include_paths+=($tmp)
shift
;;
-subset)
subset_name=$2
shift
;;
-azurevariable)
azure_variable=$2
shift
;;
esac

shift
done

ci=true # Needed in order to use pipeline-logging-functions.sh
. "$eng_root/common/pipeline-logging-functions.sh"

# -- expected args --
# $@: git diff arguments
customGitDiff() {
(
set -x
git diff -M -C -b --ignore-cr-at-eol --ignore-space-at-eol "$@"
)
}

# runs git diff with supplied filter.
# -- exit codes --
# 0: No match was found
# 1: At least 1 match was found
#
# -- expected args --
# $@: filter string
probePathsWithExitCode() {
local _filter=$@
echo ""
customGitDiff --exit-code --quiet $diff_target -- $_filter
}

# -- expected args --
# $@: filter string
printMatchedPaths() {
local _subset=$subset_name
local _filter=$@
echo ""
echo "----- Matching files for $_subset -----"
customGitDiff --name-only $diff_target -- $_filter
}

probePaths() {
local _subset=$subset_name
local _azure_devops_var_name=$azure_variable
local exclude_path_string=""
local include_path_string=""
local found_applying_changes=false

if [[ ${#exclude_paths[@]} -gt 0 ]]; then
echo ""
echo "******* Probing $_subset exclude paths *******";
for _path in "${exclude_paths[@]}"; do
echo "$_path"
if [[ "$exclude_path_string" == "" ]]; then
exclude_path_string=":!$_path"
else
exclude_path_string="$exclude_path_string :!$_path"
fi
done

if ! probePathsWithExitCode $exclude_path_string; then
found_applying_changes=true
printMatchedPaths $exclude_path_string
fi
fi

if [[ $found_applying_changes != true && ${#include_paths[@]} -gt 0 ]]; then
echo ""
echo "******* Probing $_subset include paths *******";
for _path in "${include_paths[@]}"; do
echo "$_path"
if [[ "$include_path_string" == "" ]]; then
include_path_string=":$_path"
else
include_path_string="$exclude_path_string :$_path"
fi
done

if ! probePathsWithExitCode $include_path_string; then
found_applying_changes=true
printMatchedPaths $include_path_string
fi
fi

if [[ $found_applying_changes == true ]]; then
echo ""
echo "Setting pipeline variable $_azure_devops_var_name=true"
Write-PipelineSetVariable -name $_azure_devops_var_name -value true
else
echo ""
echo "No changed files for $_subset"
fi
}

probePaths
24 changes: 24 additions & 0 deletions eng/pipelines/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ jobs:
# Checkout repository
#
- template: /eng/pipelines/common/checkout-job.yml
parameters:
paths:
- subset: coreclr
include:
- src/libraries/System.Private.CoreLib/*
exclude:
- src/installer/*
- src/libraries/*
- eng/pipelines/installer/*
- eng/pipelines/libraries/*
- subset: libraries
exclude:
- src/installer/*
- src/coreclr/*
- eng/pipelines/coreclr/*
- eng/pipelines/installer/*
- subset: installer
include:
- docs/manpages/*
exclude:
- src/coreclr/*
- src/libraries/*
- eng/pipelines/coreclr/*
- eng/pipelines/libraries/*

#
# Build CoreCLR
Expand Down

0 comments on commit fe3f975

Please sign in to comment.