#!/usr/bin/env perl
#
# Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
# 
# 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 3 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, see <http://www.gnu.org/licenses/>.
# ===
#
# make2cmake
#
# Parses the autoconf/automake artifacts throughout the cwd source
# tree and creates a set of 'cmake' files alongside the source.
#
# Usage: make2cmake [<options>]
#          --no-mbedtls     without mbedtls (ignored on unix: use ./configure --without-mbedtls)
#          --openssl        with openssl/libressl (ignored on unix)
#          --no-gui         without qt, no gui (ignored on unix)
#          --static-gui     gui statically linked to qt
#          --qt6            qt version 6
#
# On Windows by default the build will use "/MT" (static linkage) for
# the main targets but "/MD" for the GUI. Dynamic linking of the GUI
# allows linking against the binary Qt download, but it is no good
# for distribution. Use "--static-gui" when the Qt libraries have
# been built for static linkage.
#
# On Unix the 3rd-party packages (mbedtls, openssl and qt) should
# be installed in the usual places so that "-l<libname>" works and
# "moc" is on the path. Pass CXXFLAGS and LDFLAGS to the configure
# script if they are somewhere non-standard. These flags and the
# "--with" and "--enable" options are read out of the "config.status"
# file and end up in the generated cmake files.
#
# On Windows cmake must be told explicitly what 3rd-party libraries
# to use and where to find them; the cmake "find_package()" mechanism
# is not used because it is an unnecessary layer of obfuscation.
#
# When running cmake use CMAKE_BUILD_TYPE for debug/release selection
# iff using a single-config generator (eg. nmake). Otherwise pass
# the debug/release selection to the relevant build tool.
#
# Unix, cmake and make:
#   $ ./configure --with-whatever CXXFLAGS="-I/tmp" LDFLAGS="-L/tmp"
#   $ libexec/make2cmake
#   $ mkdir build
#   $ cd build
#   $ cmake ..
#   $ make -j 12
#
# Windows, cmake and visual studio:
#   $ perl libexec/make2cmake
#   $ mkdir build
#   $ cd build
#   $ cmake -G "Visual Studio ..." -A "x64" (or "win32")
#      -DQT_DIR=c:\qt5 (binary distribution, ie. /MD) (all full paths)
#      -DQT_MOC_DIR=c:\qt5\bin\ (with trailing backslash)
#      -DMBEDTLS_INC=c:\mbedtls\include
#      -DMBEDTLS_RLIB=c:\mbedtls-x64\x64\library\release (with /MT, see mbedtlsbuild.pl)
#      -DMBEDTLS_DLIB=c:\mbedtls-x64\x64\library\debug
#      -S .. -B .
#   $ msbuild /p:Configuration=Release emailrelay.sln
#
# Windows, cmake and nmake:
#   $ perl libexec/make2cmake
#   $ mkdir build
#   $ cd build
#   $ cmake -G "NMake Makefiles"
#      -DCMAKE_BUILD_TYPE=Release
#      -DQT_DIR=c:\qt5 (binary distribution, ie. /MD)
#      -DQT_MOC_DIR=c:\qt5\bin\ (with trailing backslash)
#      -DMBEDTLS_INC=c:\mbedtls\include
#      -DMBEDTLS_RLIB=c:\mbedtls-x64\x64\library\release (with /MT, see mbedtlsbuild.pl)
#      -S .. -B .
#   $ nmake /u VERBOSE=1
#
# Windows, cmake and nmake, static gui:
#   $ perl libexec/make2cmake --static-gui
#   $ mkdir build
#   $ cd build
#   $ cmake -G "NMake Makefiles"
#      -DCMAKE_BUILD_TYPE=Release
#      -DQT_DIR=c:\qt-x64 (with /MT, see qtbuild.pl)
#      -DQT_MOC_DIR=c:\qt-x64\bin\ (with trailing backslash)
#      -DMBEDTLS_INC=c:\mbedtls\include
#      -DMBEDTLS_RLIB=c:\mbedtls-x64\x64\library\release (with /MT, see mbedtlsbuild.pl)
#      -S .. -B .
#   $ nmake /u VERBOSE=1
#

use strict ;
use FileHandle ;
use Getopt::Long ;
use File::Basename ;
BEGIN { unshift @INC , dirname($0) }
use BuildInfo ;

package make2cmake ;

our $cfg_static_gui = 0 ;

