Browse Source

Add tests for user generated Erlang source files

Loïc Hoguin 9 years ago
parent
commit
cf8f820267
2 changed files with 309 additions and 9 deletions
  1. 38 8
      doc/src/guide/app.asciidoc
  2. 271 1
      test/Makefile

+ 38 - 8
doc/src/guide/app.asciidoc

@@ -235,10 +235,44 @@ transforms have changed. Erlang.mk also automatically keeps
 track of which files should be compiled first, for example
 track of which files should be compiled first, for example
 when you have behaviors used by other modules in your project.
 when you have behaviors used by other modules in your project.
 
 
-=== Generated source files ===
+Erlang.mk can also keep track of the source files generated
+by other means, for example if you generate code from a data
+file in your repository.
 
 
-Generated source files are supported: they should be listed as
-dependencies to `$(PROJECT).d`:
+=== Generating Erlang source
+
+Erlang.mk provides hooks at different stages of the build process.
+When your goal is to generate Erlang source files, you can
+add your own rules before or after the dependency tracking
+file is generated. To do this, you would add your hook before
+or after including the 'erlang.mk' file.
+
+The easiest way is after:
+
+[source,make]
+----
+PROJECT = example
+
+include erlang.mk
+
+$(PROJECT).d:: src/generated_mod.erl
+
+src/generated_mod.erl:: gen-mod.sh
+	$(gen_verbose) ./gen-mod.sh $@
+----
+
+In this case we use `$(gen_verbose)` to hide the details of
+the build by default. Erlang.mk will simply say what file
+is it currently generating.
+
+When using an external script to generate the Erlang source
+file, it is recommended to depend on that script, so that
+the source file gets generated again when the script gets
+modified.
+
+If for whatever reason you prefer to hook before including
+Erlang.mk, don't forget to set the `.DEFAULT_GOAL` variable,
+otherwise nothing will get built:
 
 
 [source,make]
 [source,make]
 ----
 ----
@@ -250,14 +284,10 @@ $(PROJECT).d:: src/generated_mod.erl
 
 
 include erlang.mk
 include erlang.mk
 
 
-src/generated_mod.erl::
+src/generated_mod.erl:: gen-mod.sh
 	$(gen_verbose) ./gen-mod.sh $@
 	$(gen_verbose) ./gen-mod.sh $@
 ----
 ----
 
 
-Note how `.DEFAULT_GOAL` is set to `all` near the beginning. Without
-this, `$(PROJECT).d` would become the default target, changing the
-expected behavior of this `Makefile`.
-
 === Cleaning
 === Cleaning
 
 
 Building typically involves creating a lot of new files. Some
 Building typically involves creating a lot of new files. Some

+ 271 - 1
test/Makefile

@@ -77,7 +77,7 @@ clean-core: clean-core-app clean-core-upgrade
 
 
 # Core: Building applications.
 # Core: Building applications.
 
 
-CORE_APP_CASES = asn1 hrl hrl-recursive mib xrl xrl-include yrl yrl-include
+CORE_APP_CASES = asn1 generate-erl generate-erl-include generate-erl-prepend hrl hrl-recursive mib xrl xrl-include yrl yrl-include
 CORE_APP_TARGETS = $(addprefix core-app-,$(CORE_APP_CASES))
 CORE_APP_TARGETS = $(addprefix core-app-,$(CORE_APP_CASES))
 CORE_APP_CLEAN_TARGETS = $(addprefix clean-,$(CORE_APP_TARGETS))
 CORE_APP_CLEAN_TARGETS = $(addprefix clean-,$(CORE_APP_TARGETS))
 
 
@@ -198,6 +198,276 @@ core-app-asn1: build clean-core-app-asn1
 		[{module, M} = code:load_file(M) || M <- Mods], \
 		[{module, M} = code:load_file(M) || M <- Mods], \
 		halt()"
 		halt()"
 
 
