# This Makefile will be invoked by the python build system (e.g. via 'pip install'),
# but you can also build individual targets by invoking 'make' directly.
#
# Overrideable variables (either as env variables, or in a gitignored-file 'config.mk')
# NOTE: Conda users normally need none of these (found via $CONDA_PREFIX)
#
#   NVCC_OPT       e.g. '-O0 -g' (default is '-O3')
#   EXTRA_INCDIRS  e.g. '-I/usr/local/include -I/home/dstn/nvidia/mathdx/26.03/include'
#   NVCC           e.g. '/usr/local/bin/nvcc' (the nvcc program; its flags are NVCCFLAGS)
#   NVCCFLAGS      e.g. '-std=c++20 ...' (all of nvcc's flags; usually tweak NVCC_OPT instead)
#   NVCC_ARCH      e.g. '-gencode arch=compute_90,code=sm_90' (default targets sm_80/86/89)
#   NVCC_DEPFLAGS  e.g. '-MMD -MP' (the default)
#   PYTHON         e.g. 'python3.11' (default is 'python3')
#
# A note on how to add new source files:
#
#   include/pirate/.*hpp            -> add to HFILES below (only needed for pip/pypi)
#   src_lib/*.cu                    -> add to LIB_SRCFILES below
#   pirate_frb/*.py                 -> add to PYFILES below (only needed for pip/pypi)
#   pirate_frb/cuda_generator/*.py  -> add to CUDAGEN_PYFILES below
#
# Autogenerated kernels (src_lib/autogenerated_kernels/*.cu) are not listed in the Makefile.
# Instead, see 'makefile_helper.py', which procedurally generates all autogenerated kernels
# (by filename), and passes the list of filenames to the Makefile.

# Disable built-in rules and variables (must be first).
MAKEFLAGS += --no-builtin-rules
MAKEFLAGS += --no-builtin-variables

# Default target 'all' must be first target in Makefile.
# The 'lib' target builds the C++ library lib/libpirate.so, and the python extension pirate_frb/pirate_pybind11...so.
# The 'build_wheel' and 'build_sdist' targets are invoked by 'pip' (or 'make all').
all: lib build_wheel build_sdist


.PHONY: all lib build_wheel build_sdist clean docs docs-clean docs-serve tex pybind11 autogenerated_kernels gddk gpfk pfom pfwr cdd2 help grpc

# Help target
help:
	@echo "Pirate Makefile - Available targets:"
	@echo ""
	@echo "Main build targets:"
	@echo "  all              - Build everything (default): lib + wheel + sdist"
	@echo "  lib              - Build C++ library (lib/libpirate.so) and Python extension"
	@echo "  build_wheel      - Build wheel files (called by pip)"
	@echo "  build_sdist      - Build source distribution (called by pip)"
	@echo "  clean            - Remove all build artifacts"
	@echo ""
	@echo "Documentation targets (note that sphinx documentation is currently a placeholder)"
	@echo "  docs             - Build sphinx documentation (HTML); also builds 'tex'"
	@echo "  tex              - Compile notes/*.tex to PDF with pdflatex"
	@echo "  docs-clean       - Remove generated documentation"
	@echo "  docs-serve       - Build and serve docs at http://localhost:8000"
	@echo ""
	@echo "Development targets:"
	@echo "  pybind11         - Compile all pybind11 object files (but not final .so)"
	@echo "  grpc             - Generate C++ and Python files from .proto"
	@echo ""
	@echo "Autogenerated kernel targets:"
	@echo "  autogenerated_kernels  - Generate all .cu files for autogenerated kernels"
	@echo "  gddk             - Build dedispersion kernels (dd_fp*.o)"
	@echo "  gpfk             - Build peak-finding kernels (pf_fp*.o)"
	@echo "  pfom             - Build peak-finding output microkernels (pf_output_fp*.o)"
	@echo "  pfwr             - Build peak-finding weight reader microkernels (pf_weight_reader*.o)"
	@echo "  cdd2             - Build coalesced dedisperser + peak-finder kernels (cdd2_fp*.o)"


# Documentation targets

# LaTeX notes: notes/*.tex -> notes/*.pdf via pdflatex. Each .tex is assumed to
# be a single self-contained source file (no \input, no .bib), so there is no
# bibtex pass; we just re-run pdflatex until cross-references (e.g. equation
# \ref's) stop changing, capped at 5 passes as a safety net. The temp files
# (*.aux/*.log/*.out/*.toc) and the generated *.pdf are gitignored and removed
# by 'make clean'.
#
# The .tex files to compile are listed EXPLICITLY in TEX_SRCS (not globbed), so a
# throwaway file like notes/scratch.tex is never compiled by accident. This
# mirrors notes/*.md, which are only rendered if listed in the docs toctree
# (docs/source/index.md). The 'tex' recipe warns about any stray .tex on disk
# that isn't listed here.
TEX_SRCS := notes/tree_dedispersion.tex
TEX_PDFS := $(TEX_SRCS:.tex=.pdf)

