IS_WIN_SHELL =
ifeq (${SHELL}, sh.exe)
  IS_WIN_SHELL = true
endif
ifeq (${SHELL}, cmd)
  IS_WIN_SHELL = true
endif

ifdef IS_WIN_SHELL
  RM := del /s /q
  RMDIR := rmdir /s /q
  NOOP := @@
else
  RM := rm -f
  RMDIR := rm -rf
  NOOP := @:
endif

# set LDFLAGS environment variable to any extra ldflags required
LDFLAGS := $(LDFLAGS)

# set OUTPUT environment variable to generate a specific binary name
# this will apply to both `stash` and `phasher`, so build them separately
# alternatively use STASH_OUTPUT or PHASHER_OUTPUT to set the value individually
ifdef OUTPUT
  STASH_OUTPUT := $(OUTPUT)
  PHASHER_OUTPUT := $(OUTPUT)
endif
ifdef STASH_OUTPUT
  STASH_OUTPUT := -o $(STASH_OUTPUT)
endif
ifdef PHASHER_OUTPUT
  PHASHER_OUTPUT := -o $(PHASHER_OUTPUT)
endif

# set GO_BUILD_FLAGS environment variable to any extra build flags required
GO_BUILD_FLAGS := $(GO_BUILD_FLAGS)

# set GO_BUILD_TAGS environment variable to any extra build tags required
GO_BUILD_TAGS := $(GO_BUILD_TAGS)
GO_BUILD_TAGS += sqlite_stat4 sqlite_math_functions

# set STASH_NOLEGACY environment variable or uncomment to disable legacy browser support
# STASH_NOLEGACY := true

# set STASH_SOURCEMAPS environment variable or uncomment to enable UI sourcemaps
# STASH_SOURCEMAPS := true

export CGO_ENABLED := 1

# define COMPILER_IMAGE for cross-compilation docker container
ifndef COMPILER_IMAGE
  COMPILER_IMAGE := stashapp/compiler:latest
endif

.PHONY: release
release: pre-ui generate ui build-release

# targets to set various build flags
# use combinations on the make command-line to configure a build, e.g.:
# for a static-pie release build: `make flags-static-pie flags-release stash`
# for a static windows debug build: `make flags-static-windows stash`

# $(NOOP) prevents "nothing to be done" warnings

.PHONY: flags-release
flags-release:
	$(NOOP)
	$(eval LDFLAGS += -s -w)
	$(eval GO_BUILD_FLAGS += -trimpath)

.PHONY: flags-pie
flags-pie:
	$(NOOP)
	$(eval GO_BUILD_FLAGS += -buildmode=pie)

.PHONY: flags-static
flags-static:
	$(NOOP)
	$(eval LDFLAGS += -extldflags=-static)
	$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo netgo)

.PHONY: flags-static-pie
flags-static-pie:
	$(NOOP)
	$(eval LDFLAGS += -extldflags=-static-pie)
	$(eval GO_BUILD_FLAGS += -buildmode=pie)
	$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo netgo)

# identical to flags-static-pie, but excluding netgo, which is not needed on windows
.PHONY: flags-static-windows
flags-static-windows:
	$(NOOP)
	$(eval LDFLAGS += -extldflags=-static-pie)
	$(eval GO_BUILD_FLAGS += -buildmode=pie)
	$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo)

.PHONY: build-info
build-info:
ifndef BUILD_DATE
	$(eval BUILD_DATE := $(shell go run scripts/getDate.go))
endif
ifndef GITHASH
	$(eval GITHASH := $(shell git rev-parse --short HEAD))
endif
ifndef STASH_VERSION
	$(eval STASH_VERSION := $(shell git describe --tags --exclude latest_develop))
endif
ifndef OFFICIAL_BUILD
	$(eval OFFICIAL_BUILD := false)
endif

.PHONY: build-flags
build-flags: build-info
	$(eval BUILD_LDFLAGS := $(LDFLAGS))
	$(eval BUILD_LDFLAGS += -X 'github.com/stashapp/stash/internal/build.buildstamp=$(BUILD_DATE)')
	$(eval BUILD_LDFLAGS += -X 'github.com/stashapp/stash/internal/build.githash=$(GITHASH)')
	$(eval BUILD_LDFLAGS += -X 'github.com/stashapp/stash/internal/build.version=$(STASH_VERSION)')
	$(eval BUILD_LDFLAGS += -X 'github.com/stashapp/stash/internal/build.officialBuild=$(OFFICIAL_BUILD)')
	$(eval BUILD_FLAGS := -v -tags "$(GO_BUILD_TAGS)" $(GO_BUILD_FLAGS) -ldflags "$(BUILD_LDFLAGS)")

