Gentoo Archives: gentoo-dev

From: "Robin H. Johnson" <robbat2@g.o>
To: gentoo-dev@l.g.o
Cc: "Robin H. Johnson" <robbat2@g.o>
Subject: [gentoo-dev] [PATCH 1/3] eclass/go-module: add support for building based on go.sum
Date: Sun, 09 Feb 2020 20:31:58
Message-Id: 20200209203121.4646-1-robbat2@gentoo.org
1 EGO_SUM mode now supplements the existing EGO_VENDOR mode.
2
3 EGO_SUM should be populated by the maintainer, directly from the go.sum
4 file of the root package. See eclass and conversion example
5 (dev-go/go-tour & app-admin/kube-bench) for further details.
6
7 The go-module_set_globals function performs validation of
8 inputs and does die on fatal errors.
9
10 Signed-off-by: Robin H. Johnson <robbat2@g.o>
11 ---
12 eclass/go-module.eclass | 328 +++++++++++++++++++++++++++++++++++--
13 profiles/thirdpartymirrors | 1 +
14 2 files changed, 311 insertions(+), 18 deletions(-)
15
16 diff --git eclass/go-module.eclass eclass/go-module.eclass
17 index d5de5f60ccdf..b8a635d52de7 100644
18 --- eclass/go-module.eclass
19 +++ eclass/go-module.eclass
20 @@ -4,22 +4,46 @@
21 # @ECLASS: go-module.eclass
22 # @MAINTAINER:
23 # William Hubbs <williamh@g.o>
24 +# @AUTHOR:
25 +# William Hubbs <williamh@g.o>
26 +# Robin H. Johnson <robbat2@g.o>
27 # @SUPPORTED_EAPIS: 7
28 # @BLURB: basic eclass for building software written as go modules
29 # @DESCRIPTION:
30 -# This eclass provides basic settings and functions
31 -# needed by all software written in the go programming language that uses
32 -# go modules.
33 +# This eclass provides basic settings and functions needed by all software
34 +# written in the go programming language that uses go modules.
35 +#
36 +# You might know the software you are packaging uses modules because
37 +# it has files named go.sum and go.mod in its top-level source directory.
38 +# If it does not have these files, try use the golang-* eclasses FIRST!
39 +# There ARE legacy Golang packages that use external modules with none of
40 +# go.mod, go.sum, vendor/ that can use this eclass regardless.
41 +#
42 +# Guidelines for usage:
43 +# "go.mod" && "go.sum" && "vendor/":
44 +# - pre-vendored package. Do NOT set EGO_SUM or EGO_VENDOR.
45 +#
46 +# "go.mod" && "go.sum":
47 +# - Populate EGO_SUM with entries from go.sum
48 +# - Do NOT include any lines that contain <version>/go.mod
49 +#
50 +# "go.mod" only:
51 +# - Populate EGO_VENDOR
52 #
53 -# You will know the software you are packaging uses modules because
54 -# it will have files named go.sum and go.mod in its top-level source
55 -# directory. If it does not have these files, use the golang-* eclasses.
56 +# None of the above:
57 +# - Did you try golang-* eclasses first? Upstream has undeclared dependencies
58 +# (perhaps really old source). You can use either EGO_SUM or EGO_VENDOR.
59 +
60 #
61 -# If it has these files and a directory named vendor in its top-level
62 -# source directory, you only need to inherit the eclass since upstream
63 -# is vendoring the dependencies.
64 +# If it has these files AND a directory named "vendor" in its top-level source
65 +# directory, you only need to inherit the eclass since upstream has already
66 +# vendored the dependencies.
67 +
68 +# If it does not have a vendor directory, you should use the EGO_SUM
69 +# variable and the go-module_gosum_uris function as shown in the
70 +# example below to handle dependencies.
71 #
72 -# If it does not have a vendor directory, you should use the EGO_VENDOR
73 +# Alternatively, older versions of this eclass used the EGO_VENDOR
74 # variable and the go-module_vendor_uris function as shown in the
75 # example below to handle dependencies.
76 #
77 @@ -28,6 +52,21 @@
78 # dependencies. So please make sure it is accurate.
79 #
80 # @EXAMPLE:
81 +# @CODE
82 +#
83 +# inherit go-module
84 +#
85 +# EGO_SUM=(
86 +# "github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ="
87 +# "github.com/BurntSushi/toml v0.3.1/go.mod h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ="
88 +# )
89 +# S="${WORKDIR}/${MY_P}"
90 +# go-module_set_globals
91 +#
92 +# SRC_URI="https://github.com/example/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz
93 +# ${EGO_SUM_SRC_URI}"
94 +#
95 +# @CODE
96 #
97 # @CODE
98 #
99 @@ -35,7 +74,7 @@
100 #
101 # EGO_VENDOR=(
102 # "github.com/xenolf/lego 6cac0ea7d8b28c889f709ec7fa92e92b82f490dd"
103 -# "golang.org/x/crypto 453249f01cfeb54c3d549ddb75ff152ca243f9d8 github.com/golang/crypto"
104 +# "golang.org/x/crypto 453249f01cfeb54c3d549ddb75ff152ca243f9d8 github.com/golang/crypto"
105 # )
106 #
107 # SRC_URI="https://github.com/example/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz
108 @@ -64,10 +103,12 @@ export GO111MODULE=on
109 export GOCACHE="${T}/go-build"
110
111 # The following go flags should be used for all builds.
112 -# -mod=vendor stopps downloading of dependencies from the internet.
113 # -v prints the names of packages as they are compiled
114 # -x prints commands as they are executed
115 -export GOFLAGS="-mod=vendor -v -x"
116 +# -mod=vendor use the vendor directory instead of downloading dependencies
117 +# -mod=readonly do not update go.mod/go.sum but fail if updates are needed
118 +export GOFLAGS="-v -x -mod=readonly"
119 +[[ ${#EGO_VENDOR[@]} -gt 0 ]] && GOFLAGS+=" -mod=vendor"
120
121 # Do not complain about CFLAGS etc since go projects do not use them.
122 QA_FLAGS_IGNORED='.*'
123 @@ -75,7 +116,23 @@ QA_FLAGS_IGNORED='.*'
124 # Go packages should not be stripped with strip(1).
125 RESTRICT="strip"
126
127 -EXPORT_FUNCTIONS src_unpack pkg_postinst
128 +EXPORT_FUNCTIONS src_unpack src_prepare pkg_postinst
129 +
130 +# @ECLASS-VARIABLE: EGO_SUM
131 +# @DESCRIPTION:
132 +# This variable duplicates the go.sum content from inside the target package.
133 +# Entries of the form <version>/go.mod should be excluded.
134 +#
135 +# <module> <version> <hash>
136 +#
137 +# The format is described upstream here:
138 +# https://tip.golang.org/cmd/go/#hdr-Module_authentication_using_go_sum
139 +#
140 +# <hash> is the Hash1 structure used by upstream Go
141 +# Note that Hash1 is MORE stable than Gentoo distfile hashing, and upstream
142 +# warns that it's conceptually possible for the Hash1 value to remain stable
143 +# while the upstream zipfiles change. E.g. it does NOT capture mtime changes in
144 +# files within a zipfile.
145
146 # @ECLASS-VARIABLE: EGO_VENDOR
147 # @DESCRIPTION:
148 @@ -106,13 +163,202 @@ go-module_vendor_uris() {
149 done
150 }
151
152 +# @ECLASS-VARIABLE: GOMODULE_GOPROXY_BASEURI
153 +# @DESCRIPTION:
154 +# Golagg module proxy service to fetch module files from. Note that the module
155 +# proxy generally verifies modules via the Hash1 code.
156 +#
157 +# Note: Users in China may find some mirrors in the list blocked, and may wish
158 +# to an explicit entry to /etc/portage/mirrors pointing mirror://goproxy/ to
159 +# https://goproxy.cn/, or change this variable.
160 +# See https://arslan.io/2019/08/02/why-you-should-use-a-go-module-proxy/ for further details
161 +: "${GOMODULE_GOPROXY_BASEURI:=mirror://goproxy/}"
162 +
163 +# @FUNCTION: go-module_set_globals
164 +# @DESCRIPTION:
165 +# Convert the information in EGO_SUM for other usage in the ebuild.
166 +# - Populates EGO_SUM_SRC_URI that can be added to SRC_URI
167 +# - Exports _EGO_SUM_MAPPING which provides reverse mapping from distfile back
168 +# to the relative part of SRC_URI, as needed for GOPROXY=file:///...
169 +go-module_set_globals() {
170 + local line error_in_gosum errorlines errormsg exts
171 + local newline=$'\n'
172 + error_in_gosum=0
173 + errorlines=( )
174 + for line in "${EGO_SUM[@]}"; do
175 + local module version modfile version_modfile hash1 x
176 + read -r module version_modfile hash1 x <<< "${line}"
177 + # Validate input
178 + if [[ -n $hash1 ]] && [[ ${hash1:0:3} != "h1:" ]] ; then
179 + error_in_gosum=1
180 + errorlines+=( "Unknown hash: ${line}" )
181 + elif [[ -n $x ]]; then
182 + error_in_gosum=1
183 + errorlines+=( "Trailing data: ${line}" )
184 + fi
185 +
186 + # Split 'v0.3.0/go.mod' into 'v0.3.0' and '/go.mod'
187 + version=${version_modfile%%/*}
188 + modfile=${version_modfile#*/}
189 + [[ "$modfile" == "${version_modfile}" ]] && modfile=
190 +
191 + # The trailing part should be either empty or '/go.mod'
192 + # There is a chance that upstream Go might add something else here in
193 + # future, and we should be prepared to capture it.
194 + exts=()
195 + errormsg=''
196 + case "$modfile" in
197 + '') exts=( mod info zip ) ;;
198 + 'go.mod'|'/go.mod') exts=( mod info ) ;;
199 + #'go.mod'|'/go.mod') errormsg="Prohibited file: You must exclude /go.mod lines from EGO_SUM! " ;;
200 + *) errormsg="Unknown modfile: line='${line}', modfile='${modfile}'" ;;
201 + esac
202 +
203 + # If it was a bad entry, restart the loop
204 + if [[ -n $errormsg ]]; then
205 + error_in_gosum=1
206 + errorlines+=( "${errormsg} line='${line}', modfile='${modfile}'" )
207 + continue
208 + fi
209 +
210 + # Directory structure for Go proxy hosts:
211 + # - def encode(s):
212 + # return re.sub('([A-Z]{1})', r'!\1', s).lower()
213 + #
214 + # Sed variant:
215 + # This uses GNU Sed extension \l to downcase the match
216 + #_dir=$(echo "${module}" |sed 's,[A-Z],!\l&,g')
217 + #
218 + # Bash variant:
219 + re='(.*)([A-Z])(.*)'
220 + input=${module}
221 + while [[ $input =~ $re ]]; do
222 + lower='!'"${BASH_REMATCH[2],}"
223 + input="${BASH_REMATCH[1]}${lower}${BASH_REMATCH[3]}"
224 + done
225 + _dir=$input
226 + unset lower input re
227 +
228 + for _ext in "${exts[@]}" ; do
229 + # Relative URI within a GOPROXY for a file
230 + _reluri="${_dir}/@v/${version}.${_ext}"
231 + # SRC_URI: LHS entry
232 + _uri="${GOMODULE_GOPROXY_BASEURI}/${_reluri}"
233 + # SRC_URI: RHS entry, encode any slash in the path as %2F in the filename
234 + _distfile="${_reluri//\//%2F}"
235 +
236 + EGO_SUM_SRC_URI+=" ${_uri} -> ${_distfile}${newline}"
237 + _EGO_SUM_MAPPING+=" ${_distfile}:${_reluri}${newline}"
238 + done
239 + done
240 +
241 + if [[ $error_in_gosum != 0 ]]; then
242 + eerror "Trailing information in EGO_SUM in ${P}.ebuild"
243 + for line in "${errorlines[@]}" ; do
244 + eerror "${line}"
245 + done
246 + die "Invalid EGO_SUM format"
247 + fi
248 +
249 + # Ensure these variables not not changed past this point
250 + readonly EGO_SUM
251 + readonly EGO_SUM_SRC_URI
252 + readonly _EGO_SUM_MAPPING
253 +
254 + # Set the guard that we are safe
255 + _GO_MODULE_SET_GLOBALS_CALLED=1
256 +}
257 +
258 +
259 # @FUNCTION: go-module_src_unpack
260 # @DESCRIPTION:
261 +# Extract & configure Go modules for consumpations.
262 +# - Modules listed in EGO_SUM are configured as a local GOPROXY via symlinks (fast!)
263 +# - Modules listed in EGO_VENDOR are extracted to "${S}/vendor" (slow)
264 +#
265 +# This function does NOT unpack the base distfile of a Go-based package.
266 +# While the entries in EGO_SUM will be listed in ${A}, they should NOT be
267 +# unpacked, Go will directly consume the files, including zips.
268 +go-module_src_unpack() {
269 + if [[ "${#EGO_VENDOR[@]}" -gt 0 ]]; then
270 + _go-module_src_unpack_vendor
271 + elif [[ "${#EGO_SUM[@]}" -gt 0 ]]; then
272 + _go-module_src_unpack_gosum
273 + else
274 + die "Neither EGO_SUM nor EGO_VENDOR are set!"
275 + fi
276 +}
277 +
278 +# @FUNCTION: go-module_src_prepare
279 +# @DESCRIPTION:
280 +# Prepare for building. Presently only needed for EGO_SUM variant.
281 +go-module_src_prepare() {
282 + # shellcheck disable=SC2120
283 + debug-print-function "${FUNCNAME}" "$@"
284 +
285 + if [[ "${#EGO_SUM[@]}" -gt 0 ]]; then
286 + _go-module_src_prepare_gosum
287 + fi
288 +
289 + default
290 +}
291 +
292 +# @ECLASS-VARIABLE: GOMODULE_GOSUM_PATH
293 +# @DESCRIPTION:
294 +# Path to root go.sum of package. If your ebuild modifies S after inheriting
295 +# the eclass, you may need to update this variable.
296 +: "${GO_MODULE_GOSUM_PATH:=${S}/go.sum}"
297 +
298 +# @FUNCTION: _go-module_src_unpack_gosum
299 +# @DESCRIPTION:
300 +# Populate a GOPROXY directory hierarchy with distfiles from EGO_SUM
301 +#
302 +# Exports GOPROXY environment variable so that Go calls will source the
303 +# directory correctly.
304 +_go-module_src_unpack_gosum() {
305 + # shellcheck disable=SC2120
306 + debug-print-function "${FUNCNAME}" "$@"
307 +
308 + if [[ ! ${_GO_MODULE_SET_GLOBALS_CALLED} ]]; then
309 + die "go-module_set_globals must be called in global scope"
310 + fi
311 +
312 + local goproxy_dir="${T}/goproxy"
313 + local goproxy_mod_dir
314 + mkdir -p "${goproxy_dir}"
315 + # Convert the list format to an associative array to avoid O(N*M)
316 + # performance when populating the GOPROXY directory structure.
317 + declare -A _EGO_SUM_MAPPING_ASSOC
318 + for s in ${_EGO_SUM_MAPPING}; do
319 + a=${s//:*}
320 + b=${s//*:}
321 + _EGO_SUM_MAPPING_ASSOC["$a"]=$b
322 + done
323 +
324 + # For each Golang module distfile, look up where it's supposed to go, and
325 + # symlink into place.
326 + for _A in ${A}; do
327 + goproxy_mod_path="${_EGO_SUM_MAPPING_ASSOC["${_A}"]}"
328 + if [[ -n "${goproxy_mod_path}" ]]; then
329 + einfo "Populating goproxy for $goproxy_mod_path"
330 + # Build symlink hierarchy
331 + goproxy_mod_dir=$( dirname "${goproxy_dir}"/"${goproxy_mod_path}" )
332 + mkdir -p "${goproxy_mod_dir}"
333 + ln -sf "${DISTDIR}"/"${_A}" "${goproxy_dir}/${goproxy_mod_path}" || die "Failed to ln"
334 + fi
335 + done
336 + export GOPROXY="file://${goproxy_dir}"
337 + unset _EGO_SUM_MAPPING_ASSOC
338 +}
339 +
340 +# @FUNCTION: _go-module_src_unpack_vendor
341 +# @DESCRIPTION:
342 # Extract all archives in ${a} which are not nentioned in ${EGO_VENDOR}
343 # to their usual locations then extract all archives mentioned in
344 # ${EGO_VENDOR} to ${S}/vendor.
345 -go-module_src_unpack() {
346 - debug-print-function ${FUNCNAME} "$@"
347 +_go-module_src_unpack_vendor() {
348 + # shellcheck disable=SC2120
349 + debug-print-function "${FUNCNAME}" "$@"
350 local f hash import line repo tarball vendor_tarballs x
351 vendor_tarballs=()
352 for line in "${EGO_VENDOR[@]}"; do
353 @@ -145,13 +391,59 @@ go-module_src_unpack() {
354 done
355 }
356
357 +# @FUNCTION: _go-module_src_prepare_gosum
358 +# @DESCRIPTION:
359 +# Validate the Go modules declared by EGO_SUM are sufficent to cover building
360 +# the package, without actually building it yet.
361 +_go-module_src_prepare_gosum() {
362 + # shellcheck disable=SC2120
363 + debug-print-function "${FUNCNAME}" "$@"
364 +
365 + if [[ ! ${_GO_MODULE_SET_GLOBALS_CALLED} ]]; then
366 + die "go-module_set_globals must be called in global scope"
367 + fi
368 +
369 + # go.sum entries ending in /go.mod aren't strictly needed at this phase
370 + if [[ ! -e "${GO_MODULE_GOSUM_PATH}" ]]; then
371 + die "Could not find package root go.sum, please update GO_MODULE_GOSUM_PATH"
372 + fi
373 + go-module_minimize_gosum "${GO_MODULE_GOSUM_PATH}"
374 +
375 + # Verify that all needed modules are present.
376 + GO111MODULE=on \
377 + go get -v -d -mod readonly || die "Some module is missing, update EGO_SUM"
378 +
379 + # Need to re-minimize because go-get expands it again
380 + go-module_minimize_gosum "${GO_MODULE_GOSUM_PATH}"
381 +}
382 +
383 +# @FUNCTION: go-module_minimize_gosum
384 +# @DESCRIPTION:
385 +# Remove all /go.mod entries from go.sum files
386 +# In most cases, if go.sum only has a /go.mod entry without a corresponding
387 +# direct entry, this is a sign of a weak dependency that is NOT required for
388 +# building the package.
389 +go-module_minimize_gosum() {
390 + local gosumfile=${1}
391 + if test ! -e "${gosumfile}".orig; then
392 + cp -f "${gosumfile}"{,.orig} || die
393 + fi
394 + awk -e '$2 ~ /\/go.mod$/{next} {print}' \
395 + <"${gosumfile}".orig \
396 + >"${gosumfile}" || die
397 + if grep -sq /go.mod "${gosumfile}"; then
398 + die "sed failed to remove all module go.mod entries from go.sum"
399 + fi
400 +}
401 +
402 # @FUNCTION: go-module_live_vendor
403 # @DESCRIPTION:
404 # This function is used in live ebuilds to vendor the dependencies when
405 # upstream doesn't vendor them.
406 go-module_live_vendor() {
407 - debug-print-function ${FUNCNAME} "$@"
408 + debug-print-function "${FUNCNAME}" "$@"
409
410 + # shellcheck disable=SC2086
411 has live ${PROPERTIES} ||
412 die "${FUNCNAME} only allowed in live ebuilds"
413 [[ "${EBUILD_PHASE}" == unpack ]] ||
414 @@ -168,7 +460,7 @@ go-module_live_vendor() {
415 # @DESCRIPTION:
416 # Display a warning about security updates for Go programs.
417 go-module_pkg_postinst() {
418 - debug-print-function ${FUNCNAME} "$@"
419 + debug-print-function "${FUNCNAME}" "$@"
420 [[ -n ${REPLACING_VERSIONS} ]] && return 0
421 ewarn "${PN} is written in the Go programming language."
422 ewarn "Since this language is statically linked, security"
423 diff --git profiles/thirdpartymirrors profiles/thirdpartymirrors
424 index ad4c4b972146..d60f166e07c9 100644
425 --- profiles/thirdpartymirrors
426 +++ profiles/thirdpartymirrors
427 @@ -25,3 +25,4 @@ sourceforge https://download.sourceforge.net
428 sourceforge.jp http://iij.dl.sourceforge.jp https://osdn.dl.sourceforge.jp https://jaist.dl.sourceforge.jp
429 ubuntu http://mirror.internode.on.net/pub/ubuntu/ubuntu/ https://mirror.tcc.wa.edu.au/ubuntu/ http://ubuntu.uni-klu.ac.at/ubuntu/ http://mirror.dhakacom.com/ubuntu-archive/ http://ubuntu.c3sl.ufpr.br/ubuntu/ http://ubuntu.uni-sofia.bg/ubuntu/ http://hr.archive.ubuntu.com/ubuntu/ http://cz.archive.ubuntu.com/ubuntu/ https://mirror.dkm.cz/ubuntu http://ftp.cvut.cz/ubuntu/ http://ftp.stw-bonn.de/ubuntu/ https://ftp-stud.hs-esslingen.de/ubuntu/ https://mirror.netcologne.de/ubuntu/ https://mirror.unej.ac.id/ubuntu/ http://kr.archive.ubuntu.com/ubuntu/ https://mirror.nforce.com/pub/linux/ubuntu/ http://mirror.amsiohosting.net/archive.ubuntu.com/ http://nl3.archive.ubuntu.com/ubuntu/ https://mirror.timeweb.ru/ubuntu/ http://ubuntu.mirror.su.se/ubuntu/ https://ftp.yzu.edu.tw/ubuntu/ https://mirror.aptus.co.tz/pub/ubuntuarchive/ https://ubuntu.volia.net/ubuntu-archive/ https://mirror.sax.uk.as61049.net/ubuntu/ https://mirror.pnl.gov/ubuntu/ http://mirror.cc.columbia.edu/pub/linux/ubuntu/archive/ https://mirrors.namecheap.com/ubuntu/
430 vdr-developerorg http://projects.vdr-developer.org/attachments/download
431 +goproxy https://proxy.golang.org/ https://goproxy.io/ https://gocenter.io/
432 --
433 2.25.0

Replies