Repository: pybuilder/pybuilder
Issue: Work around pip usability problems

There is a new "feature" in pip where installing a dependency will not work unless you add tons of unnecessary parameters.

For instance

```
pip install pil
```

will not work. What you need to run is

```
pip install pil --allow-external pil --allow-unverified pil
```

I understand this is a problem with the pil library itself (badly packaged / not hosted on pypi / no signed downloads) but having to add all these flags is really really tedious.

I'm thinking about a something like

```
project.set_property("install_dependencies_force_install_for", ["pil"])
```

which will add the required flags to pip.


Changed files:
src/unittest/python/plugins/python/install_dependencies_plugin_tests.py, src/main/python/pybuilder/plugins/python/install_dependencies_plugin.py

diff --git a/src/main/python/pybuilder/plugins/python/install_dependencies_plugin.py b/src/main/python/pybuilder/plugins/python/install_dependencies_plugin.py
index 2c5c753..1656d79 100644
--- a/src/main/python/pybuilder/plugins/python/install_dependencies_plugin.py
+++ b/src/main/python/pybuilder/plugins/python/install_dependencies_plugin.py
@@ -82,7 +82,7 @@ def install_dependency(logger, project, dependency):
     logger.info("Installing dependency '%s'%s", dependency.name, " from %s" % dependency.url if dependency.url else "")
     log_file = project.expand_path("$dir_install_logs", dependency.name)
 
-    pip_command_line = "pip install {0}'{1}'".format(build_pip_install_options(project), as_pip_argument(dependency))
+    pip_command_line = "pip install {0}'{1}'".format(build_pip_install_options(project, dependency), as_pip_argument(dependency))
     exit_code = execute_command(pip_command_line, log_file, shell=True)
     if exit_code != 0:
         if project.get_property("verbose"):
@@ -94,7 +94,7 @@ def install_dependency(logger, project, dependency):
                                        log_file)
 
 
-def build_pip_install_options(project):
+def build_pip_install_options(project, dependency):
     options = []
     if project.get_property("install_dependencies_index_url"):
         options.append("--index-url " + project.get_property("install_dependencies_index_url"))
@@ -104,6 +104,12 @@ def build_pip_install_options(project):
     if project.get_property("install_dependencies_upgrade"):
         options.append("--upgrade")
 
+    if _pip_disallows_insecure_packages_by_default():
+        for insecure_dependency in project.get_property("install_dependencies_insecure_installation", []):
+            arguments_for_insecure_installation = ["--allow-unverified", insecure_dependency,
+                                                   "--allow-external", insecure_dependency]
+            options.extend(arguments_for_insecure_installation)
+
     result = " ".join(options)
     if result:
         result += " "
@@ -114,3 +120,10 @@ def as_pip_argument(dependency):
     if dependency.url:
         return dependency.url
     return "{0}{1}".format(dependency.name, build_dependency_version_string(dependency))
+
+
+def _pip_disallows_insecure_packages_by_default():
+    import pip
+    # (2014-01-01) BACKWARD INCOMPATIBLE pip no longer will scrape insecure external urls by default
+    # nor will it install externally hosted files by default
+    return pip.__version__ >= '1.5'
diff --git a/src/unittest/python/plugins/python/install_dependencies_plugin_tests.py b/src/unittest/python/plugins/python/install_dependencies_plugin_tests.py
index 626c73d..ab1226c 100644
--- a/src/unittest/python/plugins/python/install_dependencies_plugin_tests.py
+++ b/src/unittest/python/plugins/python/install_dependencies_plugin_tests.py
@@ -51,6 +51,39 @@ class InstallDependencyTest(unittest.TestCase):
         verify(pybuilder.plugins.python.install_dependencies_plugin).execute_command(
             "pip install 'spam'", any_value(), shell=True)
 
+    def test_should_install_dependency_insecurely_when_property_is_set(self):
+        dependency = Dependency("spam")
+        self.project.set_property("install_dependencies_insecure_installation", ["spam"])
+        when(pybuilder.plugins.python.install_dependencies_plugin)._pip_disallows_insecure_packages_by_default().thenReturn(True)
+
+        install_dependency(self.logger, self.project, dependency)
+
+        verify(pybuilder.plugins.python.install_dependencies_plugin).execute_command(
+            "pip install --allow-unverified spam --allow-external spam 'spam'", any_value(), shell=True)
+
+    def test_should_install_dependency_securely_when_property_is_not_set_to_dependency(self):
+        dependency = Dependency("spam")
+        self.project.set_property("install_dependencies_insecure_installation", ["some-other-dependency"])
+        when(pybuilder.plugins.python.install_dependencies_plugin)._pip_disallows_insecure_packages_by_default().thenReturn(True)
+
+        install_dependency(self.logger, self.project, dependency)
+
+        verify(pybuilder.plugins.python.install_dependencies_plugin).execute_command(
+            "pip install --allow-unverified some-other-dependency --allow-external some-other-dependency 'spam'",
+            any_value(), shell=True)
+        #  some-other-dependency might be a dependency of 'spam'
+        #  so we always have to put the insecure dependencies in the command line :-(
+
+    def test_should_not_use_insecure_flags_when_pip_version_is_too_low(self):
+        dependency = Dependency("spam")
+        self.project.set_property("install_dependencies_insecure_installation", ["spam"])
+        when(pybuilder.plugins.python.install_dependencies_plugin)._pip_disallows_insecure_packages_by_default().thenReturn(False)
+
+        install_dependency(self.logger, self.project, dependency)
+
+        verify(pybuilder.plugins.python.install_dependencies_plugin).execute_command(
+            "pip install 'spam'", any_value(), shell=True)
+
     def test_should_install_dependency_using_custom_index_url(self):
         self.project.set_property(
             "install_dependencies_index_url", "some_index_url")