# Copyright (c) 2015-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

ORIG_SHELL = $(shell echo "$$SHELL")
SHELL = bash -e -o pipefail -u

ORIG_SHELL_PATH = $(shell printf "%s" "$$PATH")

# Make infer crash a bit more often to detect issues in the way we call infer within this repo, eg,
# using deprecated options.
export INFER_STRICT_MODE=1

include $(ROOT_DIR)/Makefile.autoconf

PLATFORM = $(shell uname)

COPY = cp -f
COPY_DIR = cp -Rf
REMOVE = rm -f
REMOVE_DIR = rm -rf

ABSOLUTE_ROOT_DIR = $(shell cd $(ROOT_DIR) && pwd)

DEPENDENCIES_DIR = $(ABSOLUTE_ROOT_DIR)/dependencies
DOCKER_DIR = $(ABSOLUTE_ROOT_DIR)/docker
EXAMPLES_DIR = $(ABSOLUTE_ROOT_DIR)/examples
INFER_DIR = $(ABSOLUTE_ROOT_DIR)/infer
FCP_DIR = $(ABSOLUTE_ROOT_DIR)/facebook-clang-plugins
M4_DIR = $(ABSOLUTE_ROOT_DIR)/m4
SCRIPT_DIR = $(ABSOLUTE_ROOT_DIR)/scripts

FCP_CLANG_OCAML_DIR = $(FCP_DIR)/clang-ocaml

ANNOTATIONS_DIR = $(INFER_DIR)/annotations
BIN_DIR = $(INFER_DIR)/bin
ETC_DIR = $(INFER_DIR)/etc
LIB_DIR = $(INFER_DIR)/lib
MAN_DIR = $(INFER_DIR)/man
MODELS_DIR = $(INFER_DIR)/models
JAVA_BUILTINS_DIR = $(MODELS_DIR)/java/builtins
JAVA_MODELS_DIR = $(MODELS_DIR)/java/src
SRC_DIR = $(INFER_DIR)/src

BUILD_DIR = $(SRC_DIR)/_build

JAVA_LIB_DIR = $(LIB_DIR)/java
SPECS_LIB_DIR = $(LIB_DIR)/specs

PYTHON_DIR = $(LIB_DIR)/python
PYTHON_LIB_DIR = $(PYTHON_DIR)/inferlib
CAPTURE_LIB_DIR = $(PYTHON_LIB_DIR)/capture

INFER_BIN = $(BIN_DIR)/infer
INFER_COMMANDS = \
  infer-analyze \
  infer-capture \
  infer-compile \
  infer-explore \
  infer-report \
  infer-reportdiff \
  infer-run \

INFERTOP_BIN = $(BIN_DIR)/infertop.bc
INFERUNIT_BIN = $(BIN_DIR)/InferUnit

INFER_CREATE_TRACEVIEW_LINKS = InferCreateTraceViewLinks
INFER_CREATE_TRACEVIEW_LINKS_BIN = $(BIN_DIR)/$(INFER_CREATE_TRACEVIEW_LINKS)

INFER_COMMAND_MANUALS = $(INFER_COMMANDS:%=$(MAN_DIR)/man1/%.1)
INFER_MANUAL = $(MAN_DIR)/man1/infer.1
INFER_MANUALS = $(INFER_COMMAND_MANUALS) $(INFER_MANUAL)
INFER_MANUALS_GZIPPED = $(INFER_MANUALS:=.gz)

ifeq ($(BUILD_JAVA_ANALYZERS),yes)
JAVA_HOME=$(USER_JAVA_HOME)
endif

ANDROID_JAR = $(LIB_DIR)/java/android/android-23.jar
GUAVA_JAR = $(DEPENDENCIES_DIR)/java/guava/guava-23.0.jar
INFER_ANNOTATIONS_JAR = $(ANNOTATIONS_DIR)/annotations.jar
JACKSON_JAR = $(DEPENDENCIES_DIR)/java/jackson/jackson-2.2.3.jar
JSR_305_JAR = $(DEPENDENCIES_DIR)/java/jsr-305/jsr305.jar

JAVA_MODELS_JAR = $(LIB_DIR)/java/models.jar

JAVA_DEPS_NO_MODELS = \
  $(addprefix $(PYTHON_LIB_DIR)/, \
      analyze.py bucklib.py config.py issues.py jwlib.py source.py utils.py) \
  $(addprefix $(CAPTURE_LIB_DIR)/, util.py) \
  $(ANDROID_JAR) $(GUAVA_JAR) $(JACKSON_JAR) $(JSR_305_JAR) $(INFER_ANNOTATIONS_JAR) \
  $(INFER_BIN)

JAVA_DEPS = $(JAVA_DEPS_NO_MODELS) $(JAVA_MODELS_JAR)

# marker to keep track of when clang models have been rebuilt
MODELS_RESULTS_FILE = $(SPECS_LIB_DIR)/clang_models

