diff options
| -rw-r--r-- | bitbake/lib/bb/fetch2/__init__.py | 4 | ||||
| -rw-r--r-- | bitbake/lib/bb/fetch2/gomod.py | 128 | ||||
| -rw-r--r-- | bitbake/lib/bb/tests/fetch.py | 65 |
3 files changed, 196 insertions, 1 deletions
diff --git a/bitbake/lib/bb/fetch2/__init__.py b/bitbake/lib/bb/fetch2/__init__.py index 5bf2c4b8cf..f84ce59992 100644 --- a/bitbake/lib/bb/fetch2/__init__.py +++ b/bitbake/lib/bb/fetch2/__init__.py | |||
| @@ -1317,7 +1317,7 @@ class FetchData(object): | |||
| 1317 | 1317 | ||
| 1318 | if checksum_name in self.parm: | 1318 | if checksum_name in self.parm: |
| 1319 | checksum_expected = self.parm[checksum_name] | 1319 | checksum_expected = self.parm[checksum_name] |
| 1320 | elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3", "az", "crate", "gs"]: | 1320 | elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3", "az", "crate", "gs", "gomod"]: |
| 1321 | checksum_expected = None | 1321 | checksum_expected = None |
| 1322 | else: | 1322 | else: |
| 1323 | checksum_expected = d.getVarFlag("SRC_URI", checksum_name) | 1323 | checksum_expected = d.getVarFlag("SRC_URI", checksum_name) |
| @@ -2088,6 +2088,7 @@ from . import npmsw | |||
| 2088 | from . import az | 2088 | from . import az |
| 2089 | from . import crate | 2089 | from . import crate |
| 2090 | from . import gcp | 2090 | from . import gcp |
| 2091 | from . import gomod | ||
| 2091 | 2092 | ||
| 2092 | methods.append(local.Local()) | 2093 | methods.append(local.Local()) |
| 2093 | methods.append(wget.Wget()) | 2094 | methods.append(wget.Wget()) |
| @@ -2110,3 +2111,4 @@ methods.append(npmsw.NpmShrinkWrap()) | |||
| 2110 | methods.append(az.Az()) | 2111 | methods.append(az.Az()) |
| 2111 | methods.append(crate.Crate()) | 2112 | methods.append(crate.Crate()) |
| 2112 | methods.append(gcp.GCP()) | 2113 | methods.append(gcp.GCP()) |
| 2114 | methods.append(gomod.GoMod()) | ||
diff --git a/bitbake/lib/bb/fetch2/gomod.py b/bitbake/lib/bb/fetch2/gomod.py new file mode 100644 index 0000000000..fe025e367f --- /dev/null +++ b/bitbake/lib/bb/fetch2/gomod.py | |||
| @@ -0,0 +1,128 @@ | |||
| 1 | """ | ||
| 2 | BitBake 'Fetch' implementation for Go modules | ||
| 3 | |||
| 4 | The gomod fetcher is used to download Go modules to the module cache from a | ||
| 5 | module proxy. | ||
| 6 | |||
| 7 | Example SRC_URI: | ||
| 8 | |||
| 9 | SRC_URI += "gomod://golang.org/x/net;version=v0.9.0;sha256sum=..." | ||
| 10 | |||
| 11 | Required SRC_URI parameters: | ||
| 12 | |||
| 13 | - version | ||
| 14 | The version of the module. | ||
| 15 | |||
| 16 | Optional SRC_URI parameters: | ||
| 17 | |||
| 18 | - mod | ||
| 19 | Fetch and unpack the go.mod file only instead of the complete module. | ||
| 20 | The go command may need to download go.mod files for many different modules | ||
| 21 | when computing the build list, and go.mod files are much smaller than | ||
| 22 | module zip files. | ||
| 23 | The default is "0", set mod=1 for the go.mod file only. | ||
| 24 | |||
| 25 | - sha256sum | ||
| 26 | The checksum of the module zip file, or the go.mod file in case of fetching | ||
| 27 | only the go.mod file. Alternatively, set the SRC_URI varible flag for | ||
| 28 | "module@version.sha256sum". | ||
| 29 | |||
| 30 | Related variables: | ||
| 31 | |||
| 32 | - GO_MOD_PROXY | ||
| 33 | The module proxy used by the fetcher. | ||
| 34 | |||
| 35 | - GO_MOD_CACHE_DIR | ||
| 36 | The directory where the module cache is located. | ||
| 37 | This must match the exported GOMODCACHE variable for the go command to find | ||
| 38 | the downloaded modules. | ||
| 39 | |||
| 40 | See the Go modules reference, https://go.dev/ref/mod, for more information | ||
| 41 | about the module cache, module proxies and version control systems. | ||
| 42 | """ | ||
| 43 | |||
| 44 | import os | ||
| 45 | import re | ||
| 46 | import shutil | ||
| 47 | import zipfile | ||
| 48 | |||
| 49 | import bb | ||
| 50 | from bb.fetch2 import FetchError | ||
| 51 | from bb.fetch2 import MissingParameterError | ||
| 52 | from bb.fetch2.wget import Wget | ||
| 53 | |||
| 54 | |||
| 55 | def escape(path): | ||
| 56 | """Escape capital letters using exclamation points.""" | ||
| 57 | return re.sub(r'([A-Z])', lambda m: '!' + m.group(1).lower(), path) | ||
| 58 | |||
| 59 | |||
| 60 | class GoMod(Wget): | ||
| 61 | """Class to fetch Go modules from a Go module proxy via wget""" | ||
| 62 | |||
| 63 | def supports(self, ud, d): | ||
| 64 | """Check to see if a given URL is for this fetcher.""" | ||
| 65 | return ud.type == 'gomod' | ||
| 66 | |||
| 67 | def urldata_init(self, ud, d): | ||
| 68 | """Set up to download the module from the module proxy. | ||
| 69 | |||
| 70 | Set up to download the module zip file to the module cache directory | ||
| 71 | and unpack the go.mod file (unless downloading only the go.mod file): | ||
| 72 | |||
| 73 | cache/download/<module>/@v/<version>.zip: The module zip file. | ||
| 74 | cache/download/<module>/@v/<version>.mod: The go.mod file. | ||
| 75 | """ | ||
| 76 | |||
| 77 | proxy = d.getVar('GO_MOD_PROXY') or 'proxy.golang.org' | ||
| 78 | moddir = d.getVar('GO_MOD_CACHE_DIR') or 'pkg/mod' | ||
| 79 | |||
| 80 | if 'version' not in ud.parm: | ||
| 81 | raise MissingParameterError('version', ud.url) | ||
| 82 | |||
| 83 | module = ud.host + ud.path | ||
| 84 | ud.parm['module'] = module | ||
| 85 | |||
| 86 | # Set URL and filename for wget download | ||
| 87 | path = escape(module + '/@v/' + ud.parm['version']) | ||
| 88 | if ud.parm.get('mod', '0') == '1': | ||
| 89 | path += '.mod' | ||
| 90 | else: | ||
| 91 | path += '.zip' | ||
| 92 | ud.parm['unpack'] = '0' | ||
| 93 | ud.url = bb.fetch2.encodeurl( | ||
| 94 | ('https', proxy, '/' + path, None, None, None)) | ||
| 95 | ud.parm['downloadfilename'] = path | ||
| 96 | |||
| 97 | # Set name parameter if sha256sum is set in recipe | ||
| 98 | name = f"{module}@{ud.parm['version']}" | ||
| 99 | if d.getVarFlag('SRC_URI', name + '.sha256sum'): | ||
| 100 | ud.parm['name'] = name | ||
| 101 | |||
| 102 | # Set subdir for unpack | ||
| 103 | ud.parm['subdir'] = os.path.join(moddir, 'cache/download', | ||
| 104 | os.path.dirname(path)) | ||
| 105 | |||
| 106 | super().urldata_init(ud, d) | ||
| 107 | |||
| 108 | def unpack(self, ud, rootdir, d): | ||
| 109 | """Unpack the module in the module cache.""" | ||
| 110 | |||
| 111 | # Unpack the module zip file or go.mod file | ||
| 112 | super().unpack(ud, rootdir, d) | ||
| 113 | |||
| 114 | if ud.localpath.endswith('.zip'): | ||
| 115 | # Unpack the go.mod file from the zip file | ||
| 116 | module = ud.parm['module'] | ||
| 117 | unpackdir = os.path.join(rootdir, ud.parm['subdir']) | ||
| 118 | name = os.path.basename(ud.localpath).rsplit('.', 1)[0] + '.mod' | ||
| 119 | bb.note(f"Unpacking {name} to {unpackdir}/") | ||
| 120 | with zipfile.ZipFile(ud.localpath) as zf: | ||
| 121 | with open(os.path.join(unpackdir, name), mode='wb') as mf: | ||
| 122 | try: | ||
| 123 | f = module + '@' + ud.parm['version'] + '/go.mod' | ||
| 124 | shutil.copyfileobj(zf.open(f), mf) | ||
| 125 | except KeyError: | ||
| 126 | # If the module does not have a go.mod file, synthesize | ||
| 127 | # one containing only a module statement. | ||
| 128 | mf.write(f'module {module}\n'.encode()) | ||
diff --git a/bitbake/lib/bb/tests/fetch.py b/bitbake/lib/bb/tests/fetch.py index 2ef2063436..2365a50960 100644 --- a/bitbake/lib/bb/tests/fetch.py +++ b/bitbake/lib/bb/tests/fetch.py | |||
| @@ -3390,3 +3390,68 @@ class FetchPremirroronlyBrokenTarball(FetcherTest): | |||
| 3390 | fetcher.download() | 3390 | fetcher.download() |
| 3391 | output = "".join(logs.output) | 3391 | output = "".join(logs.output) |
| 3392 | self.assertFalse(" not a git repository (or any parent up to mount point /)" in output) | 3392 | self.assertFalse(" not a git repository (or any parent up to mount point /)" in output) |
| 3393 | |||
| 3394 | class GoModTest(FetcherTest): | ||
| 3395 | |||
| 3396 | @skipIfNoNetwork() | ||
| 3397 | def test_gomod_url(self): | ||
| 3398 | urls = ['gomod://github.com/Azure/azure-sdk-for-go/sdk/storage/azblob;version=v1.0.0;' | ||
| 3399 | 'sha256sum=9bb69aea32f1d59711701f9562d66432c9c0374205e5009d1d1a62f03fb4fdad'] | ||
| 3400 | |||
| 3401 | fetcher = bb.fetch2.Fetch(urls, self.d) | ||
| 3402 | ud = fetcher.ud[urls[0]] | ||
| 3403 | self.assertEqual(ud.url, 'https://proxy.golang.org/github.com/%21azure/azure-sdk-for-go/sdk/storage/azblob/%40v/v1.0.0.zip') | ||
| 3404 | self.assertNotIn('name', ud.parm) | ||
| 3405 | |||
| 3406 | fetcher.download() | ||
| 3407 | fetcher.unpack(self.unpackdir) | ||
| 3408 | downloaddir = os.path.join(self.unpackdir, 'pkg/mod/cache/download') | ||
| 3409 | self.assertTrue(os.path.exists(os.path.join(downloaddir, 'github.com/!azure/azure-sdk-for-go/sdk/storage/azblob/@v/v1.0.0.zip'))) | ||
| 3410 | self.assertTrue(os.path.exists(os.path.join(downloaddir, 'github.com/!azure/azure-sdk-for-go/sdk/storage/azblob/@v/v1.0.0.mod'))) | ||
| 3411 | |||
| 3412 | @skipIfNoNetwork() | ||
| 3413 | def test_gomod_url_go_mod_only(self): | ||
| 3414 | urls = ['gomod://github.com/Azure/azure-sdk-for-go/sdk/storage/azblob;version=v1.0.0;mod=1;' | ||
| 3415 | 'sha256sum=7873b8544842329b4f385a3aa6cf82cc2bc8defb41a04fa5291c35fd5900e873'] | ||
| 3416 | |||
| 3417 | fetcher = bb.fetch2.Fetch(urls, self.d) | ||
| 3418 | ud = fetcher.ud[urls[0]] | ||
| 3419 | self.assertEqual(ud.url, 'https://proxy.golang.org/github.com/%21azure/azure-sdk-for-go/sdk/storage/azblob/%40v/v1.0.0.mod') | ||
| 3420 | self.assertNotIn('name', ud.parm) | ||
| 3421 | |||
| 3422 | fetcher.download() | ||
| 3423 | fetcher.unpack(self.unpackdir) | ||
| 3424 | downloaddir = os.path.join(self.unpackdir, 'pkg/mod/cache/download') | ||
| 3425 | self.assertTrue(os.path.exists(os.path.join(downloaddir, 'github.com/!azure/azure-sdk-for-go/sdk/storage/azblob/@v/v1.0.0.mod'))) | ||
| 3426 | |||
| 3427 | @skipIfNoNetwork() | ||
| 3428 | def test_gomod_url_sha256sum_varflag(self): | ||
| 3429 | urls = ['gomod://gopkg.in/ini.v1;version=v1.67.0'] | ||
| 3430 | self.d.setVarFlag('SRC_URI', 'gopkg.in/ini.v1@v1.67.0.sha256sum', 'bd845dfc762a87a56e5a32a07770dc83e86976db7705d7f89c5dbafdc60b06c6') | ||
| 3431 | |||
| 3432 | fetcher = bb.fetch2.Fetch(urls, self.d) | ||
| 3433 | ud = fetcher.ud[urls[0]] | ||
| 3434 | self.assertEqual(ud.url, 'https://proxy.golang.org/gopkg.in/ini.v1/%40v/v1.67.0.zip') | ||
| 3435 | self.assertEqual(ud.parm['name'], 'gopkg.in/ini.v1@v1.67.0') | ||
| 3436 | |||
| 3437 | fetcher.download() | ||
| 3438 | fetcher.unpack(self.unpackdir) | ||
| 3439 | downloaddir = os.path.join(self.unpackdir, 'pkg/mod/cache/download') | ||
| 3440 | self.assertTrue(os.path.exists(os.path.join(downloaddir, 'gopkg.in/ini.v1/@v/v1.67.0.zip'))) | ||
| 3441 | self.assertTrue(os.path.exists(os.path.join(downloaddir, 'gopkg.in/ini.v1/@v/v1.67.0.mod'))) | ||
| 3442 | |||
| 3443 | @skipIfNoNetwork() | ||
| 3444 | def test_gomod_url_no_go_mod_in_module(self): | ||
| 3445 | urls = ['gomod://gopkg.in/ini.v1;version=v1.67.0;' | ||
| 3446 | 'sha256sum=bd845dfc762a87a56e5a32a07770dc83e86976db7705d7f89c5dbafdc60b06c6'] | ||
| 3447 | |||
| 3448 | fetcher = bb.fetch2.Fetch(urls, self.d) | ||
| 3449 | ud = fetcher.ud[urls[0]] | ||
| 3450 | self.assertEqual(ud.url, 'https://proxy.golang.org/gopkg.in/ini.v1/%40v/v1.67.0.zip') | ||
| 3451 | self.assertNotIn('name', ud.parm) | ||
| 3452 | |||
| 3453 | fetcher.download() | ||
| 3454 | fetcher.unpack(self.unpackdir) | ||
| 3455 | downloaddir = os.path.join(self.unpackdir, 'pkg/mod/cache/download') | ||
| 3456 | self.assertTrue(os.path.exists(os.path.join(downloaddir, 'gopkg.in/ini.v1/@v/v1.67.0.zip'))) | ||
| 3457 | self.assertTrue(os.path.exists(os.path.join(downloaddir, 'gopkg.in/ini.v1/@v/v1.67.0.mod'))) | ||
