github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/hack/make.ps1 (about) 1 <# 2 .NOTES 3 Author: @jhowardmsft 4 5 Summary: Windows native build script. This is similar to functionality provided 6 by hack\make.sh, but uses native Windows PowerShell semantics. It does 7 not support the full set of options provided by the Linux counterpart. 8 For example: 9 10 - You can't cross-build Linux docker binaries on Windows 11 - Hashes aren't generated on binaries 12 - 'Releasing' isn't supported. 13 - Integration tests. This is because they currently cannot run inside a container, 14 and require significant external setup. 15 16 It does however provided the minimum necessary to support parts of local Windows 17 development and Windows to Windows CI. 18 19 Usage Examples (run from repo root): 20 "hack\make.ps1 -Client" to build docker.exe client 64-bit binary (remote repo) 21 "hack\make.ps1 -TestUnit" to run unit tests 22 "hack\make.ps1 -Daemon -TestUnit" to build the daemon and run unit tests 23 "hack\make.ps1 -All" to run everything this script knows about that can run in a container 24 "hack\make.ps1" to build the daemon binary (same as -Daemon) 25 "hack\make.ps1 -Binary" shortcut to -Client and -Daemon 26 27 .PARAMETER Client 28 Builds the client binaries. 29 30 .PARAMETER Daemon 31 Builds the daemon binary. 32 33 .PARAMETER Binary 34 Builds the client and daemon binaries. A convenient shortcut to `make.ps1 -Client -Daemon`. 35 36 .PARAMETER Race 37 Use -race in go build and go test. 38 39 .PARAMETER Noisy 40 Use -v in go build. 41 42 .PARAMETER ForceBuildAll 43 Use -a in go build. 44 45 .PARAMETER NoOpt 46 Use -gcflags -N -l in go build to disable optimisation (can aide debugging). 47 48 .PARAMETER CommitSuffix 49 Adds a custom string to be appended to the commit ID (spaces are stripped). 50 51 .PARAMETER DCO 52 Runs the DCO (Developer Certificate Of Origin) test (must be run outside a container). 53 54 .PARAMETER PkgImports 55 Runs the pkg\ directory imports test (must be run outside a container). 56 57 .PARAMETER GoFormat 58 Runs the Go formatting test (must be run outside a container). 59 60 .PARAMETER TestUnit 61 Runs unit tests. 62 63 .PARAMETER All 64 Runs everything this script knows about that can run in a container. 65 66 67 TODO 68 - Unify the head commit 69 - Add golint and other checks (swagger maybe?) 70 71 #> 72 73 74 param( 75 [Parameter(Mandatory=$False)][switch]$Client, 76 [Parameter(Mandatory=$False)][switch]$Daemon, 77 [Parameter(Mandatory=$False)][switch]$Binary, 78 [Parameter(Mandatory=$False)][switch]$Race, 79 [Parameter(Mandatory=$False)][switch]$Noisy, 80 [Parameter(Mandatory=$False)][switch]$ForceBuildAll, 81 [Parameter(Mandatory=$False)][switch]$NoOpt, 82 [Parameter(Mandatory=$False)][string]$CommitSuffix="", 83 [Parameter(Mandatory=$False)][switch]$DCO, 84 [Parameter(Mandatory=$False)][switch]$PkgImports, 85 [Parameter(Mandatory=$False)][switch]$GoFormat, 86 [Parameter(Mandatory=$False)][switch]$TestUnit, 87 [Parameter(Mandatory=$False)][switch]$All 88 ) 89 90 $ErrorActionPreference = "Stop" 91 $pushed=$False # To restore the directory if we have temporarily pushed to one. 92 93 # Utility function to get the commit ID of the repository 94 Function Get-GitCommit() { 95 if (-not (Test-Path ".\.git")) { 96 # If we don't have a .git directory, but we do have the environment 97 # variable DOCKER_GITCOMMIT set, that can override it. 98 if ($env:DOCKER_GITCOMMIT.Length -eq 0) { 99 Throw ".git directory missing and DOCKER_GITCOMMIT environment variable not specified." 100 } 101 Write-Host "INFO: Git commit ($env:DOCKER_GITCOMMIT) assumed from DOCKER_GITCOMMIT environment variable" 102 return $env:DOCKER_GITCOMMIT 103 } 104 $gitCommit=$(git rev-parse --short HEAD) 105 if ($(git status --porcelain --untracked-files=no).Length -ne 0) { 106 $gitCommit="$gitCommit-unsupported" 107 Write-Host "" 108 Write-Warning "This version is unsupported because there are uncommitted file(s)." 109 Write-Warning "Either commit these changes, or add them to .gitignore." 110 git status --porcelain --untracked-files=no | Write-Warning 111 Write-Host "" 112 } 113 return $gitCommit 114 } 115 116 # Utility function to get get the current build version of docker 117 Function Get-DockerVersion() { 118 if (-not (Test-Path ".\VERSION")) { Throw "VERSION file not found. Is this running from the root of a docker repository?" } 119 return $(Get-Content ".\VERSION" -raw).ToString().Replace("`n","").Trim() 120 } 121 122 # Utility function to determine if we are running in a container or not. 123 # In Windows, we get this through an environment variable set in `Dockerfile.Windows` 124 Function Check-InContainer() { 125 if ($env:FROM_DOCKERFILE.Length -eq 0) { 126 Write-Host "" 127 Write-Warning "Not running in a container. The result might be an incorrect build." 128 Write-Host "" 129 return $False 130 } 131 return $True 132 } 133 134 # Utility function to warn if the version of go is correct. Used for local builds 135 # outside of a container where it may be out of date with master. 136 Function Verify-GoVersion() { 137 Try { 138 $goVersionDockerfile=(Get-Content ".\Dockerfile" | Select-String "ENV GO_VERSION").ToString().Split(" ")[2] 139 $goVersionInstalled=(go version).ToString().Split(" ")[2].SubString(2) 140 } 141 Catch [Exception] { 142 Throw "Failed to validate go version correctness: $_" 143 } 144 if (-not($goVersionInstalled -eq $goVersionDockerfile)) { 145 Write-Host "" 146 Write-Warning "Building with golang version $goVersionInstalled. You should update to $goVersionDockerfile" 147 Write-Host "" 148 } 149 } 150 151 # Utility function to get the commit for HEAD 152 Function Get-HeadCommit() { 153 $head = Invoke-Expression "git rev-parse --verify HEAD" 154 if ($LASTEXITCODE -ne 0) { Throw "Failed getting HEAD commit" } 155 156 return $head 157 } 158 159 # Utility function to get the commit for upstream 160 Function Get-UpstreamCommit() { 161 Invoke-Expression "git fetch -q https://github.com/docker/docker.git refs/heads/master" 162 if ($LASTEXITCODE -ne 0) { Throw "Failed fetching" } 163 164 $upstream = Invoke-Expression "git rev-parse --verify FETCH_HEAD" 165 if ($LASTEXITCODE -ne 0) { Throw "Failed getting upstream commit" } 166 167 return $upstream 168 } 169 170 # Build a binary (client or daemon) 171 Function Execute-Build($type, $additionalBuildTags, $directory) { 172 # Generate the build flags 173 $buildTags = "autogen" 174 if ($Noisy) { $verboseParm=" -v" } 175 if ($Race) { Write-Warning "Using race detector"; $raceParm=" -race"} 176 if ($ForceBuildAll) { $allParm=" -a" } 177 if ($NoOpt) { $optParm=" -gcflags "+""""+"-N -l"+"""" } 178 if ($addtionalBuildTags -ne "") { $buildTags += $(" " + $additionalBuildTags) } 179 180 # Do the go build in the appropriate directory 181 # Note -linkmode=internal is required to be able to debug on Windows. 182 # https://github.com/golang/go/issues/14319#issuecomment-189576638 183 Write-Host "INFO: Building $type..." 184 Push-Location $root\cmd\$directory; $global:pushed=$True 185 $buildCommand = "go build" + ` 186 $raceParm + ` 187 $verboseParm + ` 188 $allParm + ` 189 $optParm + ` 190 " -tags """ + $buildTags + """" + ` 191 " -ldflags """ + "-linkmode=internal" + """" + ` 192 " -o $root\bundles\"+$directory+".exe" 193 Invoke-Expression $buildCommand 194 if ($LASTEXITCODE -ne 0) { Throw "Failed to compile $type" } 195 Pop-Location; $global:pushed=$False 196 } 197 198 199 # Validates the DCO marker is present on each commit 200 Function Validate-DCO($headCommit, $upstreamCommit) { 201 Write-Host "INFO: Validating Developer Certificate of Origin..." 202 # Username may only contain alphanumeric characters or dashes and cannot begin with a dash 203 $usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+' 204 205 $dcoPrefix="Signed-off-by:" 206 $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \(github: ($usernameRegex)\))?$" 207 208 $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit" 209 if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" } 210 211 # Counts of adds and deletes after removing multiple white spaces. AWK anyone? :( 212 $adds=0; $dels=0; $($counts -replace '\s+', ' ') | %{ 213 $a=$_.Split(" "); 214 if ($a[0] -ne "-") { $adds+=[int]$a[0] } 215 if ($a[1] -ne "-") { $dels+=[int]$a[1] } 216 } 217 if (($adds -eq 0) -and ($dels -eq 0)) { 218 Write-Warning "DCO validation - nothing to validate!" 219 return 220 } 221 222 $commits = Invoke-Expression "git log $upstreamCommit..$headCommit --format=format:%H%n" 223 if ($LASTEXITCODE -ne 0) { Throw "Failed git log --format" } 224 $commits = $($commits -split '\s+' -match '\S') 225 $badCommits=@() 226 $commits | %{ 227 # Skip commits with no content such as merge commits etc 228 if ($(git log -1 --format=format: --name-status $_).Length -gt 0) { 229 # Ignore exit code on next call - always process regardless 230 $commitMessage = Invoke-Expression "git log -1 --format=format:%B --name-status $_" 231 if (($commitMessage -match $dcoRegex).Length -eq 0) { $badCommits+=$_ } 232 } 233 } 234 if ($badCommits.Length -eq 0) { 235 Write-Host "Congratulations! All commits are properly signed with the DCO!" 236 } else { 237 $e = "`nThese commits do not have a proper '$dcoPrefix' marker:`n" 238 $badCommits | %{ $e+=" - $_`n"} 239 $e += "`nPlease amend each commit to include a properly formatted DCO marker.`n`n" 240 $e += "Visit the following URL for information about the Docker DCO:`n" 241 $e += "https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work`n" 242 Throw $e 243 } 244 } 245 246 # Validates that .\pkg\... is safely isolated from internal code 247 Function Validate-PkgImports($headCommit, $upstreamCommit) { 248 Write-Host "INFO: Validating pkg import isolation..." 249 250 # Get a list of go source-code files which have changed under pkg\. Ignore exit code on next call - always process regardless 251 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'pkg\*.go`'" 252 $badFiles=@(); $files | %{ 253 $file=$_ 254 # For the current changed file, get its list of dependencies, sorted and uniqued. 255 $imports = Invoke-Expression "go list -e -f `'{{ .Deps }}`' $file" 256 if ($LASTEXITCODE -ne 0) { Throw "Failed go list for dependencies on $file" } 257 $imports = $imports -Replace "\[" -Replace "\]", "" -Split(" ") | Sort-Object | Get-Unique 258 # Filter out what we are looking for 259 $imports = $imports -NotMatch "^github.com/docker/docker/pkg/" ` 260 -NotMatch "^github.com/docker/docker/vendor" ` 261 -Match "^github.com/docker/docker" ` 262 -Replace "`n", "" 263 $imports | % { $badFiles+="$file imports $_`n" } 264 } 265 if ($badFiles.Length -eq 0) { 266 Write-Host 'Congratulations! ".\pkg\*.go" is safely isolated from internal code.' 267 } else { 268 $e = "`nThese files import internal code: (either directly or indirectly)`n" 269 $badFiles | %{ $e+=" - $_"} 270 Throw $e 271 } 272 } 273 274 # Validates that changed files are correctly go-formatted 275 Function Validate-GoFormat($headCommit, $upstreamCommit) { 276 Write-Host "INFO: Validating go formatting on changed files..." 277 278 # Verify gofmt is installed 279 if ($(Get-Command gofmt -ErrorAction SilentlyContinue) -eq $nil) { Throw "gofmt does not appear to be installed" } 280 281 # Get a list of all go source-code files which have changed. Ignore exit code on next call - always process regardless 282 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'*.go`'" 283 $files = $files | Select-String -NotMatch "^vendor/" | Select-String -NotMatch "^cli/compose/schema/bindata.go" 284 $badFiles=@(); $files | %{ 285 # Deliberately ignore error on next line - treat as failed 286 $content=Invoke-Expression "git show $headCommit`:$_" 287 288 # Next set of hoops are to ensure we have LF not CRLF semantics as otherwise gofmt on Windows will not succeed. 289 # Also note that gofmt on Windows does not appear to support stdin piping correctly. Hence go through a temporary file. 290 $content=$content -join "`n" 291 $content+="`n" 292 $outputFile=[System.IO.Path]::GetTempFileName() 293 if (Test-Path $outputFile) { Remove-Item $outputFile } 294 [System.IO.File]::WriteAllText($outputFile, $content, (New-Object System.Text.UTF8Encoding($False))) 295 $currentFile = $_ -Replace("/","\") 296 Write-Host Checking $currentFile 297 Invoke-Expression "gofmt -s -l $outputFile" 298 if ($LASTEXITCODE -ne 0) { $badFiles+=$currentFile } 299 if (Test-Path $outputFile) { Remove-Item $outputFile } 300 } 301 if ($badFiles.Length -eq 0) { 302 Write-Host 'Congratulations! All Go source files are properly formatted.' 303 } else { 304 $e = "`nThese files are not properly gofmt`'d:`n" 305 $badFiles | %{ $e+=" - $_`n"} 306 $e+= "`nPlease reformat the above files using `"gofmt -s -w`" and commit the result." 307 Throw $e 308 } 309 } 310 311 # Run the unit tests 312 Function Run-UnitTests() { 313 Write-Host "INFO: Running unit tests..." 314 $testPath="./..." 315 $goListCommand = "go list -e -f '{{if ne .Name """ + '\"github.com/docker/docker\"' + """}}{{.ImportPath}}{{end}}' $testPath" 316 $pkgList = $(Invoke-Expression $goListCommand) 317 if ($LASTEXITCODE -ne 0) { Throw "go list for unit tests failed" } 318 $pkgList = $pkgList | Select-String -Pattern "github.com/docker/docker" 319 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/vendor" 320 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/man" 321 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/integration-cli" 322 $pkgList = $pkgList -replace "`r`n", " " 323 $goTestCommand = "go test" + $raceParm + " -cover -ldflags -w -tags """ + "autogen daemon" + """ -a """ + "-test.timeout=10m" + """ $pkgList" 324 Invoke-Expression $goTestCommand 325 if ($LASTEXITCODE -ne 0) { Throw "Unit tests failed" } 326 } 327 328 # Start of main code. 329 Try { 330 Write-Host -ForegroundColor Cyan "INFO: make.ps1 starting at $(Get-Date)" 331 332 # Get to the root of the repo 333 $root = $(Split-Path $MyInvocation.MyCommand.Definition -Parent | Split-Path -Parent) 334 Push-Location $root 335 336 # Handle the "-All" shortcut to turn on all things we can handle. 337 # Note we expressly only include the items which can run in a container - the validations tests cannot 338 # as they require the .git directory which is excluded from the image by .dockerignore 339 if ($All) { $Client=$True; $Daemon=$True; $TestUnit=$True } 340 341 # Handle the "-Binary" shortcut to build both client and daemon. 342 if ($Binary) { $Client = $True; $Daemon = $True } 343 344 # Default to building the daemon if not asked for anything explicitly. 345 if (-not($Client) -and -not($Daemon) -and -not($DCO) -and -not($PkgImports) -and -not($GoFormat) -and -not($TestUnit)) { $Daemon=$True } 346 347 # Verify git is installed 348 if ($(Get-Command git -ErrorAction SilentlyContinue) -eq $nil) { Throw "Git does not appear to be installed" } 349 350 # Verify go is installed 351 if ($(Get-Command go -ErrorAction SilentlyContinue) -eq $nil) { Throw "GoLang does not appear to be installed" } 352 353 # Get the git commit. This will also verify if we are in a repo or not. Then add a custom string if supplied. 354 $gitCommit=Get-GitCommit 355 if ($CommitSuffix -ne "") { $gitCommit += "-"+$CommitSuffix -Replace ' ', '' } 356 357 # Get the version of docker (eg 17.04.0-dev) 358 $dockerVersion=Get-DockerVersion 359 360 # Give a warning if we are not running in a container and are building binaries or running unit tests. 361 # Not relevant for validation tests as these are fine to run outside of a container. 362 if ($Client -or $Daemon -or $TestUnit) { $inContainer=Check-InContainer } 363 364 # If we are not in a container, validate the version of GO that is installed. 365 if (-not $inContainer) { Verify-GoVersion } 366 367 # Verify GOPATH is set 368 if ($env:GOPATH.Length -eq 0) { Throw "Missing GOPATH environment variable. See https://golang.org/doc/code.html#GOPATH" } 369 370 # Run autogen if building binaries or running unit tests. 371 if ($Client -or $Daemon -or $TestUnit) { 372 Write-Host "INFO: Invoking autogen..." 373 Try { .\hack\make\.go-autogen.ps1 -CommitString $gitCommit -DockerVersion $dockerVersion } 374 Catch [Exception] { Throw $_ } 375 } 376 377 # DCO, Package import and Go formatting tests. 378 if ($DCO -or $PkgImports -or $GoFormat) { 379 # We need the head and upstream commits for these 380 $headCommit=Get-HeadCommit 381 $upstreamCommit=Get-UpstreamCommit 382 383 # Run DCO validation 384 if ($DCO) { Validate-DCO $headCommit $upstreamCommit } 385 386 # Run `gofmt` validation 387 if ($GoFormat) { Validate-GoFormat $headCommit $upstreamCommit } 388 389 # Run pkg isolation validation 390 if ($PkgImports) { Validate-PkgImports $headCommit $upstreamCommit } 391 } 392 393 # Build the binaries 394 if ($Client -or $Daemon) { 395 # Create the bundles directory if it doesn't exist 396 if (-not (Test-Path ".\bundles")) { New-Item ".\bundles" -ItemType Directory | Out-Null } 397 398 # Perform the actual build 399 if ($Daemon) { Execute-Build "daemon" "daemon" "dockerd" } 400 if ($Client) { 401 # Get the repo and commit of the client to build. 402 "hack\dockerfile\binaries-commits" | ForEach-Object { 403 $dockerCliRepo = ((Get-Content $_ | Select-String "DOCKERCLI_REPO") -split "=")[1] 404 $dockerCliCommit = ((Get-Content $_ | Select-String "DOCKERCLI_COMMIT") -split "=")[1] 405 } 406 407 # Build from a temporary directory. 408 $tempLocation = "$env:TEMP\$(New-Guid)" 409 New-Item -ItemType Directory $tempLocation | Out-Null 410 411 # Temporarily override GOPATH, then clone, checkout, and build. 412 $saveGOPATH = $env:GOPATH 413 Try { 414 $env:GOPATH = $tempLocation 415 $dockerCliRoot = "$env:GOPATH\src\github.com\docker\cli" 416 Write-Host "INFO: Cloning client repository..." 417 Invoke-Expression "git clone -q $dockerCliRepo $dockerCliRoot" 418 if ($LASTEXITCODE -ne 0) { Throw "Failed to clone client repository $dockerCliRepo" } 419 Invoke-Expression "git -C $dockerCliRoot checkout -q $dockerCliCommit" 420 if ($LASTEXITCODE -ne 0) { Throw "Failed to checkout client commit $dockerCliCommit" } 421 Write-Host "INFO: Building client..." 422 Push-Location "$dockerCliRoot\cmd\docker"; $global:pushed=$True 423 Invoke-Expression "go build -o $root\bundles\docker.exe" 424 if ($LASTEXITCODE -ne 0) { Throw "Failed to compile client" } 425 Pop-Location; $global:pushed=$False 426 } 427 Catch [Exception] { 428 Throw $_ 429 } 430 Finally { 431 # Always restore GOPATH and remove the temporary directory. 432 $env:GOPATH = $saveGOPATH 433 Remove-Item -Force -Recurse $tempLocation 434 } 435 } 436 } 437 438 # Run unit tests 439 if ($TestUnit) { Run-UnitTests } 440 441 # Gratuitous ASCII art. 442 if ($Daemon -or $Client) { 443 Write-Host 444 Write-Host -ForegroundColor Green " ________ ____ __." 445 Write-Host -ForegroundColor Green " \_____ \ `| `|/ _`|" 446 Write-Host -ForegroundColor Green " / `| \`| `<" 447 Write-Host -ForegroundColor Green " / `| \ `| \" 448 Write-Host -ForegroundColor Green " \_______ /____`|__ \" 449 Write-Host -ForegroundColor Green " \/ \/" 450 Write-Host 451 } 452 } 453 Catch [Exception] { 454 Write-Host -ForegroundColor Red ("`nERROR: make.ps1 failed:`n$_") 455 456 # More gratuitous ASCII art. 457 Write-Host 458 Write-Host -ForegroundColor Red "___________ .__.__ .___" 459 Write-Host -ForegroundColor Red "\_ _____/____ `|__`| `| ____ __`| _/" 460 Write-Host -ForegroundColor Red " `| __) \__ \ `| `| `| _/ __ \ / __ `| " 461 Write-Host -ForegroundColor Red " `| \ / __ \`| `| `|_\ ___// /_/ `| " 462 Write-Host -ForegroundColor Red " \___ / (____ /__`|____/\___ `>____ `| " 463 Write-Host -ForegroundColor Red " \/ \/ \/ \/ " 464 Write-Host 465 466 Throw $_ 467 } 468 Finally { 469 Pop-Location # As we pushed to the root of the repo as the very first thing 470 if ($global:pushed) { Pop-Location } 471 Write-Host -ForegroundColor Cyan "INFO: make.ps1 ended at $(Get-Date)" 472 }