Repository: conan-io/conan

Issue:
CMake helper should handle fPIC like Autotools helper is doing
To help us debug your issue please explain:

- [X] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [X] I've specified the Conan version, operating system version and any tool that can be relevant.
- [X] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

Conan version 1.0.4

If fPIC is an options in the recipe. Enable/Disable it depending on the compiler should be managed by CMake helper like autotools helper is doing.

Usually this check is needed.

```python
def build(self):
    cmake = CMake(self)
    ...
    if self.options.fPIC and self.settings.compiler != "Visual Studio":
            cmake.definitions["CMAKE_CXX_FLAGS"] = "-fPIC"
            cmake.definitions["CMAKE_C_FLAGS"] = "-fPIC"
```

Example with autotools helper:

```
from conans import ConanFile, AutoToolsBuildEnvironment

class ExampleConan(ConanFile):
   ...

   def build(self):
      env_build = AutoToolsBuildEnvironment(self)
      env_build.fpic = True
      env_build.libs.append("pthread")
      env_build.defines.append("NEW_DEFINE=23")
      env_build.configure()
      env_build.make()
```




Discussion:
consider using this property https://cmake.org/cmake/help/v3.10/variable/CMAKE_POSITION_INDEPENDENT_CODE.html
@SSE4 thanks we will take a look to the compatibility and will use it when possible. 
In the example above anyway should be CONAN_CXX_FLAGS, not directly cmake.
Related to #2476. Take a decision before implementing it based on the option.
It is not clear we want to build this into the helper while the fPIC "standardization" is not there yet.
I will move this to 1.3, waiting for that.

Resolving patch:
diff --git a/conans/client/build/cmake.py b/conans/client/build/cmake.py
--- a/conans/client/build/cmake.py
+++ b/conans/client/build/cmake.py
@@ -323,6 +323,14 @@ def add_cmake_flag(cmake_flags, name, flag):
                 cpus = tools.cpu_count()
                 ret["CONAN_CXX_FLAGS"] = "/MP%s" % cpus
                 ret["CONAN_C_FLAGS"] = "/MP%s" % cpus
+
+        # fpic
+        if str(self._os) not in ["Windows", "WindowsStore"]:
+            fpic = self._conanfile.options.get_safe("fPIC")
+            if fpic is not None:
+                shared = self._conanfile.options.get_safe("shared")
+                ret["CONAN_CMAKE_POSITION_INDEPENDENT_CODE"] = "ON" if (fpic or shared) else "OFF"
+
         return ret
 
     def _get_dirs(self, source_folder, build_folder, source_dir, build_dir, cache_build_folder):
diff --git a/conans/client/generators/cmake_common.py b/conans/client/generators/cmake_common.py
--- a/conans/client/generators/cmake_common.py
+++ b/conans/client/generators/cmake_common.py
@@ -269,6 +269,13 @@ def generate_targets_section(dependencies):
     endif()
 endmacro()
 
+macro(conan_set_fpic)
+    if(DEFINED CONAN_CMAKE_POSITION_INDEPENDENT_CODE)
+        message(STATUS "Conan: Adjusting fPIC flag (${CONAN_CMAKE_POSITION_INDEPENDENT_CODE})")
+        set(CMAKE_POSITION_INDEPENDENT_CODE ${CONAN_CMAKE_POSITION_INDEPENDENT_CODE})
+    endif()
+endmacro()
+
 macro(conan_output_dirs_setup)
     set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
     set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
