Repository: catkin/catkin_tools

Issue:
ValueError: No closing quotation
[//]: # (Thank you for reporting an issue for catkin_tools!)

[//]: # (Please fill out the details below so that your issue can be resolved quickly!)
[//]: # (If you want to suggest a feature or start a discussion, feel free to delete them.)

### System Info

* Operating System: `uname -a`: 4.15.0-39-generic
* Python Version: `python --version`: Python 2.7.15rc1
* Version of catkin_tools: `catkin --version` OR *Git revision*
```
catkin_tools 0.4.4 (C) 2014-2018 Open Source Robotics Foundation
catkin_tools is released under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
---
Using Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) [GCC 7.3.0]
```
* ROS Distro: `echo $ROS_DISTRO`: melodic
* Shell: zsh 5.4.2 (x86_64-ubuntu-linux-gnu) but the bug appears also in bash

### Build / Run Issue

[//]: # (If you are migrating from catkin_make, please follow the migration guide:)
[//]: # (http://catkin-tools.readthedocs.org/en/latest/migration.html)

[//]: # (Please also check for solved issues here:)
[//]: # (http://catkin-tools.readthedocs.org/en/latest/troubleshooting.html)

[//]: # (And check for open issues here:)
[//]: # (https://github.com/catkin/catkin_tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug)

[//]: # (If you have an issue building and running code, please verify the following:)

* [ ] Works with `catkin_make`
* [ ] Works with `catkin_make_isolated --merge`
* [ ] Works with `catkin build`
* [ ] Works with `catkin build -p1`
* [ ] I did  not `read this`
* [x] Does not work with `catkin config`

### Expected Behavior

[//]: # (Please describe what you expected or think should happen.)

- `catkin config` should show the configuration

### Actual Behavior

[//]: # (Please describe what actually happens.)

- `catkin config` should show the configuration, I have (here shortened)
```
WARNING: Could not parse env string: `export CLUTTER_IM_MODULE=xim
...
export COMP_WORDBREAKS=$' \t\n"\'><;|&(:'
...
`
Traceback (most recent call last):
  File "/usr/bin/catkin", line 9, in <module>
    load_entry_point('catkin-tools==0.4.4', 'console_scripts', 'catkin')()
  File "/usr/lib/python2.7/dist-packages/catkin_tools/commands/catkin.py", line 267, in main
    catkin_main(sysargs)
  File "/usr/lib/python2.7/dist-packages/catkin_tools/commands/catkin.py", line 262, in catkin_main
    sys.exit(args.main(args) or 0)
  File "/usr/lib/python2.7/dist-packages/catkin_tools/verbs/catkin_build/cli.py", line 290, in main
    ctx = Context.load(opts.workspace, opts.profile, opts, append=True)
  File "/usr/lib/python2.7/dist-packages/catkin_tools/context.py", line 175, in load
    ctx.load_env()
  File "/usr/lib/python2.7/dist-packages/catkin_tools/context.py", line 324, in load_env
    sticky_env = get_resultspace_environment(self.devel_space_abs, quiet=True)
  File "/usr/lib/python2.7/dist-packages/catkin_tools/resultspace.py", line 160, in get_resultspace_environment
    for k, v in parse_env_str(lines).items()
  File "/usr/lib/python2.7/dist-packages/catkin_tools/common.py", line 688, in parse_env_str
    split_envs = [e.split('=', 1) for e in cmd_split(environ_str)]
  File "/usr/lib/python2.7/shlex.py", line 279, in split
    return list(lex)
  File "/usr/lib/python2.7/shlex.py", line 269, in next
    token = self.get_token()
  File "/usr/lib/python2.7/shlex.py", line 96, in get_token
    raw = self.read_token()
  File "/usr/lib/python2.7/shlex.py", line 172, in read_token
    raise ValueError, "No closing quotation"
ValueError: No closing quotation
```

[//]: # (Please put long CLI output into Gists: http://gist.github.com)
[//]: # (Use `VERBOSE=1 catkin build -p1 -v` for verbose output)
[//]: # (Please post `logs` directory if available.)


### Steps to Reproduce the Issue

[//]: # (Please give a series of command-line commands which cause the problem.)


[//]: # (Include a checkout URI for the workspace so that we can reproduce the issue.)
[//]: # (If we can't reproduce it on our own machines, we can't fix it!)

```bash
cd my_ros_workspace
export COMP_WORDBREAKS=$' \t\n"\'><;|&(:'
catkin config
```

Bash command `unset COMP_WORDBREAKS` solves the issue.


Discussion:

Patch:
diff --git a/.travis.before_install.bash b/.travis.before_install.bash
index 27eda82..a0ba725 100755
--- a/.travis.before_install.bash
+++ b/.travis.before_install.bash
@@ -6,13 +6,7 @@ if [ "$TRAVIS_OS_NAME" == "linux" ]; then
   sudo apt update
   sudo apt install enchant -y
 elif [ "$TRAVIS_OS_NAME" == "osx" ]; then
-  if [ "$PYTHON" == "/usr/local/bin/python3" ]; then
-    # Brewed Python 3.
-    brew upgrade python
-  elif [ "$PYTHON" == "/usr/local/bin/python2" ]; then
-    # Brewed Python 2.
-    brew install python@2
-  fi
+  brew upgrade python
   $PYTHON -m pip install virtualenv
   $PYTHON -m virtualenv -p $PYTHON venv
   brew install enchant
diff --git a/.travis.yml b/.travis.yml
index 7840181..bcfd6de 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,18 +4,18 @@ matrix:
   include:
     - dist: xenial
       language: python
-      python: "2.7"
+      python: "3.5"
       os: linux
-      env: PYTHON=/usr/bin/python2.7 CATKIN_VERSION=indigo-devel
-    - dist: xenial
+      env: PYTHON=/usr/bin/python3.5 CATKIN_VERSION=indigo-devel
+    - dist: bionic
       language: python
-      python: "3.4"
+      python: "3.6"
       os: linux
-      env: PYTHON=/usr/bin/python3.4 CATKIN_VERSION=indigo-devel
+      env: PYTHON=/usr/bin/python3.6 CATKIN_VERSION=indigo-devel
 before_install:
   # Install catkin_tools dependencies
   - source .travis.before_install.bash
-  - pip install setuptools argparse catkin-pkg PyYAML psutil trollius osrf_pycommon pyenchant "sphinxcontrib-spelling<4.3.0"
+  - pip install setuptools argparse catkin-pkg PyYAML psutil osrf_pycommon pyenchant sphinxcontrib-spelling
 install:
   # Install catkin_tools
   - python setup.py develop
diff --git a/catkin_tools/common.py b/catkin_tools/common.py
index b04b04e..62d0d9f 100644
--- a/catkin_tools/common.py
+++ b/catkin_tools/common.py
@@ -20,14 +20,14 @@ import os
 import re
 import sys
 
-import trollius as asyncio
+import asyncio
 
 from shlex import split as _cmd_split
 try:
     _cmd_split(u'\u00E9')
 
-    def cmd_split(s):
-        return _cmd_split(s.decode('utf-8'))
+    def cmd_split(s, *args, **kwargs):
+        return _cmd_split(s.decode('utf-8'), *args, **kwargs)
 except UnicodeEncodeError:
     cmd_split = _cmd_split
 
@@ -63,7 +63,7 @@ class FakeLock(asyncio.locks.Lock):
 
     @asyncio.coroutine
     def acquire(self):
-        raise asyncio.Return(True)
+        return(True)
 
     def release(self):
         pass
@@ -687,7 +687,9 @@ def parse_env_str(environ_str):
     """
 
     try:
-        split_envs = [e.split('=', 1) for e in cmd_split(environ_str)]
+        # Use cmd_split with posix=False because posix mode is problematic when an
+        # environment variable is set to specific bash dollar-sign strings (e.g. $'\'\n')
+        split_envs = [e.split('=', 1) for e in cmd_split(environ_str, posix=False)]
         return {
             e[0]: e[1] for e
             in split_envs
diff --git a/catkin_tools/execution/executor.py b/catkin_tools/execution/executor.py
index db04773..b3ad2a8 100644
--- a/catkin_tools/execution/executor.py
+++ b/catkin_tools/execution/executor.py
@@ -18,7 +18,7 @@ import traceback
 
 from itertools import tee
 
-import trollius as asyncio
+import asyncio
 
 from concurrent.futures import ThreadPoolExecutor
 from concurrent.futures import FIRST_COMPLETED
@@ -70,7 +70,7 @@ def async_job(verb, job, threadpool, locks, event_queue, log_path):
         # Check for stage synchronization lock
         if stage.locked_resource is not None:
             lock = locks.setdefault(stage.locked_resource, asyncio.Lock())
-            yield asyncio.From(lock)
+            yield from(lock)
         else:
             lock = FakeLock()
 
@@ -79,7 +79,7 @@ def async_job(verb, job, threadpool, locks, event_queue, log_path):
             if stage.occupy_job:
                 if not occupying_job:
                     while job_server.try_acquire() is None:
-                        yield asyncio.From(asyncio.sleep(0.05))
+                        yield from(asyncio.sleep(0.05))
                     occupying_job = True
             else:
                 if occupying_job:
@@ -103,7 +103,7 @@ def async_job(verb, job, threadpool, locks, event_queue, log_path):
                             # Get the logger
                             protocol_type = stage.logger_factory(verb, job.jid, stage.label, event_queue, log_path)
                             # Start asynchroonous execution
-                            transport, logger = yield asyncio.From(
+                            transport, logger = yield from(
                                 async_execute_process(
                                     protocol_type,
                                     **stage.async_execute_process_kwargs))
@@ -112,7 +112,7 @@ def async_job(verb, job, threadpool, locks, event_queue, log_path):
                             if 'Text file busy' in str(exc):
                                 # This is a transient error, try again shortly
                                 # TODO: report the file causing the problem (exc.filename)
-                                yield asyncio.From(asyncio.sleep(0.01))
+                                yield from(asyncio.sleep(0.01))
                                 continue
                             raise
 
@@ -125,7 +125,7 @@ def async_job(verb, job, threadpool, locks, event_queue, log_path):
                         **stage.async_execute_process_kwargs))
 
                     # Asynchronously yield until this command is completed
-                    retcode = yield asyncio.From(logger.complete)
+                    retcode = yield from(logger.complete)
                 except:  # noqa: E722
                     # Bare except is permissable here because the set of errors which the CommandState might raise
                     # is unbounded. We capture the traceback here and save it to the build's log files.
@@ -137,7 +137,7 @@ def async_job(verb, job, threadpool, locks, event_queue, log_path):
                 logger = IOBufferLogger(verb, job.jid, stage.label, event_queue, log_path)
                 try:
                     # Asynchronously yield until this function is completed
-                    retcode = yield asyncio.From(get_loop().run_in_executor(
+                    retcode = yield from(get_loop().run_in_executor(
                         threadpool,
                         stage.function,
                         logger,
@@ -179,7 +179,7 @@ def async_job(verb, job, threadpool, locks, event_queue, log_path):
             lock.release()
 
     # Finally, return whether all stages of the job completed
-    raise asyncio.Return(job.jid, all_stages_succeeded)
+    return(job.jid, all_stages_succeeded)
 
 
 @asyncio.coroutine
@@ -272,14 +272,14 @@ def execute_jobs(
         ))
 
         # Process jobs as they complete asynchronously
-        done_job_fs, active_job_fs = yield asyncio.From(asyncio.wait(
+        done_job_fs, active_job_fs = yield from(asyncio.wait(
             active_job_fs,
             timeout=0.10,
             return_when=FIRST_COMPLETED))
 
         for done_job_f in done_job_fs:
             # Capture a result once the job has finished
-            job_id, succeeded = yield asyncio.From(done_job_f)
+            job_id, succeeded = yield from(done_job_f)
 
             # Release a jobserver token now that this job has succeeded
             job_server.release(job_id)
@@ -362,7 +362,7 @@ def execute_jobs(
         completed=completed_jobs
     ))
 
-    raise asyncio.Return(all(completed_jobs.values()))
+    return(all(completed_jobs.values()))
 
 
 def run_until_complete(coroutine):
diff --git a/catkin_tools/verbs/catkin_build/build.py b/catkin_tools/verbs/catkin_build/build.py
index 5ed56ee..eb64988 100644
--- a/catkin_tools/verbs/catkin_build/build.py
+++ b/catkin_tools/verbs/catkin_build/build.py
@@ -22,7 +22,7 @@ import time
 import traceback
 import yaml
 
-import trollius as asyncio
+import asyncio
 
 try:
     # Python3
diff --git a/catkin_tools/verbs/catkin_profile/cli.py b/catkin_tools/verbs/catkin_profile/cli.py
index a24a887..79b14d6 100644
--- a/catkin_tools/verbs/catkin_profile/cli.py
+++ b/catkin_tools/verbs/catkin_profile/cli.py
@@ -113,7 +113,7 @@ def list_profiles(profiles, active_profile, unformatted=False, active=False):
 def main(opts):
     try:
         # Load a context with initialization
-        ctx = Context.load(opts.workspace)
+        ctx = Context.load(opts.workspace, load_env=False)
 
         if not ctx.initialized():
             print("A catkin workspace must be initialized before profiles can be managed.")
diff --git a/requirements.txt b/requirements.txt
index 184fceb..06ceec8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,3 @@ osrf_pycommon
 pyyaml
 setuptools
 sphinxcontrib-programoutput
-trollius
diff --git a/setup.py b/setup.py
index 03b728d..0d640a9 100644
--- a/setup.py
+++ b/setup.py
@@ -15,10 +15,7 @@ install_requires = [
     'setuptools',
     'PyYAML',
     'osrf-pycommon > 0.1.1',
-    'trollius'
 ]
-if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
-    install_requires.append('argparse')
 
 # Figure out the resources that need to be installed
 this_dir = os.path.abspath(os.path.dirname(__file__))
@@ -117,6 +114,7 @@ else:
 setup(
     name='catkin_tools',
     version='0.4.5',
+    python_requires='>=3.5',
     packages=find_packages(exclude=['tests*', 'docs']),
     package_data={
         'catkin_tools': [


Test patch:
diff --git a/tests/README.md b/tests/README.md
index af24a4b..242adfb 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -14,7 +14,7 @@ All tests can be run from the root of the repository.
 
 First, make sure the required test dependencies are installed:
 
-*Ubuntu* -- `sudo apt-get install cmake libgtest-dev build-essential python-setuptools python-pip`
+*Ubuntu* -- `sudo apt-get install cmake libgtest-dev build-essential python3-setuptools python3-pip`
 
 *OS X* -- `pip install setuptools`
 
@@ -22,7 +22,7 @@ First, make sure the required test dependencies are installed:
 
 ```
 pip install argparse catkin-pkg distribute PyYAML psutil
-pip install nose coverage flake8 mock trollius empy --upgrade
+pip install nose coverage flake8 mock empy --upgrade
 pip install git+https://github.com/osrf/osrf_pycommon.git
 ```