gray/Makefile
Michele Guerini Rocco bc28d14ebe
add infrastructure for making releases
Idea: a release will be a git tag, distributed as an autogenerated
archive (eg. by GitLab release feature).
This version string must therefore be stored in a revisioned file, so we
use `.version` for this purpose. Before tagging a new release this file
will have to be updated with the new version number.

When building gray the revision (shown in the output headers and usage
screen) will be set to either

- the latest commit hash when building from a git checkout,

- from the .version file when building from release archive.

The implementation is based on whether the .git directory is present.
2024-12-04 16:39:25 +01:00

273 lines
6.9 KiB
Makefile

# Load the configure script variables
-include configure.mk
##
## Directories
##
# Structure:
# gray - top level
# ├── src - source code
# ├── doc - documentation
# ├── tests - integration tests
# └── build - build artifacts
# ├── bin - binaries
# ├── lib - libraries
# ├── obj - objects
# └── inc - files to #include
SRCDIR = src
BUILDDIR = build
SHAREDIR = build/share
BINDIR = build/bin
LIBDIR = build/lib
OBJDIR = build/obj
INCDIR = build/inc
PREFIX ?= /usr
# Directories that need to be created
DIRS = $(BINDIR) $(OBJDIR) $(INCDIR) $(LIBDIR) $(SHAREDIR)
##
## Files
##
# All Fortran source files
SOURCES = $(shell find '$(SRCDIR)' -name '*.f90')
# All Fortran object
OBJECTS = $(addprefix $(OBJDIR)/,$(notdir $(SOURCES:f90=o)))
# Generated makefiles describing object dependencies
DEPS = $(OBJECTS:o=d)
# Fortran modules to be bundled into libraries
MODULES = $(filter-out $(OBJDIR)/main%,$(OBJECTS))
# Build outputs
GRAY = $(BINDIR)/gray
LIBGRAY = $(LIBDIR)/libgray.so
BINARIES = $(GRAY) $(GRAY)-convert
LIBRARIES = $(LIBGRAY)
MANPAGES = $(addprefix $(SHAREDIR)/,gray.1 gray-convert.1 gray.ini.5 \
profiles.txt.5 beamdata.txt.5 magneticdata.txt.5)
##
## Version information
##
ifneq ($(wildcard .git/*),)
# We are building from a git checkout
# Short hash of the latest commit
GIT_REV ?= $(shell git rev-parse --short HEAD)
# Whether the worktree and the latest commit differs
GIT_DIRTY ?= $(shell test -n "$$(git status --porcelain)" && echo "-dirty")
REVISION ?= $(GIT_REV)$(GIT_DIRTY)
else
# We are building from a release archive, use the version
REVISION ?= v$(file < .version)
endif
# Source date
ifndef SOURCE_DATE_EPOCH
# Use current date
SOURCE_DATE_EPOCH=$(shell awk 'BEGIN {print srand(srand())}')
endif
DATE=$(shell LC_TIME=C date -d @$(SOURCE_DATE_EPOCH) '+%B %Y')
##
## Fortran compiler and flags
##
LD = $(FC)
FFLAGS += -I$(INCDIR) -fpic
LDFLAGS += -L$(LIBDIR)
CPPFLAGS += -DREVISION=\"$(REVISION)\" -DPREFIX=\"$(PREFIX)\"
# Compiler-specific flags
ifdef GNU
FFLAGS += -J$(OBJDIR) -frecursive
FFLAGS += -fmax-errors=1 -ffree-line-length-none
CPPFLAGS += -DGNU
endif
ifdef INTEL
FFLAGS += -module $(OBJDIR) -assume recursion -heap-arrays
FFLAGS += -diag-error-limit=1
CPPFLAGS += -DINTEL
endif
ifdef PARALLEL
FFLAGS += -ftree-parallelize-loops=$(PARALLEL) -fopt-info-loop
LDFLAGS += -fopenmp
endif
# Sync the output from jobs running in parallel
MAKEFLAGS += --output-sync
ifndef DETERMINISTIC
ifdef AR_DEFAULT_DETERMINISTIC
# Write timestamps to allow partial updates
ARFLAGS = crU
endif
endif
# Static build options
ifdef STATIC
LIBGRAY := $(LIBGRAY:so=a)
LIBRARIES := $(LIBRARIES:so=a)
ifndef SEMISTATIC
LDFLAGS += -static
endif
else
LDFLAGS += -Wl,-rpath='$$ORIGIN/../lib/'
endif
# Debug build options
ifdef DEBUG
FFLAGS += -ggdb -O0 -Wall -Wunused-parameter
# Tricks to detect uninitialised memory
FFLAGS += -finit-integer=7777777 -finit-real=snan -ffpe-trap=invalid
else
# Optimization level 3
FFLAGS += -O3
endif
##
## Targets
##
.PHONY: all
all: $(BINARIES) $(LIBRARIES)
# Remove all generated files
.PHONY: clean
clean:
rm -r $(BUILDDIR)
# Run all tests
.PHONY: check
check: $(shell python -Bm tests --list-cases)
# Run a test case
tests.%: $(GRAY)
python -Bm tests '$@' --binary '$(GRAY)' --buffer
# Install libraries, binaries and documentation
.PHONY: install-bin
install-bin: $(BINARIES) $(LIBRARIES)
mkdir -p $(PREFIX)/{bin,lib}
install -m555 -t $(PREFIX)/bin $(BINDIR)/*
install -m555 -t $(PREFIX)/lib $(LIBDIR)/*
.PHONY: install-doc
install-doc: $(SHAREDIR)/doc $(MANPAGES)
mkdir -p $(PREFIX)/share/{doc/res,man/man{1,5}}
install -m644 -t $(PREFIX)/share/doc $(SHAREDIR)/doc/manual.*
install -m644 -t $(PREFIX)/share/doc/res $(SHAREDIR)/doc/res/*
install -m644 -t $(PREFIX)/share/man/man1 $(SHAREDIR)/*.1
install -m644 -t $(PREFIX)/share/man/man5 $(SHAREDIR)/*.5
.PHONY: install
install: install-bin install-doc
# dependencies
$(OBJDIR)/%.o: $(OBJDIR)/%.d
# GRAY binary
$(GRAY): $(OBJDIR)/main.o $(LIBGRAY) | $(BINDIR)
$(LD) $(LDFLAGS) -o '$@' $< -lgray
# gray-convert binary
$(GRAY)-convert: $(OBJDIR)/main_convert.o $(LIBGRAY) | $(BINDIR)
$(LD) $(LDFLAGS) -o '$@' $< -lgray
# GRAY shared library
$(LIBDIR)/libgray.so: $(MODULES) | $(LIBDIR)
$(LD) -shared $(LDFLAGS) -o '$@' $^
# GRAY static library
$(LIBDIR)/libgray.a($(MODULES)): | $(LIBDIR)
$(LIBDIR)/libgray.a: $(LIBDIR)/libgray.a($(MODULES))
# Don't update archives in parallel, it's unsupported
.NOTPARALLEL: $(LIBDIR)/libgray.a
# GRAY static library when compiling from JETTO
-include ../include.mk
$(JLIBDIR)/libgray.a: $(LIBDIR)/libgray.a
mkdir -p $(LIBDIR)
install -m755 '$<' '$@'
# All documentation
.PHONY: doc
docs: $(SHAREDIR)/doc $(MANPAGES)
.PHONY: $(SHAREDIR)/doc
$(SHAREDIR)/doc: | $(SHAREDIR)
+make -C doc
# Generated man pages
$(SHAREDIR)/gray.1: $(GRAY) doc/man/gray.1 | $(SHAREDIR)
help2man '$<' -i doc/man/gray.1 -N -n 'beam-tracing code for EC waves' > '$@'
$(SHAREDIR)/gray-convert.1: $(GRAY)-convert | $(SHAREDIR)
help2man '$<' -N -n 'configuration format converter' > '$@'
$(SHAREDIR)/%: doc/man/%.md $(GRAY) | $(SHAREDIR)
pandoc -s '$<' -t man \
-V date='$(DATE)' \
-V footer='$(shell $(GRAY) --version | sed -n 1p)' \
-V author='$(shell $(GRAY) --version | sed -n "3 s/.$$//p")' \
-o '$@'
# Visualise the dependency graph
# Note: requires makefile2graph and graphviz
graph.svg: Makefile
make -Bdn \
| make2graph -b \
| awk -F'( -> )| ;|[[]' ' \
(/label/ && !/(\.o|bin\/gray)/) {bad[$$1]=1; next} \
/->/ {if (bad[$$1] || bad[$$2]) next} \
{print $$0}' \
| sed 's/label/fontsize=30,label/g; s/\.o//' \
| dot -Tsvg -o '$@'
##
## Generic rules
##
# Rebuild everything if the makefile changed
.EXTRA_PREREQS += Makefile
# Search for source code in vendor/ subdirectory
vpath %.f90 $(SRCDIR):$(SRCDIR)/vendor
# Automatic generation of the Fortran prerequisites
# Note 1: this is needed because gfortran -M flag doesn't properly work;
# Note 2: this assumes matching module/file names and no circular
# dependencies (ie. the dependency graph is a DAG);
$(OBJDIR)/%.d: %.f90 | $(OBJDIR)
@printf '$(@:d=o): $< \\\n' > '$@'
@grep -vE '^[[:blank:]]*use.*(intrinsic|iso_|ifport)' '$<' | \
sed -nE 's@^[[:blank:]]*use[[:blank:]]+'\
'([^,[:blank:]&]+).*@$(OBJDIR)/\1.o \\@p' | \
sort -u >> '$@'
@sed -nE 's@^#include "(.+)"@$(INCDIR)/\1 \\@p' '$<' >> '$@'
# Load the generated rules
include $(DEPS)
# Compile a generic Fortran source
$(OBJDIR)/%.o: %.f90 | $(OBJDIR) $(INCDIR)
$(FC) -cpp $(CPPFLAGS) $(FFLAGS) -c '$<' -o '$@'
# Generate Fortran code from awk script
$(INCDIR)/%.inc: $(SRCDIR)/%.awk | $(INCDIR)
awk -f '$<' > '$@'
# Create directories
$(DIRS):
mkdir -p '$@'