update-vendor.sh 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/usr/bin/env bash
  2. # Copyright 2019 The Kubernetes Authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. set -o errexit
  16. set -o nounset
  17. set -o pipefail
  18. KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
  19. source "${KUBE_ROOT}/hack/lib/init.sh"
  20. # Explicitly opt into go modules, even though we're inside a GOPATH directory
  21. export GO111MODULE=on
  22. # Explicitly clear GOFLAGS, since GOFLAGS=-mod=vendor breaks dependency resolution while rebuilding vendor
  23. export GOFLAGS=
  24. # Ensure sort order doesn't depend on locale
  25. export LANG=C
  26. export LC_ALL=C
  27. # Detect problematic GOPROXY settings that prevent lookup of dependencies
  28. if [[ "${GOPROXY:-}" == "off" ]]; then
  29. kube::log::error "Cannot run hack/update-vendor.sh with \$GOPROXY=off"
  30. exit 1
  31. fi
  32. kube::golang::verify_go_version
  33. kube::util::require-jq
  34. TMP_DIR="${TMP_DIR:-$(mktemp -d /tmp/update-vendor.XXXX)}"
  35. LOG_FILE="${LOG_FILE:-${TMP_DIR}/update-vendor.log}"
  36. kube::log::status "logfile at ${LOG_FILE}"
  37. if [ -z "${BASH_XTRACEFD:-}" ]; then
  38. exec 19> "${LOG_FILE}"
  39. export BASH_XTRACEFD="19"
  40. set -x
  41. fi
  42. # ensure_require_replace_directives_for_all_dependencies:
  43. # - ensures all existing 'require' directives have an associated 'replace' directive pinning a version
  44. # - adds explicit 'require' directives for all transitive dependencies
  45. # - adds explicit 'replace' directives for all require directives (existing 'replace' directives take precedence)
  46. function ensure_require_replace_directives_for_all_dependencies() {
  47. local local_tmp_dir
  48. local_tmp_dir=$(mktemp -d "${TMP_DIR}/pin_replace.XXXX")
  49. # collect 'require' directives that actually specify a version
  50. local require_filter='(.Version != null) and (.Version != "v0.0.0") and (.Version != "v0.0.0-00010101000000-000000000000")'
  51. # collect 'replace' directives that unconditionally pin versions (old=new@version)
  52. local replace_filter='(.Old.Version == null) and (.New.Version != null)'
  53. # Capture local require/replace directives before running any go commands that can modify the go.mod file
  54. local require_json="${local_tmp_dir}/require.json"
  55. local replace_json="${local_tmp_dir}/replace.json"
  56. go mod edit -json | jq -r ".Require // [] | sort | .[] | select(${require_filter})" > "${require_json}"
  57. go mod edit -json | jq -r ".Replace // [] | sort | .[] | select(${replace_filter})" > "${replace_json}"
  58. # 1a. Ensure replace directives have an explicit require directive
  59. jq -r '"-require \(.Old.Path)@\(.New.Version)"' < "${replace_json}" | xargs -L 100 go mod edit -fmt
  60. # 1b. Ensure require directives have a corresponding replace directive pinning a version
  61. jq -r '"-replace \(.Path)=\(.Path)@\(.Version)"' < "${require_json}" | xargs -L 100 go mod edit -fmt
  62. jq -r '"-replace \(.Old.Path)=\(.New.Path)@\(.New.Version)"' < "${replace_json}" | xargs -L 100 go mod edit -fmt
  63. # 2. Propagate root replace/require directives into staging modules, in case we are downgrading, so they don't bump the root required version back up
  64. for repo in $(kube::util::list_staging_repos); do
  65. pushd "staging/src/k8s.io/${repo}" >/dev/null 2>&1
  66. jq -r '"-require \(.Path)@\(.Version)"' < "${require_json}" | xargs -L 100 go mod edit -fmt
  67. jq -r '"-replace \(.Path)=\(.Path)@\(.Version)"' < "${require_json}" | xargs -L 100 go mod edit -fmt
  68. jq -r '"-replace \(.Old.Path)=\(.New.Path)@\(.New.Version)"' < "${replace_json}" | xargs -L 100 go mod edit -fmt
  69. popd >/dev/null 2>&1
  70. done
  71. # 3. Add explicit require directives for indirect dependencies
  72. go list -m -json all | jq -r 'select(.Main != true) | select(.Indirect == true) | "-require \(.Path)@\(.Version)"' | xargs -L 100 go mod edit -fmt
  73. # 4. Add explicit replace directives pinning dependencies that aren't pinned yet
  74. go list -m -json all | jq -r 'select(.Main != true) | select(.Replace == null) | "-replace \(.Path)=\(.Path)@\(.Version)"' | xargs -L 100 go mod edit -fmt
  75. }
  76. function group_replace_directives() {
  77. local local_tmp_dir
  78. local_tmp_dir=$(mktemp -d "${TMP_DIR}/group_replace.XXXX")
  79. local go_mod_replace="${local_tmp_dir}/go.mod.replace.tmp"
  80. local go_mod_noreplace="${local_tmp_dir}/go.mod.noreplace.tmp"
  81. # separate replace and non-replace directives
  82. awk "
  83. # print lines between 'replace (' ... ')' lines
  84. /^replace [(]/ { inreplace=1; next }
  85. inreplace && /^[)]/ { inreplace=0; next }
  86. inreplace { print > \"${go_mod_replace}\"; next }
  87. # print ungrouped replace directives with the replace directive trimmed
  88. /^replace [^(]/ { sub(/^replace /,\"\"); print > \"${go_mod_replace}\"; next }
  89. # otherwise print to the noreplace file
  90. { print > \"${go_mod_noreplace}\" }
  91. " < go.mod
  92. {
  93. cat "${go_mod_noreplace}";
  94. echo "replace (";
  95. cat "${go_mod_replace}";
  96. echo ")";
  97. } > go.mod
  98. go mod edit -fmt
  99. }
  100. function add_generated_comments() {
  101. local local_tmp_dir
  102. local_tmp_dir=$(mktemp -d "${TMP_DIR}/add_generated_comments.XXXX")
  103. local go_mod_nocomments="${local_tmp_dir}/go.mod.nocomments.tmp"
  104. # drop comments before the module directive
  105. awk "
  106. BEGIN { dropcomments=1 }
  107. /^module / { dropcomments=0 }
  108. dropcomments && /^\/\// { next }
  109. { print }
  110. " < go.mod > "${go_mod_nocomments}"
  111. # Add the specified comments
  112. local comments="${1}"
  113. {
  114. echo "${comments}"
  115. echo ""
  116. cat "${go_mod_nocomments}"
  117. } > go.mod
  118. # Format
  119. go mod edit -fmt
  120. }
  121. # Phase 1: ensure go.mod files for staging modules and main module
  122. for repo in $(kube::util::list_staging_repos); do
  123. pushd "staging/src/k8s.io/${repo}" >/dev/null 2>&1
  124. if [[ ! -f go.mod ]]; then
  125. kube::log::status "go.mod: initialize ${repo}"
  126. rm -f Godeps/Godeps.json # remove before initializing, staging Godeps are not authoritative
  127. go mod init "k8s.io/${repo}"
  128. go mod edit -fmt
  129. fi
  130. popd >/dev/null 2>&1
  131. done
  132. if [[ ! -f go.mod ]]; then
  133. kube::log::status "go.mod: initialize k8s.io/kubernetes"
  134. go mod init "k8s.io/kubernetes"
  135. rm -f Godeps/Godeps.json # remove after initializing
  136. fi
  137. # Phase 2: ensure staging repo require/replace directives
  138. kube::log::status "go.mod: update staging references"
  139. # Prune
  140. go mod edit -json | jq -r '.Require[]? | select(.Version == "v0.0.0") | "-droprequire \(.Path)"' | xargs -L 100 go mod edit -fmt
  141. go mod edit -json | jq -r '.Replace[]? | select(.New.Path | startswith("./staging/")) | "-dropreplace \(.Old.Path)"' | xargs -L 100 go mod edit -fmt
  142. # Readd
  143. kube::util::list_staging_repos | xargs -n 1 -I {} echo "-require k8s.io/{}@v0.0.0" | xargs -L 100 go mod edit -fmt
  144. kube::util::list_staging_repos | xargs -n 1 -I {} echo "-replace k8s.io/{}=./staging/src/k8s.io/{}" | xargs -L 100 go mod edit -fmt
  145. # Phase 3: capture required (minimum) versions from all modules, and replaced (pinned) versions from the root module
  146. # pin referenced versions
  147. ensure_require_replace_directives_for_all_dependencies
  148. # resolves/expands references in the root go.mod (if needed)
  149. go mod tidy >>"${LOG_FILE}" 2>&1
  150. # pin expanded versions
  151. ensure_require_replace_directives_for_all_dependencies
  152. # group replace directives
  153. group_replace_directives
  154. # Phase 4: copy root go.mod to staging dirs and rewrite
  155. kube::log::status "go.mod: propagate to staging modules"
  156. for repo in $(kube::util::list_staging_repos); do
  157. pushd "staging/src/k8s.io/${repo}" >/dev/null 2>&1
  158. echo "=== propagating to ${repo}" >> "${LOG_FILE}"
  159. # copy root go.mod, changing module name
  160. sed "s#module k8s.io/kubernetes#module k8s.io/${repo}#" < "${KUBE_ROOT}/go.mod" > "${KUBE_ROOT}/staging/src/k8s.io/${repo}/go.mod"
  161. # remove `require` directives for staging components (will get re-added as needed by `go list`)
  162. kube::util::list_staging_repos | xargs -n 1 -I {} echo "-droprequire k8s.io/{}" | xargs -L 100 go mod edit
  163. # rewrite `replace` directives for staging components to point to peer directories
  164. kube::util::list_staging_repos | xargs -n 1 -I {} echo "-replace k8s.io/{}=../{}" | xargs -L 100 go mod edit
  165. popd >/dev/null 2>&1
  166. done
  167. # Phase 5: sort and tidy staging components
  168. kube::log::status "go.mod: sorting staging modules"
  169. # tidy staging repos in reverse dependency order.
  170. # the content of dependencies' go.mod files affects what `go mod tidy` chooses to record in a go.mod file.
  171. tidy_unordered="${TMP_DIR}/tidy_unordered.txt"
  172. kube::util::list_staging_repos | xargs -I {} echo "k8s.io/{}" > "${tidy_unordered}"
  173. rm -f "${TMP_DIR}/tidy_deps.txt"
  174. # SC2094 checks that you do not read and write to the same file in a pipeline.
  175. # We do read from ${tidy_unordered} in the pipeline and mention it within the
  176. # pipeline (but only ready it again) so we disable the lint to assure shellcheck
  177. # that :this-is-fine:
  178. # shellcheck disable=SC2094
  179. while IFS= read -r repo; do
  180. # record existence of the repo to ensure modules with no peer relationships still get included in the order
  181. echo "${repo} ${repo}" >> "${TMP_DIR}/tidy_deps.txt"
  182. pushd "${KUBE_ROOT}/staging/src/${repo}" >/dev/null 2>&1
  183. # save the original go.mod, since go list doesn't just add missing entries, it also removes specific required versions from it
  184. tmp_go_mod="${TMP_DIR}/tidy_${repo/\//_}_go.mod.original"
  185. tmp_go_deps="${TMP_DIR}/tidy_${repo/\//_}_deps.txt"
  186. cp go.mod "${tmp_go_mod}"
  187. {
  188. echo "=== sorting ${repo}"
  189. # 'go list' calculates direct imports and updates go.mod so that go list -m lists our module dependencies
  190. echo "=== computing imports for ${repo}"
  191. go list all
  192. echo "=== computing tools imports for ${repo}"
  193. go list -tags=tools all
  194. } >> "${LOG_FILE}" 2>&1
  195. # capture module dependencies
  196. go list -m -f '{{if not .Main}}{{.Path}}{{end}}' all > "${tmp_go_deps}"
  197. # restore the original go.mod file
  198. cp "${tmp_go_mod}" go.mod
  199. # list all module dependencies
  200. for dep in $(join "${tidy_unordered}" "${tmp_go_deps}"); do
  201. # record the relationship (put dep first, because we want to sort leaves first)
  202. echo "${dep} ${repo}" >> "${TMP_DIR}/tidy_deps.txt"
  203. # switch the required version to an explicit v0.0.0 (rather than an unknown v0.0.0-00010101000000-000000000000)
  204. go mod edit -require "${dep}@v0.0.0"
  205. done
  206. popd >/dev/null 2>&1
  207. done < "${tidy_unordered}"
  208. kube::log::status "go.mod: tidying"
  209. for repo in $(tsort "${TMP_DIR}/tidy_deps.txt"); do
  210. pushd "${KUBE_ROOT}/staging/src/${repo}" >/dev/null 2>&1
  211. echo "=== tidying ${repo}" >> "${LOG_FILE}"
  212. # prune replace directives that pin to the naturally selected version.
  213. # do this before tidying, since tidy removes unused modules that
  214. # don't provide any relevant packages, which forgets which version of the
  215. # unused transitive dependency we had a require directive for,
  216. # and prevents pruning the matching replace directive after tidying.
  217. go list -m -json all |
  218. jq -r 'select(.Replace != null) |
  219. select(.Path == .Replace.Path) |
  220. select(.Version == .Replace.Version) |
  221. "-dropreplace \(.Replace.Path)"' |
  222. xargs -L 100 go mod edit -fmt
  223. go mod tidy -v >>"${LOG_FILE}" 2>&1
  224. # disallow transitive dependencies on k8s.io/kubernetes
  225. loopback_deps=()
  226. kube::util::read-array loopback_deps < <(go list all 2>/dev/null | grep k8s.io/kubernetes/ || true)
  227. if [[ -n ${loopback_deps[*]:+"${loopback_deps[*]}"} ]]; then
  228. kube::log::error "Disallowed ${repo} -> k8s.io/kubernetes dependencies exist via the following imports:
  229. $(go mod why "${loopback_deps[@]}")"
  230. exit 1
  231. fi
  232. # prune unused pinned replace directives
  233. comm -23 \
  234. <(go mod edit -json | jq -r '.Replace[] | .Old.Path' | sort) \
  235. <(go list -m -json all | jq -r .Path | sort) |
  236. xargs -L 1 -I {} echo "-dropreplace={}" |
  237. xargs -L 100 go mod edit -fmt
  238. # prune replace directives that pin to the naturally selected version
  239. go list -m -json all |
  240. jq -r 'select(.Replace != null) |
  241. select(.Path == .Replace.Path) |
  242. select(.Version == .Replace.Version) |
  243. "-dropreplace \(.Replace.Path)"' |
  244. xargs -L 100 go mod edit -fmt
  245. popd >/dev/null 2>&1
  246. done
  247. echo "=== tidying root" >> "${LOG_FILE}"
  248. go mod tidy >>"${LOG_FILE}" 2>&1
  249. # Phase 6: add generated comments to go.mod files
  250. kube::log::status "go.mod: adding generated comments"
  251. add_generated_comments "
  252. // This is a generated file. Do not edit directly.
  253. // Run hack/pin-dependency.sh to change pinned dependency versions.
  254. // Run hack/update-vendor.sh to update go.mod files and the vendor directory.
  255. "
  256. for repo in $(kube::util::list_staging_repos); do
  257. pushd "staging/src/k8s.io/${repo}" >/dev/null 2>&1
  258. add_generated_comments "// This is a generated file. Do not edit directly."
  259. popd >/dev/null 2>&1
  260. done
  261. # Phase 6: rebuild vendor directory
  262. kube::log::status "vendor: running 'go mod vendor'"
  263. go mod vendor >>"${LOG_FILE}" 2>&1
  264. # sort recorded packages for a given vendored dependency in modules.txt.
  265. # `go mod vendor` outputs in imported order, which means slight go changes (or different platforms) can result in a differently ordered modules.txt.
  266. # scan | prefix comment lines with the module name | sort field 1 | strip leading text on comment lines
  267. awk '{if($1=="#") print $2 " " $0; else print}' < vendor/modules.txt | sort -k1,1 -s | sed 's/.*#/#/' > "${TMP_DIR}/modules.txt.tmp"
  268. mv "${TMP_DIR}/modules.txt.tmp" vendor/modules.txt
  269. # create a symlink in vendor directory pointing to the staging components.
  270. # This lets other packages and tools use the local staging components as if they were vendored.
  271. for repo in $(kube::util::list_staging_repos); do
  272. rm -fr "${KUBE_ROOT}/vendor/k8s.io/${repo}"
  273. ln -s "../../staging/src/k8s.io/${repo}" "${KUBE_ROOT}/vendor/k8s.io/${repo}"
  274. done
  275. kube::log::status "vendor: updating BUILD files"
  276. # Assume that anything imported through vendor doesn't need Bazel to build.
  277. # Prune out any Bazel build files, since these can break the build due to
  278. # missing dependencies that aren't included by go mod vendor.
  279. find vendor/ -type f \( -name BUILD -o -name BUILD.bazel -o -name WORKSPACE \) -exec rm -f {} \;
  280. hack/update-bazel.sh >>"${LOG_FILE}" 2>&1
  281. kube::log::status "vendor: updating LICENSES file"
  282. hack/update-vendor-licenses.sh >>"${LOG_FILE}" 2>&1
  283. kube::log::status "vendor: creating OWNERS file"
  284. rm -f "Godeps/OWNERS" "vendor/OWNERS"
  285. cat <<__EOF__ > "Godeps/OWNERS"
  286. # See the OWNERS docs at https://go.k8s.io/owners
  287. approvers:
  288. - dep-approvers
  289. __EOF__
  290. cp "Godeps/OWNERS" "vendor/OWNERS"