Skip to content

Commit

Permalink
{catkin,python} plugin: support cleaning (#2171)
Browse files Browse the repository at this point in the history
Both the Catkin and Python plugins utilize the Pip class in order to
clean their pull steps (specifically, they tell the Pip class to clean
its packages). However, when cleaning the pull step, Python has already
been removed from the installdir, which means the Pip class cannot be
instantiated, causing an error about being unable to find Python.

It's reasonable to ask Pip to clean any fetched packages, and doing so
doesn't require Python. Fix this issue by lazily looking for Python only
when it's required instead of upon class instantiation.

LP: #1778716

Signed-off-by: Kyle Fazzari <kyrofa@ubuntu.com>
  • Loading branch information
Kyle Fazzari authored and sergiusens committed Jun 27, 2018
1 parent 208a767 commit a54a7a4
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 13 deletions.
36 changes: 24 additions & 12 deletions snapcraft/plugins/_python/_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,32 @@ def __init__(self, *, python_major_version, part_dir, install_dir,
staging or part's install area.
"""
self._python_major_version = python_major_version
self._part_dir = part_dir
self._install_dir = install_dir
self._stage_dir = stage_dir

self._python_package_dir = os.path.join(
self._part_dir, 'python-packages')
self._python_package_dir = os.path.join(part_dir, 'python-packages')
os.makedirs(self._python_package_dir, exist_ok=True)

self._python_command = get_python_command(
self._python_major_version, stage_dir=self._stage_dir,
install_dir=self._install_dir)
self._python_home = get_python_home(
self._python_major_version, stage_dir=self._stage_dir,
install_dir=self._install_dir)
self.__python_command = None # type:str
self.__python_home = None # type: str

@property
def _python_command(self):
"""Lazily determine the python command required."""
if not self.__python_command:
self.__python_command = get_python_command(
self._python_major_version, stage_dir=self._stage_dir,
install_dir=self._install_dir)
return self.__python_command

@property
def _python_home(self):
"""Lazily determine the correct python home."""
if not self.__python_home:
self.__python_home = get_python_home(
self._python_major_version, stage_dir=self._stage_dir,
install_dir=self._install_dir)
return self.__python_home

def setup(self):
"""Install pip and dependencies.
Expand All @@ -159,20 +171,20 @@ def _ensure_pip_installed(self):
if not self._is_pip_installed():
logger.info('Fetching and installing pip...')

real_python_home = self._python_home
real_python_home = self.__python_home

# Make sure we're using pip from the host. Wrapping this operation
# in a try/finally to make sure we revert away from the host's
# python at the end.
try:
self._python_home = os.path.join(os.path.sep, 'usr')
self.__python_home = os.path.join(os.path.sep, 'usr')

# Using the host's pip, install our own pip
self.download({'pip'})
self.install({'pip'}, ignore_installed=True)
finally:
# Now that we have our own pip, reset the python home
self._python_home = real_python_home
self.__python_home = real_python_home

def _ensure_wheel_installed(self):
if not self._is_wheel_installed():
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/plugins/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def __init__(self, name, options, project):
# Pip requires only the major version of python rather than the command
# name like our option requires.
match = re.match(
'python(?P<major_version>\d).*', self.options.python_version)
r'python(?P<major_version>\d).*', self.options.python_version)
if not match:
raise UnsupportedPythonVersionError(
python_version=self.options.python_version)
Expand Down
22 changes: 22 additions & 0 deletions tests/integration/plugins_python/test_python_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,25 @@ def test_plugin_plays_nice_with_stage_packages(self):
self.run_snapcraft('stage')
except subprocess.CalledProcessError:
self.fail('These parts should not have conflicted')

def test_python_part_can_be_cleaned(self):
# Regression test for LP: #1778716

snapcraft_yaml = fixture_setup.SnapcraftYaml(self.path)
snapcraft_yaml.update_part('python-part', {
'plugin': 'python',
'source': '.',
})
snapcraft_yaml.update_part('dummy-part', {
'plugin': 'nil',
})
self.useFixture(snapcraft_yaml)

# First stage both parts
self.run_snapcraft('stage')

# Now clean the python one, which should succeed
try:
self.run_snapcraft(['clean', 'python-part'])
except subprocess.CalledProcessError:
self.fail('Expected python part to clean successfully')

0 comments on commit a54a7a4

Please sign in to comment.