diff -Nru ubuntu-release-upgrader-18.04.40/debian/changelog ubuntu-release-upgrader-18.04.41/debian/changelog --- ubuntu-release-upgrader-18.04.40/debian/changelog 2020-10-05 13:50:15.000000000 -0700 +++ ubuntu-release-upgrader-18.04.41/debian/changelog 2020-10-26 10:23:33.000000000 -0700 @@ -1,3 +1,12 @@ +ubuntu-release-upgrader (1:18.04.41) bionic; urgency=medium + + [ Kyle Fazzari ] + * DistUpgrade/DistUpgradeQuirks.py: Add a check for ROS packages being + installed and warn that upgrades with them installed are not likely to + work. (LP: #1611737) + + -- Kyle Fazzari Mon, 26 Oct 2020 10:23:33 -0700 + ubuntu-release-upgrader (1:18.04.40) bionic; urgency=medium [ Chad Smith ] diff -Nru ubuntu-release-upgrader-18.04.40/DistUpgrade/DistUpgradeQuirks.py ubuntu-release-upgrader-18.04.41/DistUpgrade/DistUpgradeQuirks.py --- ubuntu-release-upgrader-18.04.40/DistUpgrade/DistUpgradeQuirks.py 2019-06-19 09:49:56.000000000 -0700 +++ ubuntu-release-upgrader-18.04.41/DistUpgrade/DistUpgradeQuirks.py 2020-10-26 10:23:33.000000000 -0700 @@ -130,7 +130,10 @@ # PreCacheOpen would be better but controller.abort fails terribly """ run after the apt cache is opened the first time """ logging.debug("running Quirks.bionicPostInitialUpdate") + cache = self.controller.cache + self._test_and_warn_if_ros_installed(cache) + if 'ubuntu-desktop' not in cache or \ 'snapd' not in cache: logging.debug("package required for Quirk not in cache") @@ -367,6 +370,53 @@ self.controller.abort() self._view.processEvents() + def _test_and_warn_if_ros_installed(self, cache): + """ + Test and warn if ROS is installed. A given ROS release only + supports specific Ubuntu releases, and can cause the upgrade + to fail in an overly-cryptic manner. + """ + + # These are the root ROS 1 and 2 dependencies as of 07/27/2020 + ros_package_patterns = set() + for package_name in ( + "catkin", + "rosboost-cfg", + "rosclean", + "ros-environment", + "ros-workspace"): + ros_package_patterns.add( + re.compile("ros-[^\-]+-%s" % package_name)) + + ros_is_installed = False + for pkg in cache: + if ros_is_installed: + break + + for pattern in ros_package_patterns: + if pattern.match(pkg.name): + if pkg.is_installed or pkg.marked_install: + ros_is_installed = True + break + + if ros_is_installed: + res = self._view.askYesNoQuestion( + _("The Robot Operating System (ROS) is installed"), + _("It appears that ROS is currently installed. Each ROS " + "release is very strict about the versions of Ubuntu " + "it supports, and Ubuntu upgrades can fail if that " + "guidance isn't followed. Before continuing, please " + "either uninstall ROS, or ensure the ROS release you " + "have installed supports the version of Ubuntu to " + "which you're upgrading.\n\n" + "For ROS 1 releases, refer to REP 3:\n" + "https://www.ros.org/reps/rep-0003.html\n\n" + "For ROS 2 releases, refer to REP 2000:\n" + "https://www.ros.org/reps/rep-2000.html\n\n" + "Are you sure you want to continue?")) + if not res: + self.controller.abort() + def _checkArmCPU(self): """ parse /proc/cpuinfo and search for ARMv6 or greater diff -Nru ubuntu-release-upgrader-18.04.40/tests/test_quirks.py ubuntu-release-upgrader-18.04.41/tests/test_quirks.py --- ubuntu-release-upgrader-18.04.40/tests/test_quirks.py 2019-06-19 09:49:56.000000000 -0700 +++ ubuntu-release-upgrader-18.04.41/tests/test_quirks.py 2020-10-26 10:23:33.000000000 -0700 @@ -24,12 +24,14 @@ pass -def make_mock_pkg(name, is_installed, candidate_rec): +def make_mock_pkg(name, is_installed, candidate_rec=""): mock_pkg = mock.Mock() mock_pkg.name = name mock_pkg.is_installed = is_installed - mock_pkg.candidate = mock.Mock() - mock_pkg.candidate.record = candidate_rec + mock_pkg.marked_install = False + if candidate_rec: + mock_pkg.candidate = mock.Mock() + mock_pkg.candidate.record = candidate_rec return mock_pkg @@ -243,6 +245,75 @@ pkgname = q._get_linux_metapackage(mock_cache, headers=False) self.assertEqual(pkgname, "linux-generic-lts-quantal") + def test_ros_installed_warning(self): + ros_packages = ( + "ros-melodic-catkin", + "ros-noetic-rosboost-cfg", + "ros-foxy-rosclean", + "ros-kinetic-ros-environment", + "ros-dashing-ros-workspace") + for package_name in ros_packages: + mock_controller = mock.Mock() + mock_question = mock_controller._view.askYesNoQuestion + mock_question.return_value = True + + q = DistUpgradeQuirks(mock_controller, mock.Mock()) + mock_cache = set([ + make_mock_pkg( + name=package_name, + is_installed=True, + ), + ]) + q._test_and_warn_if_ros_installed(mock_cache) + mock_question.assert_called_once_with(mock.ANY, mock.ANY) + self.assertFalse(len(mock_controller.abort.mock_calls)) + + mock_controller.reset_mock() + mock_question.reset_mock() + mock_question.return_value = False + + mock_cache = set([ + make_mock_pkg( + name=package_name, + is_installed=True, + ), + ]) + q._test_and_warn_if_ros_installed(mock_cache) + mock_question.assert_called_once_with(mock.ANY, mock.ANY) + mock_controller.abort.assert_called_once_with() + + def test_ros_not_installed_no_warning(self): + mock_controller = mock.Mock() + mock_question = mock_controller._view.askYesNoQuestion + mock_question.return_value = False + + q = DistUpgradeQuirks(mock_controller, mock.Mock()) + mock_cache = set([ + make_mock_pkg( + name="ros-melodic-catkin", + is_installed=False, + ), + make_mock_pkg( + name="ros-noetic-rosboost-cfg", + is_installed=False, + ), + make_mock_pkg( + name="ros-foxy-rosclean", + is_installed=False, + ), + make_mock_pkg( + name="ros-kinetic-ros-environment", + is_installed=False, + ), + make_mock_pkg( + name="ros-dashing-ros-workspace", + is_installed=False, + ), + ]) + q._test_and_warn_if_ros_installed(mock_cache) + self.assertFalse(len(mock_question.mock_calls)) + self.assertFalse(len(mock_controller.abort.mock_calls)) + if __name__ == "__main__": import logging