top := $(shell pwd) CAT = cat CPR = cp -dR --preserve=mode,ownership ECHO = echo EXISTS = test -e FAIL = exit 1 FIND = find GIT = git INSTALL = install JAVA = java MKDIRS = mkdir -p PATCH = patch RM = rm -f SED = sed SVN = svn TAR = bsdtar TOUCH = touch WGET = wget all: package # memoization ################################################################## # How to use: Define 2 variables (the type you would pass to $(call): # `_NAME_main` and `_NAME_hash`. Now, `_NAME_main` is the function getting # memoized, and _NAME_hash is a function that hashes the function arguments # into a string suitable for a variable name. # # Then, define the final function like: # # NAME = $(foreach func,NAME,$(memoized)) _main = $(_$(func)_main) _hash = _memoized_$(_$(func)_hash) memoized = $(if $($(_hash)),,$(eval $(_hash) := _ $(_main)))$(call rest,$($(_hash))) # utilities #################################################################### # murl = "mangled url" # spec = type|url|extra # file = type/murl/extra # base = type/murl _spec2_main = $(shell utils/spec2 $1 '$2') _file2_main = $(shell utils/file2 $1 '$2') _spec2_hash = spec2$1_$(subst =,^3D,$(subst :,^3A,$(subst ^,^5E,$2))) _file2_hash = file2$1_$2 # There's another level of indirection, because $2 is a list, and each item # needs to be memoized separately _spec2_memo = $(foreach func,spec2,$(memoized)) _file2_memo = $(foreach func,file2,$(memoized)) name2 = $(call spec2,$1,$(foreach name,$2,$($(name)))) #spec2 = $(shell utils/spec2 $1 $(foreach a,$2,'$a')) #file2 = $(shell utils/file2 $1 $(foreach a,$2,'$a')) spec2 = $(foreach spec,$2,$(call _spec2_memo,$1,$(spec))) file2 = $(foreach file,$2,$(call _file2_memo,$1,$(file))) specs_for = $(strip $(foreach t,$1,$(filter $t|%,$(if $2,$2,$(specs))))) # These are equivalent to the same functions in GSML rest = $(wordlist 2,$(words $1),$1) merge = $(firstword $2)$(if $(call rest,$2),$1$(call merge,$1,$(call rest,$2))) # configuration ################################################################ # Load conf/sources.mk variables := $(shell $(SED) "s/[\# ].*//" conf/sources.mk) include conf/sources.mk # Load other configuration files include conf/dependencies.mk export MAVEN_LOCAL_REPO := $(shell $(CAT) conf/maven.local.repo.txt) export JAR_DIR := $(shell $(CAT) conf/jardir.txt) export bindir := $(shell $(CAT) conf/bindir.txt) # Post-processing for conf/sources.mk packages := $(filter-out _%,$(variables)) packages_specific := $(filter $(patsubst rules/%/Makefile,%,$(wildcard rules/*/Makefile)),$(packages)) packages_stub := $(foreach package,$(packages),$(if $(filter stub,$(call name2,type,$(package))),$(package))) packages_generic := $(filter-out $(packages_specific) $(packages_stub),$(packages)) specs := $(foreach variable,$(variables),$(if $(findstring |,$($(variable))),$($(variable)))) tarbombs := $(addprefix build/extract/,$(call spec2,base,$(_tarbombs))) # download ##################################################################### download: PHONY \ $(addprefix build/download/, \ $(call spec2,base,$(call specs_for,git tar)) \ $(call spec2,file,$(call specs_for,svn)) ) build/download/git/%: .tokens/network if [ -d '$@' ]; then \ cd '$@' && $(GIT) fetch --all -p; \ else \ $(MKDIRS) '$(@D)' && $(GIT) clone --mirror '$(call file2,url,git/$*)' '$@'; \ fi $(TOUCH) '$@' build/download/svn/%: .tokens/network if ! [ -d '$(call file2,base,svn/$*)' ]; then \ $(RM) -r '$@' && \ $(MKDIRS) build/download/svn && \ $(SVN) checkout --depth=empty \ '$(call file2,url ,svn/$*)' \ build/download/'$(call file2,base,svn/$*)' && \ cd build/download/'$(call file2,base,svn/$*)'; \ else \ cd build/download/'$(call file2,base,svn/$*)' && \ $(SVN) update; \ fi && \ $(SVN) update --parents '$(call file2,extra,svn/$*)' $(EXISTS) '$@' $(TOUCH) '$@' build/download/tar/%: .tokens/network $(MKDIRS) '$(@D)' $(WGET) -c -O '$@' '$(call file2,url,tar/$*)' $(TOUCH) '$@' # extract ###################################################################### extract: PHONY $(addprefix build/extract,$(call spec2,file,$(call specs_for,git svn tar union))) # This is a little gross because to get the dependencies right for # `git` and `tar`, we need to do a bit more complex processing of # `%`/`$*`. We could do that with `.SECONDEXPANSION:`, but that is a # whole can of worms. Instead, we use a foreach loop to loop over all # possibilities. `svn` doesn't have this issue, because, unlike `git` # and `tar`, its `extra` component is present in `build/download`. # git build/extract/git/%: # magic foreach loop gitref='$(firstword $(subst /, ,$(call file2,extra,git/$*)))' && \ gitdir=build/extract/'$(call file2,base,git/$*)'/$${gitref} && \ $(RM) -r "$$gitdir" && \ { \ $(MKDIRS) "$$(dirname "$$gitdir")" && \ $(GIT) clone build/download/'$(call file2,base,git/$*)' "$$gitdir" && \ ( cd "$$gitdir" && $(GIT) checkout "$$gitref" ) && \ $(EXISTS) '$@'; \ } || { $(RM) -r "$$gitdir"; $(FAIL); } $(TOUCH) '$@' # svn build/extract/svn/%: build/download/svn/% $(RM) -r '$@' $(MKDIRS) '$(@D)' $(CPR) '$<' '$@' || { $(RM) -r '$@'; $(FAIL); } $(TOUCH) '$@' # tar _tar_basedir=build/extract/$(call file2,base,tar/$*) build/extract/tar/%: # magic foreach loop $(RM) -r '$(_tar_basedir)' && \ { \ $(MKDIRS) '$(_tar_basedir)' && \ ( cd '$(_tar_basedir)' && $(TAR) -m $(if $(filter $(_tar_basedir),$(tarbombs)),,--strip-components 1) -xf '$(top)/$<' ); \ } || { $(RM) -r '$(_tar_basedir)'; $(FAIL); } # union build/extract/union/%: # magic foreach loop $(RM) -r '$@' $(MKDIRS) '$@' && $(CPR) $(foreach d,$(filter build/extract/%,$^),'$d'/*) '$@/' || { $(RM) -r '$@'; $(FAIL); } $(TOUCH) '$@' # magic foreach loop (git, tar) $(foreach spec,$(call specs_for,git tar), \ $(eval \ build/extract/$(call spec2,file,$(spec)): \ build/download/$(call spec2,base,$(spec)) \ ) \ ) # magic foreach loop (union) $(foreach spec,$(call specs_for,union), \ $(eval \ build/extract/$(call spec2,file,$(spec)): \ $(addprefix build/extract/,$(call name2,file,$(subst :, ,$(call spec2,url,$(spec))))) \ ) \ ) # place (patch) ################################################################ place: PHONY $(addprefix build/workdir/,$(filter-out $(packages_stub),$(packages))) $(addprefix build/workdir/,$(packages)): \ build/workdir/%: $(RM) -r '$@' $(MKDIRS) '$(@D)' $(CPR) '$<' '$@' || { $(RM) -r '$@'; $(FAIL); } cd '$@' && \ for patch in $(sort $(wildcard $(top)/rules/$*/*.patch)); do \ $(PATCH) -p1 < $$patch || { $(RM) -r '$@'; $(FAIL); }; \ done && \ if [ -f '$(top)/rules/$*/delete.list' ]; then \ $(RM) -r -- $$($(SED) '/^#/d' '$(top)/rules/$*/delete.list'); \ fi $(TOUCH) '$@' # Loop over our source configuration and set up the dependencies # between `build/workdir/` and `build/extract/`. $(foreach package,$(packages),$(eval \ build/workdir/$(package): \ build/extract/$(call name2,file,$(package)) \ )) # Set up the dependencies between `build/workdir/` and `rules/` # This was formerly in the above loop, but this is more efficient. $(foreach line,\ $(shell $(FIND) rules/ -mindepth 1|$(SED) 's,rules/\([^/]*\),build/workdir/\1:&,'),\ $(eval $(line))) # package ###################################################################### package: PHONY $(addprefix build/packages/,$(packages)) _dirs2jars = $(if $1,$(shell $(FIND) $1 -name '*.jar')) _deps2jars = $(filter %.jar,$1) $(call _dirs2jars,$(filter build/packages/%,$1)) deps2classpath = $(call merge,:,$(abspath $(call _deps2jars,$1)) $(CLASSPATH)) _deps2bindirs = $(if $1,$(sort $(shell $(FIND) $1 -type f -executable -printf '%h\n' 2>/dev/null))) deps2path = $(call merge,:,$(abspath $(call _deps2bindirs,$(filter build/packages/%,$1))) $(PATH)) recurse = \ PATH='$(call deps2path,$^)' \ CLASSPATH='$(call deps2classpath,$^)' \ extra_makefiles='$(abspath $(wildcard rules/$*/*.mk))' \ $(MAKE) -C build/workdir/$* -f '$1' install DESTDIR='$(top)/$@' && $(EXISTS) '$@' $(addprefix build/packages/,$(packages_specific)): \ build/packages/%: RECURSIVE build/workdir/% rules/%/Makefile for dep in $(filter-out RECURSIVE,$^); do $(EXISTS) $$dep || { $(RM) -r '$@'; $(FAIL); }; done $(call recurse,$(top)/rules/$*/Makefile) || { $(RM) -r '$@'; $(FAIL); } $(addprefix build/packages/,$(packages_generic)): \ build/packages/%: RECURSIVE build/workdir/% rules/_generic/Makefile for dep in $(filter-out RECURSIVE,$^); do $(EXISTS) $$dep || { $(RM) -r '$@'; $(FAIL); }; done $(call recurse,$(top)/rules/_generic/Makefile) || { $(RM) -r '$@'; $(FAIL); } $(addprefix build/packages/,$(packages_stub)): \ build/packages/%: $(MAKEFILE_LIST) { \ $(ECHO) '#!/bin/sh' && \ $(ECHO) 'CLASSPATH=$(call deps2classpath,$^):$${CLASSPATH:+:$${CLASSPATH}}' && \ $(ECHO) 'export CLASSPATH' && \ $(ECHO) 'exec $(JAVA) $(call name2,extra,$*)' ; \ } | $(INSTALL) -Dm755 /dev/stdin '$@/$(bindir)/$(call name2,url,$*)' && \ { \ $(ECHO) '#!/bin/sh' && \ $(ECHO) 'CLASSPATH=$(call deps2classpath,$^):$${CLASSPATH:+:$${CLASSPATH}}' && \ $(ECHO) 'printf '%s\n' "$$CLASSPATH"' ; \ } | $(INSTALL) -Dm755 /dev/stdin '$@/$(bindir)/$(call name2,url,$*)-classpath' || \ { $(RM) -r '$@'; $(FAIL); } # boilerplate ################################################################## clean: PHONY $(RM) -r build/workdir build/packages distclean: PHONY $(RM) -r .tokens build .tokens/%: $(MKDIRS) '$(@D)' $(TOUCH) '$@' .PHONY: RECURSIVE PHONY .DELETE_ON_ERROR: .SECONDARY: