Repository: conan-io/conan

Issue:
Conan server can't be setup in the subdirectory of a domain
I want to run Conan at, say, `https://example.com/conan` behind Apache.

Currently, there is no way to do this. If I set `host_name` to `example.com/conan` and `port` to `443`, the Conan client will try to upload to `https://example.com/conan:443/v1/...`

Discussion:

Patch:
diff --git a/conans/client/build_requires.py b/conans/client/build_requires.py
index d7235d13c..a7716c9cb 100644
--- a/conans/client/build_requires.py
+++ b/conans/client/build_requires.py
@@ -105,7 +105,8 @@ class BuildRequires(object):
 
         reference = str(reference)
         output.info("Installing build requirements of: %s" % (reference or "PROJECT"))
-        output.info("Build requires: [%s]" % ", ".join(str(r) for r in build_requires.values()))
+        output.info("Build requires: [%s]"
+                          % ", ".join(str(r) for r in build_requires.values()))
         # clear root package options, they won't match the build-require
         conanfile.build_requires_options.clear_unscoped_options()
         virtual = self._loader.load_virtual(build_requires.values(), scope_options=False,
diff --git a/conans/client/client_cache.py b/conans/client/client_cache.py
index b37344331..7206db020 100644
--- a/conans/client/client_cache.py
+++ b/conans/client/client_cache.py
@@ -129,17 +129,13 @@ class ClientCache(SimplePaths):
         if self._default_profile is None:
             if not os.path.exists(self.default_profile_path):
                 self._output.writeln("Auto detecting your dev setup to initialize the "
-                                     "default profile (%s)" % self.default_profile_path,
-                                     Color.BRIGHT_YELLOW)
+                                     "default profile (%s)" % self.default_profile_path, Color.BRIGHT_YELLOW)
 
                 default_settings = detect_defaults_settings(self._output)
                 self._output.writeln("Default settings", Color.BRIGHT_YELLOW)
-                self._output.writeln("\n".join(["\t%s=%s" % (k, v) for (k, v) in default_settings]),
-                                     Color.BRIGHT_YELLOW)
-                self._output.writeln("*** You can change them in %s ***" % self.default_profile_path,
-                                     Color.BRIGHT_MAGENTA)
-                self._output.writeln("*** Or override with -s compiler='other' -s ...s***\n\n",
-                                     Color.BRIGHT_MAGENTA)
+                self._output.writeln("\n".join(["\t%s=%s" % (k, v) for (k, v) in default_settings]), Color.BRIGHT_YELLOW)
+                self._output.writeln("*** You can change them in %s ***" % self.default_profile_path, Color.BRIGHT_MAGENTA)
+                self._output.writeln("*** Or override with -s compiler='other' -s ...s***\n\n", Color.BRIGHT_MAGENTA)
 
                 self._default_profile = Profile()
                 tmp = OrderedDict(default_settings)
@@ -178,7 +174,7 @@ class ClientCache(SimplePaths):
         try:
             packages = [dirname for dirname in os.listdir(packages_dir)
                         if os.path.isdir(os.path.join(packages_dir, dirname))]
-        except OSError:  # if there isn't any package folder
+        except:  # if there isn't any package folder
             packages = []
         return packages
 
@@ -189,7 +185,7 @@ class ClientCache(SimplePaths):
         try:
             builds = [dirname for dirname in os.listdir(builds_dir)
                       if os.path.isdir(os.path.join(builds_dir, dirname))]
-        except OSError:  # if there isn't any package folder
+        except:  # if there isn't any package folder
             builds = []
         return builds
 
diff --git a/conans/client/command.py b/conans/client/command.py
index af8e2e8b0..425f9c04e 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -435,6 +435,7 @@ class Command(object):
             else:
                 self._outputer.info(deps_graph, graph_updates_info, only, args.remote,
                                     args.package_filter, args.paths, project_reference)