sub create_cmake_file
{
	my ( $m ) = @_ ; # see BuildInfo.pm

	my $fh = new FileHandle( $m->{e_cmake_out} , "w" ) or die ;
	print $fh "# $$m{e_cmake_out} -- generated by $0\n" ;
	if( $m->{e_is_top_dir} )
	{
		print $fh "cmake_minimum_required(VERSION 3.12)\n" ;
		print $fh "project($$m{e_project})\n" ;
		if( $m->{e_is_windows} )
		{
			print $fh "set(CMAKE_MC_COMPILER mc CACHE FILEPATH \"Message compiler\")\n" ;
		}
	}

	if( $m->{e_is_windows} && (scalar(@{$m->{e_programs}})+scalar(@{$m->{e_libraries}})) )
	{
		if( !$m->{e_is_gui_dir} || $cfg_static_gui )
		{
			# prefer static linkage, ie. /MT and /MTd
			print $fh 'set(CompilerFlags' , "\n" ;
			print $fh '    CMAKE_CXX_FLAGS' , "\n" ;
			print $fh '    CMAKE_CXX_FLAGS_DEBUG' , "\n" ;
			print $fh '    CMAKE_CXX_FLAGS_RELEASE' , "\n" ;
			print $fh '    CMAKE_C_FLAGS' , "\n" ;
			print $fh '    CMAKE_C_FLAGS_DEBUG' , "\n" ;
			print $fh '    CMAKE_C_FLAGS_RELEASE' , "\n" ;
			print $fh ')' , "\n" ;
			print $fh 'foreach(CompilerFlag ${CompilerFlags})' , "\n" ;
			print $fh '    string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")' , "\n" ;
			print $fh 'endforeach()' , "\n" ;
		}
		else
		{
			# dynamically linked gui -- this is the default because
			# the binary qt distribution uses dynamic linkage
		}
	}
	print $fh "\n" ;
	for my $subdir ( @{$m->{e_subdirs}} )
	{
		print $fh "\n" ;
		print $fh "add_subdirectory($subdir)\n" ;
	}
	for my $library ( @{$m->{e_libraries}} )
	{
		my $e = $m->{e_library}->{$library} ;
		my $c = scalar(@{$e->{sources}}) == 0 ? "#" : "" ;
		my @includes = @{$e->{includes}} ;
		unshift @includes , '"${MBEDTLS_INC}"' if $m->{e_need_mbedtls_inc} ;
		unshift @includes , '"${OPENSSL_INC}"' if $m->{e_need_openssl_inc} ;
		unshift @includes , '"${QT_DIR}/include"' if $m->{e_need_qt_inc} ;
		my @definitions = @{$e->{definitions}} ;
		push @definitions , "G_QT_STATIC" if( $m->{e_is_gui_dir} && $cfg_static_gui ) ;
		print $fh "\n" ;
		print $fh "${c}add_library($$e{libname} STATIC ",join(" ",@{$e->{sources}}),")\n" ;
		print $fh "${c}target_compile_options($$e{libname} PRIVATE $$e{compile_options})\n" ;
		print $fh "${c}target_include_directories($$e{libname} PRIVATE ",join(" ",@includes),")\n" ;
		print $fh "${c}target_compile_definitions($$e{libname} PRIVATE ",join(" ",@definitions),")\n" ;
	}
	for my $progname ( @{$m->{e_programs}} )
	{
		my $e = $m->{e_program}->{$progname} ;
		print $fh "\n" ;
		my $win32 = ( $e->{subsystem} =~ m/windows/i ) ? "WIN32" : "" ;
		my @resources = ( $e->{messages_mc} , $e->{rcfile} ) ; # no $e->{manifest} here -- use link options
		my @includes = @{$e->{includes}} ;
		my @link_options = @{$e->{link_options}} ;
		push @link_options , $e->{uac_option} if $e->{uac_option} ;
		#push @link_options , $e->{commoncontrols_option} if $e->{commoncontrols_option} ; # is this useful?
		unshift @includes , '"${MBEDTLS_INC}"' if $m->{e_need_mbedtls_inc} ;
		unshift @includes , '"${OPENSSL_INC}"' if $m->{e_need_openssl_inc} ;
		unshift @includes , '"${QT_DIR}/include"' if $m->{e_need_qt_inc} ;
		push @includes , '"${CMAKE_CURRENT_BINARY_DIR}"' if $m->{need_qt_inc} ; # for inclusion of moc_*.cpp
		my @definitions = @{$e->{definitions}} ;
		push @definitions , "G_NO_MOC_INCLUDE" if @{$e->{moc_out}} ;
		push @definitions , "G_QT_STATIC" if( $e->{need_qt_libs} && $cfg_static_gui ) ;
		my @all_libs = @{$e->{our_libnames}} ;
		if( $m->{e_is_windows} )
		{
			push @all_libs , map { 'optimized "${MBEDTLS_RLIB}/'.$_.'.lib"' } @{$e->{mbedtls_libnames}} if $e->{need_mbedtls_libs} ;
			push @all_libs , map { 'debug "${MBEDTLS_DLIB}/'.$_.'.lib"' } @{$e->{mbedtls_libnames}} if $e->{need_mbedtls_libs} ;
			push @all_libs , map { 'optimized "${OPENSSL_RLIB}/'.$_.'.lib"' } @{$e->{openssl_libnames}} if $e->{need_openssl_libs} ;
			push @all_libs , map { 'debug "${OPENSSL_DLIB}/'.$_.'.lib"' } @{$e->{openssl_libnames}} if $e->{need_openssl_libs} ;
			if( $e->{need_qt_libs} )
			{
				push @all_libs , map { 'optimized "${QT_DIR}/lib/'.$_.'.lib"' } @{$e->{qt_libnames_release}} ;
				push @all_libs , map { 'debug "${QT_DIR}/lib/'.$_.'.lib"' } @{$e->{qt_libnames_debug}} ;
				if( $cfg_static_gui )
				{
					push @all_libs , map { 'optimized ${QT_DIR}/lib/'.$_.'.lib' } @{$e->{qt_static_libnames_release}} ;
					push @all_libs , map { 'debug ${QT_DIR}/lib/'.$_.'.lib' } @{$e->{qt_static_libnames_debug}} ;
					push @all_libs , @{$e->{qt_static_sys_libnames}} ;
				}
			}
		}
		else
		{
			push @all_libs , map { "-l$_" } @{$e->{mbedtls_libnames}} if $e->{need_mbedtls_libs} ;
			push @all_libs , map { "-l$_" } @{$e->{openssl_libnames}} if $e->{need_openssl_libs} ;
			push @all_libs , map { "optimized -l$_" } @{$e->{qt_libnames_release}} if $e->{need_qt_libs} ;
			push @all_libs , map { "debug -l${_}d" } @{$e->{qt_libnames_debug}} if $e->{need_qt_libs} ;
		}
		push @all_libs , @{$e->{sys_libnames}} ;

		print $fh "add_executable($$e{progname} ",join(" ",$win32,@resources,@{$e->{sources}},@{$e->{moc_out}}),")\n" ;
		print $fh "target_link_options($$e{progname} PRIVATE ",join(" ",@link_options),")\n" ; # cmake 3.13
		print $fh "target_include_directories($$e{progname} PRIVATE ",join(" ",@includes),")\n" ;
		print $fh "target_compile_definitions($$e{progname} PRIVATE ",join(" ",@definitions),")\n" ;
		print $fh "target_compile_options($$e{progname} PRIVATE $$e{compile_options})\n" ;
		print $fh "target_link_libraries($$e{progname} PRIVATE ",join(" ",@all_libs),")\n" ;

		if( scalar(@{$e->{moc_in}}) )
		{
			# (no AUTOMOC here because it relies on find_package())
			for my $i ( 0 .. scalar(@{$e->{moc_in}})-1 )
			{
				my $moc_in = @{$e->{moc_in}}[$i] ;
				my $moc_out = @{$e->{moc_out}}[$i] ;
				print $fh "add_custom_command(OUTPUT $moc_out MAIN_DEPENDENCY $moc_in COMMAND " ,
					"\"\${QT_MOC_DIR}moc\" -o \"$moc_out\" \"\${CMAKE_CURRENT_SOURCE_DIR}/$moc_in\" VERBATIM)\n" ;
			}
		}

		if( $e->{messages_mc} )
		{
			print $fh "add_custom_command(OUTPUT $$e{binfile} MAIN_DEPENDENCY $$e{messages_mc} COMMAND " ,
				"\"\${CMAKE_MC_COMPILER}\" \"\${CMAKE_CURRENT_SOURCE_DIR}/$$e{messages_mc}\" VERBATIM)\n" ;
		}
	}
	$fh->close() or die ;
}

