Skip to content

Commit fd6f162

Browse files
committed
✅ Add / use deploy script
1 parent 0695725 commit fd6f162

File tree

2 files changed

+254
-97
lines changed

2 files changed

+254
-97
lines changed

.github/workflows/deploy.yml

+4-97
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ on:
1010
push:
1111
branches:
1212
- import-2.1.x
13+
paths:
14+
- 'config/**'
1315

1416
jobs:
1517
deploy:
@@ -19,101 +21,6 @@ jobs:
1921
- name: Check out
2022
uses: actions/checkout@v4
2123

22-
# For each directory containing a changed config file, copy the .h files and build the code:
24+
# Run the mfconfig script with CI action
2325
- name: Deploy bugfix-2.1.x
24-
run: |
25-
IMPORT=import-2.1.x
26-
EXPORT=bugfix-2.1.x
27-
CEXA=config/examples
28-
CDEF=config/default
29-
BC=Configuration.h
30-
AC=Configuration_adv.h
31-
32-
git config user.email "[email protected]"
33-
git config user.name "Scott Lahteine"
34-
35-
echo "- Initializing BASE branch..."
36-
37-
# Copy to a temporary location
38-
TEMP=$( mktemp -d ) ; cp -R config $TEMP
39-
40-
# Strip all #error lines
41-
IFS=$'\n'; set -f
42-
for fn in $( find $TEMP/config -type f -name "Configuration.h" ); do
43-
sed -i~ -e "20,30{/#error/d}" "$fn"
44-
rm "$fn~"
45-
done
46-
unset IFS; set +f
47-
48-
# Create 'BASE' as a copy of 'init-repo' (README, LICENSE, etc.)
49-
git fetch origin init-repo >/dev/null
50-
git checkout origin/init-repo -b BASE >/dev/null || exit
51-
52-
# Copy all config files into place
53-
echo "- Copying configs from fresh $IMPORT..."
54-
cp -R "$TEMP/config" . >/dev/null
55-
56-
echo "- Deleting extras"
57-
58-
# Delete anything that's not a Configuration file
59-
find config -type f \! -name "Conf*.h" -exec rm "{}" \;
60-
61-
# Init Cartesian/SCARA/TPARA configurations to default
62-
echo "- Initializing configs to default state..."
63-
64-
find "$CEXA" -name $BC -print0 \
65-
| while read -d $'\0' F ; do cp "$CDEF/$BC" "$F" >/dev/null ; done
66-
find "$CEXA" -name $AC -print0 \
67-
| while read -d $'\0' F ; do cp "$CDEF/$AC" "$F" >/dev/null ; done
68-
69-
# Update the %VERSION% in the README.md file
70-
VERS=$( echo $EXPORT | sed 's/release-//' )
71-
eval "sed -E -i~ -e 's/%VERSION%/$VERS/g' README.md"
72-
rm -f README.md~
73-
74-
# Commit the 'BASE', ready for customizations
75-
git add . >/dev/null && git commit --amend --no-edit >/dev/null
76-
77-
# Create a new branch from 'BASE' for the final result
78-
echo "- Creating 'built-temp' branch..."
79-
git checkout -b built-temp >/dev/null || exit
80-
81-
# Delete temporary branch
82-
git branch -D BASE 2>/dev/null
83-
84-
echo "- Applying customizations..."
85-
cp -R "$TEMP/config" .
86-
find config -type f \! -name "Configuration*" -exec rm "{}" \;
87-
88-
addpathlabels() {
89-
cd $CEXA
90-
find . -name "Conf*.h" -print0 | while read -d $'\0' fn ; do
91-
fldr=$(dirname "$fn" | sed "s/^\.\///")
92-
blank_line=$(awk '/^\s*$/ {print NR; exit}' "$fn")
93-
sed -i~ "${blank_line}i\\\n#define CONFIG_EXAMPLES_DIR \"$fldr\"" "$fn"
94-
rm -f "$fn~"
95-
done
96-
cd -
97-
}
98-
99-
echo "- Applying path labels..."
100-
addpathlabels >/dev/null 2>&1
101-
102-
git add . >/dev/null && git commit -m "Examples Customizations" >/dev/null
103-
104-
echo "- Adding all the extras..."
105-
cp -R "$TEMP/config" .
106-
107-
echo "- Applying path labels..."
108-
addpathlabels >/dev/null 2>&1
109-
110-
git add . >/dev/null && git commit -m "Examples Extras" >/dev/null
111-
112-
echo "- Replace $EXPORT branch"
113-
git fetch origin $EXPORT >/dev/null
114-
git checkout >/dev/null $EXPORT
115-
git reset --hard built-temp
116-
git push -f
117-
git branch -D built-temp
118-
119-
rm -rf $TEMP
26+
run: bin/mfconfig CI

