diff --git a/CMakeLists.txt b/CMakeLists.txt index 89f1e6b2e..d2b1cb9cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,10 @@ if( "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}" ) ) endif() +# Just a couple of convenience variables +set( topdir "${CMAKE_SOURCE_DIR}" ) +set( libsrc "${topdir}/lib-src" ) + # Ignore COMPILE_DEFINITIONS_ properties cmake_policy( SET CMP0043 NEW ) @@ -73,39 +77,8 @@ endif() # Our very own project project( Audacity ) -message( STATUS "Build Info:" ) -message( STATUS " Host System: ${CMAKE_HOST_SYSTEM}" ) -message( STATUS " Host System Name: ${CMAKE_HOST_SYSTEM_NAME}" ) -message( STATUS " Host System Processor: ${CMAKE_HOST_SYSTEM_PROCESSOR}" ) -message( STATUS " Host System Version: ${CMAKE_HOST_SYSTEM_VERSION}" ) -message( STATUS ) -message( STATUS " Compiler: ${CMAKE_CXX_COMPILER}" ) -message( STATUS " Compiler Standard: ${CMAKE_CXX_STANDARD}" ) -message( STATUS " Compiler Standard Required: ${CMAKE_CXX_STANDARD_REQUIRED}" ) -message( STATUS " Compiler Extensions: ${CMAKE_CXX_EXTENSIONS}" ) -message( STATUS ) -if( APPLE ) - message( STATUS " Xcode Version: ${XCODE_VERSION}" ) - message( STATUS " MacOS SDK: ${CMAKE_OSX_SYSROOT}" ) - message( STATUS ) -endif() - -# Define option() prefix -set( _OPT "audacity_" ) - -# Try to get the current commit hash -find_package( Git QUIET ) -if( GIT_FOUND ) - execute_process( - COMMAND - ${GIT_EXECUTABLE} show -s --format='%h' - OUTPUT_VARIABLE - short_hash - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - message( STATUS " Current Commit: ${short_hash}" ) - message( STATUS ) -endif() +# Load our functions/macros +include( AudacityFunctions ) # Pull all the modules we'll need include( CheckCXXCompilerFlag ) @@ -120,6 +93,55 @@ include( CMakePushCheckState ) include( GNUInstallDirs ) include( TestBigEndian ) +message( STATUS "Build Info:" ) +message( STATUS " Host System: ${CMAKE_HOST_SYSTEM}" ) +message( STATUS " Host System Name: ${CMAKE_HOST_SYSTEM_NAME}" ) +message( STATUS " Host System Processor: ${CMAKE_HOST_SYSTEM_PROCESSOR}" ) +message( STATUS " Host System Version: ${CMAKE_HOST_SYSTEM_VERSION}" ) +message( STATUS ) +message( STATUS " Compiler: ${CMAKE_CXX_COMPILER}" ) +message( STATUS " Compiler Version: ${CMAKE_CXX_COMPILER_VERSION}" ) +message( STATUS " Compiler Standard: ${CMAKE_CXX_STANDARD}" ) +message( STATUS " Compiler Standard Required: ${CMAKE_CXX_STANDARD_REQUIRED}" ) +message( STATUS " Compiler Extensions: ${CMAKE_CXX_EXTENSIONS}" ) +message( STATUS ) + +if( CMAKE_GENERATOR MATCHES "Visual Studio" ) + message( STATUS " MSVC Version: ${MSVC_VERSION}" ) + message( STATUS " MSVC Toolset: ${MSVC_TOOLSET_VERSION}" ) + message( STATUS ) +elseif( CMAKE_SYSTEM_NAME MATCHES "Darwin" ) + if( CMAKE_GENERATOR MATCHES "Visual Studio" ) + message( STATUS " Xcode Version: ${XCODE_VERSION}" ) + endif() + message( STATUS " MacOS SDK: ${CMAKE_OSX_SYSROOT}" ) + message( STATUS ) +endif() + +# Try to get the current commit information +find_package( Git QUIET ) +if( GIT_FOUND ) + execute_process( + COMMAND + ${GIT_EXECUTABLE} show -s "--format=%h;%H;%cd" + WORKING_DIRECTORY + ${topdir} + OUTPUT_VARIABLE + output + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + list( GET output 0 GIT_COMMIT_SHORT ) + list( GET output 1 GIT_COMMIT_LONG ) + list( GET output 2 GIT_COMMIT_TIME ) + + message( STATUS " Current Commit: ${GIT_COMMIT_SHORT}" ) + message( STATUS ) +endif() + +# Define option() prefix +set( _OPT "audacity_" ) + # Organize subdirectories/targets into folders for the IDEs set_property( GLOBAL PROPERTY USE_FOLDERS ON ) @@ -158,14 +180,10 @@ set( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE ) set( CMAKE_MACOSX_RPATH FALSE ) # the RPATH to be used when installing, but only if it's not a system directory -#list( FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" isSystemDir) -#IF("${isSystemDir}" STREQUAL "-1") -# SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") -#ENDIF("${isSystemDir}" STREQUAL "-1") - -# Just a couple of convenience variables -set( topdir "${CMAKE_SOURCE_DIR}" ) -set( libsrc "${topdir}/lib-src" ) +#list( FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" isSysDir ) +#if( "${isSysDir}" STREQUAL "-1" ) +# set( CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib" ) +#endif() # Add the math library (if found) to the list of required libraries check_library_exists( m pow "" HAVE_LIBM ) @@ -222,14 +240,12 @@ endif() check_include_files( "float.h;stdarg.h;stdlib.h;string.h" STDC_HEADERS ) -check_include_file( "alloca.h" HAVE_ALLOCA_H ) check_include_file( "assert.h" HAVE_ASSERT_H ) check_include_file( "byteswap.h" HAVE_BYTESWAP_H ) check_include_file( "errno.h" HAVE_ERRNO_H ) check_include_file( "fcntl.h" HAVE_FCNTL_H ) check_include_file( "fenv.h" HAVE_FENV_H ) check_include_file( "inttypes.h" HAVE_INTTYPES_H ) -check_include_file( "libudev.h" HAVE_LIBUDEV_H ) check_include_file( "limits.h" HAVE_LIMITS_H ) check_include_file( "malloc.h" HAVE_MALLOC_H ) check_include_file( "memory.h" HAVE_MEMORY_H ) @@ -313,14 +329,6 @@ find_package( PkgConfig QUIET ) # Mostly just to make the CMP0072 policy happy find_package( OpenGL QUIET ) -# Defines several useful directory paths for the active context. -macro( def_vars ) - set( _SRCDIR "${CMAKE_CURRENT_SOURCE_DIR}" ) - set( _INTDIR "${CMAKE_CURRENT_BINARY_DIR}" ) - set( _PRVDIR "${CMAKE_CURRENT_BINARY_DIR}/private" ) - set( _PUBDIR "${CMAKE_CURRENT_BINARY_DIR}/public" ) -endmacro() - # Define the non-install and executable destinations # # If this is a multi-config build system (VS, Xcode), CMAKE_CFG_INTDIR @@ -391,200 +399,15 @@ else() set( AUDACITY_SUFFIX "" ) endif() -# Extract the current commit information -if (GIT_FOUND) - execute_process( - COMMAND - ${GIT_EXECUTABLE} show -s "--format=\"%H\";\"%cd\";" - WORKING_DIRECTORY - ${topdir} - OUTPUT_VARIABLE - output - ) - - list( GET output 0 long ) - list( GET output 1 time ) - list( APPEND DEFINES - REV_LONG=${long} - REV_TIME=${time} - ) +# Python is used for the manual and (possibly) message catalogs +find_package( Python2 ) +if( Python2_FOUND ) + set( PYTHON "${Python2_EXECUTABLE}" ) +elseif( CMAKE_SYSTEM_NAME MATCHES "Windows" ) + nuget_package( pkgdir "python2" "2.7.17" ) + file( TO_NATIVE_PATH "${pkgdir}/tools/python.exe" PYTHON ) endif() -# Helper to organize sources into folders for the IDEs -macro( organize_source root prefix sources ) - set( cleaned ) - foreach( source ${sources} ) - # Remove generator expressions - string( REGEX REPLACE ".*>:(.*)>*" "\\1" source "${source}" ) - string( REPLACE ">" "" source "${source}" ) - - # Remove keywords - string( REGEX REPLACE "^[A-Z]+$" "" source "${source}" ) - - # Add to cleaned - list( APPEND cleaned "${source}" ) - endforeach() - - # Define the source groups - if( "${prefix}" STREQUAL "" ) - source_group( TREE "${root}" FILES ${cleaned} ) - else() - source_group( TREE "${root}" PREFIX ${prefix} FILES ${cleaned} ) - endif() -endmacro() - -# Given a directory, recurse to all defined subdirectories and assign -# the given folder name to all of the targets found. -function( set_dir_folder dir folder) - get_property( subdirs DIRECTORY "${dir}" PROPERTY SUBDIRECTORIES ) - foreach( sub ${subdirs} ) - set_dir_folder( "${sub}" "${folder}" ) - endforeach() - - get_property( targets DIRECTORY "${dir}" PROPERTY BUILDSYSTEM_TARGETS ) - foreach( target ${targets} ) - get_target_property( type "${target}" TYPE ) - if( NOT "${type}" STREQUAL "INTERFACE_LIBRARY" ) - set_target_properties( ${target} PROPERTIES FOLDER ${folder} ) - endif() - endforeach() -endfunction() - -# Helper to retrieve the settings returned from pkg_check_modules() -macro( get_package_interface package ) - set( INCLUDES - ${${package}_INCLUDE_DIRS} - ) - - set( LINKDIRS - ${${package}_LIBDIR} - ) - - # We resolve the full path of each library to ensure the - # correct one is referenced while linking - foreach( lib ${${package}_LIBRARIES} ) - find_library( LIB_${lib} ${lib} HINTS ${LINKDIRS} ) - list( APPEND LIBRARIES ${LIB_${lib}} ) - endforeach() -endmacro() - -# Set the cache and context value -macro( set_cache_value var value ) - set( ${var} "${value}" ) - set_property( CACHE ${var} PROPERTY VALUE "${value}" ) -endmacro() - -# Set the given property and its config specific brethren to the same value -function( set_target_property_all target property value ) - set_target_properties( "${target}" PROPERTIES "${property}" "${value}" ) - foreach( type ${CMAKE_CONFIGURATION_TYPES} ) - string( TOUPPER "${property}_${type}" prop ) - set_target_properties( "${target}" PROPERTIES "${prop}" "${value}" ) - endforeach() -endfunction() - -# Taken from wxWidgets and modified for Audcaity -# -# cmd_option( [default] [STRINGS strings]) -# The default is ON if third parameter isn't specified -function( cmd_option name desc ) - cmake_parse_arguments( OPTION "" "" "STRINGS" ${ARGN} ) - - if( ARGC EQUAL 2 ) - if( OPTION_STRINGS ) - list( GET OPTION_STRINGS 1 default ) - else() - set( default ON ) - endif() - else() - set( default ${OPTION_UNPARSED_ARGUMENTS} ) - endif() - - if( OPTION_STRINGS ) - set( cache_type STRING ) - else() - set( cache_type BOOL ) - endif() - - set( ${name} "${default}" CACHE ${cache_type} "${desc}" ) - if( OPTION_STRINGS ) - set_property( CACHE ${name} PROPERTY STRINGS ${OPTION_STRINGS} ) - - # Check valid value - set( value_is_valid FALSE ) - set( avail_values ) - foreach( opt ${OPTION_STRINGS} ) - if( ${name} STREQUAL opt ) - set( value_is_valid TRUE ) - break() - endif() - string( APPEND avail_values " ${opt}" ) - endforeach() - if( NOT value_is_valid ) - message( FATAL_ERROR "Invalid value \"${${name}}\" for option ${name}. Valid values are: ${avail_values}" ) - endif() - endif() - - set( ${name} "${${name}}" PARENT_SCOPE ) -endfunction() - -# Downloads NuGet packages -# -# Why this is needed... -# -# To get NuGet to work, you have to add the VS_PACKAGE_REFERENCES -# property to a target. This target must NOT be a UTILITY target, -# which is what we use to compile the message catalogs and assemble -# the manual. We could add that property to the Audacity target and -# CMake would add the required nodes to the VS project. And when the -# Audacity target is built, the NuGet packages would get automatically -# downloaded. This also means that the locale and manual targets -# must be dependent on the Audacity target so the packages would get -# downloaded before they execute. This would be handled by the CMake -# provided ALL_BUILD target which is, by default, set as the startup -# project in Visual Studio. Sweet right? Well, not quite... -# -# We want the Audacity target to be the startup project to provide -# eaiser debugging. But, if we do that, the ALL_BUILD target is no -# longer "in control" and any dependents of the Audacity target would -# not get built. So, targets like "nyquist" and "plug-ins" would have -# to be manually built. This is not what we want since Nyquist would -# not be available during Audacity debugging because the Nyquist runtime -# would not be copied into the destination folder alonside the Audacity -# executable. -# -# To remedy this conundrum, we simply download the NuGet packages -# ourselves and make the Audacity target dependent on the targets -# mentioned above. This ensures that the dest folder is populated -# and laid out like Audacity expects. -# -function( nuget_package dir name version ) - # Generate the full package directory name - set( pkgdir "${CMAKE_BINARY_DIR}/packages/${name}/${version}" ) - - # Don't download it again if the package directory already exists - if( NOT EXISTS "${pkgdir}" ) - set( pkgurl "https://www.nuget.org/api/v2/package/${name}/${version}" ) - - # Create the package directory - file( MAKE_DIRECTORY "${pkgdir}" ) - - # And download the package into the package directory - file( DOWNLOAD "${pkgurl}" "${pkgdir}/package.zip" ) - - # Extract the contents of the package into the package directory - execute_process( - COMMAND - ${CMAKE_COMMAND} -E tar x "${pkgdir}/package.zip" - WORKING_DIRECTORY - ${pkgdir} - ) - endif() - - # Return the package directory name to the caller - set( ${dir} "${pkgdir}" PARENT_SCOPE ) -endfunction() - # Add our children add_subdirectory( "cmake-proxies" ) add_subdirectory( "help" ) @@ -605,13 +428,13 @@ endif() # Uncomment what follows for symbol values. #[[ - get_cmake_property(_variableNames VARIABLES) - foreach (_variableName ${_variableNames}) - message(STATUS "${_variableName}=${${_variableName}}") + get_cmake_property( _variableNames VARIABLES ) + foreach( _variableName ${_variableNames} ) + message( STATUS "${_variableName}=${${_variableName}}" ) endforeach() #]] #[[ - include(PrintProperties) - print_properties(TARGET "wxWidgets") + include( PrintProperties ) + print_properties( TARGET "wxWidgets" ) #]] diff --git a/cmake-proxies/cmake-modules/AudacityFunctions.cmake b/cmake-proxies/cmake-modules/AudacityFunctions.cmake new file mode 100644 index 000000000..fc6b3bb79 --- /dev/null +++ b/cmake-proxies/cmake-modules/AudacityFunctions.cmake @@ -0,0 +1,186 @@ +# +# A collection of functions and macros +# + +# Defines several useful directory paths for the active context. +macro( def_vars ) + set( _SRCDIR "${CMAKE_CURRENT_SOURCE_DIR}" ) + set( _INTDIR "${CMAKE_CURRENT_BINARY_DIR}" ) + set( _PRVDIR "${CMAKE_CURRENT_BINARY_DIR}/private" ) + set( _PUBDIR "${CMAKE_CURRENT_BINARY_DIR}/public" ) +endmacro() + +# Helper to organize sources into folders for the IDEs +macro( organize_source root prefix sources ) + set( cleaned ) + foreach( source ${sources} ) + # Remove generator expressions + string( REGEX REPLACE ".*>:(.*)>*" "\\1" source "${source}" ) + string( REPLACE ">" "" source "${source}" ) + + # Remove keywords + string( REGEX REPLACE "^[A-Z]+$" "" source "${source}" ) + + # Add to cleaned + list( APPEND cleaned "${source}" ) + endforeach() + + # Define the source groups + if( "${prefix}" STREQUAL "" ) + source_group( TREE "${root}" FILES ${cleaned} ) + else() + source_group( TREE "${root}" PREFIX ${prefix} FILES ${cleaned} ) + endif() +endmacro() + +# Given a directory, recurse to all defined subdirectories and assign +# the given folder name to all of the targets found. +function( set_dir_folder dir folder) + get_property( subdirs DIRECTORY "${dir}" PROPERTY SUBDIRECTORIES ) + foreach( sub ${subdirs} ) + set_dir_folder( "${sub}" "${folder}" ) + endforeach() + + get_property( targets DIRECTORY "${dir}" PROPERTY BUILDSYSTEM_TARGETS ) + foreach( target ${targets} ) + get_target_property( type "${target}" TYPE ) + if( NOT "${type}" STREQUAL "INTERFACE_LIBRARY" ) + set_target_properties( ${target} PROPERTIES FOLDER ${folder} ) + endif() + endforeach() +endfunction() + +# Helper to retrieve the settings returned from pkg_check_modules() +macro( get_package_interface package ) + set( INCLUDES + ${${package}_INCLUDE_DIRS} + ) + + set( LINKDIRS + ${${package}_LIBDIR} + ) + + # We resolve the full path of each library to ensure the + # correct one is referenced while linking + foreach( lib ${${package}_LIBRARIES} ) + find_library( LIB_${lib} ${lib} HINTS ${LINKDIRS} ) + list( APPEND LIBRARIES ${LIB_${lib}} ) + endforeach() +endmacro() + +# Set the cache and context value +macro( set_cache_value var value ) + set( ${var} "${value}" ) + set_property( CACHE ${var} PROPERTY VALUE "${value}" ) +endmacro() + +# Set the given property and its config specific brethren to the same value +function( set_target_property_all target property value ) + set_target_properties( "${target}" PROPERTIES "${property}" "${value}" ) + foreach( type ${CMAKE_CONFIGURATION_TYPES} ) + string( TOUPPER "${property}_${type}" prop ) + set_target_properties( "${target}" PROPERTIES "${prop}" "${value}" ) + endforeach() +endfunction() + +# Taken from wxWidgets and modified for Audcaity +# +# cmd_option( [default] [STRINGS strings]) +# The default is ON if third parameter isn't specified +function( cmd_option name desc ) + cmake_parse_arguments( OPTION "" "" "STRINGS" ${ARGN} ) + + if( ARGC EQUAL 2 ) + if( OPTION_STRINGS ) + list( GET OPTION_STRINGS 1 default ) + else() + set( default ON ) + endif() + else() + set( default ${OPTION_UNPARSED_ARGUMENTS} ) + endif() + + if( OPTION_STRINGS ) + set( cache_type STRING ) + else() + set( cache_type BOOL ) + endif() + + set( ${name} "${default}" CACHE ${cache_type} "${desc}" ) + if( OPTION_STRINGS ) + set_property( CACHE ${name} PROPERTY STRINGS ${OPTION_STRINGS} ) + + # Check valid value + set( value_is_valid FALSE ) + set( avail_values ) + foreach( opt ${OPTION_STRINGS} ) + if( ${name} STREQUAL opt ) + set( value_is_valid TRUE ) + break() + endif() + string( APPEND avail_values " ${opt}" ) + endforeach() + if( NOT value_is_valid ) + message( FATAL_ERROR "Invalid value \"${${name}}\" for option ${name}. Valid values are: ${avail_values}" ) + endif() + endif() + + set( ${name} "${${name}}" PARENT_SCOPE ) +endfunction() + +# Downloads NuGet packages +# +# Why this is needed... +# +# To get NuGet to work, you have to add the VS_PACKAGE_REFERENCES +# property to a target. This target must NOT be a UTILITY target, +# which is what we use to compile the message catalogs and assemble +# the manual. We could add that property to the Audacity target and +# CMake would add the required nodes to the VS project. And when the +# Audacity target is built, the NuGet packages would get automatically +# downloaded. This also means that the locale and manual targets +# must be dependent on the Audacity target so the packages would get +# downloaded before they execute. This would be handled by the CMake +# provided ALL_BUILD target which is, by default, set as the startup +# project in Visual Studio. Sweet right? Well, not quite... +# +# We want the Audacity target to be the startup project to provide +# eaiser debugging. But, if we do that, the ALL_BUILD target is no +# longer "in control" and any dependents of the Audacity target would +# not get built. So, targets like "nyquist" and "plug-ins" would have +# to be manually built. This is not what we want since Nyquist would +# not be available during Audacity debugging because the Nyquist runtime +# would not be copied into the destination folder alonside the Audacity +# executable. +# +# To remedy this conundrum, we simply download the NuGet packages +# ourselves and make the Audacity target dependent on the targets +# mentioned above. This ensures that the dest folder is populated +# and laid out like Audacity expects. +# +function( nuget_package dir name version ) + # Generate the full package directory name + set( pkgdir "${CMAKE_BINARY_DIR}/packages/${name}/${version}" ) + + # Don't download it again if the package directory already exists + if( NOT EXISTS "${pkgdir}" ) + set( pkgurl "https://www.nuget.org/api/v2/package/${name}/${version}" ) + + # Create the package directory + file( MAKE_DIRECTORY "${pkgdir}" ) + + # And download the package into the package directory + file( DOWNLOAD "${pkgurl}" "${pkgdir}/package.zip" ) + + # Extract the contents of the package into the package directory + execute_process( + COMMAND + ${CMAKE_COMMAND} -E tar x "${pkgdir}/package.zip" + WORKING_DIRECTORY + ${pkgdir} + ) + endif() + + # Return the package directory name to the caller + set( ${dir} "${pkgdir}" PARENT_SCOPE ) +endfunction() diff --git a/help/CMakeLists.txt b/help/CMakeLists.txt index 0200e2bd3..28588cf8f 100755 --- a/help/CMakeLists.txt +++ b/help/CMakeLists.txt @@ -6,6 +6,11 @@ message( STATUS "========== Configuring ${TARGET} ==========" ) def_vars() +if( NOT DEFINED PYTHON ) + message( WARNING "Python not found...unable to produce manual." ) + return() +endif() + set( host "alphamanual.audacityteam.org" ) set( src "https://${host}/man" ) set( dst "${_DEST}/help/manual" ) @@ -14,26 +19,11 @@ set( script "mw2html.py" ) set( out_dir "${_INTDIR}" ) set( out "${out_dir}/${host}/index.html" ) -if( CMAKE_SYSTEM_NAME MATCHES "Windows" ) - nuget_package( pkgdir "python2" "2.7.17" ) - file( TO_NATIVE_PATH "${pkgdir}/tools/python.exe" python ) -else() - find_package( Python2 ) - if( Python2_FOUND ) - set( python "${Python2_EXECUTABLE}" ) - endif() -endif() - -if( NOT DEFINED python ) - message( WARNING "Python not found...unable to produce manual." ) - return() -endif() - add_custom_command( COMMENT "Downloading manual from: ${src}" COMMAND - "${python}" "${script_dir}/${script}" -s "${src}" "${out_dir}" + "${PYTHON}" "${script_dir}/${script}" -s "${src}" "${out_dir}" COMMAND ${CMAKE_COMMAND} -E copy_directory "${out_dir}/${host}" "${dst}" WORKING_DIRECTORY diff --git a/locale/CMakeLists.txt b/locale/CMakeLists.txt index 617e2ed9b..79bfefb39 100755 --- a/locale/CMakeLists.txt +++ b/locale/CMakeLists.txt @@ -64,69 +64,21 @@ list( APPEND SOURCES zh_TW.po ) -if( CMAKE_SYSTEM_NAME MATCHES "Windows" ) - nuget_package( pkgdir "Gettext.Tools" "0.20.1.1" ) - file( TO_NATIVE_PATH "${pkgdir}/tools/bin/msgfmt.exe" msgfmt ) -elseif( CMAKE_SYSTEM_NAME MATCHES "Darwin" ) - find_package( Gettext ) +# Look for gettext +find_package( Gettext QUIET ) +if( GETTEXT_FOUND ) mark_as_advanced( FORCE GETTEXT_MSGFMT_EXECUTABLE ) mark_as_advanced( FORCE GETTEXT_MSGMERGE_EXECUTABLE ) - - if( GETTEXT_FOUND ) - set( msgfmt "${GETTEXT_MSGFMT_EXECUTABLE}" ) - else() - set( root "${CMAKE_CURRENT_BINARY_DIR}/usr/local" ) - set( msgfmt "${CMAKE_CURRENT_BINARY_DIR}/msgfmt" ) - - if( NOT EXISTS "${root}" ) - execute_process( - COMMAND - curl -L -o /tmp/gettext.pkg https://raw.githubusercontent.com/rudix-mac/packages/master/gettext-0.20.1-macos10.14.pkg - ) - execute_process( - COMMAND - pkgutil --force --expand /tmp/gettext.pkg /tmp/gettext - ) - execute_process( - COMMAND - tar -C "${CMAKE_CURRENT_BINARY_DIR}" -x -f /tmp/gettext/gettextinstall.pkg/Payload - ) - execute_process( - COMMAND - rm -rf /tmp/gettext.pkg /tmp/gettext - ) - endif() - - file( WRITE ${CMAKE_BINARY_DIR}/msgfmt - "#!/bin/sh\n" - "export DYLD_LIBRARY_PATH=${root}/lib\n" - "${root}/bin/msgfmt \$@ \n" - ) - - file( COPY ${CMAKE_BINARY_DIR}/msgfmt - DESTINATION ${CMAKE_CURRENT_BINARY_DIR} - FILE_PERMISSIONS - OWNER_READ OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE - ) - - file( REMOVE ${CMAKE_BINARY_DIR}/msgfmt ) - - endif() - - -else() - - find_package( Gettext ) - if( GETTEXT_FOUND ) - set( msgfmt "${GETTEXT_MSGFMT_EXECUTABLE}" ) - endif() - + set( msgfmt "${GETTEXT_MSGFMT_EXECUTABLE}" ) +elseif( PYTHON ) + set( msgfmt "${PYTHON}" "${TARGET_ROOT}/msgfmt.py" ) +elseif( CMAKE_SYSTEM_NAME MATCHES "Windows" ) + nuget_package( pkgdir "Gettext.Tools" "0.20.1.1" ) + file( TO_NATIVE_PATH "${pkgdir}/tools/bin/msgfmt.exe" msgfmt ) endif() if( NOT DEFINED msgfmt ) - message( WARNING "Gettext not found...translations will not be provided." ) + message( WARNING "The msgfmt program wasn't found...translations will not be provided." ) return() endif() @@ -159,7 +111,7 @@ foreach( source ${SOURCES} ) COMMAND "${CMAKE_COMMAND}" -E make_directory "${dst}" COMMAND - "${msgfmt}" -o "${mo}" "${po}" + ${msgfmt} -o "${mo}" "${po}" OUTPUT "${mo}" ) diff --git a/locale/msgfmt.py b/locale/msgfmt.py new file mode 100644 index 000000000..97d7640a2 --- /dev/null +++ b/locale/msgfmt.py @@ -0,0 +1,305 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# Written by Martin v. Loewis +# +# Changed by Christian 'Tiran' Heimes for the placeless +# translation service (PTS) of Zope +# +# Fixed some bugs and updated to support msgctxt +# by Hanno Schlichting + +"""Generate binary message catalog from textual translation description. + +This program converts a textual Uniforum-style message catalog (.po file) into +a binary GNU catalog (.mo file). This is essentially the same function as the +GNU msgfmt program, however, it is a simpler implementation. + +This file was taken from Python-2.3.2/Tools/i18n and altered in several ways. +Now you can simply use it from another python module: + + from msgfmt import Msgfmt + mo = Msgfmt(po).get() + +where po is path to a po file as string, an opened po file ready for reading or +a list of strings (readlines of a po file) and mo is the compiled mo file as +binary string. + +Exceptions: + + * IOError if the file couldn't be read + + * msgfmt.PoSyntaxError if the po file has syntax errors +""" + +from __future__ import print_function +import array +from ast import literal_eval +import codecs +from email.parser import HeaderParser +import getopt +import struct +import sys + +PY3 = sys.version_info[0] == 3 +if PY3: + def header_charset(s): + p = HeaderParser() + return p.parsestr(s).get_content_charset() + + import io + BytesIO = io.BytesIO + FILE_TYPE = io.IOBase +else: + def header_charset(s): + p = HeaderParser() + return p.parsestr(s.encode('utf-8', 'ignore')).get_content_charset() + + from cStringIO import StringIO as BytesIO + FILE_TYPE = file + + +class PoSyntaxError(Exception): + """ Syntax error in a po file """ + + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return 'Po file syntax error: %s' % self.msg + + +class Msgfmt: + + def __init__(self, po, name='unknown'): + self.po = po + self.name = name + self.messages = {} + self.openfile = False + # Start off assuming latin-1, so everything decodes without failure, + # until we know the exact encoding + self.encoding = 'latin-1' + + def readPoData(self): + """ read po data from self.po and return an iterator """ + output = [] + if isinstance(self.po, str): + output = open(self.po, 'rb') + elif isinstance(self.po, FILE_TYPE): + self.po.seek(0) + self.openfile = True + output = self.po + elif isinstance(self.po, list): + output = self.po + if not output: + raise ValueError("self.po is invalid! %s" % type(self.po)) + if isinstance(output, FILE_TYPE): + # remove BOM from the start of the parsed input + first = output.readline() + if len(first) == 0: + return output.readlines() + if first.startswith(codecs.BOM_UTF8): + first = first.lstrip(codecs.BOM_UTF8) + return [first] + output.readlines() + return output + + def add(self, context, id, string, fuzzy): + "Add a non-empty and non-fuzzy translation to the dictionary." + if string and not fuzzy: + # The context is put before the id and separated by a EOT char. + if context: + id = context + u'\x04' + id + if not id: + # See whether there is an encoding declaration + charset = header_charset(string) + if charset: + # decode header in proper encoding + string = string.encode(self.encoding).decode(charset) + if not PY3: + # undo damage done by literal_eval in Python 2.x + string = string.encode(self.encoding).decode(charset) + self.encoding = charset + self.messages[id] = string + + def generate(self): + "Return the generated output." + # the keys are sorted in the .mo file + keys = sorted(self.messages.keys()) + offsets = [] + ids = strs = b'' + for id in keys: + msg = self.messages[id].encode(self.encoding) + id = id.encode(self.encoding) + # For each string, we need size and file offset. Each string is + # NUL terminated; the NUL does not count into the size. + offsets.append((len(ids), len(id), len(strs), + len(msg))) + ids += id + b'\0' + strs += msg + b'\0' + output = b'' + # The header is 7 32-bit unsigned integers. We don't use hash tables, + # so the keys start right after the index tables. + keystart = 7 * 4 + 16 * len(keys) + # and the values start after the keys + valuestart = keystart + len(ids) + koffsets = [] + voffsets = [] + # The string table first has the list of keys, then the list of values. + # Each entry has first the size of the string, then the file offset. + for o1, l1, o2, l2 in offsets: + koffsets += [l1, o1 + keystart] + voffsets += [l2, o2 + valuestart] + offsets = koffsets + voffsets + # Even though we don't use a hashtable, we still set its offset to be + # binary compatible with the gnu gettext format produced by: + # msgfmt file.po --no-hash + output = struct.pack("Iiiiiii", + 0x950412de, # Magic + 0, # Version + len(keys), # # of entries + 7 * 4, # start of key index + 7 * 4 + len(keys) * 8, # start of value index + 0, keystart) # size and offset of hash table + if PY3: + output += array.array("i", offsets).tobytes() + else: + output += array.array("i", offsets).tostring() + output += ids + output += strs + return output + + def get(self): + """ """ + self.read() + # Compute output + return self.generate() + + def read(self, header_only=False): + """ """ + ID = 1 + STR = 2 + CTXT = 3 + + section = None + fuzzy = 0 + msgid = msgstr = msgctxt = u'' + + # Parse the catalog + lno = 0 + for l in self.readPoData(): + l = l.decode(self.encoding) + lno += 1 + # If we get a comment line after a msgstr or a line starting with + # msgid or msgctxt, this is a new entry + if section == STR and (l[0] == '#' or (l[0] == 'm' and + (l.startswith('msgctxt') or l.startswith('msgid')))): + self.add(msgctxt, msgid, msgstr, fuzzy) + section = None + fuzzy = 0 + # If we only want the header we stop after the first message + if header_only: + break + # Record a fuzzy mark + if l[:2] == '#,' and 'fuzzy' in l: + fuzzy = 1 + # Skip comments + if l[0] == '#': + continue + # Now we are in a msgctxt section + if l.startswith('msgctxt'): + section = CTXT + l = l[7:] + msgctxt = u'' + # Now we are in a msgid section, output previous section + elif (l.startswith('msgid') and + not l.startswith('msgid_plural')): + if section == STR: + self.add(msgid, msgstr, fuzzy) + section = ID + l = l[5:] + msgid = msgstr = u'' + is_plural = False + # This is a message with plural forms + elif l.startswith('msgid_plural'): + if section != ID: + raise PoSyntaxError( + 'msgid_plural not preceeded by ' + 'msgid on line %d of po file %s' % + (lno, repr(self.name))) + l = l[12:] + msgid += u'\0' # separator of singular and plural + is_plural = True + # Now we are in a msgstr section + elif l.startswith('msgstr'): + section = STR + if l.startswith('msgstr['): + if not is_plural: + raise PoSyntaxError( + 'plural without msgid_plural ' + 'on line %d of po file %s' % + (lno, repr(self.name))) + l = l.split(']', 1)[1] + if msgstr: + # Separator of the various plural forms + msgstr += u'\0' + else: + if is_plural: + raise PoSyntaxError( + 'indexed msgstr required for ' + 'plural on line %d of po file %s' % + (lno, repr(self.name))) + l = l[6:] + # Skip empty lines + l = l.strip() + if not l: + continue + # TODO: Does this always follow Python escape semantics? + try: + l = literal_eval(l) + except Exception as msg: + raise PoSyntaxError( + '%s (line %d of po file %s): \n%s' % + (msg, lno, repr(self.name), l)) + if isinstance(l, bytes): + l = l.decode(self.encoding) + if section == CTXT: + msgctxt += l + elif section == ID: + msgid += l + elif section == STR: + msgstr += l + else: + raise PoSyntaxError( + 'error on line %d of po file %s' % + (lno, repr(self.name))) + + # Add last entry + if section == STR: + self.add(msgctxt, msgid, msgstr, fuzzy) + + if self.openfile: + self.po.close() + + def getAsFile(self): + return BytesIO(self.get()) + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 'o:') + except getopt.error as msg: + print(msg, file=sys.stderr) + sys.exit(1) + + if not args: + print('No input file given', file=sys.stderr) + sys.exit(1) + + if not opts: + print('No output file given', file=sys.stderr) + sys.exit(1) + + with open(opts[0][1], "w") as mo: + mo.write(Msgfmt(args[0]).get()) + +if __name__ == '__main__': + main() + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7a3076ce..47c60c625 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1015,6 +1015,10 @@ list( APPEND DEFINES WXINTL_NO_GETTEXT_MACRO WXUSINGDLL CMAKE + $<$: + REV_LONG="${GIT_COMMIT_LONG}" + REV_TIME="${GIT_COMMIT_TIME}" + > $<$: HAVE_LRINT >