Browse Source

Windows: make.ps1 and Dockerfile for native builds

Signed-off-by: John Howard <jhoward@microsoft.com>
(cherry picked from commit 155435b6ceeb05b2927ecc726216666b898b6459)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
John Howard 8 years ago
parent
commit
e509501437
3 changed files with 536 additions and 61 deletions
  1. 134 54
      Dockerfile.windows
  2. 401 0
      hack/make.ps1
  3. 1 7
      hack/make/.go-autogen.ps1

+ 134 - 54
Dockerfile.windows

@@ -2,7 +2,7 @@
 
 # -----------------------------------------------------------------------------------------
 # This file describes the standard way to build Docker in a container on Windows
-# Server 2016.
+# Server 2016 or Windows 10.
 #
 # Maintainer: @jhowardmsft
 # -----------------------------------------------------------------------------------------
@@ -11,21 +11,25 @@
 # Prerequisites:
 # --------------
 #
-# 1. Windows Server 2016 with all Windows updates applied. Pre-release versions
-#    of Windows are not supported (eg Windows Server 2016 TP5). The build number
-#    must be at least 14393. This can be confirmed, for example, by running the
-#    following from an elevated PowerShell prompt - this sample output is from a 
-#    fully up to date machine as at late October 2016:
+# 1. Windows Server 2016 or Windows 10 with all Windows updates applied. The major 
+#    build number must be at least 14393. This can be confirmed, for example, by 
+#    running the following from an elevated PowerShell prompt - this sample output 
+#    is from a fully up to date machine as at mid-November 2016:
 #
 #    >> PS C:\> $(gin).WindowsBuildLabEx
-#    >> 14393.321.amd64fre.rs1_release_inmarket.161004-2338
+#    >> 14393.447.amd64fre.rs1_release_inmarket.161102-0100
 #
 # 2. Git for Windows (or another git client) must be installed. https://git-scm.com/download/win.
 #
 # 3. The machine must be configured to run containers. For example, by following
 #    the quick start guidance at https://msdn.microsoft.com/en-us/virtualization/windowscontainers/quick_start/quick_start or
 #    https://github.com/docker/labs/blob/master/windows/windows-containers/Setup.md
-
+#
+# 4. If building in a Hyper-V VM: For Windows Server 2016 using Windows Server
+#    containers as the default option, it is recommended you have at least 1GB 
+#    of memory assigned; For Windows 10 where Hyper-V Containers are employed, you
+#    should have at least 4GB of memory assigned. Note also, to run Hyper-V 
+#    containers in a VM, it is necessary to configure the VM for nested virtualization.
 
 # -----------------------------------------------------------------------------------------
 
@@ -63,18 +67,16 @@
 #    >>   docker build -t nativebuildimage -f Dockerfile.windows .
 #
 #
-# 4. Build the docker executable binaries in a container:
+# 4. Build the docker executable binaries:
 #
-#    >>   docker run --name binaries nativebuildimage sh -c 'cd /c/go/src/github.com/docker/docker; hack/make.sh binary'
+#    >>   docker run --name binaries nativebuildimage hack\make.ps1 -Binary
 #
 #
-# 5. Copy the binaries out of the above container, replacing HostPath with an appropriate destination 
+# 5. Copy the binaries out of the container, replacing HostPath with an appropriate destination 
 #    folder on the host system where you want the binaries to be located.
 #
-#    >>   $v=$(Get-Content ".\VERSION" -raw).ToString().Replace("`n","").Trim()
-#    >>   docker cp binaries:C:\go\src\github.com\docker\docker\bundles\$v\binary-client\docker-$v.exe C:\HostPath\docker.exe
-#    >>   docker cp binaries:C:\go\src\github.com\docker\docker\bundles\$v\binary-daemon\dockerd.exe C:\HostPath\dockerd.exe
-#    >>   docker cp binaries:C:\go\src\github.com\docker\docker\bundles\$v\binary-daemon\docker-proxy-$v.exe C:\HostPath\docker-proxy.exe
+#    >>   docker cp binaries:C:\go\src\github.com\docker\docker\bundles\docker.exe C:\HostPath\docker.exe
+#    >>   docker cp binaries:C:\go\src\github.com\docker\docker\bundles\dockerd.exe C:\HostPath\dockerd.exe
 #
 #
 # 6. (Optional) Remove the interim container holding the built executable binaries:
