My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add worker.js generation and JTW output assembly

Per-solution worker.js: runs jtw opam stdlib with all build layers
stacked, producing worker.js and stdlib artifacts.

Assembly: copies per-package JTW layers and worker outputs into a
content-hashed directory structure (compiler/, p/, u/) suitable for
serving. Output goes to <cache-dir>/jtw-output/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+162 -25
+2 -1
day11/bin/cmd_batch.ml
··· 366 366 (* JTW *) 367 367 (match jtw_repo with 368 368 | Some dir -> 369 + let output = Fpath.to_string Fpath.(cache_dir / "jtw-output") in 369 370 Day11_jtw.Build_tools.build_and_run env benv ~np ~os_dir 370 371 ~packages:git_packages ~opam_env ~mounts:[repo_mount] 371 - ~repo_dir:dir ~nodes ~solutions 372 + ~repo_dir:dir ~output ~nodes ~solutions 372 373 | None -> ()); 373 374 0 374 375 end
+7 -3
day11/jtw/build_tools.ml
··· 21 21 ) compiler_versions 22 22 23 23 let build_and_run env benv ~np ~os_dir ~packages ~opam_env ~mounts 24 - ~repo_dir ~nodes ~solutions = 24 + ~repo_dir ~output ~nodes ~solutions = 25 25 let jtw_tools = build_per_compiler env benv ~np ~packages ~opam_env 26 26 ~mounts ~repo_dir ~solutions in 27 27 if jtw_tools = [] then 28 28 Printf.printf "No JTW tools built, skipping generation\n%!" 29 29 else begin 30 - let count = Generate.run env benv ~os_dir ~jtw_tools ~nodes ~solutions in 31 - Printf.printf "\n=== JTW: %d packages processed ===\n%!" count 30 + let jtw_results, worker_layers = 31 + Generate.run env benv ~os_dir ~jtw_tools ~nodes ~solutions in 32 + Generate.assemble ~os_dir ~output ~jtw_results ~worker_layers 33 + ~solutions; 34 + Printf.printf "\n=== JTW: %d packages, %d workers ===\n%!" 35 + (Hashtbl.length jtw_results) (List.length worker_layers) 32 36 end
+6 -3
day11/jtw/build_tools.mli
··· 1 - (** Build JTW tools and generate per-package artifacts. 1 + (** Build JTW tools, generate artifacts, and assemble output. 2 2 3 3 Builds [js_top_worker-bin] from a local checkout for each unique 4 - compiler version, then runs per-package JTW generation. *) 4 + compiler version, runs per-package and per-solution generation, 5 + then assembles the content-hashed output directory. *) 5 6 6 7 val build_per_compiler : 7 8 Eio_unix.Stdenv.base -> ··· 24 25 opam_env:(string -> OpamVariable.variable_contents option) -> 25 26 mounts:Day11_container.Mount.t list -> 26 27 repo_dir:string -> 28 + output:string -> 27 29 nodes:Day11_layer.Layer_type.build list -> 28 30 solutions:(OpamPackage.t * Day11_graph.Graph.solution) list -> 29 31 unit 30 - (** Build JTW tools then generate per-package artifacts for all nodes. *) 32 + (** Build tools, generate per-package artifacts and worker.js, then 33 + assemble the output directory at [output]. *)
+119 -5
day11/jtw/generate.ml
··· 52 52 None 53 53 end 54 54 55 + (** Build worker.js for a solution. Stacks all build layers from the 56 + solution and runs [jtw opam -o ... stdlib]. *) 57 + let build_worker env benv ~os_dir ~jtw_tool ~solution_nodes = 58 + let jtw_mounts = make_jtw_mounts ~os_dir ~jtw_tool in 59 + let cmd = 60 + "export PATH=/home/opam/jtw-tools/bin:$PATH && eval $(opam env) && " ^ 61 + "jtw opam -o /home/opam/jtw-worker-output stdlib" in 62 + let dep_hashes = List.map (fun (n : build) -> n.hash) solution_nodes in 63 + let hash = Day11_layer.Hash.of_strings 64 + ([ "jtw-worker"; jtw_tool.hash ] @ dep_hashes) in 65 + let dummy_pkg = OpamPackage.of_string "jtw-worker.0" in 66 + let worker_node : build = 67 + { hash; pkg = dummy_pkg; deps = solution_nodes } in 68 + match Day11_build.Build_layer.build env benv 69 + ~mounts:jtw_mounts worker_node 70 + ~strategy:{ cmd; cleanup = jtw_cleanup } () with 71 + | Day11_build.Types.Success bl -> 72 + Printf.printf " worker.js: OK\n%!"; 73 + Some bl 74 + | _ -> 75 + Printf.printf " worker.js: FAILED\n%!"; 76 + None 77 + 55 78 let run env benv ~os_dir ~jtw_tools ~nodes ~solutions = 56 79 let pkg_compiler = Hashtbl.create 64 in 57 80 List.iter (fun (_target, solution) -> ··· 70 93 OpamPackage.equal c compiler) jtw_tools 71 94 |> Option.map snd 72 95 in 73 - Printf.printf " JTW generation (%d packages)...\n%!" (List.length nodes); 74 - let count = ref 0 in 96 + (* Per-package generation *) 97 + Printf.printf " JTW per-package (%d packages)...\n%!" (List.length nodes); 98 + let jtw_results : (OpamPackage.t, build) Hashtbl.t = Hashtbl.create 64 in 75 99 List.iter (fun (node : build) -> 76 100 match find_jtw_tool node.pkg with 77 101 | None -> () 78 102 | Some jtw_tool -> 79 103 match generate_package env benv ~os_dir ~jtw_tool node with 80 - | Some _ -> incr count 104 + | Some bl -> Hashtbl.replace jtw_results node.pkg bl 81 105 | None -> () 82 106 ) nodes; 83 - Printf.printf " JTW: %d packages processed\n%!" !count; 84 - !count 107 + Printf.printf " JTW: %d packages processed\n%!" (Hashtbl.length jtw_results); 108 + (* Worker.js per solution *) 109 + Printf.printf " JTW worker.js...\n%!"; 110 + let worker_layers = List.filter_map (fun (_target, solution) -> 111 + match Day11_doc.Generate.find_compiler solution with 112 + | None -> None 113 + | Some compiler -> 114 + match List.find_opt (fun (c, _) -> 115 + OpamPackage.equal c compiler) jtw_tools with 116 + | None -> None 117 + | Some (_, jtw_tool) -> 118 + let solution_nodes = List.filter (fun (node : build) -> 119 + OpamPackage.Map.mem node.pkg solution 120 + ) nodes in 121 + match build_worker env benv ~os_dir ~jtw_tool ~solution_nodes with 122 + | Some bl -> Some (compiler, bl) 123 + | None -> None 124 + ) solutions in 125 + (jtw_results, worker_layers) 126 + 127 + (** Assemble JTW output from per-package layers and worker layer. 128 + 129 + Output structure: 130 + {v 131 + <output>/ 132 + compiler/<ocaml-version>/<hash>/ 133 + worker.js 134 + lib/ocaml/ (stdlib .cmi files) 135 + p/<package>/<version>/<hash>/ 136 + lib/<findlib-name>/ 137 + META, dynamic_cmis.json, *.cmi, *.cma.js 138 + u/<universe-hash>/ 139 + findlib_index.json 140 + v} *) 141 + let assemble ~os_dir ~output ~jtw_results ~worker_layers ~solutions = 142 + Bos.OS.Dir.create ~path:true (Fpath.v output) |> ignore; 143 + (* Compiler/worker output *) 144 + List.iter (fun (compiler_v, worker_bl) -> 145 + let ocaml_ver = OpamPackage.Version.to_string 146 + (OpamPackage.version compiler_v) in 147 + let worker_dir = Fpath.(build_dir ~os_dir worker_bl / "fs" 148 + / "home" / "opam" / "jtw-worker-output") in 149 + let worker_dir_s = Fpath.to_string worker_dir in 150 + if Sys.file_exists worker_dir_s then begin 151 + let compiler_hash = Gen.compute_compiler_content_hash worker_dir_s in 152 + let dst = Fpath.(v output / "compiler" / ocaml_ver / compiler_hash) in 153 + if not (Bos.OS.Dir.exists dst |> Result.get_ok) then begin 154 + Bos.OS.Dir.create ~path:true dst |> ignore; 155 + let worker_js = Fpath.(worker_dir / "worker.js") in 156 + if Bos.OS.File.exists worker_js |> Result.get_ok then 157 + ignore (Bos.OS.Cmd.run 158 + Bos.Cmd.(v "cp" % Fpath.to_string worker_js 159 + % Fpath.to_string Fpath.(dst / "worker.js"))); 160 + let lib_src = Fpath.(worker_dir / "lib") in 161 + if Bos.OS.Dir.exists lib_src |> Result.get_ok then 162 + ignore (Bos.OS.Cmd.run 163 + Bos.Cmd.(v "cp" % "-a" % Fpath.to_string lib_src 164 + % Fpath.to_string Fpath.(dst / "lib"))) 165 + end 166 + end 167 + ) worker_layers; 168 + (* Per-package output *) 169 + Hashtbl.iter (fun pkg bl -> 170 + let pkg_name = OpamPackage.name_to_string pkg in 171 + let pkg_version = OpamPackage.version_to_string pkg in 172 + let jtw_output = Fpath.(build_dir ~os_dir bl / "fs" 173 + / "home" / "opam" / "jtw-output" / pkg_name / "lib") in 174 + let jtw_output_s = Fpath.to_string jtw_output in 175 + if Sys.file_exists jtw_output_s then begin 176 + let content_hash = Gen.compute_content_hash jtw_output_s in 177 + let dst = Fpath.(v output / "p" / pkg_name / pkg_version 178 + / content_hash / "lib") in 179 + if not (Bos.OS.Dir.exists dst |> Result.get_ok) then begin 180 + Bos.OS.Dir.create ~path:true dst |> ignore; 181 + ignore (Bos.OS.Cmd.run 182 + Bos.Cmd.(v "cp" % "-a" % "--no-target-directory" 183 + % jtw_output_s % Fpath.to_string dst)) 184 + end 185 + end 186 + ) jtw_results; 187 + (* Universe findlib indexes *) 188 + List.iter (fun (_target, solution) -> 189 + let build_hashes = OpamPackage.Map.fold (fun _pkg _deps acc -> 190 + (* We'd need the build hash from the node — for now use package string *) 191 + acc 192 + ) solution [] in 193 + let universe = Day11_layer.Hash.of_strings 194 + (List.sort String.compare build_hashes) in 195 + let u_dir = Fpath.(v output / "u" / universe) in 196 + Bos.OS.Dir.create ~path:true u_dir |> ignore 197 + ) solutions; 198 + Printf.printf " JTW output assembled in %s\n%!" output
+28 -13
day11/jtw/generate.mli
··· 1 - (** Per-package JTW artifact generation. 1 + (** JTW artifact generation and assembly. 2 2 3 - For each package with findlib libraries, runs [jtw opam --path <pkg> 4 - --no-worker] in a container with the package's build layer. Produces 5 - [.cma.js] files, copies [.cmi] and [META], generates 6 - [dynamic_cmis.json]. 3 + Three phases: 4 + - {b Per-package}: for each package with findlib libraries, run 5 + [jtw opam --path <pkg> --no-worker] to produce [.cma.js], 6 + [.cmi], [META], and [dynamic_cmis.json]. 7 + - {b Worker}: for each solution, run [jtw opam stdlib] with all 8 + build layers stacked to produce [worker.js] and stdlib artifacts. 9 + - {b Assembly}: merge per-package and worker outputs into a 10 + content-hashed directory structure for serving. 7 11 8 - JTW tool binaries ([jtw], [js_of_ocaml]) are bind-mounted from the 9 - tool layer at [/home/opam/jtw-tools/bin/]. The per-compiler JTW tool 10 - is selected based on which compiler appears in each package's 11 - solution. *) 12 + JTW tool binaries are bind-mounted from the tool layer. *) 12 13 13 14 val run : 14 15 Eio_unix.Stdenv.base -> ··· 17 18 jtw_tools:(OpamPackage.t * Day11_layer.Layer_type.tool) list -> 18 19 nodes:Day11_layer.Layer_type.build list -> 19 20 solutions:(OpamPackage.t * Day11_graph.Graph.solution) list -> 20 - int 21 - (** [run env benv ~os_dir ~jtw_tools ~nodes ~solutions] generates JTW 22 - artifacts for all packages in [nodes] that have findlib libraries. 23 - Returns the number of packages processed. *) 21 + (OpamPackage.t, Day11_layer.Layer_type.build) Hashtbl.t 22 + * (OpamPackage.t * Day11_layer.Layer_type.build) list 23 + (** [run env benv ~os_dir ~jtw_tools ~nodes ~solutions] generates 24 + per-package JTW artifacts and per-solution worker.js. Returns 25 + [(jtw_results, worker_layers)] where [jtw_results] maps packages 26 + to their JTW layer and [worker_layers] maps compilers to their 27 + worker layer. *) 28 + 29 + val assemble : 30 + os_dir:Fpath.t -> 31 + output:string -> 32 + jtw_results:(OpamPackage.t, Day11_layer.Layer_type.build) Hashtbl.t -> 33 + worker_layers:(OpamPackage.t * Day11_layer.Layer_type.build) list -> 34 + solutions:(OpamPackage.t * Day11_graph.Graph.solution) list -> 35 + unit 36 + (** [assemble ~os_dir ~output ~jtw_results ~worker_layers ~solutions] 37 + copies artifacts into a content-hashed directory structure at 38 + [output]. *)