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  }