@@ -88,6 +90,39 @@
 #    image which has all the components required to build the binaries already installed.
 #
 #    >>    docker rmi nativebuildimage
+#
+
+# -----------------------------------------------------------------------------------------
+
+
+#  The validation tests can either run in a container, or directly on the host. To run in a
+#  container, ensure you have created the nativebuildimage above. Then run the following
+#  from an (elevated) Windows PowerShell prompt:
+#
+#    >>   docker run --rm nativebuildimage hack\make.ps1 -DCO -PkgImports -GoFormat
+
+# To run the validation tests on the host, from the root of the repository, run the
+# following from a Windows PowerShell prompt (elevation is not required): (Note Go
+# must be installed to run these tests)
+#
+#    >>   hack\make.ps1 -DCO -PkgImports -GoFormat
+
+# -----------------------------------------------------------------------------------------
+
+
+#  To run unit tests, ensure you have created the nativebuildimage above. Then run the
+#  following from an (elevated) Windows PowerShell prompt:
+#
+#    >>   docker run --rm nativebuildimage hack\make.ps1 -TestUnit
+
+
+# -----------------------------------------------------------------------------------------
+
+
+#  To run all tests and binary build, ensure you have created the nativebuildimage above. Then 
+# run the following from an (elevated) Windows PowerShell prompt:
+#
+#    >>   docker run nativebuildimage hack\make.ps1 -All
 
 
 # -----------------------------------------------------------------------------------------
@@ -96,28 +131,26 @@
 # Important notes:
 # ---------------
 #
-# The posix utilities from git aren't usable interactively as at October 2016. This
-# is because they require a console window which isn't present in a container in Windows.
-# See the example at the top of this file. Do NOT use -it in that docker run. It will not work.
+# Don't attempt to use a bind-mount to pass a local directory as the bundles target
+# directory. It does not work (golang attempts for follow a mapped folder incorrectly). 
+# Instead, use docker cp as per the example.
 #
-# Don't attempt to use a volume for passing the source through to the container. The posix utilities will
-# balk at reparse points. 
+# go.zip is not removed from the image as it is used by the Windows CI servers
+# to ensure the host and image are running consistent versions of go.
 #
-# The downloaded files are not cleared from the image. go.zip is used by the Windows
-# CI servers to ensure the host and image are running consistent versions of go.
+# Nanoserver support is a work in progress. Although the image will build if the 
+# FROM statement is updated, it will not work when running autogen through hack\make.ps1. 
+# It is suspected that the required GCC utilities (eg gcc, windres, windmc) silently
+# quit due to the use of console hooks which are not available.
 #
-# The GIT installer isn't very good at unattended install. We use techniques described
-# at the links below to force it to set the path and other options accordingly. 
-# >> http://superuser.com/questions/944576/git-for-windows-silent-install-silent-arguments 
-# and follow through to installer at
-# >> https://github.com/ferventcoder/chocolatey-packages/blob/master/automatic/git.install/tools/chocolateyInstall.ps1
+# The docker integration tests do not currently run in a container on Windows, predominantly
+# due to Windows not supporting privileged mode, so anything using a volume would fail.
+# They (along with the rest of the docker CI suite) can be run using 
+# https://github.com/jhowardmsft/docker-w2wCIScripts/blob/master/runCI/Invoke-DockerCI.ps1.
 #
-# As of October 2016, this does not work on Windows 10 client, just Windows Server 2016,
-# and only with the default isolation mode (process). It does not work with isolation mode
-# set to Hyper-V containers (hyperv).
-
 # -----------------------------------------------------------------------------------------
 
+
 # The number of build steps below are explicitly minimised to improve performance.
 FROM microsoft/windowsservercore
 
@@ -128,55 +161,102 @@ SHELL ["powershell", "-command"]
 #  - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
 #  - FROM_DOCKERFILE is used for detection of building within a container.
 ENV GO_VERSION=1.7.3 `
-    GIT_LOCATION=https://github.com/git-for-windows/git/releases/download/v2.10.1.windows.1/Git-2.10.1-64-bit.exe `
+    GIT_VERSION=2.10.2 `
     GOPATH=C:\go `
     FROM_DOCKERFILE=1
 
-WORKDIR C:\
-
 RUN `
-  setx /M Path $($Env:PATH+';C:\gcc\bin;C:\go\bin'); `
-  `
   $ErrorActionPreference = 'Stop'; `
