@echo off
:: Copyright (C) 1996-2023 Free Software Foundation, Inc.
:: This file is part of GNU Make.
::
:: GNU Make 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.
::
:: GNU Make 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 <https://www.gnu.org/licenses/>.

setlocal
if not "%RECURSEME%"=="%~0" (
    set "RECURSEME=%~0"
    %ComSpec% /s /c ""%~0" %*"
    goto :EOF
)

call :Reset

if "%1" == "-h" goto Usage
if "%1" == "--help" goto Usage

echo.
echo Creating GNU Make for Windows 9X/NT/2K/XP/Vista/7/8/10/11
echo.

set MAKE=gnumake
set GUILE=Y
set COMPILER=cl.exe
set O=obj
set ARCH=x64
set DEBUG=N
set DIRENT=Y
set VERBOSE=N

if exist maintMakefile (
    set MAINT=Y
) else (
    set MAINT=N
)

:ParseSW
if "%1" == "--verbose" goto SetVerbose
if "%1" == "--debug" goto SetDebug
if "%1" == "--without-guile" goto NoGuile
if "%1" == "--x86" goto Set32Bit
if "%1" == "gcc" goto SetCC
if "%1" == "tcc" goto SetTCC
if "%1" == "" goto DoneSW
goto Usage

:SetVerbose
set VERBOSE=Y
shift
goto ParseSW

:SetDebug
set DEBUG=Y
echo - Building without compiler optimizations
shift
goto ParseSW

:NoGuile
set GUILE=N
echo - Building without Guile
shift
goto ParseSW

:Set32Bit
set ARCH=x86
echo - Building 32bit GNU Make
shift
goto ParseSW

:SetCC
set COMPILER=gcc
set O=o
echo - Building with GCC
shift
goto ParseSW

:SetTCC
set COMPILER=tcc
set O=o
echo - Building with TinyC
shift
goto ParseSW

:DoneSW
if "%MAINT%" == "Y" echo - Enabling maintainer mode

if "%COMPILER%" == "gcc" goto FindGcc
if "%COMPILER%" == "tcc" goto FindTcc

:: Find a compiler.  Visual Studio requires a lot of effort to locate :-/.
call %COMPILER% >nul 2>&1
if not ERRORLEVEL 1 goto FoundMSVC

:: Visual Studio 17 and above provides the "vswhere" tool
call :FindVswhere
if ERRORLEVEL 1 goto LegacyVS

for /f "tokens=* usebackq" %%i in (`"%VSWHERE%" -latest -property installationPath`) do (
    set InstallPath=%%i
)
set "VSVARS=%InstallPath%\VC\Auxiliary\Build\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

:: No "vswhere" or it can't find a compiler.  Go old-school.
:LegacyVS
set "VSVARS=%VS150COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS120COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS110COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS100COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS90COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS80COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS71COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%VS70COMNTOOLS%\..\..\VC\vcvarsall.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%V6TOOLS%\VC98\Bin\vcvars32.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%V6TOOLS%\VC97\Bin\vcvars32.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

set "VSVARS=%V5TOOLS%\VC\Bin\vcvars32.bat"
call :CheckMSVC
if not ERRORLEVEL 1 goto FoundMSVC

:: We did not find anything--fail
echo No MSVC compiler available.
echo Please run vcvarsall.bat and/or configure your Path.
exit 1

:FoundMSVC
set OUTDIR=.\WinRel
set LNKOUT=./WinRel
set "OPTS=/O2 /D NDEBUG"
set LINKOPTS=
if "%DEBUG%" == "Y" set OUTDIR=.\WinDebug
if "%DEBUG%" == "Y" set LNKOUT=./WinDebug
if "%DEBUG%" == "Y" set "OPTS=/Zi /Od /D _DEBUG"
if "%DEBUG%" == "Y" set LINKOPTS=/DEBUG
if "%MAINT%" == "Y" set "OPTS=%OPTS% /D MAKE_MAINTAINER_MODE"
:: Show the compiler version that we found
:: Unfortunately this also shows a "usage" note; I can't find anything better.
echo.
call %COMPILER%
goto Build

:FindGcc
set OUTDIR=.\GccRel
set LNKOUT=./GccRel
set OPTS=-O2
set DIRENT=N
if "%DEBUG%" == "Y" set OPTS=-O0
if "%DEBUG%" == "Y" set OUTDIR=.\GccDebug
if "%DEBUG%" == "Y" set LNKOUT=./GccDebug
if "%MAINT%" == "Y" set "OPTS=%OPTS% -DMAKE_MAINTAINER_MODE"
:: Show the compiler version that we found
echo.
call %COMPILER% --version
if not ERRORLEVEL 1 goto Build
echo No %COMPILER% found.
exit 1

