# LOCAL MACHINE
uname 					:= $(shell uname)
mkfile_dir 				:= $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))

gdatecmd 				:= $(shell "command" -v gdate)
ifdef gdatecmd
gdate = gdate
else
gdate = date
endif
gdateok := $(shell 2>&1 $(gdate) --version | grep coreutils > /dev/null; echo $$?)
ifneq ($(gdateok), 0)
$(info **WARNING**: Missing coreutils "date" command. Please install coreutils.) # Needed for make dist
endif

gtarcmd 				:= $(shell "command" -v gtar)
ifdef gtarcmd
gtar = gtar
else
gtar = tar
endif
gtarok := $(shell 2>&1 $(gtar) --version | grep 'GNU tar' > /dev/null; echo $$?)
ifneq ($(gtarok), 0)
$(info **WARNING**: Missing GNU "tar" command. Please install gnu-tar.) # Needed for make dist
endif

permstr = -perm +111
ifneq ($(uname), Darwin)
permstr = -perm /111
endif

# PYTHON ENVIRONMENT
PYTHONHASHSEED 			= 10
VIRTUAL_ENV 			?= $(mkfile_dir)
python 					= $(VIRTUAL_ENV)/bin/python3
sphinx 					= $(python) -m sphinx
pip 					= $(python) -m pip
safety 					= $(python) -m safety
flake8 					= $(python) -m flake8
sphinx-apidoc 			= $(python) -m sphinx.ext.apidoc

# NSCLI
version 				= $(shell $(python) -c "import sys; sys.path.insert(0, \"$(mkfile_dir)\"); import nscli; print(nscli.__version__)")
requires.txt 			= nscli.egg-info/requires.txt
egginfo 				= nscli.egg-info
config.db 				= nscli/data/config.db
nscli_files 			= $(shell find nscli -type f \! -name \*.pyc)
pkg_sources 			= setup.cfg setup.py $(nscli_files) $(manpages) $(completions)

# DOCUMENTATION
email_src 				= docs/src/ReleaseEmail.rst
email_html 				= docs/email/ReleaseEmail.html
changelog 				= docs/src/Changelog.rst
current_changelog 		= docs/src/CurrentChangelog.rst
news_files 				= $(shell find docs/src/newsfragments -type f)
regionlist 				= docs/src/RegionList.rst
docs/src 				= $(shell find docs/src -type f -name \*.rst)
webpage 				= docs/gh-pages
manpages 				= docs/man/nscli.1.gz docs/man/nscli_settings.5.gz
bash_completion 		= completions/bash/nscli
zsh_completion 			= completions/zsh/_nscli
completions 			= $(bash_completion) $(zsh_completion)

# PYTHON PACKAGING
tarball 				= dist/nscli-$(version).tar.gz
wheel 					= dist/nscli-$(version)-py3-none-any.whl

# MACOS PACKAGING
brew_formula 			= Formula/nscli.rb
bottle 					= dist/nscli-$(version).high_sierra.bottle.tar.gz

# DEBIAN PACKAGING
# The .deb installer files encode a lot of information in the filename. Here we declare the filenames we wish to create,
# which makes for easy targets. The recipe to make the .deb files parses relevant information from the desired filename
# (using the debparse_* macros defined below) and passes it to the build environment. To support a new .deb release,
# just declare the filename below and include it in $(debian_releases) for convenience. There needs to be a
# corresponding docker image available on the hub; for example, debian:10 or ubuntu:19.04.
debian10 				= dist/debian/nscli_$(version)-1debian1~10_amd64.deb
ubuntu1804 				= dist/debian/nscli_$(version)-1ubuntu1~18.04_amd64.deb
ubuntu1910 				= dist/debian/nscli_$(version)-1ubuntu1~19.10_amd64.deb
ubuntu2004 				= dist/debian/nscli_$(version)-1ubuntu1~20.04_amd64.deb
debian_releases 		= $(debian10) $(ubuntu1804) $(ubuntu1910) $(ubuntu2004)
debian_control_files 	= $(shell find debian -type f)
debparse_dist_id 		= $(shell echo $(1) | cut -d '-' -f 2 | cut -d '~' -f 1 | tr -d '[:digit:]')
debparse_dist_ver 		= $(shell echo $(1) | cut -d '-' -f 2 | cut -d '~' -f 2 | cut -d '_' -f 1)
debparse_image_name 	= nscli_builder_$(call debparse_dist_id,$1)_$(call debparse_dist_ver,$1):$(version)
debparse_codename 		= $(shell docker run --rm $(call debparse_image_name,$1) grep -m 1 CODENAME /etc/os-release | cut -d '=' -f 2)

