-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add autoupdate command #1065
Merged
Merged
Add autoupdate command #1065
Changes from 11 commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
dc1b335
First Draft for autoupdate command
lorrainealisha75 c42db57
Putting files in the right directories
lorrainealisha75 dcf206a
Making minor changes
lorrainealisha75 81a8a83
Basic functionalityof autoupdate check-in
lorrainealisha75 85df40e
Minor changens in autoupdate file
lorrainealisha75 2b09418
modify autoupdate so that tokens can be updated correctly
simonbray 3988282
working version of autoupdate
simonbray efc78ee
next commit
simonbray 0b89771
Merge remote-tracking branch 'upstream/master' into autoupdate-sb
simonbray ce54d0f
next commit
simonbray f7dc089
minor change to logging
simonbray cd139a6
add conda flags
simonbray b572fee
linting
simonbray f3160e3
small changes to docs
simonbray b52825d
add initial draft of some documentation
simonbray 3ba0775
some fixes
simonbray 6536192
rewrite code
simonbray 6b44dd8
commit
simonbray c5e6d5b
autoupdate
simonbray a1a1e3e
autoupdate
simonbray 2cb8262
do not add +galaxy0 where not already used, requested by @wm75
simonbray 6bd3a8a
restructure autoupdate code, small fixes
simonbray 6185f44
add 2 test cases (w and w/o --dry-run)
simonbray b479570
docs linting
simonbray c3b4c0e
autoupdate test passing locally, try and get it to run on the CI as well
simonbray 739f44c
[ci skip] add skiplist option as suggested by @bgruening
simonbray f27e282
another attempt at fixing the test
simonbray 940b577
create xml file for autoupdate test during the test
simonbray f5c4711
Merge remote-tracking branch 'upstream/master' into autoupdate-sb
simonbray 08e5988
change tests to check stdout
simonbray 36ac709
ensure conda is installed
simonbray f13cce1
lint
simonbray c5af2af
(re)add docs
simonbray ed5fcc4
Merge remote-tracking branch 'upstream/master' into autoupdate-sb
simonbray 8c1382f
do not modify os-dependent newlines when using autoupdate
simonbray 16e6857
add skip_requirements option to autoupdate
simonbray 5b582b5
fix bug introduced in previous commit
simonbray a5be98f
add link to autoupdate ci, fix update_test_data
simonbray bc3ed0c
add default to skip_requirements arg
simonbray 3420eae
fix for testing
simonbray 599cb59
fix autoupdate skip requirements, update docs, some general tidying
simonbray cbbbc43
update docs with @VERSION_SUFFIX@
simonbray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
"""Autoupdate older conda dependencies in the requirements section.""" | ||
from __future__ import absolute_import | ||
|
||
import re | ||
import xml.etree.ElementTree as ET | ||
import planemo.conda | ||
|
||
from planemo.io import error, info | ||
|
||
def autoupdate(tool_path, dry_run=False): | ||
""" | ||
Autoupdate an XML file | ||
""" | ||
xml_tree = ET.parse(tool_path) | ||
requirements = find_requirements(xml_tree) | ||
|
||
for macro_import in requirements['imports']: | ||
# recursively check macros | ||
macro_requirements = autoupdate('/'.join(tool_path.split('/')[:-1] + [macro_import]), dry_run) | ||
for requirement in macro_requirements: | ||
if requirement not in requirements: | ||
requirements[requirement] = macro_requirements[requirement] | ||
|
||
if not requirements.get('@TOOL_VERSION@'): | ||
# if tool_version is not specified, finish without changes | ||
error("The @TOOL_VERSION@ token is not specified in {}. This is required for autoupdating.".format(tool_path)) | ||
return requirements | ||
updated_main_req = get_latest_versions({requirements.get('main_req'): requirements.get('@TOOL_VERSION@')}) | ||
if updated_main_req[requirements.get('main_req')] == requirements.get('@TOOL_VERSION@'): | ||
# check main_req is up-to-date; if so, finish without changes | ||
info("No updates required or madeto {}.".format(tool_path)) | ||
return requirements | ||
|
||
if dry_run: | ||
error("Update required to {}! Tool main requirement has version {}, newest conda version is {}".format(tool_path, requirements.get('@TOOL_VERSION@'), updated_main_req[requirements.get('main_req')])) | ||
return requirements | ||
|
||
# if main_req is not up-to-date, update everything | ||
updated_version_dict = get_latest_versions(requirements.get('other_reqs')) | ||
update_requirements(tool_path, xml_tree, updated_version_dict, updated_main_req) | ||
info("Tool {} updated.".format(tool_path)) | ||
return requirements | ||
|
||
def find_requirements(xml_tree): | ||
""" | ||
Get all requirements with versions as a dictionary of the form | ||
{'main_req': main requirement, 'other_reqs': {req1: version, ... }, | ||
'imports: ['macros.xml'], '*VERSION@': '...'} | ||
""" | ||
requirements = {'other_reqs': {}, 'imports': []} | ||
|
||
# get tokens | ||
for token in xml_tree.iter("token"): | ||
if 'VERSION@' in token.attrib.get('name', ''): | ||
requirements[token.attrib['name']] = token.text | ||
for macro_import in xml_tree.iter("import"): | ||
requirements['imports'].append(macro_import.text) | ||
|
||
# get requirements | ||
for requirement in xml_tree.iter("requirement"): | ||
if requirement.attrib.get('version') == '@TOOL_VERSION@': | ||
requirements['main_req'] = requirement.text | ||
else: | ||
requirements['other_reqs'][requirement.text] = requirement.attrib.get('version') | ||
return requirements | ||
|
||
|
||
def get_latest_versions(version_dict): | ||
""" | ||
Update a dict with current conda versions for tool requirements | ||
""" | ||
for package in version_dict.keys(): | ||
target = planemo.conda.conda_util.CondaTarget(package) | ||
search_results = planemo.conda.best_practice_search(target) | ||
version_dict[package] = search_results[0]['version'] | ||
return version_dict | ||
|
||
|
||
def update_requirements(tool_path, xml_tree, updated_version_dict, updated_main_req): | ||
""" | ||
Update requirements to latest conda versions | ||
and update version tokens | ||
""" | ||
|
||
tags_to_update = {'tokens': [], 'requirements': []} | ||
|
||
for token in xml_tree.iter("token"): | ||
if token.attrib.get('name') == '@TOOL_VERSION@': | ||
# check this | ||
token.text = list(updated_main_req.values())[0] | ||
elif token.attrib.get('name') == '@GALAXY_VERSION@': | ||
token.text = '0' | ||
else: | ||
continue | ||
tags_to_update['tokens'].append(ET.tostring(token, encoding="unicode").strip()) | ||
|
||
if '@GALAXY_VERSION@' not in [n.attrib.get('name') for n in xml_tree.iter('token')]: | ||
tags_to_update['update_tool'] = True | ||
|
||
for requirement in xml_tree.iter("requirement"): | ||
if requirement.text not in updated_main_req: | ||
requirement.set('version', updated_version_dict[requirement.text]) | ||
tags_to_update['requirements'].append(ET.tostring(requirement, encoding="unicode").strip()) | ||
write_to_xml(tool_path, xml_tree, tags_to_update) | ||
return xml_tree | ||
|
||
|
||
def write_to_xml(tool_path, xml_tree, tags_to_update): | ||
""" | ||
Write modified XML to tool_path | ||
""" | ||
with open(tool_path, 'r+') as f: | ||
xml_text = f.read() | ||
for token in tags_to_update['tokens']: | ||
xml_text = re.sub('{}>.*<{}'.format(*re.split('>.*<', token)), token, xml_text) | ||
|
||
for requirement in tags_to_update['requirements']: | ||
xml_text = re.sub('{}version=".*"{}'.format(*re.split('version=".*"', requirement)), requirement, xml_text) | ||
|
||
# if '@GALAXY_VERSION@' not in tags_to_update['tokens']: | ||
if tags_to_update.get('update_tool'): | ||
# update the version directly in the tool tag | ||
xml_text = re.sub('version="@TOOL_VERSION@\+galaxy.*"', 'version="@TOOL_VERSION@+galaxy0"', xml_text) | ||
|
||
f.seek(0) | ||
f.truncate() | ||
f.write(xml_text) | ||
|
||
__all__ = ( | ||
"autoupdate" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
"""Module describing the planemo ``autoupdate`` command.""" | ||
import click | ||
|
||
from planemo.exit_codes import ( | ||
EXIT_CODE_GENERIC_FAILURE, | ||
EXIT_CODE_OK | ||
) | ||
from planemo.io import ( | ||
coalesce_return_codes, | ||
info | ||
) | ||
from planemo.tools import ( | ||
is_tool_load_error, | ||
yield_tool_sources_on_paths | ||
) | ||
|
||
from planemo import options, autoupdate | ||
from planemo.cli import command_function | ||
from planemo.config import planemo_option | ||
|
||
|
||
def dry_run_option(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if this should be moved to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is totally fine here, especially if only one command is using it. |
||
"""Perform a dry run autoupdate without modifying the XML files""" | ||
return planemo_option( | ||
"--dry-run", | ||
is_flag=True, | ||
help="Perform a dry run autoupdate without modifying the XML files." | ||
) | ||
|
||
@click.command('autoupdate') | ||
@options.optional_tools_arg(multiple=True) | ||
@options.report_level_option() | ||
@options.report_xunit() | ||
@options.fail_level_option() | ||
@options.skip_option() | ||
@options.recursive_option() | ||
@dry_run_option() | ||
@command_function | ||
def cli(ctx, paths, **kwds): | ||
"""Auto-update requirements section if necessary""" | ||
assert_tools = kwds.get("assert_tools", True) | ||
recursive = kwds.get("recursive", False) | ||
exit_codes = [] | ||
for (tool_path, tool_xml) in yield_tool_sources_on_paths(ctx, paths, recursive): | ||
info("Auto-updating tool %s" % tool_path) | ||
tool_xml = autoupdate.autoupdate(tool_path, kwds['dry_run']) | ||
if handle_tool_load_error(tool_path, tool_xml): | ||
exit_codes.append(EXIT_CODE_GENERIC_FAILURE) | ||
continue | ||
else: | ||
exit_codes.append(EXIT_CODE_OK) | ||
return coalesce_return_codes(exit_codes, assert_at_least_one=assert_tools) | ||
ctx.exit() | ||
|
||
|
||
def handle_tool_load_error(tool_path, tool_xml): | ||
""" Return True if tool_xml is tool load error (invalid XML), and | ||
print a helpful error message. | ||
""" | ||
is_error = False | ||
if is_tool_load_error(tool_xml): | ||
info("Could not update %s due to malformed xml." % tool_path) | ||
is_error = True | ||
return is_error |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty awesome!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @jmchilton - I think this is ready for a review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I take that back, there are still some things I can fix. Though any overall comments about the implementation would be welcome.