Repository: conan-io/conan

Issue:
Prepare 1.6 to fail gently with "conan_package" different compression extensions.
From https://github.com/conan-io/conan/issues/648 
To be ready for the future, versions from 1.6 could fail gently if a conan_package with a different extension (not tgz) is found in a remote package. Maybe saying: Your current conan version does not allow the `xxx` extension for conan package. Update to the latest Conan version to get support. 

Discussion:
memsharded: Weird failure @lasote, it seems Python3.6 does not support lzma on OSX? https://conan-ci.jfrog.info/blue/organizations/jenkins/ConanTestSuite/detail/PR-3197/2/pipeline/13
lasote: In the Mac slave, python is built from sources using pyenv. Apparently, when built from sources, you need to have the lzma library installed or python won't enable the module. I'll try to rebuild it. The distributed binary packages in home brew (which I have locally) have the lzma enabled.

Patch:
diff --git a/.ci/jenkins/conf.py b/.ci/jenkins/conf.py
index f59d4441e..02c9bc118 100644
--- a/.ci/jenkins/conf.py
+++ b/.ci/jenkins/conf.py
@@ -9,7 +9,7 @@ winpylocation = {"py27": "C:\\Python27\\python.exe",
 
 macpylocation = {"py27": "/usr/bin/python",  # /Users/jenkins_ci/.pyenv/versions/2.7.11/bin/python",
                  "py34": "/Users/jenkins_ci/.pyenv/versions/3.4.7/bin/python",
-                 "py36": "/Users/jenkins_ci/.pyenv/versions/3.6.3/bin/python"}
+                 "py36": "/Users/jenkins_ci/.pyenv/versions/3.6.5/bin/python"}
 
 linuxpylocation = {"py27": "/usr/bin/python2.7",
                    "py34": "/usr/bin/python3.4",
diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py
index dcecb3190..929174655 100644
--- a/conans/client/remote_manager.py
+++ b/conans/client/remote_manager.py
@@ -202,7 +202,8 @@ class RemoteManager(object):
         t1 = time.time()
 
         def filter_function(urls):
-            file_url = urls.get(EXPORT_SOURCES_TGZ_NAME)
+            file_url = urls.pop(EXPORT_SOURCES_TGZ_NAME, None)
+            check_compressed_files(EXPORT_SOURCES_TGZ_NAME, urls)
             if file_url:
                 urls = {EXPORT_SOURCES_TGZ_NAME: file_url}
             else:
@@ -340,8 +341,6 @@ def compress_package_files(files, symlinks, dest_folder, output):
 
 
 def compress_files(files, symlinks, name, dest_dir):
-    """Compress the package and returns the new dict (name => content) of files,
-    only with the conanXX files and the compressed file"""
     t1 = time.time()
     # FIXME, better write to disk sequentially and not keep tgz contents in memory
     tgz_path = os.path.join(dest_dir, name)
@@ -378,11 +377,20 @@ def compress_files(files, symlinks, name, dest_dir):
     return tgz_path
 
 
+def check_compressed_files(tgz_name, files):
+    bare_name = os.path.splitext(tgz_name)[0]
+    for f in files:
+        if bare_name == os.path.splitext(f)[0]:
+            raise ConanException("This Conan version is not prepared to handle '%s' file format. "
+                                 "Please upgrade conan client." % f)
+
+
 def unzip_and_get_files(files, destination_dir, tgz_name):
     """Moves all files from package_files, {relative_name: tmp_abs_path}
     to destination_dir, unzipping the "tgz_name" if found"""
 
     tgz_file = files.pop(tgz_name, None)
+    check_compressed_files(tgz_name, files)
     if tgz_file:
         uncompress_file(tgz_file, destination_dir)
         os.remove(tgz_file)
diff --git a/conans/client/tools/files.py b/conans/client/tools/files.py
index 5d6ddef97..0d42fe368 100644
--- a/conans/client/tools/files.py
+++ b/conans/client/tools/files.py
@@ -11,6 +11,7 @@ from conans.client.output import ConanOutput
 from conans.errors import ConanException
 from conans.util.files import (load, save, _generic_algorithm_sum)
 from conans.unicode import get_cwd
+import six
 
 
 _global_output = None
@@ -68,6 +69,11 @@ def unzip(filename, destination=".", keep_permissions=False, pattern=None):
             filename.endswith(".tbz2") or filename.endswith(".tar.bz2") or
             filename.endswith(".tar")):
         return untargz(filename, destination, pattern)
+    if filename.endswith(".tar.xz") or filename.endswith(".txz"):
+        if six.PY2:
+            raise ConanException("XZ format not supported in Python 2. Use Python 3 instead")
+        return untargz(filename, destination, pattern)
+
     import zipfile
     full_path = os.path.normpath(os.path.join(get_cwd(), destination))
 
diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py
index 455397546..17557672b 100644
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -12,6 +12,8 @@ from conans.model.user_info import DepsUserInfo
 from conans.paths import RUN_LOG_NAME
 from conans.tools import environment_append, no_op
 from conans.client.output import Color
+from conans.client.run_environment import RunEnvironment
+from conans.client.tools.oss import os_info
 
 
 def create_options(conanfile):