.PHONY: stash
stash: build-flags
	go build $(STASH_OUTPUT) $(BUILD_FLAGS) ./cmd/stash

.PHONY: phasher
phasher: build-flags
	go build $(PHASHER_OUTPUT) $(BUILD_FLAGS) ./cmd/phasher

# builds dynamically-linked debug binaries
.PHONY: build
build: stash phasher

# builds dynamically-linked PIE release binaries
.PHONY: build-release
build-release: flags-release flags-pie build

# compile and bundle into Stash.app
# for when on macOS itself
.PHONY: stash-macapp
stash-macapp: STASH_OUTPUT := -o stash
stash-macapp: flags-release flags-pie stash
	rm -rf Stash.app
	cp -R scripts/macos-bundle Stash.app
	mkdir Stash.app/Contents/MacOS
	cp stash Stash.app/Contents/MacOS/stash

# build-cc- targets should be run within the compiler docker container

.PHONY: build-cc-windows
build-cc-windows: export GOOS := windows
build-cc-windows: export GOARCH := amd64
build-cc-windows: export CC := x86_64-w64-mingw32-gcc
build-cc-windows: STASH_OUTPUT := -o dist/stash-win.exe
build-cc-windows: PHASHER_OUTPUT :=-o dist/phasher-win.exe
build-cc-windows: flags-release
build-cc-windows: flags-static-windows
build-cc-windows: build

.PHONY: build-cc-macos-intel
build-cc-macos-intel: export GOOS := darwin
build-cc-macos-intel: export GOARCH := amd64
build-cc-macos-intel: export CC := o64-clang
build-cc-macos-intel: STASH_OUTPUT := -o dist/stash-macos-intel
build-cc-macos-intel: PHASHER_OUTPUT := -o dist/phasher-macos-intel
build-cc-macos-intel: flags-release
# can't use static build for macOS
build-cc-macos-intel: flags-pie
build-cc-macos-intel: build

.PHONY: build-cc-macos-arm
build-cc-macos-arm: export GOOS := darwin
build-cc-macos-arm: export GOARCH := arm64
build-cc-macos-arm: export CC := oa64e-clang
build-cc-macos-arm: STASH_OUTPUT := -o dist/stash-macos-arm
build-cc-macos-arm: PHASHER_OUTPUT := -o dist/phasher-macos-arm
build-cc-macos-arm: flags-release
# can't use static build for macOS
build-cc-macos-arm: flags-pie
build-cc-macos-arm: build

.PHONY: build-cc-macos
build-cc-macos:
	make build-cc-macos-arm
	make build-cc-macos-intel

	# Combine into universal binaries
	lipo -create -output dist/stash-macos dist/stash-macos-intel dist/stash-macos-arm
	rm dist/stash-macos-intel dist/stash-macos-arm
	lipo -create -output dist/phasher-macos dist/phasher-macos-intel dist/phasher-macos-arm
	rm dist/phasher-macos-intel dist/phasher-macos-arm

	# Place into bundle and zip up
	rm -rf dist/Stash.app
	cp -R scripts/macos-bundle dist/Stash.app
	mkdir dist/Stash.app/Contents/MacOS
	cp dist/stash-macos dist/Stash.app/Contents/MacOS/stash
	cd dist && rm -f Stash.app.zip && zip -r Stash.app.zip Stash.app
	rm -rf dist/Stash.app

.PHONY: build-cc-freebsd
build-cc-freebsd: export GOOS := freebsd
build-cc-freebsd: export GOARCH := amd64
build-cc-freebsd: export CC := clang -target x86_64-unknown-freebsd12.0 --sysroot=/opt/cross-freebsd
build-cc-freebsd: STASH_OUTPUT := -o dist/stash-freebsd
build-cc-freebsd: PHASHER_OUTPUT := -o dist/phasher-freebsd
build-cc-freebsd: flags-release
build-cc-freebsd: flags-static-pie
build-cc-freebsd: build

