342 lines
11 KiB
OCaml
342 lines
11 KiB
OCaml
open Tyxml.Html
|
|
|
|
let pp_ptime = Ptime.pp_human ()
|
|
|
|
let txtf fmt = Fmt.kstrf txt fmt
|
|
let a_titlef fmt = Fmt.kstrf a_title fmt
|
|
|
|
let check_icon result =
|
|
match result with
|
|
| Builder.Exited 0 ->
|
|
span ~a:[
|
|
a_style "color: green; cursor: pointer;";
|
|
a_titlef "%a" Builder.pp_execution_result result;
|
|
]
|
|
[txt "☑"]
|
|
| _ ->
|
|
span ~a:[
|
|
a_style "color: red; cursor: pointer;";
|
|
a_titlef "%a" Builder.pp_execution_result result;
|
|
]
|
|
[txt "☒"]
|
|
|
|
let layout ~title:title_ body_ =
|
|
html
|
|
(head (title (txt title_))
|
|
[style ~a:[a_mime_type "text/css"]
|
|
[
|
|
txt "body {\
|
|
margin: 40px auto;\
|
|
line-height: 1.6;\
|
|
color: #444;\
|
|
padding: 0 10px;\
|
|
}";
|
|
txt "h1,h2,h3{line-height:1.2}";
|
|
txt ".output-ts {\
|
|
white-space: nowrap;\
|
|
cursor: pointer;\
|
|
user-select: none;\
|
|
}";
|
|
txt ".output-ts a {text-decoration: none;}";
|
|
txt ".output-ts a:hover {text-decoration: underline;}";
|
|
txt ".output-code {\
|
|
overflow: visible;\
|
|
white-space: pre;\
|
|
}";
|
|
txt ".toggleable {\
|
|
display: none;\
|
|
}";
|
|
txt ".toggleable-descr {\
|
|
cursor: pointer;\
|
|
text-decoration: underline;\
|
|
user-select: none;\
|
|
}";
|
|
txt ":checked + .toggleable {\
|
|
display: block;\
|
|
}";
|
|
]])
|
|
(body body_)
|
|
|
|
let toggleable ?(hidden=true) id description content =
|
|
let checked = if hidden then [] else [a_checked ()] in
|
|
div [
|
|
label
|
|
~a:[
|
|
a_label_for id;
|
|
a_class ["toggleable-descr"];
|
|
]
|
|
[txt description];
|
|
input
|
|
~a:(checked @ [
|
|
a_input_type `Checkbox;
|
|
a_id id;
|
|
a_style "display: none;";
|
|
]) ();
|
|
div
|
|
~a:[
|
|
a_class ["toggleable"]
|
|
]
|
|
content;
|
|
]
|
|
|
|
let artifact ?(basename=false) job_name build { Builder_db.filepath; localpath = _; sha256; size } =
|
|
[
|
|
a ~a:[a_href (Fmt.strf "/job/%s/build/%a/f/%a"
|
|
job_name
|
|
Uuidm.pp build.Builder_db.Build.Meta.uuid
|
|
Fpath.pp filepath)]
|
|
[if basename
|
|
then txt (Fpath.basename filepath)
|
|
else txtf "%a" Fpath.pp filepath];
|
|
txt " ";
|
|
code [txtf "SHA256:%a" Hex.pp (Hex.of_cstruct sha256)];
|
|
txtf " (%a)" Fmt.byte_size size;
|
|
]
|
|
|
|
|
|
|
|
let builder section_job_map =
|
|
layout ~title:"Reproducible OPAM builds"
|
|
([ h1 [txt "Reproducible OPAM builds"];
|
|
p [ txt "This website offers binary MirageOS unikernels and supplementary OS packages." ];
|
|
p [ txt {|Following is a list of jobs that are built daily. A persistent link to the latest successful build is available as /job/*jobname*/build/latest/. All builds can be reproduced with |} ;
|
|
a ~a:[a_href "https://github.com/roburio/orb/"] [txt "orb"];
|
|
txt ". The builds are scheduled and executed by ";
|
|
a ~a:[a_href "https://github.com/roburio/builder/"] [txt "builder"];
|
|
txt ". The web interface is ";
|
|
a ~a:[a_href "https://git.robur.io/robur/builder-web/"] [txt "builder-web"];
|
|
txt ". Contact team@robur.coop if you have any questions or suggestions.";
|
|
];
|
|
form ~a:[a_action "/hash"; a_method `Get]
|
|
[
|
|
label [
|
|
txt "Search artifact by SHA256";
|
|
br ();
|
|
input ~a:[
|
|
a_input_type `Search;
|
|
a_id "sha256";
|
|
a_name "sha256";
|
|
] ();
|
|
];
|
|
input ~a:[
|
|
a_input_type `Submit;
|
|
a_value "Search";
|
|
] ();
|
|
];
|
|
] @
|
|
Utils.String_map.fold (fun section jobs acc ->
|
|
acc @ [
|
|
h2 [ txt section ];
|
|
ul (List.map (fun (job_name, synopsis, latest_build, latest_artifact) ->
|
|
li ([
|
|
a ~a:[a_href ("job/" ^ job_name ^ "/")]
|
|
[txt job_name];
|
|
txt " ";
|
|
check_icon latest_build.Builder_db.Build.Meta.result;
|
|
br ();
|
|
txt (Option.value ~default:"" synopsis);
|
|
br ();
|
|
a ~a:[a_href (Fmt.strf "job/%s/build/%a/" job_name Uuidm.pp
|
|
latest_build.Builder_db.Build.Meta.uuid)]
|
|
[txtf "%a" (Ptime.pp_human ()) latest_build.Builder_db.Build.Meta.start];
|
|
txt " ";
|
|
] @ match latest_artifact with
|
|
| Some main_binary ->
|
|
artifact ~basename:true job_name latest_build main_binary
|
|
| None ->
|
|
[
|
|
txtf "Build failed";
|
|
])) jobs)
|
|
])
|
|
section_job_map
|
|
[])
|
|
|
|
let job name readme builds =
|
|
layout ~title:(Printf.sprintf "Job %s" name)
|
|
((h1 [txtf "Job %s" name] ::
|
|
(match readme with
|
|
| None -> []
|
|
| Some data ->
|
|
[
|
|
h2 ~a:[a_id "readme"] [txt "README"];
|
|
a ~a:[a_href "#builds"] [txt "Skip to builds"];
|
|
Unsafe.data Omd.(to_html (of_string data))
|
|
])) @
|
|
[
|
|
h2 ~a:[a_id "builds"] [txt "Builds"];
|
|
a ~a:[a_href "#readme"] [txt "Back to readme"];
|
|
p [
|
|
txtf "Currently %d builds."
|
|
(List.length builds)
|
|
];
|
|
ul (List.map (fun (build, main_binary) ->
|
|
li ([
|
|
a ~a:[a_href Fpath.(to_string (v "build" / Uuidm.to_string build.Builder_db.Build.Meta.uuid / ""))]
|
|
[
|
|
txtf "%a" (Ptime.pp_human ()) build.Builder_db.Build.Meta.start;
|
|
];
|
|
txt " ";
|
|
check_icon build.result;
|
|
br ();
|
|
] @ match main_binary with
|
|
| Some main_binary ->
|
|
artifact ~basename:true name build main_binary
|
|
| None ->
|
|
[
|
|
txtf "Build failed";
|
|
]))
|
|
builds);
|
|
|
|
])
|
|
|
|
let job_build
|
|
name
|
|
readme
|
|
{ Builder_db.Build.uuid; start; finish; result; console; script; _ }
|
|
artifacts
|
|
latest_uuid
|
|
previous_build
|
|
=
|
|
let delta = Ptime.diff finish start in
|
|
let successful_build = match result with Builder.Exited 0 -> true | _ -> false in
|
|
layout ~title:(Fmt.strf "Job %s %a" name pp_ptime start)
|
|
((h1 [txtf "Job %s" name] ::
|
|
(match readme with
|
|
| None -> []
|
|
| Some data ->
|
|
[
|
|
h2 ~a:[a_id "readme"] [txt "README"];
|
|
a ~a:[a_href "#build"] [txt "Skip to build"];
|
|
Unsafe.data Omd.(to_html (of_string data))
|
|
])) @
|
|
[
|
|
h2 ~a:[a_id "build"] [txtf "Build %a" pp_ptime start];
|
|
a ~a:[a_href "#readme"] [txt "Back to readme"];
|
|
p [txtf "Build took %a." Ptime.Span.pp delta ];
|
|
p [txtf "Execution result: %a." Builder.pp_execution_result result];
|
|
(match latest_uuid with
|
|
| Some latest_uuid when successful_build && not (Uuidm.equal latest_uuid uuid) ->
|
|
p [
|
|
a ~a:[Fmt.kstr a_href "/compare/%a/%a/opam-switch"
|
|
Uuidm.pp uuid Uuidm.pp latest_uuid]
|
|
[txt "Compare opam-switch with latest build"];
|
|
]
|
|
| _ -> txt "");
|
|
(match previous_build with
|
|
| Some previous_build when successful_build ->
|
|
p [
|
|
a ~a:[Fmt.kstr a_href "/compare/%a/%a/opam-switch"
|
|
Uuidm.pp uuid Uuidm.pp previous_build.Builder_db.Build.Meta.uuid]
|
|
[txt "Compare opam-switch with previous build"];
|
|
]
|
|
| _ -> txt "");
|
|
h3 [txt "Digests of build artifacts"];
|
|
dl (List.concat_map
|
|
(fun { Builder_db.filepath; localpath=_; sha256; size } ->
|
|
let (`Hex sha256_hex) = Hex.of_cstruct sha256 in
|
|
[
|
|
dt [a
|
|
~a:[Fmt.kstr a_href "f/%a" Fpath.pp filepath]
|
|
[code [txtf "%a" Fpath.pp filepath]]];
|
|
dd [
|
|
code [txt "SHA256:"; txt sha256_hex];
|
|
txtf " (%a)" Fmt.byte_size size;
|
|
];
|
|
])
|
|
artifacts);
|
|
h3 [txt "Job script"];
|
|
toggleable "job-script" "Show/hide"
|
|
[ pre [txt script] ];
|
|
h3 [txt "Build log"];
|
|
toggleable ~hidden:false "build-log" "Show/hide build log"
|
|
[
|
|
table
|
|
(List.mapi (fun idx (ts, line) ->
|
|
let ts_id = "L" ^ string_of_int idx in
|
|
tr [
|
|
td ~a:[
|
|
a_class ["output-ts"];
|
|
a_id ts_id;
|
|
]
|
|
[a ~a:[a_href ("#"^ts_id); a_class ["output-ts-anchor"]]
|
|
[code [txtf "%#d ms" (Duration.to_ms (Int64.of_int ts))]]];
|
|
td ~a:[a_class ["output-code"]]
|
|
[code [txt line]];
|
|
])
|
|
(List.rev console));
|
|
];
|
|
])
|
|
|
|
let packages packages =
|
|
OpamPackage.Set.elements packages
|
|
|> List.concat_map (fun p -> [
|
|
txtf "%a" Opamdiff.pp_opampackage p;
|
|
br ();
|
|
])
|
|
|
|
let package_diffs diffs =
|
|
List.concat_map (fun pd -> [
|
|
txtf "%a" Opamdiff.pp_version_diff pd;
|
|
br ();
|
|
])
|
|
diffs
|
|
|
|
let compare_opam job_left job_right
|
|
(build_left : Builder_db.Build.t) (build_right : Builder_db.Build.t)
|
|
(same, opam_diff, version_diff, left, right) =
|
|
layout ~title:(Fmt.strf "Comparing opam switches between builds %a and %a"
|
|
Uuidm.pp build_left.uuid Uuidm.pp build_right.uuid)
|
|
[
|
|
h1 [txt "Comparing opam switches"];
|
|
h2 [
|
|
txt "Builds ";
|
|
a ~a:[a_href
|
|
(Fmt.strf "/job/%s/build/%a/"
|
|
job_left
|
|
Uuidm.pp build_left.uuid)]
|
|
[txtf "%a" pp_ptime build_left.start];
|
|
txt " and ";
|
|
a ~a:[a_href
|
|
(Fmt.strf "/job/%s/build/%a/"
|
|
job_right
|
|
Uuidm.pp build_right.uuid)]
|
|
[txtf "%a" pp_ptime build_right.start];
|
|
];
|
|
ul [
|
|
li [
|
|
a ~a:[a_href "#packages-removed"]
|
|
[txtf "%d packages removed" (OpamPackage.Set.cardinal left)]
|
|
];
|
|
li [
|
|
a ~a:[a_href "#packages-installed"]
|
|
[txtf "%d new packages installed" (OpamPackage.Set.cardinal right)]
|
|
];
|
|
li [
|
|
a ~a:[a_href "#packages-version-diff"]
|
|
[txtf "%d packages with version changes" (List.length version_diff)]
|
|
];
|
|
li [
|
|
a ~a:[a_href "#packages-opam-diff"]
|
|
[txtf "%d packages with changes in their opam file" (OpamPackage.Set.cardinal opam_diff)]
|
|
];
|
|
li [
|
|
a ~a:[a_href "#packages-unchanged"]
|
|
[txtf "%d packages unchanged" (OpamPackage.Set.cardinal same)]
|
|
];
|
|
];
|
|
h3 ~a:[a_id "packages-removed"]
|
|
[txt "Packages removed"];
|
|
code (packages left);
|
|
h3 ~a:[a_id "packages-installed"]
|
|
[txt "New packages installed"];
|
|
code (packages right);
|
|
h3 ~a:[a_id "packages-version-diff"]
|
|
[txt "Packages with version changes"];
|
|
code (package_diffs version_diff);
|
|
h3 ~a:[a_id "packages-opam-diff"]
|
|
[txt "Packages with changes in their opam file"];
|
|
code (packages opam_diff);
|
|
h3 ~a:[a_id "packages-unchanged"]
|
|
[txt "Unchanged packages"];
|
|
code (packages same);
|
|
]
|