| 715 | /* ── Merge: per-worker entries → hash table ────────────────────── */ |
| 716 | |
| 717 | CBMHashTable *cbm_pkgmap_build(cbm_pkg_entries_t *worker_entries, int worker_count, |
| 718 | const char *project_name) { |
| 719 | /* Count total entries */ |
| 720 | int total = 0; |
| 721 | for (int w = 0; w < worker_count; w++) { |
| 722 | total += worker_entries[w].count; |
| 723 | } |
| 724 | if (total == 0) { |
| 725 | return NULL; |
| 726 | } |
| 727 | |
| 728 | CBMHashTable *map = cbm_ht_create(PKGMAP_HT_INIT); |
| 729 | int merged = 0; |
| 730 | |
| 731 | for (int w = 0; w < worker_count; w++) { |
| 732 | cbm_pkg_entries_t *we = &worker_entries[w]; |
| 733 | for (int i = 0; i < we->count; i++) { |
| 734 | /* Convert entry_rel to QN: project.dir.parts */ |
| 735 | char *qn = cbm_pipeline_fqn_module(project_name, we->items[i].entry_rel); |
| 736 | if (!qn) { |
| 737 | continue; |
| 738 | } |
| 739 | |
| 740 | /* Check for duplicate — first wins */ |
| 741 | if (cbm_ht_has(map, we->items[i].pkg_name)) { |
| 742 | free(qn); |
| 743 | continue; |
| 744 | } |
| 745 | |
| 746 | /* Transfer ownership: key = strdup'd pkg_name, value = qn */ |
| 747 | char *key = strdup(we->items[i].pkg_name); |
| 748 | cbm_ht_set(map, key, qn); |
| 749 | merged++; |
| 750 | } |
| 751 | } |
| 752 | |
| 753 | if (merged == 0) { |
| 754 | cbm_ht_free(map); |
| 755 | return NULL; |
| 756 | } |
| 757 | cbm_log_info("pkgmap.build", "entries", pkgmap_itoa(merged)); |
| 758 | return map; |
| 759 | } |
| 760 | |
| 761 | /* Returns true if basename is a package manifest we know how to parse. |
| 762 | * Used by the filesystem walker; cbm_pkgmap_try_parse is the source of |
no test coverage detected