CLANG_DEPS_NO_MODELS = \
  $(addprefix $(PYTHON_LIB_DIR)/, \
      analyze.py config.py issues.py source.py utils.py) \
  $(addprefix $(CAPTURE_LIB_DIR)/, util.py) \
  $(INFER_BIN)

CLANG_DEPS = $(CLANG_DEPS_NO_MODELS) $(MODELS_RESULTS_FILE) \
  $(shell find $(MODELS_DIR)/cpp/include -type f)

define copy_or_same_file
  $(COPY) "$(1)" "$(2)" || diff -q "$(1)" "$(2)"
endef

INTERACTIVE = $(shell [ -t 0 ] && echo 1)
SILENT = $(findstring s,$(filter-out -%,$(firstword $(MAKEFLAGS))))

ifeq (1,$(INTERACTIVE))
TERM_ERROR = $(shell printf '\e[31;1m')
TERM_INFO = $(shell printf '\e[;1m')
TERM_SUCCESS = $(shell printf '\e[;2m')
TERM_RESET = $(shell printf '\e[0m')
endif

ifneq ($(VERBOSE),1)
# quiet
QUIET = @
endif

MAKE := $(MAKE) INTERACTIVE=$(INTERACTIVE)

# 99999 PIDs ought to be enough for anybody, but check if pid_max can be found just in case
MAX_PID_SIZE = \
  $(shell PID_MAX=$$(cat /proc/sys/kernel/pid_max 2>/dev/null); echo $${\#PID_MAX} || echo 5)

# Arguments:
#   $(1) is a string describing the command
#   $(2) is the command to run
#
ifeq ($(VERBOSE),1)
define silent_on_success
  $(2)
endef
else
# Run and time the command and redirect its stdout and stderr to files. Display info about the
# command only in case of error. Try to be as helpful as possible in the error case.
#
# The PID of the process is used in the names of the output files, and as a prefix for each error
# message so that it's possible to piece error messages together even when they are interleaved with
# other messages from concurrent `make` processes.
#
# Detect if we are already wrapped inside a silent_on_success call and try not to clutter the output
# too much in that case.
define silent_on_success
  if [ -n "$${INSIDE_SILENT_ON_SUCCESS-}" ]; then \
    echo '*** inner $(1)'; \
    echo '*** inner command: $(2)'; \
    echo '*** inner CWD: $(CURDIR)'; \
    ($(2)); \
    exit $$?; \
  fi; \
  export INSIDE_SILENT_ON_SUCCESS=1; \
  HASH="$$$$"; \
  UNIX_START_DATE=$$(date +"%s"); \
  HUMAN_START_DATE=$$(date +"%H:%M:%S"); \
  if [ -z $(SILENT) ]; then \
    printf '[%s][%$(MAX_PID_SIZE)s] $(TERM_INFO)%s...$(TERM_RESET)\n' \
      "$$HUMAN_START_DATE" "$$HASH" "$(1)"; \
  fi; \
  $(MKDIR_P) $(ABSOLUTE_ROOT_DIR)/_build_logs; \
  ERRCODE=0; \
  ($(2)) 1>$(ABSOLUTE_ROOT_DIR)/_build_logs/cmd-$$HASH.out \
         2>$(ABSOLUTE_ROOT_DIR)/_build_logs/cmd-$$HASH.err \
  || ERRCODE=$$?; \
  if [ $$ERRCODE != 0 ]; then \
    echo "$(TERM_ERROR)[*ERROR**][$$HASH] *** ERROR '$(1)'$(TERM_RESET)" >&2; \
    echo "$(TERM_ERROR)[*ERROR**][$$HASH] *** command: '$(2)'$(TERM_RESET)" >&2; \
    echo "$(TERM_ERROR)[*ERROR**][$$HASH] *** CWD: '$(CURDIR)'$(TERM_RESET)" >&2; \
    echo "$(TERM_ERROR)[*ERROR**][$$HASH] *** stdout:$(TERM_RESET)" >&2; \
    sed -e "s/^\(.*\)$$/$(TERM_ERROR)[*ERROR**][$$HASH]$(TERM_RESET) \1/" \
      $(ABSOLUTE_ROOT_DIR)/_build_logs/cmd-$$HASH.out; >&2; \
    echo "$(TERM_ERROR)[*ERROR**][$$HASH] *** stderr:$(TERM_RESET)" >&2; \
    sed -e "s/^\(.*\)$$/$(TERM_ERROR)[*ERROR**][$$HASH]$(TERM_RESET) \1/" \
      $(ABSOLUTE_ROOT_DIR)/_build_logs/cmd-$$HASH.err; >&2; \
    exit $$ERRCODE; \
  elif [ -z $(SILENT) ]; then \
    UNIX_END_DATE=$$(date +"%s"); \
    printf '[%7ss][%$(MAX_PID_SIZE)s] $(TERM_SUCCESS)SUCCESS %s$(TERM_RESET)\n' \
      "$$(($$UNIX_END_DATE - $$UNIX_START_DATE))" "$$HASH" "$(1)"; \
  fi
endef
endif

# print any variable for Makefile debugging
print-%:
	$(QUIET)echo '$*=$($*)'
