7
votes

Scenario

We have TeamCity 8.1.3 building every pull request. Build failures are reported in GitHub. This is great. However view errors are not picked up. This is bad. I could turn on MvcBuildViews across the board but I'd rather not as our solution is quite large and it roughly triples compile time.

What I'd like to do is enable MvcBuildViews only if a view has been changed in a commit in the PR. For example if someone changes a .cs file then compile as normal. If a .cshtml file is changed enable MvcBuildViews and compile.

What I've tried

My first attempt used VCS triggers. I set up two almost identical projects in TeamCity. The only difference was the VCS triggers. One build was designed to build code changes and the other view changes.

Code change trigger rules: -:\**.cshtml and +:**.cs

View change trigger rules: +:**.cshtml

This didn't work as I'd hoped. Committing a .cs file and a .cshtml file on the same branch would trigger both builds.

My second attempt was using a PowerShell build step. I wondered if PowerShell could be used to read the teamcity.build.changedFiles.file agent build property, determine whether a cshtml file had been changed and if so set MvcBuildViews to true.

This failed because I couldn't figure out out to read the agent properties. I found this relevant SO question, but it didn't work.

My PS build step looks like this. I'm mostly clutching at straws here.

write-host "##teamcity[message text='Starting PhilTest build step']"

write-host "##teamcity[message text='Build number $env:build_number']" #Outputs build number

write-host "##teamcity[message text='Changed files $env:teamcity_build_changedFiles_file']" #Outputs nothing

foreach ($row in $env:teamcity_build_changedFiles_file)
{
    write-host "##teamcity[message text='Changed files row $row']" #Outputs nothing
}

write-host "##teamcity[message text='Ending PhilTest build step']"

What next?

Has anyone done this before? Does anyone know how I can get one of my previous attempts to work or know of another way to do it?

2

2 Answers

3
votes

The teamcity.build.changedFiles.file has no environment variable as you can see on this doc: Predefined Build Parameters.

In this case you can use %system.teamcity.build.changedFiles.file%. The TeamCity will pass to your PowerShell script the full path to a file with information about changed files included in the build.

The file contains new-line separated files: each line corresponds to one file and has the following format:

<relative file path>:<change type>:<revision>
2
votes

Using giacomelli's answer as a starting point I've created this TeamCity PowerShell build step which does exactly what I want. It reads in the list of changed files, determines whether a view has been changed and if so sets MvcBuildViews to true in all csproj files. Please be aware:

  • I'm no TeamCity or PowerShell expert, you'll probably want to tidy this up a bit
  • It's unlikely you'll want to set MvcBuildViews on all csproj files

Less importantly when using this I've noticed that TeamCity thinks the build is over time. Not sure if anything can be done about it.

$changedFileInfoPath = '%system.teamcity.build.changedFiles.file%'
$fileData = Get-Content $changedFileInfoPath

$containsViews = $false

foreach($line in $fileData)
{
    write-host "##teamcity[message text='File contents = $line']"
    if($line -like "*.cshtml*")
    {
      $containsViews = $true
      break
    }
}

if ($containsViews)
{
    write-host "##teamcity[message text='View changes found']"

    function xmlPoke($file, $xpath, $value) 
    {
        $filePath = $file.FullName

        [xml] $fileXml = Get-Content $filePath
        $node = $fileXml.SelectSingleNode($xpath)
        if ($node) 
        {
            $node.InnerText = $value

            $fileXml.Save($filePath)
        }
    }

    $workingDirectory = '%teamcity.build.workingDir%'

    $webCsProjFiles = Get-ChildItem -Path $workingDirectory -Recurse -Include "*.csproj"

    foreach ($csProjFile in $webCsProjFiles)
    {
        xmlPoke $csProjFile "//*[local-name()='MvcBuildViews']" "true"
        write-host "##teamcity[message text='Set MvcBuildViews true in $csProjFile']"
    }
}
else
{
    write-host "##teamcity[message text='No view changes were found']"
}

Update 22/10/2014

I've written a slightly more advanced version of this script here.