Gentoo Archives: gentoo-dev

From: "Robin H. Johnson" <robbat2@g.o>
To: gentoo-dev@l.g.o
Subject: [gentoo-dev] [PATCH v2 1/4] eclass/go-module: add support for building based on go.sum
Date: Mon, 17 Feb 2020 09:23:11
Message-Id: 20200217092232.9483-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 examples for further
5 details: dev-go/go-tour, app-admin/kube-bench, dev-vcs/cli
6
7 The go-module_set_globals function performs validation of
8 inputs and dies on fatal errors.
9
10 Signed-off-by: Robin H. Johnson <robbat2@g.o>
11 ---
12 eclass/go-module.eclass | 419 +++++++++++++++++++++++++++++++++++--
13 profiles/thirdpartymirrors | 1 +
14 2 files changed, 397 insertions(+), 23 deletions(-)
15
16 diff --git eclass/go-module.eclass eclass/go-module.eclass
17 index 80ff2902b3ad..50aff92735af 100644
18 --- eclass/go-module.eclass
19 +++ eclass/go-module.eclass
20 @@ -4,22 +4,45 @@
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 +# "vendor/":
44 +# - pre-vendored package. Do NOT set EGO_SUM or EGO_VENDOR.
45 #
46 -# You will know the software you are packaging uses modules because
47 -# it will have files named go.sum and go.mod in its top-level source
48 -# directory. If it does not have these files, use the golang-* eclasses.
49 +# "go.mod" && "go.sum":
50 +# - Populate EGO_SUM with entries from go.sum
51 +# - Append license:${GENTOO_LICENSE} to any modules needed at runtime.
52 +# dev-go/golicense can tell you which modules in a Golang binary are used at
53 +# runtime (requires network connectivity).
54 #
55 -# If it has these files and a directory named vendor in its top-level
56 -# source directory, you only need to inherit the eclass since upstream
57 -# is vendoring the dependencies.
58 +# None of the above:
59 +# - Did you try golang-* eclasses first? Upstream has undeclared dependencies
60 +# (perhaps really old source). You can use either EGO_SUM or EGO_VENDOR.
61 +
62 +#
63 +# If it has these files AND a directory named "vendor" in its top-level source
64 +# directory, you only need to inherit the eclass since upstream has already
65 +# vendored the dependencies.
66 +
67 +# If it does not have a vendor directory, you should use the EGO_SUM
68 +# variable and the go-module_gosum_uris function as shown in the
69 +# example below to handle dependencies.
70 #
71 -# If it does not have a vendor directory, you should use the EGO_VENDOR
72 +# Alternatively, older versions of this eclass used the EGO_VENDOR
73 # variable and the go-module_vendor_uris function as shown in the
74 # example below to handle dependencies.
75 #
76 @@ -28,6 +51,28 @@
77 # dependencies. So please make sure it is accurate.
78 #
79 # @EXAMPLE:
80 +# @CODE
81 +#
82 +# inherit go-module
83 +#
84 +# EGO_SUM=(
85 +# "github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= license:BSD-2,MIT"
86 +# "github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo="
87 +# )
88 +# S="${WORKDIR}/${MY_P}"
89 +# go-module_set_globals
90 +#
91 +# SRC_URI="https://github.com/example/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz
92 +# ${EGO_SUM_SRC_URI}"
93 +#
94 +# LICENSE="some-license ${EGO_SUM_LICENSES}"
95 +#
96 +# src_unpack() {
97 +# unpack ${P}.tar.gz
98 +# go-module_src_unpack
99 +# }
100 +#
101 +# @CODE
102 #
103 # @CODE
104 #
105 @@ -35,7 +80,7 @@
106 #
107 # EGO_VENDOR=(
108 # "github.com/xenolf/lego 6cac0ea7d8b28c889f709ec7fa92e92b82f490dd"
109 -# "golang.org/x/crypto 453249f01cfeb54c3d549ddb75ff152ca243f9d8 github.com/golang/crypto"
110 +# "golang.org/x/crypto 453249f01cfeb54c3d549ddb75ff152ca243f9d8 github.com/golang/crypto"
111 # )
112 #
113 # SRC_URI="https://github.com/example/${PN}/archive/v${PV}.tar.gz -> ${P}.tar.gz
114 @@ -64,10 +109,12 @@ export GO111MODULE=on
115 export GOCACHE="${T}/go-build"
116
117 # The following go flags should be used for all builds.
118 -# -mod=vendor stopps downloading of dependencies from the internet.
119 # -v prints the names of packages as they are compiled
120 # -x prints commands as they are executed
121 -export GOFLAGS="-mod=vendor -v -x"
122 +# -mod=vendor use the vendor directory instead of downloading dependencies
123 +# -mod=readonly do not update go.mod/go.sum but fail if updates are needed
124 +export GOFLAGS="-v -x -mod=readonly"
125 +[[ ${#EGO_VENDOR[@]} -gt 0 ]] && GOFLAGS+=" -mod=vendor"
126
127 # Do not complain about CFLAGS etc since go projects do not use them.
128 QA_FLAGS_IGNORED='.*'
129 @@ -77,6 +124,42 @@ RESTRICT="strip"
130
131 EXPORT_FUNCTIONS src_unpack pkg_postinst
132
133 +# @ECLASS-VARIABLE: EGO_SUM
134 +# @DESCRIPTION:
135 +# This is an array based on the go.sum content from inside the target package.
136 +# Each array entry must be quoted and contain:
137 +#
138 +# "<module> <version> [h1:<hash>] [<key>:<value>]"
139 +#
140 +# To ease conversion from EGO_VENDOR, the presence of the h1:hash is NOT
141 +# enforced in ebuilds at this time.
142 +#
143 +# The format is described upstream here:
144 +# https://tip.golang.org/cmd/go/#hdr-Module_authentication_using_go_sum
145 +#
146 +# h1:<hash> is the Hash1 structure used by upstream Go
147 +# Note that Hash1 is MORE stable than Gentoo distfile hashing, and upstream
148 +# warns that it's conceptually possible for the Hash1 value to remain stable
149 +# while the upstream zipfiles change. E.g. it does NOT capture mtime changes in
150 +# files within a zipfile.
151 +#
152 +# <key>:<value> is an additional set of key-value tuples, to amend Gentoo
153 +# metadata.
154 +#
155 +# The extra metadata keys accepted at this time are:
156 +# - license: for dependencies built into the final runtime, the value field is
157 +# a comma seperated list of Gentoo licenses to apply to the LICENSE variable,
158 +#
159 +# @EXAMPLE:
160 +# # github.com/BurntSushi/toml is a build-time only dep
161 +# # github.com/aybabtme/rgbterm is a runtime dep, annotated with licenses
162 +# EGO_SUM=(
163 +# "github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ="
164 +# "github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU="
165 +# "github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= license:BSD-2,MIT"
166 +# "github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo="
167 +# )
168 +
169 # @ECLASS-VARIABLE: EGO_VENDOR
170 # @DESCRIPTION:
171 # This variable contains a list of vendored packages.
172 @@ -95,10 +178,10 @@ go-module_vendor_uris() {
173 local hash import line repo x
174 for line in "${EGO_VENDOR[@]}"; do
175 read -r import hash repo x <<< "${line}"
176 - if [[ -n $x ]]; then
177 + if [[ -n ${x} ]]; then
178 eerror "Trailing information in EGO_VENDOR in ${P}.ebuild"
179 eerror "${line}"
180 - eerror "Trailing information is: \"$x\""
181 + eerror "Trailing information is: \"${x}\""
182 die "Invalid EGO_VENDOR format"
183 fi
184 : "${repo:=${import}}"
185 @@ -106,18 +189,242 @@ go-module_vendor_uris() {
186 done
187 }
188
189 +# @ECLASS-VARIABLE: _GOMODULE_GOPROXY_BASEURI
190 +# @DESCRIPTION:
191 +# Golang module proxy service to fetch module files from. Note that the module
192 +# proxy generally verifies modules via the Hash1 code.
193 +#
194 +# Users in China may find some mirrors in the default list blocked, and should
195 +# explicitly set an entry in /etc/portage/mirrors for goproxy to
196 +# https://goproxy.cn/ or another mirror that is not blocked in China.
197 +# See https://arslan.io/2019/08/02/why-you-should-use-a-go-module-proxy/ for further details
198 +#
199 +# This variable is NOT intended for user-level configuration of mirrors, but
200 +# rather to cover Golang modules that might exist only on specific Goproxy
201 +# servers for non-technical reasons.
202 +#
203 +# This variable should NOT be present in user-level configuration e.g.
204 +# /etc/portage/make.conf, as it will violate metadata immutability!
205 +: "${_GOMODULE_GOPROXY_BASEURI:=mirror://goproxy/}"
206 +
207 +
208 +# @ECLASS-VARIABLE: _GOMODULE_GOSUM_REVERSE_MAP
209 +# @DESCRIPTION:
210 +# Mapping back from Gentoo distfile name to upstream distfile path.
211 +# Associative array to avoid O(N*M) performance when populating the GOPROXY
212 +# directory structure.
213 +declare -A -g _GOMODULE_GOSUM_REVERSE_MAP
214 +
215 +# @FUNCTION: go-module_set_globals
216 +# @DESCRIPTION:
217 +# Convert the information in EGO_SUM for other usage in the ebuild.
218 +# - Populates EGO_SUM_SRC_URI that can be added to SRC_URI
219 +# - Exports _GOMODULE_GOSUM_REVERSE_MAP which provides reverse mapping from
220 +# distfile back to the relative part of SRC_URI, as needed for
221 +# GOPROXY=file:///...
222 +go-module_set_globals() {
223 + local line exts
224 + # for tracking go.sum errors
225 + local error_in_gosum=0
226 + local -a gosum_errorlines
227 + # used make SRC_URI easier to read
228 + local newline=$'\n'
229 + # capture unique licenses
230 + local -A unique_licenses
231 +
232 + # Now parse EGO_SUM
233 + for line in "${EGO_SUM[@]}"; do
234 + local module version modfile version_modfile kvs x
235 + local hash1='' license=''
236 + read -r module version_modfile kvs <<< "${line}"
237 + # Parse the key:value set on the end of the line
238 + for _kv in ${kvs}; do
239 + local _v=${_kv#*:}
240 + local _k=${_kv%$_v}
241 + case "$_k" in
242 + 'h1:') hash1+=" $_kv" ;;
243 + 'license:') license+=" ${_v//,/ }" ;;
244 + *) error_in_gosum=1 ; gosum_errorlines+=( "Unknown EGO_SUM key:value: k=$_k v=$_v" ) ;;
245 + esac
246 + done
247 +
248 + # Split 'v0.3.0/go.mod' into 'v0.3.0' and '/go.mod'
249 + # It might NOT have the trailing /go.mod
250 + IFS=/ read -r version modfile x <<<"${version_modfile}"
251 + # Reject multiple slashes
252 + if [[ -n ${x} ]]; then
253 + error_in_gosum=1
254 + gosum_errorlines+=( "Bad version: ${version_modfile}" )
255 + continue
256 + fi
257 +
258 + # The modfile variable should be either empty or '/go.mod'
259 + # There is a chance that upstream Go might add something else here in
260 + # future, and we should be prepared to capture it.
261 + # The .info files do not need to be downloaded, they will be created
262 + # based on the .mod file.
263 + # See https://github.com/golang/go/issues/35922#issuecomment-584824275
264 + exts=()
265 + local errormsg=''
266 + case "${modfile}" in
267 + '') exts=( zip ) ;;
268 + 'go.mod'|'/go.mod') exts=( mod ) ;;
269 + *) errormsg="Unknown modfile: line='${line}', modfile='${modfile}'" ;;
270 + esac
271 +
272 + # If it was a bad entry, restart the loop
273 + if [[ -n ${errormsg} ]]; then
274 + error_in_gosum=1
275 + gosum_errorlines+=( "${errormsg} line='${line}', modfile='${modfile}'" )
276 + continue
277 + fi
278 +
279 + # Capture unique licenses
280 + for _l in ${license} ; do
281 + unique_licenses["${_l}"]=1
282 + done
283 +
284 + _dir=$(_go-module_gomod_encode "${module}")
285 +
286 + for _ext in "${exts[@]}" ; do
287 + # Relative URI within a GOPROXY for a file
288 + _reluri="${_dir}/@v/${version}.${_ext}"
289 + # SRC_URI: LHS entry
290 + _uri="${_GOMODULE_GOPROXY_BASEURI}/${_reluri}"
291 + # SRC_URI: RHS entry, encode any slash in the path as %2F in the filename
292 + _distfile="${_reluri//\//%2F}"
293 +
294 + EGO_SUM_SRC_URI+=" ${_uri} -> ${_distfile}${newline}"
295 + _GOMODULE_GOSUM_REVERSE_MAP["${_distfile}"]="${_reluri}"
296 + done
297 + done
298 +
299 + # Dedupe licenses
300 + for license in "${!unique_licenses[@]}" ; do
301 + EGO_SUM_LICENSES+=" ${license}"
302 + done
303 +
304 +
305 + if [[ ${error_in_gosum} != 0 ]]; then
306 + eerror "Trailing information in EGO_SUM in ${P}.ebuild"
307 + for line in "${gosum_errorlines[@]}" ; do
308 + eerror "${line}"
309 + done
310 + die "Invalid EGO_SUM format"
311 + fi
312 +
313 + # Ensure these variables not not changed past this point
314 + readonly EGO_SUM
315 + readonly EGO_SUM_SRC_URI
316 + readonly EGO_SUM_LICENSES
317 + readonly _GOMODULE_GOSUM_REVERSE_MAP
318 +
319 + # Set the guard that we are safe
320 + _GO_MODULE_SET_GLOBALS_CALLED=1
321 +}
322 +
323 +
324 # @FUNCTION: go-module_src_unpack
325 # @DESCRIPTION:
326 +# Extract & configure Go modules for consumpations.
327 +# - Modules listed in EGO_SUM are configured as a local GOPROXY via symlinks (fast!)
328 +# - Modules listed in EGO_VENDOR are extracted to "${S}/vendor" (slow)
329 +#
330 +# This function does NOT unpack the base distfile of a Go-based package.
331 +# While the entries in EGO_SUM will be listed in ${A}, they should NOT be
332 +# unpacked, Go will directly consume the files, including zips.
333 +go-module_src_unpack() {
334 + if [[ "${#EGO_VENDOR[@]}" -gt 0 ]]; then
335 + _go-module_src_unpack_vendor
336 + elif [[ "${#EGO_SUM[@]}" -gt 0 ]]; then
337 + _go-module_src_unpack_gosum
338 + else
339 + die "Neither EGO_SUM nor EGO_VENDOR are set!"
340 + fi
341 +}
342 +
343 +# @FUNCTION: _go-module_src_unpack_gosum
344 +# @DESCRIPTION:
345 +# Populate a GOPROXY directory hierarchy with distfiles from EGO_SUM
346 +#
347 +# Exports GOPROXY environment variable so that Go calls will source the
348 +# directory correctly.
349 +_go-module_src_unpack_gosum() {
350 + # shellcheck disable=SC2120
351 + debug-print-function "${FUNCNAME}" "$@"
352 +
353 + if [[ ! ${_GO_MODULE_SET_GLOBALS_CALLED} ]]; then
354 + die "go-module_set_globals must be called in global scope"
355 + fi
356 +
357 + local goproxy_dir="${T}/goproxy"
358 + mkdir -p "${goproxy_dir}" || die
359 +
360 + # For each Golang module distfile, look up where it's supposed to go, and
361 + # symlink into place.
362 + local _A
363 + local goproxy_mod_dir
364 + for _A in ${A}; do
365 + goproxy_mod_path="${_GOMODULE_GOSUM_REVERSE_MAP["${_A}"]}"
366 + if [[ -n "${goproxy_mod_path}" ]]; then
367 + debug-print-function "Populating goproxy for ${goproxy_mod_path}"
368 + # Build symlink hierarchy
369 + goproxy_mod_dir=$( dirname "${goproxy_dir}"/"${goproxy_mod_path}" )
370 + mkdir -p "${goproxy_mod_dir}" || die
371 + ln -sf "${DISTDIR}"/"${_A}" "${goproxy_dir}/${goproxy_mod_path}" || die "Failed to ln"
372 + local v=${goproxy_mod_path}
373 + v="${v%.mod}"
374 + v="${v%.zip}"
375 + v="${v//*\/}"
376 + _go-module_gosum_synthesize_files "${goproxy_mod_dir}" "${v}"
377 + fi
378 + done
379 + export GOPROXY="file://${goproxy_dir}"
380 +
381 + # Validate the gosum now
382 + _go-module_src_unpack_verify_gosum
383 +}
384 +
385 +# @FUNCTION: _go-module_gosum_synthesize_files
386 +# @DESCRIPTION:
387 +# Given a path & version, populate all Goproxy metadata files which aren't
388 +# needed to be downloaded directly.
389 +# - .../@v/${version}.info
390 +# - .../@v/list
391 +_go-module_gosum_synthesize_files() {
392 + local target=$1
393 + local version=$2
394 + # 'go get' doesn't care about the hash of the .info files, they
395 + # just need a 'version' element!
396 + # This saves a download of a tiny file
397 + # The .time key is omitted, because that is the time a module was added to the
398 + # upstream goproxy, and not metadata about the module itself.
399 + cat >"${target}/${version}.info" <<-EOF
400 + {
401 + "Version": "${version}",
402 + "shortName": "${version}",
403 + "Name": "${version}"
404 + }
405 + EOF
406 + listfile="${target}"/list
407 + if ! grep -sq -x -e "${version}" "${listfile}" 2>/dev/null; then
408 + echo "${version}" >>"${listfile}"
409 + fi
410 +}
411 +
412 +# @FUNCTION: _go-module_src_unpack_vendor
413 +# @DESCRIPTION:
414 # Extract all archives in ${a} which are not nentioned in ${EGO_VENDOR}
415 # to their usual locations then extract all archives mentioned in
416 # ${EGO_VENDOR} to ${S}/vendor.
417 -go-module_src_unpack() {
418 - debug-print-function ${FUNCNAME} "$@"
419 +_go-module_src_unpack_vendor() {
420 + # shellcheck disable=SC2120
421 + debug-print-function "${FUNCNAME}" "$@"
422 local f hash import line repo tarball vendor_tarballs x
423 vendor_tarballs=()
424 for line in "${EGO_VENDOR[@]}"; do
425 read -r import hash repo x <<< "${line}"
426 - if [[ -n $x ]]; then
427 + if [[ -n ${x} ]]; then
428 eerror "Trailing information in EGO_VENDOR in ${P}.ebuild"
429 eerror "${line}"
430 die "Invalid EGO_VENDOR format"
431 @@ -125,10 +432,10 @@ go-module_src_unpack() {
432 : "${repo:=${import}}"
433 vendor_tarballs+=("${repo//\//-}-${hash}.tar.gz")
434 done
435 - for f in $A; do
436 - [[ -n ${vendor_tarballs[*]} ]] && has "$f" "${vendor_tarballs[@]}" &&
437 + for f in ${A}; do
438 + [[ -n ${vendor_tarballs[*]} ]] && has "${f}" "${vendor_tarballs[@]}" &&
439 continue
440 - unpack "$f"
441 + unpack "${f}"
442 done
443
444 [[ -z ${vendor_tarballs[*]} ]] && return
445 @@ -145,13 +452,53 @@ go-module_src_unpack() {
446 done
447 }
448
449 +# @FUNCTION: _go-module_src_unpack_verify_gosum
450 +# @DESCRIPTION:
451 +# Validate the Go modules declared by EGO_SUM are sufficent to cover building
452 +# the package, without actually building it yet.
453 +#
454 +# This might be a good candidate for src_prepare in later versions of the
455 +# eclass, but all consumers will need updating!
456 +_go-module_src_unpack_verify_gosum() {
457 + # shellcheck disable=SC2120
458 + debug-print-function "${FUNCNAME}" "$@"
459 +
460 + if [[ ! ${_GO_MODULE_SET_GLOBALS_CALLED} ]]; then
461 + die "go-module_set_globals must be called in global scope"
462 + fi
463 +
464 + cd "${S}"
465 +
466 + # Cleanup the modules before starting anything else
467 + # This will print 'downloading' messages, but it's accessing content from
468 + # the $GOPROXY file:/// URL!
469 + einfo "Tidying go.mod/go.sum"
470 + go mod tidy >/dev/null
471 +
472 + # Verify that all needed modules are really present, by fetching everything
473 + # in the package's main go.mod. If the EGO_SUM was missing an entry then
474 + # 'go mod tidy' && 'go get' will flag it.
475 + # -v = verbose
476 + # -d = download only, don't install
477 + # -mod readonly = treat modules as readonly source
478 + einfo "Verifying linked Golang modules"
479 + GO111MODULE=on \
480 + go get \
481 + -v \
482 + -d \
483 + -mod readonly \
484 + all \
485 + || die "Some module is missing, update EGO_SUM"
486 +}
487 +
488 # @FUNCTION: go-module_live_vendor
489 # @DESCRIPTION:
490 # This function is used in live ebuilds to vendor the dependencies when
491 # upstream doesn't vendor them.
492 go-module_live_vendor() {
493 - debug-print-function ${FUNCNAME} "$@"
494 + debug-print-function "${FUNCNAME}" "$@"
495
496 + # shellcheck disable=SC2086
497 has live ${PROPERTIES} ||
498 die "${FUNCNAME} only allowed in live ebuilds"
499 [[ "${EBUILD_PHASE}" == unpack ]] ||
500 @@ -168,7 +515,7 @@ go-module_live_vendor() {
501 # @DESCRIPTION:
502 # Display a warning about security updates for Go programs.
503 go-module_pkg_postinst() {
504 - debug-print-function ${FUNCNAME} "$@"
505 + debug-print-function "${FUNCNAME}" "$@"
506 [[ -n ${REPLACING_VERSIONS} ]] && return 0
507 ewarn "${PN} is written in the Go programming language."
508 ewarn "Since this language is statically linked, security"
509 @@ -179,4 +526,30 @@ go-module_pkg_postinst() {
510 ewarn "stable tree."
511 }
512
513 +# @FUNCTION: _go-module_gomod_encode
514 +# @DESCRIPTION:
515 +# Encode the name(path) of a Golang module in the format expected by Goproxy.
516 +#
517 +# Upper letters are replaced by their lowercase version with a '!' prefix.
518 +#
519 +_go-module_gomod_encode() {
520 + ## Python:
521 + # return re.sub('([A-Z]{1})', r'!\1', s).lower()
522 +
523 + ## Sed:
524 + ## This uses GNU Sed extension \l to downcase the match
525 + #echo "${module}" |sed 's,[A-Z],!\l&,g'
526 + #
527 + # Bash variant:
528 + debug-print-function "${FUNCNAME}" "$@"
529 + #local re input lower
530 + re='(.*)([A-Z])(.*)'
531 + input="${1}"
532 + while [[ ${input} =~ ${re} ]]; do
533 + lower='!'"${BASH_REMATCH[2],}"
534 + input="${BASH_REMATCH[1]}${lower}${BASH_REMATCH[3]}"
535 + done
536 + echo "${input}"
537 +}
538 +
539 fi
540 diff --git profiles/thirdpartymirrors profiles/thirdpartymirrors
541 index ad4c4b972146..d60f166e07c9 100644
542 --- profiles/thirdpartymirrors
543 +++ profiles/thirdpartymirrors
544 @@ -25,3 +25,4 @@ sourceforge https://download.sourceforge.net
545 sourceforge.jp http://iij.dl.sourceforge.jp https://osdn.dl.sourceforge.jp https://jaist.dl.sourceforge.jp
546 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/
547 vdr-developerorg http://projects.vdr-developer.org/attachments/download
548 +goproxy https://proxy.golang.org/ https://goproxy.io/ https://gocenter.io/
549 --
550 2.25.0

Replies