github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/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 $ProgressPreference = "SilentlyContinue" 92 $pushed=$False # To restore the directory if we have temporarily pushed to one. 93 94 # Utility function to get the commit ID of the repository 95 Function Get-GitCommit() { 96 if (-not (Test-Path ".\.git")) { 97 # If we don't have a .git directory, but we do have the environment 98 # variable DOCKER_GITCOMMIT set, that can override it. 99 if ($env:DOCKER_GITCOMMIT.Length -eq 0) { 100 Throw ".git directory missing and DOCKER_GITCOMMIT environment variable not specified." 101 } 102 Write-Host "INFO: Git commit ($env:DOCKER_GITCOMMIT) assumed from DOCKER_GITCOMMIT environment variable" 103 return $env:DOCKER_GITCOMMIT 104 } 105 $gitCommit=$(git rev-parse --short HEAD) 106 if ($(git status --porcelain --untracked-files=no).Length -ne 0) { 107 $gitCommit="$gitCommit-unsupported" 108 Write-Host "" 109 Write-Warning "This version is unsupported because there are uncommitted file(s)." 110 Write-Warning "Either commit these changes, or add them to .gitignore." 111 git status --porcelain --untracked-files=no | Write-Warning 112 Write-Host "" 113 } 114 return $gitCommit 115 } 116 117 # Utility function to determine if we are running in a container or not. 118 # In Windows, we get this through an environment variable set in `Dockerfile.Windows` 119 Function Check-InContainer() { 120 if ($env:FROM_DOCKERFILE.Length -eq 0) { 121 Write-Host "" 122 Write-Warning "Not running in a container. The result might be an incorrect build." 123 Write-Host "" 124 return $False 125 } 126 return $True 127 } 128 129 # Utility function to warn if the version of go is correct. Used for local builds 130 # outside of a container where it may be out of date with master. 131 Function Verify-GoVersion() { 132 Try { 133 $goVersionDockerfile=(Get-Content ".\Dockerfile" | Select-String "ENV GO_VERSION").ToString().Split(" ")[2] 134 $goVersionInstalled=(go version).ToString().Split(" ")[2].SubString(2) 135 } 136 Catch [Exception] { 137 Throw "Failed to validate go version correctness: $_" 138 } 139 if (-not($goVersionInstalled -eq $goVersionDockerfile)) { 140 Write-Host "" 141 Write-Warning "Building with golang version $goVersionInstalled. You should update to $goVersionDockerfile" 142 Write-Host "" 143 } 144 } 145 146 # Utility function to get the commit for HEAD 147 Function Get-HeadCommit() { 148 $head = Invoke-Expression "git rev-parse --verify HEAD" 149 if ($LASTEXITCODE -ne 0) { Throw "Failed getting HEAD commit" } 150 151 return $head 152 } 153 154 # Utility function to get the commit for upstream 155 Function Get-UpstreamCommit() { 156 Invoke-Expression "git fetch -q https://github.com/docker/docker.git refs/heads/master" 157 if ($LASTEXITCODE -ne 0) { Throw "Failed fetching" } 158 159 $upstream = Invoke-Expression "git rev-parse --verify FETCH_HEAD" 160 if ($LASTEXITCODE -ne 0) { Throw "Failed getting upstream commit" } 161 162 return $upstream 163 } 164 165 # Build a binary (client or daemon) 166 Function Execute-Build($type, $additionalBuildTags, $directory) { 167 # Generate the build flags 168 $buildTags = "autogen" 169 if ($Noisy) { $verboseParm=" -v" } 170 if ($Race) { Write-Warning "Using race detector"; $raceParm=" -race"} 171 if ($ForceBuildAll) { $allParm=" -a" } 172 if ($NoOpt) { $optParm=" -gcflags "+""""+"-N -l"+"""" } 173 if ($additionalBuildTags -ne "") { $buildTags += $(" " + $additionalBuildTags) } 174 175 # Do the go build in the appropriate directory 176 # Note -linkmode=internal is required to be able to debug on Windows. 177 # https://github.com/golang/go/issues/14319#issuecomment-189576638 178 Write-Host "INFO: Building $type..." 179 Push-Location $root\cmd\$directory; $global:pushed=$True 180 $buildCommand = "go build" + ` 181 $raceParm + ` 182 $verboseParm + ` 183 $allParm + ` 184 $optParm + ` 185 " -tags """ + $buildTags + """" + ` 186 " -ldflags """ + "-linkmode=internal" + """" + ` 187 " -o $root\bundles\"+$directory+".exe" 188 Invoke-Expression $buildCommand 189 if ($LASTEXITCODE -ne 0) { Throw "Failed to compile $type" } 190 Pop-Location; $global:pushed=$False 191 } 192 193 194 # Validates the DCO marker is present on each commit 195 Function Validate-DCO($headCommit, $upstreamCommit) { 196 Write-Host "INFO: Validating Developer Certificate of Origin..." 197 # Username may only contain alphanumeric characters or dashes and cannot begin with a dash 198 $usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+' 199 200 $dcoPrefix="Signed-off-by:" 201 $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \(github: ($usernameRegex)\))?$" 202 203 $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit" 204 if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" } 205 206 # Counts of adds and deletes after removing multiple white spaces. AWK anyone? :( 207 $adds=0; $dels=0; $($counts -replace '\s+', ' ') | %{ 208 $a=$_.Split(" "); 209 if ($a[0] -ne "-") { $adds+=[int]$a[0] } 210 if ($a[1] -ne "-") { $dels+=[int]$a[1] } 211 } 212 if (($adds -eq 0) -and ($dels -eq 0)) { 213 Write-Warning "DCO validation - nothing to validate!" 214 return 215 } 216 217 $commits = Invoke-Expression "git log $upstreamCommit..$headCommit --format=format:%H%n" 218 if ($LASTEXITCODE -ne 0) { Throw "Failed git log --format" } 219 $commits = $($commits -split '\s+' -match '\S') 220 $badCommits=@() 221 $commits | %{ 222 # Skip commits with no content such as merge commits etc 223 if ($(git log -1 --format=format: --name-status $_).Length -gt 0) { 224 # Ignore exit code on next call - always process regardless 225 $commitMessage = Invoke-Expression "git log -1 --format=format:%B --name-status $_" 226 if (($commitMessage -match $dcoRegex).Length -eq 0) { $badCommits+=$_ } 227 } 228 } 229 if ($badCommits.Length -eq 0) { 230 Write-Host "Congratulations! All commits are properly signed with the DCO!" 231 } else { 232 $e = "`nThese commits do not have a proper '$dcoPrefix' marker:`n" 233 $badCommits | %{ $e+=" - $_`n"} 234 $e += "`nPlease amend each commit to include a properly formatted DCO marker.`n`n" 235 $e += "Visit the following URL for information about the Docker DCO:`n" 236 $e += "https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work`n" 237 Throw $e 238 } 239 } 240 241 # Validates that .\pkg\... is safely isolated from internal code 242 Function Validate-PkgImports($headCommit, $upstreamCommit) { 243 Write-Host "INFO: Validating pkg import isolation..." 244 245 # Get a list of go source-code files which have changed under pkg\. Ignore exit code on next call - always process regardless 246 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'pkg\*.go`'" 247 $badFiles=@(); $files | %{ 248 $file=$_ 249 # For the current changed file, get its list of dependencies, sorted and uniqued. 250 $imports = Invoke-Expression "go list -e -f `'{{ .Deps }}`' $file" 251 if ($LASTEXITCODE -ne 0) { Throw "Failed go list for dependencies on $file" } 252 $imports = $imports -Replace "\[" -Replace "\]", "" -Split(" ") | Sort-Object | Get-Unique 253 # Filter out what we are looking for 254 $imports = $imports -NotMatch "^github.com/docker/docker/pkg/" ` 255 -NotMatch "^github.com/docker/docker/vendor" ` 256 -Match "^github.com/docker/docker" ` 257 -Replace "`n", "" 258 $imports | % { $badFiles+="$file imports $_`n" } 259 } 260 if ($badFiles.Length -eq 0) { 261 Write-Host 'Congratulations! ".\pkg\*.go" is safely isolated from internal code.' 262 } else { 263 $e = "`nThese files import internal code: (either directly or indirectly)`n" 264 $badFiles | %{ $e+=" - $_"} 265 Throw $e 266 } 267 } 268 269 # Validates that changed files are correctly go-formatted 270 Function Validate-GoFormat($headCommit, $upstreamCommit) { 271 Write-Host "INFO: Validating go formatting on changed files..." 272 273 # Verify gofmt is installed 274 if ($(Get-Command gofmt -ErrorAction SilentlyContinue) -eq $nil) { Throw "gofmt does not appear to be installed" } 275 276 # Get a list of all go source-code files which have changed. Ignore exit code on next call - always process regardless 277 $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'*.go`'" 278 $files = $files | Select-String -NotMatch "^vendor/" 279 $badFiles=@(); $files | %{ 280 # Deliberately ignore error on next line - treat as failed 281 $content=Invoke-Expression "git show $headCommit`:$_" 282 283 # Next set of hoops are to ensure we have LF not CRLF semantics as otherwise gofmt on Windows will not succeed. 284 # Also note that gofmt on Windows does not appear to support stdin piping correctly. Hence go through a temporary file. 285 $content=$content -join "`n" 286 $content+="`n" 287 $outputFile=[System.IO.Path]::GetTempFileName() 288 if (Test-Path $outputFile) { Remove-Item $outputFile } 289 [System.IO.File]::WriteAllText($outputFile, $content, (New-Object System.Text.UTF8Encoding($False))) 290 $currentFile = $_ -Replace("/","\") 291 Write-Host Checking $currentFile 292 Invoke-Expression "gofmt -s -l $outputFile" 293 if ($LASTEXITCODE -ne 0) { $badFiles+=$currentFile } 294 if (Test-Path $outputFile) { Remove-Item $outputFile } 295 } 296 if ($badFiles.Length -eq 0) { 297 Write-Host 'Congratulations! All Go source files are properly formatted.' 298 } else { 299 $e = "`nThese files are not properly gofmt`'d:`n" 300 $badFiles | %{ $e+=" - $_`n"} 301 $e+= "`nPlease reformat the above files using `"gofmt -s -w`" and commit the result." 302 Throw $e 303 } 304 } 305 306 # Run the unit tests 307 Function Run-UnitTests() { 308 Write-Host "INFO: Running unit tests..." 309 $testPath="./..." 310 $goListCommand = "go list -e -f '{{if ne .Name """ + '\"github.com/docker/docker\"' + """}}{{.ImportPath}}{{end}}' $testPath" 311 $pkgList = $(Invoke-Expression $goListCommand) 312 if ($LASTEXITCODE -ne 0) { Throw "go list for unit tests failed" } 313 $pkgList = $pkgList | Select-String -Pattern "github.com/docker/docker" 314 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/vendor" 315 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/man" 316 $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/integration" 317 $pkgList = $pkgList -replace "`r`n", " " 318 $goTestCommand = "go test" + $raceParm + " -cover -ldflags -w -tags """ + "autogen daemon" + """ -a """ + "-test.timeout=10m" + """ $pkgList" 319 Invoke-Expression $goTestCommand 320 if ($LASTEXITCODE -ne 0) { Throw "Unit tests failed" } 321 } 322 323 # Start of main code. 324 Try { 325 Write-Host -ForegroundColor Cyan "INFO: make.ps1 starting at $(Get-Date)" 326 327 # Get to the root of the repo 328 $root = $(Split-Path $MyInvocation.MyCommand.Definition -Parent | Split-Path -Parent) 329 Push-Location $root 330 331 # Handle the "-All" shortcut to turn on all things we can handle. 332 # Note we expressly only include the items which can run in a container - the validations tests cannot 333 # as they require the .git directory which is excluded from the image by .dockerignore 334 if ($All) { $Client=$True; $Daemon=$True; $TestUnit=$True } 335 336 # Handle the "-Binary" shortcut to build both client and daemon. 337 if ($Binary) { $Client = $True; $Daemon = $True } 338 339 # Default to building the daemon if not asked for anything explicitly. 340 if (-not($Client) -and -not($Daemon) -and -not($DCO) -and -not($PkgImports) -and -not($GoFormat) -and -not($TestUnit)) { $Daemon=$True } 341 342 # Verify git is installed 343 if ($(Get-Command git -ErrorAction SilentlyContinue) -eq $nil) { Throw "Git does not appear to be installed" } 344 345 # Verify go is installed 346 if ($(Get-Command go -ErrorAction SilentlyContinue) -eq $nil) { Throw "GoLang does not appear to be installed" } 347 348 # Get the git commit. This will also verify if we are in a repo or not. Then add a custom string if supplied. 349 $gitCommit=Get-GitCommit 350 if ($CommitSuffix -ne "") { $gitCommit += "-"+$CommitSuffix -Replace ' ', '' } 351 352 # Get the version of docker (eg 17.04.0-dev) 353 $dockerVersion="0.0.0-dev" 354 355 # Give a warning if we are not running in a container and are building binaries or running unit tests. 356 # Not relevant for validation tests as these are fine to run outside of a container. 357 if ($Client -or $Daemon -or $TestUnit) { $inContainer=Check-InContainer } 358 359 # If we are not in a container, validate the version of GO that is installed. 360 if (-not $inContainer) { Verify-GoVersion } 361 362 # Verify GOPATH is set 363 if ($env:GOPATH.Length -eq 0) { Throw "Missing GOPATH environment variable. See https://golang.org/doc/code.html#GOPATH" } 364 365 # Run autogen if building binaries or running unit tests. 366 if ($Client -or $Daemon -or $TestUnit) { 367 Write-Host "INFO: Invoking autogen..." 368 Try { .\hack\make\.go-autogen.ps1 -CommitString $gitCommit -DockerVersion $dockerVersion -Platform "$env:PLATFORM" } 369 Catch [Exception] { Throw $_ } 370 } 371 372 # DCO, Package import and Go formatting tests. 373 if ($DCO -or $PkgImports -or $GoFormat) { 374 # We need the head and upstream commits for these 375 $headCommit=Get-HeadCommit 376 $upstreamCommit=Get-UpstreamCommit 377 378 # Run DCO validation 379 if ($DCO) { Validate-DCO $headCommit $upstreamCommit } 380 381 # Run `gofmt` validation 382 if ($GoFormat) { Validate-GoFormat $headCommit $upstreamCommit } 383 384 # Run pkg isolation validation 385 if ($PkgImports) { Validate-PkgImports $headCommit $upstreamCommit } 386 } 387 388 # Build the binaries 389 if ($Client -or $Daemon) { 390 # Create the bundles directory if it doesn't exist 391 if (-not (Test-Path ".\bundles")) { New-Item ".\bundles" -ItemType Directory | Out-Null } 392 393 # Perform the actual build 394 if ($Daemon) { Execute-Build "daemon" "daemon" "dockerd" } 395 if ($Client) { 396 # Get the Docker channel and version from the environment, or use the defaults. 397 if (-not ($channel = $env:DOCKERCLI_CHANNEL)) { $channel = "edge" } 398 if (-not ($version = $env:DOCKERCLI_VERSION)) { $version = "17.06.0-ce" } 399 400 # Download the zip file and extract the client executable. 401 Write-Host "INFO: Downloading docker/cli version $version from $channel..." 402 $url = "https://download.docker.com/win/static/$channel/x86_64/docker-$version.zip" 403 Invoke-WebRequest $url -OutFile "docker.zip" 404 Try { 405 Add-Type -AssemblyName System.IO.Compression.FileSystem 406 $zip = [System.IO.Compression.ZipFile]::OpenRead("$PWD\docker.zip") 407 Try { 408 if (-not ($entry = $zip.Entries | Where-Object { $_.Name -eq "docker.exe" })) { 409 Throw "Cannot find docker.exe in $url" 410 } 411 [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, "$PWD\bundles\docker.exe", $true) 412 } 413 Finally { 414 $zip.Dispose() 415 } 416 } 417 Finally { 418 Remove-Item -Force "docker.zip" 419 } 420 } 421 } 422 423 # Run unit tests 424 if ($TestUnit) { Run-UnitTests } 425 426 # Gratuitous ASCII art. 427 if ($Daemon -or $Client) { 428 Write-Host 429 Write-Host -ForegroundColor Green " ________ ____ __." 430 Write-Host -ForegroundColor Green " \_____ \ `| `|/ _`|" 431 Write-Host -ForegroundColor Green " / `| \`| `<" 432 Write-Host -ForegroundColor Green " / `| \ `| \" 433 Write-Host -ForegroundColor Green " \_______ /____`|__ \" 434 Write-Host -ForegroundColor Green " \/ \/" 435 Write-Host 436 } 437 } 438 Catch [Exception] { 439 Write-Host -ForegroundColor Red ("`nERROR: make.ps1 failed:`n$_") 440 441 # More gratuitous ASCII art. 442 Write-Host 443 Write-Host -ForegroundColor Red "___________ .__.__ .___" 444 Write-Host -ForegroundColor Red "\_ _____/____ `|__`| `| ____ __`| _/" 445 Write-Host -ForegroundColor Red " `| __) \__ \ `| `| `| _/ __ \ / __ `| " 446 Write-Host -ForegroundColor Red " `| \ / __ \`| `| `|_\ ___// /_/ `| " 447 Write-Host -ForegroundColor Red " \___ / (____ /__`|____/\___ `>____ `| " 448 Write-Host -ForegroundColor Red " \/ \/ \/ \/ " 449 Write-Host 450 451 Throw $_ 452 } 453 Finally { 454 Pop-Location # As we pushed to the root of the repo as the very first thing 455 if ($global:pushed) { Pop-Location } 456 Write-Host -ForegroundColor Cyan "INFO: make.ps1 ended at $(Get-Date)" 457 }