build-utils.ps1 10.7 KB
Newer Older
J
Jared Parsons 已提交
1 2 3 4 5 6 7 8
# Collection of powershell build utility functions that we use across our scripts.

Set-StrictMode -version 2.0
$ErrorActionPreference="Stop"

# Declare a number of useful variables for other scripts to use
[string]$repoDir = Resolve-Path (Join-Path $PSScriptRoot "..\..")
[string]$binariesDir = Join-Path $repoDir "Binaries"
9 10 11

# Handy function for executing a command in powershell and throwing if it 
# fails.  
J
Jared Parsons 已提交
12 13 14 15 16
#
# Use this when the full command is known at script authoring time and 
# doesn't require any dynamic argument build up.  Example:
#
#   Exec-Block { & $msbuild Test.proj }
17 18
# 
# Original sample came from: http://jameskovacs.com/2010/02/25/the-exec-problem/
J
Jared Parsons 已提交
19 20
function Exec-Block([scriptblock]$cmd) {
    & $cmd
21 22 23 24

    # Need to check both of these cases for errors as they represent different items
    # - $?: did the powershell script block throw an error
    # - $lastexitcode: did a windows command executed by the script block end in error
25
    if ((-not $?) -or ($lastexitcode -ne 0)) {
J
Jared Parsons 已提交
26
        throw "Command failed to execute: $cmd"
27 28 29
    } 
}

J
Jared Parsons 已提交
30
function Exec-CommandCore([string]$command, [string]$commandArgs, [switch]$useConsole = $true) {
J
Jared Parsons 已提交
31 32 33 34 35
    $startInfo = New-Object System.Diagnostics.ProcessStartInfo
    $startInfo.FileName = $command
    $startInfo.Arguments = $commandArgs

    $startInfo.UseShellExecute = $false
36
    $startInfo.WorkingDirectory = Get-Location
J
Jared Parsons 已提交
37

J
Jared Parsons 已提交
38 39 40 41 42
    if (-not $useConsole) {
       $startInfo.RedirectStandardOutput = $true
       $startInfo.CreateNoWindow = $true
    }

J
Jared Parsons 已提交
43 44 45 46 47 48
    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $startInfo
    $process.Start() | Out-Null

    $finished = $false
    try {
J
Jared Parsons 已提交
49 50 51 52 53 54 55 56 57 58 59
        if (-not $useConsole) { 
            # The OutputDataReceived event doesn't fire as events are sent by the 
            # process in powershell.  Possibly due to subtlties of how Powershell
            # manages the thread pool that I'm not aware of.  Using blocking
            # reading here as an alternative which is fine since this blocks 
            # on completion already.
            $out = $process.StandardOutput
            while (-not $out.EndOfStream) {
                $line = $out.ReadLine()
                Write-Output $line
            }
J
Jared Parsons 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
        }

        while (-not $process.WaitForExit(100)) { 
            # Non-blocking loop done to allow ctr-c interrupts
        }

        $finished = $true
        if ($process.ExitCode -ne 0) { 
            throw "Command failed to execute: $command $commandArgs" 
        }
    }
    finally {
        # If we didn't finish then an error occured or the user hit ctrl-c.  Either
        # way kill the process
        if (-not $finished) {
            $process.Kill()
        }
    }
J
Jared Parsons 已提交
78 79
}

J
Jared Parsons 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
# Handy function for executing a windows command which needs to go through 
# windows command line parsing.  
#
# Use this when the command arguments are stored in a variable.  Particularly 
# when the variable needs reparsing by the windows command line. Example:
#
#   $args = "/p:ManualBuild=true Test.proj"
#   Exec-Command $msbuild $args
# 
function Exec-Command([string]$command, [string]$commandArgs) {
    Exec-CommandCore -command $command -commandArgs $commandargs -useConsole:$false
}

# Functions exactly like Exec-Command but lets the process re-use the current 
# console. This means items like colored output will function correctly.
#
# In general this command should be used in place of
#   Exec-Command $msbuild $args | Out-Host
#
function Exec-Console([string]$command, [string]$commandArgs) {
    Exec-CommandCore -command $command -commandArgs $commandargs -useConsole:$true
}

