Repository: conan-io/conan

Issue:
[build system] Set a default value for GNU standard installation directories
After the agreement on #3388 we should make sure that the GNU installation dirs are set to a default value by the build helpers:

- CMake: https://cmake.org/cmake/help/v3.0/module/GNUInstallDirs.html
- Autotools: https://www.gnu.org/prep/standards/html_node/Directory-Variables.html

The way I see it those default values should be:

- BINDIR, SBINDIR and LIBEXECDIR --> bin
- INCLUDEDIR, OLDINCLUDEDIR --> include
- LIBDIR --> lib (already done in #3388 )
- DATAROOTDIR --> rootdir **??** (to make sure everything is installed inside pacakge folder, or is it enough to set `--prefix` for that?)




Discussion:

Resolving patch:
diff --git a/conans/client/build/autotools_environment.py b/conans/client/build/autotools_environment.py
--- a/conans/client/build/autotools_environment.py
+++ b/conans/client/build/autotools_environment.py
@@ -9,6 +9,7 @@
                                                 build_type_flags, libcxx_flag, build_type_define,
                                                 libcxx_define, pic_flag, rpath_flags)
 from conans.client.build.cppstd_flags import cppstd_flag
+from conans.model.build_info import DEFAULT_BIN, DEFAULT_LIB, DEFAULT_INCLUDE, DEFAULT_RES
 from conans.client.tools.oss import OSInfo
 from conans.client.tools.win import unix_path
 from conans.tools import (environment_append, args_to_string, cpu_count, cross_building,
@@ -95,7 +96,7 @@ def _get_host_build_target_flags(self):
         return build, host, None
 
     def configure(self, configure_dir=None, args=None, build=None, host=None, target=None,
-                  pkg_config_paths=None, vars=None):
+                  pkg_config_paths=None, vars=None, use_default_install_dirs=True):
         """
         :param pkg_config_paths: Optional paths to locate the *.pc files
         :param configure_dir: Absolute or relative path to the configure script
@@ -106,10 +107,10 @@ def configure(self, configure_dir=None, args=None, build=None, host=None, target
                        "False" skips the --target flag
                        When the tool chain generates executable program, in which target system
                        the program will run.
-        :return: None
 
         http://jingfenghanmax.blogspot.com.es/2010/09/configure-with-host-target-and-build.html
         https://gcc.gnu.org/onlinedocs/gccint/Configure-Terms.html
+        :param use_default_install_dirs: Use or not the defaulted installation dirs
 
         """
         if not self._conanfile.should_configure:
