From 95646591d62966043f0bc25364211594adc8d963 Mon Sep 17 00:00:00 2001
From: Christian Pfeiffer <cpfeiffer@live.de>
Date: Wed, 22 Nov 2017 17:53:54 +0100
Subject: FindIconv: Add the FindIconv module.

This module provides abstraction over the various ways POSIX platforms
handle the iconv calls defined in POSIX.1-2001 and later versions.
---
 Help/manual/cmake-modules.7.rst     |   1 +
 Help/module/FindIconv.rst           |   1 +
 Help/release/dev/FindIconv.rst      |   4 ++
 Modules/FindIconv.cmake             | 133 ++++++++++++++++++++++++++++++++++++
 Tests/CMakeLists.txt                |   4 ++
 Tests/FindIconv/CMakeLists.txt      |  10 +++
 Tests/FindIconv/Test/CMakeLists.txt |  14 ++++
 Tests/FindIconv/Test/main.cxx       |  52 ++++++++++++++
 8 files changed, 219 insertions(+)
 create mode 100644 Help/module/FindIconv.rst
 create mode 100644 Help/release/dev/FindIconv.rst
 create mode 100644 Modules/FindIconv.cmake
 create mode 100644 Tests/FindIconv/CMakeLists.txt
 create mode 100644 Tests/FindIconv/Test/CMakeLists.txt
 create mode 100644 Tests/FindIconv/Test/main.cxx

diff --git a/Help/manual/cmake-modules.7.rst b/Help/manual/cmake-modules.7.rst
index 9fd92ec..694bae5 100644
--- a/Help/manual/cmake-modules.7.rst
+++ b/Help/manual/cmake-modules.7.rst
@@ -130,6 +130,7 @@ All Modules
    /module/FindIcotool
    /module/FindICU
    /module/FindImageMagick
+   /module/FindIconv
    /module/FindIntl
    /module/FindITK
    /module/FindJasper
