github.com/duglin/docker@v1.13.1/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 -Binary" to build the binaries 21 "hack\make.ps1 -Client" to build just the client 64-bit binary 22 "hack\make.ps1 -TestUnit" to run unit tests 23 "hack\make.ps1 -Binary -TestUnit" to build the binaries and run unit tests 24 "hack\make.ps1 -All" to run everything this script knows about 25 26 .PARAMETER Client 27 Builds the client binaries. 28 29 .PARAMETER Daemon 30 Builds the daemon binary. 31 32 .PARAMETER Binary 33 Builds the client binaries and the daemon binary. A convenient shortcut to `make.ps1 -Client -Daemon`. 34 35 .PARAMETER Race 36 Use -race in go build and go test. 37 38 .PARAMETER Noisy 39 Use -v in go build. 40 41 .PARAMETER ForceBuildAll 42 Use -a in go build. 43 44 .PARAMETER NoOpt 45 Use -gcflags -N -l in go build to disable optimisation (can aide debugging). 46 47 .PARAMETER CommitSuffix 48 Adds a custom string to be appended to the commit ID (spaces are stripped). 49 50 .PARAMETER DCO 51 Runs the DCO (Developer Certificate Of Origin) test. 52 53 .PARAMETER PkgImports 54 Runs the pkg\ directory imports test. 55 56 .PARAMETER GoFormat 57 Runs the Go formatting test. 58 59 .PARAMETER TestUnit 60 Runs unit tests. 61 62 .PARAMETER All 63 Runs everything this script knows about. 64 65 66 TODO 67 - Unify the head commit 68 - Sort out the GITCOMMIT environment variable in the absense of a .git (longer term) 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 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 } 130 } 131 132 # Utility function to get the commit for HEAD 133 Function Get-HeadCommit() { 134 $head = Invoke-Expression "git rev-parse --verify HEAD" 135 if ($LASTEXITCODE -ne 0) { Throw "Failed getting HEAD commit" } 136 137 return $head 138 } 139 140 # Utility function to get the commit for upstream 141 Function Get-UpstreamCommit() { 142 Invoke-Expression "git fetch -q https://github.com/docker/docker.git refs/heads/master" 143 if ($LASTEXITCODE -ne 0) { Throw "Failed fetching" } 144 145 $upstream = Invoke-Expression "git rev-parse --verify FETCH_HEAD" 146 if ($LASTEXITCODE -ne 0) { Throw "Failed getting upstream commit" } 147 148 return $upstream 149 } 150 151 # Build a binary (client or daemon) 152 Function Execute-Build($type, $additionalBuildTags, $directory) { 153 # Generate the build flags 154 $buildTags = "autogen" 155 if ($Noisy) { $verboseParm=" -v" } 156 if ($Race) { Write-Warning "Using race detector"; $raceParm=" -race"} 157 if ($ForceBuildAll) { $allParm=" -a" } 158 if ($NoOpt) { $optParm=" -gcflags "+""""+"-N -l"+"""" } 159 if ($addtionalBuildTags -ne "") { $buildTags += $(" " + $additionalBuildTags) } 160 161 # Do the go build in the appropriate directory 162 # Note -linkmode=internal is required to be able to debug on Windows. 163 # https://github.com/golang/go/issues/14319#issuecomment-189576638 164 Write-Host "INFO: Building $type..." 165 Push-Location $root\cmd\$directory; $global:pushed=$True 166 $buildCommand = "go build" + ` 167 $raceParm + ` 168 $verboseParm + ` 169 $allParm + ` 170 $optParm + ` 171 " -tags """ + $buildTags + """" + ` 172 " -ldflags """ + "-linkmode=internal" + """" + ` 173 " -o $root\bundles\"+$directory+".exe" 174 Invoke-Expression $buildCommand 175 if ($LASTEXITCODE -ne 0) { Throw "Failed to compile $type" } 176 Pop-Location; $global:pushed=$False 177 } 178 179 180 # Validates the DCO marker is present on each commit 181 Function Validate-DCO($headCommit, $upstreamCommit) { 182 Write-Host "INFO: Validating Developer Certificate of Origin..." 183 # Username may only contain alphanumeric characters or dashes and cannot begin with a dash 184 $usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+' 185 186 $dcoPrefix="Signed-off-by:" 187 $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($usernameRegex)\\))?$" 188 189 $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit" 190 if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" } 191 192 # Counts of adds and deletes after removing multiple white spaces. AWK anyone? :( 193 $adds=0; $dels=0; $($counts -replace '\s+', ' ') | %{ 194 $a=$_.Split(" "); 195 if ($a[0] -ne "-") { $adds+=[int]$a[0] } 196 if ($a[1] -ne "-") { $dels+=[int]$a[1] } 197 } 198 if (($adds -eq 0) -and ($dels -eq 0)) { 199 Write-Warning "DCO validation - nothing to validate!" 200 return 201 } 202 203 $commits = Invoke-Expression "git log $upstreamCommit..$headCommit --format=format:%H%n" 204 if ($LASTEXITCODE -ne 0) { Throw "Failed git log --format" } 205 $commits = $($commits -split '\s+' -match '\S') 206 $badCommits=@() 207 $commits | %{ 208 # Skip commits with no content such as merge commits etc 209 if ($(git log -1 --format=format: --name-status $_).Length -gt 0) { 210 # Ignore exit code on next call - always process regardless 211 $commitMessage = Invoke-Expression "git log -1 --format=format:%B --name-status $_" 212 if (($commitMessage -match $dcoRegex).Length -eq 0) { $badCommits+=$_ } 213 } 214 } 215 if ($badCommits.Length -eq 0) { 216 Write-Host "Congratulations! All commits are properly signed with the DCO!" 217 } else { 218 $e = "`nThese commits do not have a proper '$dcoPrefix' marker:`n" 219 $badCommits | %{ $e+=" - $_`n"} 220 $e += "`nPlease amend each commit to include a properly formatted DCO marker.`n`n" 221 $e += "Visit the following URL for information about the Docker DCO:`n" 222 $e += "https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work`n" 223 Throw $e 224 } 225 } 226 227 # Validates that .\pkg\... is safely isolated from internal code 228 Function Validate-PkgImports($headCommit, $upstreamCommit) { 229 Write-Host "INFO: Validating pkg import isolation..." 230 231 # Get a list of go source-code files which have changed under pkg\. Ignore exit code on next call - always process regardless 232 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'pkg\*.go`'" 233 $badFiles=@(); $files | %{ 234 $file=$_ 235 # For the current changed file, get its list of dependencies, sorted and uniqued. 236 $imports = Invoke-Expression "go list -e -f `'{{ .Deps }}`' $file" 237 if ($LASTEXITCODE -ne 0) { Throw "Failed go list for dependencies on $file" } 238 $imports = $imports -Replace "\[" -Replace "\]", "" -Split(" ") | Sort-Object | Get-Unique 239 # Filter out what we are looking for 240 $imports = $imports -NotMatch "^github.com/docker/docker/pkg/" ` 241 -NotMatch "^github.com/docker/docker/vendor" ` 242 -Match "^github.com/docker/docker" ` 243 -Replace "`n", "" 244 $imports | % { $badFiles+="$file imports $_`n" } 245 } 246 if ($badFiles.Length -eq 0) { 247 Write-Host 'Congratulations! ".\pkg\*.go" is safely isolated from internal code.' 248 } else { 249 $e = "`nThese files import internal code: (either directly or indirectly)`n" 250 $badFiles | %{ $e+=" - $_"} 251 Throw $e 252 } 253 } 254 255 # Validates that changed files are correctly go-formatted 256 Function Validate-GoFormat($headCommit, $upstreamCommit) { 257 Write-Host "INFO: Validating go formatting on changed files..." 258 259 # Verify gofmt is installed 260 if ($(Get-Command gofmt -ErrorAction SilentlyContinue) -eq $nil) { Throw "gofmt does not appear to be installed" } 261 262 # Get a list of all go source-code files which have changed. Ignore exit code on next call - always process regardless 263 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'*.go`'" 264 $files = $files | Select-String -NotMatch "^vendor/" | Select-String -NotMatch "^cli/compose/schema/bindata.go" 265 $badFiles=@(); $files | %{ 266 # Deliberately ignore error on next line - treat as failed 267 $content=Invoke-Expression "git show $headCommit`:$_" 268 269 # Next set of hoops are to ensure we have LF not CRLF semantics as otherwise gofmt on Windows will not succeed. 270 # Also note that gofmt on Windows does not appear to support stdin piping correctly. Hence go through a temporary file. 271 $content=$content -join "`n" 272 $content+="`n" 273 $outputFile=[System.IO.Path]::GetTempFileName() 274 if (Test-Path $outputFile) { Remove-Item $outputFile } 275 [System.IO.File]::WriteAllText($outputFile, $content, (New-Object System.Text.UTF8Encoding($False))) 276 $valid=Invoke-Expression "gofmt -s -l $outputFile" 277 Write-Host "Checking $outputFile" 278 if ($valid.Length -ne 0) { $badFiles+=$_ } 279 if (Test-Path $outputFile) { Remove-Item $outputFile } 280 } 281 if ($badFiles.Length -eq 0) { 282 Write-Host 'Congratulations! All Go source files are properly formatted.' 283 } else { 284 $e = "`nThese files are not properly gofmt`'d:`n" 285 $badFiles | %{ $e+=" - $_`n"} 286 $e+= "`nPlease reformat the above files using `"gofmt -s -w`" and commit the result." 287 Throw $e 288 } 289 } 290 291 # Run the unit tests 292 Function Run-UnitTests() { 293 Write-Host "INFO: Running unit tests..." 294 $testPath="./..." 295 $goListCommand = "go list -e -f '{{if ne .Name """ + '\"github.com/docker/docker\"' + """}}{{.ImportPath}}{{end}}' $testPath" 296 $pkgList = $(Invoke-Expression $goListCommand) 297 if ($LASTEXITCODE -ne 0) { Throw "go list for unit tests failed" } 298 $pkgList = $pkgList | Select-String -Pattern "github.com/docker/docker" 299 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/vendor" 300 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/man" 301 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/integration-cli" 302 $pkgList = $pkgList -replace "`r`n", " " 303 $goTestCommand = "go test" + $raceParm + " -cover -ldflags -w -tags """ + "autogen daemon" + """ -a """ + "-test.timeout=10m" + """ $pkgList" 304 Invoke-Expression $goTestCommand 305 if ($LASTEXITCODE -ne 0) { Throw "Unit tests failed" } 306 } 307 308 # Start of main code. 309 Try { 310 Write-Host -ForegroundColor Cyan "INFO: make.ps1 starting at $(Get-Date)" 311 $root=$(pwd) 312 313 # Handle the "-All" shortcut to turn on all things we can handle. 314 if ($All) { $Client=$True; $Daemon=$True; $DCO=$True; $PkgImports=$True; $GoFormat=$True; $TestUnit=$True } 315 316 # Handle the "-Binary" shortcut to build both client and daemon. 317 if ($Binary) { $Client = $True; $Daemon = $True } 318 319 # Make sure we have something to do 320 if (-not($Client) -and -not($Daemon) -and -not($DCO) -and -not($PkgImports) -and -not($GoFormat) -and -not($TestUnit)) { Throw 'Nothing to do. Try adding "-All" for everything I can do' } 321 322 # Verify git is installed 323 if ($(Get-Command git -ErrorAction SilentlyContinue) -eq $nil) { Throw "Git does not appear to be installed" } 324 325 # Verify go is installed 326 if ($(Get-Command go -ErrorAction SilentlyContinue) -eq $nil) { Throw "GoLang does not appear to be installed" } 327 328 # Get the git commit. This will also verify if we are in a repo or not. Then add a custom string if supplied. 329 $gitCommit=Get-GitCommit 330 if ($CommitSuffix -ne "") { $gitCommit += "-"+$CommitSuffix -Replace ' ', '' } 331 332 # Get the version of docker (eg 1.14.0-dev) 333 $dockerVersion=Get-DockerVersion 334 335 # Give a warning if we are not running in a container and are building binaries or running unit tests. 336 # Not relevant for validation tests as these are fine to run outside of a container. 337 if ($Client -or $Daemon -or $TestUnit) { Check-InContainer } 338 339 # Verify GOPATH is set 340 if ($env:GOPATH.Length -eq 0) { Throw "Missing GOPATH environment variable. See https://golang.org/doc/code.html#GOPATH" } 341 342 # Run autogen if building binaries or running unit tests. 343 if ($Client -or $Daemon -or $TestUnit) { 344 Write-Host "INFO: Invoking autogen..." 345 Try { .\hack\make\.go-autogen.ps1 -CommitString $gitCommit -DockerVersion $dockerVersion } 346 Catch [Exception] { Throw $_ } 347 } 348 349 # DCO, Package import and Go formatting tests. 350 if ($DCO -or $PkgImports -or $GoFormat) { 351 # We need the head and upstream commits for these 352 $headCommit=Get-HeadCommit 353 $upstreamCommit=Get-UpstreamCommit 354 355 # Run DCO validation 356 if ($DCO) { Validate-DCO $headCommit $upstreamCommit } 357 358 # Run `gofmt` validation 359 if ($GoFormat) { Validate-GoFormat $headCommit $upstreamCommit } 360 361 # Run pkg isolation validation 362 if ($PkgImports) { Validate-PkgImports $headCommit $upstreamCommit } 363 } 364 365 # Build the binaries 366 if ($Client -or $Daemon) { 367 # Create the bundles directory if it doesn't exist 368 if (-not (Test-Path ".\bundles")) { New-Item ".\bundles" -ItemType Directory | Out-Null } 369 370 # Perform the actual build 371 if ($Daemon) { Execute-Build "daemon" "daemon" "dockerd" } 372 if ($Client) { Execute-Build "client" "" "docker" } 373 } 374 375 # Run unit tests 376 if ($TestUnit) { Run-UnitTests } 377 378 # Gratuitous ASCII art. 379 if ($Daemon -or $Client) { 380 Write-Host 381 Write-Host -ForegroundColor Green " ________ ____ __." 382 Write-Host -ForegroundColor Green " \_____ \ `| `|/ _`|" 383 Write-Host -ForegroundColor Green " / `| \`| `<" 384 Write-Host -ForegroundColor Green " / `| \ `| \" 385 Write-Host -ForegroundColor Green " \_______ /____`|__ \" 386 Write-Host -ForegroundColor Green " \/ \/" 387 Write-Host 388 } 389 } 390 Catch [Exception] { 391 Write-Host -ForegroundColor Red ("`nERROR: make.ps1 failed:`n$_") 392 393 # More gratuitous ASCII art. 394 Write-Host 395 Write-Host -ForegroundColor Red "___________ .__.__ .___" 396 Write-Host -ForegroundColor Red "\_ _____/____ `|__`| `| ____ __`| _/" 397 Write-Host -ForegroundColor Red " `| __) \__ \ `| `| `| _/ __ \ / __ `| " 398 Write-Host -ForegroundColor Red " `| \ / __ \`| `| `|_\ ___// /_/ `| " 399 Write-Host -ForegroundColor Red " \___ / (____ /__`|____/\___ `>____ `| " 400 Write-Host -ForegroundColor Red " \/ \/ \/ \/ " 401 Write-Host 402 403 Throw $_ 404 } 405 Finally { 406 if ($global:pushed) { Pop-Location } 407 Write-Host -ForegroundColor Cyan "INFO: make.ps1 ended at $(Get-Date)" 408 }