Next: , Up: Build Tools   [Index]


A.1 Makefile :dependencies:env_vars:perl:


###############################################################################
### USER-DEPENDENT VARIABLES
### USE ENVIRONMENT VARIABLES WHENEVER POSSIBLE

# NOTE: All environment variables need to be exported PRIOR to starting the
# Emacs server as EDITOR in your shell startup files; otherwise, they will not
# be available to Emacs.
# When I moved from using Bash to Zsh, I inadvertently changed the order of
# import, and started the Emacs server before importing, and caused a horrible
# bug which caused the program to work on one computer but fail on another.

# The absolute path to this Template file
TEMPLATE := $(SYNC_ORG_TEMPLATE)


### TOOLS & RESOURCES
# tools is a directory holding tangled scripts, such as cmprpl
# resources is a directory holding static resources for the project
# images is a directory holding jpg and png image files
RESOURCES := resources
TOOLS            := $(RESOURCES)/tools
IMAGES    := $(RESOURCES)/images
CMPRPL    := $(TOOLS)/cmprpl

# Use emacsclient as $EDITOR; make sure it is set in a shell startup file and
# the server has been started.
EMACS          := $(EMACS)
EDITOR  := $(EDITOR)

# User’s personal GitHub token for authentication to GitHub
# DO NOT HARD-CODE THIS VALUE
GITHUB_TOKEN := $(GITHUB_TOKEN)

# The AWS Command Line Interface (AWS CLI) is an open source tool
# that enables you to interact with AWS services using commands in
# your command-line shell.  It must be present on your system.  Run the 'make'
# command 'install-aws-cli' to install it if you do not have it.  Be sure to
# run 'aws configure' after installing it.  This will place your AWS
# credentials into ~/.aws/credentials.
AWS := aws
S3  := $(AWS) s3
CFD := $(AWS) cloudfront

### END OF USER-DEPENDENT VARIABLES
###############################################################################
### MAKE-GENERATED VARIABLES

### PROJ AND ORG
# ORG is the name of this Org file with extension .org
# PROJ is the project name---the Org file name without extension.

### NOTE: there can be only one Org file in the project directory;
# so far this has not been a problem, but it might be.

PWD  := $(shell pwd)
ORG  := $(shell ls *.org)
PROJ := $(basename $(ORG))

### NOTE: S is needed only for the Template file because of the way it is nested
# one level deep in the Templates GitHub repo, which uses the plural form
# of Templates, whereas this file uses the singular form, Template.  So when
# the homepage link is updated, the curl command must be told to use the plural
# form.         This is obviously a hack only for my own use and can be removed once
# I clean up this anomaly.

ifeq ($(PROJ),$(basename $(notdir $(TEMPLATE))))
S := s
endif

# The AWS S3 bucket to use to store the html source file; it is found at the
# key #+bucket towards the beginning of the file and should include the appropriate
# suffix (.com, .net, .org, etc)
BUCKET       := $(shell $(EDITOR) --eval \
               '(with-current-buffer (find-file-noselect "$(ORG)") \
                  (save-excursion \
                    (goto-char (point-min)) \
                    (re-search-forward "^\#[+]bucket:\\(.*\\)$$" nil t) \
                    (match-string-no-properties 1)))')
S3_BUCKET    := s3://$(BUCKET)

# Buckets set up to serve static web sites from S3 can use either http
# or https protocols; some  http protocols will automatically redirect
# to https;  however, some only use  http. I would like  to accomodate
# both, and  so this code  finds the url's  that are in  my Cloudfront
# account, which presumably will serve https.  If the url is not here,
# then this must be set up to serve http instead.
HTTP_S := $(shell $(CFD) list-distributions | perl -MJSON::PP -e \
        '$$/=""; \
         my @urls = (); \
         my $$json=JSON::PP->new->decode(<STDIN>); \
         for my $$item ( @{$$json->{"DistributionList"}{"Items"}} ) { \
                push @urls, @{$$item->{"Aliases"}{"Items"}}; \
         } \
        my $$found = grep { /'$(BUCKET)'/ } @urls; \
        print "http", ($$found ? "s" : "");')

HTTPS_BUCKET := https://$(BUCKET)

### DIR, SRC
# DIR is the .info name found at '#+texinfo_filename:<DIR>.info' (at
# the bottom of this file in the export configuration settings)
# without its extension, used as the INFO filename and the name of the
# HTML export directory; this code uses the lowercased PROJ name if
# there is no '#+texinfo_filename'.
# SRC is HTML directory based upon the DIR name

