1515# The script queries the TS to get_ordered_installable_revisions
1616# and clones (to /tmp/) the mercurial repos to get all revisions
1717# (the later is only done for tools with revisions that are not
18- # installable).
18+ # installable)
19+ #
20+ # For each revision cur that has been replaced by nxt
21+ # - check that the tool versions of the revisons are really the same
22+ # - if cur and nxt are in the lock file cur is removed
23+ # - if a Galaxy URL is given it is checked that cur is not installed
24+ # - if only cur in in the list then cur is removed and nxt is added
1925
2026import argparse
27+ import logging
2128import subprocess
2229import os .path
2330import yaml
24-
25- from bioblend import toolshed
31+ from typing import (
32+ Dict ,
33+ List ,
34+ Optional ,
35+ Set ,
36+ Tuple ,
37+ )
38+
39+ import bioblend
40+ from bioblend import galaxy , toolshed
2641from galaxy .tool_util .loader_directory import load_tool_sources_from_path
2742
2843
29- def clone (toolshed_url , name , owner , repo_path ):
44+ logger = logging .getLogger ()
45+
46+
47+ def clone (toolshed_url : str , name : str , owner : str , repo_path : str ) -> None :
3048 if not os .path .exists (repo_path ):
31- print (f"Cloning { toolshed_url } { owner } { name } { repo_path } " )
49+ logger . info (f"Cloning { toolshed_url } { owner } { name } { repo_path } " )
3250 cmd = [
3351 "hg" ,
3452 "clone" ,
3553 f"{ toolshed_url } /repos/{ owner } /{ name } " ,
3654 repo_path ,
3755 ]
38- subprocess .run (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
39-
40-
41- def get_all_revisions (toolshed_url , name , owner ):
42- repo_path = f"/tmp/toolshed-{ owner } -{ name } "
56+ proc = subprocess .run (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
57+ else :
58+ cmd = ["hg" , "pull" , "-u" ]
59+ proc = subprocess .run (cmd , cwd = repo_path , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
60+ assert proc .returncode == 0 , f"failed { ' ' .join (cmd )} in { repo_path } "
61+
62+ def get_all_revisions (toolshed_url : str , name : str , owner : str ) -> List [str ]:
63+ repo_path = f"/tmp/repos/{ os .path .basename (toolshed_url )} -{ owner } -{ name } "
4364 clone (toolshed_url , name , owner , repo_path )
65+ cmd = ["hg" , "update" , "tip" ]
66+ proc = subprocess .run (cmd , cwd = repo_path , capture_output = True , text = True )
67+ assert proc .returncode == 0 , f"failed { ' ' .join (cmd )} in { repo_path } "
4468 cmd = ["hg" , "log" , "--template" , "{node|short}\n " ]
69+ assert proc .returncode == 0 , f"failed { ' ' .join (cmd )} in { repo_path } "
4570 result = subprocess .run (cmd , cwd = repo_path , capture_output = True , text = True )
4671 return list (reversed (result .stdout .splitlines ()))
4772
4873
49- def fix_uninstallable (lockfile_name , toolshed_url ):
74+ def get_all_versions (
75+ toolshed_url : str , name : str , owner : str , revisions : List [str ]
76+ ) -> Dict [str , Set [Tuple [str , str ]]]:
77+ repo_path = f"/tmp/repos/{ os .path .basename (toolshed_url )} -{ owner } -{ name } "
78+ clone (toolshed_url , name , owner , repo_path )
79+
80+ versions : Dict [str , Set [Tuple [str , str ]]] = {}
81+ for r in revisions :
82+ cmd = ["hg" , "update" , r ]
83+ subprocess .run (cmd , cwd = repo_path , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
84+
85+ versions [r ] = set ()
86+ for _ , tool in load_tool_sources_from_path (repo_path ):
87+ versions [r ].add ((tool .parse_id (), tool .parse_version ()))
88+ assert len (versions [r ]) > 0
89+
90+ return versions
91+
92+
93+ def fix_uninstallable (lockfile_name : str , toolshed_url : str , galaxy_url : Optional [str ] = None ) -> None :
5094 ts = toolshed .ToolShedInstance (url = toolshed_url )
95+ installed_tools = {}
96+ if galaxy_url :
97+ gi = galaxy .GalaxyInstance (url = galaxy_url , key = None )
98+ for t in gi .toolshed .get_repositories ():
99+ if (t ['name' ], t ['owner' ]) not in installed_tools :
100+ installed_tools [(t ['name' ], t ['owner' ])] = set ()
101+ # TODO? could also check for 'status': 'Installed'
102+ if t ['deleted' ] or t ['uninstalled' ]:
103+ continue
104+ installed_tools [(t ['name' ], t ['owner' ])].add (t ['changeset_revision' ])
51105
52106 with open (lockfile_name ) as f :
53107 lockfile = yaml .safe_load (f )
54- tools = lockfile ["tools" ]
108+ locked_tools = lockfile ["tools" ]
55109
56- for i , tool in enumerate (tools ):
57- name = tool ["name" ]
58- owner = tool ["owner" ]
59- print (f"Checking { toolshed_url } { owner } { name } " )
110+ for i , locked_tool in enumerate (locked_tools ):
111+ name = locked_tool ["name" ]
112+ owner = locked_tool ["owner" ]
113+ logger . info (f"Checking { toolshed_url } { owner } { name } " )
60114 # get ordered_installable_revisions from oldest to newest
61- ordered_installable_revisions = (
62- ts .repositories .get_ordered_installable_revisions (name , owner )
63- )
64-
65- if len (set (tool ["revisions" ]) - set (ordered_installable_revisions )):
115+ try :
116+ ordered_installable_revisions = (
117+ ts .repositories .get_ordered_installable_revisions (name , owner )
118+ )
119+ except bioblend .ConnectionError :
120+ logger .warning (f"Could not determine intstallable revisions for { name } { owner } " )
121+ continue
122+
123+ if len (set (locked_tool ["revisions" ]) - set (ordered_installable_revisions )):
66124 all_revisions = get_all_revisions (toolshed_url , name , owner )
125+ try :
126+ all_versions = get_all_versions (toolshed_url , name , owner , all_revisions )
127+ except :
128+ logger .warning (f"Could not determine versions for { name } { owner } " )
129+ continue
67130
68131 to_remove = []
69132 to_append = []
70- for cur in tool ["revisions" ]:
133+ for cur in locked_tool ["revisions" ]:
71134 if cur in ordered_installable_revisions :
72135 continue
73- if cur not in all_revisions :
74- print (f"Removing { cur } -- it is not a valid revision of { name } { owner } " )
75- to_remove .append (cur )
76- continue
136+ assert cur in all_revisions , f"{ cur } is not a valid revision of { name } { owner } "
77137 start = all_revisions .index (cur )
78138 nxt = None
79139 for i in range (start , len (all_revisions )):
80140 if all_revisions [i ] in ordered_installable_revisions :
81141 nxt = all_revisions [i ]
82142 break
83- if nxt :
84- print (f"Removing { cur } in favor of { nxt } { name } { owner } " )
85- to_remove .append (cur )
86- if nxt not in tool ["revisions" ]:
87- print (f"Adding { nxt } which was absent so far { name } { owner } " )
88- to_append .append (nxt )
89- else :
90- print (f"Could not determine the next revision for { cur } { name } { owner } " )
91143
92- for r in to_remove :
93- tool ["revisions" ].remove (r )
94- tool ["revisions" ].extend (to_append )
144+ if not nxt :
145+ logger .warning (f"Could not determine the next revision for { cur } { name } { owner } " )
146+ continue
147+
148+ if all_versions [cur ] != all_versions [nxt ]:
149+ logger .warning (f"{ name } ,{ owner } { cur } { nxt } have unequal versions" )
150+ continue
151+
152+ if nxt not in locked_tool ["revisions" ]:
153+ logger .info (f"Adding { nxt } which was absent so far { name } { owner } " )
154+ to_append .append (nxt )
155+ elif galaxy_url :
156+ assert (name , owner ) in installed_tools
157+ if cur in installed_tools [(name , owner )]:
158+ logger .warning (f"{ name } ,{ owner } { cur } still installed on { galaxy_url } " )
159+ continue
160+ logger .info (f"remove { cur } in favor of { nxt } { name } { owner } " )
161+ to_remove .append (cur )
95162
96- # maintaing unified sorting standard
97- tool ["revisions" ] = sorted (list (set (map (str , tool ['revisions' ]))))
163+ for r in to_remove :
164+ locked_tool ["revisions" ].remove (r )
165+ locked_tool ["revisions" ].extend (to_append )
98166
99167 with open (lockfile_name , "w" ) as handle :
100168 yaml .dump (lockfile , handle , default_flow_style = False )
@@ -110,5 +178,19 @@ def fix_uninstallable(lockfile_name, toolshed_url):
110178 default = "https://toolshed.g2.bx.psu.edu" ,
111179 help = "Toolshed to test against" ,
112180 )
181+ parser .add_argument ('--galaxy_url' , default = None , required = False , help = "Galaxy instance to check" )
113182 args = parser .parse_args ()
114- fix_uninstallable (args .lockfile .name , args .toolshed )
183+
184+
185+ logger .setLevel (logging .DEBUG )
186+ logging .getLogger ('urllib3' ).setLevel (logging .WARNING )
187+ logging .getLogger ('bioblend' ).setLevel (logging .WARNING )
188+ logging .getLogger ('PIL.Image' ).setLevel (logging .WARNING )
189+ # otherwise tool loading errors (of there are other xml files that can't be parsed?) are reported
190+ logging .getLogger ('galaxy.tool_util.loader_directory' ).disabled = True
191+ handler = logging .StreamHandler ()
192+ logger .addHandler (handler )
193+ formatter = logging .Formatter ("%(asctime)s - %(name)s - %(levelname)s - %(message)s" )
194+ handler .setFormatter (formatter )
195+
196+ fix_uninstallable (args .lockfile .name , args .toolshed , args .galaxy_url )
0 commit comments