]> sigrok.org Git - sigrok-util.git/blob - source/new-driver
7a9131493ad1e5bf7d8fa4fdb583e29cd7b3fad2
[sigrok-util.git] / source / new-driver
1 #!/usr/bin/python3
2 ##
3 ## This file is part of the sigrok-util 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_DRIVER = "DRIVER([${name}], [${short}])\n"
31 TMPL_AUTOCONF_AM_CONDITIONAL = """\
32 AM_CONDITIONAL(HW_${upper}, test x$HW_${upper} = xyes)
33 if test "x$HW_${upper}" = "xyes"; then
34         AC_DEFINE(HAVE_HW_${upper}, 1, [${name} support])
35 fi
36
37 """
38
39 TMPL_HWMAKE_DRIVERLIB = """if HW_${upper}
40 libsigrok_la_SOURCES += \\
41         src/hardware/${short}/protocol.h \\
42         src/hardware/${short}/protocol.c \\
43         src/hardware/${short}/api.c
44 endif
45 """
46 TMPL_DRIVERS_EXTERN = """\
47 #ifdef HAVE_HW_${upper}
48 extern SR_PRIV struct sr_dev_driver ${lib}_driver_info;
49 #endif
50 """
51 TMPL_DRIVERS_POINTER = """\
52 #ifdef HAVE_HW_${upper}
53         &${lib}_driver_info,
54 #endif
55 """
56 FILE_DRV_API = 'drv-api.c'
57 FILE_DRV_PROTOCOL = 'drv-protocol.c'
58 FILE_DRV_PROTOCOL_H = 'drv-protocol.h'
59
60 def tmpl(template):
61     out = re.sub(r'\${([^}]+)}', lambda x: str(names[x.group(1)]), template)
62
63     return out
64
65
66 def tmpl_file(filename):
67     template = open(TMPLDIR + '/' + filename).read()
68
69     return tmpl(template)
70
71
72 def new_driver():
73     tmp = tempfile.mkdtemp()
74     try:
75         os.chdir(tmp)
76         process = Popen("git clone " + LIBSR, shell=True, stderr=PIPE)
77         out, err = process.communicate()
78         if process.returncode:
79             raise Exception(err.decode())
80         gitdir = tmp + '/libsigrok/'
81         do_autoconf(gitdir)
82         do_drivers(gitdir)
83         do_automake(gitdir)
84         do_driverskel(gitdir)
85         make_patch(gitdir)
86     except Exception as e:
87         print(e)
88     shutil.rmtree(tmp)
89
90
91 # add DRIVER and AM_CONDITIONAL/AC_DEFINE entries to configure.ac
92 def do_autoconf(gitdir):
93     cacpath = gitdir + 'configure.ac'
94     configure_ac = open(cacpath).read()
95
96     out = ''
97     state = 'driver'
98     active = False
99     for line in configure_ac.split('\n')[:-1]:
100         if state == 'driver':
101             m = re.match('DRIVER\(\[([^\]]+)', line)
102             if m:
103                 active = True
104             if active:
105                 if (m and m.group(1).upper() > names['name'].upper()) or m is None:
106                     out += tmpl(TMPL_AUTOCONF_DRIVER)
107                     state = 'automake'
108                     active = False
109         elif state == 'automake':
110             m = re.match('AM_CONDITIONAL\(HW_([^,]+)', line)
111             if m:
112                 active = True
113             else:
114                 submatch = re.match('(if|\s*AC_DEFINE|fi|$)', line)
115                 if active and submatch is None:
116                     # we're past the conditionals
117                     out += tmpl(TMPL_AUTOCONF_AM_CONDITIONAL)
118                     state = 'done'
119             if active:
120                 if (m and m.group(1) > names['upper']):
121                     out += tmpl(TMPL_AUTOCONF_AM_CONDITIONAL)
122                     state = 'done'
123         out += line + '\n'
124     if state != 'done':
125         raise Exception('No DRIVER entries found in configure.ac')
126     open(cacpath, 'w').write(out)
127
128
129 # add HAVE_HW_ extern and pointers to drivers.c
130 def do_drivers(gitdir):
131     path = gitdir + 'src/drivers.c'
132     source = open(path).read()
133     out = ''
134     state = 'extern'
135     first_entry = ''
136     for line in source.split('\n')[:-1]:
137         m = re.match('#ifdef HAVE_HW_(.*)', line)
138         if m:
139             if not first_entry:
140                 first_entry = m.group(1)
141             elif m.group(1) == first_entry:
142                 # second time we see this, so we're past the externs
143                 if state != 'idle':
144                     # tack driver on to the end of the list
145                     out += tmpl(TMPL_DRIVERS_EXTERN)
146                 state = 'pointer'
147             if state == 'extern':
148                 if m.group(1) > names['upper']:
149                     out += tmpl(TMPL_DRIVERS_EXTERN)
150                     state = 'idle'
151             elif state == 'pointer':
152                 if m.group(1) > names['upper']:
153                     out += tmpl(TMPL_DRIVERS_POINTER)
154                     state = 'done'
155         elif state == 'pointer' and not re.match('(\s*&|#endif|$)', line):
156             # we passed the last entry
157             out += tmpl(TMPL_DRIVERS_POINTER)
158             state = 'done'
159         out += line + '\n'
160     if state != 'done':
161         raise Exception('No "HAVE_HW_*" markers found in drivers.c' + state)
162     open(path, 'w').write(out)
163
164
165 # add HW_ entry to Makefile.am
166 def do_automake(gitdir):
167     path = gitdir + 'Makefile.am'
168     hwmake = open(path).read()
169
170     out = ''
171     state = 'copy'
172     for line in hwmake.split('\n')[:-1]:
173         if state == 'copy' and re.match('if HW_(.*)$', line):
174             state = 'drivers'
175         if state == 'drivers':
176             m = re.match('if HW_(.*)$', line)
177             if m:
178                 drv_short = m.group(1)
179                 if drv_short > names['upper']:
180                     out += tmpl(TMPL_HWMAKE_DRIVERLIB)
181                     state = 'done'
182             elif not re.match('(libsigrok_la_SOURCES|\s*src/hardware/|endif)', line):
183                 print("[%s]" % line.strip())
184                 # we passed the last entry
185                 out += tmpl(TMPL_HWMAKE_DRIVERLIB)
186                 state = 'done'
187         out += line + '\n'
188     if state != 'done':
189         raise Exception('No "if HW_" markers found in Makefile.am')
190     open(path, 'w').write(out)
191
192
193 def do_driverskel(gitdir):
194     drvdir = gitdir + 'src/hardware/' + names['short']
195     os.mkdir(drvdir)
196     open(drvdir + '/api.c', 'w').write(tmpl_file(FILE_DRV_API))
197     open(drvdir + '/protocol.c', 'w').write(tmpl_file(FILE_DRV_PROTOCOL))
198     open(drvdir + '/protocol.h', 'w').write(tmpl_file(FILE_DRV_PROTOCOL_H))
199
200
201 def make_patch(gitdir):
202     os.chdir(gitdir)
203     command('git add src/hardware/' + names['short'])
204     cmd = 'git commit -m "%s: Initial driver skeleton." ' % names['short']
205     cmd += 'configure.ac Makefile.am src/drivers.c src/hardware/' + names['short']
206     command(cmd)
207     cmd = "git format-patch HEAD~1"
208     out, err = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE).communicate()
209     if err:
210         raise Exception(err.decode())
211     patch = out.decode().strip()
212     shutil.move(gitdir + '/' + patch, scriptdir + '/' + patch)
213     print(patch)
214
215
216 def command(cmd):
217     out, err = Popen(cmd, shell=True, stderr=PIPE).communicate()
218     if err:
219         raise Exception(err.decode())
220
221
222 def parse_gitconfig():
223     try:
224         author = email = None
225         for line in open(os.environ['HOME'] + '/.gitconfig').readlines():
226             m = re.match('\s*(\w+)\s*=\s*(.*)\s*$', line)
227             if m:
228                 key, value = m.groups()
229                 if key == 'name':
230                     author = value
231                 elif key == 'email':
232                     email = value
233                 if author and email:
234                     break
235     except:
236         pass
237     if not author or not email:
238         print("Please put your name and email in ~/.gitconfig")
239         sys.exit()
240
241     return author, email
242
243 #
244 # main
245 #
246
247 scriptdir = os.getcwd()
248 if scriptdir.split('/')[-2:] != ['sigrok-util', 'source']:
249         print("Please call this script from the 'source' directory.")
250         sys.exit(1)
251
252 LIBSR = 'git://sigrok.org/libsigrok'
253 TMPLDIR = scriptdir
254
255 if len(sys.argv) < 2:
256     print("Usage: new-driver <name>")
257     sys.exit()
258
259 author, email = parse_gitconfig()
260 name = ' '.join(sys.argv[1:])
261 names = {
262     'name': name,
263     'short': re.sub('[^a-z0-9]', '-', name.lower()),
264     'lib': re.sub('[^a-z0-9]', '_', name.lower()),
265     'upper': re.sub('[^A-Z0-9]', '_', name.upper()),
266     'libupper': re.sub('[^A-Z0-9]', '', name.upper()),
267     'year': datetime.datetime.now().year,
268     'author': author,
269     'email': email,
270 }
271 new_driver()
272