# Copyright lowRISC contributors (OpenTitan project).
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

export SHELL  := /bin/bash
.DEFAULT_GOAL := all

LOCK_ROOT_DIR ?= flock --timeout 3600 ${proj_root} --command

all: build run

###############################
## sim build and run targets ##
###############################
build: build_result

pre_build:
	@echo "[make]: pre_build"
	mkdir -p ${build_dir}
ifneq (${pre_build_cmds},)
	# pre_build_cmds are likely changing the in-tree sources. We hence use FLOCK
	# utility to prevent multiple builds that may be running in parallel from
	# stepping on each other. TODO: Enforce the list of pre_build_cmds is
	# identical across all build modes.
	${LOCK_ROOT_DIR} "cd ${build_dir} && ${pre_build_cmds}"
endif

gen_sv_flist: pre_build
	@echo "[make]: gen_sv_flist"
ifneq (${sv_flist_gen_cmd},)
	cd ${build_dir} && ${sv_flist_gen_cmd} ${sv_flist_gen_opts}
endif

do_build: gen_sv_flist
	@echo "[make]: build"
	cd ${sv_flist_gen_dir} && ${build_cmd} ${build_opts}

post_build: do_build
	@echo "[make]: post_build"
ifneq (${post_build_cmds},)
	cd ${build_dir} && ${post_build_cmds}
endif

build_result: post_build
	@echo "[make]: build_result"

run: run_result

pre_run:
	@echo "[make]: pre_run"
	mkdir -p ${run_dir}
ifneq (${pre_run_cmds},)
	cd ${run_dir} && ${pre_run_cmds}
endif

sw_build: pre_run
	@echo "[make]: sw_build"