@@ -255,15 +257,23 @@ class ConanFile(object):
         """ define cpp_build_info, flags, etc
         """
 
-    def run(self, command, output=True, cwd=None, win_bash=False, subsystem=None, msys_mingw=True):
-        if not win_bash:
-            retcode = self._runner(command, output, os.path.abspath(RUN_LOG_NAME), cwd)
-        else:
+    def run(self, command, output=True, cwd=None, win_bash=False, subsystem=None, msys_mingw=True,
+            ignore_errors=False, run_environment=False):
+        def _run():
+            if not win_bash:
+                return self._runner(command, output, os.path.abspath(RUN_LOG_NAME), cwd)
             # FIXME: run in windows bash is not using output
-            retcode = tools.run_in_windows_bash(self, bashcmd=command, cwd=cwd, subsystem=subsystem,
-                                                msys_mingw=msys_mingw)
+            return tools.run_in_windows_bash(self, bashcmd=command, cwd=cwd, subsystem=subsystem,
+                                             msys_mingw=msys_mingw)
+        if run_environment:
+            with tools.environment_append(RunEnvironment(self).vars):
+                if os_info.is_macos:
+                    command = 'DYLD_LIBRARY_PATH="%s" %s' % (os.environ.get('DYLD_LIBRARY_PATH', ''), command)
+                retcode = _run()
+        else:
+            retcode = _run()
 
-        if retcode != 0:
+        if not ignore_errors and retcode != 0:
             raise ConanException("Error %d while executing %s" % (retcode, command))
 
         return retcode
diff --git a/conans/server/rest/controllers/file_upload_download_controller.py b/conans/server/rest/controllers/file_upload_download_controller.py
index b49091a07..fe8ec36ab 100644
--- a/conans/server/rest/controllers/file_upload_download_controller.py
+++ b/conans/server/rest/controllers/file_upload_download_controller.py
@@ -20,7 +20,12 @@ class FileUploadDownloadController(Controller):
             token = request.query.get("signature", None)
             file_path = service.get_file_path(filepath, token)
             # https://github.com/kennethreitz/requests/issues/1586
-            mimetype = "x-gzip" if filepath.endswith(".tgz") else "auto"
+            if filepath.endswith(".tgz"):
+                mimetype = "x-gzip"
+            elif filepath.endswith(".txz"):
+                mimetype = "x-xz"
+            else:
+                mimetype = "auto"
             return static_file(os.path.basename(file_path),
                                root=os.path.dirname(file_path),
                                mimetype=mimetype)


Test patch:
diff --git a/conans/test/functional/runner_test.py b/conans/test/functional/runner_test.py
index f0dc4fda8..d00f9eb71 100644
--- a/conans/test/functional/runner_test.py
+++ b/conans/test/functional/runner_test.py
@@ -2,7 +2,6 @@ import os
 import six
 import unittest
 
-from io import StringIO
 from conans.client.runner import ConanRunner
 from conans.test.utils.tools import TestClient
 
@@ -19,6 +18,18 @@ class RunnerTest(unittest.TestCase):
         client.run("build .")
         return client
 
