add new-driver tool
[sigrok-util.git] / source / new-driver
1 #!/usr/bin/python3
2 #
3 # This file is part of the sigrok project.
4 #
5 # Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20
21 import os
22 import sys
23 import tempfile
24 from subprocess import Popen, PIPE
25 import shutil
26 import re
27 import socket
28 import datetime
29
30 TMPL_AUTOCONF_AC_ARG_ENABLE = """AC_ARG_ENABLE(${short}, AC_HELP_STRING([--enable-${short}],
31           [enable ${name} driver support [default=yes]]),
32           [HW_${upper}="$enableval"],
33           [HW_${upper}=yes])
34 AM_CONDITIONAL(HW_${upper}, test x$HW_${upper} = xyes)
35 if test "x$HW_${upper}" = "xyes"; then
36     AC_DEFINE(HAVE_HW_${upper}, 1, [${name} driver support])
37 fi
38
39 """
40 TMPL_AUTOCONF_AC_CONFIG_FILES = "\t\t hardware/${short}/Makefile\n"
41 TMPL_AUTOCONF_SUMMARY = 'echo "  - ${summary}"\n'
42 TMPL_HWMAKE_SUBDIR = '\t${short}'
43 FILE_DRV_MAKEFILE = 'drv-Makefile.am'
44 FILE_DRV_API = 'drv-api.c'
45 FILE_DRV_PROTOCOL = 'drv-protocol.c'
46 FILE_DRV_PROTOCOL_H = 'drv-protocol.h'
47
48 def tmpl(template):
49     out = re.sub(r'\${([^}]+)}', lambda x: str(names[x.group(1)]), template)
50
51     return out
52
53
54 def tmpl_file(filename):
55     template = open(TMPLDIR + '/' + filename).read()
56
57     return tmpl(template)
58
59
60 def new_driver():
61     tmp = tempfile.mkdtemp()
62     try:
63         os.chdir(tmp)
64         out, err = Popen("git clone " + LIBSR, shell=True, stderr=PIPE).communicate()
65         if err:
66             raise Exception(err.decode())
67         gitdir = tmp + '/libsigrok/'
68         do_configure_ac(gitdir)
69         do_hwmake(gitdir)
70         do_driverskel(gitdir)
71         make_patch(gitdir)
72     except Exception as e:
73         print(e)
74     shutil.rmtree(tmp)
75
76
77 def do_configure_ac(gitdir):
78     cacpath = gitdir + 'configure.ac'
79     configure_ac = open(cacpath).read()
80
81     # add AC_ARG_ENABLE option
82     out = ''
83     state = 'copy'
84     for line in configure_ac.split('\n')[:-1]:
85         if state == 'copy':
86             if line == "# Hardware support '--enable' options.":
87                 state = 'acarg'
88         elif state == 'acarg':
89             m = re.match('AC_ARG_ENABLE\(([^,]+)', line)
90             if m:
91                 drv_short = m.group(1)
92                 if drv_short.lower() > names['short']:
93                     out += tmpl(TMPL_AUTOCONF_AC_ARG_ENABLE)
94                     state = 'done'
95             if line == '# Checks for libraries.':
96                 # new one at the end
97                 out += tmpl(TMPL_AUTOCONF_AC_ARG_ENABLE)
98                 state = 'done'
99         out += line + '\n'
100     if state != 'done':
101         raise Exception('AC_ARG_ENABLE markers not found in configure.ac')
102     configure_ac = out
103
104     # add driver Makefile to AC_CONFIG_FILES
105     out = ''
106     state = 'copy'
107     for line in configure_ac.split('\n')[:-1]:
108         if state == 'copy':
109             if line.find("AC_CONFIG_FILES([Makefile") > -1:
110                 state = 'acconf'
111         elif state == 'acconf':
112             m = re.match('\t\t hardware/([^/]+)/Makefile', line)
113             if m:
114                 drv_short = m.group(1)
115                 if drv_short.lower() > names['short']:
116                     out += tmpl(TMPL_AUTOCONF_AC_CONFIG_FILES)
117                     state = 'done'
118             else:
119                 # new one at the end
120                 out += tmpl(TMPL_AUTOCONF_AC_CONFIG_FILES)
121                 state = 'done'
122         out += line + '\n'
123     if state != 'done':
124         raise Exception('AC_CONFIG_FILES marker not found in configure.ac')
125     configure_ac = out
126
127     # add summary line
128     out = ''
129     state = 'copy'
130     names['summary'] = "%s%s $HW_%s" % (names['name'],
131             '.' * (32 - len(names['name'])), names['upper'])
132     for line in configure_ac.split('\n')[:-1]:
133         if state == 'copy':
134             if line.find('Enabled hardware drivers') > -1:
135                 state = 'echo'
136         elif state == 'echo':
137             m = re.match('echo "  - ([^\.]+)', line)
138             if m:
139                 drv_short = m.group(1)
140                 if drv_short.lower() > names['name'].lower():
141                     out += tmpl(TMPL_AUTOCONF_SUMMARY)
142                     state = 'done'
143             else:
144                 # new one at the end
145                 out += tmpl(TMPL_AUTOCONF_SUMMARY)
146                 state = 'done'
147         out += line + '\n'
148     if state != 'done':
149         raise Exception('summary marker not found in configure.ac')
150     configure_ac = out
151
152     open(cacpath, 'w').write(configure_ac)
153
154
155 def do_hwmake(gitdir):
156     path = gitdir + 'hardware/Makefile.am'
157     hwmake = open(path).read()
158
159     # add AC_ARG_ENABLE option
160     out = ''
161     state = 'copy'
162     for line in hwmake.split('\n')[:-1]:
163         if state == 'copy':
164             if line.find('SUBDIRS =') > -1:
165                 state = 'subdirs'
166         elif state == 'subdirs':
167             m = re.match('\t([^ \\\]+\s*\\\)', line)
168             if m:
169                 drv_short = m.group(1)
170                 if drv_short.lower() > names['short']:
171                     out += tmpl(TMPL_HWMAKE_SUBDIR) + ' \\\n'
172                     state = 'done'
173             else:
174                 out += tmpl(TMPL_HWMAKE_SUBDIR) + ' \\\n'
175                 state = 'done'
176         out += line + '\n'
177     if state != 'done':
178         raise Exception('SUBDIRS markers not found in hardware/Makefile.am')
179     hwmake = out
180
181     open(path, 'w').write(hwmake)
182
183
184 def do_driverskel(gitdir):
185     drvdir = gitdir + 'hardware/' + names['short']
186     os.mkdir(drvdir)
187     open(drvdir + '/Makefile.am', 'w').write(tmpl_file(FILE_DRV_MAKEFILE))
188     open(drvdir + '/api.c', 'w').write(tmpl_file(FILE_DRV_API))
189     open(drvdir + '/protocol.c', 'w').write(tmpl_file(FILE_DRV_PROTOCOL))
190     open(drvdir + '/protocol.h', 'w').write(tmpl_file(FILE_DRV_PROTOCOL_H))
191
192
193 def make_patch(gitdir):
194     os.chdir(gitdir)
195     command('git add hardware/' + names['short'])
196     cmd = 'git commit -m "%s: initial driver skeleton" ' % names['short']
197     cmd += 'configure.ac hardware/Makefile.am hardware/' + names['short']
198     command(cmd)
199     cmd = "git format-patch HEAD~1"
200     out, err = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE).communicate()
201     if err:
202         raise Exception(err.decode())
203     patch = out.decode().strip()
204     shutil.move(gitdir + '/' + patch, scriptdir + '/' + patch)
205     print(patch)
206
207
208 def command(cmd):
209     out, err = Popen(cmd, shell=True, stderr=PIPE).communicate()
210     if err:
211         raise Exception(err.decode())
212
213
214 def parse_gitconfig():
215         try:
216                 author = email = None
217                 for line in open(os.environ['HOME'] + '/.gitconfig').readlines():
218                         m = re.match('\s*(\w+)\s*=\s*(.*)\s*$', line)
219                         if m:
220                                 key, value = m.groups()
221                                 if key == 'name':
222                                         author = value
223                                 elif key == 'email':
224                                         email = value
225                                 if author and email:
226                                         break
227         except:
228                 pass
229         if not author or not email:
230                 print("Please put your name and email in ~/.gitconfig")
231                 sys.exit()
232
233         return author, email
234
235 #
236 # main
237 #
238
239 scriptdir = os.getcwd()
240 if socket.gethostname() == 'sigrok':
241         LIBSR = '/data/git/libsigrok'
242         TMPLDIR = '/data/tools/tmpl'
243 else:
244         LIBSR = 'git://sigrok.org/libsigrok'
245         TMPLDIR = scriptdir
246
247 if len(sys.argv) < 2:
248     print("Usage: new-driver.py <name>")
249     sys.exit()
250
251 author, email = parse_gitconfig()
252 name = ' '.join(sys.argv[1:])
253 names = {
254     'name': name,
255     'short': re.sub('[^a-z0-9]', '-', name.lower()),
256     'lib': re.sub('[^a-z0-9]', '_', name.lower()),
257     'upper': re.sub('[^A-Z0-9]', '_', name.upper()),
258     'libupper': re.sub('[^A-Z0-9]', '', name.upper()),
259         'year': datetime.datetime.now().year,
260         'author': author,
261         'email': email,
262 }
263 new_driver()
264
265