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