+        return
 
     def source(self, *args):
         """ Calls your local conanfile.py 'source()' method.
@@ -837,6 +838,7 @@ class Command(object):
                                          prog="conan upload")
         parser.add_argument('pattern', help='Pattern or package recipe reference, '
                                             'e.g., "openssl/*", "MyPackage/1.2@user/channel"')
+        # TODO: packageparser.add_argument('package', help='user name')
         parser.add_argument("-p", "--package", default=None, help='package ID to upload',
                             action=OnceArgument)
         parser.add_argument("-r", "--remote", help='upload to this specific remote',
@@ -956,7 +958,7 @@ class Command(object):
 
         parser_new = subparsers.add_parser('new', help='Creates a new empty profile')
         parser_new.add_argument('profile',  help="name for the profile in the '.conan/profiles' "
-                                                 "folder or path and name for a profile file")
+                                                  "folder or path and name for a profile file")
         parser_new.add_argument("--detect", action='store_true',
                                 default=False,
                                 help='Autodetect settings and fill [settings] section')
@@ -969,7 +971,7 @@ class Command(object):
         parser_get = subparsers.add_parser('get', help='Get a profile key')
         parser_get.add_argument('item', help='key of the value to get, e.g: settings.compiler')
         parser_get.add_argument('profile',  help="name of the profile in the '.conan/profiles' "
-                                                 "folder or path to a profile file")
+                                                  "folder or path to a profile file")
 
         parser_remove = subparsers.add_parser('remove', help='Remove a profile key')
         parser_remove.add_argument('item', help='key, e.g: settings.compiler')
@@ -1000,6 +1002,8 @@ class Command(object):
         elif args.subcommand == "remove":
             self._conan.delete_profile_key(profile, args.item)
 
+        return
+
     def get(self, *args):
         """ Gets a file or list a directory of a given reference or package.
         """
@@ -1042,6 +1046,8 @@ class Command(object):
 
         self._conan.export_alias(args.reference, args.target)
 
+        return
+
     def _show_help(self):
         """ prints a summary of all commands
         """
diff --git a/conans/client/importer.py b/conans/client/importer.py
index 8a9af6b32..a66c222ae 100644
--- a/conans/client/importer.py
+++ b/conans/client/importer.py
@@ -116,7 +116,7 @@ class _FileImporter(object):
         self.copied_files = set()
 
     def __call__(self, pattern, dst="", src="", root_package=None, folder=False,
-                 ignore_case=False, excludes=None, keep_path=True):
+                 ignore_case=False, excludes=None):
         """
         param pattern: an fnmatch file pattern of the files that should be copied. Eg. *.dll
         param dst: the destination local folder, wrt to current conanfile dir, to which
@@ -136,7 +136,7 @@ class _FileImporter(object):
             final_dst_path = os.path.join(real_dst_folder, name) if folder else real_dst_folder
             file_copier = FileCopier(matching_path, final_dst_path)
             files = file_copier(pattern, src=src, links=True, ignore_case=ignore_case,
-                                excludes=excludes, keep_path=keep_path)
+                                excludes=excludes)
             self.copied_files.update(files)
 
     def _get_folders(self, pattern):
diff --git a/conans/client/loader.py b/conans/client/loader.py
index b072b7d26..824217cdb 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -8,7 +8,6 @@ from conans.model.options import OptionsValues
 from conans.model.ref import ConanFileReference
 from conans.model.settings import Settings
 from conans.model.values import Values
-from conans.model.requires import Requirements
 from conans.util.files import load
 
 
@@ -96,11 +95,6 @@ class ConanFileLoader(object):
         for requirement_text in parser.requirements:
             ConanFileReference.loads(requirement_text)  # Raise if invalid
             conanfile.requires.add(requirement_text)
-        for build_requirement_text in parser.build_requirements:
-            ConanFileReference.loads(build_requirement_text)
-            if not hasattr(conanfile, "build_requires"):
-                conanfile.build_requires = []
-            conanfile.build_requires.append(build_requirement_text)
 
         conanfile.generators = parser.generators
 
diff --git a/conans/client/loader_parse.py b/conans/client/loader_parse.py
index e1a47365b..0e52ae2c4 100644
--- a/conans/client/loader_parse.py
+++ b/conans/client/loader_parse.py
@@ -95,7 +95,7 @@ class ConanFileTextLoader(object):
     def __init__(self, input_text):
         # Prefer composition over inheritance, the __getattr__ was breaking things
         self._config_parser = ConfigParser(input_text,  ["requires", "generators", "options",
-                                                         "imports", "build_requires"], parse_lines=True)
+                                                         "imports"], parse_lines=True)
 
     @property
     def requirements(self):