+    def ignore_error_test(self):
+        conanfile = """from conans import ConanFile
+class Pkg(ConanFile):
+    def source(self):
+        ret = self.run("not_a_command", ignore_errors=True)
+        self.output.info("RETCODE %s" % (ret!=0))
+"""
+        client = TestClient()
+        client.save({"conanfile.py": conanfile})
+        client.run("source .")
+        self.assertIn("RETCODE True", client.out)
+
     def basic_test(self):
         conanfile = '''
 from conans import ConanFile
diff --git a/conans/test/integration/run_envronment_test.py b/conans/test/integration/run_envronment_test.py
index 48a3638b9..efd78257b 100644
--- a/conans/test/integration/run_envronment_test.py
+++ b/conans/test/integration/run_envronment_test.py
@@ -34,3 +34,68 @@ class HelloConan(ConanFile):
         client.save({"conanfile.py": reuse}, clean_first=True)
         client.run("install . --build missing")
         client.run("build .")
+        self.assertIn("Hello Hello0", client.out)
+
+    def test_shared_run_environment(self):
+        client = TestClient()
+        cmake = """set(CMAKE_CXX_COMPILER_WORKS 1)
+set(CMAKE_CXX_ABI_COMPILED 1)
+project(MyHello CXX)
+cmake_minimum_required(VERSION 2.8.12)
+
+add_library(hello SHARED hello.cpp)
+add_executable(say_hello main.cpp)
+target_link_libraries(say_hello hello)"""
+        hello_h = """#ifdef WIN32
+  #define HELLO_EXPORT __declspec(dllexport)
+#else
+  #define HELLO_EXPORT
+#endif
+
+HELLO_EXPORT void hello();
+"""
+        hello_cpp = r"""#include "hello.h"
+#include <iostream>
+void hello(){
+    std::cout<<"Hello Tool!\n";
+}
+"""
+        main = """#include "hello.h"
+        int main(){
+            hello();
+        }
+        """
+        conanfile = """from conans import ConanFile, CMake
+class Pkg(ConanFile):
+    exports_sources = "*"
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+
+    def package(self):
+        self.copy("*say_hello.exe", dst="bin", keep_path=False)
+        self.copy("*say_hello", dst="bin", keep_path=False)
+        self.copy(pattern="*.dll", dst="bin", keep_path=False)
+        self.copy(pattern="*.dylib", dst="lib", keep_path=False)
+        self.copy(pattern="*.so", dst="lib", keep_path=False)
+"""
+        client.save({"conanfile.py": conanfile,
+                     "CMakeLists.txt": cmake,
+                     "main.cpp": main,
+                     "hello.cpp": hello_cpp,
+                     "hello.h": hello_h})
+        client.run("create . Pkg/0.1@user/testing")
+
+        reuse = '''from conans import ConanFile
+class HelloConan(ConanFile):
+    requires = "Pkg/0.1@user/testing"
+
+    def build(self):
+        self.run("say_hello", run_environment=True)
+'''
+
+        client.save({"conanfile.py": reuse}, clean_first=True)
+        client.run("install .")
+        client.run("build .")
+        self.assertIn("Hello Tool!", client.out)
diff --git a/conans/test/util/xz_test.py b/conans/test/util/xz_test.py
new file mode 100644
index 000000000..262549736
--- /dev/null
+++ b/conans/test/util/xz_test.py
@@ -0,0 +1,88 @@
+import os
+from unittest import TestCase
+import six
+import unittest
+import tarfile
+
+from conans.test.utils.test_files import temp_folder
+from conans.tools import unzip, save
+from conans.util.files import load, save_files
+from conans.errors import ConanException
+from conans.test.utils.tools import TestClient, TestServer
+from conans.model.ref import ConanFileReference, PackageReference
+
+
+class XZTest(TestCase):
+    def test_error_xz(self):
+        server = TestServer()
+        ref = ConanFileReference.loads("Pkg/0.1@user/channel")
+        export = server.paths.export(ref)
+        save_files(export, {"conanfile.py": "#",
+                            "conanmanifest.txt": "#",
+                            "conan_export.txz": "#"})
+        client = TestClient(servers={"default": server},
+                            users={"default": [("lasote", "mypass")]})
+        error = client.run("install Pkg/0.1@user/channel", ignore_error=True)
+        self.assertTrue(error)
+        self.assertIn("ERROR: This Conan version is not prepared to handle "
+                      "'conan_export.txz' file format", client.out)
+
+    def test_error_sources_xz(self):
+        server = TestServer()
+        ref = ConanFileReference.loads("Pkg/0.1@user/channel")
+        client = TestClient(servers={"default": server},
+                            users={"default": [("lasote", "mypass")]})
+        export = server.paths.export(ref)
+        conanfile = """from conans import ConanFile
+class Pkg(ConanFile):
+    exports_sources = "*"
+"""
+        save_files(export, {"conanfile.py": conanfile,
+                            "conanmanifest.txt": "1",
+                            "conan_sources.txz": "#"})
+        error = client.run("install Pkg/0.1@user/channel --build", ignore_error=True)
+        self.assertTrue(error)
+        self.assertIn("ERROR: This Conan version is not prepared to handle "
+                      "'conan_sources.txz' file format", client.out)
+
+    def test_error_package_xz(self):
+        server = TestServer()
+        ref = ConanFileReference.loads("Pkg/0.1@user/channel")
+        client = TestClient(servers={"default": server},
+                            users={"default": [("lasote", "mypass")]})
+        export = server.paths.export(ref)
+        conanfile = """from conans import ConanFile
+class Pkg(ConanFile):
+    exports_sources = "*"
+"""
+        save_files(export, {"conanfile.py": conanfile,
+                            "conanmanifest.txt": "1"})
+        pkg_ref = PackageReference(ref, "5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9")
+        package = server.paths.package(pkg_ref)
+        save_files(package, {"conaninfo.txt": "#",
+                             "conanmanifest.txt": "1",
+                             "conan_package.txz": "#"})
+        error = client.run("install Pkg/0.1@user/channel", ignore_error=True)
+        self.assertTrue(error)
+        self.assertIn("ERROR: This Conan version is not prepared to handle "
+                      "'conan_package.txz' file format", client.out)
+
+    @unittest.skipUnless(six.PY3, "only Py3")
+    def test(self):
+        tmp_dir = temp_folder()
+        file_path = os.path.join(tmp_dir, "a_file.txt")
+        save(file_path, "my content!")
+        txz = os.path.join(tmp_dir, "sample.tar.xz")
+        with tarfile.open(txz, "w:xz") as tar:
+            tar.add(file_path, "a_file.txt")
+
+        dest_folder = temp_folder()
+        unzip(txz, dest_folder)
+        content = load(os.path.join(dest_folder, "a_file.txt"))
+        self.assertEqual(content, "my content!")
+
+    @unittest.skipUnless(six.PY2, "only Py2")
+    def test_error_python2(self):
+        with self.assertRaisesRegexp(ConanException, "XZ format not supported in Python 2"):
+            dest_folder = temp_folder()
+            unzip("somefile.tar.xz", dest_folder)