ifneq (${sw_images},)
	# Loop through the list of sw_images and invoke Bazel on each.
	# `sw_images` is a space-separated list of tests to be built into an image.
	# Optionally, each item in the list can have additional metadata / flags using
	# the delimiter ':'. The format is as follows:
	# <Bazel label>:<index>:<flag1>:<flag2>
	#
	# If one delimiter is detected, then the full string is considered to be the
	# <Bazel label>. If two delimiters are detected, then it must be <Bazel label>
	# followed by <index>. The <flag> is considered optional.
	#
	# After the images are built, we use `bazel cquery ...` to locate the built
	# software artifacts so they can be copied to the test bench run directory.
	# We only copy device SW images, and do not copy host-side artifacts (like
	# opentitantool) that are also dependencies of the Bazel test target that
	# encode the software image targets.
	set -e; \
	for sw_image in ${sw_images}; do \
		if [[ -z $$sw_image ]]; then \
			echo "ERROR: SW image \"$$sw_image\" is malformed."; \
			echo "Expected format: <Bazel label>:<index>:<optional-flags>."; \
			exit 1; \
		fi; \
		prebuilt_path=`echo $$sw_image | cut -d: -f 1`; \
		bazel_target=`echo $$sw_image | cut -d: -f 2`; \
		index=`echo $$sw_image | cut -d: -f 3`; \
		flags=(`echo $$sw_image | cut -d: -f 4- --output-delimiter " "`); \
		bazel_label="`echo $$sw_image | cut -d: -f 1-2`"; \
		if [[ $${index} != 4 && $${index} != 5 ]]; then \
			if [[ $${flags[@]} =~ "silicon_creator" ]]; then \
				bazel_label="$${bazel_label}_silicon_creator"; \
			else \
				bazel_label="$${bazel_label}_$${sw_build_device}"; \
			fi; \
			bazel_cquery="labels(data, $${bazel_label}) union labels(srcs, $${bazel_label})"; \
		else \
			bazel_cquery="$${bazel_label}"; \
		fi; \
		cd ${proj_root}; \
		if [[ $${flags[@]} =~ "prebuilt" ]]; then \
			echo "SW image \"$$bazel_label\" is prebuilt - copying sources."; \
			cp ${proj_root}/$${prebuilt_path} $${run_dir}/`basename $${prebuilt_path}`; \
		else \
			echo "Building SW image \"$${bazel_label}\"."; \
			bazel_airgapped_opts=""; \
			bazel_opts="${sw_build_opts} --define DISABLE_VERILATOR_BUILD=true"; \
			if [[ -n $${BAZEL_OTP_DATA_PERM_FLAG} ]]; then \
				bazel_opts+=" --//util/design/data:data_perm=$${BAZEL_OTP_DATA_PERM_FLAG}"; \
			fi; \
			if [[ $${OT_AIRGAPPED} != true ]]; then \
				echo "Building \"$${bazel_label}\" on network connected machine."; \
				bazel_cmd="./bazelisk.sh"; \
			else \
				echo "Building \"$${bazel_label}\" on air-gapped machine."; \
				bazel_airgapped_opts+=" --define SPECIFY_BINDGEN_LIBSTDCXX=true"; \
				bazel_airgapped_opts+=" --distdir=$${BAZEL_DISTDIR}"; \
				bazel_airgapped_opts+=" --repository_cache=$${BAZEL_CACHE}"; \
				bazel_cmd="bazel"; \
			fi; \
			echo "Building with command: $${bazel_cmd} build $${bazel_opts} $${bazel_label}"; \
			$${bazel_cmd} build $${bazel_airgapped_opts} $${bazel_opts} $${bazel_label}; \
			kind=$$($${bazel_cmd} cquery $${bazel_airgapped_opts} \
				$${bazel_label} \
				--ui_event_filters=-info \
				--noshow_progress \
				--output=label_kind | cut -f1 -d' '); \
			if [[ $${kind} == "opentitan_test" ]]; then \
				for artifact in $$($${bazel_cmd} cquery $${bazel_airgapped_opts} $${bazel_opts} \
					$${bazel_label} \
					--ui_event_filters=-info \
					--noshow_progress \
					--output=starlark \
					`# An opentitan_test rule has all of its needed files in its runfiles.` \
					--starlark:expr='"\n".join([f.path for f in target.data_runfiles.files.to_list()])'); do \
						cp -f $${artifact} $${run_dir}/$$(basename $${artifact}); \
						if [[ $$artifact == *.bin && \
							-f "$$(echo $${artifact} | cut -d. -f 1).elf" ]]; then \
							cp -f "$$(echo $${artifact} | cut -d. -f 1).elf" \
								$${run_dir}/$$(basename -s .bin $${artifact}).elf; \
						fi; \
				done; \
			elif [[ $${kind} == "alias" || $${kind} == "opentitan_binary" ]]; then \
				for artifact in $$($${bazel_cmd} cquery $${bazel_airgapped_opts} $${bazel_opts} \
					$${bazel_label} \
					--ui_event_filters=-info \
					--noshow_progress \
					--output=starlark \
					`# An opentitan_binary rule has all of its needed files in its runfiles.` \
					--starlark:expr='"\n".join([f.path for f in target.files.to_list()])'); do \
						cp -f $${artifact} $${run_dir}/$$(basename $${artifact}); \
						if [[ $$artifact == *.bin && \
							-f "$$(echo $${artifact} | cut -d. -f 1).elf" ]]; then \
							cp -f "$$(echo $${artifact} | cut -d. -f 1).elf" \
								$${run_dir}/$$(basename -s .bin $${artifact}).elf; \
						fi; \
				done; \
			else \
				for dep in $$($${bazel_cmd} cquery $${bazel_airgapped_opts} $${bazel_opts} \
					$${bazel_cquery} \
					--ui_event_filters=-info \
					--noshow_progress \
					--output=starlark \
					`# Bazel 6 cquery outputs repository targets in canonical format (@//blabla) whereas bazel 5 does not, ` \
					`# so we use a custom starlark printer to remove in leading @ when needed.` \
					--starlark:expr='str(target.label)[1:] if str(target.label).startswith("@//") else target.label'); do \
					if [[ $$dep == //hw/top_*/ip_autogen/otp_ctrl/data* ]] || \
					  ([[ $$dep != //hw* ]] && [[ $$dep != //util* ]] && [[ $$dep != //sw/host* ]]); then \
						for artifact in $$($${bazel_cmd} cquery $${bazel_airgapped_opts} $${bazel_opts} $${dep} \
							--ui_event_filters=-info \
							--noshow_progress \
							--output=starlark \
							--starlark:expr="\"\\n\".join([f.path for f in target.files.to_list()])"); do \
							cp -f $${artifact} $${run_dir}/$$(basename $${artifact}); \
							if [[ $$artifact == *.bin && \
								-f "$$(echo $${artifact} | cut -d. -f 1).elf" ]]; then \
								cp -f "$$(echo $${artifact} | cut -d. -f 1).elf" \
									$${run_dir}/$$(basename -s .bin $${artifact}).elf; \
							fi; \
						done; \
					fi; \
				done; \
			fi; \
		fi; \
	done;
endif

simulate: sw_build
	@echo "[make]: simulate"
	cd ${run_dir} && ${run_cmd} ${run_opts}

post_run: simulate
	@echo "[make]: post_run"
ifneq (${post_run_cmds},)
	cd ${run_dir} && ${post_run_cmds}
endif

run_result: post_run
	@echo "[make]: run_result"

#######################
## Load waves target ##
#######################
debug_waves:
	${debug_waves_cmd} ${debug_waves_opts}

############################
## coverage rated targets ##
############################
cov_unr_build: gen_sv_flist
	@echo "[make]: cov_unr_build"
	cd ${sv_flist_gen_dir} && ${cov_unr_build_cmd} ${cov_unr_build_opts}

cov_unr_vcs: cov_unr_build
	@echo "[make]: cov_unr"
	cd ${sv_flist_gen_dir} && ${cov_unr_run_cmd} ${cov_unr_run_opts}

cov_unr_xcelium:
	@echo "[make]: cov_unr"
	mkdir -p ${cov_unr_dir}
	cd ${cov_unr_dir} && ${cov_unr_run_cmd} ${cov_unr_run_opts}

cov_unr_merge:
	cd ${cov_unr_dir} && ${job_prefix} ${cov_merge_cmd} -init ${cov_unr_dir}/jgproject/sessionLogs/session_0/unr_imc_coverage_merge.cmd

ifeq (${SIMULATOR}, xcelium)
  cov_unr: cov_unr_xcelium cov_unr_merge
else
  cov_unr: cov_unr_vcs
endif

# Merge coverage if there are multiple builds.
cov_merge:
	@echo "[make]: cov_merge"
	${job_prefix} ${cov_merge_cmd} ${cov_merge_opts}

# Generate coverage reports.
cov_report:
	@echo "[make]: cov_report"
	${cov_report_cmd} ${cov_report_opts}

# Open coverage tool to review and create report or exclusion file.
cov_analyze:
	@echo "[make]: cov_analyze"
	${cov_analyze_cmd} ${cov_analyze_opts}

.PHONY: build \
	pre_build \
	gen_sv_flist \
	do_build \
	post_build \
	build_result \
	run \
	pre_run \
	sw_build \
	simulate \
	post_run \
	run_result \
	debug_waves \
	cov_merge \
	cov_analyze \
	cov_report
