]> git.donarmstrong.com Git - qmk_firmware.git/blob - tool/mbed/mbed-sdk/workspace_tools/synch.py
Squashed 'tmk_core/' changes from 7967731..b9e0ea0
[qmk_firmware.git] / tool / mbed / mbed-sdk / workspace_tools / synch.py
1 """
2 mbed SDK
3 Copyright (c) 2011-2013 ARM Limited
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9     http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17
18 One repository to update them all
19 On mbed.org the mbed SDK is split up in multiple repositories, this script takes
20 care of updating them all.
21 """
22 import sys
23 from copy import copy
24 from os import walk, remove, makedirs
25 from os.path import join, abspath, dirname, relpath, exists, isfile
26 from shutil import copyfile
27 from optparse import OptionParser
28 import re
29 import string
30
31 ROOT = abspath(join(dirname(__file__), ".."))
32 sys.path.insert(0, ROOT)
33
34 from workspace_tools.settings import MBED_ORG_PATH, MBED_ORG_USER, BUILD_DIR
35 from workspace_tools.paths import LIB_DIR
36 from workspace_tools.utils import run_cmd
37
38 MBED_URL = "mbed.org"
39 MBED_USER = "mbed_official"
40
41 changed = []
42 push_remote = True
43 quiet = False
44 commit_msg = ''
45
46 # Code that does have a mirror in the mbed SDK
47 # Tuple data: (repo_name, list_of_code_dirs, [team])
48 # team is optional - if not specified, the code is published under mbed_official
49 OFFICIAL_CODE = (
50     ("mbed-src" , "mbed"),
51     ("mbed-rtos", "rtos"),
52     ("mbed-dsp" , "dsp"),
53     ("mbed-rpc" , "rpc"),
54
55     ("lwip"    , "net/lwip/lwip"),
56     ("lwip-sys", "net/lwip/lwip-sys"),
57     ("Socket"  , "net/lwip/Socket"),
58
59     ("lwip-eth"         , "net/eth/lwip-eth"),
60     ("EthernetInterface", "net/eth/EthernetInterface"),
61
62     ("USBDevice", "USBDevice"),
63     ("USBHost"  , "USBHost"),
64
65     ("CellularModem", "net/cellular/CellularModem"),
66     ("CellularUSBModem", "net/cellular/CellularUSBModem"),
67     ("UbloxUSBModem", "net/cellular/UbloxUSBModem"),
68     ("UbloxModemHTTPClientTest", ["tests/net/cellular/http/common", "tests/net/cellular/http/ubloxusb"]),
69     ("UbloxModemSMSTest", ["tests/net/cellular/sms/common", "tests/net/cellular/sms/ubloxusb"]),
70     ("FATFileSystem", "fs/fat", "mbed-official"),
71 )
72
73
74 # Code that does have dependencies to libraries should point to
75 # the latest revision. By default, they point to a specific revision.
76 CODE_WITH_DEPENDENCIES = (
77     # Libraries
78     "EthernetInterface",
79
80     # RTOS Examples
81     "rtos_basic",
82     "rtos_isr",
83     "rtos_mail",
84     "rtos_mutex",
85     "rtos_queue",
86     "rtos_semaphore",
87     "rtos_signals",
88     "rtos_timer",
89
90     # Net Examples
91     "TCPEchoClient",
92     "TCPEchoServer",
93     "TCPSocket_HelloWorld",
94     "UDPSocket_HelloWorld",
95     "UDPEchoClient",
96     "UDPEchoServer",
97     "BroadcastReceive",
98     "BroadcastSend",
99
100     # mbed sources
101     "mbed-src-program",
102 )
103
104 # A list of regular expressions that will be checked against each directory
105 # name and skipped if they match.
106 IGNORE_DIRS = (
107 )
108
109 IGNORE_FILES = (
110     'COPYING',
111     '\.md',
112     "\.lib",
113     "\.bld"
114 )
115
116 def ignore_path(name, reg_exps):
117     for r in reg_exps:
118         if re.search(r, name):
119             return True
120     return False
121
122 class MbedRepository:
123     @staticmethod
124     def run_and_print(command, cwd):
125         stdout, _, _ = run_cmd(command, wd=cwd, redirect=True)
126         print(stdout)
127
128     def __init__(self, name, team = None):
129         self.name = name
130         self.path = join(MBED_ORG_PATH, name)
131         if team is None:
132             self.url = "http://" + MBED_URL + "/users/" + MBED_USER + "/code/%s/"
133         else:
134             self.url = "http://" + MBED_URL + "/teams/" + team + "/code/%s/"
135         if not exists(self.path):
136             # Checkout code
137             if not exists(MBED_ORG_PATH):
138                 makedirs(MBED_ORG_PATH)
139
140             self.run_and_print(['hg', 'clone', self.url % name], cwd=MBED_ORG_PATH)
141
142         else:
143             # Update
144             self.run_and_print(['hg', 'pull'], cwd=self.path)
145             self.run_and_print(['hg', 'update'], cwd=self.path)
146
147     def publish(self):
148         # The maintainer has to evaluate the changes first and explicitly accept them
149         self.run_and_print(['hg', 'addremove'], cwd=self.path)
150         stdout, _, _ = run_cmd(['hg', 'status'], wd=self.path)
151         if stdout == '':
152             print "No changes"
153             return False
154         print stdout
155         if quiet:
156             commit = 'Y'
157         else:
158             commit = raw_input(push_remote and "Do you want to commit and push? Y/N: " or "Do you want to commit? Y/N: ")
159         if commit == 'Y':
160             args = ['hg', 'commit', '-u', MBED_ORG_USER]
161             if commit_msg:
162                 args = args + ['-m', commit_msg]
163             self.run_and_print(args, cwd=self.path)
164             if push_remote:
165                 self.run_and_print(['hg', 'push'], cwd=self.path)
166         return True
167
168 # Check if a file is a text file or a binary file
169 # Taken from http://code.activestate.com/recipes/173220/
170 text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
171 _null_trans = string.maketrans("", "")
172 def is_text_file(filename):
173     block_size = 1024
174     def istext(s):
175         if "\0" in s:
176             return 0
177
178         if not s:  # Empty files are considered text
179             return 1
180
181         # Get the non-text characters (maps a character to itself then
182         # use the 'remove' option to get rid of the text characters.)
183         t = s.translate(_null_trans, text_characters)
184
185         # If more than 30% non-text characters, then
186         # this is considered a binary file
187         if float(len(t))/len(s) > 0.30:
188             return 0
189         return 1
190     with open(filename) as f:
191         res = istext(f.read(block_size))
192     return res
193
194 # Return the line ending type for the given file ('cr' or 'crlf')
195 def get_line_endings(f):
196   examine_size = 1024
197   try:
198     tf = open(f, "rb")
199     lines, ncrlf = tf.readlines(examine_size), 0
200     tf.close()
201     for l in lines:
202       if l.endswith("\r\n"):
203         ncrlf = ncrlf + 1
204     return 'crlf' if ncrlf > len(lines) >> 1 else 'cr'
205   except:
206     return 'cr'
207
208 # Copy file to destination, but preserve destination line endings if possible
209 # This prevents very annoying issues with huge diffs that appear because of
210 # differences in line endings
211 def copy_with_line_endings(sdk_file, repo_file):
212     if not isfile(repo_file):
213         copyfile(sdk_file, repo_file)
214         return
215     is_text = is_text_file(repo_file)
216     if is_text:
217         sdk_le = get_line_endings(sdk_file)
218         repo_le = get_line_endings(repo_file)
219     if not is_text or sdk_le == repo_le:
220         copyfile(sdk_file, repo_file)
221     else:
222         print "Converting line endings in '%s' to '%s'" % (abspath(repo_file), repo_le)
223         f = open(sdk_file, "rb")
224         data = f.read()
225         f.close()
226         f = open(repo_file, "wb")
227         data = data.replace("\r\n", "\n") if repo_le == 'cr' else data.replace('\n','\r\n')
228         f.write(data)
229         f.close()
230
231 def visit_files(path, visit):
232     for root, dirs, files in walk(path):
233         # Ignore hidden directories
234         for d in copy(dirs):
235             full = join(root, d)
236             if d.startswith('.'):
237                 dirs.remove(d)
238             if ignore_path(full, IGNORE_DIRS):
239                 print "Skipping '%s'" % full
240                 dirs.remove(d)
241
242         for file in files:
243             if ignore_path(file, IGNORE_FILES):
244                 continue
245
246             visit(join(root, file))
247
248
249 def update_repo(repo_name, sdk_paths, team_name):
250     repo = MbedRepository(repo_name, team_name)
251     # copy files from mbed SDK to mbed_official repository
252     def visit_mbed_sdk(sdk_file):
253         repo_file = join(repo.path, relpath(sdk_file, sdk_path))
254
255         repo_dir = dirname(repo_file)
256         if not exists(repo_dir):
257             makedirs(repo_dir)
258
259         copy_with_line_endings(sdk_file, repo_file)
260     for sdk_path in sdk_paths:
261         visit_files(sdk_path, visit_mbed_sdk)
262
263     # remove repository files that do not exist in the mbed SDK
264     def visit_repo(repo_file):
265         for sdk_path in sdk_paths:
266             sdk_file = join(sdk_path, relpath(repo_file, repo.path))
267             if exists(sdk_file):
268                 break
269         else:
270             remove(repo_file)
271             print "remove: %s" % repo_file
272     visit_files(repo.path, visit_repo)
273
274     if repo.publish():
275         changed.append(repo_name)
276
277
278 def update_code(repositories):
279     for r in repositories:
280         repo_name, sdk_dir = r[0], r[1]
281         team_name = r[2] if len(r) == 3 else None
282         print '\n=== Updating "%s" ===' % repo_name
283         sdk_dirs = [sdk_dir] if type(sdk_dir) != type([]) else sdk_dir
284         sdk_path = [join(LIB_DIR, d) for d in sdk_dirs]
285         update_repo(repo_name, sdk_path, team_name)
286
287 def update_single_repo(repo):
288     repos = [r for r in OFFICIAL_CODE if r[0] == repo]
289     if not repos:
290         print "Repository '%s' not found" % repo
291     else:
292         update_code(repos)
293
294 def update_dependencies(repositories):
295     for repo_name in repositories:
296         print '\n=== Updating "%s" ===' % repo_name
297         repo = MbedRepository(repo_name)
298
299         # point to the latest libraries
300         def visit_repo(repo_file):
301             with open(repo_file, "r") as f:
302                 url = f.read()
303             with open(repo_file, "w") as f:
304                 f.write(url[:(url.rindex('/')+1)])
305         visit_files(repo.path, visit_repo, None, MBED_REPO_EXT)
306
307         if repo.publish():
308             changed.append(repo_name)
309
310
311 def update_mbed():
312     update_repo("mbed", [join(BUILD_DIR, "mbed")], None)
313
314 def do_sync(options):
315     global push_remote, quiet, commit_msg, changed
316
317     push_remote = not options.nopush
318     quiet = options.quiet
319     commit_msg = options.msg
320     chnaged = []
321
322     if options.code:
323         update_code(OFFICIAL_CODE)
324
325     if options.dependencies:
326         update_dependencies(CODE_WITH_DEPENDENCIES)
327
328     if options.mbed:
329         update_mbed()
330
331     if options.repo:
332         update_single_repo(options.repo)
333
334     if changed:
335         print "Repositories with changes:", changed
336
337     return changed
338
339 if __name__ == '__main__':
340     parser = OptionParser()
341
342     parser.add_option("-c", "--code",
343                   action="store_true",  default=False,
344                   help="Update the mbed_official code")
345
346     parser.add_option("-d", "--dependencies",
347                   action="store_true",  default=False,
348                   help="Update the mbed_official code dependencies")
349
350     parser.add_option("-m", "--mbed",
351                   action="store_true",  default=False,
352                   help="Release a build of the mbed library")
353
354     parser.add_option("-n", "--nopush",
355                   action="store_true", default=False,
356                   help="Commit the changes locally only, don't push them")
357
358     parser.add_option("", "--commit_message",
359                   action="store", type="string", default='', dest='msg',
360                   help="Commit message to use for all the commits")
361
362     parser.add_option("-r", "--repository",
363                   action="store", type="string", default='', dest='repo',
364                   help="Synchronize only the given repository")
365
366     parser.add_option("-q", "--quiet",
367                   action="store_true", default=False,
368                   help="Don't ask for confirmation before commiting or pushing")
369
370     (options, args) = parser.parse_args()
371
372     do_sync(options)
373