diff --git a/Help/module/FindIconv.rst b/Help/module/FindIconv.rst
new file mode 100644
index 0000000..c1f3ed0
--- /dev/null
+++ b/Help/module/FindIconv.rst
@@ -0,0 +1 @@
+.. cmake-module:: ../../Modules/FindIconv.cmake
diff --git a/Help/release/dev/FindIconv.rst b/Help/release/dev/FindIconv.rst
new file mode 100644
index 0000000..98f2591
--- /dev/null
+++ b/Help/release/dev/FindIconv.rst
@@ -0,0 +1,4 @@
+FindIconv
+---------
+
+* A :module:`FindIconv` module was added to locate iconv support.
diff --git a/Modules/FindIconv.cmake b/Modules/FindIconv.cmake
new file mode 100644
index 0000000..bf20f6f
--- /dev/null
+++ b/Modules/FindIconv.cmake
@@ -0,0 +1,133 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+#[=======================================================================[.rst:
+FindIconv
+---------
+
+This module finds the ``iconv()`` POSIX.1 functions on the system.
+These functions might be provided in the regular C library or externally
+in the form of an additional library.
+
+The following variables are provided to indicate iconv support:
+
+.. variable:: Iconv_FOUND
+
+  Variable indicating if the iconv support was found.
+
+.. variable:: Iconv_INCLUDE_DIRS
+
+  The directories containing the iconv headers.
+
+.. variable:: Iconv_LIBRARIES
+
+  The iconv libraries to be linked.
+
+.. variable:: Iconv_IS_BUILT_IN
+
+  A variable indicating whether iconv support is stemming from the
+  C library or not. Even if the C library provides `iconv()`, the presence of
+  an external `libiconv` implementation might lead to this being false.
+
+Additionally, the following :prop_tgt:`IMPORTED` target is being provided:
+
+.. variable:: Iconv::Iconv
+
+  Imported target for using iconv.
+
+The following cache variables may also be set:
+
+.. variable:: Iconv_INCLUDE_DIR
+
+  The directory containing the iconv headers.
+
+.. variable:: Iconv_LIBRARY
+
+  The iconv library (if not implicitly given in the C library).
+
+.. note::
+  On POSIX platforms, iconv might be part of the C library and the cache
+  variables ``Iconv_INCLUDE_DIR`` and ``Iconv_LIBRARY`` might be empty.
+
+#]=======================================================================]
+
+include(${CMAKE_CURRENT_LIST_DIR}/CMakePushCheckState.cmake)
+if(CMAKE_C_COMPILER_LOADED)
+  include(${CMAKE_CURRENT_LIST_DIR}/CheckCSourceCompiles.cmake)
+elseif(CMAKE_CXX_COMPILER_LOADED)
+  include(${CMAKE_CURRENT_LIST_DIR}/CheckCXXSourceCompiles.cmake)
+else()
+  # If neither C nor CXX are loaded, implicit iconv makes no sense.
+  set(Iconv_IS_BUILT_IN FALSE)
+endif()
+
+# iconv can only be provided in libc on a POSIX system.
+# If any cache variable is already set, we'll skip this test.
+if(NOT DEFINED Iconv_IS_BUILT_IN)
+  if(UNIX AND NOT DEFINED Iconv_INCLUDE_DIR AND NOT DEFINED Iconv_LIBRARY)
+    cmake_push_check_state(RESET)
+    # We always suppress the message here: Otherwise on supported systems
+    # not having iconv in their C library (e.g. those using libiconv)
+    # would always display a confusing "Looking for iconv - not found" message
+    set(CMAKE_FIND_QUIETLY TRUE)
+    # The following code will not work, but it's sufficient to see if it compiles.
+    # Note: libiconv will define the iconv functions as macros, so CheckSymbolExists
+    # will not yield correct results.
+    set(Iconv_IMPLICIT_TEST_CODE
+      "
+      #include <stddef.h>
+      #include <iconv.h>
+      int main() {
+        char *a, *b;
+        size_t i, j;
+        iconv_t ic;
+        ic = iconv_open(\"to\", \"from\");
+        iconv(ic, &a, &i, &b, &j);
+        iconv_close(ic);
+      }
+      "
+    )
+    if(CMAKE_C_COMPILER_LOADED)
+      check_c_source_compiles("${Iconv_IMPLICIT_TEST_CODE}" Iconv_IS_BUILT_IN)
+    else()
+      check_cxx_source_compiles("${Iconv_IMPLICIT_TEST_CODE}" Iconv_IS_BUILT_IN)
+    endif()
+    cmake_pop_check_state()
+  else()
+    set(Iconv_IS_BUILT_IN FALSE)
+  endif()
+endif()
+
+if(NOT Iconv_IS_BUILT_IN)
+  find_path(Iconv_INCLUDE_DIR
+    NAMES "iconv.h"
+    DOC "iconv include directory")
+  set(Iconv_LIBRARY_NAMES "iconv" "libiconv")
+else()
+  set(Iconv_INCLUDE_DIR "" CACHE FILEPATH "iconv include directory")
+  set(Iconv_LIBRARY_NAMES "c")
+endif()
+
+find_library(Iconv_LIBRARY
+  NAMES ${Iconv_LIBRARY_NAMES}
+  DOC "iconv library (potentially the C library)")
+
+mark_as_advanced(Iconv_INCLUDE_DIR)
+mark_as_advanced(Iconv_LIBRARY)
+
+include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
+if(NOT Iconv_IS_BUILT_IN)
+  find_package_handle_standard_args(Iconv REQUIRED_VARS Iconv_LIBRARY Iconv_INCLUDE_DIR)
+else()
+  find_package_handle_standard_args(Iconv REQUIRED_VARS Iconv_LIBRARY)
+endif()
+
+if(Iconv_FOUND)
+  set(Iconv_INCLUDE_DIRS "${Iconv_INCLUDE_DIR}")
+  set(Iconv_LIBRARIES "${Iconv_LIBRARY}")
+  if(NOT TARGET Iconv::Iconv)
+    add_library(Iconv::Iconv INTERFACE IMPORTED)
+  endif()
+  set_property(TARGET Iconv::Iconv PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${Iconv_INCLUDE_DIRS}")
+  set_property(TARGET Iconv::Iconv PROPERTY INTERFACE_LINK_LIBRARIES "${Iconv_LIBRARIES}")
+endif()
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 4a7b8c9..a61864f 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -1433,6 +1433,10 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
     add_subdirectory(GoogleTest)
   endif()
 
+  if(CMake_TEST_FindIconv)
+    add_subdirectory(FindIconv)
+  endif()
+
   if(CMake_TEST_FindICU)
     add_subdirectory(FindICU)
   endif()
diff --git a/Tests/FindIconv/CMakeLists.txt b/Tests/FindIconv/CMakeLists.txt
new file mode 100644
index 0000000..b205b80
--- /dev/null
+++ b/Tests/FindIconv/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_test(NAME FindIconv.Test COMMAND
+  ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+  --build-and-test
+  "${CMake_SOURCE_DIR}/Tests/FindIconv/Test"
+  "${CMake_BINARY_DIR}/Tests/FindIconv/Test"
+  ${build_generator_args}
+  --build-project TestFindIconv
+  --build-options ${build_options}
+  --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+  )
diff --git a/Tests/FindIconv/Test/CMakeLists.txt b/Tests/FindIconv/Test/CMakeLists.txt
new file mode 100644
index 0000000..c59adb3
--- /dev/null
+++ b/Tests/FindIconv/Test/CMakeLists.txt
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.10)
+project(TestFindIconv CXX)
+include(CTest)
+
+find_package(Iconv REQUIRED)
+
+add_executable(test_iconv_tgt main.cxx)
+target_link_libraries(test_iconv_tgt Iconv::Iconv)
+add_test(NAME test_iconv_tgt COMMAND test_iconv_tgt)
+
+add_executable(test_iconv_var main.cxx)
+target_include_directories(test_iconv_var PRIVATE ${Iconv_INCLUDE_DIRS})
+target_link_libraries(test_iconv_var PRIVATE ${Iconv_LIBRARIES})
+add_test(NAME test_iconv_var COMMAND test_iconv_var)
diff --git a/Tests/FindIconv/Test/main.cxx b/Tests/FindIconv/Test/main.cxx
new file mode 100644
index 0000000..415ee37
--- /dev/null
+++ b/Tests/FindIconv/Test/main.cxx
@@ -0,0 +1,52 @@
+extern "C" {
+#include <iconv.h>
+}
+#include <array>
+#include <cstddef>
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <system_error>
+
+class iconv_desc
+{
+private:
+  iconv_t iconvd_;
+
+public:
+  iconv_desc(const std::string& tocode, const std::string& fromcode)
+  {
+    iconvd_ = iconv_open(tocode.c_str(), fromcode.c_str());
+    if (iconvd_ == reinterpret_cast<iconv_t>(-1))
+      throw std::system_error(errno, std::system_category());
+  }
+
+  ~iconv_desc() { iconv_close(iconvd_); }
+
+  operator iconv_t() { return this->iconvd_; }
+};
+
+int main()
+{
+  try {
+    auto conv_d = iconv_desc{ "ISO-8859-1", "UTF-8" };
+    auto from_str = std::array<char, 10>{ u8"a\xC3\xA4o\xC3\xB6u\xC3\xBC" };
+    auto to_str = std::array<char, 7>{};
+
+    auto from_str_ptr = from_str.data();
+    auto from_len = from_str.size();
+    auto to_str_ptr = to_str.data();
+    auto to_len = to_str.size();
+    const auto iconv_ret =
+      iconv(conv_d, &from_str_ptr, &from_len, &to_str_ptr, &to_len);
+    if (iconv_ret == static_cast<std::size_t>(-1))
+      throw std::system_error(errno, std::system_category());
+    std::cout << '\'' << from_str.data() << "\' converted to \'"
+              << to_str.data() << '\'' << std::endl;
+    return EXIT_SUCCESS;
+  } catch (const std::system_error& ex) {
+    std::cerr << "ERROR: " << ex.code() << '\n'
+              << ex.code().message() << std::endl;
+  }
+  return EXIT_FAILURE;
+}
-- 
cgit v0.12