bin/mfconfig

+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
#!/usr/bin/env python3
2+
#
3+
# mfconfig [manual|init|repath] [source] [dest] [repo-path]
4+
#
5+
# Operate on the MarlinFirmware/Configurations repository.
6+
#
7+
# The MarlinFirmware/Configurations layout could be broken up into branches,
8+
# but this makes management more complicated and requires more commits to
9+
# perform the same operation, so this uses a single branch with subfolders.
10+
#
11+
# init - Initialize the repo with a base commit and changes:
12+
# - Source will be an 'import' branch containing all current configs.
13+
# - Create an empty 'WORK' branch from 'init-repo'.
14+
# - Add Marlin config files, but reset all to defaults.
15+
# - Commit this so changes will be clear in following commits.
16+
# - Add changed Marlin config files and commit.
17+
#
18+
# manual - Import changes from a local Marlin folder, then init.
19+
# - Replace 'default' configs with your local Marlin configs.
20+
# - Wait for manual propagation to the rest of the configs.
21+
# - Run init with the given 'source' and 'dest'
22+
#
23+
# repath - Add path labels to all config files, if needed
24+
# - Add a #define CONFIG_EXAMPLES_DIR to each Configuration*.h file.
25+
#
26+
# CI - Run in CI mode, using the current folder as the repo.
27+
# - For GitHub Actions to update the Configurations repo.
28+
#
29+
import os, sys, subprocess, shutil, datetime, tempfile
30+
from pathlib import Path
31+
32+
# Set to 1 for extra debug commits (no deployment)
33+
DEBUG = 0
34+
35+
# Get the shell arguments into ACTION, IMPORT, and EXPORT
36+
ACTION = sys.argv[1] if len(sys.argv) > 1 else 'manual'
37+
IMPORT = sys.argv[2] if len(sys.argv) > 2 else 'import-2.1.x'
38+
EXPORT = sys.argv[3] if len(sys.argv) > 3 else 'bugfix-2.1.x'
39+
40+
# Get repo paths
41+
CI = False
42+
if ACTION == 'CI':
43+
_REPOS = "."
44+
REPOS = Path(_REPOS)
45+
CONFIGREPO = REPOS
46+
ACTION = 'init'
47+
CI = True
48+
else:
49+
_REPOS = sys.argv[4] if len(sys.argv) > 4 else '~/Projects/Maker/Firmware'
50+
REPOS = Path(_REPOS).expanduser()
51+
CONFIGREPO = REPOS / "Configurations"
52+
53+
def usage():
54+
print(f"Usage: {os.path.basename(sys.argv[0])} [manual|init|repath] [source] [dest] [repo-path]")
55+
56+
if ACTION not in ('manual','init','repath'):
57+
print(f"Unknown action '{ACTION}'")
58+
usage()
59+
sys.exit(1)
60+
61+
CONFIGCON = CONFIGREPO / "config"
62+
CONFIGDEF = CONFIGCON / "default"
63+
CONFIGEXA = CONFIGCON / "examples"
64+
65+
# Configurations repo folder must exist
66+
if not CONFIGREPO.exists():
67+
print(f"Can't find Configurations repo at {_REPOS}")
68+
sys.exit(1)
69+
70+
# Run git within CONFIGREPO
71+
GITSTDERR = None if DEBUG else subprocess.DEVNULL
72+
def git(etc):
73+
if DEBUG:
74+
print(f"> git {' '.join(etc)}")
75+
if etc[0] == "push":
76+
info("*** DRY RUN ***")
77+
return subprocess.run(["echo"])
78+
return subprocess.run(["git"] + etc, cwd=CONFIGREPO, stdout=subprocess.PIPE, stderr=GITSTDERR)
79+
80+
# Get the current branch name
81+
def branch(): return git(["rev-parse", "--abbrev-ref", "HEAD"])
82+
83+
# git add . ; git commit -m ...
84+
def commit(msg, who="."): git(["add", who]) ; return git(["commit", "-m", msg])
85+
86+
# git checkout ...
87+
def checkout(etc): return git(["checkout"] + ([etc] if isinstance(etc, str) else etc))
88+
89+
# git branch -D ...
90+
def gitbd(name): return git(["branch", "-D", name]).stdout
91+
92+
# git status --porcelain : to check for changes
93+
def changes(): return git(["status", "--porcelain"]).stdout.decode().strip()
94+
95+
# Configure git user
96+
git(["config", "user.email", "[email protected]"])
97+
git(["config", "user.name", "Scott Lahteine"])
98+
99+
# Stash uncommitted changes at the destination?
100+
if changes():
101+
print(f"There are uncommitted Configurations repo changes.")
102+
STASH_YES = input("Stash changes? [Y/n] ") ; print()
103+
if STASH_YES not in ('Y','y',''): print("Can't continue") ; sys.exit()
104+
git(["stash", "-m", f"!!GitHub_Desktop<{branch()}>"])
105+
if changes(): print(f"Can't stash changes!") ; sys.exit(1)
106+
107+
def info(msg):
108+
infotag = "[INFO] " if CI else ""
109+
print(f"- {infotag}{msg}")
110+
111+
def add_path_labels():
112+
info("Adding path labels to all configs...")
113+
for fn in CONFIGEXA.glob("**/Configuration*.h"):
114+
fldr = str(fn.parent.relative_to(CONFIGCON)).replace("examples/", "")
115+
with open(fn, 'r') as f:
116+
lines = f.readlines()
117+
emptyline = -1
118+
for i, line in enumerate(lines):
119+
issp = line.isspace()
120+
if emptyline < 0:
121+
if issp: emptyline = i
122+
elif not issp:
123+
if not "CONFIG_EXAMPLES_DIR" in line:
124+
lines.insert(emptyline, f"\n#define CONFIG_EXAMPLES_DIR \"{fldr}\"\n")
125+
with open(fn, 'w') as f: f.writelines(lines)
126+
break
127+
128+
if ACTION == "repath":
129+
add_path_labels()
130+
131+
elif ACTION == "manual":
132+
133+
MARLINREPO = Path(REPOS / "MarlinFirmware")
134+
if not MARLINREPO.exists():
135+
print("Can't find MarlinFirmware at {_REPOS}!")
136+
sys.exit(1)
137+
138+
info(f"Updating '{IMPORT}' from Marlin...")
139+
140+
checkout(IMPORT)
141+
142+
# Replace examples/default with our local copies
143+
shutil.copy(MARLINREPO / "Marlin" / "Configuration*.h", CONFIGDEF)
144+
145+
#git add . && git commit -m "Changes from Marlin ($(date '+%Y-%m-%d %H:%M'))."
146+
#commit(f"Changes from Marlin ({datetime.datetime.now()}).")
147+
148+
print(f"Prepare the import branch and continue when ready.")
149+
INIT_YES = input("Ready to init? [y/N] ") ; print()
150+
if INIT_YES not in ('Y','y'): print("Done.") ; sys.exit()
151+
152+
ACTION = 'init'
153+
154+
if ACTION == "init":
155+
print(f"Building branch '{EXPORT}'...")
156+
info("Init WORK branch...")
157+
158+
info(f"Copy {IMPORT} to temporary location...")
159+
160+
# Use the import branch as the source
161+
result = checkout(IMPORT)
162+
if result.returncode != 0:
163+
print(f"Can't find branch '{IMPORT}'!") ; sys.exit()
164+
165+
# Copy to a temporary location
166+
TEMP = Path(tempfile.mkdtemp())
167+
TEMPCON = TEMP / "config"
168+
shutil.copytree(CONFIGCON, TEMPCON)
169+
170+
# Strip #error lines from Configuration.h
171+
for fn in TEMPCON.glob("**/Configuration.h"):
172+
with open(fn, 'r') as f:
173+
lines = f.readlines()
174+
outlines = []
175+
for line in lines:
176+
if not line.startswith("#error"):
177+
outlines.append(line)
178+
with open(fn, 'w') as f:
179+
f.writelines(outlines)
180+
181+
# Create a fresh 'WORK' as a copy of 'init-repo' (README, LICENSE, etc.)
182+
gitbd("WORK")
183+
if CI: git(["fetch", "origin", "init-repo"])
184+
checkout(["init-repo", "-b", "WORK"])
185+
186+
# Copy default configurations into the repo
187+
info("Create configs in default state...")
188+
for fn in TEMPCON.glob("**/*"):
189+
if fn.is_dir(): continue
190+
relpath = fn.relative_to(TEMPCON)
191+
os.makedirs(CONFIGCON / os.path.dirname(relpath), exist_ok=True)
192+
if fn.name.startswith("Configuration"):
193+
shutil.copy(TEMPCON / "default" / fn.name, CONFIGCON / relpath)
194+
195+
# DEBUG: Commit the reset for review
196+
if DEBUG: commit("[DEBUG] Create defaults")
197+
198+
def replace_in_file(fn, search, replace):
199+
with open(fn, 'r') as f: lines = f.read()
200+
with open(fn, 'w') as f: f.write(lines.replace(search, replace))
201+
202+
# Update the %VERSION% in the README.md file
203+
replace_in_file(CONFIGREPO / "README.md", "%VERSION%", EXPORT.replace("release-", ""))
204+
205+
# Commit all changes up to now; amend if not debugging
206+
if DEBUG:
207+
commit("[DEBUG] Update README.md version", "README.md")
208+
else:
209+
git(["add", "."])
210+
git(["commit", "--amend", "--no-edit"])
211+
212+
# Copy configured Configuration*.h to the working copy
213+
info("Copy examples into place...")
214+
for fn in TEMPCON.glob("examples/**/Configuration*.h"):
215+
shutil.copy(fn, CONFIGCON / fn.relative_to(TEMPCON))
216+
217+
# Put #define CONFIG_EXAMPLES_DIR .. before the first blank line
218+
add_path_labels()
219+
220+
info("Commit config changes...")
221+
commit("Examples Customizations")
222+
223+
# Copy over all files not matching Configuration*.h to the working copy
224+
info("Copy extras into place...")
225+
for fn in TEMPCON.glob("examples/**/*"):
226+
if fn.is_dir(): continue
227+
if fn.name.startswith("Configuration"): continue
228+
shutil.copy(fn, CONFIGCON / fn.relative_to(TEMPCON))
229+
230+
info("Commit extras...")
231+
commit("Examples Extras")
232+
233+
# Delete the temporary folder
234+
shutil.rmtree(TEMP)
235+
236+
# Push to the remote (if desired)
237+
if CI:
238+
PUSH_YES = 'Y'
239+
else:
240+
print()
241+
PUSH_YES = input(f"Push to upstream/{EXPORT}? [y/N] ")
242+
print()
243+
244+
REMOTE = "origin" if CI else "upstream"
245+
246+
if PUSH_YES in ('Y','y'):
247+
info("Push to remote...")
248+
git(["push", "-f", REMOTE, f"WORK:{EXPORT}"])
249+
250+
info("Done.")

0 commit comments

Comments
 (0)