.PHONY: all
all: help

.PHONY: clean
clean: docclean  ## Delete temporary files
	-rm -rf dist build .eggs nscli.egg-info .tox .coverage htmlcov coverage.xml .config share version
	-rm -f $(config.db)
	-find nscli -name __pycache__ -exec rm -rf "{}" \;
	-find nscli -name '*.pyc' -exec rm -f "{}" \;

.PHONY: distclean
distclean: clean  ## Delete anything that's not part of the repo
	git reset HEAD --hard
	git clean -fxd

.PHONY: test
test: $(config.db) $(manpages) $(python) $(completions) ## Run tests
	# Ensure necessary test credentials are loaded
	$(python) -m nscli.tests.tools
	# Run test suites
	tox

.PHONY: dist
dist: $(tarball) $(wheel)  ## Create a new distribution

.PHONY: bottle
bottle: $(bottle)  ## Create a new Homebrew bottle

.PHONY: debian
debian: $(debian_releases)  ## Create new Debian and Ubuntu packages

.PHONY: webpage
webpage: $(webpage)  ## Regenerate the webpage files

.PHONY: formula
brew_formula: $(brew_formula)  ## Update the homebrew installer.

.PHONY: formula_upload
formula_upload: $(bottle)  ## Upload the homebrew installer to github.
	@echo "Making sure the OAUTHTOKEN environment variable is exported..."
	@test -n "$(OAUTHTOKEN)"
	@-rm -rf Formula/homebrew-rs
	@mkdir -p Formula
	@git clone -b nscli git@github.rackspace.com:SupportTools/homebrew-rs.git Formula/homebrew-rs
	@cp $(brew_formula) Formula/homebrew-rs/Formula
	@if ! [ -z "$$(git -C Formula/homebrew-rs status --porcelain -- Formula/nscli.rb)" ]; then \
		git -C Formula/homebrew-rs config --local user.email "$$(git config --local --get user.email)"; \
		git -C Formula/homebrew-rs config --local user.signingkey "$$(git config --local --get user.signingkey)"; \
		git -C Formula/homebrew-rs commit Formula/nscli.rb -m "update nscli.rb"; \
		git -C Formula/homebrew-rs push; \
		curl -fsS https://github.rackspace.com/api/v3/repos/SupportTools/homebrew-rs/pulls -H "Authorization: token $(OAUTHTOKEN)" -d '{"title":"update nscli.rb","body":"New version of NSCLI formula","head":"nscli","base":"master"}' > /dev/null || (echo "Make a PR manually at https://github.rackspace.com/SupportTools/homebrew-rs/compare/nscli" && /usr/bin/false);\
	else \
		echo "No change"; \
	fi

.PHONY: devenv
devenv:  ## Set up your nscli dev environment. Creates a virtual environment.
	python3 -m venv $(VIRTUAL_ENV)
	$(pip) install -U --upgrade-strategy eager pip setuptools
	# https://setuptools.readthedocs.io/en/latest/setuptools.html#develop-deploy-the-project-source-in-development-mode
	$(pip) install -U --upgrade-strategy eager -e '.[dev]'
	make $(config.db)

.PHONY: lint
lint: $(python) version_check  ## Check code style
	# Check for files with executable bits set. This should only be nscli/shared/2048.sh and nscli/__main__.py
	$(eval execs:=$(shell find nscli -type f $(permstr)))
	test "$(execs)" = "nscli/shared/2048.sh nscli/__main__.py" -o "nscli/__main__.py nscli/shared/2048.sh"
	# All directories and Python files in NSCLI must be lowercase and must not include spaces or dashes.
	! find nscli -type f -name '*.py' -o -type d | grep -e '[A-Z ]'
	# All Python packages under nscli must include a __init__.py file. If a directory contains Python code but no init,
	# the next line will error out.
	test -z `find nscli -type d '!' -exec test -e "{}/__init__.py" ';' -exec find "{}" -name '*.py' ';'`
	$(safety) check --full-report
	$(python) scripts/plugin_checker.py
	$(flake8) --config=$(mkfile_dir)/setup.cfg

