diff --git a/python/lib/dependabot/python/update_checker/pipenv_version_resolver.rb b/python/lib/dependabot/python/update_checker/pipenv_version_resolver.rb index c6d8f8c2d9f9..f969903c2fa5 100644 --- a/python/lib/dependabot/python/update_checker/pipenv_version_resolver.rb +++ b/python/lib/dependabot/python/update_checker/pipenv_version_resolver.rb @@ -39,6 +39,12 @@ class PipenvVersionResolver UNSUPPORTED_DEP_REGEX = /"python setup\.py egg_info".*(?:#{UNSUPPORTED_DEPS.join("|")})/. freeze + PIPENV_INSTALLATION_ERROR = "pipenv.patched.notpip._internal."\ + "exceptions.InstallationError: "\ + "Command \"python setup.py egg_info\" "\ + "failed with error code 1 in" + PIPENV_INSTALLATION_ERROR_REGEX = + %r{#{Regexp.quote(PIPENV_INSTALLATION_ERROR)}.+/(?.+)/$}.freeze attr_reader :dependency, :dependency_files, :credentials @@ -169,7 +175,6 @@ def handle_pipenv_errors(error) return if error.message.match?(/#{Regexp.quote(dependency.name)}/i) end - puts error.message if error.message.match?(GIT_DEPENDENCY_UNREACHABLE_REGEX) url = error.message.match(GIT_DEPENDENCY_UNREACHABLE_REGEX). named_captures.fetch("url") @@ -232,6 +237,10 @@ def handle_pipenv_errors_resolving_original_reqs(error) raise DependencyFileNotResolvable, msg end + # NOTE: Pipenv masks the actualy error, see this issue for updates: + # https://github.com/pypa/pipenv/issues/2791 + handle_pipenv_installation_error(error.message) if error.message.match?(PIPENV_INSTALLATION_ERROR_REGEX) + # Raise an unhandled error, as this could be a problem with # Dependabot's infrastructure, rather than the Pipfile raise @@ -257,6 +266,19 @@ def clean_error_message(message) msg.gsub(/http.*?(?=\s)/, "") end + def handle_pipenv_installation_error(error_message) + # Find the dependency that's causing resolution to fail + dependency_name = error_message.match(PIPENV_INSTALLATION_ERROR_REGEX).named_captures["name"] + raise unless dependency_name + + msg = "Pipenv failed to install \"#{dependency_name}\". This could be caused by missing system "\ + "dependencies that can't be installed by Dependabot or required installation flags.\n\n"\ + "Error output from running \"pipenv lock\":\n"\ + "#{clean_error_message(error_message)}" + + raise DependencyFileNotResolvable, msg + end + def write_temporary_dependency_files(updated_req: nil, update_pipfile: true) dependency_files.each do |file| diff --git a/python/spec/dependabot/python/update_checker/pipenv_version_resolver_spec.rb b/python/spec/dependabot/python/update_checker/pipenv_version_resolver_spec.rb index e432cb673b1e..6950d41d9efb 100644 --- a/python/spec/dependabot/python/update_checker/pipenv_version_resolver_spec.rb +++ b/python/spec/dependabot/python/update_checker/pipenv_version_resolver_spec.rb @@ -469,6 +469,35 @@ end end end + + context "with a missing system libary" do + # NOTE: Attempt to update an unrelated dependency (tensorflow) to cause + # resolution to fail for rtree which has a system dependency on + # libspatialindex which isn't installed in dependabot-core's Dockerfile. + let(:dependency_files) do + project_dependency_files("pipenv/missing-system-library") + end + let(:updated_requirement) { "==2.3.1" } + let(:dependency_name) { "tensorflow" } + let(:dependency_version) { "2.1.0" } + let(:dependency_requirements) do + [{ + file: "Pipfile", + requirement: "==2.1.0", + groups: ["default"], + source: nil + }] + end + + it "raises a helpful error" do + expect { subject }. + to raise_error(Dependabot::DependencyFileNotResolvable) do |error| + expect(error.message).to include( + "Pipenv failed to install \"rtree\"" + ) + end + end + end end describe "#resolvable?" do diff --git a/python/spec/fixtures/projects/pipenv/missing-system-library/Pipfile b/python/spec/fixtures/projects/pipenv/missing-system-library/Pipfile new file mode 100644 index 000000000000..ed3d0bd217ea --- /dev/null +++ b/python/spec/fixtures/projects/pipenv/missing-system-library/Pipfile @@ -0,0 +1,8 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +rtree = "==0.9.3" +tensorflow = "==2.1.0" diff --git a/python/spec/fixtures/projects/pipenv/missing-system-library/Pipfile.lock b/python/spec/fixtures/projects/pipenv/missing-system-library/Pipfile.lock new file mode 100644 index 000000000000..b7af358bae96 --- /dev/null +++ b/python/spec/fixtures/projects/pipenv/missing-system-library/Pipfile.lock @@ -0,0 +1,41 @@ +{ + "_meta": { + "hash": { + "sha256": "e98725a3545acb9f6a84023a297838e6edf163ef50824694b0ebeb0d372ca0f5" + }, + "pipfile-spec": 6, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "rtree": { + "hashes": [ + "sha256:cae327e2c03b3da4ea40d0fdf68f3e55fe9f302c56b9f31e1bfeb36dbea73f44" + ], + "index": "pypi", + "version": "==0.9.3" + }, + "tensorflow": { + "hashes": [ + "sha256:1cf129ccda0aea616b122f34b0c4bc39da959d34c4a4d8c23ed944555c5e47ab", + "sha256:2e8fc9764b7ea87687a4c80c2fbde69aeeb459a536eb5a591938d7931ab004c2", + "sha256:33e4b16e8f8905ee088bf8f413dcce2820b777fdf7f799009b3a47f354ebb23f", + "sha256:513d48dd751e0076d1b1e5e498e3522891305bedd2840f3cb4b1c57ffcb7d97d", + "sha256:5cfa729fc71f6f2dca0ea77ebe768ea293e723e22ecb086a0b3ab26cc1776e37", + "sha256:7bad8ea686a1f33d9dac13eb578c4597346789d4f826980c8bbcfbd08e7dc921", + "sha256:8c0fae0f9f772ed7e3370f1b286f88c27debbcf09468e5036670ea2c67e239ec", + "sha256:92c4f1c939de438fbe484d011e5eebe059fc8e5244cfe32a81c6891b3357d109", + "sha256:c420e70d4127c2ac00054aece54cf04a1a43d5d4f25de90267f247873f1bd5a8", + "sha256:e631f55cf30054fee3230c89a7f998fd08748aa3045651a5a760cec2c5b9f9d6", + "sha256:e877fbf373d5be42fb118269df1670b8d3c0df9be223904a2584a8f8ed23b082" + ], + "index": "pypi", + "version": "==2.1.0" + } + } +}