make.ps1 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <#
  2. .NOTES
  3. Author: @jhowardmsft
  4. Summary: Windows native build script. This is similar to functionality provided
  5. by hack\make.sh, but uses native Windows PowerShell semantics. It does
  6. not support the full set of options provided by the Linux counterpart.
  7. For example:
  8. - You can't cross-build Linux docker binaries on Windows
  9. - Hashes aren't generated on binaries
  10. - 'Releasing' isn't supported.
  11. - Integration tests. This is because they currently cannot run inside a container,
  12. and require significant external setup.
  13. It does however provided the minimum necessary to support parts of local Windows
  14. development and Windows to Windows CI.
  15. Usage Examples (run from repo root):
  16. "hack\make.ps1 -Binary" to build the binaries
  17. "hack\make.ps1 -Client" to build just the client 64-bit binary
  18. "hack\make.ps1 -TestUnit" to run unit tests
  19. "hack\make.ps1 -Binary -TestUnit" to build the binaries and run unit tests
  20. "hack\make.ps1 -All" to run everything this script knows about
  21. .PARAMETER Client
  22. Builds the client binaries.
  23. .PARAMETER Daemon
  24. Builds the daemon binary.
  25. .PARAMETER Binary
  26. Builds the client binaries and the daemon binary. A convenient shortcut to `make.ps1 -Client -Daemon`.
  27. .PARAMETER Race
  28. Use -race in go build and go test.
  29. .PARAMETER Noisy
  30. Use -v in go build.
  31. .PARAMETER ForceBuildAll
  32. Use -a in go build.
  33. .PARAMETER NoOpt
  34. Use -gcflags -N -l in go build to disable optimisation (can aide debugging).
  35. .PARAMETER CommitSuffix
  36. Adds a custom string to be appended to the commit ID (spaces are stripped).
  37. .PARAMETER DCO
  38. Runs the DCO (Developer Certificate Of Origin) test.
  39. .PARAMETER PkgImports
  40. Runs the pkg\ directory imports test.
  41. .PARAMETER GoFormat
  42. Runs the Go formatting test.
  43. .PARAMETER TestUnit
  44. Runs unit tests.
  45. .PARAMETER All
  46. Runs everything this script knows about.
  47. TODO
  48. - Unify the head commit
  49. - Sort out the GITCOMMIT environment variable in the absense of a .git (longer term)
  50. - Add golint and other checks (swagger maybe?)
  51. #>
  52. param(
  53. [Parameter(Mandatory=$False)][switch]$Client,
  54. [Parameter(Mandatory=$False)][switch]$Daemon,
  55. [Parameter(Mandatory=$False)][switch]$Binary,
  56. [Parameter(Mandatory=$False)][switch]$Race,
  57. [Parameter(Mandatory=$False)][switch]$Noisy,
  58. [Parameter(Mandatory=$False)][switch]$ForceBuildAll,
  59. [Parameter(Mandatory=$False)][switch]$NoOpt,
  60. [Parameter(Mandatory=$False)][string]$CommitSuffix="",
  61. [Parameter(Mandatory=$False)][switch]$DCO,
  62. [Parameter(Mandatory=$False)][switch]$PkgImports,
  63. [Parameter(Mandatory=$False)][switch]$GoFormat,
  64. [Parameter(Mandatory=$False)][switch]$TestUnit,
  65. [Parameter(Mandatory=$False)][switch]$All
  66. )
  67. $ErrorActionPreference = "Stop"
  68. $pushed=$False # To restore the directory if we have temporarily pushed to one.
  69. # Utility function to get the commit ID of the repository
  70. Function Get-GitCommit() {
  71. if (-not (Test-Path ".\.git")) {
  72. # If we don't have a .git directory, but we do have the environment
  73. # variable DOCKER_GITCOMMIT set, that can override it.
  74. if ($env:DOCKER_GITCOMMIT.Length -eq 0) {
  75. Throw ".git directory missing and DOCKER_GITCOMMIT environment variable not specified."
  76. }
  77. Write-Host "INFO: Git commit assumed from DOCKER_GITCOMMIT environment variable"
  78. return $env:DOCKER_GITCOMMIT
  79. }
  80. $gitCommit=$(git rev-parse --short HEAD)
  81. if ($(git status --porcelain --untracked-files=no).Length -ne 0) {
  82. $gitCommit="$gitCommit-unsupported"
  83. Write-Host ""
  84. Write-Warning "This version is unsupported because there are uncommitted file(s)."
  85. Write-Warning "Either commit these changes, or add them to .gitignore."
  86. git status --porcelain --untracked-files=no | Write-Warning
  87. Write-Host ""
  88. }
  89. return $gitCommit
  90. }
  91. # Utility function to get get the current build version of docker
  92. Function Get-DockerVersion() {
  93. if (-not (Test-Path ".\VERSION")) { Throw "VERSION file not found. Is this running from the root of a docker repository?" }
  94. return $(Get-Content ".\VERSION" -raw).ToString().Replace("`n","").Trim()
  95. }
  96. # Utility function to determine if we are running in a container or not.
  97. # In Windows, we get this through an environment variable set in `Dockerfile.Windows`
  98. Function Check-InContainer() {
  99. if ($env:FROM_DOCKERFILE.Length -eq 0) {
  100. Write-Host ""
  101. Write-Warning "Not running in a container. The result might be an incorrect build."
  102. Write-Host ""
  103. }
  104. }
  105. # Utility function to get the commit for HEAD
  106. Function Get-HeadCommit() {
  107. $head = Invoke-Expression "git rev-parse --verify HEAD"
  108. if ($LASTEXITCODE -ne 0) { Throw "Failed getting HEAD commit" }
  109. return $head
  110. }
  111. # Utility function to get the commit for upstream
  112. Function Get-UpstreamCommit() {
  113. Invoke-Expression "git fetch -q https://github.com/docker/docker.git refs/heads/master"
  114. if ($LASTEXITCODE -ne 0) { Throw "Failed fetching" }
  115. $upstream = Invoke-Expression "git rev-parse --verify FETCH_HEAD"
  116. if ($LASTEXITCODE -ne 0) { Throw "Failed getting upstream commit" }
  117. return $upstream
  118. }
  119. # Build a binary (client or daemon)
  120. Function Execute-Build($type, $additionalBuildTags, $directory) {
  121. # Generate the build flags
  122. $buildTags = "autogen"
  123. if ($Noisy) { $verboseParm=" -v" }
  124. if ($Race) { Write-Warning "Using race detector"; $raceParm=" -race"}
  125. if ($ForceBuildAll) { $allParm=" -a" }
  126. if ($NoOpt) { $optParm=" -gcflags "+""""+"-N -l"+"""" }
  127. if ($addtionalBuildTags -ne "") { $buildTags += $(" " + $additionalBuildTags) }
  128. # Do the go build in the appropriate directory
  129. # Note -linkmode=internal is required to be able to debug on Windows.
  130. # https://github.com/golang/go/issues/14319#issuecomment-189576638
  131. Write-Host "INFO: Building $type..."
  132. Push-Location $root\cmd\$directory; $global:pushed=$True
  133. $buildCommand = "go build" + `
  134. $raceParm + `
  135. $verboseParm + `
  136. $allParm + `
  137. $optParm + `
  138. " -tags """ + $buildTags + """" + `
  139. " -ldflags """ + "-linkmode=internal" + """" + `
  140. " -o $root\bundles\"+$directory+".exe"
  141. Invoke-Expression $buildCommand
  142. if ($LASTEXITCODE -ne 0) { Throw "Failed to compile $type" }
  143. Pop-Location; $global:pushed=$False
  144. }
  145. # Validates the DCO marker is present on each commit
  146. Function Validate-DCO($headCommit, $upstreamCommit) {
  147. Write-Host "INFO: Validating Developer Certificate of Origin..."
  148. # Username may only contain alphanumeric characters or dashes and cannot begin with a dash
  149. $usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+'
  150. $dcoPrefix="Signed-off-by:"
  151. $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($githubUsernameRegex)\\))?$"
  152. $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit"
  153. if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" }
  154. # Counts of adds and deletes after removing multiple white spaces. AWK anyone? :(
  155. $adds=0; $dels=0; $($counts -replace '\s+', ' ') | %{ $a=$_.Split(" "); $adds+=[int]$a[0]; $dels+=[int]$a[1] }
  156. if (($adds -eq 0) -and ($dels -eq 0)) {
  157. Write-Warning "DCO validation - nothing to validate!"
  158. return
  159. }
  160. $commits = Invoke-Expression "git log $upstreamCommit..$headCommit --format=format:%H%n"
  161. if ($LASTEXITCODE -ne 0) { Throw "Failed git log --format" }
  162. $commits = $($commits -split '\s+' -match '\S')
  163. $badCommits=@()
  164. $commits | %{
  165. # Skip commits with no content such as merge commits etc
  166. if ($(git log -1 --format=format: --name-status $_).Length -gt 0) {
  167. # Ignore exit code on next call - always process regardless
  168. $commitMessage = Invoke-Expression "git log -1 --format=format:%B --name-status $_"
  169. if (($commitMessage -match $dcoRegex).Length -eq 0) { $badCommits+=$_ }
  170. }
  171. }
  172. if ($badCommits.Length -eq 0) {
  173. Write-Host "Congratulations! All commits are properly signed with the DCO!"
  174. } else {
  175. $e = "`nThese commits do not have a proper '$dcoPrefix' marker:`n"
  176. $badCommits | %{ $e+=" - $_`n"}
  177. $e += "`nPlease amend each commit to include a properly formatted DCO marker.`n`n"
  178. $e += "Visit the following URL for information about the Docker DCO:`n"
  179. $e += "https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work`n"
  180. Throw $e
  181. }
  182. }
  183. # Validates that .\pkg\... is safely isolated from internal code
  184. Function Validate-PkgImports($headCommit, $upstreamCommit) {
  185. Write-Host "INFO: Validating pkg import isolation..."
  186. # Get a list of go source-code files which have changed under pkg\. Ignore exit code on next call - always process regardless
  187. $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'pkg\*.go`'"
  188. $badFiles=@(); $files | %{
  189. $file=$_
  190. # For the current changed file, get its list of dependencies, sorted and uniqued.
  191. $imports = Invoke-Expression "go list -e -f `'{{ .Deps }}`' $file"
  192. if ($LASTEXITCODE -ne 0) { Throw "Failed go list for dependencies on $file" }
  193. $imports = $imports -Replace "\[" -Replace "\]", "" -Split(" ") | Sort-Object | Get-Unique
  194. # Filter out what we are looking for
  195. $imports = $imports -NotMatch "^github.com/docker/docker/pkg/" `
  196. -NotMatch "^github.com/docker/docker/vendor" `
  197. -Match "^github.com/docker/docker" `
  198. -Replace "`n", ""
  199. $imports | % { $badFiles+="$file imports $_`n" }
  200. }
  201. if ($badFiles.Length -eq 0) {
  202. Write-Host 'Congratulations! ".\pkg\*.go" is safely isolated from internal code.'
  203. } else {
  204. $e = "`nThese files import internal code: (either directly or indirectly)`n"
  205. $badFiles | %{ $e+=" - $_"}
  206. Throw $e
  207. }
  208. }
  209. # Validates that changed files are correctly go-formatted
  210. Function Validate-GoFormat($headCommit, $upstreamCommit) {
  211. Write-Host "INFO: Validating go formatting on changed files..."
  212. # Verify gofmt is installed
  213. if ($(Get-Command gofmt -ErrorAction SilentlyContinue) -eq $nil) { Throw "gofmt does not appear to be installed" }
  214. # Get a list of all go source-code files which have changed. Ignore exit code on next call - always process regardless
  215. $files=@(); $files = Invoke-Expression "git diff $upstreamCommit...$headCommit --diff-filter=ACMR --name-only -- `'*.go`'"
  216. $files = $files | Select-String -NotMatch "^vendor/"
  217. $badFiles=@(); $files | %{
  218. # Deliberately ignore error on next line - treat as failed
  219. $content=Invoke-Expression "git show $headCommit`:$_"
  220. # Next set of hoops are to ensure we have LF not CRLF semantics as otherwise gofmt on Windows will not succeed.
  221. # Also note that gofmt on Windows does not appear to support stdin piping correctly. Hence go through a temporary file.
  222. $content=$content -join "`n"
  223. $content+="`n"
  224. $outputFile=[System.IO.Path]::GetTempFileName()
  225. if (Test-Path $outputFile) { Remove-Item $outputFile }
  226. [System.IO.File]::WriteAllText($outputFile, $content, (New-Object System.Text.UTF8Encoding($False)))
  227. $valid=Invoke-Expression "gofmt -s -l $outputFile"
  228. Write-Host "Checking $outputFile"
  229. if ($valid.Length -ne 0) { $badFiles+=$_ }
  230. if (Test-Path $outputFile) { Remove-Item $outputFile }
  231. }
  232. if ($badFiles.Length -eq 0) {
  233. Write-Host 'Congratulations! All Go source files are properly formatted.'
  234. } else {
  235. $e = "`nThese files are not properly gofmt`'d:`n"
  236. $badFiles | %{ $e+=" - $_`n"}
  237. $e+= "`nPlease reformat the above files using `"gofmt -s -w`" and commit the result."
  238. Throw $e
  239. }
  240. }
  241. # Run the unit tests
  242. Function Run-UnitTests() {
  243. Write-Host "INFO: Running unit tests..."
  244. $testPath="./..."
  245. $goListCommand = "go list -e -f '{{if ne .Name """ + '\"github.com/docker/docker\"' + """}}{{.ImportPath}}{{end}}' $testPath"
  246. $pkgList = $(Invoke-Expression $goListCommand)
  247. if ($LASTEXITCODE -ne 0) { Throw "go list for unit tests failed" }
  248. $pkgList = $pkgList | Select-String -Pattern "github.com/docker/docker"
  249. $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/vendor"
  250. $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/man"
  251. $pkgList = $pkgList | Select-String -NotMatch "github.com/docker/docker/integration-cli"
  252. $pkgList = $pkgList -replace "`r`n", " "
  253. $goTestCommand = "go test" + $raceParm + " -cover -ldflags -w -tags """ + "autogen daemon" + """ -a """ + "-test.timeout=10m" + """ $pkgList"
  254. Invoke-Expression $goTestCommand
  255. if ($LASTEXITCODE -ne 0) { Throw "Unit tests failed" }
  256. }
  257. # Start of main code.
  258. Try {
  259. Write-Host -ForegroundColor Cyan "INFO: make.ps1 starting at $(Get-Date)"
  260. $root=$(pwd)
  261. # Handle the "-All" shortcut to turn on all things we can handle.
  262. if ($All) { $Client=$True; $Daemon=$True; $DCO=$True; $PkgImports=$True; $GoFormat=$True; $TestUnit=$True }
  263. # Handle the "-Binary" shortcut to build both client and daemon.
  264. if ($Binary) { $Client = $True; $Daemon = $True }
  265. # Make sure we have something to do
  266. 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' }
  267. # Verify git is installed
  268. if ($(Get-Command git -ErrorAction SilentlyContinue) -eq $nil) { Throw "Git does not appear to be installed" }
  269. # Verify go is installed
  270. if ($(Get-Command go -ErrorAction SilentlyContinue) -eq $nil) { Throw "GoLang does not appear to be installed" }
  271. # Get the git commit. This will also verify if we are in a repo or not. Then add a custom string if supplied.
  272. $gitCommit=Get-GitCommit
  273. if ($CommitSuffix -ne "") { $gitCommit += "-"+$CommitSuffix -Replace ' ', '' }
  274. # Get the version of docker (eg 1.14.0-dev)
  275. $dockerVersion=Get-DockerVersion
  276. # Give a warning if we are not running in a container and are building binaries or running unit tests.
  277. # Not relevant for validation tests as these are fine to run outside of a container.
  278. if ($Client -or $Daemon -or $TestUnit) { Check-InContainer }
  279. # Verify GOPATH is set
  280. if ($env:GOPATH.Length -eq 0) { Throw "Missing GOPATH environment variable. See https://golang.org/doc/code.html#GOPATH" }
  281. # Run autogen if building binaries or running unit tests.
  282. if ($Client -or $Daemon -or $TestUnit) {
  283. Write-Host "INFO: Invoking autogen..."
  284. Try { .\hack\make\.go-autogen.ps1 -CommitString $gitCommit -DockerVersion $dockerVersion }
  285. Catch [Exception] { Throw $_ }
  286. }
  287. # DCO, Package import and Go formatting tests.
  288. if ($DCO -or $PkgImports -or $GoFormat) {
  289. # We need the head and upstream commits for these
  290. $headCommit=Get-HeadCommit
  291. $upstreamCommit=Get-UpstreamCommit
  292. # Run DCO validation
  293. if ($DCO) { Validate-DCO $headCommit $upstreamCommit }
  294. # Run `gofmt` validation
  295. if ($GoFormat) { Validate-GoFormat $headCommit $upstreamCommit }
  296. # Run pkg isolation validation
  297. if ($PkgImports) { Validate-PkgImports $headCommit $upstreamCommit }
  298. }
  299. # Build the binaries
  300. if ($Client -or $Daemon) {
  301. # Create the bundles directory if it doesn't exist
  302. if (-not (Test-Path ".\bundles")) { New-Item ".\bundles" -ItemType Directory | Out-Null }
  303. # Perform the actual build
  304. if ($Daemon) { Execute-Build "daemon" "daemon" "dockerd" }
  305. if ($Client) { Execute-Build "client" "" "docker" }
  306. }
  307. # Run unit tests
  308. if ($TestUnit) { Run-UnitTests }
  309. # Gratuitous ASCII art.
  310. if ($Daemon -or $Client) {
  311. Write-Host
  312. Write-Host -ForegroundColor Green " ________ ____ __."
  313. Write-Host -ForegroundColor Green " \_____ \ `| `|/ _`|"
  314. Write-Host -ForegroundColor Green " / `| \`| `<"
  315. Write-Host -ForegroundColor Green " / `| \ `| \"
  316. Write-Host -ForegroundColor Green " \_______ /____`|__ \"
  317. Write-Host -ForegroundColor Green " \/ \/"
  318. Write-Host
  319. }
  320. }
  321. Catch [Exception] {
  322. Write-Host -ForegroundColor Red ("`nERROR: make.ps1 failed:`n$_")
  323. # More gratuitous ASCII art.
  324. Write-Host
  325. Write-Host -ForegroundColor Red "___________ .__.__ .___"
  326. Write-Host -ForegroundColor Red "\_ _____/____ `|__`| `| ____ __`| _/"
  327. Write-Host -ForegroundColor Red " `| __) \__ \ `| `| `| _/ __ \ / __ `| "
  328. Write-Host -ForegroundColor Red " `| \ / __ \`| `| `|_\ ___// /_/ `| "
  329. Write-Host -ForegroundColor Red " \___ / (____ /__`|____/\___ `>____ `| "
  330. Write-Host -ForegroundColor Red " \/ \/ \/ \/ "
  331. Write-Host
  332. }
  333. Finally {
  334. if ($global:pushed) { Pop-Location }
  335. Write-Host -ForegroundColor Cyan "INFO: make.ps1 ended at $(Get-Date)"
  336. }