.PHONY: webpage_upload
webpage_upload: $(webpage) ## Upload the nscli documentation webpage to the gh-pages branch in GitHub
	# Verify the gh_pages branch exists. This branch should be pulled down, not created new
	git rev-parse --verify gh-pages || (echo "gh-pages branch not found. Try 'git fetch --all && git checkout gh-pages'" && exit 1)
	$(eval branch := $(shell git rev-parse --abbrev-ref HEAD))
	git checkout gh-pages
	git pull
	git rm -rf .  # un-stage all files
	git add docs/gh-pages/*  # stage only the output files we need
	git mv docs/gh-pages/* .  # move output files to root of repo
	touch .nojekyll && git add .nojekyll  # This file tells github to serve up the site as-is
	git commit --all -m "rebuild docs"
	git push
	git checkout $(branch)
	-$(python) -c 'import webbrowser; webbrowser.open("https://pages.github.rackspace.com/nsi/nscli/")'

.PHONY: email
email: $(email_html) $(python)  ## Send the release-day email
	$(python) scripts/send_release_email.py -t

.PHONY: upload
upload: $(tarball) $(wheel) $(python)  ## Upload a new distribution to artifactory.
	$(python) -m twine upload --repository-url https://artifacts.rackspace.net/artifactory/api/pypi/nscli-pypi-local $(tarball) $(wheel)

.PHONY: bottle_upload
bottle_upload: $(bottle)  ## Upload the built bottle to Artifactory
	@echo "Checking if a bottle by this name has already been uploaded..."
	@! curl -fsI "https://artifacts.rackspace.net/artifactory/nsi-bottles-local/$(bottle)" > /dev/null
	@echo "Making sure the API_KEY environment variable is exported..."
	@test -n "$(API_KEY)"
	@curl -i -H 'X-JFrog-Art-Api: $(API_KEY)' -T $(bottle) "https://artifacts.rackspace.net/artifactory/nsi-bottles-local/$(notdir $(bottle))"

.PHONY: version_check
version_check: $(python)
	@echo Validating version number...
	@$(python) scripts/versioning.py

.PHONY: docclean
docclean:
	# Delete cached documentation to avoid a lot of bugs in Sphinx. Especially required for sphinx_selective_exclude
	-find . -name .doctrees -exec rm -rf "{}" \;

.PHONY: banner
banner:
	@echo ""
	@echo "  ██╗    ██╗███████╗ ██████╗██╗     ██╗  "
	@echo "  ████╗  ██║██╔════╝██╔════╝██║     ██║  "
	@echo "  ██╔██╗ ██║███████╗██║     ██║     ██║  "
	@echo "  ██║╚██╗██║╚════██║██║     ██║     ██║  "
	@echo "  ██║ ╚████║███████║╚██████╗███████╗██║  "
	@echo "  ╚═╝  ╚═══╝╚══════╝ ╚═════╝╚══════╝╚═╝  "
	@echo ""

.PHONY: help
help: banner
# Help function shamelessly stolen from the Rackspace Engineering Handbook
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: buildcontainer
buildcontainer:
# Build the nscli testing environment locally. Best left to Jenkins.
	docker build --pull -t nscli_base:latest .
	test -f docker/credentials.json
	echo 'FROM nscli_base:latest' | docker build -t nscli_loaded:latest . -f -

#.PHONY: release
#release: devenv lint test webpage_upload dist tag formula upload mirror_upload email ## Perform all the steps to release a new version of nscli

requirements.txt: $(requires.txt)
	@printf "%s\n" '-i https://artifacts.rackspace.net/artifactory/api/pypi/pypi/simple/' > requirements.txt
	@cat $(requires.txt) | sed -e '/^$$/,$$d' >> requirements.txt
	# TODO: This next line is a gross hack
	@printf "%s\n" importlib_metadata >> requirements.txt

version: nscli/__init__.py
	@printf "$(version)\n" > version && chmod 644 version

$(brew_formula): requirements.txt $(tarball) $(python)
	# This recipe can only run on a Mac
	@test "Darwin" = "$(uname)"
	@-rm -rf pip_packages; mkdir pip_packages
	@echo Downloading required packages with pip in order to record their URLs and hashes...
	$(pip) download --no-binary :all: -i https://artifacts.rackspace.net/artifactory/api/pypi/pypi/simple/ -d pip_packages -r requirements.txt | tee pip_packages/download_log
	cd pip_packages && awk '/Downloading https/ {print $$2} /Using cached https/ {print $$3}' download_log > urls
#	cd pip_packages && awk '/Using cached https/ {print $$3}' download_log >> urls
	cd pip_packages && cat urls | xargs basename | xargs shasum -a 256 | awk '{print $$1}' > shasums && awk '/^Collecting/ {print $$2}' download_log | cut -d ';' -f 1 > requirements && pr -w 999 -m -t requirements urls shasums > data
	mkdir -p "$(dir $(brew_formula))"
	$(python) scripts/make_formula.py scripts/formula_template.jinja2 pip_packages/data $(brew_formula) $(version) $(shell shasum -a 256 $(tarball) | awk '{print $$1}')

$(bottle): $(brew_formula) $(python)
	# This recipe can only run on a Mac
	@test "Darwin" = "$(uname)"
#	HOMEBREW_MAKE_JOBS=64
	@echo "Checking if the Python sdist needs to be uploaded to artifactory..."
	@curl -fsI https://artifacts.rackspace.net/artifactory/api/pypi/pypi/packages/nscli/$(version)/nscli-$(version).tar.gz > /dev/null || make upload
	@-brew uninstall --force nscli
	@brew install ./Formula/nscli.rb --build-bottle
	@brew bottle ./Formula/nscli.rb --force-core-tap --json
	@$(python) scripts/fixup_bottle.py nscli--$(version).high_sierra.bottle.json --root_url 'https://artifacts.rackspace.net/artifactory/nsi-bottles-local'
	@brew bottle ./nscli--$(version).high_sierra.bottle.json --merge --write --no-commit
	@mv nscli--$(version).high_sierra.bottle.tar.gz $(bottle)
	@cat nscli--$(version).high_sierra.bottle.json | $(python) -m json.tool
	@rm nscli--$(version).high_sierra.bottle.json
	@sleep 2 && touch $(bottle)
#	brew test-bot homebrew/core/nscli --keep-logs

%.deb: requirements.txt $(pkg_sources) $(manpages) $(debian_control_files)
	@mkdir -p "$(dir $@)"
	docker build --pull -t $(call debparse_image_name,$@) \
		--build-arg VERSION=$(version) \
		--build-arg DIST_VERSION=$(call debparse_dist_ver,$@) \
		--build-arg DIST_ID=$(call debparse_dist_id,$@) \
		-f debian/Dockerfile .
	docker run --rm $(call debparse_image_name,$@) tar -C /dpkg -c "$(notdir $@)" | tar -C "$(dir $@)" -xv

.PHONY: debian_upload
debian_upload: $(debian_releases:.deb=.deb.upload)  ## Uploads the .deb release files to artifactory

%.deb.upload: %.deb
	$(eval FILE=$*.deb)
	@if test -z "$(API_KEY)"; then \
		echo "The API_KEY environment variable for artifactory is not exported."; \
		exit 1; \
	fi
	@if curl -fsI "https://artifacts.rackspace.net/artifactory/nscli-debian-local/pool/$(notdir $(FILE))" > /dev/null; then \
		echo $(notdir $(FILE)) already exists on artifactory.; \
		return 0; \
	fi
	@echo Uploading $(FILE)
	@curl -i -H "X-JFrog-Art-Api:$(API_KEY)" -XPUT -T $(FILE) \
	-H "X-Checksum-Sha1: $(shell shasum -a 1 $(FILE) | awk '{print $$1}')" -H "X-Checksum-Md5: $(shell md5sum $(FILE) | awk '{print $$1}')" \
	"https://artifacts.rackspace.net/artifactory/nscli-debian-local/pool/$(notdir $(FILE));deb.distribution=$(call debparse_codename,$(FILE));deb.component=main;deb.architecture=amd64" && echo

$(python):
	make devenv

$(requires.txt): setup.cfg setup.py | $(egginfo)

$(egginfo): $(pkg_sources) $(python)
	@-rm -rf nscli.egg-info
	$(python) setup.py egg_info > /dev/null

$(webpage): $(regionlist) $(docs/src) $(python)
	PYTHONHASHSEED=$(PYTHONHASHSEED) $(sphinx) -b html docs/src docs/gh-pages -D version="$(version)" -D release="$(version)"

.PHONY: changelog
changelog: $(changelog)  ## Updates the changelog from the news files
$(changelog): $(news_files)
	$(python) -m towncrier --yes

$(current_changelog): $(changelog)
	head -n $$(($$(grep -n '=======' docs/src/Changelog.rst | head -n 2 | tail -n 1 | cut -d ':' -f 1)-2)) $(changelog) > $(current_changelog)

$(email_html): $(email_src) $(current_changelog) $(python) | docclean
	$(sphinx) -t email -b html docs/src docs/email -D version="$(version)" -D release="$(version)"

$(regionlist): $(config.db) $(python)
	$(python) scripts/print_regions.py > $(regionlist)

$(config.db): $(python)
	$(python) -c 'from nscli.config_db import DataBase; DataBase("nscli/data/config.db")'

.PHONY: manpage
manpage: $(manpages)  ## Rebuild the man pages
$(manpages): docs/src $(regionlist) $(python) | docclean
	PYTHONHASHSEED=$(PYTHONHASHSEED) $(sphinx) -b man docs/src docs/man -D version="$(version)" -D release="$(version)"
	-rm -rf docs/man/*.gz
	gzip --no-name -9 docs/man/*

.PHONY: completions
completions: $(completions)  ## Rebuild the shell completion files

$(zsh_completion): $(nscli_files) $(python)
	@mkdir -p completions/zsh
	$(python) -m nscli settings --zsh > $(zsh_completion)

$(bash_completion): $(nscli_files) $(python)
	@mkdir -p completions/bash
	$(python) -m nscli settings --bash > $(bash_completion)

$(tarball): $(config.db) $(pkg_sources) $(manpages) $(python) | version_check
	@-rm -rf dist/pre
	@mkdir -p dist/pre
	@echo Creating python sdist...
	@PYTHONHASHSEED=$(PYTHONHASHSEED) $(python) setup.py sdist --formats=gztar --dist-dir dist/pre > /dev/null
	@echo Normalizing tarball for consistent hashes...
	@$(gtar) -C dist/pre -zxf dist/pre/nscli-$(version).tar.gz
	@for longfile in $$(find dist/pre/nscli-$(version)); do \
		FILE=$$(echo "$$longfile" | sed 's/dist\/pre\/nscli-$(version)\///'); \
		TIME=$$(git log --pretty=format:%ct -n 1 -- "$$FILE"); \
		if [ ! -n "$$TIME" ]; then \
			TIME=$$(git log --pretty=format:%ct -n 1); \
		fi; \
		TOUCHTIME=$$($(gdate) --date="@$$TIME" +"%Y%m%d%H%M"); \
		touch -t "$$TOUCHTIME" "$$longfile"; \
	done
	@$(gtar) -C dist/pre --sort=name -cf dist/pre/nscli-$(version).tar nscli-$(version)
	@gzip -9 dist/pre/nscli-$(version).tar --no-name -c > $(tarball)
	@echo Created $(tarball)

$(wheel): $(config.db) $(pkg_sources) $(manpages) $(python) | version_check
	SOURCE_DATE_EPOCH=$$(git log --pretty=format:%ct -n 1) $(python) setup.py bdist_wheel

needs_review:  ## Generate a list of files that need review per NSCLI's SDLC policy.
	@-rm -f needs_review
	@$(eval all_files := $(shell git ls-tree --name-only -r HEAD nscli))
	@echo "Evaluating each file's commit history, please wait..."
	@for f in $(all_files); do \
		found=$$(git log --since='1 year ago' --pretty=format:"%ct %s" -- "$$f" | grep 'SDLC Review Passed'); \
		if [ -z "$$found" ]; then \
			git log --pretty=format:"%ct $$f%n" -n 1 -- "$$f" >> needs_review; \
		fi; \
	done
	@sort needs_review > needs_review_tmp
	@mv needs_review_tmp needs_review