@@ -104,13 +104,6 @@ class ConanFileTextLoader(object):
         """
         return [r.strip() for r in self._config_parser.requires.splitlines()]
 
-    @property
-    def build_requirements(self):
-        """returns a list of build_requires
-        EX:  "OpenCV/2.4.10@phil/stable"
-        """
-        return [r.strip() for r in self._config_parser.build_requires.splitlines()]
-
     @property
     def options(self):
         return self._config_parser.options
@@ -118,7 +111,7 @@ class ConanFileTextLoader(object):
     @property
     def _import_parameters(self):
         def _parse_args(param_string):
-            root_package, ignore_case, folder, excludes, keep_path = None, False, False, None, True
+            root_package, ignore_case, folder, excludes = None, False, False, None
             params = param_string.split(",")
             params = [p.split("=") for p in params if p]
             for (var, value) in params:
@@ -132,11 +125,9 @@ class ConanFileTextLoader(object):
                     folder = (value.lower() == "true")
                 elif var == "excludes":
                     excludes = value.split()
-                elif var == "keep_path":
-                    keep_path = (value.lower() == "true")
                 else:
                     raise Exception("Invalid imports. Unknown argument %s" % var)
-            return root_package, ignore_case, folder, excludes, keep_path
+            return root_package, ignore_case, folder, excludes
 
         def _parse_import(line):
             pair = line.split("->")
@@ -165,9 +156,9 @@ class ConanFileTextLoader(object):
                     params = tokens[1]
                 else:
                     params = ""
-                root_package, ignore_case, folder, excludes, keep_path = _parse_args(params)
+                root_package, ignore_case, folder, excludes = _parse_args(params)
                 pattern, dest, src = _parse_import(line)
-                ret.append((pattern, dest, src, root_package, folder, ignore_case, excludes, keep_path))
+                ret.append((pattern, dest, src, root_package, folder, ignore_case, excludes))
             except Exception as e:
                 raise ConanException("%s\n%s" % (invalid_line_msg, str(e)))
         return ret
diff --git a/conans/client/remote_registry.py b/conans/client/remote_registry.py
index 61ab50519..93fcc5225 100644
--- a/conans/client/remote_registry.py
+++ b/conans/client/remote_registry.py
@@ -191,7 +191,7 @@ class RemoteRegistry(object):
             if insert is not None:
                 try:
                     insert_index = int(insert)
-                except ValueError:
+                except:
                     raise ConanException("insert argument must be an integer")
                 remotes.pop(remote_name, None)  # Remove if exists (update)
                 remotes_list = list(remotes.items())
diff --git a/conans/client/rest/rest_client.py b/conans/client/rest/rest_client.py
index 7af2aac66..df21c11b0 100644
--- a/conans/client/rest/rest_client.py
+++ b/conans/client/rest/rest_client.py
@@ -11,7 +11,7 @@ import os
 from conans.model.manifest import FileTreeManifest
 from conans.client.rest.uploader_downloader import Uploader, Downloader
 from conans.model.ref import ConanFileReference
-from six.moves.urllib.parse import urlsplit, parse_qs, urlencode
+from six.moves.urllib.parse import urlsplit, parse_qs, urlencode, urlparse, urljoin
 from conans import COMPLEX_SEARCH_CAPABILITY
 from conans.search.search import filter_packages
 from conans.model.info import ConanInfo
@@ -103,7 +103,7 @@ class RestApiClient(object):
 
         # Obtain the URLs
         url = "%s/conans/%s/digest" % (self._remote_api_url, "/".join(conan_reference))
-        urls = self._get_json(url)
+        urls = self._get_file_to_url_dict(url)
 
         # Get the digest
         contents = self.download_files(urls)
@@ -118,7 +118,7 @@ class RestApiClient(object):
         url = "%s/conans/%s/packages/%s/digest" % (self._remote_api_url,
                                                    "/".join(package_reference.conan),
                                                    package_reference.package_id)
-        urls = self._get_json(url)
+        urls = self._get_file_to_url_dict(url)
 
         # Get the digest
         contents = self.download_files(urls)
@@ -132,7 +132,7 @@ class RestApiClient(object):
         url = "%s/conans/%s/packages/%s/download_urls" % (self._remote_api_url,
                                                           "/".join(package_reference.conan),
                                                           package_reference.package_id)
-        urls = self._get_json(url)
+        urls = self._get_file_to_url_dict(url)
         if not urls:
             raise NotFoundException("Package not found!")
 
@@ -149,7 +149,7 @@ class RestApiClient(object):
         """Gets a dict of filename:contents from conans"""
         # Get the conanfile snapshot first
         url = "%s/conans/%s/download_urls" % (self._remote_api_url, "/".join(conan_reference))
-        urls = self._get_json(url)
+        urls = self._get_file_to_url_dict(url)
 
         urls = filter_files_function(urls)
         if not urls:
@@ -164,7 +164,7 @@ class RestApiClient(object):
         url = "%s/conans/%s/packages/%s/download_urls" % (self._remote_api_url,
                                                           "/".join(package_reference.conan),
                                                           package_reference.package_id)
-        urls = self._get_json(url)
+        urls = self._get_file_to_url_dict(url)
         if not urls:
             raise NotFoundException("Package not found!")
         # TODO: Get fist an snapshot and compare files and download only required?
@@ -198,7 +198,7 @@ class RestApiClient(object):
             url = "%s/conans/%s/upload_urls" % (self._remote_api_url, "/".join(conan_reference))
             filesizes = {filename.replace("\\", "/"): os.stat(abs_path).st_size
                          for filename, abs_path in files_to_upload.items()}
-            urls = self._get_json(url, data=filesizes)
+            urls = self._get_file_to_url_dict(url, data=filesizes)
             self.upload_files(urls, files_to_upload, self._output, retry, retry_wait)
         if deleted:
             self._remove_conanfile_files(conan_reference, deleted)
@@ -230,7 +230,7 @@ class RestApiClient(object):
             filesizes = {filename: os.stat(abs_path).st_size for filename,
                          abs_path in files_to_upload.items()}
             self._output.rewrite_line("Requesting upload permissions...")
-            urls = self._get_json(url, data=filesizes)
+            urls = self._get_file_to_url_dict(url, data=filesizes)
             self._output.rewrite_line("Requesting upload permissions...Done!")
             self._output.writeln("")
             self.upload_files(urls, files_to_upload, self._output, retry, retry_wait)
@@ -388,6 +388,20 @@ class RestApiClient(object):
                                        json=payload)
         return response
 
+    def _complete_url(self, url):
+        """ Ensures that an url is absolute by completing relative urls with
+            the remote url. urls that are already absolute are not modified.
+        """
+        if bool(urlparse(url).netloc):
+            return url
+        return urljoin(self.remote_url, url)
+
+    def _get_file_to_url_dict(self, url, data=None):
+        """Call to url and decode the json returning a dict of {filepath: url} dict
+        converting the url to a complete url when needed"""
+        urls = self._get_json(url, data=data)
+        return {filepath: self._complete_url(url) for filepath, url in urls.items()}
+
     def _get_json(self, url, data=None):
         t1 = time.time()
         headers = self.custom_headers
@@ -509,7 +523,7 @@ class RestApiClient(object):
                                                               "/".join(conan_reference),
                                                               package_id)
         try:
-            urls = self._get_json(url)
+            urls = self._get_file_to_url_dict(url)
         except NotFoundException:
             if package_id:
                 raise NotFoundException("Package %s:%s not found" % (conan_reference, package_id))
diff --git a/conans/client/tools/pkg_config.py b/conans/client/tools/pkg_config.py
index 2e56ac4bc..00ec7e0b8 100644
--- a/conans/client/tools/pkg_config.py
+++ b/conans/client/tools/pkg_config.py
@@ -2,7 +2,6 @@
 # -*- coding: utf-8 -*-
 
 import subprocess
-from conans.errors import ConanException
 
 
 class PkgConfig(object):
@@ -36,13 +35,10 @@ class PkgConfig(object):
         if self.define_variables:
             for name, value in self.define_variables.items():
                 command.append('--define-variable=%s=%s' % (name, value))
-        try:
-            return self._cmd_output(command)
-        except subprocess.CalledProcessError as e:
-            raise ConanException('pkg-config command %s failed with error: %s' % (command, e))
+        return self._cmd_output(command)
 
     def _get_option(self, option):
-        if option not in self.info:
+        if not option in self.info:
             self.info[option] = self._parse_output(option).split()
         return self.info[option]
 
diff --git a/conans/server/conf/__init__.py b/conans/server/conf/__init__.py
index 03114d6d8..1e2a88a63 100644
--- a/conans/server/conf/__init__.py
+++ b/conans/server/conf/__init__.py
@@ -49,7 +49,9 @@ class ConanServerConfigParser(ConfigParser):
                            "users": get_env("CONAN_SERVER_USERS", None, environment)}
 
     def _get_file_conf(self, section, varname=None):
-        """Gets the section from config file or raises an exception"""
+        """ Gets the section or variable from config file.
+        If the queried element is not found an exception is raised.
+        """
         try:
             if not os.path.exists(self.config_filename):
                 jwt_random_secret = ''.join(random.choice(string.ascii_letters) for _ in range(24))
@@ -71,7 +73,7 @@ class ConanServerConfigParser(ConfigParser):
                 return section[varname]
             else:
                 return self.items(section)
-        except NoSectionError as exc:
+        except NoSectionError:
             raise ConanException("No section '%s' found" % section)
         except Exception as exc:
             logger.debug(exc)
@@ -80,51 +82,55 @@ class ConanServerConfigParser(ConfigParser):
 
     @property
     def ssl_enabled(self):
-        if self.env_config["ssl_enabled"]:
-            return self.env_config["ssl_enabled"] == "true" or \
-                   self.env_config["ssl_enabled"] == "1"
-        else:
-            return self._get_file_conf("server", "ssl_enabled").lower() == "true" or \
-                   self._get_file_conf("server", "ssl_enabled").lower() == "1"
+        try:
+            ssl_enabled = self._get_conf_server_string("ssl_enabled").lower()
+            return ssl_enabled == "true" or ssl_enabled == "1"
+        except ConanException:
+            return None
 
     @property
     def port(self):
-        if self.env_config["port"]:
-            return int(self.env_config["port"])
-        else:
-            return int(self._get_file_conf("server", "port"))
+        return int(self._get_conf_server_string("port"))
 
     @property
     def public_port(self):
-        if self.env_config["public_port"]:
-            return int(self.env_config["public_port"])
-        elif self._get_file_conf("server", "public_port"):
-            return int(self._get_file_conf("server", "public_port"))
-        else:
+        try:
+            return int(self._get_conf_server_string("public_port"))
+        except ConanException:
             return self.port
 
     @property
     def host_name(self):
-        return self._get_conf_server_string("host_name")
+        try:
+            return self._get_conf_server_string("host_name")
+        except ConanException:
+            return None
 
     @property
     def public_url(self):
-        protocol = "https" if self.ssl_enabled else "http"
-        port = ":%s" % self.public_port if self.public_port != 80 else ""
-        return "%s://%s%s/v1" % (protocol, self.host_name, port)
+        host_name = self.host_name
+        ssl_enabled = self.ssl_enabled
+        protocol_version = "v1"
+        if host_name is None and ssl_enabled is None:
+            # No hostname and ssl config means that the transfer and the
+            # logical endpoint are the same and a relative URL is sufficient
+            return protocol_version
+        elif host_name is None or ssl_enabled is None:
+            raise ConanException("'host_name' and 'ssl_enable' have to be defined together.")
+        else:
+            protocol = "https" if ssl_enabled else "http"
+            port = ":%s" % self.public_port if self.public_port != 80 else ""
+            return "%s://%s%s/%s" % (protocol, host_name, port, protocol_version)
 
     @property
     def disk_storage_path(self):
         """If adapter is disk, means the directory for storage"""
-        if self.env_config["disk_storage_path"]:
-            ret = self.env_config["disk_storage_path"]
-        else:
-            try:
-                ret = conan_expand_user(self._get_file_conf("server", "disk_storage_path"))
-            except ConanException:
-                # If storage_path is not defined in file, use the current dir
-                # So tests use test folder instead of user/.conan_server
-                ret = os.path.dirname(self.config_filename)
+        try:
+            ret = conan_expand_user(self._get_conf_server_string("disk_storage_path"))
+        except ConanException:
+            # If storage_path is not defined, use the current dir
+            # So tests use test folder instead of user/.conan_server
+            ret = os.path.dirname(self.config_filename)
         ret = os.path.normpath(ret)  # Convert to O.S paths
         mkdir(ret)
         return ret
@@ -170,45 +176,45 @@ class ConanServerConfigParser(ConfigParser):
 
     @property
     def jwt_secret(self):
-        tmp = self._get_conf_server_string("jwt_secret")
-        if not tmp:
+        try:
+            return self._get_conf_server_string("jwt_secret")
+        except ConanException:
             raise ConanException("'jwt_secret' setting is needed. Please, write a value "
                                  "in server.conf or set CONAN_JWT_SECRET env value.")
-        return tmp
+
 
     @property
     def updown_secret(self):
-        tmp = self._get_conf_server_string("updown_secret")
-        if not tmp:
+        try:
+            return self._get_conf_server_string("updown_secret")
+        except ConanException:
             raise ConanException("'updown_secret' setting is needed. Please, write a value "
                                  "in server.conf or set CONAN_UPDOWN_SECRET env value.")
-        return self._get_conf_server_string("updown_secret")
 
     @property
     def store_adapter(self):
         return self._get_conf_server_string("store_adapter")
 
     def _get_conf_server_string(self, keyname):
+        """ Gets the value of a server config value either from the environment
+        or the config file. Values from the environment have priority. If the
+        value is not defined or empty an exception is raised.
+        """
         if self.env_config[keyname]:
             return self.env_config[keyname]
-        else:
-            return self._get_file_conf("server", keyname)
+
+        value = self._get_file_conf("server", keyname)
+        if value == "":
+            raise ConanException("no value for 'server.%s' is defined in the config file" % keyname)
+        return value
 
     @property
     def authorize_timeout(self):
-        if self.env_config["authorize_timeout"]:
-            return timedelta(seconds=int(self.env_config["authorize_timeout"]))
-        else:
-            tmp = self._get_file_conf("server", "authorize_timeout")
-            return timedelta(seconds=int(tmp))
+        return timedelta(seconds=int(self._get_conf_server_string("authorize_timeout")))
 
     @property
     def jwt_expire_time(self):
-        if self.env_config["jwt_expire_minutes"]:
-            return timedelta(minutes=int(self.env_config["jwt_expire_minutes"]))
-        else:
-            tmp = float(self._get_file_conf("server", "jwt_expire_minutes"))
-            return timedelta(minutes=tmp)
+        return timedelta(minutes=float(self._get_conf_server_string("jwt_expire_minutes")))
 
 
 def get_file_manager(config, public_url=None, updown_auth_manager=None):


Test patch:
diff --git a/conans/test/functional/conanfile_loader_test.py b/conans/test/functional/conanfile_loader_test.py
index b4152992b..b2c8fbb51 100644
--- a/conans/test/functional/conanfile_loader_test.py
+++ b/conans/test/functional/conanfile_loader_test.py
@@ -97,8 +97,6 @@ OpenCV2:other_option=Cosa #
         file_content = '''[requires]
 OpenCV/2.4.10@phil/stable
 OpenCV2/2.4.10@phil/stable
-[build_requires]
-MyPkg/1.0.0@phil/stable
 [generators]
 one
 two
@@ -123,11 +121,8 @@ OpenCV2:other_option=Cosa""")
         requirements = Requirements()
         requirements.add("OpenCV/2.4.10@phil/stable")
         requirements.add("OpenCV2/2.4.10@phil/stable")