.PHONY: build-cc-linux
build-cc-linux: export GOOS := linux
build-cc-linux: export GOARCH := amd64
build-cc-linux: STASH_OUTPUT := -o dist/stash-linux
build-cc-linux: PHASHER_OUTPUT := -o dist/phasher-linux
build-cc-linux: flags-release
build-cc-linux: flags-static-pie
build-cc-linux: build

.PHONY: build-cc-linux-arm64v8
build-cc-linux-arm64v8: export GOOS := linux
build-cc-linux-arm64v8: export GOARCH := arm64
build-cc-linux-arm64v8: export CC := aarch64-linux-gnu-gcc
build-cc-linux-arm64v8: STASH_OUTPUT := -o dist/stash-linux-arm64v8
build-cc-linux-arm64v8: PHASHER_OUTPUT := -o dist/phasher-linux-arm64v8
build-cc-linux-arm64v8: flags-release
build-cc-linux-arm64v8: flags-static-pie
build-cc-linux-arm64v8: build

.PHONY: build-cc-linux-arm32v7
build-cc-linux-arm32v7: export GOOS := linux
build-cc-linux-arm32v7: export GOARCH := arm
build-cc-linux-arm32v7: export GOARM := 7
build-cc-linux-arm32v7: export CC := arm-linux-gnueabi-gcc -march=armv7-a
build-cc-linux-arm32v7: STASH_OUTPUT := -o dist/stash-linux-arm32v7
build-cc-linux-arm32v7: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v7
build-cc-linux-arm32v7: flags-release
build-cc-linux-arm32v7: flags-static
build-cc-linux-arm32v7: build

.PHONY: build-cc-linux-arm32v6
build-cc-linux-arm32v6: export GOOS := linux
build-cc-linux-arm32v6: export GOARCH := arm
build-cc-linux-arm32v6: export GOARM := 6
build-cc-linux-arm32v6: export CC := arm-linux-gnueabi-gcc
build-cc-linux-arm32v6: STASH_OUTPUT := -o dist/stash-linux-arm32v6
build-cc-linux-arm32v6: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v6
build-cc-linux-arm32v6: flags-release
build-cc-linux-arm32v6: flags-static
build-cc-linux-arm32v6: build

.PHONY: build-cc-all
build-cc-all:
	make build-cc-windows
	make build-cc-macos
	make build-cc-linux
	make build-cc-linux-arm64v8
	make build-cc-linux-arm32v7
	make build-cc-linux-arm32v6
	make build-cc-freebsd

.PHONY: touch-ui
touch-ui:
ifdef IS_WIN_SHELL
	@if not exist "ui\\v2.5\\build" mkdir ui\\v2.5\\build
	@type nul >> ui/v2.5/build/index.html
else
	@mkdir -p ui/v2.5/build
	@touch ui/v2.5/build/index.html
endif

# Regenerates GraphQL files
.PHONY: generate
generate: generate-backend generate-ui

.PHONY: generate-ui
generate-ui:
	cd ui/v2.5 && npm run gqlgen

.PHONY: generate-backend
generate-backend: touch-ui
	go generate ./cmd/stash

.PHONY: generate-login-locale
generate-login-locale:
	go generate ./ui

.PHONY: generate-dataloaders
generate-dataloaders:
	go generate ./internal/api/loaders

# Regenerates stash-box client files
.PHONY: generate-stash-box-client
generate-stash-box-client:
	go run github.com/Yamashou/gqlgenc

# Runs gofmt -w on the project's source code, modifying any files that do not match its style.
.PHONY: fmt
fmt:
	go fmt ./...

.PHONY: lint
lint:
	golangci-lint run

# runs unit tests - excluding integration tests
.PHONY: test
test:
	go test ./...

# runs all tests - including integration tests
.PHONY: it
it:
	$(eval GO_BUILD_TAGS += integration)
	go test -tags "$(GO_BUILD_TAGS)" ./...

# generates test mocks
.PHONY: generate-test-mocks
generate-test-mocks:
	go run github.com/vektra/mockery/v2

# runs server
# sets the config file to use the local dev config
.PHONY: server-start
server-start: export STASH_CONFIG_FILE := config.yml
server-start: build-flags
ifdef IS_WIN_SHELL
	@if not exist ".local" mkdir .local
else
	@mkdir -p .local
