# copyright (c) 2007, 2009 Arno Rehn arno@arnorehn.de # copyright (c) 2008 Helio castro helio@kde.org # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # This file adds support for the C# language to cmake. # # It adds the following functions: # # csharp_add_executable ( [UNSAFE] [WINEXE] [REFERENCES ] # [COMPILE_FLAGS ] # [COMPILE_DEFINITIONS ] ) # # csharp_add_library ( [UNSAFE] [REFERENCES ] # [COMPILE_FLAGS ] # [COMPILE_DEFINITIONS ] ) # # install_assembly ( [NO_GAC] DESTINATION # [PACKAGE ] ) # The assembly destination directory is only used if we compile with Visual C# and thus can't use gacutil. # If a package is specified and a file called .pc.cmake exists in the current source directory, # this function will configure the template file. All occurences of @assembly@ will be replaced with # the path to the assembly. The resulting .pc file will be installed to # /lib/pkgconfig/ . If you want to have a different basename for the template file, # set the 'pkg-config_template_basename' property of the target with set_property. # # Example: # ------------------------------ # cmake code: # ------------------------------ # csharp_add_library(foo foo.cs) # install_assembly(foo DESTINATION lib) # # ------------------------------ # contents of foo.pc.cmake file: # ------------------------------ # Name: Foo # Description: Foo library # Version: 1.0 # Libs: -r:@assembly@ # ----- support macros ----- macro(GET_LIBRARY_OUTPUT_DIR var) if (NOT LIBRARY_OUTPUT_PATH) set(${var} ${CMAKE_CURRENT_BINARY_DIR}) else (NOT LIBRARY_OUTPUT_PATH) set(${var} ${LIBRARY_OUTPUT_PATH}) endif (NOT LIBRARY_OUTPUT_PATH) endmacro(GET_LIBRARY_OUTPUT_DIR) macro(GET_EXECUTABLE_OUTPUT_DIR var) if (NOT EXECUTABLE_OUTPUT_PATH) set(${var} ${CMAKE_CURRENT_BINARY_DIR}) else (NOT EXECUTABLE_OUTPUT_PATH) set(${var} ${EXECUTABLE_OUTPUT_PATH}) endif (NOT EXECUTABLE_OUTPUT_PATH) endmacro(GET_EXECUTABLE_OUTPUT_DIR) # This does just not always work... why?! # macro(MAKE_PROPER_FILE_LIST var) # foreach(file ${ARGN}) # if (IS_ABSOLUTE "${file}") # file(GLOB globbed "${file}") # else (IS_ABSOLUTE "${file}") # file(GLOB globbed "${CMAKE_CURRENT_SOURCE_DIR}/${file}") # endif (IS_ABSOLUTE "${file}") # # foreach (glob ${globbed}) # file(TO_NATIVE_PATH "${glob}" native) # list(APPEND proper_file_list "${native}") # endforeach (glob ${globbed}) # endforeach(file ${ARGN}) # endmacro(MAKE_PROPER_FILE_LIST) # ----- actual functions ----- # ----- add an executable ----- function(csharp_add_executable target) set(current "s") set(dotnet_target "exe") foreach (arg ${ARGN}) file(TO_NATIVE_PATH ${arg} native_path) if (arg STREQUAL "UNSAFE") set (unsafe "/unsafe") elseif (arg STREQUAL "WINEXE") set (dotnet_target "winexe") elseif (arg STREQUAL "REFERENCES") set (current "r") elseif (arg STREQUAL "COMPILE_FLAGS") set (current "flags") elseif (arg STREQUAL "COMPILE_DEFINITIONS") set (current "defs") else (arg STREQUAL "UNSAFE") if (current STREQUAL "s") # source file list(APPEND sources ${native_path}) elseif (current STREQUAL "r") # reference if (TARGET ${arg}) # this is an existing target - get the target assembly get_property(prop TARGET ${arg} PROPERTY _assembly) list(APPEND references "/r:${prop}") list(APPEND deps ${arg}) else (TARGET ${arg}) # something different (e.g. assembly name in the gac) list(APPEND references "/r:${native_path}") endif (TARGET ${arg}) elseif (current STREQUAL "flags") list(APPEND _csc_opts "${arg}") elseif (current STREQUAL "defs") list(APPEND _csc_opts "/define:${arg}") endif (current STREQUAL "s") endif (arg STREQUAL "UNSAFE") endforeach (arg ${ARGN}) if (CMAKE_BUILD_TYPE STREQUAL "Debug") list(APPEND _csc_opts "/define:DEBUG") list(APPEND _csc_opts "/debug") endif (CMAKE_BUILD_TYPE STREQUAL "Debug") get_executable_output_dir(outdir) if (NOT IS_ABSOLUTE "${outdir}") message(FATAL_ERROR "Directory \"${outdir}\" is not an absolute path!") endif (NOT IS_ABSOLUTE "${outdir}") file(RELATIVE_PATH relative_path "${CMAKE_BINARY_DIR}" "${outdir}/${target}.exe") file(TO_NATIVE_PATH "${outdir}/${target}" native_target) # inlined - this doesn't work as a macro :( foreach(file ${sources}) file(TO_CMAKE_PATH "${file}" cmake_file) if (IS_ABSOLUTE "${cmake_file}") file(GLOB globbed "${cmake_file}") else (IS_ABSOLUTE "${cmake_file}") file(GLOB globbed "${CMAKE_CURRENT_SOURCE_DIR}/${cmake_file}") endif (IS_ABSOLUTE "${cmake_file}") foreach (glob ${globbed}) file(TO_CMAKE_PATH "${glob}" cmake_path) list(APPEND cmake_file_list "${cmake_path}") endforeach (glob ${globbed}) if (NOT globbed) list(APPEND cmake_file_list "${cmake_file}") endif (NOT globbed) list(APPEND compiler_file_list ${file}) endforeach(file ${sources}) get_directory_property(compile_definitions COMPILE_DEFINITIONS) foreach (def ${compile_definitions}) # macros with values aren't supported by C# if (NOT def MATCHES ".*=.*") list(APPEND _csc_opts "/define:${def}") endif (NOT def MATCHES ".*=.*") endforeach (def ${compile_definitions}) get_directory_property(link_dirs LINK_DIRECTORIES) foreach (dir ${link_dirs}) list(APPEND _csc_opts "/lib:${dir}") endforeach (dir ${link_dirs}) add_custom_command(OUTPUT "${outdir}/${target}.stubexe" COMMAND "${CMAKE_COMMAND}" -E make_directory "${outdir}" # create the output dir COMMAND "${CMAKE_CSharp_COMPILER}" /nologo /target:${dotnet_target} "/out:${native_target}.exe" # build the executable ${_csc_opts} ${unsafe} ${references} ${compiler_file_list} COMMAND "${CMAKE_COMMAND}" -E touch "${outdir}/${target}.stubexe" # create the stub so that DEPENDS will work WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" # working directory is the source directory, so we don't have to care about relative paths DEPENDS ${cmake_file_list} COMMENT "Building ${relative_path}" VERBATIM) # nice comment add_custom_target(${target} ALL DEPENDS "${outdir}/${target}.stubexe" SOURCES ${cmake_file_list}) # create the actual target set_property(TARGET ${target} PROPERTY _assembly "${native_target}.exe") set_property(TARGET ${target} PROPERTY _assembly_type "exe") if (deps) add_dependencies(${target} ${deps}) endif(deps) endfunction(csharp_add_executable) # ----- add a library ----- function(csharp_add_library target) set(current "s") foreach (arg ${ARGN}) file(TO_NATIVE_PATH ${arg} native_path) if (arg STREQUAL "UNSAFE") set (unsafe "/unsafe") elseif (arg STREQUAL "REFERENCES") set (current "r") elseif (arg STREQUAL "COMPILE_FLAGS") set (current "flags") elseif (arg STREQUAL "COMPILE_DEFINITIONS") set (current "defs") else (arg STREQUAL "UNSAFE") if (current STREQUAL "s") # source file list(APPEND sources ${native_path}) elseif (current STREQUAL "r") # reference if (TARGET ${arg}) # this is an existing target - get the target assembly get_property(prop TARGET ${arg} PROPERTY _assembly) list(APPEND references "/r:${prop}") list(APPEND deps ${arg}) else (TARGET ${arg}) # something different (e.g. assembly name in the gac) list(APPEND references "/r:${native_path}") endif (TARGET ${arg}) elseif (current STREQUAL "flags") list(APPEND _csc_opts "${arg}") elseif (current STREQUAL "defs") list(APPEND _csc_opts "/define:${arg}") endif (current STREQUAL "s") endif (arg STREQUAL "UNSAFE") endforeach (arg ${ARGN}) if (CMAKE_BUILD_TYPE STREQUAL "Debug") list(APPEND _csc_opts "/define:DEBUG") list(APPEND _csc_opts "/debug") endif (CMAKE_BUILD_TYPE STREQUAL "Debug") get_library_output_dir(outdir) if (NOT IS_ABSOLUTE "${outdir}") message(FATAL_ERROR "Directory \"${outdir}\" is not an absolute path!") endif (NOT IS_ABSOLUTE "${outdir}") file(RELATIVE_PATH relative_path "${CMAKE_BINARY_DIR}" "${outdir}/${target}.dll") file(TO_NATIVE_PATH "${outdir}/${target}" native_target) # inlined - this doesn't work as a macro :( foreach(file ${sources}) file(TO_CMAKE_PATH "${file}" cmake_file) if (IS_ABSOLUTE "${cmake_file}") file(GLOB globbed "${cmake_file}") else (IS_ABSOLUTE "${cmake_file}") file(GLOB globbed "${CMAKE_CURRENT_SOURCE_DIR}/${cmake_file}") endif (IS_ABSOLUTE "${cmake_file}") foreach (glob ${globbed}) file(TO_CMAKE_PATH "${glob}" cmake_path) list(APPEND cmake_file_list "${cmake_path}") endforeach (glob ${globbed}) if (NOT globbed) list(APPEND cmake_file_list "${cmake_file}") endif (NOT globbed) list(APPEND compiler_file_list ${file}) endforeach(file ${sources}) # message("CMake File List for target ${target}: ${cmake_file_list}") get_directory_property(compile_definitions COMPILE_DEFINITIONS) foreach (def ${compile_definitions}) # macros with values aren't supported by C# if (NOT def MATCHES ".*=.*") list(APPEND _csc_opts "/define:${def}") endif (NOT def MATCHES ".*=.*") endforeach (def ${compile_definitions}) get_directory_property(link_dirs LINK_DIRECTORIES) foreach (dir ${link_dirs}) list(APPEND _csc_opts "/lib:${dir}") endforeach (dir ${link_dirs}) add_custom_command(OUTPUT "${outdir}/${target}.dll" COMMAND "${CMAKE_COMMAND}" -E make_directory "${outdir}" # create the output dir COMMAND "${CMAKE_CSharp_COMPILER}" /nologo /target:library "/out:${native_target}.dll" # build the library ${_csc_opts} ${unsafe} ${references} ${compiler_file_list} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" # working directory is the source directory, so we don't have to care about relative paths DEPENDS ${cmake_file_list} COMMENT "Building ${relative_path}" VERBATIM) # nice comment add_custom_target(${target} ALL DEPENDS "${outdir}/${target}.dll" SOURCES ${cmake_file_list}) # create the actual target set_property(TARGET ${target} PROPERTY _assembly "${native_target}.dll") set_property(TARGET ${target} PROPERTY _assembly_type "dll") if (deps) add_dependencies(${target} ${deps}) endif(deps) endfunction(csharp_add_library) # ----- install an assembly ----- function(install_assembly) set (current "t") foreach (arg ${ARGN}) # flag handling if (arg STREQUAL "NO_GAC") set(no_gac TRUE) # option handling elseif (arg STREQUAL DESTINATION) set (current "d") elseif (arg STREQUAL "PACKAGE") set (current "p") # value handling elseif (current STREQUAL "t") set (target ${arg}) elseif (current STREQUAL "d") if (IS_ABSOLUTE "${arg}") set (destination_dir "${arg}") else (IS_ABSOLUTE "${arg}") set (destination_dir "${CMAKE_INSTALL_PREFIX}/${arg}") endif (IS_ABSOLUTE "${arg}") elseif (current STREQUAL "p") set (package ${arg}) endif (arg STREQUAL "NO_GAC") endforeach (arg) if (NOT destination_dir) message(FATAL_ERROR "The destination directory is mandatory, even if the assembly is installed into the GAC.") elseif (NOT target) message(FATAL_ERROR "No target given.") endif (NOT destination_dir) # retrieve the absolute path of the generated assembly get_property(filename TARGET ${target} PROPERTY _assembly) get_property(type TARGET ${target} PROPERTY _assembly_type) get_property(pc_file TARGET ${target} PROPERTY pkg-config_template_basename) if (NOT pc_file) set (pc_file ${target}) endif (NOT pc_file) # default assembly location (for pkg-config) set(assembly "${GAC_DIR}/${package}/${target}.dll") if (NOT filename) message(FATAL_ERROR "Couldn't retrieve the assembly filename for target ${target}! Are you sure the target is a .NET library assembly?") endif (NOT filename) if (package) set (package_option "-package ${package}") endif (package) if (NOT MONO_FOUND OR no_gac OR type STREQUAL "exe") install(FILES "${filename}" DESTINATION ${destination_dir}) if (EXISTS "${filename}.config") install(FILES "${filename}.config" DESTINATION ${destination_dir}) endif (EXISTS "${filename}.config") # don't install anything into the GAC set (no_gac TRUE) # set new assembly location for pkg-config set(assembly "${destination_dir}/${target}.${type}") endif (NOT MONO_FOUND OR no_gac OR type STREQUAL "exe") if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${pc_file}.pc.cmake") configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${pc_file}.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${pc_file}.pc" @ONLY) if (NOT LIB_INSTALL_DIR) set (LIB_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/lib) endif (NOT LIB_INSTALL_DIR) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${pc_file}.pc" DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) endif (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${pc_file}.pc.cmake") if (no_gac) return() endif (no_gac) # So we have the mono runtime and we can use gacutil (it has the -root option, which the MS version doesn't have). install(CODE "execute_process(COMMAND ${GACUTIL_EXECUTABLE} -i ${filename} ${package_option} -root ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac)") file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac/mono) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac/mono) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tmp_gac/mono/ DESTINATION ${GAC_DIR} ) endfunction(install_assembly) set(CMAKE_CSharp_INFORMATION_LOADED 1)