summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-01-04 11:40:15 -0500
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-01-04 11:40:15 -0500
commitdbf720ccb0519a4dbf143dbaed1633527b8d7b60 (patch)
tree16c72e1b58d63d1a116a3a91b02e13646ba8bee0
parentd7434129524162f356c3cd154f46a395c9076df7 (diff)
downloadmeta-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.bbclass81
-rwxr-xr-xscripts/oe-go-mod-fetcher-hybrid.py46
-rwxr-xr-xscripts/oe-go-mod-fetcher.py32
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
118GO_MOD_DISCOVERY_RECIPEDIR ?= "${FILE_DIRNAME}" 118GO_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
123GO_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)
121TAGS ?= "" 126TAGS ?= ""
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
411do_generate_modules[nostamp] = "1" 424do_generate_modules[nostamp] = "1"
412do_generate_modules[vardeps] += "GO_MOD_DISCOVERY_MODULES_JSON GO_MOD_DISCOVERY_GIT_REPO \ 425do_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"
427do_generate_modules[postfuncs] = "do_show_hybrid_recommendation"
428
429# Show hybrid conversion recommendations after VCS generation
430python 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
443do_discover_and_generate[depends] = "${PN}:do_prepare_recipe_sysroot" 520do_discover_and_generate[depends] = "${PN}:do_prepare_recipe_sysroot"
444do_discover_and_generate[network] = "1" 521do_discover_and_generate[network] = "1"
445do_discover_and_generate[nostamp] = "1" 522do_discover_and_generate[nostamp] = "1"
446do_discover_and_generate[postfuncs] = "do_discover_modules do_extract_modules do_generate_modules" 523do_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
244def 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
244def format_size(size_bytes: int) -> str: 273def 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