+core-app-generate-erl: build clean-core-app-generate-erl
+
+	$i "Bootstrap a new OTP library named $(APP)"
+	$t mkdir $(APP)/
+	$t cp ../erlang.mk $(APP)/
+	$t $(MAKE) -C $(APP) -f erlang.mk bootstrap-lib $v
+
+	$i "Create a fake script file to be used as dependency"
+	$t touch $(APP)/script.sh
+
+	$i "Append rules to the Makefile to generate a .erl module"
+	$t echo "\$$(PROJECT).d:: src/generated.erl" >> $(APP)/Makefile
+	$t echo "src/generated.erl:: script.sh; echo \"-module(generated).\" > \$$@" >> $(APP)/Makefile
+
+	$i "Generate unrelated .erl files"
+	$t echo "-module(boy)." > $(APP)/src/boy.erl
+	$t echo "-module(girl)." > $(APP)/src/girl.erl
+
+	$i "Build the application"
+	$t $(MAKE) -C $(APP) $v
+
+	$i "Check that all compiled files exist"
+	$t test -f $(APP)/$(APP).d
+	$t test -f $(APP)/ebin/$(APP).app
+	$t test -f $(APP)/ebin/boy.beam
+	$t test -f $(APP)/ebin/generated.beam
+	$t test -f $(APP)/ebin/girl.beam
+	$t test -f $(APP)/src/generated.erl
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
+	$i "Touch the script file; check that only required files are rebuilt"
+	$t printf "%s\n" \
+		$(APP)/$(APP).d \
+		$(APP)/ebin/$(APP).app \
+		$(APP)/ebin/generated.beam \
+		$(APP)/src/generated.erl | sort > $(APP)/EXPECT
+	$t touch $(APP)/script.sh
+	$t $(MAKE) -C $(APP) $v
+	$t find $(APP) -type f -newer $(APP)/script.sh | sort | diff $(APP)/EXPECT -
+	$t rm $(APP)/EXPECT
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
+	$i "Clean the application"
+	$t $(MAKE) -C $(APP) clean $v
+
+	$i "Check that source files still exist"
+	$t test -f $(APP)/Makefile
+	$t test -f $(APP)/erlang.mk
+	$t test -f $(APP)/script.sh
+	$t test -f $(APP)/src/$(APP).app.src
+	$t test -f $(APP)/src/boy.erl
+	$t test -f $(APP)/src/girl.erl
+
+	$i "Check that the generated .erl file still exists"
+	$t test -f $(APP)/src/generated.erl
+
+	$i "Check that all build artifacts are removed"
+	$t test ! -e $(APP)/$(APP).d
+	$t test ! -e $(APP)/ebin/
+
+	$i "Add a rule to remove the generated .erl file on clean"
+	$t echo "clean:: ; rm src/generated.erl" >> $(APP)/Makefile
+
+	$i "Clean the application again"
+	$t $(MAKE) -C $(APP) clean $v
+
+	$i "Check that the generated .erl file was removed"
+	$t test ! -e $(APP)/src/generated.erl
+
+	$i "Build the application again"
+	$t $(MAKE) -C $(APP) $v
+
+	$i "Check that all compiled files exist"
+	$t test -f $(APP)/$(APP).d
+	$t test -f $(APP)/ebin/$(APP).app
+	$t test -f $(APP)/ebin/boy.beam
+	$t test -f $(APP)/ebin/generated.beam
+	$t test -f $(APP)/ebin/girl.beam
+	$t test -f $(APP)/src/generated.erl
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
+core-app-generate-erl-include: build clean-core-app-generate-erl-include
+
+	$i "Bootstrap a new OTP library named $(APP)"
+	$t mkdir $(APP)/
+	$t cp ../erlang.mk $(APP)/
+	$t $(MAKE) -C $(APP) -f erlang.mk bootstrap-lib $v
+
+	$i "Create a fake script file to be used as dependency"
+	$t touch $(APP)/script.sh
+
+	$i "Append rules to the Makefile to generate a .erl module"
+	$t echo "\$$(PROJECT).d:: src/generated.erl" >> $(APP)/Makefile
+	$t echo "src/generated.erl:: script.sh;" \
+		"printf \"%s\n\"" \
+			"\"-module(generated).\"" \
+			"\"-include(\\\"included.hrl\\\").\" > \$$@" >> $(APP)/Makefile
+
+	$i "Generate the .hrl file"
+	$t mkdir $(APP)/include/
+	$t touch $(APP)/include/included.hrl
+
+	$i "Generate unrelated .erl files"
+	$t echo "-module(boy)." > $(APP)/src/boy.erl
+	$t echo "-module(girl)." > $(APP)/src/girl.erl
+
+	$i "Build the application"
+	$t $(MAKE) -C $(APP) $v
+
+	$i "Check that all compiled files exist"
+	$t test -f $(APP)/$(APP).d
+	$t test -f $(APP)/ebin/$(APP).app
+	$t test -f $(APP)/ebin/boy.beam
+	$t test -f $(APP)/ebin/generated.beam
+	$t test -f $(APP)/ebin/girl.beam
+	$t test -f $(APP)/src/generated.erl
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
+	$i "Touch the .hrl file; check that only required files are rebuilt"
+	$t printf "%s\n" \
+		$(APP)/$(APP).d \
+		$(APP)/ebin/$(APP).app \
+		$(APP)/ebin/generated.beam \
+		$(APP)/src/generated.erl | sort > $(APP)/EXPECT
+	$t touch $(APP)/include/included.hrl
+	$t $(MAKE) -C $(APP) $v
+	$t find $(APP) -type f -newer $(APP)/include/included.hrl | sort | diff $(APP)/EXPECT -
+	$t rm $(APP)/EXPECT
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
+core-app-generate-erl-prepend: build clean-core-app-generate-erl-prepend
+
+	$i "Bootstrap a new OTP library named $(APP)"
+	$t mkdir $(APP)/
+	$t cp ../erlang.mk $(APP)/
+	$t $(MAKE) -C $(APP) -f erlang.mk bootstrap-lib $v
+
+	$i "Create a fake script file to be used as dependency"
+	$t touch $(APP)/script.sh
+
+	$i "Generate a Makefile and prepend rules that generate a .erl module"
+	$t echo "PROJECT = $(APP)" > $(APP)/Makefile
+	$t echo ".DEFAULT_GOAL = all" >> $(APP)/Makefile
+	$t echo "\$$(PROJECT).d:: src/generated.erl" >> $(APP)/Makefile
+	$t echo "src/generated.erl:: script.sh; echo \"-module(generated).\" > \$$@" >> $(APP)/Makefile
+	$t echo "include erlang.mk" >> $(APP)/Makefile
+
+	$i "Generate unrelated .erl files"
+	$t echo "-module(boy)." > $(APP)/src/boy.erl
+	$t echo "-module(girl)." > $(APP)/src/girl.erl
+
+	$i "Build the application"
+	$t $(MAKE) -C $(APP) $v
+
+	$i "Check that all compiled files exist"
+	$t test -f $(APP)/$(APP).d
+	$t test -f $(APP)/ebin/$(APP).app
+	$t test -f $(APP)/ebin/boy.beam
+	$t test -f $(APP)/ebin/generated.beam
+	$t test -f $(APP)/ebin/girl.beam
+	$t test -f $(APP)/src/generated.erl
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
+	$i "Touch the script file; check that only required files are rebuilt"
+	$t printf "%s\n" \
+		$(APP)/$(APP).d \
+		$(APP)/ebin/$(APP).app \
+		$(APP)/ebin/generated.beam \
+		$(APP)/src/generated.erl | sort > $(APP)/EXPECT
+	$t touch $(APP)/script.sh
+	$t $(MAKE) -C $(APP) $v
+	$t find $(APP) -type f -newer $(APP)/script.sh | sort | diff $(APP)/EXPECT -
+	$t rm $(APP)/EXPECT
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
+	$i "Clean the application"
+	$t $(MAKE) -C $(APP) clean $v
+
+	$i "Check that source files still exist"
+	$t test -f $(APP)/Makefile
+	$t test -f $(APP)/erlang.mk
+	$t test -f $(APP)/script.sh
+	$t test -f $(APP)/src/$(APP).app.src
+	$t test -f $(APP)/src/boy.erl
+	$t test -f $(APP)/src/girl.erl
+
+	$i "Check that the generated .erl file still exists"
+	$t test -f $(APP)/src/generated.erl
+
+	$i "Check that all build artifacts are removed"
+	$t test ! -e $(APP)/$(APP).d
+	$t test ! -e $(APP)/ebin/
+
+	$i "Add a rule to remove the generated .erl file on clean"
+	$t echo "clean:: ; rm src/generated.erl" >> $(APP)/Makefile
+
+	$i "Clean the application again"
+	$t $(MAKE) -C $(APP) clean $v
+
+	$i "Check that the generated .erl file was removed"
+	$t test ! -e $(APP)/src/generated.erl
+
+	$i "Build the application again"
+	$t $(MAKE) -C $(APP) $v
+
+	$i "Check that all compiled files exist"
+	$t test -f $(APP)/$(APP).d
+	$t test -f $(APP)/ebin/$(APP).app
+	$t test -f $(APP)/ebin/boy.beam
+	$t test -f $(APP)/ebin/generated.beam
+	$t test -f $(APP)/ebin/girl.beam
+	$t test -f $(APP)/src/generated.erl
+
+	$i "Check that the application was compiled correctly"
+	$t $(ERL) -pa $(APP)/ebin/ -eval " \
+		ok = application:start($(APP)), \
+		{ok, Mods = [boy, generated, girl]} \
+			= application:get_key($(APP), modules), \
+		[{module, M} = code:load_file(M) || M <- Mods], \
+		halt()"
+
 core-app-hrl: build clean-core-app-hrl
 core-app-hrl: build clean-core-app-hrl
 
 
 	$i "Bootstrap a new OTP library named $(APP)"
 	$i "Bootstrap a new OTP library named $(APP)"