+  $ProgressPreference = 'SilentlyContinue'; `
+  `
+  Function Test-Nano() { `
+    $EditionId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID').EditionId; `
+    return (($EditionId -eq 'ServerStandardNano') -or ($EditionId -eq 'ServerDataCenterNano') -or ($EditionId -eq 'NanoServer')); `
+  }`
+  `
   Function Download-File([string] $source, [string] $target) { `
-    $wc = New-Object net.webclient; $wc.Downloadfile($source, $target) `
+    if (Test-Nano) { `
+      $handler = New-Object System.Net.Http.HttpClientHandler; `
+      $client = New-Object System.Net.Http.HttpClient($handler); `
+      $client.Timeout = New-Object System.TimeSpan(0, 30, 0); `
+      $cancelTokenSource = [System.Threading.CancellationTokenSource]::new(); `
+      $responseMsg = $client.GetAsync([System.Uri]::new($source), $cancelTokenSource.Token); `
+      $responseMsg.Wait(); `
+      if (!$responseMsg.IsCanceled) { `
+        $response = $responseMsg.Result; `
+        if ($response.IsSuccessStatusCode) { `
+          $downloadedFileStream = [System.IO.FileStream]::new($target, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write); `
+          $copyStreamOp = $response.Content.CopyToAsync($downloadedFileStream); `
+          $copyStreamOp.Wait(); `
+          $downloadedFileStream.Close(); `
+          if ($copyStreamOp.Exception -ne $null) { throw $copyStreamOp.Exception } `
+        } `
+      } else { `
+      Throw ("Failed to download " + $source) `
+      }`
+    } else { `
+      $webClient = New-Object System.Net.WebClient; `
+      $webClient.DownloadFile($source, $target); `
+    } `
   } `
   `
+  setx /M PATH $('C:\git\bin;C:\git\usr\bin;'+$Env:PATH+';C:\gcc\bin;C:\go\bin'); `
+  `
   Write-Host INFO: Downloading git...; `
-  Download-File $Env:GIT_LOCATION gitsetup.exe; `
+  $location='https://github.com/git-for-windows/git/releases/download/v'+$env:GIT_VERSION+'.windows.1/PortableGit-'+$env:GIT_VERSION+'-64-bit.7z.exe'; `
+  Download-File $location C:\gitsetup.7z.exe; `
   `
   Write-Host INFO: Downloading go...; `
-  Download-File $('https://golang.org/dl/go'+$Env:GO_VERSION+'.windows-amd64.zip') go.zip; `
+  Download-File $('https://golang.org/dl/go'+$Env:GO_VERSION+'.windows-amd64.zip') C:\go.zip; `
   `
   Write-Host INFO: Downloading compiler 1 of 3...; `
-  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/gcc.zip gcc.zip; `
+  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/gcc.zip C:\gcc.zip; `
   `
   Write-Host INFO: Downloading compiler 2 of 3...; `
-  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/runtime.zip runtime.zip; `
+  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/runtime.zip C:\runtime.zip; `
   `
   Write-Host INFO: Downloading compiler 3 of 3...; `
-  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/binutils.zip binutils.zip; `
+  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/binutils.zip C:\binutils.zip; `
   `
-  Write-Host INFO: Installing git...; `
-  $installPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'; `
-  $installItem = 'Git_is1'; `
-  New-Item -Path $installPath -Name $installItem -Force; `
-  $installKey = $installPath+'\'+$installItem; `
-  New-ItemProperty $installKey -Name 'Inno Setup CodeFile: Path Option' -Value 'CmdTools' -PropertyType 'String' -Force; `
-  New-ItemProperty $installKey -Name 'Inno Setup CodeFile: Bash Terminal Option' -Value 'ConHost' -PropertyType 'String' -Force; `
-  New-ItemProperty $installKey -Name 'Inno Setup CodeFile: CRLF Option' -Value 'CRLFCommitAsIs' -PropertyType 'String' -Force; `
-  Start-Process gitsetup.exe -ArgumentList '/VERYSILENT /SUPPRESSMSGBOXES /CLOSEAPPLICATIONS /DIR=C:\git\' -Wait; `
+  Write-Host INFO: Installing PS7Zip package...; `
+  Install-Package PS7Zip -Force | Out-Null; `
+  Write-Host INFO: Importing PS7Zip...; `
+  Import-Module PS7Zip -Force; `
+  New-Item C:\git -ItemType Directory | Out-Null ; `
+  cd C:\git; `
+  Write-Host INFO: Extracting git...; `
+  Expand-7Zip C:\gitsetup.7z.exe | Out-Null; `
+  cd C:\; `
   `
   Write-Host INFO: Expanding go...; `
   Expand-Archive C:\go.zip -DestinationPath C:\; `
   `
-  Write-Host INFO: Expanding compiler...; `
+  Write-Host INFO: Expanding compiler 1 of 3...; `
   Expand-Archive C:\gcc.zip -DestinationPath C:\gcc -Force; `
+  Write-Host INFO: Expanding compiler 2 of 3...; `
   Expand-Archive C:\runtime.zip -DestinationPath C:\gcc -Force; `
+  Write-Host INFO: Expanding compiler 3 of 3...; `
   Expand-Archive C:\binutils.zip -DestinationPath C:\gcc -Force; `
   `
+  Write-Host INFO: Removing downloaded files...; `
+  Remove-Item C:\gcc.zip; `
+  Remove-Item C:\runtime.zip; `
+  Remove-Item C:\binutils.zip; `
+  Remove-Item C:\gitsetup.7z.exe; `
+  `
+  Write-Host INFO: Creating source directory...; `
+  New-Item -ItemType Directory -Path C:\go\src\github.com\docker\docker | Out-Null; `
+  `
+  Write-Host INFO: Configuring git core.autocrlf...; `
+  C:\git\bin\git config --global core.autocrlf true; `
+  `
   Write-Host INFO: Completed
 
-# Prepare for building
-COPY . C:\go\src\github.com\docker\docker
+# Make PowerShell the default entrypoint
+ENTRYPOINT ["powershell.exe"]
+
+# Set the working directory to the location of the sources
+WORKDIR C:\go\src\github.com\docker\docker
 
+# Copy the sources into the container
+COPY . .

+ 401 - 0
hack/make.ps1

@@ -0,0 +1,401 @@
+<#
+.NOTES
+    Author:  @jhowardmsft
+
+    Summary: Windows native build script. This is similar to functionality provided
+             by hack\make.sh, but uses native Windows PowerShell semantics. It does
+             not support the full set of options provided by the Linux counterpart.
+             For example:
+             
+             - You can't cross-build Linux docker binaries on Windows
+             - Hashes aren't generated on binaries
+             - 'Releasing' isn't supported.
+             - Integration tests. This is because they currently cannot run inside a container,
+               and require significant external setup. 
+             
+             It does however provided the minimum necessary to support parts of local Windows 
+             development and Windows to Windows CI.
+
+             Usage Examples (run from repo root): 
+                "hack\make.ps1 -Binary" to build the binaries
+                "hack\make.ps1 -Client" to build just the client 64-bit binary
+                "hack\make.ps1 -TestUnit" to run unit tests
+                "hack\make.ps1 -Binary -TestUnit" to build the binaries and run unit tests
+                "hack\make.ps1 -All" to run everything this script knows about
+
+.PARAMETER Client
+     Builds the client binaries.
+
+.PARAMETER Daemon
+     Builds the daemon binary.
+
+.PARAMETER Binary
+     Builds the client binaries and the daemon binary. A convenient shortcut to `make.ps1 -Client -Daemon`.
+
+.PARAMETER Race
+     Use -race in go build and go test.
+
+.PARAMETER Noisy
+     Use -v in go build.
+
+.PARAMETER ForceBuildAll
+     Use -a in go build.
+
+.PARAMETER NoOpt
+     Use -gcflags -N -l in go build to disable optimisation (can aide debugging).
+
+.PARAMETER CommitSuffix
+     Adds a custom string to be appended to the commit ID (spaces are stripped).
+
+.PARAMETER DCO
+     Runs the DCO (Developer Certificate Of Origin) test.
+
+.PARAMETER PkgImports
+     Runs the pkg\ directory imports test.
+
+.PARAMETER GoFormat
+     Runs the Go formatting test.
+
+.PARAMETER TestUnit
+     Runs unit tests.
+
+.PARAMETER All
+     Runs everything this script knows about.
+
+
+TODO
+- Unify the head commit
+- Sort out the GITCOMMIT environment variable in the absense of a .git (longer term)
+- Add golint and other checks (swagger maybe?)
+
+#>
+
+
+param(
+    [Parameter(Mandatory=$False)][switch]$Client,
+    [Parameter(Mandatory=$False)][switch]$Daemon,
+    [Parameter(Mandatory=$False)][switch]$Binary,
+    [Parameter(Mandatory=$False)][switch]$Race,
+    [Parameter(Mandatory=$False)][switch]$Noisy,
+    [Parameter(Mandatory=$False)][switch]$ForceBuildAll,
+    [Parameter(Mandatory=$False)][switch]$NoOpt,
+    [Parameter(Mandatory=$False)][string]$CommitSuffix="",
+    [Parameter(Mandatory=$False)][switch]$DCO,
+    [Parameter(Mandatory=$False)][switch]$PkgImports,
+    [Parameter(Mandatory=$False)][switch]$GoFormat,
+    [Parameter(Mandatory=$False)][switch]$TestUnit,
+    [Parameter(Mandatory=$False)][switch]$All
+)
+
+$ErrorActionPreference = "Stop"
+$pushed=$False  # To restore the directory if we have temporarily pushed to one.
+
+# Utility function to get the commit ID of the repository
+Function Get-GitCommit() {
+    if (-not (Test-Path ".\.git")) {
+        # If we don't have a .git directory, but we do have the environment
+        # variable DOCKER_GITCOMMIT set, that can override it.
+        if ($env:DOCKER_GITCOMMIT.Length -eq 0) {
+            Throw ".git directory missing and DOCKER_GITCOMMIT environment variable not specified."
+        }
+        Write-Host "INFO: Git commit assumed from DOCKER_GITCOMMIT environment variable"
+        return $env:DOCKER_GITCOMMIT
+    }
+    $gitCommit=$(git rev-parse --short HEAD)
+    if ($(git status --porcelain --untracked-files=no).Length -ne 0) {
+        $gitCommit="$gitCommit-unsupported"
+        Write-Host ""
+        Write-Warning "This version is unsupported because there are uncommitted file(s)."
+        Write-Warning "Either commit these changes, or add them to .gitignore."
+        git status --porcelain --untracked-files=no | Write-Warning
+        Write-Host ""
+    }
+    return $gitCommit
+}
+
+# Utility function to get get the current build version of docker
+Function Get-DockerVersion() {
+    if (-not (Test-Path ".\VERSION")) { Throw "VERSION file not found. Is this running from the root of a docker repository?" }
+    return $(Get-Content ".\VERSION" -raw).ToString().Replace("`n","").Trim()
+}
+
+# Utility function to determine if we are running in a container or not.
+# In Windows, we get this through an environment variable set in `Dockerfile.Windows`
+Function Check-InContainer() {
+    if ($env:FROM_DOCKERFILE.Length -eq 0) {
+        Write-Host ""
+        Write-Warning "Not running in a container. The result might be an incorrect build."
+        Write-Host ""
+    }
+}
+
+# Utility function to get the commit for HEAD
+Function Get-HeadCommit() {
+    $head = Invoke-Expression "git rev-parse --verify HEAD"
+    if ($LASTEXITCODE -ne 0) { Throw "Failed getting HEAD commit" }
+
+    return $head
+}
+
+# Utility function to get the commit for upstream
+Function Get-UpstreamCommit() {
+    Invoke-Expression "git fetch -q https://github.com/docker/docker.git refs/heads/master"
+    if ($LASTEXITCODE -ne 0) { Throw "Failed fetching" }
+
+    $upstream = Invoke-Expression "git rev-parse --verify FETCH_HEAD"
+    if ($LASTEXITCODE -ne 0) { Throw "Failed getting upstream commit" }
+
+    return $upstream
+}
+
+# Build a binary (client or daemon)
+Function Execute-Build($type, $additionalBuildTags, $directory) {
+    # Generate the build flags
+    $buildTags = "autogen"
+    if ($Noisy)                     { $verboseParm=" -v" }
+    if ($Race)                      { Write-Warning "Using race detector"; $raceParm=" -race"}
+    if ($ForceBuildAll)             { $allParm=" -a" }
+    if ($NoOpt)                     { $optParm=" -gcflags "+""""+"-N -l"+"""" }
+    if ($addtionalBuildTags -ne "") { $buildTags += $(" " + $additionalBuildTags) }
+
+    # Do the go build in the appropriate directory
+    # Note -linkmode=internal is required to be able to debug on Windows.
+    # https://github.com/golang/go/issues/14319#issuecomment-189576638
+    Write-Host "INFO: Building $type..."
+    Push-Location $root\cmd\$directory; $global:pushed=$True
+    $buildCommand = "go build" + `
+                    $raceParm + `
+                    $verboseParm + `
+                    $allParm + `
+                    $optParm + `
+                    " -tags """ + $buildTags + """" + `
+                    " -ldflags """ + "-linkmode=internal" + """" + `
+                    " -o $root\bundles\"+$directory+".exe"
+    Invoke-Expression $buildCommand
+    if ($LASTEXITCODE -ne 0) { Throw "Failed to compile $type" }
+    Pop-Location; $global:pushed=$False
+}
+
+# Validates the DCO marker is present on each commit
+Function Validate-DCO($headCommit, $upstreamCommit) {
+    Write-Host "INFO: Validating Developer Certificate of Origin..."
+    # Username may only contain alphanumeric characters or dashes and cannot begin with a dash
+    $usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+'
+
+    $dcoPrefix="Signed-off-by:"
+    $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($githubUsernameRegex)\\))?$"
+
+    $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit"
+    if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" }
+
+    # Counts of adds and deletes after removing multiple white spaces. AWK anyone? :(
+    $adds=0; $dels=0; $($counts -replace '\s+', ' ') | %{ $a=$_.Split(" "); $adds+=[int]$a[0]; $dels+=[int]$a[1] }
+    if (($adds -eq 0) -and ($dels -eq 0)) { 
+        Write-Warning "DCO validation - nothing to validate!"
+        return
+    }
+
+    $commits = Invoke-Expression "git log  $upstreamCommit..$headCommit --format=format:%H%n"
+    if ($LASTEXITCODE -ne 0) { Throw "Failed git log --format" }
+    $commits = $($commits -split '\s+' -match '\S')
+    $badCommits=@()
+    $commits | %{ 
+        # Skip commits with no content such as merge commits etc
+        if ($(git log -1 --format=format: --name-status $_).Length -gt 0) {
+            # Ignore exit code on next call - always process regardless
+            $commitMessage = Invoke-Expression "git log -1 --format=format:%B --name-status $_"
+            if (($commitMessage -match $dcoRegex).Length -eq 0) { $badCommits+=$_ }
+        }
+    }
+    if ($badCommits.Length -eq 0) {
+        Write-Host "Congratulations!  All commits are properly signed with the DCO!"
+    } else {
+        $e = "`nThese commits do not have a proper '$dcoPrefix' marker:`n"
+        $badCommits | %{ $e+=" - $_`n"}
+        $e += "`nPlease amend each commit to include a properly formatted DCO marker.`n`n"
+        $e += "Visit the following URL for information about the Docker DCO:`n"
+        $e += "https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work`n"
+        Throw $e
+    }
+}
+
+# Validates that .\pkg\... is safely isolated from internal code
+Function Validate-PkgImports($headCommit, $upstreamCommit) {
+    Write-Host "INFO: Validating pkg import isolation..."
+
+    # Get a list of go source-code files which have changed under pkg\. Ignore exit code on next call - always process regardless
+    $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'pkg\*.go`'"
+    $badFiles=@(); $files | %{
+        $file=$_
+        # For the current changed file, get its list of dependencies, sorted and uniqued.
+        $imports = Invoke-Expression "go list -e -f `'{{ .Deps }}`' $file"
+        if ($LASTEXITCODE -ne 0) { Throw "Failed go list for dependencies on $file" }
+        $imports = $imports -Replace "\[" -Replace "\]", "" -Split(" ") | Sort-Object | Get-Unique 
+        # Filter out what we are looking for
+        $imports = $imports -NotMatch "^github.com/docker/docker/pkg/" `
+                            -NotMatch "^github.com/docker/docker/vendor" `
+                            -Match "^github.com/docker/docker" `
+                            -Replace "`n", ""
+        $imports | % { $badFiles+="$file imports $_`n" }
+    }
+    if ($badFiles.Length -eq 0) {
+        Write-Host 'Congratulations!  ".\pkg\*.go" is safely isolated from internal code.'
+    } else {
+        $e = "`nThese files import internal code: (either directly or indirectly)`n"
+        $badFiles | %{ $e+=" - $_"}
+        Throw $e
+    }
+}
+
+# Validates that changed files are correctly go-formatted
+Function Validate-GoFormat($headCommit, $upstreamCommit) {
+    Write-Host "INFO: Validating go formatting on changed files..."
+
+    # Verify gofmt is installed
+    if ($(Get-Command gofmt -ErrorAction SilentlyContinue) -eq $nil) { Throw "gofmt does not appear to be installed" }
+
+    # Get a list of all go source-code files which have changed.  Ignore exit code on next call - always process regardless
+    $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'*.go`'" 
+    $files = $files | Select-String -NotMatch "^vendor/"
+    $badFiles=@(); $files | %{
+        # Deliberately ignore error on next line - treat as failed
+        $content=Invoke-Expression "git show $headCommit`:$_" 
+
+        # Next set of hoops are to ensure we have LF not CRLF semantics as otherwise gofmt on Windows will not succeed.
+        # Also note that gofmt on Windows does not appear to support stdin piping correctly. Hence go through a temporary file.
+        $content=$content -join "`n"
+        $content+="`n"
+        $outputFile=[System.IO.Path]::GetTempFileName()
+        if (Test-Path $outputFile) { Remove-Item $outputFile }
+        [System.IO.File]::WriteAllText($outputFile, $content, (New-Object System.Text.UTF8Encoding($False)))
+        $valid=Invoke-Expression "gofmt -s -l $outputFile"
+        Write-Host "Checking $outputFile"
+        if ($valid.Length -ne 0) { $badFiles+=$_ }
+        if (Test-Path $outputFile) { Remove-Item $outputFile }
+    }
+    if ($badFiles.Length -eq 0) {
+        Write-Host 'Congratulations!  All Go source files are properly formatted.'
+    } else {
+        $e = "`nThese files are not properly gofmt`'d:`n"
+        $badFiles | %{ $e+=" - $_`n"}
+        $e+= "`nPlease reformat the above files using `"gofmt -s -w`" and commit the result."
+        Throw $e
+    }
+}
+
+# Run the unit tests
+Function Run-UnitTests() {
+    Write-Host "INFO: Running unit tests..."
+    $testPath="./..."
+    $goListCommand = "go list -e -f '{{if ne .Name """ + '\"github.com/docker/docker\"' + """}}{{.ImportPath}}{{end}}' $testPath"
+    $pkgList = $(Invoke-Expression $goListCommand)
+    if ($LASTEXITCODE -ne 0) { Throw "go list for unit tests failed" }
+    $pkgList = $pkgList | Select-String -Pattern "github.com/docker/docker"
+    $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/vendor"
+    $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/man"
+    $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/integration-cli"
+    $pkgList = $pkgList -replace "`r`n", " "
+    $goTestCommand = "go test" + $raceParm + " -cover -ldflags -w -tags """ + "autogen daemon" + """ -a """ + "-test.timeout=10m" + """ $pkgList"
+    Invoke-Expression $goTestCommand
+    if ($LASTEXITCODE -ne 0) { Throw "Unit tests failed" }
+}
+
+# Start of main code.
+Try {
+    Write-Host -ForegroundColor Cyan "INFO: make.ps1 starting at $(Get-Date)"
+    $root=$(pwd)
+
+    # Handle the "-All" shortcut to turn on all things we can handle.
+    if ($All) { $Client=$True; $Daemon=$True; $DCO=$True; $PkgImports=$True; $GoFormat=$True; $TestUnit=$True }
+
+    # Handle the "-Binary" shortcut to build both client and daemon.
+    if ($Binary) { $Client = $True; $Daemon = $True }
+
+    # Make sure we have something to do
+    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' }
+
+    # Verify git is installed
+    if ($(Get-Command git -ErrorAction SilentlyContinue) -eq $nil) { Throw "Git does not appear to be installed" }
+
+    # Verify go is installed
+    if ($(Get-Command go -ErrorAction SilentlyContinue) -eq $nil) { Throw "GoLang does not appear to be installed" }
+
+    # Get the git commit. This will also verify if we are in a repo or not. Then add a custom string if supplied.
+    $gitCommit=Get-GitCommit
+    if ($CommitSuffix -ne "") { $gitCommit += "-"+$CommitSuffix -Replace ' ', '' }
+
+    # Get the version of docker (eg 1.14.0-dev)
+    $dockerVersion=Get-DockerVersion
+
+    # Give a warning if we are not running in a container and are building binaries or running unit tests. 
+    # Not relevant for validation tests as these are fine to run outside of a container.
+    if ($Client -or $Daemon -or $TestUnit) { Check-InContainer }
+
+    # Verify GOPATH is set
+    if ($env:GOPATH.Length -eq 0) { Throw "Missing GOPATH environment variable. See https://golang.org/doc/code.html#GOPATH" }
+
+    # Run autogen if building binaries or running unit tests.
+    if ($Client -or $Daemon -or $TestUnit) {
+        Write-Host "INFO: Invoking autogen..."
+        Try { .\hack\make\.go-autogen.ps1 -CommitString $gitCommit -DockerVersion $dockerVersion }
+        Catch [Exception] { Throw $_ }
+    }
+
+    # DCO, Package import and Go formatting tests. 
+    if ($DCO -or $PkgImports -or $GoFormat) {
+        # We need the head and upstream commits for these
+        $headCommit=Get-HeadCommit
+        $upstreamCommit=Get-UpstreamCommit
+
+        # Run DCO validation
+        if ($DCO) { Validate-DCO $headCommit $upstreamCommit }
+
+        # Run `gofmt` validation
+        if ($GoFormat) { Validate-GoFormat $headCommit $upstreamCommit }
+
+        # Run pkg isolation validation
+        if ($PkgImports) { Validate-PkgImports $headCommit $upstreamCommit }
+    }
+
+    # Build the binaries
+    if ($Client -or $Daemon) {
+        # Create the bundles directory if it doesn't exist
+        if (-not (Test-Path ".\bundles")) { New-Item ".\bundles" -ItemType Directory | Out-Null }
+
+        # Perform the actual build
+        if ($Daemon) { Execute-Build "daemon" "daemon" "dockerd" }
+        if ($Client) { Execute-Build "client" "" "docker" }
+    }
+
+    # Run unit tests
+    if ($TestUnit) { Run-UnitTests }
+
+    # Gratuitous ASCII art.
+    if ($Daemon -or $Client) {
+        Write-Host
+        Write-Host -ForegroundColor Green " ________   ____  __."
+        Write-Host -ForegroundColor Green " \_____  \ `|    `|/ _`|"
+        Write-Host -ForegroundColor Green " /   `|   \`|      `<"
+        Write-Host -ForegroundColor Green " /    `|    \    `|  \"
+        Write-Host -ForegroundColor Green " \_______  /____`|__ \"
+        Write-Host -ForegroundColor Green "         \/        \/"
+        Write-Host
+    }
+}
+Catch [Exception] {
+    Write-Host -ForegroundColor Red ("`nERROR: make.ps1 failed:`n$_")
+
+    # More gratuitous ASCII art.
+    Write-Host
+    Write-Host -ForegroundColor Red  "___________      .__.__             .___"
+    Write-Host -ForegroundColor Red  "\_   _____/____  `|__`|  `|   ____   __`| _/"
+    Write-Host -ForegroundColor Red  " `|    __) \__  \ `|  `|  `| _/ __ \ / __ `| "
+    Write-Host -ForegroundColor Red  " `|     \   / __ \`|  `|  `|_\  ___// /_/ `| "
+    Write-Host -ForegroundColor Red  " \___  /  (____  /__`|____/\___  `>____ `| "
+    Write-Host -ForegroundColor Red  "     \/        \/             \/     \/ "
+    Write-Host
+}
+Finally {
+    if ($global:pushed) { Pop-Location }
+    Write-Host -ForegroundColor Cyan "INFO: make.ps1 ended at $(Get-Date)"
+}

+ 1 - 7
hack/make/.go-autogen.ps1

@@ -10,16 +10,11 @@
 
 .PARAMETER DockerVersion
      The version such as 1.14.0-dev. This is calculated externally to this script.
-
-.PARAMETER StaticSQLite
-     A string indicating if the daemon binary is compiled with the SQLite
-     sources compiled in. This is calculated externally to this script.
 #>
 
 param(
     [Parameter(Mandatory=$true)][string]$CommitString,
-    [Parameter(Mandatory=$true)][string]$DockerVersion,
-    [Parameter(Mandatory=$true)][string]$StaticSQLiteString
+    [Parameter(Mandatory=$true)][string]$DockerVersion
 )
 
 $ErrorActionPreference = "Stop"
@@ -48,7 +43,6 @@ const (
     GitCommit          string = "'+$CommitString+'"
     Version            string = "'+$DockerVersion+'"
     BuildTime          string = "'+$buildDateTime+'"
-    StaticSQLite       string = "'+$StaticSQLiteString+'"
 )
 
 // AUTOGENERATED FILE; see hack\make\.go-autogen.ps1