The Makefile uses an auto-registration system to discover and include project build rules. This keeps the main Makefile clean and project-agnostic.
The system supports both flat and nested project structures for flexibility and organization.
A simple text file in the root listing enabled projects:
# Project Auto-Registration
# Supports both flat and nested structures
# Nested projects (organized by category)
games/cs-pathway:dev
lessons/collision-mechanics
systems/student-management
# Flat projects (legacy or uncategorized)
another-project
third-project
Rules:
- One project per line
- Project path (flat:
nameor nested:category/name) - Lines starting with
#are comments - Blank lines ignored
- Must have corresponding Makefile at path
- Optional
:devsuffix for auto-build in dev mode
Supported Structures:
- Flat:
_projects/<project-name>/Makefile→ registered as<project-name> - Nested:
_projects/<category>/<project-name>/Makefile→ registered as<category>/<project-name>
The Makefile reads .makeprojects and includes each project:
###########################################
# Project Auto-Registration
###########################################
-include $(shell test -f .makeprojects && \
grep -v '^\#' .makeprojects | \
grep -v '^$$' | \
sed 's|^|_projects/|' | \
sed 's|$$|/Makefile|' || echo)What it does:
- Checks if
.makeprojectsexists - Filters out comments (#) and blank lines
- Prepends
_projects/and appends/Makefile - Supports both
project→_projects/project/Makefileandcategory/project→_projects/category/project/Makefile - Uses
-include(silent if file missing)
The main Makefile contains zero project-specific code. All project targets come from individual Makefiles.
Each project must have a Makefile, regardless of structure:
Flat Structure:
_projects/<project-name>/
├── README.md # Documentation
├── notebook.src.ipynb # Source notebook (optional)
├── js/ # JavaScript source code
├── sass/ # SCSS definitions (must include a main.scss)
├── levels/ # Legacy project code
├── model/ # Legacy data layer
├── images/ # Assets
└── docs/ # Project docs
Nested Structure:
_projects/games/<project-name>/
├── README.md # Documentation
├── notebook.src.ipynb # Source notebook (optional)
├── js/ # JavaScript source code
├── sass/ # SCSS definitions
├── images/ # Assets
└── docs/ # Project docs
Recommended Categories:
games/- Interactive game projects and mechanicslessons/- Educational lessons and tutorialssystems/- Tools, utilities, and infrastructure projects
The build system automatically generates Makefiles for all registered projects:
- Single Source of Truth: Only
_projects/_template/Makefileis version-controlled - Auto-Copy on Build: When you run any make target, the template is copied to projects missing a Makefile
- Always Up-to-Date: Template improvements instantly benefit all projects
- Clean Repository: No duplicate build configuration in git
What this means for you:
- ✅ Create new projects without copying/editing Makefiles
- ✅ Bug fixes in template propagate automatically
- ✅ Consistent build behavior across all projects
- ✅ Simpler git diffs (only source code changes)
Projects can seamlessly deploy standard styles and scripts to the global assets/ and _sass directories:
- JS: Any files inside
js/will be copied toassets/js/projects/<project-name>/. - SASS: Any
.scssfiles insidesass/will be copied to_sass/projects/<project-name>/. If amain.scssexists inside thesass/directory, it configures Jekyll to natively compile the SCSS intoassets/css/projects/<project-name>/main.css.
The _projects/_template/Makefile is the single source that powers all projects. It includes:
Smart Depth Detection:
- Automatically detects if project is flat (
_projects/project/) or nested (_projects/games/project/) - Sets
WORKSPACE_ROOTcorrectly for both structures
Standard Build Targets:
build- Copy assets and notebooks to distribution directoriesassets- Copy JS, SASS, images to assets directoriesclean- Remove distributed files (preserves source)watch- Auto-rebuild on file changes (for dev mode)docs- Copy documentation to _postsdocs-clean- Remove documentation posts
Watch System (Timestamp-Based, No External Dependencies):
- Uses POSIX
find -newerwith timestamp markers - No fswatch or inotify required
- Individual markers per project:
/tmp/.project_<name>_marker - Checks for changes every 2 seconds
- Automatically rebuilds assets when JS, SASS, images, or notebooks change
- Filters out Makefile changes to avoid regeneration loops
Auto-Detection Features:
- Detects project name from directory
- Handles both flat and nested directory structures
- Silently skips missing source directories (js/, sass/, images/)
Creating a new project is simple - no Makefile needed!
For a flat project:
# 1. Create project directory
mkdir -p _projects/my-new-game
# 2. Add your source files
mkdir -p _projects/my-new-game/js
echo 'console.log("Hello");' > _projects/my-new-game/js/game.js
# 3. Register in .makeprojects
echo "my-new-game" >> _projects/.makeprojects
# 4. Build it (Makefile auto-generated!)
make -C _projects/my-new-game buildFor a nested project:
# 1. Create in category subdirectory
mkdir -p _projects/games/my-new-game
mkdir -p _projects/games/my-new-game/js
# 2. Add source files
echo 'console.log("Hello");' > _projects/games/my-new-game/js/game.js
# 3. Register with category path
echo "games/my-new-game" >> _projects/.makeprojects
# 4. Build it (Makefile auto-generated!)
make -C _projects/games/my-new-game buildThe template Makefile will be automatically copied on first build!
make list-projectsOutput:
📦 Registered Projects:
✅ cs-pathway (active)
⚠️ broken-project (missing Makefile.fragment)
Available projects (in _projects/ directory):
• cs-pathway (registered)
• new-project (not registered)
-
Create project directory:
mkdir -p _projects/new-game/{levels,model,images,docs} -
Create
Makefile:cp _projects/_template/Makefile \ _projects/new-game/Makefile # Or copy from an existing project -
Add to
.makeprojects:echo "new-game" >> .makeprojects
-
Test:
make list-projects # Verify registration make new-game-build # Test build
Comment out in .makeprojects:
# Temporarily disabled
# old-project
cs-pathway
Or remove the line entirely.
Uncomment in .makeprojects:
old-project # Re-enabled!
cs-pathway
Projects can integrate with dev workflow:
# In Makefile
dev: ...existing targets...
@make watch-cs-pathway &
@make watch-other-project &Problem: Hardcoded project names!
Solution: Use a pattern or convention:
# In main Makefile (future enhancement)
dev: bundle-install jekyll-serve watch-notebooks watch-files watch-all-projects
watch-all-projects:
@grep -v '^\#' .makeprojects | grep -v '^$$' | while read proj; do \
if [ -f "_projects/$$proj/Makefile" ]; then \
make -C _projects/$$proj watch & \
fi; \
doneSimilar pattern:
clean: stop
@echo "Cleaning converted files..."
# ...existing clean tasks...
@echo "Cleaning project distributions..."
@grep -v '^\#' .makeprojects | grep -v '^$$' | while read proj; do \
make $$proj-clean 2>/dev/null || true; \
doneProjects should clean up watchers:
stop:
# ...existing stop tasks...
@echo "Stopping project watchers..."
@grep -v '^\#' .makeprojects | grep -v '^$$' | while read proj; do \
ps aux | grep "watch-$$proj" | grep -v grep | awk '{print $$2}' | xargs kill 2>/dev/null || true; \
done- Add 100 projects without touching main Makefile
- Each project self-contained
- Project-specific code lives with project
- Main Makefile stays clean and focused
- Easy to understand what's active (one file)
- Enable/disable projects easily
- No recompilation or complex logic
- Simple text file configuration
make list-projectsshows what's available- Clear separation: registry vs implementation
- Students see their project as a unit
- Copy entire
_projects/example/to start new project - No scary main Makefile edits
# 1. Create new project structure
mkdir -p _projects/quiz-game/{levels,model,images/sprites,docs}
# 2. Copy template files
cp _projects/_template/Makefile \
_projects/quiz-game/Makefile
cp _projects/_template/README.md \
_projects/quiz-game/README.md 2>/dev/null || true
# 3. Edit Makefile
# - Depending on the template used, you may need to update targets.
# - Update paths and targets
# 4. Register the project
echo "quiz-game" >> .makeprojects
# 5. Verify
make list-projects
# 6. Test build
make quiz-game-build
# 7. Integrate with dev (if needed)
# Edit main Makefile dev target:
# @make watch-quiz-game &make list-projects
# Check if project is:
# - Listed in .makeprojects
# - Has Makefile
# - Named correctly (no typos)# Check if targets are defined (e.g. build, clean, watch)
grep -A 5 "^build:" _projects/quiz-game/Makefile
# Test make is working
make -C _projects/quiz-game build# Makefile caches includes - restart
make stop
make devCould enhance main Makefile to auto-discover watch/clean targets:
# Pseudo-code for future
auto-watch-projects:
@for proj in $(REGISTERED_PROJECTS); do \
make watch-$$proj & \
done
REGISTERED_PROJECTS := $(shell grep -v '^\#' .makeprojects | grep -v '^$$')The .makeprojects file now supports a minimal metadata format for each project:
# Format: name[:yes]
cs-pathway:yes
quiz-game
docs-only-project
- name: Project name (matches directory in
_projects/) - :yes (optional): If present, project is included in
make dev(regeneration/watch). If absent, project is not included inmake devby default.
Example:
# Only cs-pathway is included in make dev by default
cs-pathway:yes
quiz-game
docs-only-project
Rules:
- All projects must use this metadata format:
nameorname:yes - Only projects with
:yesare included in the defaultmake devtarget - To add a project to
make dev, append:yesto its line in.makeprojects - To remove a project from
make dev, remove:yesfrom its line
Managing Inclusion in make dev:
-
Edit
.makeprojectsand add or remove:yesfor any project you want to auto-watch inmake dev -
Example command to enable dev/watch for a project:
# Enable quiz-game for make dev sed -i '' 's/^quiz-game$/quiz-game:yes/' .makeprojects
-
Example command to disable:
# Disable quiz-game from make dev sed -i '' 's/^quiz-game:yes$/quiz-game/' .makeprojects
Note: The make dev target should be minimal by default. Only essential projects (e.g., cs-pathway) are included unless explicitly enabled.
validate-projects:
@echo "Validating registered projects..."
@grep -v '^\#' .makeprojects | while read proj; do \
test -f _projects/$$proj/Makefile || echo "⚠️ $$proj missing Makefile"; \
test -f _projects/$$proj/README.md || echo "⚠️ $$proj missing README"; \
doneOld Way:
# In Makefile - hardcoded!
include _projects/cs-pathway/Makefile
include _projects/quiz-game/Makefile
include _projects/another-game/Makefile
dev:
@make watch-cs-pathway &
@make watch-quiz-game &
@make watch-another-game &
clean:
@make cs-pathway-clean
@make quiz-game-clean
@make another-game-cleanNew Way:
# In Makefile - project-agnostic!
-include $(shell grep -v '^\#' .makeprojects | ...)
# In .makeprojects
```text
# Format: name[:dev]
cs-pathway:dev
quiz-game
docs-only-project- name: Project name (matches directory in
_projects/) - :dev (optional): If present, project is included in
make dev(regeneration/watch). If absent, project is not included inmake devby default.
Example:
# Only cs-pathway is included in make dev by default
cs-pathway:dev
quiz-game
docs-only-project
Rules:
- All projects must use this metadata format:
nameorname:dev - Only projects with
:devare included in the defaultmake devtarget - To add a project to
make dev, append:devto its line in.makeprojects - To remove a project from
make dev, remove:devfrom its line
Managing Inclusion in make dev:
-
Edit
.makeprojectsand add or remove:devfor any project you want to auto-watch inmake dev -
Example command to enable dev/watch for a project:
# Enable quiz-game for make dev sed -i '' 's/^quiz-game$/quiz-game:dev/' .makeprojects
-
Example command to disable:
# Disable quiz-game from make dev sed -i '' 's/^quiz-game:dev$/quiz-game/' .makeprojects
Note: The make dev target should be minimal by default. Only essential projects (e.g., cs-pathway) are included unless explicitly enabled.