diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-01-04 11:40:15 -0500 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-01-04 11:40:15 -0500 |
| commit | dbf720ccb0519a4dbf143dbaed1633527b8d7b60 (patch) | |
| tree | 16c72e1b58d63d1a116a3a91b02e13646ba8bee0 | |
| parent | d7434129524162f356c3cd154f46a395c9076df7 (diff) | |
| download | meta-virtualization-dbf720ccb0519a4dbf143dbaed1633527b8d7b60.tar.gz | |
go-mod-fetcher: fix shallow clone handling, duplicates, and discovery workflow
oe-go-mod-fetcher.py:
- Remove BB_GIT_SHALLOW_EXTRA_REFS generation - refs must be present in
ALL repositories which isn't the case for module dependencies. Instead,
use tag= parameter in individual SRC_URI entries.
- Add tag=<tagname> to SRC_URI when ref is a tag, allowing BitBake's
shallow clone to include the necessary tag (with BB_GIT_SHALLOW=1)
- Remove premature _ref_points_to_commit() check that was clearing
ref_hints before repos were fetched, preventing tag= from being added
- Fix pseudo-version verification: only use shallow fetch for actual
tags (refs/tags/...), not branch refs. Pseudo-versions with branch
refs (refs/heads/...) now correctly use unshallow path to reach
historical commits that aren't fetchable with depth=1
oe-go-mod-fetcher-hybrid.py:
- Fix duplicate SRC_URI entries when multiple modules share the same
git repo/commit (e.g., errdefs and errdefs/pkg). Track added vcs_hashes
to skip duplicates.
- Add --discovery-cache option to calculate module sizes from discovery
cache .zip files, enabling size recommendations during discover_and_generate
go-mod-discovery.bbclass:
- Add automatic hybrid mode recommendations after generate_modules,
showing module sizes and suggested --git prefixes for conversion
- Add GO_MOD_DISCOVERY_SKIP_VERIFY variable to skip commit verification
on retries (useful after fixing verification issues)
- Pass --discovery-cache to hybrid script for accurate size calculations
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
| -rw-r--r-- | classes/go-mod-discovery.bbclass | 81 | ||||
| -rwxr-xr-x | scripts/oe-go-mod-fetcher-hybrid.py | 46 | ||||
| -rwxr-xr-x | scripts/oe-go-mod-fetcher.py | 32 |
3 files changed, 135 insertions, 24 deletions
diff --git a/classes/go-mod-discovery.bbclass b/classes/go-mod-discovery.bbclass index fc7c2008..d41d70a5 100644 --- a/classes/go-mod-discovery.bbclass +++ b/classes/go-mod-discovery.bbclass | |||
| @@ -117,6 +117,11 @@ GO_MOD_DISCOVERY_GIT_REF ?= "${SRCREV}" | |||
| 117 | # Recipe directory for generated .inc files - defaults to recipe's directory | 117 | # Recipe directory for generated .inc files - defaults to recipe's directory |
| 118 | GO_MOD_DISCOVERY_RECIPEDIR ?= "${FILE_DIRNAME}" | 118 | GO_MOD_DISCOVERY_RECIPEDIR ?= "${FILE_DIRNAME}" |
| 119 | 119 | ||
| 120 | # Skip commit verification during generation (use cached results only) | ||
| 121 | # Set to "1" to skip verification on retries after initial discovery | ||
| 122 | # Usage: GO_MOD_DISCOVERY_SKIP_VERIFY = "1" in local.conf or recipe | ||
| 123 | GO_MOD_DISCOVERY_SKIP_VERIFY ?= "" | ||
| 124 | |||
| 120 | # Empty default for TAGS if not set by recipe (avoids undefined variable errors) | 125 | # Empty default for TAGS if not set by recipe (avoids undefined variable errors) |
| 121 | TAGS ?= "" | 126 | TAGS ?= "" |
| 122 | 127 | ||
| @@ -384,11 +389,19 @@ Or run 'bitbake ${PN} -c show_upgrade_commands' to see manual options." | |||
| 384 | bbfatal "Could not find oe-go-mod-fetcher.py in any layer" | 389 | bbfatal "Could not find oe-go-mod-fetcher.py in any layer" |
| 385 | fi | 390 | fi |
| 386 | 391 | ||
| 392 | # Build fetcher command with optional flags | ||
| 393 | SKIP_VERIFY_FLAG="" | ||
| 394 | if [ "${GO_MOD_DISCOVERY_SKIP_VERIFY}" = "1" ]; then | ||
| 395 | echo "NOTE: Skipping commit verification (GO_MOD_DISCOVERY_SKIP_VERIFY=1)" | ||
| 396 | SKIP_VERIFY_FLAG="--skip-verify" | ||
| 397 | fi | ||
| 398 | |||
| 387 | python3 "${FETCHER_SCRIPT}" \ | 399 | python3 "${FETCHER_SCRIPT}" \ |
| 388 | --discovered-modules "${GO_MOD_DISCOVERY_MODULES_JSON}" \ | 400 | --discovered-modules "${GO_MOD_DISCOVERY_MODULES_JSON}" \ |
| 389 | --git-repo "${GO_MOD_DISCOVERY_GIT_REPO}" \ | 401 | --git-repo "${GO_MOD_DISCOVERY_GIT_REPO}" \ |
| 390 | --git-ref "${GO_MOD_DISCOVERY_GIT_REF}" \ | 402 | --git-ref "${GO_MOD_DISCOVERY_GIT_REF}" \ |
| 391 | --recipedir "${GO_MOD_DISCOVERY_RECIPEDIR}" | 403 | --recipedir "${GO_MOD_DISCOVERY_RECIPEDIR}" \ |
| 404 | ${SKIP_VERIFY_FLAG} | ||
| 392 | 405 | ||
| 393 | if [ $? -eq 0 ]; then | 406 | if [ $? -eq 0 ]; then |
| 394 | echo "" | 407 | echo "" |
| @@ -411,6 +424,70 @@ addtask generate_modules | |||
| 411 | do_generate_modules[nostamp] = "1" | 424 | do_generate_modules[nostamp] = "1" |
| 412 | do_generate_modules[vardeps] += "GO_MOD_DISCOVERY_MODULES_JSON GO_MOD_DISCOVERY_GIT_REPO \ | 425 | do_generate_modules[vardeps] += "GO_MOD_DISCOVERY_MODULES_JSON GO_MOD_DISCOVERY_GIT_REPO \ |
| 413 | GO_MOD_DISCOVERY_GIT_REF GO_MOD_DISCOVERY_RECIPEDIR" | 426 | GO_MOD_DISCOVERY_GIT_REF GO_MOD_DISCOVERY_RECIPEDIR" |
| 427 | do_generate_modules[postfuncs] = "do_show_hybrid_recommendation" | ||
| 428 | |||
| 429 | # Show hybrid conversion recommendations after VCS generation | ||
| 430 | python do_show_hybrid_recommendation() { | ||
| 431 | """ | ||
| 432 | Show recommendations for converting to hybrid gomod:// + git:// mode. | ||
| 433 | Runs automatically after generate_modules completes. | ||
| 434 | """ | ||
| 435 | import subprocess | ||
| 436 | from pathlib import Path | ||
| 437 | |||
| 438 | recipedir = d.getVar('GO_MOD_DISCOVERY_RECIPEDIR') | ||
| 439 | git_inc = Path(recipedir) / 'go-mod-git.inc' | ||
| 440 | |||
| 441 | if not git_inc.exists(): | ||
| 442 | return | ||
| 443 | |||
| 444 | # Find the hybrid script | ||
| 445 | layerdir = None | ||
| 446 | for layer in d.getVar('BBLAYERS').split(): | ||
| 447 | if 'meta-virtualization' in layer: | ||
| 448 | layerdir = layer | ||
| 449 | break | ||
| 450 | |||
| 451 | if not layerdir: | ||
| 452 | return | ||
| 453 | |||
| 454 | scriptpath = Path(layerdir) / "scripts" / "oe-go-mod-fetcher-hybrid.py" | ||
| 455 | if not scriptpath.exists(): | ||
| 456 | return | ||
| 457 | |||
| 458 | bb.plain("") | ||
| 459 | bb.plain("=" * 70) | ||
| 460 | bb.plain("HYBRID MODE RECOMMENDATION") | ||
| 461 | bb.plain("=" * 70) | ||
| 462 | |||
| 463 | cmd = ['python3', str(scriptpath), '--recipedir', recipedir, '--recommend'] | ||
| 464 | |||
| 465 | # Try to find module sizes from discovery cache or vcs_cache | ||
| 466 | discovery_dir = d.getVar('GO_MOD_DISCOVERY_DIR') | ||
| 467 | workdir = d.getVar('WORKDIR') | ||
| 468 | |||
| 469 | # Check discovery cache first (has .zip files with accurate sizes) | ||
| 470 | if discovery_dir: | ||
| 471 | discovery_cache = Path(discovery_dir) / 'cache' / 'cache' / 'download' | ||
| 472 | if discovery_cache.exists(): | ||
| 473 | cmd.extend(['--discovery-cache', str(discovery_cache)]) | ||
| 474 | |||
| 475 | # Also check vcs_cache if it exists (from a previous build) | ||
| 476 | if workdir: | ||
| 477 | vcs_cache = Path(workdir) / 'sources' / 'vcs_cache' | ||
| 478 | if vcs_cache.exists(): | ||
| 479 | cmd.extend(['--workdir', workdir]) | ||
| 480 | |||
| 481 | try: | ||
| 482 | result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) | ||
| 483 | if result.stdout: | ||
| 484 | for line in result.stdout.splitlines(): | ||
| 485 | bb.plain(line) | ||
| 486 | bb.plain("") | ||
| 487 | bb.plain("") | ||
| 488 | except Exception as e: | ||
| 489 | bb.note(f"Could not run hybrid recommendation: {e}") | ||
| 490 | } | ||
| 414 | 491 | ||
| 415 | # ============================================================================= | 492 | # ============================================================================= |
| 416 | # TASK 4: do_discover_and_generate - All-in-one convenience task | 493 | # TASK 4: do_discover_and_generate - All-in-one convenience task |
| @@ -443,7 +520,7 @@ addtask discover_and_generate after do_unpack | |||
| 443 | do_discover_and_generate[depends] = "${PN}:do_prepare_recipe_sysroot" | 520 | do_discover_and_generate[depends] = "${PN}:do_prepare_recipe_sysroot" |
| 444 | do_discover_and_generate[network] = "1" | 521 | do_discover_and_generate[network] = "1" |
| 445 | do_discover_and_generate[nostamp] = "1" | 522 | do_discover_and_generate[nostamp] = "1" |
| 446 | do_discover_and_generate[postfuncs] = "do_discover_modules do_extract_modules do_generate_modules" | 523 | do_discover_and_generate[postfuncs] = "do_discover_modules do_extract_modules do_generate_modules do_show_hybrid_recommendation" |
| 447 | 524 | ||
| 448 | # ============================================================================= | 525 | # ============================================================================= |
| 449 | # TASK: do_clean_discovery - Clean the persistent cache | 526 | # TASK: do_clean_discovery - Clean the persistent cache |
diff --git a/scripts/oe-go-mod-fetcher-hybrid.py b/scripts/oe-go-mod-fetcher-hybrid.py index f5934ed2..9704cca0 100755 --- a/scripts/oe-go-mod-fetcher-hybrid.py +++ b/scripts/oe-go-mod-fetcher-hybrid.py | |||
| @@ -241,6 +241,35 @@ def get_repo_sizes(vcs_info: dict, workdir: Optional[Path] = None) -> dict[str, | |||
| 241 | return sizes | 241 | return sizes |
| 242 | 242 | ||
| 243 | 243 | ||
| 244 | def get_discovery_sizes(modules: list[dict], discovery_cache: Optional[Path] = None) -> dict[str, int]: | ||
| 245 | """Get sizes of modules from discovery cache .zip files.""" | ||
| 246 | sizes = {} | ||
| 247 | |||
| 248 | if discovery_cache is None or not discovery_cache.exists(): | ||
| 249 | return sizes | ||
| 250 | |||
| 251 | for mod in modules: | ||
| 252 | module_path = mod.get('module', '') | ||
| 253 | version = mod.get('version', '') | ||
| 254 | vcs_hash = mod.get('vcs_hash', '') | ||
| 255 | |||
| 256 | if not module_path or not version or not vcs_hash: | ||
| 257 | continue | ||
| 258 | |||
| 259 | # Build path to .zip file: discovery_cache/<module>/@v/<version>.zip | ||
| 260 | zip_path = discovery_cache / module_path / '@v' / f'{version}.zip' | ||
| 261 | |||
| 262 | if zip_path.exists(): | ||
| 263 | try: | ||
| 264 | size = zip_path.stat().st_size | ||
| 265 | # Accumulate size by vcs_hash (same repo may have multiple modules) | ||
| 266 | sizes[vcs_hash] = sizes.get(vcs_hash, 0) + size | ||
| 267 | except OSError: | ||
| 268 | pass | ||
| 269 | |||
| 270 | return sizes | ||
| 271 | |||
| 272 | |||
| 244 | def format_size(size_bytes: int) -> str: | 273 | def format_size(size_bytes: int) -> str: |
| 245 | """Format bytes as human readable.""" | 274 | """Format bytes as human readable.""" |
| 246 | for unit in ['B', 'KB', 'MB', 'GB']: | 275 | for unit in ['B', 'KB', 'MB', 'GB']: |
| @@ -604,6 +633,9 @@ def main(): | |||
| 604 | parser.add_argument('--workdir', type=Path, default=None, | 633 | parser.add_argument('--workdir', type=Path, default=None, |
| 605 | help='BitBake workdir containing vcs_cache (for size calculations)') | 634 | help='BitBake workdir containing vcs_cache (for size calculations)') |
| 606 | 635 | ||
| 636 | parser.add_argument('--discovery-cache', type=Path, default=None, | ||
| 637 | help='Discovery cache directory containing module .zip files (for size calculations)') | ||
| 638 | |||
| 607 | # Actions | 639 | # Actions |
| 608 | parser.add_argument('--list', action='store_true', | 640 | parser.add_argument('--list', action='store_true', |
| 609 | help='List all modules with sizes') | 641 | help='List all modules with sizes') |
| @@ -650,12 +682,20 @@ def main(): | |||
| 650 | vcs_info = parse_go_mod_git_inc(git_inc) | 682 | vcs_info = parse_go_mod_git_inc(git_inc) |
| 651 | print(f" Found {len(vcs_info)} VCS entries") | 683 | print(f" Found {len(vcs_info)} VCS entries") |
| 652 | 684 | ||
| 653 | # Get sizes if workdir provided | 685 | # Get sizes from discovery cache and/or workdir |
| 654 | sizes = {} | 686 | sizes = {} |
| 687 | if args.discovery_cache: | ||
| 688 | print(f"Calculating sizes from discovery cache {args.discovery_cache}...") | ||
| 689 | sizes = get_discovery_sizes(modules, args.discovery_cache) | ||
| 690 | print(f" Got sizes for {len(sizes)} modules from discovery cache") | ||
| 691 | |||
| 655 | if args.workdir: | 692 | if args.workdir: |
| 656 | print(f"Calculating sizes from {args.workdir}...") | 693 | print(f"Calculating sizes from {args.workdir}...") |
| 657 | sizes = get_repo_sizes(vcs_info, args.workdir) | 694 | vcs_sizes = get_repo_sizes(vcs_info, args.workdir) |
| 658 | print(f" Got sizes for {len(sizes)} repos") | 695 | print(f" Got sizes for {len(vcs_sizes)} repos from vcs_cache") |
| 696 | # Merge vcs_sizes into sizes (vcs_cache sizes override discovery if both exist) | ||
| 697 | for k, v in vcs_sizes.items(): | ||
| 698 | sizes[k] = v | ||
| 659 | 699 | ||
| 660 | # Handle actions | 700 | # Handle actions |
| 661 | if args.list: | 701 | if args.list: |
diff --git a/scripts/oe-go-mod-fetcher.py b/scripts/oe-go-mod-fetcher.py index 20ceee7c..13399a73 100755 --- a/scripts/oe-go-mod-fetcher.py +++ b/scripts/oe-go-mod-fetcher.py | |||
| @@ -815,7 +815,10 @@ def verify_commit_accessible(vcs_url: str, commit: str, ref_hint: str = "", vers | |||
| 815 | # Strategy depends on whether this is a tagged version or pseudo-version | 815 | # Strategy depends on whether this is a tagged version or pseudo-version |
| 816 | commit_fetched = commit_present # If already present, no need to fetch | 816 | commit_fetched = commit_present # If already present, no need to fetch |
| 817 | 817 | ||
| 818 | if ref_hint and not commit_present: | 818 | # Only use shallow fetch for actual tags - pseudo-versions with branch refs need unshallow |
| 819 | is_tag_ref = ref_hint and ref_hint.startswith('refs/tags/') | ||
| 820 | |||
| 821 | if is_tag_ref and not commit_present: | ||
| 819 | # Tagged version: try shallow fetch of the specific commit (only if not already present) | 822 | # Tagged version: try shallow fetch of the specific commit (only if not already present) |
| 820 | try: | 823 | try: |
| 821 | fetch_cmd = ["git", "fetch", "--depth=1", "origin", commit] | 824 | fetch_cmd = ["git", "fetch", "--depth=1", "origin", commit] |
| @@ -3924,9 +3927,9 @@ def generate_recipe(modules: List[Dict], source_dir: Path, output_dir: Optional[ | |||
| 3924 | else: | 3927 | else: |
| 3925 | commit_sha = repo_info['commits'][commit_hash]['commit_sha'] | 3928 | commit_sha = repo_info['commits'][commit_hash]['commit_sha'] |
| 3926 | 3929 | ||
| 3930 | # Trust the ref_hint from discovery - it will be validated/corrected during | ||
| 3931 | # the verification pass if needed (e.g., force-pushed tags are auto-corrected) | ||
| 3927 | ref_hint = module.get('vcs_ref', '') | 3932 | ref_hint = module.get('vcs_ref', '') |
| 3928 | if ref_hint and not _ref_points_to_commit(vcs_url, ref_hint, commit_hash): | ||
| 3929 | ref_hint = '' | ||
| 3930 | 3933 | ||
| 3931 | entry = repo_info['commits'][commit_hash] | 3934 | entry = repo_info['commits'][commit_hash] |
| 3932 | entry['modules'].append(module) | 3935 | entry['modules'].append(module) |
| @@ -4080,7 +4083,9 @@ def generate_recipe(modules: List[Dict], source_dir: Path, output_dir: Optional[ | |||
| 4080 | # For branches, use the branch name directly | 4083 | # For branches, use the branch name directly |
| 4081 | if ref_hint.startswith('refs/tags/'): | 4084 | if ref_hint.startswith('refs/tags/'): |
| 4082 | # Tags: BitBake can fetch tagged commits with nobranch=1 | 4085 | # Tags: BitBake can fetch tagged commits with nobranch=1 |
| 4083 | branch_param = ';nobranch=1' | 4086 | # Add tag= so shallow clones include this tag (with BB_GIT_SHALLOW=1 in recipe) |
| 4087 | tag_name = ref_hint[10:] # Strip "refs/tags/" | ||
| 4088 | branch_param = f';nobranch=1;tag={tag_name}' | ||
| 4084 | elif ref_hint.startswith('refs/heads/'): | 4089 | elif ref_hint.startswith('refs/heads/'): |
| 4085 | # Branches: use the actual branch name | 4090 | # Branches: use the actual branch name |
| 4086 | branch_name = ref_hint[11:] # Strip "refs/heads/" | 4091 | branch_name = ref_hint[11:] # Strip "refs/heads/" |
| @@ -4161,21 +4166,10 @@ def generate_recipe(modules: List[Dict], source_dir: Path, output_dir: Optional[ | |||
| 4161 | f.write(f'SRC_URI += "{entry}"\n') | 4166 | f.write(f'SRC_URI += "{entry}"\n') |
| 4162 | f.write('\n') | 4167 | f.write('\n') |
| 4163 | 4168 | ||
| 4164 | # Collect all tag references for shallow cloning | 4169 | # Note: BB_GIT_SHALLOW_EXTRA_REFS is NOT used here because those refs must be |
| 4165 | # BB_GIT_SHALLOW_EXTRA_REFS ensures these refs are included in shallow clones | 4170 | # present in ALL repositories, which isn't the case for module dependencies. |
| 4166 | tag_refs = set() | 4171 | # Instead, we use tag= in individual SRC_URI entries when the ref is a tag. |
| 4167 | for module in modules: | 4172 | # The recipe should set BB_GIT_SHALLOW = "1" to enable shallow clones globally. |
| 4168 | vcs_ref = module.get('vcs_ref', '') | ||
| 4169 | if vcs_ref and 'refs/tags/' in vcs_ref: | ||
| 4170 | tag_refs.add(vcs_ref) | ||
| 4171 | |||
| 4172 | if tag_refs: | ||
| 4173 | f.write("# Tag references for shallow cloning\n") | ||
| 4174 | f.write("# Ensures shallow clones include all necessary tags\n") | ||
| 4175 | f.write("BB_GIT_SHALLOW_EXTRA_REFS = \"\\\n") | ||
| 4176 | for tag_ref in sorted(tag_refs): | ||
| 4177 | f.write(f" {tag_ref} \\\n") | ||
| 4178 | f.write('"\n') | ||
| 4179 | 4173 | ||
| 4180 | # Note: SRCREV_* variables are not needed since rev= is embedded directly in SRC_URI | 4174 | # Note: SRCREV_* variables are not needed since rev= is embedded directly in SRC_URI |
| 4181 | 4175 | ||
