forked from electro-smith/DaisyExamples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
helper.py
263 lines (246 loc) · 11.5 KB
/
helper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#!/usr/bin/env python
import argparse, glob, os, shutil, codecs
import subprocess
################################################################
# String Data for clarity
################################################################
description = 'Helper script for managing Daisy examples, and creating new projects.'
usage = {
'operation': 'Keyword argument for the desired helper action. Can be any of the following: create, copy, update, rebuild_all.',
'destination': 'Second positional argument to set where the action should be applied. This is the final destination of the project.',
'source': 'optional argument for selecting the project to copy from. required for the copy operation.',
'board': 'optional argument for selecting the template when using the create operation. Default is seed. Options are: seed, field, patch, petal, pod, versio'
}
supported_boards= ['seed', 'pod', 'patch', 'field', 'petal', 'versio']
################################################################
# Helper functions
################################################################
# Opens file at filepath,
# creates a temp file where all instances of string "a"
# are replaced with "b", and then
# removes the original file, and renames the temp file.
def rewrite_replace(filepath, a, b):
try:
in_name = os.path.abspath(filepath)
in_base = os.path.basename(in_name)
t_name = in_name.replace(in_base, 'tmp_{}'.format(in_base))
with codecs.open(in_name, 'r', encoding='utf-8') as fi, \
codecs.open(t_name, 'w', encoding='utf-8') as fo:
for line in fi:
newline = line.replace(a, b)
fo.write(newline)
os.remove(in_name)
os.rename(t_name, in_name)
except Exception as e:
print("Cannot process file: {} \t {} into {}".format(filepath, a, b))
print(e)
# Takes a filename and a list of extensions
# returns true if the file name ends with that extension
def has_extension(fname, ext_list):
f, ext = os.path.splitext(fname)
if ext in ext_list:
return True
else:
return False
################################################################
# BEGIN OPERATIONS
################################################################
# Called via the 'update' operation
# Removes old, copies new from template, processes for string replacement
# only affects .vscode/, vs/ and .sln files.
def update_project(destination):
basedir = os.path.abspath(destination)
root = os.path.dirname(os.path.realpath(__file__))
tdir = os.path.sep.join((root,'utils','Template'))
if not os.path.isdir(basedir):
print('destination should be a directory')
return
proj_name = os.path.basename(basedir)
print('updating {}...'.format(proj_name))
# Removalsd
# Maybe make this optional..
rm_patts = ['*.sln', '*.vcxproj', '*.vcxproj*', '*.vgdbsettings*', '.vscode/*', 'vs/*', 'stm32.props', 'stm32.xml']
rmlists = list(glob.glob(basedir+os.path.sep+pat) for pat in rm_patts)
f_to_rm = list(item for sublist in rmlists for item in sublist)
if len(f_to_rm) > 0:
for f in f_to_rm:
print('deleting: {}'.format(os.path.relpath(f)))
os.remove(f)
# Copying
cp_patts = ['*.sln', '*.vgdbsettings', '.vscode/*', 'vs/*']
cplists = list(glob.glob(tdir+os.path.sep+pat) for pat in cp_patts)
f_to_cp = list(item for sublist in cplists for item in sublist)
for f in f_to_cp:
sname = os.path.abspath(f)
dname = os.path.abspath(sname.replace(tdir, basedir)).replace('Template', proj_name)
dir_path = os.path.dirname(dname)
if not os.path.isdir(dir_path):
os.mkdir(dir_path)
print('copying: {} to {}'.format(os.path.relpath(sname), os.path.relpath(dname)))
shutil.copyfile(sname, dname)
rewrite_replace(dname, 'Template', proj_name)
# Called via the 'copy' operation
# copies _all_ files from source directory,
# deletes old build directories.
def copy_project(destination, source):
print('copying {} to new project: {}'.format(source, destination))
srcAbs = os.path.abspath(source)
srcBase = os.path.basename(srcAbs)
destAbs = os.path.abspath(destination)
destBase = os.path.basename(destAbs)
# First check if src is valid directory.
if os.path.isdir(srcAbs):
# Then make a copy of the folder renaming it to be the same
shutil.copytree(srcAbs, destAbs)
# remove build/ and VisualGDB/ if necessary.
exclude_dirs = ['build', 'vs/VisualGDB', 'VisualGDB']
bdirs = list(os.path.sep.join((destAbs, exclusion)) for exclusion in exclude_dirs)
for d in bdirs:
if os.path.isdir(d):
print('deleting build files: {}', d)
shutil.rmtree(d)
# Go through and rename all the files first.
found = glob.glob(destAbs+ os.path.sep + '*' + os.path.sep + srcBase + '*', recursive=True)
found += glob.glob(destAbs+ os.path.sep + '*' + srcBase + '*', recursive=True)
for f in found:
s = os.path.abspath(f)
d = os.path.abspath(f.replace(srcBase, destBase))
os.rename(s, d)
allFiles = glob.glob(destAbs + os.path.sep + '*', recursive=True)
allFiles += glob.glob(destAbs + os.path.sep + '**' + os.path.sep + '*')
allFiles += glob.glob(os.path.sep.join((destAbs, '.vscode', '*')))
# remove unacceptable extensions
avoid_exts = ['.ai', '.png']
procFiles = list(f for f in allFiles if not has_extension(f, avoid_exts))
# process files with internal text replacement
for f in procFiles:
if not os.path.isdir(f) and os.path.exists(f):
rewrite_replace(f, srcBase, destBase)
else:
print("source directory is not valid.")
# Called via the 'create' operation
def create_from_template(destination, board):
print("creating new project: {} for platform: {}".format(destination, board))
# Essentially need to:
# * run copy_project on template and then rewrite the cpp file..
template_dir = os.path.abspath(os.path.sep.join(('utils', 'Template')))
copy_project(destination, template_dir)
src_file = os.path.abspath(destination + os.path.sep + os.path.basename(destination) + '.cpp')
# Copy resources/diagram files (if available)
dfiles = glob.glob(os.path.sep.join(('resources', '*')))
dfiles += glob.glob(os.path.sep.join(('resources', '**', '*')))
dfiles = list(f for f in dfiles if board in f)
if len(dfiles) > 0:
dsrc = list(os.path.abspath(f) for f in dfiles)
ddest = list(os.path.abspath(os.path.sep.join((destination,f))) for f in dfiles)
# Make resources/ and resources/png/
os.mkdir(os.path.sep.join((destination,'resources')))
os.mkdir(os.path.sep.join((destination,'resources', 'png')))
for s, d in zip(dsrc, ddest):
shutil.copy(s, d)
# Platform specific differences summarized:
# - seed: needs hw.Configure() before init. No hw.UpdateAllControls()
# - patch: four channels instead of two in audio callback.
if board == 'seed':
need_configure = True
controls = False
else:
need_configure = False
controls = True
if board == 'patch':
audio_channels = 4
else:
audio_channels = 2
# Rewrite Source file
with open(src_file, 'w') as f:
f.write('#include "daisy_{}.h"\n'.format(board))
f.write('#include "daisysp.h"\n\n') # extra line below
f.write('using namespace daisy;\n')
f.write('using namespace daisysp;\n\n') # extra line below
f.write('Daisy{} hw;\n'.format(board.capitalize()))
f.write('void AudioCallback(float **in, float **out, size_t size)\n')
f.write('{\n')
if controls:
f.write('\thw.ProcessAllControls();\n')
f.write('\tfor (size_t i = 0; i < size; i++)\n')
f.write('\t{\n')
for i in range(0, audio_channels):
f.write('\t\tout[{}][i] = in[{}][i];\n'.format(i, i))
f.write('\t}\n')
f.write('}\n\n') # extra line before main
f.write('int main(void)\n')
f.write('{\n')
if need_configure:
f.write('\thw.Configure();\n')
f.write('\thw.Init();\n')
f.write('\thw.StartAdc();\n')
f.write('\thw.StartAudio(AudioCallback);\n')
f.write('\twhile(1) {}\n')
f.write('}\n')
def rebuild(destination):
if destination:
print("rebuild coming soon")
else:
print("rebuild all coming soon")
# The below does not work as expected on windows.
# Seems to use wsl's bash, and the default arm-none-eabi in that location
# I'll look into solutions for Windows, though the below may work on other
# platforms already.
# -----------------
# proc = subprocess.Popen(['bash', '-c', './ci/build_libs.sh'],
# stdout=subprocess.PIPE,
# stderr=subprocess.PIPE,
# shell=False)
# stdout, stderr = proc.communicate()
# print(stdout.decode('utf-8'), stderr.decode('utf-8'))
# proc = subprocess.Popen(['./ci/build_examples.sh'],
# stdout=subprocess.PIPE,
# stderr=subprocess.PIPE)
# stdout, stderr = process.communicate()
# print(stdout, stderr)
################################################################
# BEGIN SCRIPT
################################################################
def run():
parser = argparse.ArgumentParser(description=description)
parser.add_argument('operation', help=usage.get('operation'))
parser.add_argument('destination', help =usage.get('destination'), nargs='?')
parser.add_argument('-s', '--source', help=usage.get('source'))
parser.add_argument('-b', '--board', help=usage.get('board'), default='seed', choices=supported_boards)
args = parser.parse_args()
op = args.operation.casefold()
if op == 'create':
# create new project
brd = args.board
dest = args.destination
create_from_template(dest, brd)
elif op == 'copy':
# copy from source to dest
src = args.source
dest = args.destination
copy_project(dest, src)
elif op == 'update':
if args.destination:
dest = args.destination
update_project(dest)
else:
for brd_dir in supported_boards:
brd_ex = list(os.path.sep.join((brd_dir, d)) for d in os.listdir(brd_dir) if 'experimental' not in d.casefold())
brd_ex = list(d for d in brd_ex if os.path.isdir(d))
for ex in brd_ex:
try:
update_project(ex)
except Exception as e:
print('Unable to update example: {}'.format(ex))
print(e)
elif op == 'rebuild':
rebuild(args.destination)
else:
print('invalid operation. please use one of the following:')
print('create - creates a new project with the name \'destination\' for the specified board (or seed if not specified).')
print('copy - duplicates an existing project to the specified \'destination\', replacing names and paths as necessary')
print('update - updates debugging files and directories to latest in template. Won\'t change source files.')
print('rebuild - rebuilds the libraries, and all examples, or a single example if destination is passed in.')
print('done')
if __name__ == '__main__':
run()