···1717 | 0, _ -> 0
1818 | r, 0 -> r
1919 | _, n ->
2020- OpamConsole.note "retry %i: %s" (tries - n + 1) (String.concat " " cmd);
2121- Unix.sleepf (Random.float 2.0); loop (n - 1)
2020+ OpamConsole.note "retry %i: %s" (tries - n + 1) (String.concat " " cmd);
2121+ Unix.sleepf (Random.float 2.0);
2222+ loop (n - 1)
2223 in
2323- loop tries
2424+ loop tries
24252526let run cmd =
2627 let inp = Unix.open_process_in cmd in
···3132let nproc () = run "nproc" |> String.trim |> int_of_string
3233let mkdir dir = if not (Sys.file_exists dir) then Sys.mkdir dir 0o755
33343434-let rec rm ?(recursive=false) path =
3535+let rec rm ?(recursive = false) path =
3536 try
3637 let stat = Unix.lstat path in
3738 match stat.st_kind with
3838- | S_REG | S_LNK | S_CHR | S_BLK | S_FIFO | S_SOCK ->
3939- (try
4040- Unix.unlink path
4141- with
4242- Unix.Unix_error (Unix.EACCES, _, _) ->
4343- Unix.chmod path (stat.st_perm lor 0o222);
4444- Unix.unlink path)
3939+ | S_REG
4040+ | S_LNK
4141+ | S_CHR
4242+ | S_BLK
4343+ | S_FIFO
4444+ | S_SOCK -> (
4545+ try Unix.unlink path with
4646+ | Unix.Unix_error (Unix.EACCES, _, _) ->
4747+ Unix.chmod path (stat.st_perm lor 0o222);
4848+ Unix.unlink path)
4549 | S_DIR ->
4646- if recursive then
4747- Sys.readdir path
4848- |> Array.iter (fun f ->
4949- rm ~recursive (Filename.concat path f));
5050- Unix.rmdir path
5050+ if recursive then Sys.readdir path |> Array.iter (fun f -> rm ~recursive (Filename.concat path f));
5151+ Unix.rmdir path
5152 with
5252- | Unix.Unix_error (Unix.ENOENT, _, _) -> ()
5353+ | Unix.Unix_error (Unix.ENOENT, _, _) -> ()
53545455module IntSet = Set.Make (Int)
5556···9394exception Copy_error of string
94959596let cp ?(buffer_size = 65536) ?(preserve_permissions = true) ?(preserve_times = true) src dst =
9696- let safe_close fd = try Unix.close fd with _ -> () in
9797+ let safe_close fd =
9898+ try Unix.close fd with
9999+ | _ -> ()
100100+ in
97101 let src_stats =
9898- try Unix.stat src
9999- with Unix.Unix_error (err, _, _) ->
100100- raise (Copy_error (Printf.sprintf "Cannot stat source file '%s': %s" src (Unix.error_message err)))
102102+ try Unix.stat src with
103103+ | Unix.Unix_error (err, _, _) -> raise (Copy_error (Printf.sprintf "Cannot stat source file '%s': %s" src (Unix.error_message err)))
101104 in
102102- if src_stats.st_kind <> S_REG then
103103- raise (Copy_error (Printf.sprintf "Source '%s' is not a regular file" src));
105105+ if src_stats.st_kind <> S_REG then raise (Copy_error (Printf.sprintf "Source '%s' is not a regular file" src));
104106 let src_fd =
105105- try Unix.openfile src [O_RDONLY] 0
106106- with Unix.Unix_error (err, _, _) ->
107107- raise (Copy_error (Printf.sprintf "Cannot open source file '%s': %s" src (Unix.error_message err)))
107107+ try Unix.openfile src [ O_RDONLY ] 0 with
108108+ | Unix.Unix_error (err, _, _) -> raise (Copy_error (Printf.sprintf "Cannot open source file '%s': %s" src (Unix.error_message err)))
108109 in
109110 let dst_fd =
110110- try Unix.openfile dst [O_WRONLY; O_CREAT; O_TRUNC] src_stats.st_perm
111111- with Unix.Unix_error (err, _, _) ->
112112- safe_close src_fd;
113113- raise (Copy_error (Printf.sprintf "Cannot open destination file '%s': %s" dst (Unix.error_message err)))
111111+ try Unix.openfile dst [ O_WRONLY; O_CREAT; O_TRUNC ] src_stats.st_perm with
112112+ | Unix.Unix_error (err, _, _) ->
113113+ safe_close src_fd;
114114+ raise (Copy_error (Printf.sprintf "Cannot open destination file '%s': %s" dst (Unix.error_message err)))
114115 in
115116 let buffer = Bytes.create buffer_size in
116117 let rec copy_loop () =
117118 try
118118- match (Unix.read src_fd buffer 0 buffer_size) with
119119+ match Unix.read src_fd buffer 0 buffer_size with
119120 | 0 -> ()
120121 | bytes_read ->
121121- let rec write_all pos remaining =
122122- if remaining > 0 then
123123- let bytes_written = Unix.write dst_fd buffer pos remaining in
124124- write_all (pos + bytes_written) (remaining - bytes_written)
125125- in
126126- write_all 0 bytes_read;
127127- copy_loop ()
128128- with Unix.Unix_error (err, _, _) ->
129129- safe_close src_fd;
130130- safe_close dst_fd;
131131- raise (Copy_error (Printf.sprintf "Error during copy: %s" (Unix.error_message err)))
122122+ let rec write_all pos remaining =
123123+ if remaining > 0 then
124124+ let bytes_written = Unix.write dst_fd buffer pos remaining in
125125+ write_all (pos + bytes_written) (remaining - bytes_written)
126126+ in
127127+ write_all 0 bytes_read;
128128+ copy_loop ()
129129+ with
130130+ | Unix.Unix_error (err, _, _) ->
131131+ safe_close src_fd;
132132+ safe_close dst_fd;
133133+ raise (Copy_error (Printf.sprintf "Error during copy: %s" (Unix.error_message err)))
132134 in
133135 copy_loop ();
134136 safe_close src_fd;
135137 safe_close dst_fd;
136136- if preserve_permissions then begin
137137- try Unix.chmod dst src_stats.st_perm
138138- with Unix.Unix_error (err, _, _) ->
139139- Printf.eprintf "Warning: Could not preserve permissions: %s\n" (Unix.error_message err)
140140- end;
141141- if preserve_times then begin
142142- try Unix.utimes dst src_stats.st_atime src_stats.st_mtime
143143- with Unix.Unix_error (err, _, _) ->
144144- Printf.eprintf "Warning: Could not preserve timestamps: %s\n" (Unix.error_message err)
145145- end
138138+ (if preserve_permissions then
139139+ try Unix.chmod dst src_stats.st_perm with
140140+ | Unix.Unix_error (err, _, _) -> Printf.eprintf "Warning: Could not preserve permissions: %s\n" (Unix.error_message err));
141141+ if preserve_times then
142142+ try Unix.utimes dst src_stats.st_atime src_stats.st_mtime with
143143+ | Unix.Unix_error (err, _, _) -> Printf.eprintf "Warning: Could not preserve timestamps: %s\n" (Unix.error_message err)
146144147145let hardlink_tree ~source ~target =
148146 let rec process_directory current_source current_target =
149147 let entries = Sys.readdir current_source in
150150- Array.iter (fun entry ->
151151- let source = Filename.concat current_source entry in
152152- let target = Filename.concat current_target entry in
153153- try
154154- let stat = Unix.lstat source in
155155- match stat.st_kind with
156156- | Unix.S_LNK ->
157157- if not (Sys.file_exists target) then
158158- Unix.symlink (Unix.readlink source) target
159159- | Unix.S_REG ->
160160- if not (Sys.file_exists target) then
161161- Unix.link source target
162162- | Unix.S_DIR ->
163163- mkdir target;
164164- process_directory source target
165165- | S_CHR | S_BLK | S_FIFO | S_SOCK ->
166166- ()
167167- with
168168- | Unix.Unix_error (Unix.EMLINK, _, _) ->
169169- cp source target
170170- | Unix.Unix_error (err, _, _) ->
171171- Printf.eprintf "Warning: %s -> %s = %s\n" source target (Unix.error_message err);
172172- ) entries
148148+ Array.iter
149149+ (fun entry ->
150150+ let source = Filename.concat current_source entry in
151151+ let target = Filename.concat current_target entry in
152152+ try
153153+ let stat = Unix.lstat source in
154154+ match stat.st_kind with
155155+ | S_LNK -> if not (Sys.file_exists target) then Unix.symlink (Unix.readlink source) target
156156+ | S_REG -> if not (Sys.file_exists target) then Unix.link source target
157157+ | S_DIR ->
158158+ mkdir target;
159159+ process_directory source target
160160+ | S_CHR
161161+ | S_BLK
162162+ | S_FIFO
163163+ | S_SOCK ->
164164+ ()
165165+ with
166166+ | Unix.Unix_error (Unix.EMLINK, _, _) -> cp source target
167167+ | Unix.Unix_error (err, _, _) -> Printf.eprintf "Warning: %s -> %s = %s\n" source target (Unix.error_message err))
168168+ entries
173169 in
174170 process_directory source target
+10
bin/s.ml
···11+module type CONTAINER = sig
22+ type t
33+44+ val init : config:Config.t -> t
55+ val deinit : t:t -> unit
66+ val config : t:t -> Config.t
77+ val run : t:t -> temp_dir:string -> string -> string -> unit
88+ val build : t:t -> temp_dir:string -> string -> OpamPackage.t -> OpamPackage.Set.t OpamPackage.Map.t -> OpamPackage.Set.t -> unit
99+ val std_env : string -> OpamTypes.variable_contents option
1010+end
+22
bin/util.ml
···11+let hash_of_set s = s |> OpamPackage.Set.to_list |> List.map OpamPackage.to_string |> String.concat " " |> Digest.string |> Digest.to_hex
22+33+let std_env ?(ocaml_native = true) ?opam_version ~arch ~os ~os_distribution ~os_family ~os_version () = function
44+ | "arch" -> Some (OpamTypes.S arch)
55+ | "os" -> Some (OpamTypes.S os)
66+ | "os-distribution" -> Some (OpamTypes.S os_distribution)
77+ | "os-version" -> Some (OpamTypes.S os_version)
88+ | "os-family" -> Some (OpamTypes.S os_family)
99+ | "opam-version" -> Some (OpamVariable.S (Option.value ~default:OpamVersion.(to_string current) opam_version))
1010+ (* There is no system compliler *)
1111+ | "sys-ocaml-arch"
1212+ | "sys-ocaml-cc"
1313+ | "sys-ocaml-libc"
1414+ | "sys-ocaml-system"
1515+ | "sys-ocaml-version" ->
1616+ Some (OpamTypes.S "")
1717+ | "ocaml:native" -> Some (OpamTypes.B ocaml_native)
1818+ | "ocaml:version" -> Some (OpamTypes.S "5.3.0")
1919+ | "enable-ocaml-beta-repository" -> None (* Fake variable? *)
2020+ | v ->
2121+ OpamConsole.warning "Unknown variable %S" v;
2222+ None
+136
bin/windows.ml
···11+type t = {
22+ config : Config.t;
33+ network : string;
44+}
55+66+let hostname = "builder"
77+let env = [ ("OPAMYES", "1"); ("OPAMCONFIRMLEVEL", "unsafe-yes"); ("OPAMERRLOGLEN", "0"); ("OPAMPRECISETRACKING", "1") ]
88+let std_env = Util.std_env ~arch:"x86_64" ~os:"win32" ~os_distribution:"cygwin" ~os_family:"windows" ~os_version:"10.0.20348" ()
99+let strings xs = `List (List.map (fun x -> `String x) xs)
1010+1111+let make_config_json ~layers ~cwd ~argv ~hostname ~uid ~gid ~env ~mounts ~network : Yojson.Safe.t =
1212+ `Assoc
1313+ [
1414+ ("ociVersion", `String "1.1.0");
1515+ ( "process",
1616+ `Assoc
1717+ [
1818+ ("terminal", `Bool false);
1919+ ("user", `Assoc [ ("uid", `Int uid); ("gid", `Int gid) ]);
2020+ ("args", strings argv);
2121+ ("env", strings (List.map (fun (k, v) -> Printf.sprintf "%s=%s" k v) env));
2222+ ("cwd", `String cwd);
2323+ ] );
2424+ ("root", `Assoc [ ("path", `String ""); ("readonly", `Bool false) ]);
2525+ ("hostname", `String hostname);
2626+ ("mounts", `List (Mount.user_mounts mounts));
2727+ ( "windows",
2828+ `Assoc
2929+ [
3030+ ("layerFolders", strings layers);
3131+ ("ignoreFlushesDuringBoot", `Bool true);
3232+ ("network", `Assoc [ ("allowUnqualifiedDNSQuery", `Bool true); ("networkNamespace", `String network) ]);
3333+ ] );
3434+ ]
3535+3636+let init ~(config : Config.t) = { config; network = Os.run "hcn-namespace create" |> String.trim }
3737+let deinit ~t = ignore (Os.exec [ "hcn-namespace"; "delete"; t.network ])
3838+let config ~t = t.config
3939+4040+let run ~t ~temp_dir opam_repository build_log =
4141+ let rootfs = Os.path [ temp_dir; "fs" ] in
4242+ let () = Os.mkdir rootfs in
4343+ let argv =
4444+ [
4545+ "cmd";
4646+ "/c";
4747+ String.concat " && "
4848+ [
4949+ "set";
5050+ "curl.exe -L -o c:\\Windows\\opam.exe https://github.com/ocaml/opam/releases/download/2.3.0/opam-2.3.0-x86_64-windows.exe";
5151+ "curl.exe -L -o c:\\Users\\ContainerAdministrator\\AppData\\Local\\opam\\opam-build.exe \
5252+ https://github.com/mtelvers/opam-build/releases/download/1.0.0/opam-build-1.0.0-x86_64-windows.exe";
5353+ (* "net user opam /nopassword /add"; *)
5454+ "opam.exe init -k local -a c:\\opam-repository --bare -y";
5555+ "opam.exe switch create default --empty";
5656+ ];
5757+ ]
5858+ in
5959+ let mounts =
6060+ [
6161+ { Mount.ty = "bind"; src = rootfs; dst = "c:\\Users\\ContainerAdministrator\\AppData\\Local\\opam"; options = [ "rw"; "rbind"; "rprivate" ] };
6262+ (*{ Mount.ty = "bind"; src = etc_hosts; dst = "/etc/hosts"; options = [ "ro"; "rbind"; "rprivate" ] }; *)
6363+ { ty = "bind"; src = opam_repository; dst = "c:\\opam-repository"; options = [ "rbind"; "rprivate" ] };
6464+ ]
6565+ in
6666+ let mounts_json = Os.path [ temp_dir; "mounts.json" ] in
6767+ let _ =
6868+ Os.retry_exec ~stdout:mounts_json
6969+ [ "ctr"; "snapshot"; "prepare"; "--mounts"; Filename.basename temp_dir; "sha256:6f75278129ccaff6084617218cb8a28e8acc1748beeaae2946dfa92c5ca425ee" ]
7070+ in
7171+ let layers = Json_layers.read_layers mounts_json in
7272+ let config = make_config_json ~layers ~cwd:"c:\\" ~argv ~hostname ~uid:0 ~gid:0 ~env ~mounts ~network:t.network in
7373+ let config_json = Os.path [ temp_dir; "config.json" ] in
7474+ let () = Os.write_to_file config_json (Yojson.Safe.pretty_to_string config) in
7575+ let result = Os.exec ~stdout:build_log ~stderr:build_log [ "ctr"; "run"; "--cni"; "--rm"; "--config"; config_json; Filename.basename temp_dir ] in
7676+ let _ = Os.rm (Os.path [ rootfs; "lock" ]) in
7777+ let _ = Os.rm (Os.path [ rootfs; "conf.lock" ]) in
7878+ let _ = Os.rm (Os.path [ rootfs; "default"; ".opam-switch"; "lock" ]) in
7979+ let _ = Os.rm (Os.path [ rootfs; "repo"; "state-33BF9E46.cache" ]) in
8080+ let _ = Os.rm (Os.path [ rootfs; "repo"; "conf.lock" ]) in
8181+ let () = Os.write_to_file (Os.path [ temp_dir; "status" ]) (string_of_int result) in
8282+ let _ = Os.exec [ "ctr"; "snapshot"; "rm"; Filename.basename temp_dir ] in
8383+ ()
8484+8585+let build ~t ~temp_dir build_log pkg dependencies deps =
8686+ let config = t.config in
8787+ let target = Os.path [ temp_dir; "fs" ] in
8888+ let () = Os.mkdir target in
8989+ let pin = if OpamPackage.name_to_string pkg = config.package then [ "opam pin -yn " ^ OpamPackage.to_string pkg ^ " $HOME/src/"; "cd src" ] else [] in
9090+ let argv =
9191+ [
9292+ "cmd";
9393+ "/c";
9494+ String.concat " && " (pin @ [ "set && c:\\Users\\ContainerAdministrator\\AppData\\Local\\opam\\opam-build.exe -v " ^ OpamPackage.to_string pkg ]);
9595+ ]
9696+ in
9797+ let _ = Os.hardlink_tree ~source:(Os.path [ config.dir; "root"; "fs" ]) ~target in
9898+ let () =
9999+ OpamPackage.Set.iter
100100+ (fun dep ->
101101+ let hash = Util.hash_of_set (OpamPackage.Set.add dep (OpamPackage.Map.find dep dependencies)) in
102102+ Os.hardlink_tree ~source:(Os.path [ config.dir; hash; "fs" ]) ~target)
103103+ deps
104104+ in
105105+ let () =
106106+ let default_switch = Os.path [ temp_dir; "fs"; "default" ] in
107107+ if Sys.file_exists default_switch then Opamh.dump_state default_switch
108108+ in
109109+ let mounts =
110110+ [
111111+ { Mount.ty = "bind"; src = target; dst = "c:\\Users\\ContainerAdministrator\\AppData\\Local\\opam"; options = [ "rw"; "rbind"; "rprivate" ] };
112112+ {
113113+ ty = "bind";
114114+ src = config.opam_repository;
115115+ dst = "c:\\users\\ContainerAdministrator\\AppData\\Local\\opam\\repo\\default";
116116+ options = [ "rbind"; "rprivate" ];
117117+ };
118118+ ]
119119+ in
120120+ let mounts_json = Os.path [ temp_dir; "mounts.json" ] in
121121+ let _ =
122122+ Os.retry_exec ~stdout:mounts_json
123123+ [ "ctr"; "snapshot"; "prepare"; "--mounts"; Filename.basename temp_dir; "sha256:6f75278129ccaff6084617218cb8a28e8acc1748beeaae2946dfa92c5ca425ee" ]
124124+ in
125125+ let layers = Json_layers.read_layers mounts_json in
126126+ let config = make_config_json ~layers ~cwd:"c:\\" ~argv ~hostname ~uid:0 ~gid:0 ~env ~mounts ~network:t.network in
127127+ let config_json = Os.path [ temp_dir; "config.json" ] in
128128+ let () = Os.write_to_file config_json (Yojson.Safe.pretty_to_string config) in
129129+ let result = Os.exec ~stdout:build_log ~stderr:build_log [ "ctr"; "run"; "--cni"; "--rm"; "--config"; config_json; Filename.basename temp_dir ] in
130130+ let () = Os.write_to_file (Os.path [ temp_dir; "status" ]) (string_of_int result) in
131131+ let _ = Os.exec [ "ctr"; "snapshot"; "rm"; Filename.basename temp_dir ] in
132132+ let _ = Os.rm (Os.path [ target; "repo"; "state-33BF9E46.cache" ]) in
133133+ let _ = Os.rm ~recursive:true (Os.path [ target; "default"; ".opam-switch"; "sources" ]) in
134134+ let _ = Os.rm ~recursive:true (Os.path [ target; "default"; ".opam-switch"; "build" ]) in
135135+ let _ = Os.rm (Os.path [ target; "default"; ".opam-switch"; "lock" ]) in
136136+ ()