blob: ba4a5fc2f0467655f7f9cb146a0ce479453c7952 [file] [log] [blame]
Anthony Kingb8607632015-05-01 22:06:37 +03001#!/usr/bin/env python
Marco Brohetcb5cdb42014-07-11 22:41:53 +02002# -*- coding: utf-8 -*-
Michael Bestas1ab959b2014-07-26 16:01:01 +03003# crowdin_sync.py
Marco Brohetcb5cdb42014-07-11 22:41:53 +02004#
5# Updates Crowdin source translations and pushes translations
Abhisek Devkotab78def42016-12-27 13:06:52 -08006# directly to LineageOS' Gerrit.
Marco Brohetcb5cdb42014-07-11 22:41:53 +02007#
Michael Bestaseb4629a2018-11-14 23:03:18 +02008# Copyright (C) 2014-2016 The CyanogenMod Project
Michael W1bb2f922019-02-27 17:46:28 +01009# Copyright (C) 2017-2019 The LineageOS Project
Marco Brohetcb5cdb42014-07-11 22:41:53 +020010#
11# Licensed under the Apache License, Version 2.0 (the "License");
12# you may not use this file except in compliance with the License.
13# You may obtain a copy of the License at
14#
15# http://www.apache.org/licenses/LICENSE-2.0
16#
17# Unless required by applicable law or agreed to in writing, software
18# distributed under the License is distributed on an "AS IS" BASIS,
19# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20# See the License for the specific language governing permissions and
21# limitations under the License.
22
Anthony Kingb8607632015-05-01 22:06:37 +030023# ################################# IMPORTS ################################## #
24
25from __future__ import print_function
Marco Brohet6b6b4e52014-07-20 00:05:16 +020026
27import argparse
Michael W6e0a7032019-02-27 17:04:16 +010028import json
Marco Brohetcb5cdb42014-07-11 22:41:53 +020029import git
30import os
Michael Wbfa2f952019-03-10 19:53:10 +010031import re
Michael Wb26b8662019-08-10 23:26:59 +020032import shutil
Marco Brohetcb5cdb42014-07-11 22:41:53 +020033import subprocess
34import sys
Michael W1bb2f922019-02-27 17:46:28 +010035import yaml
Anthony Kingb8607632015-05-01 22:06:37 +030036
Michael W2ae05622019-02-28 15:27:22 +010037from lxml import etree
Michael We3af0fc2020-01-19 12:51:55 +010038from signal import signal, SIGINT
Marco Brohetcb5cdb42014-07-11 22:41:53 +020039
Anthony Kingd0d56cf2015-06-05 10:48:38 +010040# ################################# GLOBALS ################################## #
41
42_DIR = os.path.dirname(os.path.realpath(__file__))
Tom Powell44256852016-07-06 15:23:25 -070043_COMMITS_CREATED = False
Anthony Kingd0d56cf2015-06-05 10:48:38 +010044
Anthony Kingb8607632015-05-01 22:06:37 +030045# ################################ FUNCTIONS ################################# #
46
47
48def run_subprocess(cmd, silent=False):
49 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
50 universal_newlines=True)
51 comm = p.communicate()
52 exit_code = p.returncode
53 if exit_code != 0 and not silent:
54 print("There was an error running the subprocess.\n"
55 "cmd: %s\n"
56 "exit code: %d\n"
57 "stdout: %s\n"
58 "stderr: %s" % (cmd, exit_code, comm[0], comm[1]),
59 file=sys.stderr)
60 return comm, exit_code
61
Marco Brohet6b6b4e52014-07-20 00:05:16 +020062
Michael W2ae05622019-02-28 15:27:22 +010063def add_target_paths(config_files, repo, base_path, project_path):
Michael W1bb2f922019-02-27 17:46:28 +010064 # Add or remove the files given in the config files to the commit
65 count = 0
66 file_paths = []
67 for f in config_files:
68 fh = open(f, "r")
69 try:
Michael W6633d752020-02-02 13:04:39 +010070 config = yaml.safe_load(fh)
Michael W1bb2f922019-02-27 17:46:28 +010071 for tf in config['files']:
72 if project_path in tf['source']:
73 target_path = tf['translation']
74 lang_codes = tf['languages_mapping']['android_code']
75 for l in lang_codes:
76 lpath = get_target_path(tf['translation'], tf['source'],
77 lang_codes[l], project_path)
78 file_paths.append(lpath)
79 except yaml.YAMLError as e:
80 print(e, '\n Could not parse YAML.')
81 exit()
82 fh.close()
83
Michael W2ae05622019-02-28 15:27:22 +010084 # Strip all comments
85 for f in file_paths:
Michael Wb26b8662019-08-10 23:26:59 +020086 clean_xml_file(base_path, project_path, f, repo)
Michael W2ae05622019-02-28 15:27:22 +010087
88 # Modified and untracked files
89 modified = repo.git.ls_files(m=True, o=True)
Michael W1bb2f922019-02-27 17:46:28 +010090 for m in modified.split('\n'):
91 if m in file_paths:
92 repo.git.add(m)
93 count += 1
94
95 deleted = repo.git.ls_files(d=True)
96 for d in deleted.split('\n'):
97 if d in file_paths:
98 repo.git.rm(d)
99 count += 1
100
101 return count
102
103
104def split_path(path):
105 # Split the given string to path and filename
106 if '/' in path:
107 original_file_name = path[1:][path.rfind("/"):]
108 original_path = path[:path.rfind("/")]
109 else:
110 original_file_name = path
111 original_path = ''
112
113 return original_path, original_file_name
114
115
116def get_target_path(pattern, source, lang, project_path):
117 # Make strings like '/%original_path%-%android_code%/%original_file_name%' valid file paths
118 # based on the source string's path
119 original_path, original_file_name = split_path(source)
120
121 target_path = pattern #.lstrip('/')
122 target_path = target_path.replace('%original_path%', original_path)
123 target_path = target_path.replace('%android_code%', lang)
124 target_path = target_path.replace('%original_file_name%', original_file_name)
125 target_path = target_path.replace(project_path, '')
126 target_path = target_path.lstrip('/')
127 return target_path
128
129
Michael Wb26b8662019-08-10 23:26:59 +0200130def clean_xml_file(base_path, project_path, filename, repo):
Michael W2ae05622019-02-28 15:27:22 +0100131 path = base_path + '/' + project_path + '/' + filename
132
133 # We don't want to create every file, just work with those already existing
134 if not os.path.isfile(path):
135 return
136
137 try:
138 fh = open(path, 'r+')
139 except:
140 print('Something went wrong while opening file %s' % (path))
141 return
142
143 XML = fh.read()
Michael Wb26b8662019-08-10 23:26:59 +0200144 try:
145 tree = etree.fromstring(XML)
146 except etree.XMLSyntaxError as err:
147 print('%s: XML Error: %s' % (filename, err.error_log))
148 filename, ext = os.path.splitext(path)
149 if ext == '.xml':
150 reset_file(path, repo)
151 return
Michael W2ae05622019-02-28 15:27:22 +0100152
Michael Wb1587982019-06-19 15:29:24 +0200153 # Remove strings with 'product=*' attribute but no 'product=default'
154 # This will ensure aapt2 will not throw an error when building these
155 productStrings = tree.xpath("//string[@product]")
156 for ps in productStrings:
157 stringName = ps.get('name')
158 stringsWithSameName = tree.xpath("//string[@name='{0}']"
159 .format(stringName))
160
161 # We want to find strings with product='default' or no product attribute at all
162 hasProductDefault = False
163 for string in stringsWithSameName:
164 product = string.get('product')
165 if product is None or product == 'default':
166 hasProductDefault = True
167 break
168
169 # Every occurance of the string has to be removed when no string with the same name and
170 # 'product=default' (or no product attribute) was found
171 if not hasProductDefault:
172 print("{0}: Found string '{1}' with missing 'product=default' attribute"
173 .format(path, stringName))
174 for string in stringsWithSameName:
175 tree.remove(string)
176 productStrings.remove(string)
177
Michael W2ae05622019-02-28 15:27:22 +0100178 header = ''
179 comments = tree.xpath('//comment()')
180 for c in comments:
181 p = c.getparent()
182 if p is None:
183 # Keep all comments in header
184 header += str(c).replace('\\n', '\n').replace('\\t', '\t') + '\n'
185 continue
186 p.remove(c)
187
188 content = ''
189
190 # Take the original xml declaration and prepend it
191 declaration = XML.split('\n')[0]
192 if '<?' in declaration:
193 content = declaration + '\n'
194
195 content += etree.tostring(tree, pretty_print=True, encoding="utf-8", xml_declaration=False)
196
197 if header != '':
198 content = content.replace('?>\n', '?>\n' + header)
199
200 # Sometimes spaces are added, we don't want them
201 content = re.sub("[ ]*<\/resources>", "</resources>", content)
202
203 # Overwrite file with content stripped by all comments
204 fh.seek(0)
205 fh.write(content)
206 fh.truncate()
207 fh.close()
208
209 # Remove files which don't have any translated strings
Michael Wed3af932019-06-19 22:41:09 +0200210 contentList = list(tree)
211 if len(contentList) == 0:
212 print('Removing ' + path)
213 os.remove(path)
Michael W2ae05622019-02-28 15:27:22 +0100214
Michael Wb26b8662019-08-10 23:26:59 +0200215
216# For files we can't process due to errors, create a backup
217# and checkout the file to get it back to the previous state
218def reset_file(filepath, repo):
219 backupFile = None
220 parts = filepath.split("/")
221 found = False
222 for s in parts:
223 curPart = s
224 if not found and s.startswith("res"):
225 curPart = s + "_backup"
226 found = True
227 if backupFile is None:
228 backupFile = curPart
229 else:
230 backupFile = backupFile + '/' + curPart
231
232 path, filename = os.path.split(backupFile)
233 if not os.path.exists(path):
234 os.makedirs(path)
235 if os.path.exists(backupFile):
236 i = 1
237 while os.path.exists(backupFile + str(i)):
238 i+=1
239 backupFile = backupFile + str(i)
240 shutil.copy(filepath, backupFile)
241 repo.git.checkout(filepath)
242
243
Michael W1bb2f922019-02-27 17:46:28 +0100244def push_as_commit(config_files, base_path, path, name, branch, username):
Michael We3af0fc2020-01-19 12:51:55 +0100245 print('Committing %s on branch %s: ' % (name, branch), end='')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200246
247 # Get path
Michael W1bb2f922019-02-27 17:46:28 +0100248 project_path = path
Michael Bestas118fcaf2015-06-04 23:02:20 +0300249 path = os.path.join(base_path, path)
Anthony Kingb8607632015-05-01 22:06:37 +0300250 if not path.endswith('.git'):
251 path = os.path.join(path, '.git')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200252
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200253 # Create repo object
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200254 repo = git.Repo(path)
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200255
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200256 # Add all files to commit
Michael W2ae05622019-02-28 15:27:22 +0100257 count = add_target_paths(config_files, repo, base_path, project_path)
Michael W1bb2f922019-02-27 17:46:28 +0100258
259 if count == 0:
260 print('Nothing to commit')
261 return
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200262
263 # Create commit; if it fails, probably empty so skipping
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200264 try:
Michael Bestas80b22ef2018-11-14 23:12:33 +0200265 repo.git.commit(m='Automatic translation import')
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200266 except:
Michael We3af0fc2020-01-19 12:51:55 +0100267 print('Failed, probably empty: skipping', file=sys.stderr)
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200268 return
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200269
270 # Push commit
Michael Bestasf96f67b2014-10-21 00:43:37 +0300271 try:
Abhisek Devkotab78def42016-12-27 13:06:52 -0800272 repo.git.push('ssh://%s@review.lineageos.org:29418/%s' % (username, name),
Anthony Kingb8607632015-05-01 22:06:37 +0300273 'HEAD:refs/for/%s%%topic=translation' % branch)
Michael We3af0fc2020-01-19 12:51:55 +0100274 print('Success')
Michael Bestasf96f67b2014-10-21 00:43:37 +0300275 except:
Michael We3af0fc2020-01-19 12:51:55 +0100276 print('Failed', file=sys.stderr)
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200277
Tom Powell44256852016-07-06 15:23:25 -0700278 _COMMITS_CREATED = True
279
Anthony Kingb8607632015-05-01 22:06:37 +0300280
Michael Wd13658a2019-01-13 14:05:37 +0100281def submit_gerrit(branch, username):
282 # Find all open translation changes
283 cmd = ['ssh', '-p', '29418',
284 '{}@review.lineageos.org'.format(username),
285 'gerrit', 'query',
286 'status:open',
287 'branch:{}'.format(branch),
288 'message:"Automatic translation import"',
289 'topic:translation',
Michael W6e0a7032019-02-27 17:04:16 +0100290 '--current-patch-set',
291 '--format=JSON']
292 commits = 0
Michael Wd13658a2019-01-13 14:05:37 +0100293 msg, code = run_subprocess(cmd)
294 if code != 0:
295 print('Failed: {0}'.format(msg[1]))
296 return
297
Michael W6e0a7032019-02-27 17:04:16 +0100298 # Each line is one valid JSON object, except the last one, which is empty
299 for line in msg[0].strip('\n').split('\n'):
300 js = json.loads(line)
301 # We get valid JSON, but not every result line is one we want
302 if not 'currentPatchSet' in js or not 'revision' in js['currentPatchSet']:
303 continue
Michael Wd13658a2019-01-13 14:05:37 +0100304 # Add Code-Review +2 and Verified+1 labels and submit
305 cmd = ['ssh', '-p', '29418',
306 '{}@review.lineageos.org'.format(username),
307 'gerrit', 'review',
308 '--verified +1',
309 '--code-review +2',
Michael W6e0a7032019-02-27 17:04:16 +0100310 '--submit', js['currentPatchSet']['revision']]
Michael Wd13658a2019-01-13 14:05:37 +0100311 msg, code = run_subprocess(cmd, True)
Michael We3af0fc2020-01-19 12:51:55 +0100312 print('Submitting commit %s: ' % js[url], end='')
Michael Wd13658a2019-01-13 14:05:37 +0100313 if code != 0:
314 errorText = msg[1].replace('\n\n', '; ').replace('\n', '')
Michael We3af0fc2020-01-19 12:51:55 +0100315 print('Failed: %s' % errorText)
Michael Wd13658a2019-01-13 14:05:37 +0100316 else:
Michael We3af0fc2020-01-19 12:51:55 +0100317 print('Success')
Michael W6e0a7032019-02-27 17:04:16 +0100318
319 commits += 1
320
321 if commits == 0:
322 print("Nothing to submit!")
323 return
Michael Wd13658a2019-01-13 14:05:37 +0100324
325
Anthony Kingb8607632015-05-01 22:06:37 +0300326def check_run(cmd):
Michael Bestas97677e12015-02-08 13:11:59 +0200327 p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
328 ret = p.wait()
329 if ret != 0:
Anthony Kingb8607632015-05-01 22:06:37 +0300330 print('Failed to run cmd: %s' % ' '.join(cmd), file=sys.stderr)
Michael Bestas97677e12015-02-08 13:11:59 +0200331 sys.exit(ret)
332
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200333
Michael Bestas118fcaf2015-06-04 23:02:20 +0300334def find_xml(base_path):
335 for dp, dn, file_names in os.walk(base_path):
Anthony Kingb8607632015-05-01 22:06:37 +0300336 for f in file_names:
337 if os.path.splitext(f)[1] == '.xml':
338 yield os.path.join(dp, f)
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200339
Anthony Kingb8607632015-05-01 22:06:37 +0300340# ############################################################################ #
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200341
Michael Bestas6b6db122015-02-08 13:22:22 +0200342
Anthony Kingb8607632015-05-01 22:06:37 +0300343def parse_args():
344 parser = argparse.ArgumentParser(
Abhisek Devkotab78def42016-12-27 13:06:52 -0800345 description="Synchronising LineageOS' translations with Crowdin")
Michael Bestasfd5d1362015-12-18 20:34:32 +0200346 parser.add_argument('-u', '--username', help='Gerrit username')
Abhisek Devkotab78def42016-12-27 13:06:52 -0800347 parser.add_argument('-b', '--branch', help='LineageOS branch',
Anthony Kingb8607632015-05-01 22:06:37 +0300348 required=True)
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300349 parser.add_argument('-c', '--config', help='Custom yaml config')
Michael Bestasfd5d1362015-12-18 20:34:32 +0200350 parser.add_argument('--upload-sources', action='store_true',
351 help='Upload sources to Crowdin')
352 parser.add_argument('--upload-translations', action='store_true',
353 help='Upload translations to Crowdin')
354 parser.add_argument('--download', action='store_true',
355 help='Download translations from Crowdin')
Michael Wd13658a2019-01-13 14:05:37 +0100356 parser.add_argument('-s', '--submit', action='store_true',
357 help='Merge open translation commits')
Anthony Kingb8607632015-05-01 22:06:37 +0300358 return parser.parse_args()
Michael Bestas6b6db122015-02-08 13:22:22 +0200359
Anthony Kingb8607632015-05-01 22:06:37 +0300360# ################################# PREPARE ################################## #
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200361
Anthony Kingb8607632015-05-01 22:06:37 +0300362
363def check_dependencies():
Michael Bestaseb4629a2018-11-14 23:03:18 +0200364 # Check for Java version of crowdin
365 cmd = ['dpkg-query', '-W', 'crowdin']
Anthony Kingb8607632015-05-01 22:06:37 +0300366 if run_subprocess(cmd, silent=True)[1] != 0:
Michael Bestaseb4629a2018-11-14 23:03:18 +0200367 print('You have not installed crowdin.', file=sys.stderr)
Anthony Kingb8607632015-05-01 22:06:37 +0300368 return False
Anthony Kingb8607632015-05-01 22:06:37 +0300369 return True
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200370
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200371
Michael Bestas118fcaf2015-06-04 23:02:20 +0300372def load_xml(x):
Anthony Kingb8607632015-05-01 22:06:37 +0300373 try:
Michael Wda610ba2020-03-22 18:38:19 +0100374 return etree.parse(x)
375 except etree.XMLSyntaxError:
376 print('Malformed %s.' % x, file=sys.stderr)
Anthony Kingb8607632015-05-01 22:06:37 +0300377 return None
378 except Exception:
Michael Wda610ba2020-03-22 18:38:19 +0100379 print('You have no %s.' % x, file=sys.stderr)
Anthony Kingb8607632015-05-01 22:06:37 +0300380 return None
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200381
Michael Bestas4b26c4e2014-10-23 23:21:59 +0300382
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300383def check_files(files):
Anthony Kingb8607632015-05-01 22:06:37 +0300384 for f in files:
385 if not os.path.isfile(f):
386 print('You have no %s.' % f, file=sys.stderr)
387 return False
Anthony Kingb8607632015-05-01 22:06:37 +0300388 return True
Michael Bestas4b26c4e2014-10-23 23:21:59 +0300389
Anthony Kingb8607632015-05-01 22:06:37 +0300390# ################################### MAIN ################################### #
Michael Bestas4b26c4e2014-10-23 23:21:59 +0300391
Michael Bestas4b26c4e2014-10-23 23:21:59 +0300392
Michael Bestasfd5d1362015-12-18 20:34:32 +0200393def upload_sources_crowdin(branch, config):
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300394 if config:
Michael Bestasfd5d1362015-12-18 20:34:32 +0200395 print('\nUploading sources to Crowdin (custom config)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200396 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200397 '--config=%s/config/%s' % (_DIR, config),
Michael Bestas44fbb352015-12-17 02:01:42 +0200398 'upload', 'sources', '--branch=%s' % branch])
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300399 else:
Michael Bestasfd5d1362015-12-18 20:34:32 +0200400 print('\nUploading sources to Crowdin (AOSP supported languages)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200401 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200402 '--config=%s/config/%s.yaml' % (_DIR, branch),
Michael Bestas44fbb352015-12-17 02:01:42 +0200403 'upload', 'sources', '--branch=%s' % branch])
Anthony King69a95382015-02-08 18:44:10 +0000404
Michael Bestasfd5d1362015-12-18 20:34:32 +0200405 print('\nUploading sources to Crowdin (non-AOSP supported languages)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200406 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200407 '--config=%s/config/%s_aosp.yaml' % (_DIR, branch),
Michael Bestas44fbb352015-12-17 02:01:42 +0200408 'upload', 'sources', '--branch=%s' % branch])
Anthony Kingb8607632015-05-01 22:06:37 +0300409
410
Michael Bestasfd5d1362015-12-18 20:34:32 +0200411def upload_translations_crowdin(branch, config):
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300412 if config:
Michael Bestasfd5d1362015-12-18 20:34:32 +0200413 print('\nUploading translations to Crowdin (custom config)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200414 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200415 '--config=%s/config/%s' % (_DIR, config),
Michael Bestasfd5d1362015-12-18 20:34:32 +0200416 'upload', 'translations', '--branch=%s' % branch,
417 '--no-import-duplicates', '--import-eq-suggestions',
418 '--auto-approve-imported'])
419 else:
420 print('\nUploading translations to Crowdin '
421 '(AOSP supported languages)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200422 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200423 '--config=%s/config/%s.yaml' % (_DIR, branch),
Michael Bestasfd5d1362015-12-18 20:34:32 +0200424 'upload', 'translations', '--branch=%s' % branch,
425 '--no-import-duplicates', '--import-eq-suggestions',
426 '--auto-approve-imported'])
427
428 print('\nUploading translations to Crowdin '
429 '(non-AOSP supported languages)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200430 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200431 '--config=%s/config/%s_aosp.yaml' % (_DIR, branch),
Michael Bestasfd5d1362015-12-18 20:34:32 +0200432 'upload', 'translations', '--branch=%s' % branch,
433 '--no-import-duplicates', '--import-eq-suggestions',
434 '--auto-approve-imported'])
435
436
Michael Bestas80b22ef2018-11-14 23:12:33 +0200437def download_crowdin(base_path, branch, xml, username, config):
Michael Bestasfd5d1362015-12-18 20:34:32 +0200438 if config:
439 print('\nDownloading translations from Crowdin (custom config)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200440 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200441 '--config=%s/config/%s' % (_DIR, config),
Michael Bestas44fbb352015-12-17 02:01:42 +0200442 'download', '--branch=%s' % branch])
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300443 else:
Michael Bestasfd5d1362015-12-18 20:34:32 +0200444 print('\nDownloading translations from Crowdin '
445 '(AOSP supported languages)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200446 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200447 '--config=%s/config/%s.yaml' % (_DIR, branch),
Michael Bestas44fbb352015-12-17 02:01:42 +0200448 'download', '--branch=%s' % branch])
Michael Bestas50579d22014-08-09 17:49:14 +0300449
Michael Bestasfd5d1362015-12-18 20:34:32 +0200450 print('\nDownloading translations from Crowdin '
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300451 '(non-AOSP supported languages)')
Michael Bestaseb4629a2018-11-14 23:03:18 +0200452 check_run(['crowdin',
Michael Bestas03bc7052016-03-12 03:19:10 +0200453 '--config=%s/config/%s_aosp.yaml' % (_DIR, branch),
Michael Bestas44fbb352015-12-17 02:01:42 +0200454 'download', '--branch=%s' % branch])
Marco Brohetcb5cdb42014-07-11 22:41:53 +0200455
Michael Bestas99f5fce2015-06-04 22:07:51 +0300456 print('\nCreating a list of pushable translations')
Michael Bestas919053f2014-10-20 23:30:54 +0300457 # Get all files that Crowdin pushed
Anthony Kingb8607632015-05-01 22:06:37 +0300458 paths = []
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300459 if config:
Michael Bestas03bc7052016-03-12 03:19:10 +0200460 files = ['%s/config/%s' % (_DIR, config)]
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300461 else:
Michael Bestas03bc7052016-03-12 03:19:10 +0200462 files = ['%s/config/%s.yaml' % (_DIR, branch),
463 '%s/config/%s_aosp.yaml' % (_DIR, branch)]
Michael Bestas6c327e62015-05-02 01:58:01 +0300464 for c in files:
Michael Bestaseb4629a2018-11-14 23:03:18 +0200465 cmd = ['crowdin', '--config=%s' % c, 'list', 'project',
Michael Bestas44fbb352015-12-17 02:01:42 +0200466 '--branch=%s' % branch]
Anthony Kingb8607632015-05-01 22:06:37 +0300467 comm, ret = run_subprocess(cmd)
468 if ret != 0:
469 sys.exit(ret)
470 for p in str(comm[0]).split("\n"):
471 paths.append(p.replace('/%s' % branch, ''))
Michael Bestas50579d22014-08-09 17:49:14 +0300472
Michael Bestas99f5fce2015-06-04 22:07:51 +0300473 print('\nUploading translations to Gerrit')
Michael Wda610ba2020-03-22 18:38:19 +0100474 items = [x for xmlfile in xml for x in xmlfile.findall("//project")]
Michael Bestas919053f2014-10-20 23:30:54 +0300475 all_projects = []
476
Anthony Kingb8607632015-05-01 22:06:37 +0300477 for path in paths:
478 path = path.strip()
Michael Bestas919053f2014-10-20 23:30:54 +0300479 if not path:
480 continue
481
Anthony Kingb8607632015-05-01 22:06:37 +0300482 if "/res" not in path:
483 print('WARNING: Cannot determine project root dir of '
484 '[%s], skipping.' % path)
Anthony King69a95382015-02-08 18:44:10 +0000485 continue
Michael W67f14932019-03-11 09:59:32 +0100486
487 # Usually the project root is everything before /res
488 # but there are special cases where /res is part of the repo name as well
489 parts = path.split("/res")
490 if len(parts) == 2:
491 result = parts[0]
492 elif len(parts) == 3:
493 result = parts[0] + '/res' + parts[1]
494 else:
495 print('WARNING: Splitting the path not successful for [%s], skipping' % path)
496 continue
497
498 result = result.strip('/')
Anthony Kingb8607632015-05-01 22:06:37 +0300499 if result == path.strip('/'):
500 print('WARNING: Cannot determine project root dir of '
501 '[%s], skipping.' % path)
502 continue
Marco Brohet6b6b4e52014-07-20 00:05:16 +0200503
Michael Bestasc899b8c2015-03-03 00:53:19 +0200504 if result in all_projects:
Michael Bestasc899b8c2015-03-03 00:53:19 +0200505 continue
Michael Bestas50579d22014-08-09 17:49:14 +0300506
Anthony Kingb8607632015-05-01 22:06:37 +0300507 # When a project has multiple translatable files, Crowdin will
508 # give duplicates.
509 # We don't want that (useless empty commits), so we save each
510 # project in all_projects and check if it's already in there.
Michael Bestasc899b8c2015-03-03 00:53:19 +0200511 all_projects.append(result)
Anthony King69a95382015-02-08 18:44:10 +0000512
Michael Bestas42e25e32016-03-12 20:18:39 +0200513 # Search android/default.xml or config/%(branch)_extra_packages.xml
Anthony Kingb8607632015-05-01 22:06:37 +0300514 # for the project's name
Michael W1f187762019-03-11 19:38:00 +0100515 resultPath = None
516 resultProject = None
Anthony Kingb8607632015-05-01 22:06:37 +0300517 for project in items:
Michael Wda610ba2020-03-22 18:38:19 +0100518 path = project.get('path')
Anthony Kingb8607632015-05-01 22:06:37 +0300519 if not (result + '/').startswith(path +'/'):
Michael Bestasc899b8c2015-03-03 00:53:19 +0200520 continue
Michael W1f187762019-03-11 19:38:00 +0100521 # We want the longest match, so projects in subfolders of other projects are also
522 # taken into account
523 if resultPath is None or len(path) > len(resultPath):
524 resultPath = path
525 resultProject = project
Anthony King69a95382015-02-08 18:44:10 +0000526
Michael W1f187762019-03-11 19:38:00 +0100527 # Just in case no project was found
528 if resultPath is None:
529 continue
Anthony King69a95382015-02-08 18:44:10 +0000530
Michael W1f187762019-03-11 19:38:00 +0100531 if result != resultPath:
532 if resultPath in all_projects:
533 continue
534 result = resultPath
535 all_projects.append(result)
536
Michael Wda610ba2020-03-22 18:38:19 +0100537 br = resultProject.get('revision') or branch
Michael W1f187762019-03-11 19:38:00 +0100538
539 push_as_commit(files, base_path, result,
Michael Wda610ba2020-03-22 18:38:19 +0100540 resultProject.get('name'), br, username)
Anthony King69a95382015-02-08 18:44:10 +0000541
Anthony King69a95382015-02-08 18:44:10 +0000542
Michael We3af0fc2020-01-19 12:51:55 +0100543def sig_handler(signal_received, frame):
544 print('')
545 print('SIGINT or CTRL-C detected. Exiting gracefully')
546 exit(0)
547
548
Anthony Kingb8607632015-05-01 22:06:37 +0300549def main():
Michael We3af0fc2020-01-19 12:51:55 +0100550 signal(SIGINT, sig_handler)
Anthony Kingb8607632015-05-01 22:06:37 +0300551 args = parse_args()
552 default_branch = args.branch
Michael Bestas118fcaf2015-06-04 23:02:20 +0300553
Michael Wd13658a2019-01-13 14:05:37 +0100554 if args.submit:
555 if args.username is None:
556 print('Argument -u/--username is required for submitting!')
557 sys.exit(1)
558 submit_gerrit(default_branch, args.username)
559 sys.exit(0)
560
Michael Bestaseb4629a2018-11-14 23:03:18 +0200561 base_path_branch_suffix = default_branch.replace('-', '_').replace('.', '_').upper()
562 base_path_env = 'LINEAGE_CROWDIN_BASE_PATH_%s' % base_path_branch_suffix
563 base_path = os.getenv(base_path_env)
Michael Bestas118fcaf2015-06-04 23:02:20 +0300564 if base_path is None:
Anthony Kingd0d56cf2015-06-05 10:48:38 +0100565 cwd = os.getcwd()
Michael Bestaseb4629a2018-11-14 23:03:18 +0200566 print('You have not set %s. Defaulting to %s' % (base_path_env, cwd))
Michael Bestas118fcaf2015-06-04 23:02:20 +0300567 base_path = cwd
Michael Bestas118fcaf2015-06-04 23:02:20 +0300568 if not os.path.isdir(base_path):
Michael Bestaseb4629a2018-11-14 23:03:18 +0200569 print('%s is not a real directory: %s' % (base_path_env, base_path))
Michael Bestas118fcaf2015-06-04 23:02:20 +0300570 sys.exit(1)
Anthony Kingb8607632015-05-01 22:06:37 +0300571
Michael Bestas99f5fce2015-06-04 22:07:51 +0300572 if not check_dependencies():
573 sys.exit(1)
Anthony Kingb8607632015-05-01 22:06:37 +0300574
Michael Bestas118fcaf2015-06-04 23:02:20 +0300575 xml_android = load_xml(x='%s/android/default.xml' % base_path)
Anthony Kingb8607632015-05-01 22:06:37 +0300576 if xml_android is None:
577 sys.exit(1)
578
Michael Bestas42e25e32016-03-12 20:18:39 +0200579 xml_extra = load_xml(x='%s/config/%s_extra_packages.xml'
Anthony Kingd0d56cf2015-06-05 10:48:38 +0100580 % (_DIR, default_branch))
Anthony Kingb8607632015-05-01 22:06:37 +0300581 if xml_extra is None:
582 sys.exit(1)
583
Michael Bestas19dc3352018-02-03 20:24:00 +0200584 xml_snippet = load_xml(x='%s/android/snippets/lineage.xml' % base_path)
585 if xml_snippet is None:
586 xml_snippet = load_xml(x='%s/android/snippets/cm.xml' % base_path)
587 if xml_snippet is None:
588 xml_snippet = load_xml(x='%s/android/snippets/hal_cm_all.xml' % base_path)
589 if xml_snippet is not None:
590 xml_files = (xml_android, xml_snippet, xml_extra)
Michael Bestas687679f2016-12-07 23:20:12 +0200591 else:
592 xml_files = (xml_android, xml_extra)
593
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300594 if args.config:
Michael Bestas03bc7052016-03-12 03:19:10 +0200595 files = ['%s/config/%s' % (_DIR, args.config)]
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300596 else:
Michael Bestas03bc7052016-03-12 03:19:10 +0200597 files = ['%s/config/%s.yaml' % (_DIR, default_branch),
598 '%s/config/%s_aosp.yaml' % (_DIR, default_branch)]
Michael Bestas2f8c4a52015-08-05 21:33:50 +0300599 if not check_files(files):
Anthony Kingb8607632015-05-01 22:06:37 +0300600 sys.exit(1)
601
Michael Bestasfd5d1362015-12-18 20:34:32 +0200602 if args.download and args.username is None:
603 print('Argument -u/--username is required for translations download')
604 sys.exit(1)
605
606 if args.upload_sources:
607 upload_sources_crowdin(default_branch, args.config)
608 if args.upload_translations:
609 upload_translations_crowdin(default_branch, args.config)
610 if args.download:
Michael Bestas687679f2016-12-07 23:20:12 +0200611 download_crowdin(base_path, default_branch, xml_files,
Michael Bestas80b22ef2018-11-14 23:12:33 +0200612 args.username, args.config)
Tom Powell44256852016-07-06 15:23:25 -0700613
614 if _COMMITS_CREATED:
615 print('\nDone!')
616 sys.exit(0)
Tom Powellf42586f2016-07-11 11:02:54 -0700617 else:
Tom Powell44256852016-07-06 15:23:25 -0700618 print('\nNothing to commit')
619 sys.exit(-1)
Anthony Kingb8607632015-05-01 22:06:37 +0300620
621if __name__ == '__main__':
622 main()