all:

-include Makefile.win.local # for optional local options e.g. threads

# Recipes for this Makefile

## Build the compiler
##   $ make -f Makefile.win
## Build the compiler with progress output
##   $ make -f Makefile.win progress=1
## Clean up built files then build the compiler
##   $ make -f Makefile.win clean crystal
## Build the compiler in release mode
##   $ make -f Makefile.win crystal release=1 interpreter=1
## Run tests
##   $ make -f Makefile.win test
## Run stdlib tests
##   $ make -f Makefile.win std_spec
## Run compiler tests
##   $ make -f Makefile.win compiler_spec
## Run generators (Unicode, SSL config, ...)
##   $ make -B generate_data

CRYSTAL ?= crystal ## which previous crystal compiler use

release ?=        ## Compile in release mode
stats ?=          ## Enable statistics output
progress ?=       ## Enable progress output
threads ?=        ## Maximum number of threads to use
debug ?=          ## Add symbolic debug info
verbose ?=        ## Run specs in verbose mode
junit_output ?=   ## Path to output junit results
static ?=         ## Enable static linking
target ?=         ## Cross-compilation target
interpreter ?=    ## Enable interpreter feature
check ?=          ## Enable only check when running format
order ?=random    ## Enable order for spec execution (values: "default" | "random" | seed number)
docs_sanitizer ?= ## Enable sanitization for documentation generation
sequential_codegen ?= ## Enforce sequential codegen in compiler builds. Base compiler before Crystal 1.8 cannot build with `-Dpreview_mt`

MAKEFLAGS += --no-builtin-rules
.SUFFIXES:

SHELL := cmd.exe
CXX := cl.exe
RC := rc.exe

GLOB = $(shell dir $1 /B /S)
MKDIR = if not exist $1 mkdir $1
CP = copy /B /Y $1 $2
CPDIR = robocopy /E /NJH /NJS /NS /NC /NP $1 $2 & if %%ERRORLEVEL%% GEQ 8 exit /B 1
INSTALL = copy /B /Y $1 $2
INSTALLDIR = robocopy /E /NJH /NJS /NS /NC /NP $1 $2 & if %%ERRORLEVEL%% GEQ 8 exit /B 1
MV = move /Y $1 $2
RM = if exist $1 del /F /Q $1
RMDIR = if exist $1 rd /S /Q $1

O := .build
SOURCES := $(call GLOB,src\\*.cr)
SPEC_SOURCES := $(call GLOB,spec\\*.cr)
override FLAGS += -D strict_multi_assign -D preview_overload_order $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )
# NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2.
# Newly built compilers should never be distributed with libpcre to ensure syntax consistency.
override COMPILER_FLAGS += $(if $(interpreter),,-Dwithout_interpreter )$(if $(docs_sanitizer),,-Dwithout_libxml2 ) -Dwithout_openssl -Dwithout_zlib$(if $(sequential_codegen),, -Dpreview_mt -Dexecution_context) $(if $(USE_PCRE1),-Duse_pcre,-Duse_pcre2)
SPEC_WARNINGS_OFF := --exclude-warnings spec\std --exclude-warnings spec\compiler --exclude-warnings spec\primitives --exclude-warnings src\float\printer
SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )$(if $(order),--order=$(order) )
CRYSTAL_CONFIG_LIBRARY_PATH := $$ORIGIN\lib
CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD)
CRYSTAL_CONFIG_PATH := $$ORIGIN\src
ifndef CRYSTAL_VERSION
	CRYSTAL_VERSION := $(shell type src\VERSION)
endif
ifndef SOURCE_DATE_EPOCH
	SOURCE_DATE_EPOCH := $(or $(shell type src\SOURCE_DATE_EPOCH 2>NUL),$(shell git show -s --format=%ct HEAD))
endif
export_vars = $(eval export CRYSTAL_CONFIG_BUILD_COMMIT CRYSTAL_CONFIG_PATH SOURCE_DATE_EPOCH)
export_build_vars = $(eval export CRYSTAL_CONFIG_LIBRARY_PATH)