J
Jared Parsons 已提交
103 104 105 106 107
# Handy function for executing a powershell script in a clean environment with 
# arguments.  Prefer this over & sourcing a script as it will both use a clean
# environment and do proper error checking
function Exec-Script([string]$script, [string]$scriptArgs = "") {
    Exec-Command "powershell" "-noprofile -executionPolicy RemoteSigned -file `"$script`" $scriptArgs"
108 109
}

J
Jared Parsons 已提交
110 111
# Ensure that NuGet is installed and return the path to the 
# executable to use.
112
function Ensure-NuGet() {
J
Jared Parsons 已提交
113
    Exec-Block { & (Join-Path $PSScriptRoot "download-nuget.ps1") } | Out-Host
114 115 116
    $nuget = Join-Path $repoDir "NuGet.exe"
    return $nuget
}
J
Jared Parsons 已提交
117

J
Jared Parsons 已提交
118 119
# Ensure a basic tool used for building our Repo is installed and 
# return the path to it.
J
Jared Parsons 已提交
120 121 122 123 124
function Ensure-BasicTool([string]$name, [string]$version = "") {
    if ($version -eq "") { 
        $version = Get-PackageVersion $name
    }

J
Jared Parsons 已提交
125 126 127
    $p = Join-Path (Get-PackagesDir) "$($name).$($version)"
    if (-not (Test-Path $p)) {
        $nuget = Ensure-NuGet
J
Jared Parsons 已提交
128
        Exec-Block { & $nuget install $name -OutputDirectory (Get-PackagesDir) -Version $version } | Out-Null
J
Jared Parsons 已提交
129 130 131 132 133
    }
    
    return $p
}

J
Jared Parsons 已提交
134 135
# Ensure that MSBuild is installed and return the path to the
# executable to use.
J
Jared Parsons 已提交
136 137 138 139
function Ensure-MSBuild([switch]$xcopy = $false) {
    $both = Get-MSBuildKindAndDir -xcopy:$xcopy
    $msbuildDir = $both[1]
    switch ($both[0]) {
140 141 142
        "xcopy" { break; }
        "vscmd" { break; }
        "vsinstall" { break; }
J
Jared Parsons 已提交
143 144 145 146 147 148
        default {
            throw "Unknown MSBuild installation type $($both[0])"
        }
    }

    $p = Join-Path $msbuildDir "msbuild.exe"
J
Jared Parsons 已提交
149 150 151
    return $p
}

J
Jared Parsons 已提交
152
function Create-Directory([string]$dir) {
153
    New-Item $dir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
J
Jared Parsons 已提交
154 155 156 157
}

# Return the version of the NuGet package as used in this repo
function Get-PackageVersion([string]$name) {
J
Jared Parsons 已提交
158
    $name = $name.Replace(".", "")
J
Jared Parsons 已提交
159
    $deps = Join-Path $repoDir "build\Targets\Packages.props"
J
Jared Parsons 已提交
160 161
    $nodeName = "$($name)Version"
    $x = [xml](Get-Content -raw $deps)
J
Jared Parsons 已提交
162 163 164 165 166 167
    $node = $x.Project.PropertyGroup.FirstChild
    while ($node -ne $null) {
        if ($node.Name -eq $nodeName) {
            return $node.InnerText
        }
        $node = $node.NextSibling
J
Jared Parsons 已提交
168 169
    }

J
Jared Parsons 已提交
170
    throw "Cannot find package $name in Packages.props"
J
Jared Parsons 已提交
171 172 173 174
}

# Locate the directory where our NuGet packages will be deployed.  Needs to be kept in sync
# with the logic in Version.props
J
Jared Parsons 已提交
175
function Get-PackagesDir() {
J
Jared Parsons 已提交
176 177 178 179 180 181 182 183 184 185 186
    $d = $null
    if ($env:NUGET_PACKAGES -ne $null) {
        $d = $env:NUGET_PACKAGES
    }
    else {
        $d = Join-Path $env:UserProfile ".nuget\packages\"
    }

    Create-Directory $d
    return $d
}
187

J
Jared Parsons 已提交
188 189 190 191 192 193 194 195 196 197 198 199 200
# Locate the directory of a specific NuGet package which is restored via our main 
# toolset values.
function Get-PackageDir([string]$name, [string]$version = "") {
    if ($version -eq "") {
        $version = Get-PackageVersion $name
    }

    $p = Get-PackagesDir
    $p = Join-Path $p $name
    $p = Join-Path $p $version
    return $p
}

201 202 203 204 205 206
# The intent of this script is to locate and return the path to the MSBuild directory that
# we should use for bulid operations.  The preference order for MSBuild to use is as 
# follows
#
#   1. MSBuild from an active VS command prompt
#   2. MSBuild from a machine wide VS install
J
Jared Parsons 已提交
207
#   3. MSBuild from the xcopy toolset 
208 209
#
# This function will return two values: the kind of MSBuild chosen and the MSBuild directory.
J
Jared Parsons 已提交
210 211 212 213 214 215 216
function Get-MSBuildKindAndDir([switch]$xcopy = $false) {

    if ($xcopy) { 
        Write-Output "xcopy"
        Write-Output (Get-MSBuildDirXCopy)
        return
    }
217 218 219 220 221 222

    # MSBuild from an active VS command prompt.  
    if (${env:VSINSTALLDIR} -ne $null) {

        # This line deliberately avoids using -ErrorAction.  Inside a VS command prompt
        # an MSBuild command should always be available.
J
Fixup  
Jared Parsons 已提交
223 224 225 226 227 228 229
        $command = (Get-Command msbuild -ErrorAction SilentlyContinue)
        if ($command -ne $null) {
            $p = Split-Path -parent $command.Path
            Write-Output "vscmd"
            Write-Output $p
            return
        }
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
    }

    # Look for a valid VS installation
    try {
        $p = Get-VisualStudioDir
        $p = Join-Path $p "MSBuild\15.0\Bin"
        Write-Output "vsinstall"
        Write-Output $p
        return
    }
    catch { 
        # Failures are expected here when no VS installation is present on the 
        # machine.
    }

J
Jared Parsons 已提交
245 246 247 248 249 250 251
    Write-Output "xcopy"
    Write-Output (Get-MSBuildDirXCopy)
    return
}

# Locate the xcopy version of MSBuild
function Get-MSBuildDirXCopy() {
J
Jared Parsons 已提交
252
    $p = Ensure-BasicTool "RoslynTools.MSBuild"
J
Jared Parsons 已提交
253 254
    $p = Join-Path $p "tools\msbuild"
    return $p
255 256
}

J
Jared Parsons 已提交
257 258
function Get-MSBuildDir([switch]$xcopy = $false) {
    $both = Get-MSBuildKindAndDir -xcopy:$xcopy
259 260 261
    return $both[1]
}

262 263 264
# Get the directory and instance ID of the first Visual Studio version which 
# meets our minimal requirements for the Roslyn repo.
function Get-VisualStudioDirAndId() {
J
Jared Parsons 已提交
265
    $vswhere = Join-Path (Ensure-BasicTool "vswhere") "tools\vswhere.exe"
266 267 268 269 270 271
    $output = & $vswhere -requires Microsoft.Component.MSBuild -format json | Out-String
    if (-not $?) {
        throw "Could not locate a valid Visual Studio"
    }

    $j = ConvertFrom-Json $output
272 273 274 275 276 277 278 279 280
    Write-Output $j[0].installationPath
    Write-Output $j[0].instanceId
}

# Get the directory of the first Visual Studio which meets our minimal 
# requirements for the Roslyn repo
function Get-VisualStudioDir() {
    $both = Get-VisualStudioDirAndId
    return $both[0]
281 282
}

J
Jared Parsons 已提交
283 284 285
# Clear out the NuGet package cache
function Clear-PackageCache() {
    $nuget = Ensure-NuGet
J
Jared Parsons 已提交
286
    Exec-Block { & $nuget locals all -clear } | Out-Host
J
Jared Parsons 已提交
287 288 289
}

# Restore a single project
J
Jared Parsons 已提交
290
function Restore-Project([string]$fileName, [string]$nuget, [string]$msbuildDir) {
J
Jared Parsons 已提交
291
    $nugetConfig = Join-Path $repoDir "nuget.config"
J
Jared Parsons 已提交
292 293 294 295 296 297

    $filePath = $fileName
    if (-not (Test-Path $filePath)) {
        $filePath = Join-Path $repoDir $fileName
    }

J
Jared Parsons 已提交
298
    Exec-Block { & $nuget restore -verbosity quiet -configfile $nugetConfig -MSBuildPath $msbuildDir -Project2ProjectTimeOut 1200 $filePath } | Write-Host
J
Jared Parsons 已提交
299 300 301
}

# Restore all of the projects that the repo consumes
J
Jared Parsons 已提交
302 303 304 305
function Restore-Packages([string]$msbuildDir = "", [string]$project = "") {
    $nuget = Ensure-NuGet
    if ($msbuildDir -eq "") {
        $msbuildDir = Get-MSBuildDir
J
Jared Parsons 已提交
306 307 308 309 310 311
    }

    Write-Host "Restore using MSBuild at $msbuildDir"

    if ($project -ne "") {
        Write-Host "Restoring project $project"
J
Jared Parsons 已提交
312
        Restore-Project -fileName $project -msbuildDir $msbuildDir -nuget $nuget
J
Jared Parsons 已提交
313 314 315
    }
    else {
        $all = @(
J
Jared Parsons 已提交
316 317
            "Base Toolset:build\ToolsetPackages\BaseToolset.csproj",
            "Closed Toolset:build\ToolsetPackages\ClosedToolset.csproj",
J
Jared Parsons 已提交
318
            "Roslyn:Roslyn.sln",
319 320 321
            "Samples:src\Samples\Samples.sln",
            "Templates:src\Setup\Templates\Templates.sln",
            "DevDivInsertionFiles:src\Setup\DevDivInsertionFiles\DevDivInsertionFiles.sln")
J
Jared Parsons 已提交
322 323 324 325

        foreach ($cur in $all) {
            $both = $cur.Split(':')
            Write-Host "Restoring $($both[0])"
J
Jared Parsons 已提交
326
            Restore-Project -fileName $both[1] -msbuildDir $msbuildDir -nuget $nuget
J
Jared Parsons 已提交
327 328 329 330 331
        }
    }
}

# Restore all of the projects that the repo consumes
332 333
function Restore-All([string]$msbuildDir = "") {
    Restore-Packages -msbuildDir $msbuildDir
J
Jared Parsons 已提交
334
}
J
Jared Parsons 已提交
335