| 988 | /* ── Merge: per-worker entries → hash table ────────────────────── */ |
| 989 | |
| 990 | CBMHashTable *cbm_pkgmap_build(cbm_pkg_entries_t *worker_entries, int worker_count, |
| 991 | const char *project_name) { |
| 992 | /* Count total entries */ |
| 993 | int total = 0; |
| 994 | for (int w = 0; w < worker_count; w++) { |
| 995 | total += worker_entries[w].count; |
| 996 | } |
| 997 | if (total == 0) { |
| 998 | return NULL; |
| 999 | } |
| 1000 | |
| 1001 | CBMHashTable *map = cbm_ht_create(PKGMAP_HT_INIT); |
| 1002 | int merged = 0; |
| 1003 | |
| 1004 | for (int w = 0; w < worker_count; w++) { |
| 1005 | cbm_pkg_entries_t *we = &worker_entries[w]; |
| 1006 | for (int i = 0; i < we->count; i++) { |
| 1007 | /* Convert entry_rel to QN: project.dir.parts */ |
| 1008 | char *qn = cbm_pipeline_fqn_module(project_name, we->items[i].entry_rel); |
| 1009 | if (!qn) { |
| 1010 | continue; |
| 1011 | } |
| 1012 | |
| 1013 | /* Check for duplicate — first wins */ |
| 1014 | if (cbm_ht_has(map, we->items[i].pkg_name)) { |
| 1015 | free(qn); |
| 1016 | continue; |
| 1017 | } |
| 1018 | |
| 1019 | /* Transfer ownership: key = strdup'd pkg_name, value = qn */ |
| 1020 | char *key = strdup(we->items[i].pkg_name); |
| 1021 | cbm_ht_set(map, key, qn); |
| 1022 | merged++; |
| 1023 | } |
| 1024 | } |
| 1025 | |
| 1026 | if (merged == 0) { |
| 1027 | cbm_ht_free(map); |
| 1028 | return NULL; |
| 1029 | } |
| 1030 | cbm_log_info("pkgmap.build", "entries", pkgmap_itoa(merged)); |
| 1031 | return map; |
| 1032 | } |
| 1033 | |
| 1034 | /* Returns true if basename is a package manifest we know how to parse. |
| 1035 | * Used by the filesystem walker; cbm_pkgmap_try_parse is the source of |
no test coverage detected