@@ -490,7 +497,7 @@ def generate_targets_section(dependencies):
 
 cmake_macros = """
 macro(conan_basic_setup)
-    set(options TARGETS NO_OUTPUT_DIRS SKIP_RPATH KEEP_RPATHS SKIP_STD)
+    set(options TARGETS NO_OUTPUT_DIRS SKIP_RPATH KEEP_RPATHS SKIP_STD SKIP_FPIC)
     cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
     if(CONAN_EXPORTED)
         message(STATUS "Conan: called by CMake conan helper")
@@ -520,6 +527,9 @@ def generate_targets_section(dependencies):
         message(STATUS "Conan: Adjusting language standard")
         conan_set_std()
     endif()
+    if(NOT ARGUMENTS_SKIP_FPIC)
+        conan_set_fpic()
+    endif()
     conan_set_vs_runtime()
     conan_set_libcxx()
     conan_set_find_paths()
@@ -615,6 +625,7 @@ def generate_targets_section(dependencies):
     conan_set_vs_runtime()
     conan_set_libcxx()
     conan_set_find_paths()
+    conan_set_fpic()
 endmacro()
 
 macro(conan_set_vs_runtime)
diff --git a/conans/model/options.py b/conans/model/options.py
--- a/conans/model/options.py
+++ b/conans/model/options.py
@@ -365,6 +365,11 @@ def __contains__(self, option):
     def loads(text):
         return PackageOptions(yaml.load(text) or {})
 
+    def get_safe(self, field):
+        if field not in self._data:
+            return None
+        return self._data[field]
+
     def validate(self):
         for child in self._data.values():
             child.validate()


Test patch:
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
@@ -77,6 +77,61 @@ def cmake_generator_test(self):
             cmake = CMake(conan_file)
             self.assertIn('-G "My CMake Generator"', cmake.command_line)
 
+    def cmake_fpic_test(self):
+        settings = Settings.loads(default_settings_yml)
+        settings.os = "Linux"
+        settings.compiler = "gcc"
+        settings.compiler.version = "6.3"
+        settings.arch = "x86"
+
+        def assert_fpic(the_settings, input_shared, input_fpic, expected_option):
+            options = []
+            values = {}
+            if input_shared is not None:
+                options.append('"shared": [True, False]')
+                values["shared"] = input_shared
+            if input_fpic is not None:
+                options.append('"fPIC": [True, False]')
+                values["fPIC"] = input_fpic
+
+            conan_file = ConanFileMock(options='{%s}' % ", ".join(options),
+                                       options_values=values)
+            conan_file.settings = the_settings
+            cmake = CMake(conan_file)
+            cmake.configure()
+            if expected_option is not None:
+                self.assertEquals(cmake.definitions["CONAN_CMAKE_POSITION_INDEPENDENT_CODE"],
+                                  expected_option)
+            else:
+                self.assertNotIn("CONAN_CMAKE_POSITION_INDEPENDENT_CODE", cmake.definitions)
+
+        # Test shared=False and fpic=False
+        assert_fpic(settings, input_shared=False, input_fpic=False, expected_option="OFF")
+
+        # Test shared=True and fpic=False
+        assert_fpic(settings, input_shared=True, input_fpic=False, expected_option="ON")
+
+        # Test shared=True and fpic=True
+        assert_fpic(settings, input_shared=True, input_fpic=True, expected_option="ON")
+
+        # Test shared not defined and fpic=True
+        assert_fpic(settings, input_shared=None, input_fpic=True, expected_option="ON")
+
+        # Test shared not defined and fpic not defined
+        assert_fpic(settings, input_shared=None, input_fpic=None, expected_option=None)
+
+        # Test shared True and fpic not defined
+        assert_fpic(settings, input_shared=True, input_fpic=None, expected_option=None)
+
+        # Test nothing in Windows
+        settings = Settings.loads(default_settings_yml)
+        settings.os = "Windows"
+        settings.compiler = "Visual Studio"
+        settings.compiler.version = "15"
+        settings.arch = "x86_64"
+
+        assert_fpic(settings, input_shared=True, input_fpic=True, expected_option=None)
+
     def cmake_make_program_test(self):
         settings = Settings.loads(default_settings_yml)
         settings.os = "Linux"
@@ -725,12 +780,16 @@ def scape(args):
 
 
 class ConanFileMock(ConanFile):
-    def __init__(self, shared=None):
+    def __init__(self, shared=None, options=None, options_values=None):
+        options = options or ""
         self.command = None
         self.path = None
         self.source_folder = self.build_folder = "."
         self.settings = None
-        self.options = Options(PackageOptions.loads(""))
+        self.options = Options(PackageOptions.loads(options))
+        if options_values:
+            for var, value in options_values.items():
+                self.options._data[var] = value
         self.deps_cpp_info = namedtuple("deps_cpp_info", "sysroot")("/path/to/sysroot")
         self.output = TestBufferConanOutput()
         self.in_local_cache = False
diff --git a/conans/test/generators/cmake_test.py b/conans/test/generators/cmake_test.py
--- a/conans/test/generators/cmake_test.py
+++ b/conans/test/generators/cmake_test.py
@@ -135,7 +135,7 @@ def aux_cmake_test_setup_test(self):
         # extract the conan_basic_setup macro
         macro = self._extract_macro("conan_basic_setup", aux_cmake_test_setup)
         self.assertEqual("""macro(conan_basic_setup)