# notes/*.tex present on disk but not listed in TEX_SRCS (e.g. scratch files).
TEX_STRAY := $(filter-out $(TEX_SRCS),$(wildcard notes/*.tex))

tex: $(TEX_PDFS)
	@$(if $(TEX_STRAY),echo "WARNING: ignoring stray notes/*.tex not listed in TEX_SRCS: $(TEX_STRAY)" >&2,true)

notes/%.pdf: notes/%.tex
	@echo "pdflatex $<"
	@cd notes && for i in 1 2 3 4 5; do \
		pdflatex -interaction=nonstopmode -halt-on-error $*.tex >/dev/null 2>&1 || \
			{ echo "  ERROR: pdflatex failed on $*.tex (see notes/$*.log):"; tail -n 20 $*.log; exit 1; }; \
		grep -q 'Rerun' $*.log || break; \
	done

# Sphinx documentation. Depends on:
#   - 'lib': conf.py imports pirate_frb (to generate the CLI tables), which needs
#     the compiled pybind11 extension + libpirate.so, and also reads the generated
#     configs/example_*.yml -- all produced by 'lib'. (Use 'make -j 32 docs': the
#     C++/CUDA compile dominates the wall-clock.)
#   - 'tex': so the notes PDFs exist for the {download} links.
# We invoke Sphinx as '$(PYTHON) -m sphinx' (not the 'sphinx-build' console
# script) so the docs build uses the SAME interpreter as the rest of this
# Makefile -- important when ksgpu / pirate_frb live in a venv but the PATH
# 'sphinx-build' resolves to a different interpreter that cannot import them.
SPHINX_SOURCEDIR := docs/source
SPHINX_BUILDDIR  := docs/build
SPHINXOPTS       ?=

docs: lib tex
	$(PYTHON) -m sphinx -M html "$(SPHINX_SOURCEDIR)" "$(SPHINX_BUILDDIR)" $(SPHINXOPTS)

docs-clean:
	rm -rf docs/build docs/source/notes docs/source/configs docs/source/grpc docs/source/_autosummary docs/source/_cli_generated.md docs/source/_grpc_generated.md docs/source/_static/*.pdf

docs-serve: docs
	@echo "Starting documentation server at http://localhost:8000"
	@cd $(SPHINX_BUILDDIR)/html && $(PYTHON) -m http.server


####################################################################################################
#
# Variables encoding configuration: PYTHON, NVCC, NVCCFLAGS, NVCC_OPT, NVCC_ARCH,
# NVCC_DEPFLAGS, EXTRA_INCDIRS. These can be overridden from the environment or a
# gitignored 'config.mk' (see the overridable-variables list at the top of this file),
# so you normally don't need to edit the defaults below.
#
# FIXME some day I'll replace the env/config.mk mechanism with a proper configure script.

# Optional per-machine overrides (gitignored). A non-conda build can set
# EXTRA_INCDIRS, NVCC_OPT, etc. here instead of editing this file. Included before
# the '?=' defaults below so its assignments take effect.
-include config.mk

PYTHON ?= python3

# NVCC is the nvcc program alone; its flags live in NVCCFLAGS. Keeping them
# separate means you can point NVCC at a different toolkit (NVCC=/path/to/nvcc)
# without retyping the flags, and tweak the optimization level via NVCC_OPT
# (a debug build is just NVCC_OPT='-O0 -g') without retyping the whole command.
# To time nvcc invocations, prepend: time -f "   %e seconds"
NVCC      ?= nvcc
NVCC_OPT  ?= -O3
NVCCFLAGS ?= -std=c++17 -m64 $(NVCC_OPT) -lineinfo --use_fast_math --compiler-options -Wall,-fPIC,-march=x86-64-v3

# If using a conda env, add include, lib, and rpath paths. The rpath is what
# makes libpirate.so self-locating: without it, libgrpc++/libprotobuf/libmkl_rt
# are found at link time (via CONDA_LIBFLAGS) but not at runtime, forcing users
# to prepend $CONDA_PREFIX/lib to LD_LIBRARY_PATH. With CONDA_RPATHFLAGS baked
# into the linker line, the compiled DSO carries a RUNPATH pointing at
# $CONDA_PREFIX/lib and no shell-side shim is needed.
ifneq ($(CONDA_PREFIX),)
CONDA_INCFLAGS = -I$(CONDA_PREFIX)/include
CONDA_LIBFLAGS = -L$(CONDA_PREFIX)/lib
CONDA_RPATHFLAGS = -Xcompiler '"-Wl,-rpath=$(CONDA_PREFIX)/lib"'
endif

# Extra nvcc flags needed to build Makefile dependencies
#   -MMD create dep file, omitting "system" headers
#   -MP add phony target for each header in dep file (makes error reporting less confusing)
# Note: we don't need "-MT $@", since we use in-tree object filenames (x.cu -> x.o).
# Note: we don't need "-MT $*.d", since we use in-tree depfile names (x.cu -> x.d).
NVCC_DEPFLAGS ?= -MMD -MP

# NVIDIA archictecture.
DEFAULT_NVCC_ARCH = -gencode arch=compute_80,code=sm_80
DEFAULT_NVCC_ARCH += -gencode arch=compute_86,code=sm_86
DEFAULT_NVCC_ARCH += -gencode arch=compute_89,code=sm_89
# DEFAULT_ARCH += -gencode arch=compute_90,code=sm_90
NVCC_ARCH ?= $(DEFAULT_NVCC_ARCH)

# Include dirs passed to every nvcc compile. mathdx normally comes from conda
# ($(CONDA_INCFLAGS)); a non-conda build adds hand-installed include dirs (mathdx,
# system asdf, ...) via EXTRA_INCDIRS, which defaults empty -> old conda-only behavior.
EXTRA_INCDIRS ?=
NVCC_CFLAGS = -Iasdf-cxx/include -I$(KSGPU_DIR)/include $(CONDA_INCFLAGS) $(EXTRA_INCDIRS)


####################################################################################################
#
# gRPC code generation.
# The .proto file generates both C++ files (in grpc/) and Python stubs (in pirate_frb/rpc/grpc/).

GRPC_PROTO := grpc/frb_search.proto
GRPC_PROTO += grpc/frb_grouper.proto
GRPC_PROTO += grpc/frb_sifter.proto

# C++ generated files (protobuf message classes + gRPC service stubs)
GRPC_HFILES = $(GRPC_PROTO:%.proto=%.pb.h) 
GRPC_HFILES += $(GRPC_PROTO:%.proto=%.grpc.pb.h) 
GRPC_CCFILES = $(GRPC_PROTO:%.proto=%.pb.cc) 
GRPC_CCFILES += $(GRPC_PROTO:%.proto=%.grpc.pb.cc) 

# Python generated files (protobuf message classes + gRPC service stubs)
GRPC_PYFILES = $(GRPC_PROTO:grpc/%.proto=pirate_frb/rpc/grpc/%_pb2.py)
GRPC_PYFILES += $(GRPC_PROTO:grpc/%.proto=pirate_frb/rpc/grpc/%_pb2_grpc.py)
GRPC_PYFILES += pirate_frb/rpc/grpc/__init__.py

# These .cpp files include generated gRPC headers, so the headers must exist
# before these objects are compiled (matters under 'make -j' on a fresh build).
src_lib/FrbServer.o: $(GRPC_HFILES)
src_lib/FrbGrouper.o: $(GRPC_HFILES)
src_lib/FakeXEngine.o: $(GRPC_HFILES)


####################################################################################################
#
# "Derived" config variables:
#   - PYTHON_INCDIR
#   - NUMPY_INCDIR
#   - PYBIND11_INCDIR
#   - PYEXT_SUFFIX
#   - KSGPU_DIR
#   - AUTOGENERATED_KERNELS
#
# These are autogenerated by makefile_helper.py, and cached in makefile_helper.out.
# PYEXT_SUFFIX is something like .cpython-312-x86_64-linux-gnu.so.


ifneq ($(MAKECMDGOALS),help)
ifneq ($(MAKECMDGOALS),clean)
  include makefile_helper.out
endif
endif

makefile_helper.out: makefile_helper.py Makefile
	$(PYTHON) makefile_helper.py


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


# The main output of the build process is these two libraries.
# Reminder: PYEXT_SUFFIX is something like .cpython-312-x86_64-linux-gnu.so.
PIRATE_LIB := lib/libpirate.so
PIRATE_PYEXT := pirate_frb/pirate_pybind11$(PYEXT_SUFFIX)

# These get compiled into lib/libpirate.so.
# Autogenerated source files (either by pirate_frb/cuda_generator or grpc) aren't listed here.
# Note: .cpp files are pure C++ (or C++ using only CUDA runtime API), compiled by nvcc forwarding to host compiler.
# Note: .cu files contain actual CUDA kernels (__global__ functions) and must be compiled by nvcc's CUDA frontend.

# asdf-cxx source files (Erik Schnetter's ASDF library, compiled directly into libpirate.so)
ASDF_CXX_SRCFILES = \
  asdf-cxx/src/asdf.cxx \
  asdf-cxx/src/byteorder.cxx \
  asdf-cxx/src/config.cxx \
  asdf-cxx/src/datatype.cxx \
  asdf-cxx/src/entry.cxx \
  asdf-cxx/src/io.cxx \
  asdf-cxx/src/ndarray.cxx \
  asdf-cxx/src/reference.cxx \
  asdf-cxx/src/table.cxx

# asdf-cxx headers, shipped in the sdist so the .cxx files can compile from a source build.
# config.hxx is excluded: it is generated at build time from misc/asdf_cxx_config.hxx.
ASDF_CXX_HFILES := $(filter-out asdf-cxx/include/asdf/config.hxx,$(wildcard asdf-cxx/include/asdf/*.hxx))

LIB_SRCFILES = \
  src_lib/AssembledFrame.cpp \
  src_lib/Barrier.cpp \
  src_lib/BumpAllocator.cpp \
  src_lib/CasmBeamformer.cu \
  src_lib/ChimeBeamformer1.cu \
  src_lib/ChimeBeamformer2.cu \
  src_lib/CoalescedDdKernel2.cu \
  src_lib/CudaEventRingbuf.cpp \
  src_lib/CudaStreamPool.cpp \
  src_lib/DedispersionBuffer.cpp \
  src_lib/DedispersionConfig.cpp \
  src_lib/DedispersionKernel.cu \
  src_lib/DedispersionPlan.cpp \
  src_lib/HwtestSender.cpp \
  src_lib/Hwtest.cpp \
  src_lib/FakeXEngine.cpp \
  src_lib/FileWriter.cpp \
  src_lib/FrbServer.cpp \
  src_lib/FrbGrouper.cpp \
  src_lib/FrequencySubbands.cpp \
  src_lib/GpuDedisperser.cpp \
  src_lib/GpuDequantizationKernel.cu \
  src_lib/GpuLaggedDownsamplingKernel.cu \
  src_lib/LaggedDownsamplingKernelParams.cpp \
  src_lib/MegaRingbuf.cpp \
  src_lib/PeakFindingKernel.cu \
  src_lib/PfVariance.cpp \
  src_lib/Receiver.cpp \
  src_lib/ReferenceDedisperser.cpp \
  src_lib/ReferenceLagbuf.cpp \
  src_lib/ReferenceLaggedDownsamplingKernel.cpp \
  src_lib/ReferenceTree.cpp \
  src_lib/ResourceTracker.cpp \
  src_lib/RingbufCopyKernel.cu \
  src_lib/SimulatedFrameFactory.cpp \
  src_lib/TreeGriddingKernel.cu \
  src_lib/YamlFile.cpp \
  src_lib/avx2_utils.cpp \
  src_lib/file_utils.cpp \
  src_lib/network_utils.cpp \
  src_lib/scratch.cu \
  src_lib/SlabAllocator.cpp \
  src_lib/SparseTile.cpp \
  src_lib/simpulse.cpp \
  src_lib/system_utils.cpp \
  src_lib/utils.cpp \
  src_lib/XEngineMetadata.cpp \
  src_lib/loose_ends/cpu_downsample.cpp \
  src_lib/loose_ends/gpu_downsample.cu \
  src_lib/loose_ends/gpu_transpose.cu \
  src_lib/loose_ends/test_avx256_m64_outbuf.cpp \
  src_lib/loose_ends/test_cpu_downsampler.cpp \
  src_lib/loose_ends/test_gpu_downsample.cpp \
  src_lib/loose_ends/test_gpu_reduce2.cu \
  src_lib/loose_ends/test_gpu_transpose.cpp \
  src_lib/loose_ends/time_cpu_downsample.cpp \
  src_lib/loose_ends/time_gpu_downsample.cpp \
  src_lib/loose_ends/time_gpu_transpose.cpp \
  src_lib/autogenerated_kernels/debug.cu

# These get compiled into pirate_frb/pirate_pybind11....so.
PYEXT_SRCFILES = \
  src_pybind11/pirate_pybind11.cpp \
  src_pybind11/pirate_pybind11_avar.cpp \
  src_pybind11/pirate_pybind11_core.cpp \
  src_pybind11/pirate_pybind11_kernels.cpp \
  src_pybind11/pirate_pybind11_casm.cpp \
  src_pybind11/pirate_pybind11_chime.cpp \
  src_pybind11/pirate_pybind11_loose_ends.cpp \
  src_pybind11/pirate_pybind11_simpulse.cpp \
  src_pybind11/pirate_pybind11_utils.cpp

# Must list all python source files here.
# (Otherwise they won't show up in 'pip install' or pypi.)
# Note: gRPC-generated files (frb_search_pb2*.py) are in GRPC_PYFILES, not here.
PYFILES = \
  pirate_frb/__init__.py \
  pirate_frb/__main__.py \
  pirate_frb/HwtestSender.py \
  pirate_frb/Hwtest.py \
  pirate_frb/Hardware.py \
  pirate_frb/pybind11_injections.py \
  pirate_frb/run_server.py \
  pirate_frb/run_fake_xengine.py \
  pirate_frb/run_rpc_status.py \
  pirate_frb/run_toy_grouper.py \
  pirate_frb/run_toy_sifter.py \
  pirate_frb/yaml_utils.py \
  pirate_frb/chime/__init__.py \
  pirate_frb/casm/__init__.py \
  pirate_frb/casm/CasmReferenceBeamformer.py \
  pirate_frb/casm/Dense1dBeamformer.py \
  pirate_frb/core/__init__.py \
  pirate_frb/slow_avar/__init__.py \
  pirate_frb/slow_avar/SparseTile.py \
  pirate_frb/slow_avar/PfVariance.py \
  pirate_frb/slow_avar/check_approximation.py \
  pirate_frb/slow_avar/check_mc.py \
  pirate_frb/fast_avar/__init__.py \
  pirate_frb/fast_avar/test_fast_avar.py \
  pirate_frb/simpulse/__init__.py \
  pirate_frb/simpulse/test_pulse_upsampling.py \
  pirate_frb/simpulse/plot_pulses.py \
  pirate_frb/kernels/__init__.py \
  pirate_frb/loose_ends/__init__.py \
  pirate_frb/tests/__init__.py \
  pirate_frb/tests/test_assembled_frame_allocator.py \
  pirate_frb/tests/test_assembled_frame_asdf.py \
  pirate_frb/tests/test_network.py \
  pirate_frb/tests/test_pulse_injection.py \
  pirate_frb/tests/test_server.py \
  pirate_frb/tests/utils.py \
  pirate_frb/utils/__init__.py \
  pirate_frb/utils/core.py \
  pirate_frb/utils/ThreadAffinity.py \
  pirate_frb/utils/network.py \
  pirate_frb/utils/time_cupy_dedisperser.py \
  pirate_frb/utils/safe_memcpy.py \
  pirate_frb/utils/show_asdf.py \
  pirate_frb/rpc/__init__.py \
  pirate_frb/rpc/_FrbGrouper.py \
  pirate_frb/rpc/FileSubscriber.py \
  pirate_frb/rpc/FrbSearchClient.py \
  pirate_frb/rpc/FrbSifterClient.py

# Python source files in the 'pirate_frb.cuda_generator' submodule are listed separately.
# Any changes to these files will trigger recompilation of all autogenerated kernels!
# FIXME it would be nice to have more granular dependencies for autogenerated kernels.

CUDAGEN_PYFILES = \
  pirate_frb/cuda_generator/__init__.py \
  pirate_frb/cuda_generator/CoalescedDdKernel2.py \
  pirate_frb/cuda_generator/Dedisperser.py \
  pirate_frb/cuda_generator/Dtype.py \
  pirate_frb/cuda_generator/FrequencySubbands.py \
  pirate_frb/cuda_generator/Kernel.py \
  pirate_frb/cuda_generator/PeakFinder.py \
  pirate_frb/cuda_generator/Ringbuf.py \
  pirate_frb/cuda_generator/utils.py

# Must list all header files here.
# (Otherwise they won't show up in 'pip install' or pypi.)
HFILES = \
  include/pirate/AssembledFrame.hpp \
  include/pirate/avx2_utils.hpp \
  include/pirate/Barrier.hpp \
  include/pirate/BumpAllocator.hpp \
  include/pirate/CasmBeamformer.hpp \
  include/pirate/ChimeBeamformer.hpp \
  include/pirate/CoalescedDdKernel2.hpp \
  include/pirate/constants.hpp \
  include/pirate/CudaEventRingbuf.hpp \
  include/pirate/CudaStreamPool.hpp \
  include/pirate/Dedisperser.hpp \
  include/pirate/DedispersionBuffer.hpp \
  include/pirate/DedispersionConfig.hpp \
  include/pirate/DedispersionKernel.hpp \
  include/pirate/DedispersionPlan.hpp \
  include/pirate/DedispersionTree.hpp \
  include/pirate/HwtestSender.hpp \
  include/pirate/Hwtest.hpp \
  include/pirate/FakeXEngine.hpp \
  include/pirate/file_utils.hpp \
  include/pirate/FileWriter.hpp \
  include/pirate/FrbServer.hpp \
  include/pirate/FrbGrouper.hpp \
  include/pirate/FrequencySubbands.hpp \
  include/pirate/GpuDequantizationKernel.hpp \
  include/pirate/inlines.hpp \
  include/pirate/KernelRegistry.hpp \
  include/pirate/LaggedDownsamplingKernel.hpp \
  include/pirate/MegaRingbuf.hpp \
  include/pirate/network_utils.hpp \
  include/pirate/PeakFindingKernel.hpp \
  include/pirate/PfVariance.hpp \
  include/pirate/Receiver.hpp \
  include/pirate/ReferenceLagbuf.hpp \
  include/pirate/ReferenceTree.hpp \
  include/pirate/ResourceTracker.hpp \
  include/pirate/RingbufCopyKernel.hpp \
  include/pirate/SimulatedFrameFactory.hpp \
  include/pirate/SlabAllocator.hpp \
  include/pirate/SparseTile.hpp \
  include/pirate/simpulse.hpp \
  include/pirate/system_utils.hpp \
  include/pirate/TreeGriddingKernel.hpp \
  include/pirate/utils.hpp \
  include/pirate/XEngineMetadata.hpp \
  include/pirate/YamlFile.hpp \
  include/pirate/loose_ends/avx256_downsample.hpp \
  include/pirate/loose_ends/bitvec.hpp \
  include/pirate/loose_ends/cpu_downsample.hpp \
  include/pirate/loose_ends/DownsampleKernel.hpp \
  include/pirate/loose_ends/gpu_downsample.hpp \
  include/pirate/loose_ends/gpu_transpose.hpp \
  include/pirate/loose_ends/m128_outbuf.hpp \
  include/pirate/loose_ends/m64_outbuf.hpp \
  include/pirate/loose_ends/reduce2.hpp \
  include/pirate/loose_ends/tests.hpp \
  include/pirate/loose_ends/timing.hpp \
  include/pirate/loose_ends/TransposeKernel.hpp

# 'make clean' deletes {*~, *.o, *.d, *.so, *.pyc} from these dirs.
CLEAN_DIRS := . lib src_lib src_lib/loose_ends src_lib/template_instantiations src_lib/autogenerated_kernels src_pybind11 grpc pirate_frb pirate_frb/__pycache__ pirate_frb/casm pirate_frb/casm/__pycache__ pirate_frb/chime pirate_frb/chime/__pycache__ pirate_frb/core pirate_frb/core/__pycache__ pirate_frb/slow_avar pirate_frb/slow_avar/__pycache__ pirate_frb/fast_avar pirate_frb/fast_avar/__pycache__ pirate_frb/simpulse pirate_frb/simpulse/__pycache__ pirate_frb/kernels pirate_frb/kernels/__pycache__ pirate_frb/loose_ends pirate_frb/loose_ends/__pycache__ pirate_frb/utils pirate_frb/utils/__pycache__ pirate_frb/cuda_generator/__pycache__ pirate_frb/rpc pirate_frb/rpc/__pycache__ pirate_frb/rpc/grpc pirate_frb/rpc/grpc/__pycache__ include include/pirate include/pirate/loose_ends include/pirate/cuda_kernels asdf-cxx/src

# Extra files to be deleted by 'make clean'.
# Note that 'pirate_frb/include' and 'pirate_frb/lib' are symlinks, so we put them in CLEAN_FILES, not CLEAN_RMDIRS
# Note that asdf-cxx/include/asdf/config.hxx is generated from misc/asdf_cxx_config.hxx
CLEAN_FILES := sdist_files.txt wheel_files.txt makefile_helper.out pirate_frb/include pirate_frb/lib $(AUTOGENERATED_KERNELS) $(GRPC_CCFILES) $(GRPC_HFILES) $(GRPC_PYFILES) grpc/*.protoc_*_sentinel asdf-cxx/include/asdf/config.hxx notes/*.aux notes/*.log notes/*.out notes/*.toc notes/*.pdf

# Directories that should be empty at the end of 'make clean', and can be deleted.
# Note: pirate_frb/rpc keeps its git-tracked __init__.py, so it is not on this list.
# pirate_frb/rpc/grpc is also kept (not rmdir'd): clean removes its *generated* __init__.py,
# but leaving the directory avoids a fresh-build race where the __init__.py rule's 'touch'
# could run before any _pb2 rule recreates the directory under 'make -j'.
CLEAN_RMDIRS := lib pirate_frb/__pycache__ pirate_frb/casm/__pycache__ pirate_frb/chime/__pycache__ pirate_frb/core/__pycache__ pirate_frb/slow_avar/__pycache__ pirate_frb/fast_avar/__pycache__ pirate_frb/simpulse/__pycache__ pirate_frb/kernels/__pycache__ pirate_frb/loose_ends/__pycache__ pirate_frb/utils/__pycache__ pirate_frb/cuda_generator/__pycache__ pirate_frb/rpc/__pycache__ pirate_frb/rpc/grpc/__pycache__

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


AUTOGENERATED_OFILES := $(AUTOGENERATED_KERNELS:%.cu=%.o)
GRPC_OFILES := $(GRPC_CCFILES:%.cc=%.o)
ASDF_CXX_OFILES := $(ASDF_CXX_SRCFILES:%.cxx=%.o)

# Convert .cu, .cpp, .cc, and .cxx source files to .o object files
LIB_OFILES_CU := $(filter %.cu,$(LIB_SRCFILES))
LIB_OFILES_CPP := $(filter %.cpp,$(LIB_SRCFILES))
LIB_OFILES := $(LIB_OFILES_CU:%.cu=%.o) $(LIB_OFILES_CPP:%.cpp=%.o) $(AUTOGENERATED_OFILES) $(GRPC_OFILES) $(ASDF_CXX_OFILES)

PYEXT_OFILES_CU := $(filter %.cu,$(PYEXT_SRCFILES))
PYEXT_OFILES_CPP := $(filter %.cpp,$(PYEXT_SRCFILES))
PYEXT_OFILES := $(PYEXT_OFILES_CU:%.cu=%.o) $(PYEXT_OFILES_CPP:%.cpp=%.o)

# Must include all .d files, or build will break!
ALL_SRCFILES := $(LIB_SRCFILES) $(PYEXT_SRCFILES) $(AUTOGENERATED_KERNELS) $(GRPC_CCFILES) $(ASDF_CXX_SRCFILES)
ALL_SRCFILES_CU := $(filter %.cu,$(ALL_SRCFILES))
ALL_SRCFILES_CPP := $(filter %.cpp,$(ALL_SRCFILES))
ALL_SRCFILES_CC := $(filter %.cc,$(ALL_SRCFILES))
ALL_SRCFILES_CXX := $(filter %.cxx,$(ALL_SRCFILES))
DEPFILES := $(ALL_SRCFILES_CU:%.cu=%.d) $(ALL_SRCFILES_CPP:%.cpp=%.d) $(ALL_SRCFILES_CC:%.cc=%.d) $(ALL_SRCFILES_CXX:%.cxx=%.d)

# Note that autogenerated files are not included in the sdist.
# asdf-cxx is a git submodule compiled into libpirate.so, so a source build needs its sources,
# its headers, and misc/asdf_cxx_config.hxx (the source for the generated config.hxx).
SDIST_FILES := pyproject.toml Makefile makefile_helper.py autogenerate_kernel.py vendorize.py
SDIST_FILES += $(PYFILES) $(CUDAGEN_PYFILES) $(LIB_SRCFILES) $(PYEXT_SRCFILES) $(HFILES) $(GRPC_PROTO)
SDIST_FILES += grpc/finalize_grpc_stubs.py grpc/wrap_ndebug.py
SDIST_FILES += $(ASDF_CXX_SRCFILES) $(ASDF_CXX_HFILES) misc/asdf_cxx_config.hxx

# Some symlinks for the wheel:
#  - header file include/%.hpp gets symlinked to pirate_frb/include/%.hpp
#  - library lib/libpirate.so gets symlinked to pirate_frb/lib/libpirate.so
#  - python extension pirate_frb/pirate_pybind11...so does not need to be symlinked/renamed.
WHEEL_FILES := $(PYFILES) $(CUDAGEN_PYFILES) $(GRPC_PYFILES) $(PIRATE_PYEXT) pirate_frb/$(PIRATE_LIB)
WHEEL_FILES += $(HFILES:%=pirate_frb/%)

# Phony targets. The special targets 'build_wheel' and 'build_sdist' are needed by pip/pipmake.
lib: $(PIRATE_LIB) $(PIRATE_PYEXT) configs/example_asdf_header.yml configs/example_dedispersion_plan.yml
build_wheel: wheel_files.txt $(PIRATE_LIB) $(PIRATE_PYEXT)
build_sdist: sdist_files.txt

# Auto-generated snapshot of the verbose ASDF YAML header emitted by
# AssembledFrame::write_asdf(). Checked into git and included in the Sphinx docs.
configs/example_asdf_header.yml: configs/xengine_metadata.yml $(PIRATE_PYEXT) $(PIRATE_LIB) $(GRPC_PYFILES)
	$(PYTHON) -m pirate_frb show_file_format $< > $@

# Auto-generated snapshot of 'show_dedisperser -v' run on a representative
# dedispersion config. Checked into git and included in the Sphinx docs.
configs/example_dedispersion_plan.yml: configs/dedispersion/chord_sb2_et.yml $(PIRATE_PYEXT) $(PIRATE_LIB) $(GRPC_PYFILES)
	$(PYTHON) -m pirate_frb show_dedisperser -v $< > $@

# Symlink {include,lib} into python directory 'pirate_frb'.
pirate_frb/include:
	ln -sf ../include $@
pirate_frb/lib:
	ln -sf ../lib $@

# Build object files from .cu files (CUDA kernels)
%.o: %.cu %.d
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) $(NVCC_DEPFLAGS) $(NVCC_CFLAGS) -c -o $@ $<

# Build object files from .cpp files (pure C++, forwarded to host compiler by nvcc)
%.o: %.cpp %.d
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) $(NVCC_DEPFLAGS) -x c++ $(NVCC_CFLAGS) -c -o $@ $<

# Build object files from .cc files (e.g. protoc-generated gRPC files)
%.o: %.cc %.d
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) $(NVCC_DEPFLAGS) -x c++ $(NVCC_CFLAGS) -c -o $@ $<

# Build object files from .cxx files (asdf-cxx library)
%.o: %.cxx %.d
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) $(NVCC_DEPFLAGS) -x c++ $(NVCC_CFLAGS) -c -o $@ $<

# Build object files in src_pybind11/ from .cu files with special flags.
src_pybind11/%.o: src_pybind11/%.cu src_pybind11/%.d
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) $(NVCC_DEPFLAGS) -I$(KSGPU_DIR)/include -I$(PYTHON_INCDIR) -I$(NUMPY_INCDIR) -I$(PYBIND11_INCDIR) $(CONDA_INCFLAGS) -c -o $@ $<

# Build object files in src_pybind11/ from .cpp files with special flags.
src_pybind11/%.o: src_pybind11/%.cpp src_pybind11/%.d
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) $(NVCC_DEPFLAGS) -x c++ -I$(KSGPU_DIR)/include -I$(PYTHON_INCDIR) -I$(NUMPY_INCDIR) -I$(PYBIND11_INCDIR) $(CONDA_INCFLAGS) -c -o $@ $<

# Source files in 'src_lib/autogenerated_kernels' are created using './autogenerate_kernel.py'...
src_lib/autogenerated_kernels/%.cu: autogenerate_kernel.py $(CUDAGEN_PYFILES)
	@mkdir -p src_lib/autogenerated_kernels
	$(PYTHON) autogenerate_kernel.py $@

# ... except for 'src_lib/autogenerated_kernels/debug.cu', which is a "hook" for debugging.
src_lib/autogenerated_kernels/debug.cu:
	@mkdir -p src_lib/autogenerated_kernels
	touch $@

####################################################################################################
#
# asdf-cxx config generation.
# The config.hxx file is generated by copying from misc/asdf_cxx_config.hxx.
# This avoids using cmake to generate the config file.

asdf-cxx/include/asdf/config.hxx: misc/asdf_cxx_config.hxx
	mkdir -p asdf-cxx/include/asdf
	cp $< $@

# All asdf-cxx object files depend on the generated config.hxx
$(ASDF_CXX_OFILES): asdf-cxx/include/asdf/config.hxx

####################################################################################################
#
# gRPC code generation rules (pattern rules for any grpc/*.proto file).
# Generates C++ files in grpc/ and Python stubs in pirate_frb/rpc/grpc/.

# Phony target to regenerate all gRPC files.
#
# NOTE: after editing a .proto, `make grpc` alone is NOT enough -- run a full
# `make`. Under the default ("cpp") protobuf Python implementation, the compiled
# pirate_pybind11 extension links the generated *.pb.o and registers each .proto
# into protobuf's PROCESS-GLOBAL C++ descriptor pool at import time. That stale
# descriptor then SILENTLY SHADOWS the freshly regenerated _pb2.py stub (e.g. a
# new field reads back as missing), until the .pb.o is recompiled and the .so is
# relinked. A full `make` does both.
grpc: $(GRPC_CCFILES) $(GRPC_HFILES) $(GRPC_PYFILES)

# Generate C++ protobuf + gRPC files from .proto. A single protoc invocation
# emits all four outputs (.pb.{h,cc} and .grpc.pb.{h,cc}), so they are the
# targets of ONE pattern rule. A multi-target pattern rule is a grouped target:
# make runs the recipe once to produce all of them, and stamps the outputs
# after the .proto. (The previous sentinel file was touched AFTER its outputs,
# so it stayed permanently newer than them; the empty-recipe rules then 'remade'
# the headers on every 'make', and GNU make treats a remade target as newer than
# its dependents -- forcing a spurious rebuild of every grpc-dependent object,
# both shared libs, and configs/example_asdf_header.yml on each invocation.)
# Note: $* is the stem (the part matched by %).
grpc/%.pb.cc grpc/%.pb.h grpc/%.grpc.pb.cc grpc/%.grpc.pb.h: grpc/%.proto grpc/wrap_ndebug.py
	protoc --cpp_out=grpc --grpc_out=grpc \
	  --plugin=protoc-gen-grpc=$$(which grpc_cpp_plugin) \
	  -I grpc $*.proto
	$(PYTHON) grpc/wrap_ndebug.py grpc/$*.grpc.pb.cc

# Generate Python protobuf + gRPC stubs from .proto. Likewise, one
# grpc_tools.protoc invocation produces both _pb2.py and _pb2_grpc.py, so they
# are the targets of a single (grouped) pattern rule. finalize_grpc_stubs.py
# then rewrites `import xxx_pb2` -> `from . import xxx_pb2` in the generated
# _pb2_grpc.py so the stubs work as a subpackage (pirate_frb.rpc.grpc).
# (Previously we ran `protol` for this, but protoletariat's conda-forge package
# pins protobuf<6 which in turn pinned libgrpc<=1.71.)
pirate_frb/rpc/grpc/%_pb2.py pirate_frb/rpc/grpc/%_pb2_grpc.py: grpc/%.proto grpc/finalize_grpc_stubs.py
	@mkdir -p pirate_frb/rpc/grpc
	$(PYTHON) -m grpc_tools.protoc \
	  -I grpc \
	  --python_out=pirate_frb/rpc/grpc \
	  --grpc_python_out=pirate_frb/rpc/grpc \
	  $*.proto
	$(PYTHON) grpc/finalize_grpc_stubs.py \
	  pirate_frb/rpc/grpc/$*_pb2.py \
	  pirate_frb/rpc/grpc/$*_pb2_grpc.py

# __init__.py has its own touch recipe (previously created as a side effect of
# `protol --create-package`; finalize_grpc_stubs.py deliberately does not).
# It depends on $(GRPC_PROTO) so it is recreated whenever a proto changes.
pirate_frb/rpc/grpc/__init__.py: $(GRPC_PROTO)
	@mkdir -p pirate_frb/rpc/grpc
	touch $@

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

# Build the C++ library (lib/libpirate.so)
# We want it to automatically pull in the C++ library $(KSGPU_DIR)/lib/libkspgu.so.
#
# The python extension has been built correctly if 'objdump -x' shows the following:
#   NEEDED   libksgpu.so
#   RUNPATH  $(KSGPU_DIR)/lib    # where Makefile var $(KSGPU_DIR) is read from makefile_helper.out
#
# The quoting can be understood by working backwards as follows:
#  - g++ command line should look like:   g++ -Wl,-rpath="$(KSGPU_DIR)/lib"
#  - nvcc command line should look like:  nvcc -Xcompiler '"-Wl,-rpath=$(KSGPU_DIR)/lib"'

# Note: depends on makefile_helper.out so that adding/removing autogenerated kernels triggers a relink.
# Libraries: ksgpu, yaml-cpp, grpc++, protobuf (asdf-cxx uses yaml-cpp which is already linked),
# mkl_rt (Intel MKL single-dynamic-library; used by src_lib/simpulse.cpp for the DFTI c2r FFT).
$(PIRATE_LIB): $(LIB_OFILES) makefile_helper.out
	@mkdir -p lib
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) -shared -o $@ $(LIB_OFILES) -lksgpu -lyaml-cpp -lgrpc++ -lprotobuf -lmkl_rt -L$(KSGPU_DIR)/lib $(CONDA_LIBFLAGS) -Xcompiler '"-Wl,-rpath=$(KSGPU_DIR)/lib"' $(CONDA_RPATHFLAGS)

# Build the python extension (pirate_frb/pirate_pybind11...so)
# We want it to automatically pull in the C++ library pirate_frb/lib/libpirate.so.
#
# The python extension has been built correctly if 'objdump -x' shows the following:
#   NEEDED   libpirate.so
#   RUNPATH  $ORIGIN/lib
#
# The quoting can be understood by working backwards as follows:
#  - g++ command line should look like:   g++ -Wl,-rpath="\$ORIGIN/lib"
#  - nvcc command line should look like:  nvcc -Xcompiler '"-Wl,-rpath=\\$ORIGIN/lib"'
#  - Makefile line should look like:      nvcc -Xcompiler '"-Wl,-rpath=\\$$ORIGIN/lib"'
#
# Note that we don't link to libksgpu.so or ksgpu_pybind11...so in this step.
# These libraries end up getting imported as follows:
#
#  1. When 'pirate_frb' is imported, we do 'import ksgpu' (in pirate_frb/__init__.py)
#     before 'import pirate_pybind11'.
#
#  2. When 'ksgpu' is imported, we use the "ctypes trick" (see comment in ksgpu/__init__.py)
#     to load the libraries libksgpu.so and ksgpu_pybind11...so with globally visible symbols.

$(PIRATE_PYEXT): $(PYEXT_OFILES) $(PIRATE_LIB) pirate_frb/lib
	$(NVCC) $(NVCCFLAGS) $(NVCC_ARCH) -shared -o $@ $(PYEXT_OFILES) -lpirate -Lpirate_frb/lib -Xcompiler '"-Wl,-rpath=\\$$ORIGIN/lib"'

# Special targets for autogenerated kernels
#   - autogenerated kernels -> all .cu files
#   - gddk -> dd_fp*.o              (dedispersion kernels)
#   - gpfk -> pf_fp*.o              (peak-finding kernels)
#   - pfom -> pf_output_fp*.o       (peak-finding output microkernels)
#   - pfwr -> pf_weight_reader*.o   (peak-finding weight reader microkernels)
#   - cdd2 -> cdd2_fp*.o            (coalesced dedisperser + peak-finder kernels)

autogenerated_kernels: $(AUTOGENERATED_KERNELS)
gddk: $(filter src_lib/autogenerated_kernels/dd_fp%.o, $(AUTOGENERATED_OFILES))
gpfk: $(filter src_lib/autogenerated_kernels/pf_fp%.o, $(AUTOGENERATED_OFILES))
pfom: $(filter src_lib/autogenerated_kernels/pf_output_fp%.o, $(AUTOGENERATED_OFILES))
pfwr: $(filter src_lib/autogenerated_kernels/pf_weight_reader%.o, $(AUTOGENERATED_OFILES))
cdd2: $(filter src_lib/autogenerated_kernels/cdd2_fp%.o, $(AUTOGENERATED_OFILES))

# Special target for pybind11: compile all pybind11 .o files (but not the final .so)
pybind11: $(PYEXT_OFILES)

# Needed by pip/pipmake: list of all files that go into the (non-editable) wheel.
# Depends on $(GRPC_PYFILES): those are GENERATED members of WHEEL_FILES, and
# pipmake checks every listed file exists on disk. The other WHEEL_FILES members
# are either checked into git ($(PYFILES), $(CUDAGEN_PYFILES)) or already built
# as build_wheel prerequisites ($(PIRATE_PYEXT), $(PIRATE_LIB)). Before the grpc
# codegen was made sentinel-free, $(PIRATE_LIB) depended on the python sentinel,
# which generated the _pb2 stubs as a side effect; without that, a fresh build
# never generated them and pipmake failed with "frb_search_pb2.py not found".
wheel_files.txt: Makefile pirate_frb/include pirate_frb/lib $(GRPC_PYFILES)
	rm -f $@
	for f in $(WHEEL_FILES); do echo $$f; done >>$@

# Needed by pip/pipmake: list of all files that go into the sdist.
sdist_files.txt: Makefile
	rm -f $@
	for f in $(SDIST_FILES); do echo $$f; done >>$@

# 'clean' depends on 'docs-clean' so a single 'make clean' leaves a pristine tree
# (including all Sphinx-generated docs artifacts).
clean: docs-clean
	@for f in $(foreach d,$(CLEAN_DIRS),$(wildcard $d/*~ $d/*.o $d/*.d $d/*.so $d/*.pyc)); do echo rm $$f; rm -f $$f; done
	@for f in $(wildcard src_lib/autogenerated_kernels/dd_fp*.cu); do echo rm $$f; rm -f $$f; done
	@for f in $(wildcard src_lib/autogenerated_kernels/pf_weight_reader_fp*.cu); do echo rm $$f; rm -f $$f; done
	@for f in $(wildcard src_lib/autogenerated_kernels/pf_output_fp*.cu); do echo rm $$f; rm -f $$f; done
	@for f in $(wildcard src_lib/autogenerated_kernels/pf_fp*.cu); do echo rm $$f; rm -f $$f; done
	@for f in $(wildcard src_lib/autogenerated_kernels/cdd2_fp*.cu); do echo rm $$f; rm -f $$f; done
	@for f in $(wildcard $(CLEAN_FILES)); do echo rm $$f; rm -f $$f; done
	@for d in $(wildcard $(CLEAN_RMDIRS)); do echo rmdir $$d; rmdir $$d 2>/dev/null || true; done

# Delete a target if its recipe fails, so a partial output left by a failed
# redirection (e.g. 'configs/example_asdf_header.yml: ... > $@') is not kept and treated
# as up-to-date on the next build.
.DELETE_ON_ERROR:

# Specifying .SECONDARY with no prerequisites disables auto-deletion of intermediate files.
.SECONDARY:

# If a depfile is absent, build can still proceed.
$(DEPFILES):

# Include any depfiles which are present.
include $(wildcard $(DEPFILES))
