[PATCH] fetch2/gomod: support pipe-separated proxy list in GO_MOD_PROXY
tengf <[email protected]> Fri, 10 Jul 2026 17:40:53 +0800
| Newsgroups | org.openembedded.lists.bitbake-devel |
|---|---|
| Message-ID | <[email protected]> |
From cb7d8f5176781eb441ec83241f05ab272d4611c6 Mon Sep 17 00:00:00 2001 From: tengf <[email protected]> Date: Fri, 10 Jul 2026 16:54:20 +0800 Subject: [PATCH] fetch2/gomod: support pipe-separated proxy list in GO_MOD_PROXY MIME-Version: 1.0 Content-Type: text/plain; charset=3DUTF-8 Content-Transfer-Encoding: 8bit The Go toolchain supports GOPROXY as a pipe-separated list of proxies tried in order (e.g. "goproxy.cn|proxy.golang.org|direct"). The BitBake gomod fetcher previously passed the entire string verbatim as an HTTP hostname, causing fetch failures in environments where proxy.golang.org is unreachable and a mirror such as goproxy.cn is required. Parse GO_MOD_PROXY as a '|'-separated list and try each proxy in order in a new download() override on the GoMod class. The special token "direct" is silently skipped =E2=80=94 direct VCS fetching is handled by = the gomodgit fetcher. If every proxy fails, a FetchError is raised listing the module and version. A single proxy value (the existing usage) is fully backward compatible. Add two unit tests that exercise proxy-list parsing without requiring network access: - test_gomod_multi_proxy_uses_first_proxy: verifies the first non-direct proxy is selected for ud.url and that the full proxy list is stored. - test_gomod_direct_only_falls_back_to_default: verifies that a "direct"-only value falls back to proxy.golang.org. Signed-off-by: tengf <[email protected]> --- lib/bb/fetch2/gomod.py | 39 +++++++++++++++++++++++++++++++++++---- lib/bb/tests/fetch.py | 22 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/lib/bb/fetch2/gomod.py b/lib/bb/fetch2/gomod.py index 5cdf8f998..7bb91e2a0 100644 --- a/lib/bb/fetch2/gomod.py +++ b/lib/bb/fetch2/gomod.py @@ -48,7 +48,10 @@ Optional SRC_URI parameters: Related variables: =20 - GO_MOD_PROXY - The module proxy used by the fetcher. + The module proxy used by the fetcher. Supports a pipe-separated list= of + proxies tried in order, matching Go's GOPROXY syntax (e.g. + "goproxy.cn|proxy.golang.org|direct"). The special value "direct" is + skipped (direct VCS fetching is handled by the gomodgit fetcher). =20 - GO_MOD_CACHE_DIR The directory where the module cache is located. @@ -97,7 +100,7 @@ class GoMod(Wget): cache/download/<module>/@v/<version>.mod: The go.mod file. """ =20 - proxy =3D d.getVar('GO_MOD_PROXY') or 'proxy.golang.org' + proxy_setting =3D d.getVar('GO_MOD_PROXY') or 'proxy.golang.org' moddir =3D d.getVar('GO_MOD_CACHE_DIR') or 'pkg/mod' =20 if 'version' not in ud.parm: @@ -109,14 +112,24 @@ class GoMod(Wget): ud.parm['module'] =3D module version =3D ud.parm['version'] =20 - # Set URL and filename for wget download + # Parse pipe-separated proxy list (Go GOPROXY syntax); drop "dir= ect" + # entries since direct VCS fetching is the job of the gomodgit f= etcher. + proxies =3D [p for p in proxy_setting.split('|') if p.strip() an= d p.strip() !=3D 'direct'] + if not proxies: + proxies =3D ['proxy.golang.org'] + ud.parm['proxies'] =3D proxies + + # Set URL and filename for wget download. Use the first proxy f= or + # urldata_init so that localpath / checksum keys are stable rega= rdless + # of which proxy ultimately serves the file. if ud.parm.get('mod', '0') =3D=3D '1': ext =3D '.mod' else: ext =3D '.zip' path =3D escape(f"{module}/@v/{version}{ext}") + ud.parm['mod_path'] =3D path ud.url =3D bb.fetch2.encodeurl( - ('https', proxy, '/' + path, None, None, None)) + ('https', proxies[0], '/' + path, None, None, None)) ud.parm['downloadfilename'] =3D f"{module.replace('/', '.')}@{v= ersion}{ext}" =20 # Set name for checksum verification @@ -127,6 +140,24 @@ class GoMod(Wget): =20 super().urldata_init(ud, d) =20 + def download(self, ud, d): + """Try each proxy in GO_MOD_PROXY in order, falling back on fail= ure.""" + proxies =3D ud.parm.get('proxies', ['proxy.golang.org']) + path =3D ud.parm['mod_path'] + last_exc =3D None + for proxy in proxies: + ud.url =3D bb.fetch2.encodeurl( + ('https', proxy, '/' + path, None, None, None)) + try: + super().download(ud, d) + return + except FetchError as e: + bb.warn(f"GoMod: proxy {proxy} failed for {ud.parm['modu= le']}: {e}, trying next proxy") + last_exc =3D e + raise FetchError( + f"All proxies failed for {ud.parm['module']}@{ud.parm['versi= on']}: {last_exc}", + ud.url) + def unpack(self, ud, rootdir, d): """Unpack the module in the module cache.""" =20 diff --git a/lib/bb/tests/fetch.py b/lib/bb/tests/fetch.py index d021ad786..76b2e203c 100644 --- a/lib/bb/tests/fetch.py +++ b/lib/bb/tests/fetch.py @@ -3815,6 +3815,28 @@ class GoModTest(FetcherTest): self.assertEqual(bb.utils.sha256_file(os.path.join(downloaddir, = 'go.opencensus.io/@v/v0.24.0.mod')), '0dc9ccc660ad21cebaffd548f2cc6efa27891c68b4fbc1= f8a3893b00f1acec96') =20 + def test_gomod_multi_proxy_uses_first_proxy(self): + """First non-direct proxy in GO_MOD_PROXY is used to build ud.ur= l.""" + urls =3D ['gomod://golang.org/x/net;version=3Dv0.9.0;' + 'sha256sum=3Dd9b70a10f88ededacb4e0e974de5d5a98e3db9c1e0e= 01c4dbfe5a5279b65a0c'] + self.d.setVar('GO_MOD_PROXY', 'goproxy.cn|proxy.golang.org|direc= t') + + fetcher =3D bb.fetch2.Fetch(urls, self.d) + ud =3D fetcher.ud[urls[0]] + self.assertIn('goproxy.cn', ud.url) + self.assertEqual(ud.parm['proxies'], ['goproxy.cn', 'proxy.golan= g.org']) + + def test_gomod_direct_only_falls_back_to_default(self): + """A GO_MOD_PROXY of 'direct' (only) falls back to proxy.golang.= org.""" + urls =3D ['gomod://golang.org/x/net;version=3Dv0.9.0;' + 'sha256sum=3Dd9b70a10f88ededacb4e0e974de5d5a98e3db9c1e0e= 01c4dbfe5a5279b65a0c'] + self.d.setVar('GO_MOD_PROXY', 'direct') + + fetcher =3D bb.fetch2.Fetch(urls, self.d) + ud =3D fetcher.ud[urls[0]] + self.assertEqual(ud.parm['proxies'], ['proxy.golang.org']) + self.assertIn('proxy.golang.org', ud.url) + class GoModGitTest(FetcherTest): =20 @skipIfNoNetwork() --=20 2.34.1