Skip to content

Commit b02b769

Browse files
committed
Add top-level makefile and remove run-tests script
1 parent ec767c3 commit b02b769

File tree

5 files changed

+139
-159
lines changed

5 files changed

+139
-159
lines changed

.github/workflows/checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
1515

1616
- name: Shell script static analysis
17-
run: shellcheck bin/fetch-configlet bin/verify-unity-version bin/check-unitybegin bin/run-tests format.sh
17+
run: shellcheck bin/fetch-configlet bin/verify-unity-version bin/check-unitybegin format.sh
1818

1919
- name: Check concept exercises formatting
2020
run: |

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ jobs:
2020

2121
steps:
2222
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
23+
- name: Determine number of available hardware threads
24+
run: echo "NUM_THREADS=$(nproc || sysctl -n hw.ncpu)" >> $GITHUB_ENV
2325
- name: Test Exercises
2426
env:
2527
CC: ${{ matrix.compiler }}
26-
run: ./bin/run-tests -a
28+
run: make -j ${{ env.NUM_THREADS }}

bin/run-tests

Lines changed: 0 additions & 150 deletions
This file was deleted.

docs/CONTRIBUTING.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ The structure of en exercise directory is as follows (note the differing hyphen
105105
These are both skipped by the `exercism` CLI when downloading to the client, so it is imperative that you do not reference the names of the files in your code.
106106
If you need to provide a header file example that is necessary to run your tests it should be named `{my_exercise}.h` instead.
107107
Please also use [include guards][] in your header files.
108-
The exercise tests can be run using the [`bin/run-tests`][run-tests] script which will rename the `example.{c|h}` files accordingly.
108+
The exercise tests can be run using `make` from the repository root.
109+
The top-level makefile will rename the `example.{c|h}` files accordingly (use `make help` to learn about individual targets).
109110
* `makefile` - is the makefile for the exercise as it would build using proper filenames (i.e. `{exercise_name}.c` and `{exercise_name}.h` instead of `example.c` and `example.h` respectively).
110111
Makefiles are expected to change very little between exercises so it should be easy to copy one from another exercise.
111112
* `README.md` - is the readme that relates to the exercise.
@@ -142,7 +143,7 @@ If you would like the [`/format`][format-workflow] automated action to work corr
142143
* [Lychee link checker][lychee] action
143144
* `configlet.yml` fetches the latest version of configlet from which it then runs the `lint` command on the track
144145
* `format-code.yml` checks for the string `/format` within any comment on a PR, if it finds it then `format.sh` is run on the exercises and any resulting changes are committed. A deploy key is required for the commit to be able to re-trigger CI. The deploy key is administered by Exercism directly.
145-
* `build.yml` runs the `./bin/run-tests` tool on all exercises
146+
* `test.yml` runs `make` in the repository root to test all exercises
146147

147148
### The Tools
148149

@@ -160,21 +161,20 @@ The work the tools in this directory perform is described as follows:
160161
```
161162

162163
* `fetch-configlet` fetches the `configlet` tool from its [repository][configlet].
163-
* `run-tests` loops through each exercise, prepares the exercise for building and then builds it using `make`, runs the unit tests and then checks it for memory leaks with AddressSanitizer.
164164

165165
### Run Tools Locally
166166

167167
You can also run individual tools on your own machine before committing.
168168
Firstly make sure you have the necessary applications installed (such as `clang-format`, [`git`][git], [`sed`][sed], [`make`][make] and a C compiler), and then run the required tool from the repository root. For example:
169169

170170
```bash
171-
~/git/c$ ./bin/run-tests
171+
~/git/c$ make
172172
```
173173

174-
If you'd like to run only some of the tests to check your work, you can specify them as arguments to the run-tests script.
174+
If you'd like to run only some of the tests to check your work, you can specify them as targets to make.
175175

176176
```bash
177-
~/git/c$ ./bin/run-tests -p -e acronym -e all-your-base -e allergies
177+
~/git/c$ make acronym all-your-base allergies
178178
```
179179

180180
## Test Runner
@@ -207,7 +207,6 @@ Read more about [test runners].
207207
[versions]: ./VERSIONS.md
208208
[test-file-layout]: ./C_STYLE_GUIDE.md#test-file-layout
209209
[include guards]: https://en.wikipedia.org/wiki/Include_guard
210-
[run-tests]: ../bin/run-tests
211210
[configlet]: https://github.com/exercism/configlet
212211
[configlet releases page]: https://github.com/exercism/configlet/releases
213212
[hosted runners]: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners

makefile

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# This makefile creates a target to build and test each exercise using the provided example
2+
# implementation. The exercise target depends on a list of other targets that
3+
# 1) Copy the main test file and adjust it to include all tests,
4+
# 2) Copy the example implementation so that it is used,
5+
# 3) Copy the makefile and unittest framework,
6+
# 4) Build and test the exercise.
7+
#
8+
# Use `make <slug>` to build and test a specific exercise. Simply running `make` builds and
9+
# tests all available exercises.
10+
11+
12+
# Macro to create the rules for one exercise.
13+
# Arguments:
14+
# $(1) - slug
15+
# $(2) - slug with dashes replaced by underscores
16+
# $(3) - type of exercise: 'practice' or 'concept'
17+
# $(4) - name of test implementation: 'example' or 'exemplar'
18+
define setup_exercise
19+
20+
# Copy the test file and removes TEST_IGNORE
21+
build/exercises/$(3)/$(1)/test_$(2).c: exercises/$(3)/$(1)/test_$(2).c
22+
@mkdir -p $$(dir $$@)
23+
@sed 's#TEST_IGNORE();#// &#' $$< > $$@
24+
25+
# Copy example/exemplar implementation
26+
build/exercises/$(3)/$(1)/$(2).c: exercises/$(3)/$(1)/.meta/$(4).c
27+
@mkdir -p $$(dir $$@)
28+
@cp $$< $$@
29+
30+
build/exercises/$(3)/$(1)/$(2).h: $$(wildcard exercises/$(3)/$(1)/.meta/$(4).h exercises/$(3)/$(1)/*.h)
31+
@# Copy all .h files in the exercises directory
32+
@cp exercises/$(3)/$(1)/*.h build/exercises/$(3)/$(1) || true
33+
@# If an example.h/exemplar.h file exists in .meta, replace slug.h with that one
34+
@if [ -e exercises/$(3)/$(1)/.meta/$(4).h ]; then \
35+
cp exercises/$(3)/$(1)/.meta/$(4).h build/exercises/$(3)/$(1)/$(2).h; \
36+
fi
37+
38+
# Copy Makefile
39+
build/exercises/$(3)/$(1)/makefile: exercises/$(3)/$(1)/makefile
40+
@mkdir -p $$(dir $$@)
41+
@cp $$< $$@
42+
43+
# Copy the test framework
44+
build/exercises/$(3)/$(1)/test-framework: $$(wildcard exercises/$(3)/$(1)/test-framework/*)
45+
@mkdir -p $$@
46+
@cp exercises/$(3)/$(1)/test-framework/* build/exercises/$(3)/$(1)/test-framework/
47+
48+
INPUT_FILE_TARGETS = \
49+
build/exercises/$(3)/$(1)/test_$(2).c \
50+
build/exercises/$(3)/$(1)/$(2).c \
51+
build/exercises/$(3)/$(1)/$(2).h \
52+
build/exercises/$(3)/$(1)/makefile \
53+
build/exercises/$(3)/$(1)/test-framework
54+
55+
# Build the exercise.
56+
build/exercises/$(3)/$(1)/tests.out: $$(INPUT_FILE_TARGETS)
57+
$$(MAKE) -C build/exercises/$(3)/$(1) tests.out
58+
59+
# Run the exercise's test binary and create a stamp file if all tests pass
60+
build/exercises/$(3)/$(1)/tests-passed.stamp: build/exercises/$(3)/$(1)/tests.out
61+
@rm -f build/exercises/$(3)/$(1)/tests-passed.stamp
62+
@build/exercises/$(3)/$(1)/tests.out && touch build/exercises/$(3)/$(1)/tests-passed.stamp
63+
64+
# Build and run the memcheck variant
65+
# This is only a single target, since an exercise's makefile builds and runs memcheck as a single target
66+
build/exercises/$(3)/$(1)/memcheck-passed.stamp: $$(INPUT_FILE_TARGETS)
67+
@rm -f build/exercises/$(3)/$(1)/memcheck-passed.stamp
68+
$$(MAKE) -C build/exercises/$(3)/$(1) memcheck && touch build/exercises/$(3)/$(1)/memcheck-passed.stamp
69+
70+
# Top-level target for an exercise. It depends on the stamp files for the actual tests and the memcheck
71+
# run. Only when both stamp files are present will this target be considered "done", so as long as there
72+
# are some errors, building that target will trigger a re-run of the tests or memcheck binary.
73+
.PHONY: $(1)
74+
$(1): build/exercises/$(3)/$(1)/tests-passed.stamp build/exercises/$(3)/$(1)/memcheck-passed.stamp
75+
76+
# Run `make clean` for this exercise.
77+
.PHONY: $(1)-clean
78+
$(1)-clean: $$(INPUT_FILE_TARGETS)
79+
$$(MAKE) -C build/exercises/$(3)/$(1) clean
80+
81+
endef
82+
83+
PRACTICE_EXERCISES := $(notdir $(wildcard exercises/practice/*))
84+
CONCEPT_EXERCISES := $(notdir $(wildcard exercises/concept/*))
85+
86+
all: practice concept
87+
88+
.PHONY: practice
89+
practice: $(PRACTICE_EXERCISES)
90+
@if [ -z "$(PRACTICE_EXERCISES)" ]; then \
91+
echo "No practice exercises found."; \
92+
fi
93+
94+
.PHONY: concept
95+
concept: $(CONCEPT_EXERCISES)
96+
@if [ -z "$(CONCEPT_EXERCISES)" ]; then \
97+
echo "No concept exercises found."; \
98+
fi
99+
100+
# Instantiate the macro for each practice exercise to create targets for each exercise.
101+
$(foreach exercise,$(PRACTICE_EXERCISES),$(eval $(call setup_exercise,$(exercise),$(subst -,_,$(exercise)),practice,example)))
102+
$(foreach exercise,$(CONCEPT_EXERCISES),$(eval $(call setup_exercise,$(exercise),$(subst -,_,$(exercise)),concept,exemplar)))
103+
104+
.PHONY: list-practice
105+
list-practice:
106+
@for exercise in $(PRACTICE_EXERCISES); do \
107+
echo "$$exercise"; \
108+
done
109+
110+
.PHONY: list-concept
111+
list-concept:
112+
@for exercise in $(CONCEPT_EXERCISES); do \
113+
echo "$$exercise"; \
114+
done
115+
116+
.PHONY: clean
117+
clean:
118+
rm -rf build
119+
120+
.PHONY: help
121+
help:
122+
@echo "Available targets:"
123+
@echo " all - Build and test all practice exercises (default)"
124+
@echo " <slug> - Build and test a specific exercise given by its slug"
125+
@echo " clean - Remove all build artifacts"
126+
@echo " <slug>-clean - Remove build artifacts of a specific exercise given by its slug"
127+
@echo " list-practice - List all practice exercises"
128+
@echo " list-concept - List all concept exercises"
129+
@echo " help - Show this help message"

0 commit comments

Comments
 (0)