@@ -172,10 +172,27 @@ TIMING_DIR := $(BUILD_DIR)/timing
172172# Metrics directory for CPU/memory CSV data
173173METRICS_DIR := $(BUILD_DIR ) /metrics
174174
175+ # Stamp files — Make uses these to know a stage is complete.
176+ # Phony targets like `stage2` always re-run their recipe, which causes `test`
177+ # (which depends on `stage2`) to re-execute the entire build even when nothing
178+ # changed. File-based stamps let Make skip already-completed stages.
179+ STAGE0_STAMP := $(BUILD_DIR ) /.stamp-stage0
180+ STAGE1_STAMP := $(BUILD_DIR ) /.stamp-stage1
181+ STAGE2_STAMP := $(BUILD_DIR ) /.stamp-stage2
182+
183+ # Stamp fallback rules: if a stamp doesn't exist, invoke the corresponding
184+ # stage via recursive make. The stage recipe touches the stamp on success.
185+ # Because there are no prerequisites, Make won't re-run these when the stamp
186+ # file already exists — which is the whole point: `test: $(STAGE2_STAMP)` will
187+ # skip the build if stage2 already completed.
188+ $(STAGE0_STAMP ) : ; @$(MAKE ) stable-cabal
189+ $(STAGE1_STAMP ) : ; @$(MAKE ) stage1
190+ $(STAGE2_STAMP ) : ; @$(MAKE ) stage2
191+
175192# HOST_PLATFROM is always from the bootstrap compiler
176193HOST_PLATFORM := $(shell $(GHC0 ) --print-host-platform)
177194
178- CABAL : = $(BUILD_DIR ) /cabal/bin/cabal$(EXE_EXT )
195+ CABAL ? = $(BUILD_DIR ) /cabal/bin/cabal$(EXE_EXT )
179196
180197STAGE1_PATH := $(let STAGE,stage1,$(STORE_DIR ) /host/$(HOST_PLATFORM ) )
181198STAGE2_PATH := $(let STAGE,stage2,$(STORE_DIR ) /host/$(HOST_PLATFORM ) )
@@ -301,13 +318,15 @@ define CABAL_BUILD
301318 $(call CABAL_BUILD_WITH,$(CABAL ) )
302319endef
303320
304- define CABAL_BUILD_STAGE0
321+ define CABAL_INSTALL_STAGE0
305322 $(CABAL0 ) \
306323 --store-dir $(call NORMALIZE_FP,$(CURDIR ) /$(STORE_DIR ) ) \
307324 --logs-dir $(call NORMALIZE_FP,$(CURDIR ) /$(LOGS_DIR ) ) \
308- build \
309- --project-file cabal.project. $( STAGE ) \
325+ install \
326+ --installdir $( dir $( CABAL ) ) \
310327 --builddir $(call NORMALIZE_FP,$(CURDIR ) /$(STAGE_DIR ) ) \
328+ --project-file cabal.project.$(STAGE ) \
329+ --overwrite-policy=always --install-method=copy \
311330 $(CABAL_ARGS )
312331endef
313332
@@ -521,17 +540,19 @@ all: stage2
521540# | (_| (_| | |_) | (_| | |_____| | | | \__ \ || (_| | | |
522541# \___\__,_|_.__/ \__,_|_| |_|_| |_|___/\__\__,_|_|_|
523542
524- .PHONY : $(CABAL )
525- $(CABAL ) : STAGE=stage0
526- $(CABAL ) :
543+ # TODO: Building cabal-install from source as part of the Makefile is a
544+ # temporary workaround. We should eventually require cabal to be provided
545+ # externally (e.g. via ghcup) and drop this target entirely.
546+ .PHONY : stable-cabal
547+ stable-cabal : STAGE=stage0
548+ stable-cabal :
549+ ifeq (,$(USE_SYSTEM_CABAL ) )
527550 $(call PHASE_START,cabal)
528- $(call LOG,Building $@ )
529- $(CABAL_BUILD_STAGE0 ) --with-compiler $(GHC0 ) cabal-install:exe:cabal
530- @mkdir -p $(@D )
531- @cp $$($(CABAL0 ) list-bin -v0 -j --with-compiler $(GHC0 ) --project-file=cabal.project.stage0 --builddir=$(CURDIR ) /$(STAGE_DIR ) cabal-install:exe:cabal | $(CYGPATH ) ) $@
551+ $(call LOG,Building $(CABAL))
552+ $(CABAL_INSTALL_STAGE0) --with-compiler $(GHC0) cabal-install:exe:cabal
532553 $(call PHASE_END_OK,cabal)
533-
534- stage0 : $( CABAL )
554+ @touch $(STAGE0_STAMP)
555+ endif
535556
536557# ____ _ _
537558# / ___|| |_ __ _ __ _ ___ / |
@@ -569,7 +590,7 @@ STAGE1_CABAL_BUILD = \
569590 --ghc-options "-ghcversion-file=$(call NORMALIZE_FP,$(CURDIR ) /rts/include/ghcversion.h) "
570591
571592stage1 : STAGE=stage1
572- stage1 : $( CABAL ) $(CONFIGURE_SCRIPTS ) $(CONFIGURED_FILES ) cabal.project.stage1 cabal.project.common libraries/ghc-boot-th-next | hackage
593+ stage1 : stable-cabal $(CONFIGURE_SCRIPTS ) $(CONFIGURED_FILES ) cabal.project.stage1 cabal.project.common libraries/ghc-boot-th-next | hackage
573594 $(call PHASE_START,stage1)
574595 $(call LOG,Starting build of $(STAGE ) )
575596
@@ -590,6 +611,7 @@ endif
590611
591612 $(call LOG,Finished building $(STAGE))
592613 $(call PHASE_END_OK,stage1)
614+ @touch $(STAGE1_STAMP)
593615
594616$(addprefix $(STAGE1_PATH ) /bin/,$(STAGE1_EXECUTABLES ) ) : stage1
595617
@@ -689,7 +711,7 @@ STAGE2_CABAL_BUILD = \
689711
690712stage2 : STAGE=stage2
691713stage2 : TARGET_PLATFORM:=$(HOST_PLATFORM )
692- stage2 : $(GHC1 ) $( CABAL ) $(CONFIGURE_SCRIPTS ) $(CONFIGURED_FILES ) cabal.project.stage2 cabal.project.stage2.settings cabal.project.common libraries/ghc-boot-th-next | stage1
714+ stage2 : $(GHC1 ) stable-cabal $(CONFIGURE_SCRIPTS ) $(CONFIGURED_FILES ) cabal.project.stage2 cabal.project.stage2.settings cabal.project.common libraries/ghc-boot-th-next | stage1
693715 $(call PHASE_START,stage2)
694716 $(call LOG,Starting build of $(STAGE ) )
695717
@@ -752,6 +774,7 @@ endif
752774 $(call LOG,Finished building $(STAGE) in $(DIST_DIR))
753775 $(call PHASE_END_OK,stage2.dist)
754776 $(call PHASE_END_OK,stage2)
777+ @touch $(STAGE2_STAMP)
755778
756779$(addprefix $(STAGE2_PATH ) /bin/,$(STAGE2_EXECUTABLES ) ) : stage2
757780
@@ -1009,19 +1032,19 @@ $(DIST_DIR)/ghc.tar.gz: stage2
10091032 lib/$(HOST_PLATFORM )
10101033 @echo " ::endgroup::"
10111034
1012- $(DIST_DIR ) /cabal.tar.gz : $( CABAL )
1035+ $(DIST_DIR ) /cabal.tar.gz : stable-cabal
10131036 @echo " ::group::Creating cabal.tar.gz..."
10141037 @mkdir -p $(DIST_DIR ) /bin
1015- @cp $< $(DIST_DIR ) /bin/
1038+ @cp $( CABAL ) $(DIST_DIR ) /bin/
10161039 @tar czf $@ \
10171040 --directory=$(DIST_DIR ) \
10181041 bin/cabal
10191042 @echo " ::endgroup::"
10201043
1021- $(DIST_DIR ) /haskell-toolchain.tar.gz : $( CABAL ) stage2 stage3-javascript-unknown-ghcjs
1044+ $(DIST_DIR ) /haskell-toolchain.tar.gz : stable-cabal stage2 stage3-javascript-unknown-ghcjs
10221045 @echo " ::group::Creating haskell-toolchain.tar.gz..."
10231046 @mkdir -p $(DIST_DIR ) /bin
1024- @cp $< $(DIST_DIR ) /bin/
1047+ @cp $( CABAL ) $(DIST_DIR ) /bin/
10251048 @tar czf $@ \
10261049 --directory=$(DIST_DIR ) \
10271050 $(foreach exe,$(STAGE2_EXECUTABLES ) ,bin/$(exe )$(EXE_EXT ) ) \
@@ -1103,8 +1126,11 @@ libraries/ghc-boot-th-next: \
11031126clean-cabal : clean-stage0
11041127clean-stage0 :
11051128 @echo " ::group::Cleaning build artifacts..."
1129+ ifeq (,$(USE_SYSTEM_CABAL ) )
11061130 rm -rf $(BUILD_DIR)/cabal
1131+ endif
11071132 rm -rf $(BUILD_DIR)/stage0
1133+ rm -f $(STAGE0_STAMP)
11081134 @echo "::endgroup::"
11091135
11101136clean : clean-stage1 clean-stage2 clean-stage3
@@ -1113,11 +1139,13 @@ clean: clean-stage1 clean-stage2 clean-stage3
11131139clean-stage1 :
11141140 @echo " ::group::Cleaning stage1 build artifacts..."
11151141 rm -rf $(BUILD_DIR ) /stage1
1142+ rm -f $(STAGE1_STAMP )
11161143 @echo " ::endgroup::"
11171144
11181145clean-stage2 :
11191146 @echo " ::group::Cleaning stage2 build artifacts..."
11201147 rm -rf $(BUILD_DIR ) /stage2
1148+ rm -f $(STAGE2_STAMP )
11211149 @echo " ::endgroup::"
11221150
11231151clean-stage3 :
@@ -1163,7 +1191,7 @@ testsuite-timeout:
11631191
11641192# --- Test Target ---
11651193
1166- test : stage2 testsuite-timeout
1194+ test : $( STAGE2_STAMP ) testsuite-timeout
11671195 $(call PHASE_START,test)
11681196 @echo " ::group::Running tests with THREADS=$( THREADS) " >&2
11691197 # If any required tool is missing, testsuite logic will skip related tests.
0 commit comments