tangled
alpha
login
or
join now
anil.recoil.org
/
unpac
0
fork
atom
A monorepo management tool for the agentic ages
0
fork
atom
overview
issues
pulls
pipelines
group
anil.recoil.org
3 months ago
96ef1df3
18956d70
+90
-43
1 changed file
expand all
collapse all
unified
split
bin
main.ml
+90
-43
bin/main.ml
···
55
55
String.starts_with ~prefix:"../" s || (* Relative path *)
56
56
String.contains s ':' (* URL with scheme *)
57
57
58
58
+
(* Normalize a dev-repo URL for grouping comparison *)
59
59
+
let normalize_dev_repo url =
60
60
+
let s = url in
61
61
+
(* Strip git+ prefix *)
62
62
+
let s = if String.starts_with ~prefix:"git+" s then
63
63
+
String.sub s 4 (String.length s - 4) else s in
64
64
+
(* Strip trailing .git *)
65
65
+
let s = if String.ends_with ~suffix:".git" s then
66
66
+
String.sub s 0 (String.length s - 4) else s in
67
67
+
(* Strip trailing slash *)
68
68
+
let s = if String.ends_with ~suffix:"/" s then
69
69
+
String.sub s 0 (String.length s - 1) else s in
70
70
+
(* Normalize github URLs: git@github.com:x/y -> https://github.com/x/y *)
71
71
+
let s = if String.starts_with ~prefix:"git@github.com:" s then
72
72
+
"https://github.com/" ^ String.sub s 15 (String.length s - 15) else s in
73
73
+
String.lowercase_ascii s
74
74
+
75
75
+
(* Group solved packages by their dev-repo *)
76
76
+
type package_group = {
77
77
+
canonical_name : string; (* First package name, used as vendor name *)
78
78
+
dev_repo : string; (* Original dev-repo URL *)
79
79
+
packages : string list; (* All package names in this group *)
80
80
+
}
81
81
+
82
82
+
let group_packages_by_dev_repo ~config (pkgs : OpamPackage.t list) : package_group list =
83
83
+
let repos = config.Unpac.Config.opam.repositories in
84
84
+
(* Build a map from normalized dev-repo to package info *)
85
85
+
let groups = Hashtbl.create 16 in
86
86
+
List.iter (fun pkg ->
87
87
+
let name = OpamPackage.Name.to_string (OpamPackage.name pkg) in
88
88
+
let version = OpamPackage.Version.to_string (OpamPackage.version pkg) in
89
89
+
match Unpac_opam.Repo.find_package ~repos ~name ~version () with
90
90
+
| None -> () (* Skip packages not found *)
91
91
+
| Some result ->
92
92
+
match result.metadata.dev_repo with
93
93
+
| None -> () (* Skip packages without dev-repo *)
94
94
+
| Some dev_repo ->
95
95
+
let key = normalize_dev_repo dev_repo in
96
96
+
match Hashtbl.find_opt groups key with
97
97
+
| None ->
98
98
+
Hashtbl.add groups key (dev_repo, [name])
99
99
+
| Some (orig_url, names) ->
100
100
+
Hashtbl.replace groups key (orig_url, name :: names)
101
101
+
) pkgs;
102
102
+
(* Convert to list of groups *)
103
103
+
Hashtbl.fold (fun _key (dev_repo, names) acc ->
104
104
+
let names = List.rev names in (* Preserve order *)
105
105
+
let canonical_name = List.hd names in
106
106
+
{ canonical_name; dev_repo; packages = names } :: acc
107
107
+
) groups []
108
108
+
|> List.sort (fun a b -> String.compare a.canonical_name b.canonical_name)
109
109
+
58
110
(* Helper to resolve vendor cache *)
59
111
let resolve_cache ~proc_mgr ~fs ~config ~cli_cache =
60
112
match Unpac.Config.resolve_vendor_cache ?cli_override:cli_cache config with
···
239
291
let info = Cmd.info "config" ~doc in
240
292
Cmd.group info [opam_config_compiler_cmd]
241
293
242
242
-
(* Helper to add a single package by name *)
243
243
-
let add_single_package ~proc_mgr ~root ?cache ~config ~name ~version_opt ~branch_opt () =
244
244
-
let repos = config.Unpac.Config.opam.repositories in
245
245
-
match Unpac_opam.Repo.find_package ~repos ~name ?version:version_opt () with
246
246
-
| None ->
247
247
-
Format.eprintf "Package '%s' not found in configured repositories@." name;
248
248
-
`Failed
249
249
-
| Some result ->
250
250
-
match result.metadata.dev_repo with
251
251
-
| None ->
252
252
-
Format.eprintf "Package '%s' has no dev-repo field, skipping@." name;
253
253
-
`Skipped
254
254
-
| Some dev_repo ->
255
255
-
(* Strip git+ prefix if present (opam dev-repo format) *)
256
256
-
let url = if String.starts_with ~prefix:"git+" dev_repo then
257
257
-
String.sub dev_repo 4 (String.length dev_repo - 4)
258
258
-
else dev_repo in
259
259
-
let info : Unpac.Backend.package_info = {
260
260
-
name;
261
261
-
url;
262
262
-
branch = branch_opt;
263
263
-
} in
264
264
-
match Unpac_opam.Opam.add_package ~proc_mgr ~root ?cache info with
265
265
-
| Unpac.Backend.Added { name = pkg_name; sha } ->
266
266
-
Format.printf "Added %s (%s)@." pkg_name (String.sub sha 0 7);
267
267
-
`Added
268
268
-
| Unpac.Backend.Already_exists pkg_name ->
269
269
-
Format.printf "Package %s already vendored@." pkg_name;
270
270
-
`Exists
271
271
-
| Unpac.Backend.Failed { name = pkg_name; error } ->
272
272
-
Format.eprintf "Error adding %s: %s@." pkg_name error;
273
273
-
`Failed
274
274
-
275
294
(* Opam add command - enhanced to support package names and dependency solving *)
276
295
let opam_add_cmd =
277
296
let doc = "Vendor an opam package (by name or git URL)." in
···
337
356
(OpamPackage.Name.to_string (OpamPackage.name p))
338
357
(OpamPackage.Version.to_string (OpamPackage.version p))
339
358
) pkgs;
340
340
-
Format.printf "@.Vendoring packages...@.";
359
359
+
360
360
+
(* Group packages by dev-repo to avoid duplicating sources *)
361
361
+
let groups = group_packages_by_dev_repo ~config pkgs in
362
362
+
Format.printf "@.Grouped into %d unique repositories:@." (List.length groups);
363
363
+
List.iter (fun (g : package_group) ->
364
364
+
if List.length g.packages > 1 then
365
365
+
Format.printf " %s (%d packages: %s)@."
366
366
+
g.canonical_name
367
367
+
(List.length g.packages)
368
368
+
(String.concat ", " g.packages)
369
369
+
else
370
370
+
Format.printf " %s@." g.canonical_name
371
371
+
) groups;
372
372
+
373
373
+
Format.printf "@.Vendoring repositories...@.";
341
374
let added = ref 0 in
342
375
let failed = ref 0 in
343
343
-
List.iter (fun p ->
344
344
-
let name = OpamPackage.Name.to_string (OpamPackage.name p) in
345
345
-
match add_single_package ~proc_mgr ~root ?cache ~config ~name ~version_opt:None ~branch_opt () with
346
346
-
| `Added -> incr added
347
347
-
| `Exists -> ()
348
348
-
| `Skipped -> ()
349
349
-
| `Failed -> incr failed
350
350
-
) pkgs;
351
351
-
Format.printf "@.Done: %d added, %d failed@." !added !failed;
376
376
+
List.iter (fun (g : package_group) ->
377
377
+
(* Use canonical name as vendor name, dev-repo as URL *)
378
378
+
let url = if String.starts_with ~prefix:"git+" g.dev_repo then
379
379
+
String.sub g.dev_repo 4 (String.length g.dev_repo - 4)
380
380
+
else g.dev_repo in
381
381
+
let info : Unpac.Backend.package_info = {
382
382
+
name = g.canonical_name;
383
383
+
url;
384
384
+
branch = None;
385
385
+
} in
386
386
+
match Unpac_opam.Opam.add_package ~proc_mgr ~root ?cache info with
387
387
+
| Unpac.Backend.Added { name = pkg_name; sha } ->
388
388
+
Format.printf "Added %s (%s)@." pkg_name (String.sub sha 0 7);
389
389
+
if List.length g.packages > 1 then
390
390
+
Format.printf " Contains: %s@." (String.concat ", " g.packages);
391
391
+
incr added
392
392
+
| Unpac.Backend.Already_exists pkg_name ->
393
393
+
Format.printf "Package %s already vendored@." pkg_name
394
394
+
| Unpac.Backend.Failed { name = pkg_name; error } ->
395
395
+
Format.eprintf "Error adding %s: %s@." pkg_name error;
396
396
+
incr failed
397
397
+
) groups;
398
398
+
Format.printf "@.Done: %d repositories added, %d failed@." !added !failed;
352
399
if !failed > 0 then exit 1
353
400
end else begin
354
401
(* Single package mode *)