#DIR := $(shell $(EDITOR) --eval \
#        '(with-current-buffer (find-file-noselect "$(ORG)") \
#                (save-excursion \
#                (goto-char (point-min)) \
#                (re-search-forward "^\#[+]\\(?:texinfo_filename\\|TEXINFO_FILENAME\\):\\(.*\\).info$$" nil t) \
#                (match-string-no-properties 1)))')

DIR := $(shell sed -E -n "/^\#\+texinfo_filename/s/^.*:(.*)\.info$$/\1/p" $(ORG))
ifeq ($(DIR),$(EMPTY))
        DIR := $(shell echo $(PROJ) | tr "[:upper:]" "[:lower:]")
endif

SRC := $(DIR)/

### VERS: v1.2.34/
# VERS is the version number of this Org document.
# When sync is run after the version number has been updated, then VERS
# picks up the newly-changed value.  VERS used to be staticly imbedded
# when the Makefile was tangled, but it needs to be dynamic for
# development.

# QUERY: should this number be formatted like this, or should it be just the numbers?
# The reason it includes them is the S3PROJ obtains the name from the S3 bucket, and
# it includes them.  But it only includes them because I have made it so.  Not a good
# reason just by itself.  The ending slash is not actually a part of the version, but
# comes from the way the 'aws2 ls' command returns its values.        So VERS should probably
# not include the trailing slash, although it doesn’t hurt anything.

VERS := v$(shell $(EDITOR) --eval \
        '(with-current-buffer (find-file-noselect "$(ORG)") \
                (save-excursion \
                  (goto-char (point-min)) \
                  (re-search-forward "^\#[+]\\(?:macro\\|MACRO\\):version Version \\(\\(?:[[:digit:]]+[.]?\\)\\{3\\}\\)") \
                  (match-string-no-properties 1)))')/

### AWS
# PROJ_LIST contains the list of projects currently uploaded to
# the S3 bucket; each item contains the name of the project and its
# current version.

# Created function using elisp instead of the shell.
# This variable contains an elisp list of strings of the form '("proj1-v1.2.3/" "proj2-v4.5.6/" ...)'
# However, when it prints to the shell, the quotes are lost.
# Need to make sure elisp's variable 'exec-path contains the proper $PATH instead of adding to 'exec-path.

PROJ_LIST := $(shell $(EDITOR) --eval \
        "(progn \
                (require (quote seq)) (add-to-list (quote exec-path) (quote \"/usr/local/bin\")) \
                (seq-map (lambda (s) (replace-regexp-in-string \"^\s+PRE \" \"\" s)) \
                        (seq-filter (lambda (s) (string-match-p (regexp-quote \" PRE \") s)) \
                        (process-lines \"$(AWS)\" \"s3\" \"ls\" \"$(S3_BUCKET)\"))))")

### S3PROJ
# The name of the current project as obtained from S3: 'proj-v1.2.34/'
# If there is no current project in the S3 bucket, then assign a value equal to
# the Org project and version instead.  It is set to the project if found, and
# NO if not found, then updated in the ifeq block below.
S3PROJ := $(shell $(EDITOR) --eval \
                '(let ((proj (seq-find (lambda (s) (string-match-p "$(DIR)" s)) (quote $(PROJ_LIST))))) \
                   (or proj (quote NO)))')

### PROJINS3
# is used by make sync; this allows the index.html file to be generated the first
# time the project is synced.  It is set to NO if this project is not currently in an
# S3 bucket, and it is set to YES if it is.
PROJINS3 :=

### S3VERS
# The version of this project currently installed in the S3 bucket: 'v1.2.34/'
# If there is no current version in the S3 bucket, then assign the version from
# this Org file instead.
S3VERS   :=

# Update S3PROJ, S3VERS, and PROJINS3
ifeq ($(S3PROJ), NO)
        S3PROJ := $(DIR)-$(VERS)
        S3VERS := $(VERS)
        PROJINS3 := NO
else
        S3VERS := $(subst $(DIR)-,,$(S3PROJ))
        PROJINS3 := YES
endif

### GITHUB
# USER is the current user's GitHub login name.

# The user name used to be statically embedded into the Makefile
# during tangle, but in an effort to make the Makefile dynamically
# indepedent, dynamic code has replaced the static code.  The code
# that placed the static name in the Makefile was a 'node' script that
# ran in a separate Org process during tangle.        An unfortunate fact of
# 'make' is that 'make' strips the quote marks from the string
# obtained from the 'curl' command when the 'make shell' command
# returns the string.         This makes the string malformed JSON and
# unparsable by most JSON parsers, including 'node’.        However,
# 'perl'’s core module JSON::PP (but not JSON::XS) has facilities to
# parse very malformed JSON strings.        Therefore, this dynamic code
# uses 'perl' and the core module JSON::PP to parse the 'curl' string
# into a 'perl' JSON object which can return the login name.        This
# code should work with any version of 'perl' without having to
# install any modules.

