123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512(**************************************************************************)(* *)(* Copyright 2016-2019 OCamlPro *)(* *)(* All rights reserved. This file is distributed under the terms of the *)(* GNU Lesser General Public License version 2.1, with the special *)(* exception on linking described in the file LICENSE. *)(* *)(**************************************************************************)openOpamTypesopenOpamProcess.Job.OpopenOpamStd.Option.OpmoduleO=OpamFile.OPAMletupgradeto_version_string="2.0"letupgradeto_version=OpamVersion.of_stringupgradeto_version_stringletocaml_wrapper_pkgname=OpamPackage.Name.of_string"ocaml"letocaml_official_pkgname=OpamPackage.Name.of_string"ocaml-base-compiler"letocaml_variants_pkgname=OpamPackage.Name.of_string"ocaml-variants"letocaml_system_pkgname=OpamPackage.Name.of_string"ocaml-system"letocaml_conflict_class=OpamPackage.Name.of_string"ocaml-core-compiler"letocaml_package_names=[ocaml_wrapper_pkgname;ocaml_official_pkgname;ocaml_variants_pkgname;ocaml_system_pkgname](* OCaml script that generates the .config file for a given ocaml compiler *)letwrapper_conf_script="let () =\n\
\ let ocaml_version =\n\
\ let v = Sys.ocaml_version in\n\
\ try String.sub v 0 (String.index v '+') with Not_found -> v\n\
\ in\n\
\ if ocaml_version <> \"%{_:version}%\" then\n\
\ (Printf.eprintf\n\
\ \"OCaml version mismatch: %%s, expected %{_:version}%\"\n\
\ ocaml_version;\n\
\ exit 1)\n\
\ else\n\
\ let oc = open_out \"%{_:name}%.config\" in\n\
\ let exe = \".exe\" in\n\
\ let (ocaml, suffix) =\n\
\ let s = Sys.executable_name in\n\
\ if Filename.check_suffix s exe then\n\
\ (Filename.chop_suffix s exe, exe)\n\
\ else\n\
\ (s, \"\")\n\
\ in\n\
\ let ocamlc = ocaml^\"c\"^suffix in\n\
\ let libdir =\n\
\ let ic = Unix.open_process_in (ocamlc^\" -where\") in\n\
\ set_binary_mode_in ic false;\n\
\ let r = input_line ic in\n\
\ if Unix.close_process_in ic <> Unix.WEXITED 0 then \n\
\ failwith \"Bad return from 'ocamlc -where'\";\n\
\ r\n\
\ in\n\
\ let stubsdir =\n\
\ let ic = open_in (Filename.concat libdir \"ld.conf\") in\n\
\ let rec r acc = try r (input_line ic::acc) with End_of_file -> acc in\n\
\ let lines = List.rev (r []) in\n\
\ close_in ic;\n\
\ String.concat \":\" lines\n\
\ in\n\
\ let p fmt = Printf.fprintf oc (fmt ^^ \"\\n\") in\n\
\ p \"opam-version: \\\""^upgradeto_version_string^"\\\"\";\n\
\ p \"variables {\";\n\
\ p \" native: %%b\"\n\
\ (Sys.file_exists (ocaml^\"opt\"^suffix));\n\
\ p \" native-tools: %%b\"\n\
\ (Sys.file_exists (ocamlc^\".opt\"^suffix));\n\
\ p \" native-dynlink: %%b\"\n\
\ (Sys.file_exists (Filename.concat libdir \"dynlink.cmxa\"));\n\
\ p \" stubsdir: %%S\"\n\
\ stubsdir;\n\
\ p \" preinstalled: %{ocaml-system:installed}%\";\n\
\ p \" compiler: \\\"%{ocaml-system:installed?system:}%\
%{ocaml-base-compiler:version}%\
%{ocaml-variants:version}%\\\"\";\n\
\ p \"}\";\n\
\ close_out oc\n\
"letsystem_conf_script="let () =\n\
\ let exe = \".exe\" in\n\
\ let ocamlc =\n\
\ let (base, suffix) =\n\
\ let s = Sys.executable_name in\n\
\ if Filename.check_suffix s exe then\n\
\ (Filename.chop_suffix s exe, exe)\n\
\ else\n\
\ (s, \"\") in\n\
\ base ^ \"c\" ^ suffix in\n\
\ if Sys.ocaml_version <> \"%{_:version}%\" then\n\
\ (Printf.eprintf\n\
\ \"ERROR: The compiler found at %%s has version %%s,\\n\\\n\
\ and this package requires %{_:version}%.\\n\\\n\
\ You should use e.g. 'opam switch create %{_:name}%.%%s' \\\n\
\ instead.\"\n\
\ ocamlc Sys.ocaml_version Sys.ocaml_version;\n\
\ exit 1)\n\
\ else\n\
\ let ocamlc_digest = Digest.to_hex (Digest.file ocamlc) in\n\
\ let libdir =\n\
\ if Sys.command (ocamlc^\" -where > %{_:name}%.config\") = 0 then\n\
\ let ic = open_in \"%{_:name}%.config\" in\n\
\ let r = input_line ic in\n\
\ close_in ic;\n\
\ Sys.remove \"%{_:name}%.config\";\n\
\ r\n\
\ else\n\
\ failwith \"Bad return from 'ocamlc -where'\"\n\
\ in\n\
\ let graphics = Filename.concat libdir \"graphics.cmi\" in\n\
\ let graphics_digest =\n\
\ if Sys.file_exists graphics then\n\
\ Digest.to_hex (Digest.file graphics)\n\
\ else\n\
\ String.make 32 '0'\n\
\ in\n\
\ let oc = open_out \"%{_:name}%.config\" in\n\
\ Printf.fprintf oc \"opam-version: \\\""^upgradeto_version_string^"\\\"\\n\\\n\
\ file-depends: [ [ %%S %%S ] [ %%S %%S ] ]\\n\\\n\
\ variables { path: %%S }\\n\"\n\
\ ocamlc ocamlc_digest graphics graphics_digest (Filename.dirname ocamlc);\n\
\ close_out oc\n\
"letconf_script_name="gen_ocaml_config.ml"letall_base_packages=OpamPackage.Name.Set.of_list(List.mapOpamPackage.Name.of_string["base-bigarray";"base-threads";"base-unix";])letcache_file:stringlistlistOpamFile.t=OpamFile.make@@OpamFilename.of_string"~/.cache/opam-compilers-to-packages/url-hashes"letdo_upgraderepo_root=letwrite_opam?(add_files=[])opam=letnv=O.packageopaminletpfx=Some(OpamPackage.name_to_stringnv)inletfiles_dir=OpamRepositoryPath.filesrepo_rootpfxnvinO.write(OpamRepositoryPath.opamrepo_rootpfxnv)opam;List.iter(fun(base,contents)->OpamFilename.(writeOp.(files_dir//base)contents))add_filesinletcompilers=letcompilers_dir=OpamFilename.Op.(repo_root/"compilers")inifOpamFilename.exists_dircompilers_dirthen(List.fold_left(funmapf->ifOpamFilename.check_suffixf".comp"thenletc=OpamFilename.(Base.to_string(basename(chop_extensionf)))inOpamStd.String.Map.addcfmapelsemap)OpamStd.String.Map.empty(OpamFilename.rec_filescompilers_dir))elseOpamStd.String.Map.emptyinletget_url_md5,save_cache=leturl_md5=Hashtbl.create187inlet()=OpamFile.Lines.read_optcache_file+![]|>List.iter@@function|[url;md5]->OpamStd.Option.iter(funurl->Hashtbl.addurl_md5url(OpamHash.of_stringmd5))(OpamUrl.parse_opt~handle_suffix:falseurl)|_->failwith"Bad cache, run 'opam admin upgrade --clear-cache'"in(funurl->tryDone(Some(Hashtbl.findurl_md5url))withNot_found->OpamFilename.with_tmp_dir_job@@fundir->OpamProcess.Job.ignore_errors~default:None(fun()->(* Download to package.patch, rather than allowing the name to be
guessed since, on Windows, some of the characters which are
valid in URLs are not valid in filenames *)letf=letbase=OpamFilename.Base.of_string"package.patch"inOpamFilename.createdirbaseinOpamDownload.download_as~overwrite:falseurlf@@|fun()->lethash=OpamHash.compute(OpamFilename.to_stringf)inHashtbl.addurl_md5urlhash;Somehash)),(fun()->Hashtbl.fold(funurlhashl->[OpamUrl.to_stringurl;OpamHash.to_stringhash]::l)url_md5[]|>funlines->tryOpamFile.Lines.writecache_filelineswithe->OpamStd.Exn.fatale;OpamConsole.log"REPO_UPGRADE""Could not write archive hash cache to %s, skipping (%s)"(OpamFile.to_stringcache_file)(Printexc.to_stringe))inletocaml_versions=OpamStd.String.Map.fold(funccomp_fileocaml_versions->letcomp=OpamFile.Comp.read(OpamFile.makecomp_file)inletdescr_file=OpamFilename.(opt_file(add_extension(chop_extensioncomp_file)"descr"))inletdescr=descr_file>>|funf->OpamFile.Descr.read(OpamFile.makef)inletnv,ocaml_version,variant=matchOpamStd.String.cut_atc'+'with|None->OpamPackage.createocaml_official_pkgname(OpamPackage.Version.of_stringc),c,None|Some(version,variant)->OpamPackage.createocaml_variants_pkgname(OpamPackage.Version.of_string(version^"+"^variant)),version,Somevariantin(* (Some exotic compiler variants have e.g. 'lwt' as base package, which
won't work in our current setup. They'll need to be rewritten, but
break the following detection of all base packages, which isn't
idempotent anyway...)
List.iter (fun (name, _) ->
all_base_packages := OpamPackage.Name.Set.add name !all_base_packages)
(OpamFormula.atoms (OpamFile.Comp.packages comp));
*)letopam=OpamFormatUpgrade.comp_file~package:nv?descrcompinletopam=O.with_conflict_class[ocaml_conflict_class]opaminletopam=matchOpamFile.OPAM.urlopamwith|SomeurlfwhenOpamFile.URL.checksumurlf=[]->leturl=OpamFile.URL.urlurlfin(matchurl.OpamUrl.backendwith|#OpamUrl.version_control->Someopam|`rsyncwhenOpamUrl.local_dirurl<>None->Someopam|_->(matchOpamProcess.Job.run(get_url_md5url)with|None->None|Somehash->Some(OpamFile.OPAM.with_url(OpamFile.URL.with_checksum[hash]urlf)opam)))|_->Someopaminmatchopamwith|None->OpamConsole.error"Could not get the archive of %s, skipping"(OpamPackage.to_stringnv);ocaml_versions|Someopam->letpatches=OpamFile.Comp.patchescompinifpatches<>[]thenOpamConsole.msg"Fetching patches of %s to check their hashes...\n"(OpamPackage.to_stringnv);letextra_sources=(* Download them just to get their MD5 *)OpamParallel.map~jobs:3~command:(funurl->get_url_md5url@@|function|Somemd5->Some(url,md5,None)|None->OpamConsole.error"Could not get patch file for %s from %s, skipping"(OpamPackage.to_stringnv)(OpamUrl.to_stringurl);None)(OpamFile.Comp.patchescomp)inifList.memNoneextra_sourcesthenocaml_versionselseletopam=opam|>OpamFile.OPAM.with_extra_sources(List.map(fun(url,hash,_)->OpamFilename.Base.of_string(OpamUrl.basenameurl),OpamFile.URL.create~checksum:[hash]url)(OpamStd.List.filter_someextra_sources))inwrite_opamopam;ifvariant=Nonethenbegin(* "official" compiler release: generate a system compiler package *)letsys_nv=OpamPackage.createocaml_system_pkgnamenv.versioninletrev_dep_flag=Filter(FIdent([],OpamVariable.of_string"post",None))inletsystem_opam=O.createsys_nv|>O.with_substs[OpamFilename.Base.of_stringconf_script_name]|>O.with_build[List.map(funs->CStrings,None)["ocaml";conf_script_name],None]|>O.with_conflict_class[ocaml_conflict_class]|>O.with_depends(OpamFormula.ands(List.map(funname->Atom(OpamPackage.Name.of_stringname,Atom(rev_dep_flag)))["ocaml";"base-unix";"base-threads";"base-bigarray"]))|>O.with_maintainer["platform@lists.ocaml.org"]|>O.with_flags[Pkgflag_Compiler]|>O.with_descr(OpamFile.Descr.create"The OCaml compiler (system version, from outside of opam)")|>O.with_available(FOp(FIdent([],OpamVariable.of_string"sys-ocaml-version",None),`Eq,FString(OpamPackage.Version.to_stringnv.version)))(* add depext towards an 'ocaml' package? *)inwrite_opam~add_files:[conf_script_name^".in",system_conf_script]system_opamend;(* cleanup *)OpamFilename.removecomp_file;OpamStd.Option.iterOpamFilename.removedescr_file;OpamFilename.rmdir_cleanup(OpamFilename.dirnamecomp_file);OpamConsole.status_line"Compiler %s successfully converted to package %s"c(OpamPackage.to_stringnv);OpamStd.String.Set.addocaml_versionocaml_versions)compilersOpamStd.String.Set.emptyinOpamConsole.clear_status();save_cache();(* Generate "ocaml" package wrappers depending on one of the implementations
at the appropriate version *)letgen_ocaml_wrapperstr_version=letversion=OpamPackage.Version.of_stringstr_versioninletwrapper_nv=OpamPackage.createocaml_wrapper_pkgnameversioninletupper_bound_v=letg=Re.(exec@@compile@@seq[repdigit;eos])str_versionintryletsn=Re.Group.getg0inString.substr_version0(fst(Re.Group.offsetg0))^(string_of_int(1+int_of_stringsn))^"~"withNot_found->str_version^"a"inletwrapper_opam=O.createwrapper_nv|>O.with_substs[OpamFilename.Base.of_stringconf_script_name]|>O.with_build[List.map(funs->CStrings,None)["ocaml";"unix.cma";conf_script_name],None]|>O.with_maintainer["platform@lists.ocaml.org"]|>O.with_build_env["CAML_LD_LIBRARY_PATH",Eq,"",None]|>O.with_env["CAML_LD_LIBRARY_PATH",Eq,"%{_:stubsdir}%",None;"CAML_LD_LIBRARY_PATH",PlusEq,"%{lib}%/stublibs",None;"OCAML_TOPLEVEL_PATH",Eq,"%{toplevel}%",None;]|>(* leave the Compiler flag to the implementations (since the user
needs to select one)
O.with_flags [Pkgflag_Compiler] |> *)O.with_descr(OpamFile.Descr.create"The OCaml compiler (virtual package)\n\
This package requires a matching implementation of OCaml,\n\
and polls it to initialise specific variables like \
`ocaml:native-dynlink`")|>O.with_depends(OpamFormula.ors[Atom(ocaml_official_pkgname,Atom(Constraint(`Eq,FStringstr_version)));Atom(ocaml_variants_pkgname,OpamFormula.ands[Atom(Constraint(`Geq,FStringstr_version));Atom(Constraint(`Lt,FStringupper_bound_v));]);Atom(ocaml_system_pkgname,Atom(Constraint(`Eq,FStringstr_version)))])inwrite_opam~add_files:[conf_script_name^".in",wrapper_conf_script]wrapper_opaminOpamStd.String.Set.itergen_ocaml_wrapperocaml_versions;letpackages=OpamRepository.packages_with_prefixesrepo_rootinOpamConsole.log"REPO_UPGRADE""Will not update base packages: %s\n"(OpamPackage.Name.Set.to_stringall_base_packages);OpamPackage.Map.iter(funpackageprefix->letopam_file=OpamRepositoryPath.opamrepo_rootprefixpackageinletopam0=OpamFile.OPAM.readopam_fileinOpamFile.OPAM.print_errors~file:opam_fileopam0;letnv=OpamFile.OPAM.packageopam0inifnot(List.memnv.nameocaml_package_names)&¬(OpamPackage.Name.Set.memnv.nameall_base_packages)thenletopam=OpamFileTools.add_aux_files~files_subdir_hashes:trueopam0inletopam=OpamFormatUpgrade.opam_file_from_1_2_to_2_0~filename:opam_fileopaminifopam<>opam0then(OpamFile.OPAM.write_with_preserved_formatopam_fileopam;List.iterOpamFilename.remove[OpamFile.filename(OpamRepositoryPath.descrrepo_rootprefixpackage);OpamFile.filename(OpamRepositoryPath.urlrepo_rootprefixpackage);];OpamConsole.status_line"Updated %s"(OpamFile.to_stringopam_file)))packages;OpamConsole.clear_status();letrepo_file=OpamRepositoryPath.reporepo_rootinOpamFile.Repo.writerepo_file(OpamFile.Repo.with_opam_versionupgradeto_version(OpamFile.Repo.safe_readrepo_file))letclear_cache()=OpamFilename.remove(OpamFile.filenamecache_file)letdo_upgrade_mirrorrepo_rootbase_url=OpamFilename.with_tmp_dir@@funtmp_mirror_dir->letopenOpamFilename.Opinletcopy_dird=letsrc=repo_root/dinifOpamFilename.exists_dirsrcthenOpamFilename.copy_dir~src~dst:(tmp_mirror_dir/d)inletcopy_filef=letsrc=repo_root//finifOpamFilename.existssrcthenOpamFilename.copy~src~dst:(tmp_mirror_dir//f)incopy_dir"packages";copy_dir"compilers";copy_file"repo";do_upgradetmp_mirror_dir;letrepo_file=OpamFile.make(OpamFilename.of_string"repo")inletrepo0=OpamFile.Repo.safe_readrepo_fileinletopam_version_fid=FIdent([],OpamVariable.of_string"opam-version",None)inletredir=OpamUrl.to_stringOpamUrl.Op.(base_url/upgradeto_version_string),Some(FOp(opam_version_fid,`Geq,FString(upgradeto_version_string^"~")))inletrepo0=ifOpamFile.Repo.opam_versionrepo0=NonethenOpamFile.Repo.with_opam_version(OpamVersion.of_string"1.2")repo0elserepo0inletrepo0=OpamFile.Repo.with_redirect(List.filter(funr->r<>redir)(OpamFile.Repo.redirectrepo0))repo0inletrepo_12=OpamFile.Repo.with_redirect(redir::OpamFile.Repo.redirectrepo0)repo0inletrepo_20=letredir=(OpamUrl.to_stringbase_url,Some(FOp(opam_version_fid,`Lt,FString(upgradeto_version_string^"~"))))inrepo0|>OpamFile.Repo.with_opam_versionOpamFile.Repo.format_version|>OpamFile.Repo.with_redirect(redir::OpamFile.Repo.redirectrepo0)inOpamFile.Repo.writerepo_filerepo_12;OpamFile.Repo.write(OpamFile.makeOpamFilename.Op.(tmp_mirror_dir//"repo"))repo_20;letdir20=OpamFilename.Dir.of_stringupgradeto_version_stringinOpamFilename.rmdirdir20;OpamFilename.move_dir~src:tmp_mirror_dir~dst:dir20;OpamConsole.note"Indexes need updating: you should now run\n\
\n%s\
\ cd %s && opam admin index"(ifrepo_12<>repo0&&OpamFilename.exists(OpamFilename.of_string"urls.txt")then" opam admin index --full-urls-txt\n"else"")(OpamFilename.remove_prefix_dirrepo_rootdir20)