@@ -146,11 +147,20 @@ def configure(self, configure_dir=None, args=None, build=None, host=None, target
         if self._conanfile.package_folder is not None:
             if not args:
                 args = ["--prefix=%s" % self._conanfile.package_folder.replace("\\", "/")]
-            elif not any(["--prefix=" in arg for arg in args]):
+            elif not self._is_flag_in_args("prefix", args):
                 args.append("--prefix=%s" % self._conanfile.package_folder.replace("\\", "/"))
 
-            if not any(["--libdir=" in arg for arg in args]):
-                args.append("--libdir=${prefix}/lib")
+            if use_default_install_dirs:
+                for varname in ["bindir", "sbin", "libexec"]:
+                    if not self._is_flag_in_args(varname, args):
+                        args.append("--%s=${prefix}/%s" % (varname, DEFAULT_BIN))
+                if not self._is_flag_in_args("libdir", args):
+                    args.append("--libdir=${prefix}/%s" % DEFAULT_LIB)
+                for varname in ["includedir", "oldincludedir"]:
+                    if not self._is_flag_in_args(varname, args):
+                        args.append("--%s=${prefix}/%s" % (varname, DEFAULT_INCLUDE))
+                if not self._is_flag_in_args("datarootdir", args):
+                    args.append("--datarootdir=${prefix}/%s" % DEFAULT_RES)
 
         with environment_append(pkg_env):
             with environment_append(vars or self.vars):
@@ -167,6 +177,11 @@ def _adjust_path(self, path):
             path = unix_path(path, path_flavor=self.subsystem)
         return '"%s"' % path if " " in path else path
 
+    @staticmethod
+    def _is_flag_in_args(varname, args):
+        flag = "--%s=" % varname
+        return any([flag in arg for arg in args])
+
     def make(self, args="", make_program=None, target=None, vars=None):
         if not self._conanfile.should_build:
             return
diff --git a/conans/client/build/cmake_flags.py b/conans/client/build/cmake_flags.py
--- a/conans/client/build/cmake_flags.py
+++ b/conans/client/build/cmake_flags.py
@@ -6,6 +6,7 @@
 from conans.client.build.cppstd_flags import cppstd_flag
 from conans.client.tools import cross_building
 from conans.client.tools.oss import get_cross_building_settings
+from conans.model.build_info import DEFAULT_BIN, DEFAULT_LIB, DEFAULT_INCLUDE, DEFAULT_RES
 from conans.errors import ConanException
 from conans.util.env_reader import get_env
 from conans.util.log import logger
@@ -277,6 +278,13 @@ def get_definitions(self):
         try:
             if self._conanfile.package_folder:
                 ret["CMAKE_INSTALL_PREFIX"] = self._conanfile.package_folder
+                ret["CMAKE_INSTALL_BINDIR"] = DEFAULT_BIN
+                ret["CMAKE_INSTALL_SBINDIR"] = DEFAULT_BIN
+                ret["CMAKE_INSTALL_LIBEXECDIR"] = DEFAULT_BIN
+                ret["CMAKE_INSTALL_LIBDIR"] = DEFAULT_LIB
+                ret["CMAKE_INSTALL_INCLUDEDIR"] = DEFAULT_INCLUDE
+                ret["CMAKE_INSTALL_OLDINCLUDEDIR"] = DEFAULT_INCLUDE
+                ret["CMAKE_INSTALL_DATAROOTDIR"] = DEFAULT_RES
         except AttributeError:
             pass
 


Test patch:
diff --git a/conans/test/build_helpers/autotools_configure_test.py b/conans/test/build_helpers/autotools_configure_test.py
--- a/conans/test/build_helpers/autotools_configure_test.py
+++ b/conans/test/build_helpers/autotools_configure_test.py
@@ -6,7 +6,7 @@
 from conans import tools
 from conans.client.build.autotools_environment import AutoToolsBuildEnvironment
 from conans.client.tools.oss import cpu_count
-from conans.model.ref import ConanFileReference
+from conans.model.ref import ConanFileReference, PackageReference
 from conans.model.settings import Settings
 from conans.paths import CONANFILE
 from conans.test.build_helpers.cmake_test import ConanFileMock
@@ -514,43 +514,58 @@ def test_make_targets_install(self):
         ab.install()
         self.assertEquals(runner.command_called, "make install -j%s" % cpu_count())
 
-    def autotools_prefix_libdir_test(self):
+    def autotools_install_dirs_test(self):
         runner = RunnerMock()
         conanfile = MockConanfile(MockSettings({}), None, runner)
         # Package folder is not defined
         ab = AutoToolsBuildEnvironment(conanfile)
         ab.configure()
         self.assertNotIn("--prefix", runner.command_called)
+        self.assertNotIn("--bindir", runner.command_called)
         self.assertNotIn("--libdir", runner.command_called)
+        self.assertNotIn("--includedir", runner.command_called)
+        self.assertNotIn("--dataroot", runner.command_called)
         # package folder defined
         conanfile.package_folder = "/package_folder"
         ab.configure()
         if platform.system() == "Windows":
-            self.assertIn("./configure --prefix=/package_folder --libdir=${prefix}/lib",
-                          runner.command_called)
+            self.assertIn("./configure --prefix=/package_folder --bindir=${prefix}/bin "
+                          "--sbin=${prefix}/bin --libexec=${prefix}/bin --libdir=${prefix}/lib "
+                          "--includedir=${prefix}/include --oldincludedir=${prefix}/include "
+                          "--datarootdir=${prefix}/res", runner.command_called)
         else:
-            self.assertIn("./configure '--prefix=/package_folder' '--libdir=${prefix}/lib'",
-                          runner.command_called)
+            self.assertIn("./configure '--prefix=/package_folder' '--bindir=${prefix}/bin' "
+                          "'--sbin=${prefix}/bin' '--libexec=${prefix}/bin' '--libdir=${prefix}/lib' "
+                          "'--includedir=${prefix}/include' '--oldincludedir=${prefix}/include' "
+                          "'--datarootdir=${prefix}/res'", runner.command_called)
         # --prefix already used in args
         ab.configure(args=["--prefix=/my_package_folder"])
+        self.assertIn("--prefix=/my_package_folder", runner.command_called)
+        self.assertNotIn("--prefix=/package_folder", runner.command_called)
+        # --bindir, --libdir, --includedir already used in args
+        ab.configure(args=["--bindir=/pf/superbindir", "--libdir=/pf/superlibdir",
+                           "--includedir=/pf/superincludedir"])
+        self.assertNotIn("--bindir=${prefix}/bin", runner.command_called)
+        self.assertNotIn("--libdir=${prefix}/lib", runner.command_called)
+        self.assertNotIn("--includedir=${prefix}/lib", runner.command_called)
         if platform.system() == "Windows":
-            self.assertIn("./configure --prefix=/my_package_folder --libdir=${prefix}/lib",
+            self.assertIn("./configure --bindir=/pf/superbindir --libdir=/pf/superlibdir "
+                          "--includedir=/pf/superincludedir --prefix=/package_folder "
+                          "--sbin=${prefix}/bin --libexec=${prefix}/bin "
+                          "--oldincludedir=${prefix}/include --datarootdir=${prefix}/res",
                           runner.command_called)
-            self.assertNotIn("--prefix=/package_folder", runner.command_called)
         else:
-            self.assertIn("./configure '--prefix=/my_package_folder' '--libdir=${prefix}/lib'",
+            self.assertIn("./configure '--bindir=/pf/superbindir' '--libdir=/pf/superlibdir' "
+                          "'--includedir=/pf/superincludedir' '--prefix=/package_folder' "
+                          "'--sbin=${prefix}/bin' '--libexec=${prefix}/bin' "
+                          "'--oldincludedir=${prefix}/include' '--datarootdir=${prefix}/res'",
                           runner.command_called)
-            self.assertNotIn("'--prefix=/package_folder'", runner.command_called)
-        # --libdir already used in args
-        ab.configure(args=["--libdir=/my_package_folder/superlibdir"])
-        if platform.system() == "Windows":
-            self.assertIn("./configure --libdir=/my_package_folder/superlibdir "
-                          "--prefix=/package_folder", runner.command_called)
-            self.assertNotIn("--libdir=${prefix}/lib", runner.command_called)
-        else:
-            self.assertIn("./configure '--libdir=/my_package_folder/superlibdir' "
-                          "'--prefix=/package_folder'", runner.command_called)
-            self.assertNotIn("'--libdir=${prefix}/lib'", runner.command_called)
+        # opt-out from default installation dirs
+        ab.configure(use_default_install_dirs=False)
+        self.assertIn("--prefix=/package_folder", runner.command_called)
+        self.assertNotIn("--bindir=${prefix}/bin", runner.command_called)
+        self.assertNotIn("--libdir=${prefix}/lib", runner.command_called)
+        self.assertNotIn("--includedir=${prefix}/lib", runner.command_called)
 
     def autotools_configure_vars_test(self):
         from mock import patch
@@ -621,3 +636,86 @@ def autotools_fpic_test(self):
         self.assertFalse(ab.fpic)
         ab.fpic = True
         self.assertIn("-fPIC", ab.vars["CXXFLAGS"])
+
+    @unittest.skipUnless(platform.system() == "Linux", "Requires make")
+    def autotools_real_install_dirs_test(self):
+        body = r"""#include "hello.h"
+#include <iostream>
+using namespace std;
+
+void hello()
+{
+    cout << "Hola Mundo!";
+}
+"""
+        header = """
+#pragma once
+void hello();
+"""
+        main = """
+#include "hello.h"
+
+int main()
+{
+    hello();
+    return 0;
+}
+"""
+        conanfile = """
+from conans import ConanFile, AutoToolsBuildEnvironment, tools
+
+class TestConan(ConanFile):
+    name = "test"
+    version = "1.0"
+    settings = "os", "compiler", "arch", "build_type"
+    exports_sources = "*"
+
+    def build(self):
+        makefile_am = '''
+bin_PROGRAMS = main
+lib_LIBRARIES = libhello.a
+libhello_a_SOURCES = hello.cpp
+main_SOURCES = main.cpp
+main_LDADD = libhello.a
+'''
+        configure_ac = '''
+AC_INIT([main], [1.0], [luism@jfrog.com])
+AM_INIT_AUTOMAKE([-Wall -Werror foreign])
+AC_PROG_CXX
+AC_PROG_RANLIB
+AM_PROG_AR
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
+'''
+        tools.save("Makefile.am", makefile_am)
+        tools.save("configure.ac", configure_ac)
+        self.run("aclocal")
+        self.run("autoconf")
+        self.run("automake --add-missing --foreign")
+        autotools = AutoToolsBuildEnvironment(self)
+        autotools.configure()
+        autotools.make()
+        autotools.install()
+        
+    def package_id(self):
+        # easier to have same package_id for the test
+        self.info.header_only()
+"""
+        client = TestClient()
+        client.save({"conanfile.py": conanfile,
+                     "main.cpp": main,
+                     "hello.h": header,
+                     "hello.cpp": body})
+        client.run("create . danimtb/testing")
+        pkg_path = client.client_cache.package(
+            PackageReference.loads(
+                "test/1.0@danimtb/testing:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9"))
+
+        [self.assertIn(folder, os.listdir(pkg_path)) for folder in ["lib", "bin"]]
+
+        new_conanfile = conanfile.replace("autotools.configure()",
+                                          "autotools.configure(args=['--bindir=${prefix}/superbindir', '--libdir=${prefix}/superlibdir'])")
+        client.save({"conanfile.py": new_conanfile})
+        client.run("create . danimtb/testing")
+        [self.assertIn(folder, os.listdir(pkg_path)) for folder in ["superlibdir", "superbindir"]]
+        [self.assertNotIn(folder, os.listdir(pkg_path)) for folder in ["lib", "bin"]]
diff --git a/conans/test/build_helpers/cmake_test.py b/conans/test/build_helpers/cmake_test.py
--- a/conans/test/build_helpers/cmake_test.py
+++ b/conans/test/build_helpers/cmake_test.py
@@ -1003,6 +1003,32 @@ def test_cmake_system_version_android(self):
             cmake = CMake(conan_file)
             self.assertEquals(cmake.definitions["CMAKE_SYSTEM_VERSION"], "32")
 
+    def install_definitions_test(self):
+        conanfile = ConanFileMock()
+        conanfile.package_folder = None
+        conanfile.settings = Settings.loads(default_settings_yml)
+        install_defintions = {"CMAKE_INSTALL_PREFIX": conanfile.package_folder,
+                              "CMAKE_INSTALL_BINDIR": "bin",
+                              "CMAKE_INSTALL_SBINDIR": "bin",
+                              "CMAKE_INSTALL_SBINDIR": "bin",
+                              "CMAKE_INSTALL_LIBEXECDIR": "bin",
+                              "CMAKE_INSTALL_LIBDIR": "lib",
+                              "CMAKE_INSTALL_INCLUDEDIR": "include",
+                              "CMAKE_INSTALL_OLDINCLUDEDIR": "include",
+                              "CMAKE_INSTALL_DATAROOTDIR": "res"}
+
+        # Without package_folder
+        cmake = CMake(conanfile)
+        for key, value in cmake.definitions.items():
+            self.assertNotIn(key, install_defintions.keys())
+
+        # With package_folder
+        conanfile.package_folder = "my_package_folder"
+        install_defintions["CMAKE_INSTALL_PREFIX"] = conanfile.package_folder
+        cmake = CMake(conanfile)
+        for key, value in install_defintions.items():
+            self.assertEquals(cmake.definitions[key], value)
+
     @mock.patch('platform.system', return_value="Macos")
     def test_cmake_system_version_osx(self, _):
         settings = Settings.loads(default_settings_yml)