USER        := $(shell \
          curl -sH "Authorization: token $(GITHUB_TOKEN)" https://api.github.com/user \
          | \
          perl -MJSON::PP -e \
              '$$/ = ""; \
               my $$json = JSON::PP->new->loose->allow_barekey->decode(<STDIN>); \
               print $$json->{login};' \
          )
SAVE                := resources

### TEXINFO
TEXI                := $(PROJ).texi
INFO                := $(DIR).info
INFOTN        := $(shell $(EDITOR) --eval "(file-truename \"$(INFO)\")")
PDF                := $(PROJ).pdf
INDEX                := index.html
HTML                := $(DIR)/$(INDEX)
DIR_OLD        := $(DIR)-old

### AWS S3
DST_OLD        := $(S3_BUCKET)/$(S3PROJ)
DST_NEW        := $(S3_BUCKET)/$(DIR)-$(VERS)
EXCL_INCL        := --exclude "*" --include "*.html"
INCL_IMAGES        := --exclude "*" --include "*.jpg" --include "*.png"
GRANTS        := --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
S3SYNC        := $(S3) sync --delete $(EXCL_INCL) $(SRC) $(DST_OLD) $(GRANTS)
S3MOVE        := $(S3) mv --recursive $(DST_OLD) $(DST_NEW) $(GRANTS)
S3COPY        := $(S3) cp $(INDEX) $(S3_BUCKET) $(GRANTS)
S3REMOVE        := $(S3) rm $(S3_BUCKET)/$(S3PROJ) --recursive
S3IMAGESYNC        := $(S3) sync $(INCL_IMAGES) $(IMAGES) $(S3_BUCKET)/$(IMAGES) $(GRANTS)

###############################################################################

default: check texi info html pdf

PHONY: default all check values boot \
          texi info html pdf \
          open-org open-texi open-html open-pdf \
          clean dist-clean wiped-clean \
          help sync update delete-proj \
          install-aws-cli \
          index-html upload-index-html

values: check
          @printf "$${BLUE}Values...$${CLEAR}\n"
          @echo TEMPLATE:        $(TEMPLATE)
          @echo EDITOR:        $(EDITOR)
          @echo USER:                $(USER)
          @echo PWD:                $(PWD)
          @echo ORG:                $(ORG)
          @echo TEXI:                $(TEXI)
          @echo INFO:                $(INFO)
          @ECHO INFOTN:        $(INFOTN)
          @echo BUCKET:        $(BUCKET)
          @echo PROJ:                $(PROJ) $S
          @echo S3_BUCKET:        $(S3_BUCKET)
          @echo HTTP_S:        $(HTTP_S)
          @echo HTTPS_BUCKET:        $(HTTPS_BUCKET)
          @echo VERS:                $(VERS)
          @echo S3PROJ:        $(S3PROJ)
          @echo S3VERS:        $(S3VERS)
          @echo DIR:                $(DIR)
          @echo DIR_OLD:        $(DIR_OLD)
          @echo SRC:                $(SRC)
          @echo DST_OLD:        $(DST_OLD)
          @echo DST_NEW:        $(DST_NEW)
          @echo PROJ_LIST:        "$(PROJ_LIST)"
          @echo PROJINS3:        $(PROJINS3)