endif
	cd .local && go run $(BUILD_FLAGS) ../cmd/stash

# removes local dev config files
.PHONY: server-clean
server-clean:
	$(RMDIR) .local

# installs UI dependencies. Run when first cloning repository, or if UI
# dependencies have changed
# If CI is set, configures pnpm to use a local store to avoid
# putting .pnpm-store in /stash
# NOTE: to run in the docker build container, using the existing
# node_modules folder, rename the .modules.yaml to .modules.yaml.bak
# and a new one will be generated. This will need to be reversed after
# building.
.PHONY: pre-ui
pre-ui:
ifdef CI
	cd ui/v2.5 && pnpm config set store-dir ~/.pnpm-store && pnpm install --frozen-lockfile
else
	cd ui/v2.5 && pnpm install --frozen-lockfile
endif

.PHONY: ui-env
ui-env: build-info
	$(eval export VITE_APP_DATE := $(BUILD_DATE))
	$(eval export VITE_APP_GITHASH := $(GITHASH))
	$(eval export VITE_APP_STASH_VERSION := $(STASH_VERSION))
ifdef STASH_NOLEGACY
	$(eval export VITE_APP_NOLEGACY := true)
endif
ifdef STASH_SOURCEMAPS
	$(eval export VITE_APP_SOURCEMAPS := true)
endif

.PHONY: ui
ui: ui-only generate-login-locale

.PHONY: ui-only
ui-only: ui-env
	cd ui/v2.5 && npm run build

.PHONY: zip-ui
zip-ui:
	rm -f dist/stash-ui.zip
	cd ui/v2.5/build && zip -r ../../../dist/stash-ui.zip .

.PHONY: ui-start
ui-start: ui-env
	cd ui/v2.5 && npm run start -- --host

.PHONY: fmt-ui
fmt-ui:
	cd ui/v2.5 && npm run format

# runs all of the frontend PR-acceptance steps
.PHONY: validate-ui
validate-ui:
	cd ui/v2.5 && npm run validate

# these targets run the same steps as fmt-ui and validate-ui, but only on files that have changed
fmt-ui-quick:
	cd ui/v2.5 && \
	files=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
	if [ -n "$$files" ]; then \
	  npm run prettier -- --write $$files; \
	fi

# does not run tsc checks, as they are slow
validate-ui-quick:
	cd ui/v2.5 && \
	tsfiles=$$(git diff --name-only --relative --diff-filter d src | grep -e "\.tsx\?\$$"); \
	scssfiles=$$(git diff --name-only --relative --diff-filter d src | grep "\.scss"); \
	prettyfiles=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
	if [ -n "$$tsfiles" ]; then npm run eslint -- $$tsfiles; fi && \
	if [ -n "$$scssfiles" ]; then npm run stylelint -- $$scssfiles; fi && \
	if [ -n "$$prettyfiles" ]; then npm run prettier -- --check $$prettyfiles; fi

# runs all of the backend PR-acceptance steps
.PHONY: validate-backend
validate-backend: lint it

# runs all of the tests and checks required for a PR to be accepted
.PHONY: validate
validate: validate-ui validate-backend

# locally builds and tags a 'stash/build' docker image
.PHONY: docker-build
docker-build: build-info
	docker build --build-arg GITHASH=$(GITHASH) --build-arg STASH_VERSION=$(STASH_VERSION) -t stash/build -f docker/build/x86_64/Dockerfile .

# locally builds and tags a 'stash/cuda-build' docker image
.PHONY: docker-cuda-build
docker-cuda-build: build-info
	docker build --build-arg GITHASH=$(GITHASH) --build-arg STASH_VERSION=$(STASH_VERSION) -t stash/cuda-build -f docker/build/x86_64/Dockerfile-CUDA .

# start the build container - for cross compilation
# this is adapted from the github actions build.yml file
.PHONY: start-compiler-container
start-compiler-container:
	docker run -d --name build --mount type=bind,source="$(PWD)",target=/stash,consistency=delegated $(EXTRA_CONTAINER_ARGS) -w /stash $(COMPILER_IMAGE) tail -f /dev/null

# run the cross-compilation using
# docker exec -t build /bin/bash -c "make build-cc-<platform>"

.PHONY: remove-compiler-container
remove-compiler-container:
	docker rm -f -v build