:FindTcc
set OUTDIR=.\TccRel
set LNKOUT=./TccRel
set OPTS=-O2
if "%DEBUG%" == "Y" set OPTS=-O0
if "%DEBUG%" == "Y" set OUTDIR=.\TccDebug
if "%DEBUG%" == "Y" set LNKOUT=./TccDebug
if "%MAINT%" == "Y" set "OPTS=%OPTS% -DMAKE_MAINTAINER_MODE"
:: Show the compiler version that we found
echo.
call %COMPILER% -v
if not ERRORLEVEL 1 goto Build
echo No %COMPILER% found.
exit 1

:Build
:: Clean the directory if it exists
if exist %OUTDIR%\nul rmdir /S /Q %OUTDIR%

:: Recreate it
mkdir %OUTDIR%
mkdir %OUTDIR%\src
mkdir %OUTDIR%\src\w32
mkdir %OUTDIR%\src\w32\compat
mkdir %OUTDIR%\src\w32\subproc
mkdir %OUTDIR%\lib

if "%GUILE%" == "Y" call :ChkGuile

if not exist src\config.h.W32 goto NotConfig

echo.
echo Compiling %OUTDIR% version

copy src\config.h.W32 %OUTDIR%\src\config.h

copy lib\glob.in.h %OUTDIR%\lib\glob.h
copy lib\fnmatch.in.h %OUTDIR%\lib\fnmatch.h

if exist %OUTDIR%\link.sc del %OUTDIR%\link.sc

call :Compile src/ar
call :Compile src/arscan
call :Compile src/commands
call :Compile src/default
call :Compile src/dir
call :Compile src/expand
call :Compile src/file
call :Compile src/function
call :Compile src/getopt
call :Compile src/getopt1
call :Compile src/guile GUILE
call :Compile src/hash
call :Compile src/implicit
call :Compile src/job
call :Compile src/load
call :Compile src/loadapi
call :Compile src/main GUILE
call :Compile src/misc
call :Compile src/output
call :Compile src/read
call :Compile src/remake
call :Compile src/remote-stub
call :Compile src/rule
call :Compile src/shuffle
call :Compile src/signame
call :Compile src/strcache
call :Compile src/variable
call :Compile src/version
call :Compile src/vpath
call :Compile src/w32/pathstuff
call :Compile src/w32/w32os
call :Compile src/w32/compat/posixfcn
call :Compile src/w32/subproc/misc
call :Compile src/w32/subproc/sub_proc
call :Compile src/w32/subproc/w32err
call :Compile lib/fnmatch
call :Compile lib/glob
call :Compile lib/getloadavg

:: Compile dirent unless it is supported by compiler library (like with gcc).
if "%DIRENT%" == "Y" call :Compile src\w32\compat\dirent

call :Link

echo.
if exist %OUTDIR%\%MAKE%.exe goto Success
echo %OUTDIR% build FAILED!
exit 1

:Success
echo %OUTDIR% build succeeded.
if exist Basic.mk copy /Y Basic.mk Makefile
if not exist tests\config-flags.pm copy /Y tests\config-flags.pm.W32 tests\config-flags.pm
call :Reset
goto :EOF

::
:: Subroutines
::

:Compile
if "%VERBOSE%" == "N" echo - Compiling %1.c
echo %LNKOUT%/%1.%O% >>%OUTDIR%\link.sc
set EXTRAS=
if "%2" == "GUILE" set "EXTRAS=%GUILECFLAGS%"
if exist "%OUTDIR%\%1.%O%" del "%OUTDIR%\%1.%O%"
if "%COMPILER%" == "gcc" goto GccCompile
if "%COMPILER%" == "tcc" goto TccCompile

:: MSVC Compile
if "%VERBOSE%" == "Y" echo on
call %COMPILER% /nologo /MT /W4 /EHsc %OPTS% /I %OUTDIR%/src /I src /I %OUTDIR%/lib /I lib /I src/w32/include /D _CONSOLE /D HAVE_CONFIG_H /FR%OUTDIR% /Fp%OUTDIR%\%MAKE%.pch /Fo%OUTDIR%\%1.%O% /Fd%OUTDIR%\%MAKE%.pdb %EXTRAS% /c %1.c
@echo off
goto CompileDone

:GccCompile
:: GCC Compile
if "%VERBOSE%" == "Y" echo on
call %COMPILER% -mthreads -Wall -std=gnu99 -gdwarf-2 -g3 %OPTS% -I%OUTDIR%/src -I./src -I%OUTDIR%/lib -I./lib -I./src/w32/include -DHAVE_CONFIG_H %EXTRAS% -o %OUTDIR%/%1.%O% -c %1.c
@echo off
goto CompileDone

:TccCompile
:: TCC Compile
if "%VERBOSE%" == "Y" echo on
call %COMPILER% -mthreads -Wall -std=c11 %OPTS% -I%OUTDIR%/src -I./src -I%OUTDIR%/lib -I./lib -I./src/w32/include -D_cdecl= -D_MSC_VER -DHAVE_CONFIG_H %EXTRAS% -o %OUTDIR%/%1.%O% -c %1.c
@echo off
goto CompileDone

:CompileDone
if not exist "%OUTDIR%\%1.%O%" exit 1
goto :EOF