ifeq ($(LLVM_VERSION),)
  LLVM_CONFIG ?=
  LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell "$(LLVM_CONFIG)" --version))
endif

LLVM_EXT_DIR = src\llvm\ext
LLVM_EXT_OBJ = $(LLVM_EXT_DIR)\llvm_ext.obj
CXXFLAGS += $(if $(static),$(if $(debug),/MTd /Od ,/MT ),$(if $(debug),/MDd /Od ,/MD ))

prefix ?= $(or $(ProgramW6432),$(ProgramFiles))\crystal
BINDIR ?= $(prefix)
LIBDIR ?= $(prefix)\lib
SRCDIR ?= $(prefix)\src
DATADIR ?= $(prefix)

colorize = $(info $1)

DEPS = $(LLVM_EXT_OBJ)
ifneq ($(LLVM_VERSION),)
  ifeq ($(shell if $(firstword $(subst ., ,$(LLVM_VERSION))) GEQ 18 echo 0),0)
    DEPS =
  endif
endif

check_llvm_config = $(eval \
	check_llvm_config := $(if $(LLVM_VERSION),\
		$(call colorize,Using $(or $(LLVM_CONFIG),externally configured LLVM) [version=$(LLVM_VERSION)]),\
		$(error "Could not locate compatible llvm-config, make sure it is installed and in your PATH, or set LLVM_VERSION / LLVM_CONFIG. Compatible versions: $(shell type src\llvm\ext\llvm-versions.txt)))\
	)

.PHONY: all
all: crystal ## Build all files (currently crystal only) [default]

.PHONY: test
test: spec ## Run tests

.PHONY: spec
spec: std_spec primitives_spec compiler_spec

.PHONY: std_spec
std_spec: $(O)\std_spec.exe ## Run standard library specs
	$(O)\std_spec $(SPEC_FLAGS)

.PHONY: compiler_spec
compiler_spec: $(O)\compiler_spec.exe ## Run compiler specs
	$(O)\compiler_spec $(SPEC_FLAGS)

.PHONY: primitives_spec
primitives_spec: $(O)\primitives_spec.exe ## Run primitives specs
	$(O)\primitives_spec $(SPEC_FLAGS)

.PHONY: interpreter_spec
interpreter_spec: $(O)\interpreter_spec ## Run interpreter specs
	$(O)\interpreter_spec $(SPEC_FLAGS)

.PHONY: smoke_test
smoke_test: ## Build specs as a smoke test
smoke_test: $(O)\std_spec.exe $(O)\compiler_spec.exe $(O)\crystal.exe

.PHONY: all_spec
all_spec: $(O)\all_spec.exe ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage)
	$(O)\all_spec $(SPEC_FLAGS)

.PHONY: samples
samples: ## Build example programs
	$(MAKE) -C samples -f $(MAKEFILE_LIST)

.PHONY: docs
docs: ## Generate standard library documentation
	$(call check_llvm_config)
	.\bin\crystal docs src\docs_main.cr $(DOCS_OPTIONS) --project-name=Crystal --project-version=$(CRYSTAL_VERSION) --source-refname=$(CRYSTAL_CONFIG_BUILD_COMMIT)

.PHONY: crystal
crystal: $(O)\crystal.exe ## Build the compiler

.PHONY: deps llvm_ext
deps: $(DEPS) ## Build dependencies
llvm_ext: $(LLVM_EXT_OBJ)

.PHONY: format
format: ## Format sources
	.\bin\crystal tool format$(if $(check), --check) src spec samples scripts

.PHONY: generate_data
generate_data: ## Run generator scripts for Unicode, SSL config, ...
	$(MAKE) -B -f scripts/generate_data.mk

.PHONY: install
install: $(O)\crystal.exe ## Install the compiler at prefix
	$(call MKDIR,"$(BINDIR)")
	$(call INSTALL,"$(O)\crystal.exe","$(BINDIR)\crystal.exe")
	$(call INSTALL,"$(O)\crystal.pdb","$(BINDIR)\crystal.pdb")

	$(call MKDIR,"$(DATADIR)")
	$(call INSTALLDIR,src,"$(DATADIR)\src")
	$(call RM,"$(DATADIR)\$(LLVM_EXT_OBJ)")

	$(call INSTALL,LICENSE,"$(DATADIR)\LICENSE.txt")

.PHONY: uninstall
uninstall: ## Uninstall the compiler from prefix
	$(call RM,"$(DATADIR)\LICENSE.txt")

	$(call RMDIR,"$(DATADIR)\src")

	$(call RM,"$(BINDIR)\crystal.exe")
	$(call RM,"$(BINDIR)\crystal.pdb")

.PHONY: install_docs
install_docs: docs ## Install docs at prefix
	$(call MKDIR,"$(DATADIR)")
	$(call INSTALLDIR,docs,"$(DATADIR)\docs")
	$(call INSTALLDIR,samples,"$(DATADIR)\examples")

.PHONY: uninstall_docs
uninstall_docs: ## Uninstall docs from prefix
	$(call RMDIR,"$(DATADIR)\docs")
	$(call RMDIR,"$(DATADIR)\examples")

$(O)\all_spec.exe: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
	$(call check_llvm_config)
	@$(call MKDIR,"$(O)")
	$(call export_vars)
	.\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\all_spec.cr

$(O)\std_spec.exe: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
	$(call check_llvm_config)
	@$(call MKDIR,"$(O)")
	.\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\std_spec.cr

$(O)\compiler_spec.exe: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
	$(call check_llvm_config)
	@$(call MKDIR,"$(O)")
	$(call export_vars)
	.\bin\crystal build $(FLAGS) $(COMPILER_FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\compiler_spec.cr --release

$(O)\primitives_spec.exe: $(O)\crystal.exe $(DEPS) $(SOURCES) $(SPEC_SOURCES)
	@$(call MKDIR,"$(O)")
	.\bin\crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o "$@" spec\primitives_spec.cr

$(O)\interpreter_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
	$(eval interpreter=1)
	@$(call MKDIR, "$(O)")
	.\bin\crystal build $(FLAGS) $(COMPILER_FLAGS)  $(SPEC_WARNINGS_OFF) -o $@ spec\compiler\interpreter_spec.cr

$(O)\crystal.exe: $(DEPS) $(SOURCES) $(O)\crystal.res
	$(call check_llvm_config)
	@$(call MKDIR,"$(O)")
	$(call export_vars)
	$(call export_build_vars)
	.\bin\crystal build $(FLAGS) $(COMPILER_FLAGS)  -o "$(O)\crystal-next.exe" src\compiler\crystal.cr --link-flags=/PDBALTPATH:crystal.pdb "--link-flags=$(realpath $(O)\crystal.res)"
	$(call MV,"$(O)\crystal-next.exe","$@")
	$(call MV,"$(O)\crystal-next.pdb","$(O)\crystal.pdb")

$(O)\crystal.res: $(O)\crystal.rc
	@$(call MKDIR,"$(O)")
	$(RC) /nologo /Fo "$@" "$<"

$(O)\crystal.rc: $(MAKEFILE_LIST)
	@$(call MKDIR,"$(O)")
	$(MAKE) -s -f $(MAKEFILE_LIST) rc > "$@"

$(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)\llvm_ext.cc
	$(call check_llvm_config)
	@rem Disable some harmless warnings:
	@rem - warning C4244: 'initializing': conversion from '_Ty' to '_Ty2', possible loss of data
	@rem - warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
	@rem - warning C4624: '...': destructor was implicitly defined as deleted
	$(CXX) /nologo /c $(CXXFLAGS) /WX /wd4244 /wd4624 /wd4530 "/Fo$@" "$<" $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --cxxflags))

.PHONY: clean
clean: clean_crystal ## Clean up built directories and files
	$(call RM,"$(LLVM_EXT_OBJ)")

.PHONY: clean_crystal
clean_crystal: ## Clean up crystal built files
	$(call RMDIR,"$(O)")
	$(call RMDIR,docs)

.PHONY: clean_cache
clean_cache: ## Clean up CRYSTAL_CACHE_DIR files
	$(call RMDIR,"$(shell .\bin\crystal env CRYSTAL_CACHE_DIR)")

.PHONY: rc
rc: ## Write compiler resource script to standard output
	$(eval comma := ,)
	$(eval ver_comma := $(subst .,$(comma),$(subst -dev,,$(CRYSTAL_VERSION))))
	$(eval source_date := $(shell git show -s --format=%cs HEAD))
	$(eval source_year := $(firstword $(subst -, ,$(source_date))))
	$(eval ver_full := $(CRYSTAL_VERSION)$(if $(CRYSTAL_CONFIG_BUILD_COMMIT), [$(CRYSTAL_CONFIG_BUILD_COMMIT)])$(if $(source_date), ($(source_date))))
	@setlocal EnableDelayedExpansion &\
	echo #include ^<windows.h^>&\
	echo.&\
	set "_dir=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))" &\
	echo 1 ICON ^"!_dir:/=\\!src\\compiler\\crystal\\tools\\playground\\public\\favicon.ico^"&\
	echo.&\
	echo VS_VERSION_INFO VERSIONINFO&\
	echo     PRODUCTVERSION $(ver_comma),0&\
	echo     FILEVERSION    $(ver_comma),1&\
	echo     FILEFLAGSMASK  VS_FFI_FILEFLAGSMASK&\
	echo     FILEFLAGS      $(if $(findstring -dev,$(CRYSTAL_VERSION)),VS_FF_PRERELEASE,0)&\
	echo     FILEOS         VOS_NT_WINDOWS32&\
	echo     FILETYPE       VFT_APP&\
	echo     FILESUBTYPE    VFT2_UNKNOWN&\
	echo BEGIN&\
	echo     BLOCK "StringFileInfo"&\
	echo     BEGIN&\
	echo         BLOCK "040904B0"&\
	echo         BEGIN&\
	echo             VALUE "ProductName",      "Crystal"&\
	echo             VALUE "ProductVersion",   "$(ver_full)"&\
	echo             VALUE "FileVersion",      "$(ver_full)"&\
	echo             VALUE "FileDescription",  "Crystal$(if $(LLVM_CONFIG), $(shell $(LLVM_CONFIG) --host-target))"&\
	echo             VALUE "Comments",         "$(if $(LLVM_VERSION),LLVM $(LLVM_VERSION))"&\
	echo             VALUE "InternalName",     "crystal.exe"&\
	echo             VALUE "OriginalFilename", "crystal.exe"&\
	echo             VALUE "CompanyName",      "Manas Technology Solutions"&\
	echo             VALUE "LegalCopyright",   "Copyright 2012-$(source_year) Manas Technology Solutions"&\
	echo         END&\
	echo     END&\
	echo     BLOCK "VarFileInfo"&\
	echo     BEGIN&\
	echo         VALUE "Translation", 0x0409, 0x04B0&\
	echo     END&\
	echo END

.PHONY: help
help: ## Show this help
	@setlocal EnableDelayedExpansion &\
	echo. &\
	echo targets: &\
	(for /F "usebackq tokens=1* delims=:" %%g in ($(MAKEFILE_LIST)) do (\
		if not "%%h" == "" (\
			set "_line=%%g                " &\
			set "_rest=%%h" &\
			set "_comment=!_rest:* ## =!" &\
			if not "!_comment!" == "!_rest!"\
				if "!_line:_rest=!" == "!_line!"\
					echo   !_line:~0,16!!_comment!\
		)\
	)) &\
	echo. &\
	echo optional variables: &\
	(for /F "usebackq tokens=1,3 delims=?#" %%g in ($(MAKEFILE_LIST)) do (\
		if not "%%h" == "" (\
			set "_var=%%g              " &\
			echo   !_var:~0,14! %%h\
		)\
	)) &\
	echo. &\
	echo recipes: &\
	(for /F "usebackq tokens=* delims=" %%g in ($(MAKEFILE_LIST)) do (\
		set "_line=%%g" &\
		if "!_line:~0,7!" == "##   $$ " (\
			echo !_name! &\
			echo  !_line:~2!\
		) else if "!_line:~0,3!" == "## "\
			set "_name=  !_line:~3!"\
	))