-        build_requirements = []
-        build_requirements.append("MyPkg/1.0.0@phil/stable")
 
         self.assertEquals(ret.requires, requirements)
-        self.assertEquals(ret.build_requires, build_requirements)
         self.assertEquals(ret.generators, ["one", "two"])
         self.assertEquals(ret.options.values.dumps(), options1.dumps())
 
@@ -167,7 +162,6 @@ OpenCV/bin, * -> ./bin # I need this binaries
 OpenCV/lib, * -> ./lib @ root_package=Pkg
 OpenCV/data, * -> ./data @ root_package=Pkg, folder=True # Irrelevant
 docs, * -> ./docs @ root_package=Pkg, folder=True, ignore_case=True, excludes="a b c" # Other
-licenses, * -> ./licenses @ root_package=Pkg, folder=True, ignore_case=True, excludes="a b c", keep_path=False # Other
 '''
         tmp_dir = temp_folder()
         file_path = os.path.join(tmp_dir, "file.txt")
@@ -177,12 +171,10 @@ licenses, * -> ./licenses @ root_package=Pkg, folder=True, ignore_case=True, exc
 
         ret.copy = Mock()
         ret.imports()
-        expected = [call(u'*', u'./bin', u'OpenCV/bin', None, False, False, None, True),
-                    call(u'*', u'./lib', u'OpenCV/lib', u'Pkg', False, False, None, True),
-                    call(u'*', u'./data', u'OpenCV/data', u'Pkg', True, False, None, True),
-                    call(u'*', u'./docs', u'docs', u'Pkg', True, True, [u'"a', u'b', u'c"'], True),
-                    call(u'*', u'./licenses', u'licenses', u'Pkg', True, True, [u'"a', u'b', u'c"'],
-                         False)]
+        expected = [call(u'*', u'./bin', u'OpenCV/bin', None, False, False, None),
+                    call(u'*', u'./lib', u'OpenCV/lib', u'Pkg', False, False, None),
+                    call(u'*', u'./data', u'OpenCV/data', u'Pkg', True, False, None),
+                    call(u'*', u'./docs', u'docs', u'Pkg', True, True, [u'"a', u'b', u'c"'])]
         self.assertEqual(ret.copy.call_args_list, expected)
 
     def test_package_settings(self):
diff --git a/conans/test/functional/imports_tests.py b/conans/test/functional/imports_tests.py
index 8ab8dbd57..5d93ee436 100644
--- a/conans/test/functional/imports_tests.py
+++ b/conans/test/functional/imports_tests.py
@@ -15,11 +15,6 @@ class TestConan(ConanFile):
         self.copy("*")
 """
 
-conanfile_txt = """
-[requires]
-%s
-"""
-
 
 class ImportTest(unittest.TestCase):
     def _set_up(self):
@@ -122,45 +117,3 @@ bin, *.dll ->  @ excludes=Foo/*.dll Baz/*.dll
         self.assertTrue(os.path.exists(os.path.join(client.current_folder, "a.dll")))
         self.assertFalse(os.path.exists(os.path.join(client.current_folder, "Foo/b.dll")))
         self.assertFalse(os.path.exists(os.path.join(client.current_folder, "Baz/b.dll")))
-
-    def imports_keep_path_test(self):
-        client = TestClient()
-        client.save({"conanfile.py": conanfile % "LibC",
-                     "path/to/license.txt": "LicenseC"}, clean_first=True)
-        client.run("export . lasote/testing")
-
-        # keep_path = True AND conanfile.py
-        testconanfile = conanfile % "LibD" + "    requires='LibC/0.1@lasote/testing'"
-        testconanfile += """
-    def imports(self):
-        self.copy("*license*", dst="licenses", keep_path=True)
-"""
-        client.save({"conanfile.py": testconanfile}, clean_first=True)
-        client.run("install . --build=missing")
-        self.assertTrue(os.path.exists(os.path.join(client.current_folder,
-                                                     "licenses/path/to/license.txt")))
-
-        # keep_path = False AND conanfile.py
-        testconanfile = testconanfile.replace("keep_path=True", "keep_path=False")
-        client.save({"conanfile.py": testconanfile}, clean_first=True)
-        client.run("install . --build=missing")
-        self.assertTrue(os.path.exists(os.path.join(client.current_folder,
-                                                     "licenses/license.txt")))
-
-        # keep_path = True AND conanfile.txt
-        testconanfile = conanfile_txt % "LibC/0.1@lasote/testing"
-        testconanfile += """
-[imports]:
-., *license* -> ./licenses @ keep_path=True
-"""
-        client.save({"conanfile.txt": testconanfile}, clean_first=True)
-        client.run("install . --build=missing")
-        self.assertTrue(os.path.exists(os.path.join(client.current_folder,
-                                                     "licenses/path/to/license.txt")))
-
-        # keep_path = False AND conanfile.txt
-        testconanfile = testconanfile.replace("keep_path=True", "keep_path=False")
-        client.save({"conanfile.txt": testconanfile}, clean_first=True)
-        client.run("install . --build=missing")
-        self.assertTrue(os.path.exists(os.path.join(client.current_folder, "licenses")))
-        self.assertTrue(os.path.exists(os.path.join(client.current_folder, "licenses/license.txt")))
diff --git a/conans/test/integration/build_requires_test.py b/conans/test/integration/build_requires_test.py
index e15776a04..a3eb80275 100644
--- a/conans/test/integration/build_requires_test.py
+++ b/conans/test/integration/build_requires_test.py
@@ -4,7 +4,6 @@ from nose_parameterized.parameterized import parameterized
 from conans.test.utils.tools import TestClient
 from conans.paths import CONANFILE
 import os
-from conans.util.files import load
 
 
 tool_conanfile = """from conans import ConanFile
@@ -51,24 +50,6 @@ nonexistingpattern*: SomeTool/1.2@user/channel
 
 class BuildRequiresTest(unittest.TestCase):
 
-    def test_dependents_txt(self):
-        client = TestClient()
-        boost = """from conans import ConanFile
-class Boost(ConanFile):
-    def package_info(self):
-        self.env_info.PATH.append("myboostpath")
-"""
-        client.save({CONANFILE: boost})
-        client.run("create . Boost/1.0@user/channel")
-        other = """[build_requires]
-Boost/1.0@user/channel
-"""
-        client.save({"conanfile.txt": other}, clean_first=True)
-        client.run("install .")
-        self.assertIn("PROJECT: Build requires: [Boost/1.0@user/channel]", client.out)
-        conanbuildinfo = load(os.path.join(client.current_folder, "conanbuildinfo.txt"))
-        self.assertIn('PATH=["myboostpath"]', conanbuildinfo)
-
     def test_dependents(self):
         client = TestClient()
         boost = """from conans import ConanFile
diff --git a/conans/test/integration/complete_test.py b/conans/test/integration/complete_test.py
index cb8e90bfb..153f58fcb 100644
--- a/conans/test/integration/complete_test.py
+++ b/conans/test/integration/complete_test.py
@@ -1,4 +1,7 @@
 import unittest
+
+from nose_parameterized import parameterized
+
 from conans.test.utils.tools import TestServer, TestClient
 from conans.model.ref import ConanFileReference, PackageReference
 import os
@@ -11,12 +14,12 @@ from conans.test.utils.test_files import uncompress_packaged_files
 @attr("slow")
 class CompleteFlowTest(unittest.TestCase):
 
-    def setUp(self):
-        test_server = TestServer()
+    @parameterized.expand([(True, ), (False, )])
+    def reuse_test(self, complete_urls):
+        test_server = TestServer(complete_urls=complete_urls)
         self.servers = {"default": test_server}
         self.client = TestClient(servers=self.servers, users={"default": [("lasote", "mypass")]})
 
-    def reuse_test(self):
         conan_reference = ConanFileReference.loads("Hello0/0.1@lasote/stable")
         files = cpp_hello_conan_files("Hello0", "0.1", need_patch=True)
         self.client.save(files)
@@ -80,7 +83,6 @@ class CompleteFlowTest(unittest.TestCase):
             self._assert_library_exists(ref, other_conan.paths)
 
         client3 = TestClient(servers=self.servers, users={"default": [("lasote", "mypass")]})
-        conan_reference = ConanFileReference.loads("Hello1/0.2@lasote/stable")
         files3 = cpp_hello_conan_files("Hello1", "0.1", ["Hello0/0.1@lasote/stable"])
         client3.save(files3)
         client3.run('install .')
diff --git a/conans/test/remote/rest_api_test.py b/conans/test/remote/rest_api_test.py
index 1973ddfe7..d77975995 100644
--- a/conans/test/remote/rest_api_test.py
+++ b/conans/test/remote/rest_api_test.py
@@ -49,6 +49,37 @@ class RestApiTest(unittest.TestCase):
     def tearDown(self):
         RestApiTest.server.clean()
 
+    def relative_url_completion_test(self):
+        api = RestApiClient(TestBufferConanOutput(), requester=requests)
+
+        # test absolute urls
+        self.assertEquals(api._complete_url("http://host"), "http://host")
+        self.assertEquals(api._complete_url("http://host:1234"), "http://host:1234")
+        self.assertEquals(api._complete_url("https://host"), "https://host")
+        self.assertEquals(api._complete_url("https://host:1234"), "https://host:1234")
+
+        # test relative urls
+        api.remote_url = "http://host"
+        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
+                          "http://host/v1/path_to_file.txt")
+
+        api.remote_url = "http://host:1234"
+        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
+                          "http://host:1234/v1/path_to_file.txt")
+
+        api.remote_url = "https://host"
+        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
+                          "https://host/v1/path_to_file.txt")
+
+        api.remote_url = "https://host:1234"
+        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
+                          "https://host:1234/v1/path_to_file.txt")
+
+        # test relative urls with subdirectory
+        api.remote_url = "https://host:1234/subdir/"
+        self.assertEquals(api._complete_url("v1/path_to_file.txt"),
+                          "https://host:1234/subdir/v1/path_to_file.txt")
+
     def server_info_test(self):
         check, version, capabilities = self.api.server_info()
         self.assertEquals(version, "0.16.0")
diff --git a/conans/test/server/conan_server_config_parser_test.py b/conans/test/server/conan_server_config_parser_test.py
index feaba8ac0..0b55a82aa 100644
--- a/conans/test/server/conan_server_config_parser_test.py
+++ b/conans/test/server/conan_server_config_parser_test.py
@@ -53,3 +53,20 @@ demo: %s
 
         server_config = ConanServerConfigParser(tmp_dir, environment={"CONAN_SERVER_USERS": "demo:manolito!@"})
         self.assertEquals(server_config.users, {"demo": "manolito!@"})
+
+    def test_relative_public_url(self):
+        tmp_dir = temp_folder()
+        server_conf = """
+[server]
+
+[write_permissions]
+
+[users]
+        """
+        server_dir = os.path.join(tmp_dir, ".conan_server")
+        mkdir(server_dir)
+        conf_path = os.path.join(server_dir, "server.conf")
+        save(conf_path, server_conf)
+
+        server_config = ConanServerConfigParser(tmp_dir)
+        self.assertEquals(server_config.public_url, "v1")
diff --git a/conans/test/server/conf_test.py b/conans/test/server/conf_test.py
index 3cf050945..f52c48736 100644
--- a/conans/test/server/conf_test.py
+++ b/conans/test/server/conf_test.py
@@ -16,6 +16,8 @@ jwt_expire_minutes: 121
 disk_storage_path: %s
 ssl_enabled: true
 port: 9220
+host_name: localhost
+public_port: 12345
 
 
 [write_permissions]
@@ -80,6 +82,9 @@ text=value
         self.assertEquals(config.read_permissions, [("*/*@*/*", "*"),
                                                     ("openssl/2.0.1@lasote/testing", "pepe")])
         self.assertEquals(config.users, {"lasote": "defaultpass", "pepe": "pepepass"})
+        self.assertEquals(config.host_name, "localhost")
+        self.assertEquals(config.public_port, 12345)
+        self.assertEquals(config.public_url, "https://localhost:12345/v1")
 
         # Now check with environments
         tmp_storage = temp_folder()
@@ -89,6 +94,8 @@ text=value
         self.environ["CONAN_SSL_ENABLED"] = "False"
         self.environ["CONAN_SERVER_PORT"] = "1233"
         self.environ["CONAN_SERVER_USERS"] = "lasote:lasotepass,pepe2:pepepass2"
+        self.environ["CONAN_HOST_NAME"] = "remotehost"
+        self.environ["CONAN_SERVER_PUBLIC_PORT"] = "33333"
 
         config = ConanServerConfigParser(self.file_path, environment=self.environ)
         self.assertEquals(config.jwt_secret,  "newkey")
@@ -100,3 +107,6 @@ text=value
         self.assertEquals(config.read_permissions, [("*/*@*/*", "*"),
                                                     ("openssl/2.0.1@lasote/testing", "pepe")])
         self.assertEquals(config.users, {"lasote": "lasotepass", "pepe2": "pepepass2"})
+        self.assertEquals(config.host_name, "remotehost")
+        self.assertEquals(config.public_port, 33333)
+        self.assertEquals(config.public_url, "http://remotehost:33333/v1")
diff --git a/conans/test/util/pkg_config_test.py b/conans/test/util/pkg_config_test.py
index ea0b5d578..3f51ffbb2 100644
--- a/conans/test/util/pkg_config_test.py
+++ b/conans/test/util/pkg_config_test.py
@@ -7,7 +7,6 @@ import os
 from nose.plugins.attrib import attr
 from conans.tools import PkgConfig, environment_append
 from conans.test.utils.test_files import temp_folder
-from conans.errors import ConanException
 
 libastral_pc = """
 PC FILE EXAMPLE:
@@ -27,13 +26,6 @@ Cflags: -I${includedir}/libastral -D_USE_LIBASTRAL
 
 @attr("unix")
 class PkgConfigTest(unittest.TestCase):
-    def test_negative(self):
-        if platform.system() == "Windows":
-            return
-        pc = PkgConfig('libsomething_that_does_not_exist_in_the_world')
-        with self.assertRaises(ConanException):
-            pc.libs()
-
     def test_pc(self):
         if platform.system() == "Windows":
             return
diff --git a/conans/test/utils/tools.py b/conans/test/utils/tools.py
index b8a1cb3b8..035e99488 100644
--- a/conans/test/utils/tools.py
+++ b/conans/test/utils/tools.py
@@ -175,7 +175,7 @@ class TestServer(object):
                  write_permissions=None, users=None, plugins=None, base_path=None,
                  server_version=Version(SERVER_VERSION),
                  min_client_compatible_version=Version(MIN_CLIENT_COMPATIBLE_VERSION),
-                 server_capabilities=None):
+                 server_capabilities=None, complete_urls=False):
         """
              'read_permissions' and 'write_permissions' is a list of:
                  [("opencv/2.3.4@lasote/testing", "user1, user2")]
@@ -195,9 +195,10 @@ class TestServer(object):
 
         self.fake_url = "http://fake%s.com" % str(uuid.uuid4()).replace("-", "")
         min_client_ver = min_client_compatible_version
+        base_url = "%s/v1" % self.fake_url if complete_urls else "v1"
         self.test_server = TestServerLauncher(base_path, read_permissions,
                                               write_permissions, users,
-                                              base_url=self.fake_url + "/v1",
+                                              base_url=base_url,
                                               plugins=plugins,
                                               server_version=server_version,
                                               min_client_compatible_version=min_client_ver,