123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508(**************************************************************************)(* *)(* Copyright 2012-2019 OCamlPro *)(* Copyright 2012 INRIA *)(* *)(* 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.Opletlogfmt=OpamConsole.log"REPOSITORY"fmtletslog=OpamConsole.slogletfind_backend_by_kind=function|`http->(moduleOpamHTTP.B:OpamRepositoryBackend.S)|`rsync->(moduleOpamLocal.B:OpamRepositoryBackend.S)|`git->(moduleOpamGit.B:OpamRepositoryBackend.S)|`hg->(moduleOpamHg.B:OpamRepositoryBackend.S)|`darcs->(moduleOpamDarcs.B:OpamRepositoryBackend.S)letfind_vcs_backend=function|`git->(moduleOpamGit.VCS:OpamVCS.VCS)|`hg->(moduleOpamHg.VCS:OpamVCS.VCS)|`darcs->(moduleOpamDarcs.VCS:OpamVCS.VCS)leturl_backendurl=find_backend_by_kindurl.OpamUrl.backendletfind_backendr=url_backendr.repo_urlletcache_urlroot_cache_urlchecksum=List.fold_leftOpamUrl.Op.(/)root_cache_url(OpamHash.to_pathchecksum)letcache_filecache_dirchecksum=letrecauxacc=function|[f]->OpamFilename.Op.(acc//f)|d::d1->auxOpamFilename.Op.(acc/d)d1|[]->assertfalseinauxcache_dir(OpamHash.to_pathchecksum)letfetch_from_cache=letcurrently_downloading=ref[]inletrecno_concurrent_dlskeyfx=ifList.memkey!currently_downloadingthenRun(OpamProcess.command"sleep"["1"],(fun_->no_concurrent_dlskeyfx))else(currently_downloading:=key::!currently_downloading;OpamProcess.Job.finally(fun()->currently_downloading:=List.filter(funk->k<>key)!currently_downloading)(fun()->fx))infuncache_dircache_urlschecksums->letmismatchfile=OpamConsole.error"Conflicting file hashes, or broken or compromised cache!\n%s"(OpamStd.Format.itemize(funck->OpamHash.to_stringck^ifOpamHash.check_file(OpamFilename.to_stringfile)ckthenOpamConsole.colorise`green" (match)"elseOpamConsole.colorise`red" (MISMATCH)")checksums);OpamFilename.removefile;letm="cache CONFLICT"inDone(Not_available(Somem,m))inletdl_from_cache_jobroot_cache_urlchecksumfile=leturl=cache_urlroot_cache_urlchecksuminmatchurl.OpamUrl.backendwith|`http->OpamDownload.download_as~quiet:true~validate:false~overwrite:true~checksumurlfile|`rsync->beginmatchOpamUrl.local_fileurlwith|Somesrc->OpamFilename.copy~src~dst:file;OpamProcess.Job.Op.Done()|None->(OpamLocal.rsync_fileurlfile@@|function|Result_|Up_to_date_->()|Not_available(s,l)->raise(OpamDownload.Download_fail(s,l)))end|#OpamUrl.version_control->failwith"Version control not allowed as cache URL"intrylethit_checksum,hit_file=OpamStd.List.find_map(funck->letf=cache_filecache_dirckinifOpamFilename.existsfthenSome(ck,f)elseNone)checksumsinifList.for_all(funck->ck=hit_checksum||OpamHash.check_file(OpamFilename.to_stringhit_file)ck)checksumsthenDone(Up_to_date(hit_file,OpamUrl.empty))elsemismatchhit_filewithNot_found->matchchecksumswith|[]->letm="cache miss"inDone(Not_available(Somem,m))|checksum::_->(* Try all cache urls in order, but only the first checksum *)letlocal_file=cache_filecache_dirchecksuminlettmpfile=OpamFilename.add_extensionlocal_file"tmp"inletrectry_cache_dl=function|[]->letm="cache miss"inDone(Not_available(Somem,m))|root_cache_url::other_caches->OpamProcess.Job.catch(functionFailure_|OpamDownload.Download_fail_->try_cache_dlother_caches|e->raisee)@@fun()->dl_from_cache_jobroot_cache_urlchecksumtmpfile@@+fun()->ifList.for_all(OpamHash.check_file(OpamFilename.to_stringtmpfile))checksumsthen(OpamFilename.move~src:tmpfile~dst:local_file;Done(Result(local_file,root_cache_url)))elsemismatchtmpfileinno_concurrent_dlschecksumtry_cache_dlcache_urlsletvalidate_and_add_to_cachelabelurlcache_dirfilechecksums=tryletmismatch,expected=OpamStd.List.find_map(func->matchOpamHash.mismatch(OpamFilename.to_stringfile)cwith|Somefound->Some(found,c)|None->None)checksumsinOpamConsole.error"%s: Checksum mismatch for %s:\n\
\ expected %s\n\
\ got %s"label(OpamUrl.to_stringurl)(OpamHash.to_stringexpected)(OpamHash.to_stringmismatch);OpamFilename.removefile;falsewithNot_found->(matchcache_dir,checksumswith|Somedir,ck::_->OpamFilename.copy~src:file~dst:(cache_filedirck)(* idea: hardlink to the other checksums? *)|_->());true(* [cache_dir] used to add to cache only *)letpull_from_upstreamlabel?(working_dir=false)?subpathcache_dirdestdirchecksumsurl=letmoduleB=(valurl_backendurl:OpamRepositoryBackend.S)inletcksum=matchchecksumswith[]->None|c::_->Somecinlettext=OpamProcess.make_command_textlabel(OpamUrl.string_of_backendurl.OpamUrl.backend)inOpamProcess.Job.with_texttext@@(ifworking_dirthenB.sync_dirty?subpathdestdirurlelseletpin_cache_dir=OpamRepositoryPath.pin_cacheurlinleturl,pull=ifOpamUrl.(matchurl.backendwith|#version_control->false|_->true)&&OpamFilename.exists_dirpin_cache_dirthen(log"Pin cache existing for %s : %s\n"(OpamUrl.to_stringurl)@@OpamFilename.Dir.to_stringpin_cache_dir;letrsync=OpamUrl.parse~backend:`rsync~from_file:false@@OpamFilename.Dir.to_stringpin_cache_dirinletpull=letmoduleBR=(valurl_backendrsync:OpamRepositoryBackend.S)inBR.pull_urlinrsync,pull)elseifOpamUrl.(matchurl.backendwith|`git->true|_->false)&&OpamFilename.exists_dirpin_cache_dirthen(log"Pin cache (git) existing for %s : %s\n"(OpamUrl.to_stringurl)@@OpamFilename.Dir.to_stringpin_cache_dir;letgit_cached=OpamUrl.parse~backend:`git@@OpamFilename.Dir.to_stringpin_cache_dirinletpull=letmoduleBR=(valurl_backendgit_cached:OpamRepositoryBackend.S)inBR.pull_urlingit_cached,pull)elseurl,B.pull_urlinpull?cache_dir?subpathdestdircksumurl)@@|function|(Result(Somefile)|Up_to_date(Somefile))asret->ifOpamRepositoryConfig.(!r.force_checksums)=Somefalse||validate_and_add_to_cachelabelurlcache_dirfilechecksumsthenretelseletm="Checksum mismatch"inNot_available(Somem,m)|(ResultNone|Up_to_dateNone)asret->ret|Not_available_asna->naletpull_from_mirrorslabel?working_dir?subpathcache_dirdestdirchecksumsurls=letrecaux=function|[]->invalid_arg"pull_from_mirrors: empty mirror list"|[url]->pull_from_upstreamlabel?working_dir?subpathcache_dirdestdirchecksumsurl@@|funr->url,r|url::mirrors->pull_from_upstreamlabel?working_dir?subpathcache_dirdestdirchecksumsurl@@+function|Not_available(_,s)->OpamConsole.warning"%s: download of %s failed (%s), trying mirror"label(OpamUrl.to_stringurl)s;auxmirrors|r->Done(url,r)inauxurls@@|function|url,(ResultNone|Up_to_dateNone)whenchecksums<>[]->OpamConsole.error"%s: file checksum specified, but a directory was \
retrieved from %s"label(OpamUrl.to_stringurl);OpamFilename.rmdirdestdir;letm="can't check directory checksum"inurl,Not_available(Somem,m)|ret->retletpull_treelabel?cache_dir?(cache_urls=[])?working_dir?subpathlocal_dirnamechecksumsremote_urls=letextract_archivefs=OpamFilename.cleandirlocal_dirname;OpamFilename.extract_jobflocal_dirname@@+function|None->Done(Up_to_dates)|Some(Failures)->Done(Not_available(Somes,"Could not extract archive:\n"^s))|Some(OpamSystem.Process_errorpe)->Done(Not_available(Some(OpamProcess.result_summarype),OpamProcess.string_of_resultpe))|Somee->Done(Not_available(None,Printexc.to_stringe))in(matchcache_dirwith|Somecache_dir->lettext=OpamProcess.make_command_textlabel"dl"inOpamProcess.Job.with_texttext@@fetch_from_cachecache_dircache_urlschecksums|None->assert(cache_urls=[]);letm="no cache"inDone(Not_available(Somem,m)))@@+function|Up_to_date(archive,_)->extract_archivearchive"cached"|Result(archive,url)->letmsg=matchurl.OpamUrl.backendwith|`rsync->url.OpamUrl.path|_->OpamUrl.to_stringurlinextract_archivearchivemsg|Not_available_->ifchecksums=[]&&OpamRepositoryConfig.(!r.force_checksums=Sometrue)thenDone(Not_available(Some("missing checksum"),label^": Missing checksum, and `--require-checksums` was set."))elsepull_from_mirrorslabel?working_dir?subpathcache_dirlocal_dirnamechecksumsremote_urls@@+function|_,Up_to_dateNone->Done(Up_to_date"no changes")|url,(Up_to_date(Somearchive)|Result(Somearchive))->OpamFilename.with_tmp_dir_job@@funtmpdir->lettmp_archive=OpamFilename.(createtmpdir(basenamearchive))inOpamFilename.move~src:archive~dst:tmp_archive;extract_archivetmp_archive(OpamUrl.to_stringurl)|url,ResultNone->Done(Result(OpamUrl.to_stringurl))|_,(Not_available_asna)->Donenaletrevisiondirnameurl=letkind=url.OpamUrl.backendinletmoduleB=(valfind_backend_by_kindkind:OpamRepositoryBackend.S)inB.revisiondirnameletpull_filelabel?cache_dir?(cache_urls=[])?(silent_hits=false)filechecksumsremote_urls=(matchcache_dirwith|Somecache_dir->lettext=OpamProcess.make_command_textlabel"dl"inOpamProcess.Job.with_texttext@@fetch_from_cachecache_dircache_urlschecksums|None->assert(cache_urls=[]);letm="no cache"inDone(Not_available(Somem,m)))@@+function|Up_to_date(f,_)->ifnotsilent_hitsthenOpamConsole.msg"[%s] found in cache\n"(OpamConsole.colorise`greenlabel);OpamFilename.copy~src:f~dst:file;Done(Result())|Result(f,url)->OpamConsole.msg"[%s] downloaded from %s\n"(OpamConsole.colorise`greenlabel)(OpamUrl.to_stringurl);OpamFilename.copy~src:f~dst:file;Done(Result())|Not_available_->ifchecksums=[]&&OpamRepositoryConfig.(!r.force_checksums=Sometrue)thenDone(Not_available(Some"missing checksum",label^": Missing checksum, and `--require-checksums` was set."))elseOpamFilename.with_tmp_dir_job(funtmpdir->pull_from_mirrorslabelcache_dirtmpdirchecksumsremote_urls@@|function|_,Up_to_date_->assertfalse|_,Result(Somef)->OpamFilename.move~src:f~dst:file;Result()|_,ResultNone->letm="is a directory"inNot_available(Somem,m)|_,(Not_available_asna)->na)letpull_file_to_cachelabel~cache_dir?(cache_urls=[])checksumsremote_urls=lettext=OpamProcess.make_command_textlabel"dl"inOpamProcess.Job.with_texttext@@fetch_from_cachecache_dircache_urlschecksums@@+function|Up_to_date(_,_)->Done(Up_to_date"cached")|Result(_,url)->Done(Result(OpamUrl.to_stringurl))|Not_available_->OpamFilename.with_tmp_dir_job(funtmpdir->pull_from_mirrorslabel(Somecache_dir)tmpdirchecksumsremote_urls@@|function|_,Up_to_date_->assertfalse|url,Result(Some_)->Result(OpamUrl.to_stringurl)|_,ResultNone->letm="is a directory"inNot_available(Somem,m)|_,(Not_available_asna)->na)letpackagesrepo_root=OpamPackage.list(OpamRepositoryPath.packages_dirrepo_root)letpackages_with_prefixesrepo_root=OpamPackage.prefixes(OpamRepositoryPath.packages_dirrepo_root)letvalidate_repo_updatereporepo_rootupdate=matchrepo.repo_trust,OpamRepositoryConfig.(!r.validation_hook),OpamRepositoryConfig.(!r.force_checksums)with|None,Some_,Sometrue->OpamConsole.error"No trust anchors for repository %s, and security was enforced: \
not updating"(OpamRepositoryName.to_stringrepo.repo_name);Donefalse|None,_,_|_,None,_|_,_,Somefalse->Donetrue|Someta,Somehook,_->letcmd=letopenOpamRepositoryBackendinletenvv=matchOpamVariable.Full.to_stringv,updatewith|"anchors",_->Some(S(String.concat","ta.fingerprints))|"quorum",_->Some(S(string_of_intta.quorum))|"repo",_->Some(S(OpamFilename.Dir.to_stringrepo_root))|"patch",Update_patchf->Some(S(OpamFilename.to_stringf))|"incremental",Update_patch_->Some(Btrue)|"incremental",_->Some(Bfalse)|"dir",Update_fulld->Some(S(OpamFilename.Dir.to_stringd))|_->NoneinmatchOpamFilter.single_commandenvhookwith|cmd::args->OpamSystem.make_command~name:"validation-hook"~verbose:OpamCoreConfig.(!r.verbose_level>=2)cmdargs|[]->failwith"Empty validation hook"incmd@@>funr->log"validation: %s"(OpamProcess.result_summaryr);Done(OpamProcess.check_success_and_cleanupr)openOpamRepositoryBackendletapply_repo_updatereporepo_root=function|Update_fulld->log"%a: applying update from scratch at %a"(slogOpamRepositoryName.to_string)repo.repo_name(slogOpamFilename.Dir.to_string)d;OpamFilename.rmdirrepo_root;ifOpamFilename.is_symlink_dirdthen(OpamFilename.copy_dir~src:d~dst:repo_root;OpamFilename.rmdird)elseOpamFilename.move_dir~src:d~dst:repo_root;OpamConsole.msg"[%s] Initialised\n"(OpamConsole.colorise`green(OpamRepositoryName.to_stringrepo.repo_name));Done()|Update_patchf->OpamConsole.msg"[%s] synchronised from %s\n"(OpamConsole.colorise`green(OpamRepositoryName.to_stringrepo.repo_name))(OpamUrl.to_stringrepo.repo_url);log"%a: applying patch update at %a"(slogOpamRepositoryName.to_string)repo.repo_name(slogOpamFilename.to_string)f;letpreprocess=matchrepo.repo_url.OpamUrl.backendwith|`http|`rsync->false|_->truein(OpamFilename.patch~preprocessfrepo_root@@+function|Somee->ifnot(OpamConsole.debug())thenOpamFilename.removef;raisee|None->OpamFilename.removef;Done())|Update_empty->OpamConsole.msg"[%s] no changes from %s\n"(OpamConsole.colorise`green(OpamRepositoryName.to_stringrepo.repo_name))(OpamUrl.to_stringrepo.repo_url);log"%a: applying empty update"(slogOpamRepositoryName.to_string)repo.repo_name;Done()|Update_err_->assertfalseletcleanup_repo_updateupd=ifnot(OpamConsole.debug())thenmatchupdwith|Update_fulld->OpamFilename.rmdird|Update_patchf->OpamFilename.removef|_->()letupdatereporepo_root=log"update %a"(slogOpamRepositoryBackend.to_string)repo;letmoduleB=(valfind_backendrepo:OpamRepositoryBackend.S)inB.fetch_repo_updaterepo.repo_namerepo_rootrepo.repo_url@@+function|Update_erre->raisee|Update_empty->log"update empty, no validation performed";apply_repo_updatereporepo_rootUpdate_empty@@+fun()->B.repo_update_completerepo_rootrepo.repo_url|(Update_full_|Update_patch_)asupd->OpamProcess.Job.catch(funexn->cleanup_repo_updateupd;raiseexn)@@fun()->validate_repo_updatereporepo_rootupd@@+function|false->cleanup_repo_updateupd;failwith"Invalid repository signatures, update aborted"|true->apply_repo_updatereporepo_rootupd@@+fun()->B.repo_update_completerepo_rootrepo.repo_urlleton_local_version_controlurl~defaultf=matchurl.OpamUrl.backendwith|#OpamUrl.version_controlasbackend->(matchOpamUrl.local_dirurlwith|None->default|Somedir->fdir(find_vcs_backendbackend))|#OpamUrl.backend->defaultletcurrent_branchurl=on_local_version_controlurl~default:(DoneNone)@@fundir(moduleVCS)->VCS.current_branchdirletis_dirty?subpathurl=on_local_version_controlurl~default:(Donefalse)@@fundir(moduleVCS)->VCS.is_dirty?subpathdirletreport_fetch_resultpkg=function|Resultmsg->OpamConsole.msg"[%s] synchronised (%s)\n"(OpamConsole.colorise`green(OpamPackage.to_stringpkg))msg;Result()|Up_to_datemsg->OpamConsole.msg"[%s] synchronised (%s)\n"(OpamConsole.colorise`green(OpamPackage.to_stringpkg))msg;Up_to_date()|Not_available(s,l)->letmsg=matchswithNone->l|Somes->sinOpamConsole.msg"[%s] fetching sources failed: %s\n"(OpamConsole.colorise`red(OpamPackage.to_stringpkg))msg;Not_available(s,l)