check:
          @printf "$${BLUE}Checking dependencies...$${CLEAR}\n"

          @[[ -z $(BUCKET) ]] && \
             { printf "$${RED}$(BUCKET) $${CYAN}must be set.$${CLEAR}\n"; exit 1; } || \
             printf "$${CYAN}BUCKET: $${GREEN}$(BUCKET)$${CLEAR}\n";

          @[[ -z $${GITHUB_TOKEN} ]] && \
             { printf "$${RED}GITHUB_TOKEN $${CYAN}must be set.$${CLEAR}\n"; exit 1; } || \
             printf "$${CYAN}GITHUB_TOKEN: $${GREEN}SET$${CLEAR}\n";

          @[[ (-d ~/.aws) && (-f ~/.aws/credentials) && (-f ~/.aws/config) ]] && \
             printf "$${CYAN}AWS credentials and config: $${GREEN}SET$${CLEAR}\n" || \
             { printf "$${RED}~/.aws 'credentials' and 'config' must be set.$${CLEAR}\n"; exit 1; }

          @[[ "$(shell $(EDITOR) --eval '(member (quote texinfo) org-export-backends)')" = "(texinfo)" ]] && \
                printf "$${CYAN}Texinfo backend: $${GREEN}INSTALLED.$${CLEAR}\n" || \
                { printf "$${YELLOW}Texinfo backend:$${CLEAR} $${RED}NOT INSTALLED; it must be installed.$${CLEAR}\n"; exit 1; }

          @[[ $(shell $(EDITOR) --eval '(symbol-value org-confirm-babel-evaluate)') == "t" ]] && \
                { printf "$${YELLOW}org-confirm-babel-evaluate:$${CLEAR} $${RED}T; set to NIL.$${CLEAR}\n"; exit 1; } || \
                printf "$${CYAN}org-confirm-babel-evaluate: $${GREEN}OFF.$${CLEAR}\n\n"

open-org: $(ORG)
          @$(EDITOR) -n $(ORG)
$(ORG):
          @echo 'THERE IS NO $(ORG) FILE!!!'
          exit 1

texi: $(TEXI)
$(TEXI): $(ORG)
         @echo Making TEXI...
         @$(EDITOR) -u --eval \
                "(with-current-buffer (find-file-noselect \"$(ORG)\" t) \
                        (save-excursion \
                        (org-texinfo-export-to-texinfo)))"
         @echo Done making TEXI.
open-texi: texi
         @$(EDITOR) -n $(TEXI)

info: $(INFO)
$(INFO): $(TEXI)
         @echo Making INFO...
         @makeinfo -o $(INFO) $(TEXI)
         @$(EDITOR) -u -eval \
                "(when (get-buffer \"$(INFO)\") \
                        (with-current-buffer (get-buffer \"$(INFO)\") \
                                (revert-buffer t t t)))"
         @echo Done making INFO.

open-info: info
         @$(EDITOR) -u -eval \
                "(if (get-buffer \"*info*\") \
                        (with-current-buffer (get-buffer \"*info*\") \
                              (when (not (string= \"(symbol-value (quote Info-current-file))\" \"$(INFOTN)\")) \
                                      (info \"$(INFOTN)\")) \
                              (revert-buffer t t t)) \
                    (info \"$(INFOTN)\"))"

html: $(HTML)
$(HTML): $(TEXI)
         @echo Making HTML INFO..
         @makeinfo --html -o $(DIR) $(TEXI)
         @echo Done making HTML.
         $(CMPRPL) $(DIR) $(DIR_OLD)
open-html: html
         @open $(HTML)

# If pdftexi2dvi produces an error, it may still produce a viable PDF;
# therefore, use --tidy.  If it produces an error, try to link the PDF;
# if it does not produce an error, the PDF will be added to the top dir
# and there will be no attempt to link.
pdf:        $(PDF)
$(PDF): $(TEXI)
        @echo Making PDF INFO...
        @-pdftexi2dvi --quiet --build=tidy $(TEXI) || ln -s $(PROJ).t2d/pdf/build/$(PDF) $(PDF)
        @echo Done making PDF.
open-pdf:pdf
         @open $(PDF)

sync:   $(HTML)
        @echo Syncing version $(VERS) onto $(S3VERS)...
        $(S3SYNC)
        $(S3IMAGESYNC)
        @echo Done syncing.
        [[ $(VERS) != $(S3VERS) ]] && { echo Moving...; $(S3MOVE); echo Done moving.;  make homepage; } || :
        [[ $(PROJINS3) = "NO" ]] && make homepage || :

# This is a target-specific variable for updating the “description”
# key on the GitHub repo page with the current version number.  It
# first makes a curl call to the GitHub project repo, finds the
# “description” line, pulls out the description only (leaving the old
# version) and then prints the value with the current version number.
# This value is used by the “homepage:” target in the PATCH call.
# This method is arguably harder to code but faster to run than using
# Perl with the JSON::PP module.

homepage: description = $(shell \
        curl -s \
                -H "Authorization: token $(GITHUB_TOKEN)" \
                https://api.github.com/repos/$(USER)/$(PROJ)$S | \
                (perl -ne 'if (/^\s*\"description\":\s*\"(.*): v(?:(?:[[:digit:]]+[.]?){3})/) {print $$1}'))