-    set(options TARGETS NO_OUTPUT_DIRS SKIP_RPATH KEEP_RPATHS SKIP_STD)
+    set(options TARGETS NO_OUTPUT_DIRS SKIP_RPATH KEEP_RPATHS SKIP_STD SKIP_FPIC)
     cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
     if(CONAN_EXPORTED)
         message(STATUS "Conan: called by CMake conan helper")
@@ -165,6 +165,9 @@ def aux_cmake_test_setup_test(self):
         message(STATUS "Conan: Adjusting language standard")
         conan_set_std()
     endif()
+    if(NOT ARGUMENTS_SKIP_FPIC)
+        conan_set_fpic()
+    endif()
     conan_set_vs_runtime()
     conan_set_libcxx()
     conan_set_find_paths()
diff --git a/conans/test/integration/cmake_flags_test.py b/conans/test/integration/cmake_flags_test.py
--- a/conans/test/integration/cmake_flags_test.py
+++ b/conans/test/integration/cmake_flags_test.py
@@ -340,3 +340,49 @@ def build(self):
         libname = "libmylib.a" if platform.system() != "Windows" else "mylib.lib"
         libpath = os.path.join(client.current_folder, "build", "lib", libname)
         self.assertTrue(os.path.exists(libpath))
+
+    def fpic_applied_test(self):
+        conanfile = """
+import os
+from conans import ConanFile, CMake
+class MyLib(ConanFile):
+    name = "MyLib"
+    version = "0.1"
+    settings = "arch", "compiler"
+    options = {"fPIC": [True, False]}
+    default_options = "fPIC=False"
+    generators = "cmake"
+    exports_sources = "CMakeLists.txt"
+
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+"""
+        cmakelists = """
+set(CMAKE_CXX_COMPILER_WORKS 1)
+set(CMAKE_CXX_ABI_COMPILED 1)
+project(MyHello CXX)
+cmake_minimum_required(VERSION 2.8.12)
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup()
+"""
+        client = TestClient()
+        client.save({"conanfile.py": conanfile,
+                     "CMakeLists.txt": cmakelists})
+
+        client.run("create . user/channel -o MyLib:fPIC=True")
+        self.assertIn("Conan: Adjusting fPIC flag (ON)", client.out)
+
+        client.run("create . user/channel -o MyLib:fPIC=False")
+        self.assertIn("Conan: Adjusting fPIC flag (OFF)", client.out)
+
+        client.save({"conanfile.py": conanfile.replace("fPIC", "fpic")}, clean_first=False)
+        client.run("create . user/channel -o MyLib:fpic=True")
+        self.assertNotIn("Conan: Adjusting fPIC flag (ON)", client.out)
+
+        # Skip fpic adjustements in basic setup
+        tmp = cmakelists.replace("conan_basic_setup()", "conan_basic_setup(SKIP_FPIC)")
+        client.save({"CMakeLists.txt": tmp, "conanfile.py": conanfile}, clean_first=True)
+        client.run("create . user/channel -o MyLib:fPIC=True")
+        self.assertNotIn("Conan: Adjusting fPIC flag (ON)", client.out)