# LOCAL MACHINE # This is a bunch of stuff that idk what even is this 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 # These sections set variables for make to track timestamps for the purposes of rebuilding files # PYTHON ENVIRONMENT PYTHONHASHSEED = 10 VIRTUAL_ENV ?= $(mkfile_dir)/.venv root_dir = $(shell pwd) python = $(VIRTUAL_ENV)/bin/python3 sphinx = $(python) -m sphinx pip = $(python) -m pip 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 = $(nscli_files) $(manpages) $(completions) pdm_list = pdm_list constraints.txt = constraints.txt # 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 brew_repository = $(shell brew --repository) brew_arch = $(shell brew unbottled nscli | grep "bottle status" | cut -d ':' -f2 | cut -d ' ' -f1) bottle = dist/nscli-$(version).$(brew_arch).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. debian11 = dist/debian/nscli_$(version)-1debian1~11_amd64.deb ubuntu2004 = dist/debian/nscli_$(version)-1ubuntu1~20.04_amd64.deb ubuntu2204 = dist/debian/nscli_$(version)-1ubuntu1~22.04_amd64.deb debian_releases = $(debian11) $(ubuntu2004) $(ubuntu2204) 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 $(python) -m tox # .PHONY: dist # dist: $(tarball) $(wheel) ## Create a new distribution .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 #release: release_env pdm_list build_env dist webpage debian brew_formula bottle # make the tar.gz and whl .PHONY: dist dist: $(config.db) $(pkg_sources) $(manpages) $(python) | version_check $(python) -m build .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 brew_formula: $(brew_formula) ## Update the homebrew installer. # upload the formula to homebrew-rs (gts/rs tap) .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 # something happened with editable installs and the location returned from importlib.metadata.distribution("nscli") # we create a symlink to trick python that has nscli installed as an editable package where to find the files .PHONY: site_link site_link: ln -s $(root_dir)/nscli $(shell $(python) -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])') || echo "Link already exists" .PHONY: devenv devenv: ## Set up your nscli dev environment. Creates a virtual environment. # ensure VENV is not active if [[ ! -z $$VIRTUAL_ENV ]]; then echo ; echo "!! ERROR !! Deactivate VENV before running 'make devenv'"; echo; exit 1; fi # install dev packages from lockfile and don't update lockfile -pdm venv remove in-project -y pdm sync -G dev make $(config.db) .PHONY: build_env build_env: ## Set up your nscli dev environment. Creates a virtual environment. # ensure VENV is not active if [[ ! -z $$VIRTUAL_ENV ]]; then echo ; echo "!! ERROR !! Deactivate VENV before running 'make build_env'"; echo; exit 1; fi -pdm venv remove in-project -y # install build packages from lockfile and don't update lockfile pdm sync -G build make $(config.db) .PHONY: release_env release_env: ## Set up your nscli dev environment. Creates a virtual environment. # ensure VENV is not active if [[ ! -z $$VIRTUAL_ENV ]]; then echo ; echo "!! ERROR !! Deactivate VENV before running 'make release_env'"; echo; exit 1; fi -pdm venv remove in-project -y # install prod packages from lockfile and don't update lockfile pdm sync --prod 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' ';'` $(python) scripts/plugin_checker.py $(flake8) --config=$(mkfile_dir)/pyproject.toml .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_TOKEN environment variable is exported..." @test -n "$(API_TOKEN)" @curl -i -H 'Authorization: Bearer $(API_TOKEN)' -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 --progress=plain --pull -t nscli_base:latest . test -f docker/credentials.json echo 'FROM nscli_base:latest' | docker build --progress=plain -t nscli_loaded:latest . -f - # this creates a requirements.txt with "prod" packages from the .egg-info, the flag to use artifactory, and a forced install of importlib_metadata 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 $(constraints.txt): $(python) scripts/generate_constraints.py > constraints.txt $(pdm_list): # creating initial pdm_list for brew_formula use @pdm list | awk '!/nscli/ {if (NR>3) {print $$2}}' > pdm_list .PHONY: pdm_lock pdm_lock: # update lockfile pdm lock --strategy static_urls version: nscli/__init__.py @printf "$(version)\n" > version && chmod 644 version # creates a formula using scripts/make_formula.py and existing compiled data/hashes from the lockfile $(brew_formula): $(python) # creating brew formula # This recipe can only run on a Mac @test "Darwin" = "$(uname)" mkdir -p "$(dir $(brew_formula))" $(python) scripts/make_formula.py scripts/formula_template.jinja2 $(mkfile_dir)/pdm_list $(brew_formula) $(version) $(shell shasum -a 256 $(tarball) | awk '{print $$1}') # to support some changes with homebrew, we must build the bottle using the path of the nscli formula in the gts/rs tap $(bottle): $(python) | $(brew_formula) # creating brew bottle # 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 # Uninstalling nscli from brew @-brew uninstall --force nscli # brew install ./Formula/nscli.rb --build-bottle --formula @brew install ./Formula/nscli.rb --build-bottle --formula # mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak @mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak # cp ./Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/ @cp ./Formula/nscli.rb $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/ # brew bottle $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb --no-rebuild --json --root-url 'https://artifacts.rackspace.net/artifactory/nsi-bottles-local' @brew bottle $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb --no-rebuild --json --root-url 'https://artifacts.rackspace.net/artifactory/nsi-bottles-local' @#$(python) scripts/fixup_bottle.py nscli--$(version).$(brew_arch).bottle.json --root_url 'https://artifacts.rackspace.net/artifactory/nsi-bottles-local' # brew bottle ./nscli--$(version).$(brew_arch).bottle.json --merge --write --no-commit @brew bottle ./nscli--$(version).$(brew_arch).bottle.json --merge --write --no-commit # mv -f $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb ./Formula/nscli.rb @mv -f $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb ./Formula/nscli.rb # mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb @mv $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb.bak $(brew_repository)/Library/Taps/gts/homebrew-rs/Formula/nscli.rb # mv nscli--$(version).$(brew_arch).bottle.tar.gz $(bottle) @mv nscli--$(version).$(brew_arch).bottle.tar.gz $(bottle) # cat nscli--$(version).$(brew_arch).bottle.json | $(python) -m json.tool @cat nscli--$(version).$(brew_arch).bottle.json | $(python) -m json.tool # rm nscli--$(version).$(brew_arch).bottle.json @rm nscli--$(version).$(brew_arch).bottle.json # sleep 2 && touch $(bottle) @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 --no-cache --platform linux/amd64 --progress=plain --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 --platform linux/amd64 --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_TOKEN)"; then \ echo "The API_TOKEN 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 "Authorization: Bearer $(API_TOKEN)" -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 build_env $(webpage): $(regionlist) $(docs/src) $(python) $(constraints.txt) PYTHONHASHSEED=$(PYTHONHASHSEED) $(sphinx) -b html docs/src docs/gh-pages -D version="$(version)" -D release="$(version)" cp constraints.txt docs/gh-pages/ .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) site_link $(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) 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