# ==

package main ;

if( basename($0) eq "make2cmake" )
{
	my %opt = () ;
	if( !GetOptions( \%opt , "help|h" , "no-mbedtls" , "openssl" , "no-gui" ,
		"static-gui|s" , "qt6" ,
		"debug-dump" , "debug-dump-all" ) || $opt{help} )
	{
		my $error = $opt{help} ? "" : "error: " ;
		print "${error}usage: make2cmake [<options>]\n" ;
		print "          --no-mbedtls     without mbedtls (windows only)\n" ;
		print "          --openssl        with openssl (windows only)\n" ;
		print "          --no-gui         without qt, no gui (windows only)\n" ;
		print "          --static-gui     gui statically linked to qt\n" ;
		print "          --qt6            qt version 6\n" ;
		print "          --debug-dump\n" ;
		print "          --debug-dump-all\n" ;
		exit( $error ? 1 : 0 ) ;
	}

	my $opt_qt_version = $opt{qt6} ? 6 : 5 ; # see also BuildInfo::qt_version()

	$make2cmake::cfg_static_gui = $opt{'static-gui'} ; # see also BuildInfo::qt_is_static()
	my %makefiles_opt = (
		windows_mbedtls => !$opt{'no-mbedtls'} ,
		windows_openssl => $opt{openssl} ,
		windows_gui => !$opt{'no-gui'} ,
		qt_version => $opt_qt_version ,
		verbose => 0 , # AutoMakeParser::readall()
	) ;

	my @m = BuildInfo::read_makefiles( "." , "make2cmake: " , \%makefiles_opt ) ;
	if( $opt{'debug-dump'} ) { BuildInfo::dump( @m ) ; exit(0) }
	if( $opt{'debug-dump-all'} ) { BuildInfo::dumpall( @m ) ; exit(0) }
	for my $m ( @m )
	{
		print "make2cmake: cmake-file=[$$m{e_cmake_out}]\n" ;
		make2cmake::create_cmake_file( $m ) ;
	}
	print "make2cmake: CMakeList.txt files created: now use cmake in a build directory\n" ;
}
else
{
	1 ;
}

