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.
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.
16 It does however provided the minimum necessary to support parts of local Windows
17 development and Windows to Windows CI.
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
28 Builds the client binaries.
31 Builds the daemon binary.
34 Builds the client and daemon binaries. A convenient shortcut to `make.ps1 -Client -Daemon`.
37 Use -race in go build and go test.
42 .PARAMETER ForceBuildAll
46 Use -gcflags -N -l in go build to disable optimisation (can aide debugging).
48 .PARAMETER CommitSuffix
49 Adds a custom string to be appended to the commit ID (spaces are stripped).
52 Runs the DCO (Developer Certificate Of Origin) test (must be run outside a container).
55 Runs the pkg\ directory imports test (must be run outside a container).
58 Runs the Go formatting test (must be run outside a container).
64 Runs everything this script knows about that can run in a container.
68 - Unify the head commit
69 - Add golint and other checks (swagger maybe?)
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
90 $ErrorActionPreference = "Stop"
91 $pushed=$False # To restore the directory if we have temporarily pushed to one.
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."
101 Write-Host "INFO: Git commit ($env:DOCKER_GITCOMMIT) assumed from DOCKER_GITCOMMIT environment variable"
102 return $env:DOCKER_GITCOMMIT
104 $gitCommit=$(git rev-parse --short HEAD)
105 if ($(git status --porcelain --untracked-files=no).Length -ne 0) {
106 $gitCommit="$gitCommit-unsupported"
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
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()
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) {
127 Write-Warning "Not running in a container. The result might be an incorrect build."
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() {
138 $goVersionDockerfile=(Get-Content ".\Dockerfile" | Select-String "ENV GO_VERSION").ToString().Split(" ")[2]
139 $goVersionInstalled=(go version).ToString().Split(" ")[2].SubString(2)
142 Throw "Failed to validate go version correctness: $_"
144 if (-not($goVersionInstalled -eq $goVersionDockerfile)) {
146 Write-Warning "Building with golang version $goVersionInstalled. You should update to $goVersionDockerfile"
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" }
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" }
164 $upstream = Invoke-Expression "git rev-parse --verify FETCH_HEAD"
165 if ($LASTEXITCODE -ne 0) { Throw "Failed getting upstream commit" }
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) }
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" + `
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
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-]+'
205 $dcoPrefix="Signed-off-by:"
206 $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \(github: ($usernameRegex)\))?$"
208 $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit"
209 if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" }
211 # Counts of adds and deletes after removing multiple white spaces. AWK anyone? :(
212 $adds=0; $dels=0; $($counts -replace '\s+', ' ') | %{
214 if ($a[0] -ne "-") { $adds+=[int]$a[0] }
215 if ($a[1] -ne "-") { $dels+=[int]$a[1] }
217 if (($adds -eq 0) -and ($dels -eq 0)) {
218 Write-Warning "DCO validation - nothing to validate!"
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')
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+=$_ }
234 if ($badCommits.Length -eq 0) {
235 Write-Host "Congratulations! All commits are properly signed with the DCO!"
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"
246 # Validates that .\pkg\... is safely isolated from internal code
247 Function Validate-PkgImports($headCommit, $upstreamCommit) {
248 Write-Host "INFO: Validating pkg import isolation..."
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 | %{
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" `
263 $imports | % { $badFiles+="$file imports $_`n" }
265 if ($badFiles.Length -eq 0) {
266 Write-Host 'Congratulations! ".\pkg\*.go" is safely isolated from internal code.'
268 $e = "`nThese files import internal code: (either directly or indirectly)`n"
269 $badFiles | %{ $e+=" - $_"}
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..."
278 # Verify gofmt is installed
279 if ($(Get-Command gofmt -ErrorAction SilentlyContinue) -eq $nil) { Throw "gofmt does not appear to be installed" }
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/"
284 $badFiles=@(); $files | %{
285 # Deliberately ignore error on next line - treat as failed
286 $content=Invoke-Expression "git show $headCommit`:$_"
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"
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 }
301 if ($badFiles.Length -eq 0) {
302 Write-Host 'Congratulations! All Go source files are properly formatted.'
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."
312 Function Run-UnitTests() {
313 Write-Host "INFO: Running unit tests..."
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" }
328 # Start of main code.
330 Write-Host -ForegroundColor Cyan "INFO: make.ps1 starting at $(Get-Date)"
332 # Get to the root of the repo
333 $root = $(Split-Path $MyInvocation.MyCommand.Definition -Parent | Split-Path -Parent)
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 }
341 # Handle the "-Binary" shortcut to build both client and daemon.
342 if ($Binary) { $Client = $True; $Daemon = $True }
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 }
347 # Verify git is installed
348 if ($(Get-Command git -ErrorAction SilentlyContinue) -eq $nil) { Throw "Git does not appear to be installed" }
350 # Verify go is installed
351 if ($(Get-Command go -ErrorAction SilentlyContinue) -eq $nil) { Throw "GoLang does not appear to be installed" }
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 ' ', '' }
357 # Get the version of docker (eg 17.04.0-dev)
358 $dockerVersion=Get-DockerVersion
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 }
364 # If we are not in a container, validate the version of GO that is installed.
365 if (-not $inContainer) { Verify-GoVersion }
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" }
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 $_ }
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
384 if ($DCO) { Validate-DCO $headCommit $upstreamCommit }
386 # Run `gofmt` validation
387 if ($GoFormat) { Validate-GoFormat $headCommit $upstreamCommit }
389 # Run pkg isolation validation
390 if ($PkgImports) { Validate-PkgImports $headCommit $upstreamCommit }
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 }
398 # Perform the actual build
399 if ($Daemon) { Execute-Build "daemon" "daemon" "dockerd" }
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]
407 # Build from a temporary directory.
408 $tempLocation = "$env:TEMP\$(New-Guid)"
409 New-Item -ItemType Directory $tempLocation | Out-Null
411 # Temporarily override GOPATH, then clone, checkout, and build.
412 $saveGOPATH = $env:GOPATH
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
431 # Always restore GOPATH and remove the temporary directory.
432 $env:GOPATH = $saveGOPATH
433 Remove-Item -Force -Recurse $tempLocation
439 if ($TestUnit) { Run-UnitTests }
441 # Gratuitous ASCII art.
442 if ($Daemon -or $Client) {
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 " \/ \/"
454 Write-Host -ForegroundColor Red ("`nERROR: make.ps1 failed:`n$_")
456 # More gratuitous ASCII art.
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 " \/ \/ \/ \/ "
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)"