SHELL = /bin/bash CXX = g++ # CXX = clang++ 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 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) -fsanitize=undefined,address -o $@ $< $(BUILD_GEN_DIR)/%: $(GEN_DIR)/%.cpp | $(BUILD_GEN_DIR) @echo "Compiling Generator $< -> $@" $(CXX) $(CXXFLAGS) -I$(INC_DIR) -o $@ $< $(BUILD_VAL_DIR)/%: $(VAL_DIR)/%.cpp | $(BUILD_VAL_DIR) @echo "Compiling Validator $< -> $@" $(CXX) $(CXXFLAGS) -I$(INC_DIR) -o $@ $< $(BUILD_CHK_DIR)/%: $(CHK_DIR)/%.cpp | $(BUILD_CHK_DIR) @echo "Compiling Checker $< -> $@" $(CXX) $(CXXFLAGS) -I$(INC_DIR) -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" > $@ .PHONY: force force: $(BUILD_OUT_A_DIR)/$(SOL_A)%.out: $(BUILD_IN_DIR)/input% $(SOL_A_EXE) force | $(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) force | $(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= [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=" @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=" @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 -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! ---"; \ ' /dev/null | 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)