Files
BMSSP/Makefile
2025-10-30 21:49:29 +09:00

228 lines
7.5 KiB
Makefile

SHELL = /bin/bash
CXX = g++
SRC_DIR = src
GEN_DIR = generator
VAL_DIR = validator
INC_DIR = include
CHK_DIR = checker
BUILD_DIR = build
BUILD_TARGET_DIR = $(BUILD_DIR)/target
BUILD_GEN_DIR = $(BUILD_DIR)/gen
BUILD_VAL_DIR = $(BUILD_DIR)/val
BUILD_CHK_DIR = $(BUILD_DIR)/chk
LOG_DIR = $(BUILD_DIR)/logs
BUILD_IN_DIR = $(BUILD_DIR)/input
BUILD_OUT_A_DIR = $(BUILD_DIR)/$(SOL_A)_out
BUILD_OUT_B_DIR = $(BUILD_DIR)/$(SOL_B)_out
DIRS = $(BUILD_DIR) $(BUILD_TARGET_DIR) $(BUILD_GEN_DIR) $(BUILD_VAL_DIR) $(BUILD_CHK_DIR) $(LOG_DIR) $(BUILD_IN_DIR) $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR)
CXXFLAGS = -Wall -O2 -std=c++2a -I$(INC_DIR)
GENFLAGS = -N $(N) -M $(M) -pm $(pm)
SRC_MAIN_FILES = $(wildcard $(SRC_DIR)/*/main.cpp)
SOLS_EXE = $(patsubst $(SRC_DIR)/%/main.cpp, $(BUILD_TARGET_DIR)/%, $(SRC_MAIN_FILES))
SOL_A ?= bsb
SOL_B ?= dijk
SOL_A_EXE = $(BUILD_TARGET_DIR)/$(SOL_A)
SOL_B_EXE = $(BUILD_TARGET_DIR)/$(SOL_B)
GEN_SRCS = $(wildcard $(GEN_DIR)/*.cpp)
GENS_EXE = $(patsubst $(GEN_DIR)/%.cpp, $(BUILD_GEN_DIR)/%, $(GEN_SRCS))
gen ?= gen1
GEN_TO_RUN = $(BUILD_GEN_DIR)/$(gen)
VAL_EXE = $(BUILD_VAL_DIR)/validator
CHK_EXE = $(BUILD_CHK_DIR)/checker
PADDED_ID = $(shell printf "%05d" $(id))
IN_FILE = $(BUILD_IN_DIR)/input$(PADDED_ID)
OUT_A_FILE = $(BUILD_OUT_A_DIR)/$(SOL_A)$(PADDED_ID).out
OUT_B_FILE = $(BUILD_OUT_B_DIR)/$(SOL_B)$(PADDED_ID).out
TEST_COUNT ?= 100
N ?= 1000
M ?= 5000
pm ?= 0.1
.PHONY: all
all: $(SOLS_EXE) $(GENS_EXE) $(VAL_EXE) $(CHK_EXE) $(DIRS)
@echo "All executables compiled in $(BUILD_DIR)/"
$(BUILD_DIR) $(BUILD_TARGET_DIR) $(BUILD_GEN_DIR) $(BUILD_VAL_DIR) $(BUILD_CHK_DIR) $(LOG_DIR) $(BUILD_IN_DIR) $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR):
@echo "Creating directory: $@"
@mkdir -p $@
$(BUILD_TARGET_DIR)/%: $(SRC_DIR)/%/main.cpp | $(BUILD_TARGET_DIR)
@echo "Compiling Solution $< -> $@"
$(CXX) $(CXXFLAGS) -o $@ $<
$(BUILD_GEN_DIR)/%: $(GEN_DIR)/%.cpp | $(BUILD_GEN_DIR)
@echo "Compiling Generator $< -> $@"
$(CXX) $(CXXFLAGS) -o $@ $<
$(BUILD_VAL_DIR)/%: $(VAL_DIR)/%.cpp | $(BUILD_VAL_DIR)
@echo "Compiling Validator $< -> $@"
$(CXX) $(CXXFLAGS) -o $@ $<
$(BUILD_CHK_DIR)/%: $(CHK_DIR)/%.cpp | $(BUILD_CHK_DIR)
@echo "Compiling Checker $< -> $@"
$(CXX) $(CXXFLAGS) -o $@ $<
.PRECIOUS: $(BUILD_IN_DIR)/input%
$(BUILD_IN_DIR)/input%: $(GEN_TO_RUN) | $(BUILD_IN_DIR)
@echo "--- Generating Input: $@ (Using [$(gen)]) ---"
@SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd 'a-zA-Z0-9' | head -c 32); \
echo "Using Seed: $$SEED" >&2; \
./$(GEN_TO_RUN) $(GENFLAGS) "$$SEED" > $@
$(BUILD_OUT_A_DIR)/$(SOL_A)%.out: $(BUILD_IN_DIR)/input% $(SOL_A_EXE) | $(BUILD_OUT_A_DIR)
@echo "--- Running $(SOL_A) (ID: $*) ---"
@time ./$(SOL_A_EXE) < $< > $@
$(BUILD_OUT_B_DIR)/$(SOL_B)%.out: $(BUILD_IN_DIR)/input% $(SOL_B_EXE) | $(BUILD_OUT_B_DIR)
@echo "--- Running $(SOL_B) (ID: $*) ---"
@time ./$(SOL_B_EXE) < $< > $@
.PHONY: gen
gen: all
ifeq ($(id),)
@echo "Error: 'make gen' requires an 'id' variable."
@echo "Usage: make gen id=<test_id> [gen=gen_name]"
@exit 1
else
@$(MAKE) --no-print-directory $(IN_FILE) id=$(id) gen=$(gen) N=$(N) M=$(M)
@echo "--- Generated $(IN_FILE) ---"
endif
# # 레거시 gen1 타겟
# .PHONY: gen1
# gen1: $(BUILD_GEN_DIR)/gen1
# @echo "--- gen1 : Generating 1 test case (DEPRECATED: use 'make gen id=1') ---"
# @SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd 'a-zA-Z0-9' | head -c 32); \
# echo "./$(BUILD_GEN_DIR)/gen1 -N $(N) -M $(M) $$SEED | tee $(BUILD_IN_DIR)/input_legacy"; \
# ./$(BUILD_GEN_DIR)/gen1 -N $(N) -M $(M) $$SEED | tee $(BUILD_IN_DIR)/input_legacy
# # 레거시 gen1-silence 타겟
# .PHONY: gen1-silence
# gen1-silence: $(BUILD_GEN_DIR)/gen1
# @echo "--- gen1 : Generating 1 test case (DEPRECATED: use 'make gen id=1') ---"
# @SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd 'a-zA-Z0-9' | head -c 32); \
# ./$(BUILD_GEN_DIR)/gen1 -N $(N) -M $(M) "$$SEED" > $(BUILD_IN_DIR)/input_legacy
.PHONY: val
val: all $(VAL_EXE)
ifeq ($(id),)
@echo "Error: 'make val' requires an 'id' variable."
@echo "Usage: make val id=<test_id>"
@exit 1
else
@$(MAKE) --no-print-directory $(IN_FILE) id=$(id) gen=$(gen) N=$(N) M=$(M)
@echo "--- Validating $(IN_FILE) (ID=$(id)) ---"
@./$(VAL_EXE) < $(IN_FILE)
@echo "Validator OK."
endif
.PHONY: check
check: all $(CHK_EXE)
ifeq ($(id),)
@echo "Error: 'make check' requires an 'id' variable."
@echo "Usage: make check id=<test_id>"
@exit 1
else
@echo "--- Checking ID=$(id) ---"
@$(MAKE) --no-print-directory $(OUT_A_FILE) id=$(id) gen=$(gen) SOL_A=$(SOL_A) GENARGS=$(GENARGS)
@$(MAKE) --no-print-directory $(OUT_B_FILE) id=$(id) gen=$(gen) SOL_B=$(SOL_B) GENARGS=$(GENARGS)
@echo "--- Comparing Outputs (ID=$(id)) ---"
@./$(CHK_EXE) $(IN_FILE) $(OUT_A_FILE) $(OUT_B_FILE) || ( \
echo "WA (Wrong Answer): Checker found an issue!" && \
echo "Input: $(IN_FILE)" && \
exit 1 \
)
@echo "OK: ID=$(id) outputs match!"
endif
.PHONY: test
test: all $(LOG_DIR) $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR)
ifeq ($(id),)
@echo "--- Running $(TEST_COUNT) tests (Comparing $(SOL_A) vs $(SOL_B)) ---"
@echo "--- Using Generator [$(gen)] (N=$(N), M=$(M)) ---"
@LOG_FILE=$(LOG_DIR)/test_run_$$(date +%Y%m%d_%H%M%S).log; \
echo "--- Logging to $${LOG_FILE} ---"; \
script -q /dev/null /bin/bash -c ' \
set -o pipefail; \
for i in {1..$(TEST_COUNT)}; do \
IN_FILE_LOOP=$(BUILD_IN_DIR)/input$$(printf "%05d" $$i); \
OUT_A_FILE_LOOP=$(BUILD_OUT_A_DIR)/$(SOL_A)$$(printf "%05d" $$i).out; \
OUT_B_FILE_LOOP=$(BUILD_OUT_B_DIR)/$(SOL_B)$$(printf "%05d" $$i).out; \
\
if [ ! -f $$IN_FILE_LOOP ]; then \
SEED=$$(export LC_ALL=C; cat /dev/urandom | tr -cd "a-zA-Z0-9" | head -c 32); \
printf "Test %4d (Seed: %s): " "$$i" "$$SEED"; \
./$(GEN_TO_RUN) -N $(N) -M $(M) "$$SEED" > $$IN_FILE_LOOP; \
printf "(GEN) "; \
else \
printf "Test %4d (Seed: EXISTING): " "$$i"; \
fi; \
\
printf "$(SOL_A): "; \
( TIMEFORMAT=%R; time ./$(SOL_A_EXE) < $$IN_FILE_LOOP > $$OUT_A_FILE_LOOP ) 2>&1 | tr "\n" " " ; \
printf "$(SOL_B): "; \
( TIMEFORMAT=%R; time ./$(SOL_B_EXE) < $$IN_FILE_LOOP > $$OUT_B_FILE_LOOP ) 2>&1 | tr "\n" " " ; \
printf "\t CHK: "; \
( ./$(CHK_EXE) $$IN_FILE_LOOP $$OUT_A_FILE_LOOP $$OUT_B_FILE_LOOP ) 2>&1 || { printf "\nWA! (Input: $$IN_FILE_LOOP)\n"; echo "Input saved to $$IN_FILE_LOOP"; exit 1; }; \
done; \
echo "--- All $(TEST_COUNT) tests passed! ---"; \
' | tee $${LOG_FILE}; \
if [ $${PIPESTATUS[0]} -ne 0 ]; then \
printf "\n--- TESTS FAILED! See %s ---\n" "$${LOG_FILE}"; \
exit 1; \
fi
else
@echo "--- Running single test for ID=$(id) ---"
@$(MAKE) --no-print-directory check id=$(id) gen=$(gen) SOL_A=$(SOL_A) SOL_B=$(SOL_B) N=$(N) M=$(M)
endif
.PHONY: bench
bench: all
ifeq ($(id),)
@echo "Error: 'make bench' requires an 'id' variable."
@exit 1
else
@$(MAKE) --no-print-directory $(IN_FILE) id=$(id) gen=$(gen) N=$(N) M=$(M)
@echo "--- Benchmarking $(SOL_A_EXE) with $(IN_FILE) (ID=$(id)) ---"
@time ./$(SOL_A_EXE) < $(IN_FILE) > $(OUT_A_FILE)
endif
.PHONY: format
format:
@echo "--- Formatting all C/C++ files ---"
@find $(SRC_DIR) $(GEN_DIR) $(VAL_DIR) $(CHK_DIR) $(INC_DIR) -type f \( -name "*.cpp" -o -name "*.c" -o -name "*.hpp" -o -name "*.h" \) -print0 | xargs -0 clang-format -i
@echo "Formatting complete."
.PHONY: clean-outputs
clean-outputs:
@echo "Cleaning up $(BUILD_OUT_A_DIR)/ and $(BUILD_OUT_B_DIR)/..."
@rm -rf $(BUILD_OUT_A_DIR) $(BUILD_OUT_B_DIR)
.PHONY: clean-inputs
clean-inputs:
@echo "Cleaning up $(BUILD_IN_DIR)/..."
@rm -rf $(BUILD_IN_DIR)
.PHONY: clean-tests
clean-tests: clean-inputs clean-outputs
.PHONY: clean
clean:
@echo "Cleaning up $(BUILD_DIR)/..."
@rm -rf $(BUILD_DIR)