Fix fpm description by wrapping in shell quote#2124
Fix fpm description by wrapping in shell quote#2124boegel merged 13 commits intoeasybuilders:developfrom
Conversation
|
This will resolve #2014 |
| '--version', pkgver, | ||
| '--iteration', pkgrel, | ||
| '--description', quote_str(easyblock.cfg["description"]), | ||
| '--description', quote_str(eb_shell_quote(easyblock.cfg["description"])), |
There was a problem hiding this comment.
@rjeschmi do we need both quote_str and eb_shell_quote?
There was a problem hiding this comment.
I'll check. I thought the quote_str would handle the outside wrapping, but it probably doesn't work the way I think it does.
The tests should help me figure it out I'd think.
There was a problem hiding this comment.
You can see now that I've removed both quotes. If you pass the commands as a list it will handle doing the escaping properly.
It is necessary to pass each separate part of the command line as its own list entry though (so I had to also change the way I was handling the excludes and such).
I think I'm missing a test case...
|
well the test is failing nicely, but it seems like both escapes are not enough :) |
| """ | ||
| # escape any non-escaped single quotes, and wrap entire token in single quotes | ||
| return "'%s'" % re.sub(r"(?<!\\)'", r"\'", str(token)) | ||
| return "'%s'" % re.sub(r"(?<!\\)'", r"'\''", str(token)) |
There was a problem hiding this comment.
I think this is "more correct" but I'm no longer using this command so I could remove this from this PR.
There was a problem hiding this comment.
if this is not relevant to this PR, please roll back (and maybe open a new PR for it)
|
@rjeschmi I plan to do a v3.1.1 bugfix release this week, any idea how close this is to being good to go? |
|
I don't think it is too far. I'll maybe remove the changes to eb_shell_quote? |
|
and you wanted me to write command lists to tuples right? |
| """ | ||
| # escape any non-escaped single quotes, and wrap entire token in single quotes | ||
| return "'%s'" % re.sub(r"(?<!\\)'", r"\'", str(token)) | ||
| return "'%s'" % re.sub(r"(?<!\\)'", r"'\''", str(token)) |
There was a problem hiding this comment.
if this is not relevant to this PR, please roll back (and maybe open a new PR for it)
| This function will build a package using fpm and return the directory where the packages are | ||
| """ | ||
| # avoid circular import, should move eb_shell_quote | ||
| from easybuild.tools.options import eb_shell_quote |
There was a problem hiding this comment.
no longer used, so please remove?
| _log.debug("The list of excluded files passed to fpm: %s", exclude_files_glob) | ||
| # use env to find FPM https://stackoverflow.com/questions/5658622/python-subprocess-popen-environment-path | ||
| cmdlist = [ | ||
| '/usr/bin/env', |
There was a problem hiding this comment.
this should be done by run_cmd in case shell=False is used?
There was a problem hiding this comment.
possibly, but do you think shell=false will rely on path in all cases?
| if build_option('debug'): | ||
| cmdlist.append('--debug') | ||
|
|
||
| cmdlist.extend(deplist) |
There was a problem hiding this comment.
should we move down the definition of deplist here, to keep things together?
| easyblock.module_generator.get_module_filepath(), | ||
| ]) | ||
| cmd = ' '.join(cmdlist) | ||
| print "cmd: %s" % cmdlist |
There was a problem hiding this comment.
please remove, or add to debug log statement below
| print "cmd: %s" % cmdlist | ||
| _log.debug("The flattened cmdlist looks like: %s", cmd) | ||
| run_cmd(cmd, log_all=True, simple=True) | ||
| run_cmd(cmdlist, log_all=True, simple=True, cache=False, shell=False) |
There was a problem hiding this comment.
it's not immediately clear to me why you are disabling caching here?
There was a problem hiding this comment.
the cache doesn't work (in that it tries to hash a list, I will try to fix that, but this command is also not cacheable).
| versionsuffix = '-test' | ||
|
|
||
| homepage = 'http://hpcugent.github.com/easybuild' | ||
| description = "Toy C program. Now with `backticks'" |
There was a problem hiding this comment.
rather than adding a new easyconfig, can we just use toy-0.0.eb, read it, do:
toytxt = re.sub('description = .*', 'description = "Toy C program. Now with `backticks'", toytxt)and then write it to a temporary file to use for the test?
|
|
||
| ec_desc = EasyConfig( | ||
| os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0-gompi-1.3.12-test-description.eb'), | ||
| validate=False) |
There was a problem hiding this comment.
do a hard check on the description with the backtick in it, to make it clear why this is an important test case
| validate=False) | ||
| easyblock_desc = EB_toy(ec_desc) | ||
| easyblock_desc.run_all_steps(False) | ||
| pkgdir = package(easyblock_desc) |
There was a problem hiding this comment.
also check that the package is really there?
|
I think this resolves the review and it passed package test locally. |
|
|
||
| _log.debug("Got the PNS values name: %s version: %s release: %s", pkgname, pkgver, pkgrel) | ||
| cmdlist = [ | ||
| '/usr/bin/env', |
| _log.debug("The dep added looks like %s ", dep) | ||
| dep_pkgname = package_naming_scheme.name(dep) | ||
| depstring += " --depends %s" % quote_str(dep_pkgname) | ||
| deplist.extend(["--depends", dep_pkgname]) |
There was a problem hiding this comment.
why not just extend cmdlist?
also, the extend(deplist) below should be right below this for loop, having something unrelated in between is confusing (although it doesn't matter anymore if you just extend to cmdlist straight away)
There was a problem hiding this comment.
It just kept it more like it was, but sure I can do it the other way now too since I have the cmdlist moved up.
| if build_option('debug'): | ||
| cmdlist.append('--debug') | ||
| _log.debug("The list of excluded files passed to fpm: %s", exclude_files_glob) | ||
| # use env to find FPM https://stackoverflow.com/questions/5658622/python-subprocess-popen-environment-path |
| cmdlist.extend(exclude_files_glob) | ||
| exclude_files_cmd = [] | ||
| for x in exclude_files_glob: | ||
| exclude_files_cmd.extend(['--exclude', os.path.join(easyblock.installdir.lstrip(os.sep), x)]) |
There was a problem hiding this comment.
same here, just use cmdlist.extend?
| if cmd in cache: | ||
| _log.debug("Using cached value for command '%s': %s", cmd, cache[cmd]) | ||
| return cache[cmd] | ||
| # fetch from cache if available, cache it if it's not, but only on cmd strings |
There was a problem hiding this comment.
why only on strings?
if they're a list, you can just use tuple(cmd) as cache key?
There was a problem hiding this comment.
You only cache based on strings. It seems safer to harden the code for what is expected rather than trying to handle all sorts of things that aren't necessary (including lists since none of the cached commands are lists at the moment and it doesn't work).
There was a problem hiding this comment.
This one not done. Rest are done.
There was a problem hiding this comment.
Since we're only using strings in CACHED_COMMANDS, why do we need to check the type of cmd at all?
Since we never cache a non-string cmd right now, cmd in cache will always be False, and so we'll always call out to the actual func?
This looks like a case of premature optimisation here, especially for a command that's executed very rarely...
There was a problem hiding this comment.
OK, ignore my rant, the cmd in cache will fail if cmd is of type list:
>>> [] in {}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
| _log.info('running cmd: %s ' % cmd) | ||
| try: | ||
| p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, | ||
| p = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, |
There was a problem hiding this comment.
prefix cmd with /usr/bin/env if shell is False (and take into account that cmd may be either a string or a list/tuple)
There was a problem hiding this comment.
ok, I worry that it will have side effects, but it should be backwards compatible anyway.
| self.assertTrue(os.path.isfile(pkgfile)) | ||
| pkgtxt = read_file(pkgfile) | ||
| regex_pkg_regex = re.compile(r"""DESCRIPTION:.*`backticks'.*""") | ||
| self.assertTrue(regex_pkg_regex.search(pkgtxt)) |
There was a problem hiding this comment.
self.assertTrue(regex_pkg_regex.search(pkgtxt), "Pattern '%s' not found in: %s" % (regex.pattern, pkgtxt))|
I've handled the rest of the comments save the one about checking for string in the cache code. All of the commands in CACHED_COMMANDS are strings. If it is really a useful feature to allow it I think you should rethink how cached commands are handled. Right now it works and is better performing since it doesn't uselessly convert the command list to a tuple to check against a constant list of strings. |
| from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME | ||
| from easybuild.tools.utilities import import_available_modules, quote_str | ||
|
|
||
| from subprocess import list2cmdline |
There was a problem hiding this comment.
yeah sorry I was using it to debug, should have checked pylint.
There was a problem hiding this comment.
I actually removed quote_str as well since I no longer use it with the cmd list version.
| if cmd in cache: | ||
| _log.debug("Using cached value for command '%s': %s", cmd, cache[cmd]) | ||
| return cache[cmd] | ||
| # fetch from cache if available, cache it if it's not, but only on cmd strings |
There was a problem hiding this comment.
Since we're only using strings in CACHED_COMMANDS, why do we need to check the type of cmd at all?
Since we never cache a non-string cmd right now, cmd in cache will always be False, and so we'll always call out to the actual func?
This looks like a case of premature optimisation here, especially for a command that's executed very rarely...
| if not shell and isinstance(cmd, list): | ||
| cmd.insert(0, '/usr/bin/env') | ||
| elif not shell and isinstance(cmd, str): | ||
| cmd = '/usr/bin/env %s' % cmd |
There was a problem hiding this comment.
# run command via 'env' if it is not run in a shell to trigger $PATH lookup of used command;
# cfr. <include-missing-reference>
if not shell:
if isinstance(cmd, list):
cmd.insert(0, '/usr/bin/env')
elif isinstance(cmd, basestring):
cmd = '/usr/bin/env ' + cmd
else:
raise EasyBuildError("Don't know how to prefix with /usr/bin/env for commands of type %s", type(cmd))| return cache[cmd] | ||
| # fetch from cache if available, cache it if it's not, but only on cmd strings | ||
| if isinstance(cmd, str): | ||
| if cmd in cache: |
There was a problem hiding this comment.
let's chain these together:
if isinstance(cmd, basestring) and cmd in cache:There was a problem hiding this comment.
you want me to prefix the next conditional with isinstance as well? Just seems harder to read...
There was a problem hiding this comment.
it'll avoid the indent and the extra else at the end which is just a trimmed-down version of the inner else
There was a problem hiding this comment.
ok, thanks for the explanation on chat. I have it fixed now.
|
I think I got them all this time. |
| "debug": ('', 'on')[DEBUG], | ||
| "debug_fpm_file": os.path.join(tmpdir, DEBUG_FPM_FILE)} | ||
| ) | ||
| "debug_fpm_file": os.path.join(tmpdir, DEBUG_FPM_FILE)}) |
There was a problem hiding this comment.
put }) on new line instead, indenting like ) was before
| print read_file(pkgfile) | ||
|
|
||
| toy_txt = read_file(os.path.join(test_easyconfigs, 't', 'toy', 'toy-0.0-gompi-1.3.12-test.eb')) | ||
| replace_str = '''description = """Toy C program. Now with `backticks' ''' "\n" '''and newlines"""''' |
There was a problem hiding this comment.
since you're using triple quotes, you can just insert a hard newline, but then it looks icky due to left indent
you can also:
replace_str = '''description = """Toy C program. Now with `backticks'\n''''
replace_str += '''and newlines"""'''There was a problem hiding this comment.
this is a syntax error. I assume you mean one less '
There was a problem hiding this comment.
yes, first line has an extra trailing '... I didn't say I tested this ;)
| "Pattern '%s' found in: %s" % (desc_backticks_regex.pattern, toy_txt)) | ||
| ec_desc = EasyConfig( | ||
| toy_file, | ||
| validate=False) |
There was a problem hiding this comment.
looks a bit funky here, not the usual code style
how about:
regex = re.compile(r"""`backticks'""")
self.assertTrue(regex.search(toy_txt), "Pattern '%s' found in: %s" % (regex.pattern, toy_txt))
ec_desc = EasyConfig(toy_file, validate=False)|
Going in, thanks for the effort @rjeschmi! |
Still need to write the test.