:Link
echo.
echo - Linking %LNKOUT%/%MAKE%.exe
if "%COMPILER%" == "gcc" goto GccLink
if "%COMPILER%" == "tcc" goto TccLink

:: MSVC Link
echo %GUILELIBS% kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib >>%OUTDIR%\link.sc
if "%VERBOSE%" == "Y" echo on
call link.exe /NOLOGO /SUBSYSTEM:console /PDB:%LNKOUT%\%MAKE%.pdb %LINKOPTS% /OUT:%LNKOUT%\%MAKE%.exe @%LNKOUT%\link.sc
@echo off
goto :EOF

:GccLink
:: GCC Link
if "%VERBOSE%" == "Y" echo on
echo %GUILELIBS% -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -luuid -lodbc32 -lodbccp32 >>%OUTDIR%\link.sc
call %COMPILER% -mthreads -gdwarf-2 -g3 %OPTS% -o %LNKOUT%/%MAKE%.exe @%LNKOUT%/link.sc -Wl,--out-implib=%LNKOUT%/libgnumake-1.dll.a
@echo off
goto :EOF

:TccLink
:: TCC Link
if "%VERBOSE%" == "Y" echo on
echo %GUILELIBS% -lkernel32 -luser32 -lgdi32 -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -lodbc32 -lodbccp32 >>%OUTDIR%\link.sc
call %COMPILER% -mthreads %OPTS% -o %LNKOUT%/%MAKE%.exe @%LNKOUT%/link.sc 
@echo off
goto :EOF

:ChkGuile
:: Build with Guile is supported only on NT and later versions
if not "%OS%" == "Windows_NT" goto NoGuile
call pkg-config --help > %OUTDIR%\guile.tmp 2> NUL
if ERRORLEVEL 1 goto NoPkgCfg

set PKGMSC=
if not "%COMPILER%" == "gcc" set PKGMSC=--msvc-syntax

echo Checking for Guile 2.0
call pkg-config --cflags --short-errors "guile-2.0" > %OUTDIR%\gl-c2.tmp 2> NUL
if not ERRORLEVEL 1 set /P GUILECFLAGS= < %OUTDIR%\gl-c2.tmp

call pkg-config --libs --static --short-errors %PKGMSC% "guile-2.0" > %OUTDIR%\gl-l2.tmp 2> NUL
if not ERRORLEVEL 1 set /P GUILELIBS= < %OUTDIR%\gl-l2.tmp

if not "%GUILECFLAGS%" == "" goto GuileDone

echo Checking for Guile 1.8
call pkg-config --cflags --short-errors "guile-1.8" > %OUTDIR%\gl-c18.tmp 2> NUL
if not ERRORLEVEL 1 set /P GUILECFLAGS= < %OUTDIR%\gl-c18.tmp

call pkg-config --libs --static --short-errors %PKGMSC% "guile-1.8" > %OUTDIR%\gl-l18.tmp 2> NUL
if not ERRORLEVEL 1 set /P GUILELIBS= < %OUTDIR%\gl-l18.tmp

if not "%GUILECFLAGS%" == "" goto GuileDone

echo - No Guile found, building without Guile
goto GuileDone

:NoPkgCfg
echo - pkg-config not found, building without Guile

:GuileDone
if "%GUILECFLAGS%" == "" goto :EOF

echo - Guile found: building with Guile
set "GUILECFLAGS=%GUILECFLAGS% -DHAVE_GUILE"
goto :EOF

:FindVswhere
set VSWHERE=vswhere
call "%VSWHERE%" -help >nul 2>&1
if not ERRORLEVEL 1 exit /b 0
set "VSWHERE=C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere"
call "%VSWHERE%" -help >nul 2>&1
if ERRORLEVEL 1 exit /b 1
goto :EOF

:CheckMSVC
if not exist "%VSVARS%" exit /b 1
call "%VSVARS%" %ARCH%
if ERRORLEVEL 1 exit /b 1
call %COMPILER% >nul 2>&1
if ERRORLEVEL 1 exit /b 1
goto :EOF

:NotConfig
echo.
echo *** This workspace is not configured.
echo Either retrieve the configured source in the release tarball
echo or, if building from Git, run the .\bootstrap.bat script first.
exit /b 1

:Usage
echo Usage: %0 [options] [gcc] OR [tcc]
echo Options:
echo.  --without-guile   Do not compile Guile support even if found
echo.  --debug           Make a Debug build--default is Release
echo.  --x86             Make a 32bit binary--default is 64bit
echo.  --help            Display these instructions and exit
echo.
echo. "gcc" means compile with GCC, "tcc" means compile with Tiny C's TCC
goto :EOF

:Reset
set ARCH=
set COMPILER=
set DEBUG=
set GUILE=
set GUILECFLAGS=
set GUILELIBS=
set LINKOPTS=
set MAKE=
set NOGUILE=
set O=
set OPTS=
set OUTDIR=
set LNKOUT=
set PKGMSC=
set VSVARS=
goto :EOF