### NOTE the use of the S variable at the end of PROJ; this is to handle
# the singular case of the GitHub repo using the plural form, Templates
# whereas the the Template.org file uses the singular form.
homepage: $(ORG) upload-index-html
          @echo Updating homepage...
          @echo DESCRIPTION: $(description)
          @echo VERS: $(VERS)
          @curl -i \
                -H "Authorization: token $(GITHUB_TOKEN)" \
                -H "Content-Type: application/json" \
                -X PATCH \
                -d "{\"homepage\":\"$(HTTPS_BUCKET)/$(DIR)-$(VERS)\",\
                     \"description\":\"$(description): $(VERS)\"}" \
                https://api.github.com/repos/$(USER)/$(PROJ)$S
          @echo Done updating homepage.

delete-proj:
        @echo Deleting project $(PROJ)...
        @curl -i \
                -H "Authorization: token $(GITHUB_TOKEN)" \
                -H "Accept: application/vnd.github.v3+json" \
                -X DELETE \
                https://api.github.com/repos/$(USER)/$(PROJ)$S
        @$(S3REMOVE)
        @make dist-clean
        @make upload-index-html
        @$(EDITOR) -u --eval "(kill-buffer \"$(ORG)\")"
        @rm -rf "../$(PROJ)"
        @echo Done deleting project.

index-html: $(INDEX)
$(INDEX): $(ORG)
        @echo making index.html...
        $(EDITOR) --eval \
        "(with-current-buffer (find-file-noselect \"$(ORG)\") \
                (save-excursion \
                  (org-link-search \"#project-index-title\") \
                  (org-export-to-file (quote html) \"index.html\" nil t)))"
        @echo Done making index.html.

upload-index-html: $(INDEX)
         @echo Uploading index.html...
         $(S3COPY)
         @echo Done uploading index.html

install-aws-cli:
          curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" && \
          sudo installer -pkg AWSCLIV2.pkg -target / && \
          which aws && aws --version
          rm -rf AWSCLIV2.pkg

clean:
        @echo Cleaning...
          -@rm *~ 2>/dev/null
          -@for file in *.??*; \
          do \
                  ext=$${file#$(PROJ).}; \
                  [[ ! $${ext} =~ org|texi|info|pdf|html ]] && rm -rv $${file}; \
          done

dist-clean: clean
        @echo Dist Cleaning...
          @${EDITOR} -u --eval \
            "(kill-buffer \"$(ORG)\")"
          -@rm -rf *.{texi*,info*,html*,pdf*} $(DIR) $(TOOLS)
          -@for dir in *; \
              do \
                  [ -d $$dir -a $$dir != "$(DIR_OLD)" -a $$dir != $(SAVE) ] && \
                  rm -vr $$dir; \
              done

wipe-clean: dist-clean
        @echo Wipe Clean...
          -@rm -rf Makefile Readme.md $(DIR_OLD)
          @git checkout Makefile README.md

git-ready: dist-clean
          git checkout Makefile
          git checkout README.md
          git status

help:
          @echo '"make boot" tangles all of the files in Template'
          @echo '"make default" makes the .texi file, the .info file, \
          the html files, and the .pdf file.'
          @echo

          @echo '"make check" checks for prerequistes'
          @echo '"make values" runs check and prints variable values'
          @echo

          @echo '"make texi" makes the .texi file'
          @echo '"make info" makes the .info file'
          @echo '"make html" makes the html distribution in a subdirectory'
          @echo '"make pdf" makes the .pdf file'
          @echo

          @echo '"make open-org" opens the ORG program using emacsclient for editing'
          @echo '"make open-texi" opens the .texi file using emacsclient for review'
          @echo '"make open-html" opens the distribution index.html file \
          in the default web browser'
          @echo '"make open-pdf" opens the .pdf file'
          @echo

          @echo '"make sync" syncs the html files in the AWS S3 bucket BUCKET; \
          you must have your AWS S3 bucket name in the env var AWS_S3_BUCKET; \
          You must have your AWS credentials installed in ~/.aws/credentials'
          @echo

          @echo '"make install-aws-cli" installs the "aws cli v2" command-line tools'
          @echo 'You also need to run "aws configure" and supply your Access Key and Secret Access Key'
          @echo

          @echo '"make clean" removes the .texi, .info, and backup files ("*~")'
          @echo '"make dist-clean" cleans, removes the html distribution, \
          and removes the build directory'
          @echo '"make wipe-clean" wipes clean the directory, including old directories'
          @echo

          @echo '"make delete-proj" deletes the project from the file system, GitHub and AWS'


Next: Compare Replace, Up: Build Tools   [Index]