Revert "Revert "crowdin_sync: Optimise!""
This reverts commit c899b8c5960abf40cfc60862f79e857a904ba84e.
Change-Id: I2f84197e546f3d07545ad4e1eef61f7403bda918
diff --git a/crowdin_sync.py b/crowdin_sync.py
index 2dd16b0..04cf3d5 100755
--- a/crowdin_sync.py
+++ b/crowdin_sync.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
# crowdin_sync.py
#
@@ -19,37 +19,51 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-############################################# IMPORTS ##############################################
+# ################################# IMPORTS ################################## #
+
+from __future__ import print_function
import argparse
-import codecs
import git
import os
-import os.path
-import re
-import shutil
import subprocess
import sys
-from urllib import urlretrieve
+
from xml.dom import minidom
-############################################ FUNCTIONS #############################################
+# ################################ FUNCTIONS ################################# #
+
+
+def run_subprocess(cmd, silent=False):
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=True)
+ comm = p.communicate()
+ exit_code = p.returncode
+ if exit_code != 0 and not silent:
+ print("There was an error running the subprocess.\n"
+ "cmd: %s\n"
+ "exit code: %d\n"
+ "stdout: %s\n"
+ "stderr: %s" % (cmd, exit_code, comm[0], comm[1]),
+ file=sys.stderr)
+ return comm, exit_code
+
def push_as_commit(path, name, branch, username):
- print('Committing ' + name + ' on branch ' + branch)
+ print('Committing %s on branch %s' % (name, branch))
# Get path
- path = os.getcwd() + '/' + path
+ path = os.path.join(os.getcwd(), path)
+ if not path.endswith('.git'):
+ path = os.path.join(path, '.git')
# Create repo object
repo = git.Repo(path)
# Remove previously deleted files from Git
- removed_files = repo.git.ls_files(d=True).split('\n')
- try:
- repo.git.rm(removed_files)
- except:
- pass
+ files = repo.git.ls_files(d=True).split('\n')
+ if files and files[0]:
+ repo.git.rm(files)
# Add all files to commit
repo.git.add('-A')
@@ -58,188 +72,246 @@
try:
repo.git.commit(m='Automatic translation import')
except:
- print('Failed to create commit for ' + name + ', probably empty: skipping')
+ print('Failed to create commit for %s, probably empty: skipping'
+ % name, file=sys.stderr)
return
# Push commit
try:
- repo.git.push('ssh://' + username + '@review.cyanogenmod.org:29418/' + name, 'HEAD:refs/for/' + branch + '%topic=translation')
- print('Succesfully pushed commit for ' + name)
+ repo.git.push('ssh://%s@review.cyanogenmod.org:29418/%s' % (username, name),
+ 'HEAD:refs/for/%s%%topic=translation' % branch)
+ print('Successfully pushed commit for %s' % name)
except:
- print('Failed to push commit for ' + name)
+ print('Failed to push commit for %s' % name, file=sys.stderr)
-def run_command(cmd):
+
+def check_run(cmd):
p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
ret = p.wait()
if ret != 0:
- print('Failed to run cmd: %s' % ' '.join(cmd))
+ print('Failed to run cmd: %s' % ' '.join(cmd), file=sys.stderr)
sys.exit(ret)
-####################################################################################################
-parser = argparse.ArgumentParser(description='Synchronising CyanogenMod\'s translations with Crowdin')
-sync = parser.add_mutually_exclusive_group()
-parser.add_argument('-u', '--username', help='Gerrit username', required=True)
-parser.add_argument('-b', '--branch', help='CyanogenMod branch', required=True)
-sync.add_argument('--no-upload', action='store_true', help='Only download CM translations from Crowdin')
-sync.add_argument('--no-download', action='store_true', help='Only upload CM source translations to Crowdin')
-args = parser.parse_args()
-argsdict = vars(args)
+def find_xml():
+ for dp, dn, file_names in os.walk(os.getcwd()):
+ for f in file_names:
+ if os.path.splitext(f)[1] == '.xml':
+ yield os.path.join(dp, f)
-username = argsdict['username']
-default_branch = argsdict['branch']
+# ############################################################################ #
-####################################################################################################
-print('Welcome to the CM Crowdin sync script!')
+def parse_args():
+ parser = argparse.ArgumentParser(
+ description="Synchronising CyanogenMod's translations with Crowdin")
+ sync = parser.add_mutually_exclusive_group()
+ parser.add_argument('-u', '--username', help='Gerrit username',
+ required=True)
+ parser.add_argument('-b', '--branch', help='CyanogenMod branch',
+ required=True)
+ sync.add_argument('--no-upload', action='store_true',
+ help='Only download CM translations from Crowdin')
+ sync.add_argument('--no-download', action='store_true',
+ help='Only upload CM source translations to Crowdin')
+ return parser.parse_args()
-############################################# PREPARE ##############################################
+# ################################# PREPARE ################################## #
-print('\nSTEP 0: Checking dependencies & define shared variables')
-# Check for Ruby version of crowdin-cli
-if subprocess.check_output(['rvm', 'all', 'do', 'gem', 'list', 'crowdin-cli', '-i']) == 'true':
- sys.exit('You have not installed crowdin-cli. Terminating.')
-else:
+
+def check_dependencies():
+ print('\nSTEP 0: Checking dependencies & define shared variables')
+
+ # Check for Ruby version of crowdin-cli
+ cmd = ['gem', 'list', 'crowdin-cli', '-i']
+ if run_subprocess(cmd, silent=True)[1] != 0:
+ print('You have not installed crowdin-cli.', file=sys.stderr)
+ return False
print('Found: crowdin-cli')
-# Check for repo
-try:
- subprocess.check_output(['which', 'repo'])
-except:
- sys.exit('You have not installed repo. Terminating.')
+ return True
-# Check for android/default.xml
-if not os.path.isfile('android/default.xml'):
- sys.exit('You have no android/default.xml. Terminating.')
-else:
- print('Found: android/default.xml')
-# Variables regarding android/default.xml
-print('Loading: android/default.xml')
-xml_android = minidom.parse('android/default.xml')
+def load_xml(x='android/default.xml'):
+ # Variables regarding android/default.xml
+ print('Loading: %s' % x)
+ try:
+ return minidom.parse(x)
+ except IOError:
+ print('You have no %s.' % x, file=sys.stderr)
+ return None
+ except Exception:
+ # TODO: minidom should not be used.
+ print('Malformed %s.' % x, file=sys.stderr)
+ return None
-# Check for crowdin/extra_packages_' + default_branch + '.xml
-if not os.path.isfile('crowdin/extra_packages_' + default_branch + '.xml'):
- sys.exit('You have no crowdin/extra_packages_' + default_branch + '.xml. Terminating.')
-else:
- print('Found: crowdin/extra_packages_' + default_branch + '.xml')
-# Check for crowdin/config.yaml
-if not os.path.isfile('crowdin/config.yaml'):
- sys.exit('You have no crowdin/config.yaml. Terminating.')
-else:
- print('Found: crowdin/config.yaml')
+def check_files(branch):
+ files = ['crowdin/config.yaml',
+ 'crowdin/extra_packages_%s.xml' % branch,
+ 'crowdin/config_aosp.yaml',
+ 'crowdin/crowdin_%s.yaml' % branch,
+ 'crowdin/crowdin_%s_aosp.yaml' % branch
+ ]
+ for f in files:
+ if not os.path.isfile(f):
+ print('You have no %s.' % f, file=sys.stderr)
+ return False
+ print('Found: %s' % f)
+ return True
-# Check for crowdin/config_aosp.yaml
-if not os.path.isfile('crowdin/config_aosp.yaml'):
- sys.exit('You have no crowdin/config_aosp.yaml. Terminating.')
-else:
- print('Found: crowdin/config_aosp.yaml')
+# ################################### MAIN ################################### #
-# Check for crowdin/crowdin_' + default_branch + '.yaml
-if not os.path.isfile('crowdin/crowdin_' + default_branch + '.yaml'):
- sys.exit('You have no crowdin/crowdin_' + default_branch + '.yaml. Terminating.')
-else:
- print('Found: crowdin/crowdin_' + default_branch + '.yaml')
-# Check for crowdin/crowdin_' + default_branch + '_aosp.yaml
-if not os.path.isfile('crowdin/crowdin_' + default_branch + '_aosp.yaml'):
- sys.exit('You have no crowdin/crowdin_' + default_branch + '_aosp.yaml. Terminating.')
-else:
- print('Found: crowdin/crowdin_' + default_branch + '_aosp.yaml')
-
-############################################### MAIN ###############################################
-
-if not args.no_upload:
+def upload_crowdin(branch, no_upload=False):
print('\nSTEP 1: Upload Crowdin source translations')
- print('Uploading Crowdin source translations (AOSP supported languages)')
- # Execute 'crowdin-cli upload sources' and show output
- run_command(['crowdin-cli', '--config=crowdin/crowdin_' + default_branch + '.yaml', '--identity=crowdin/config.yaml', 'upload', 'sources'])
+ if no_upload:
+ print('Skipping source translations upload')
+ return
+ print('\nUploading Crowdin source translations (AOSP supported languages)')
- print('\nUploading Crowdin source translations (non-AOSP supported languages)')
# Execute 'crowdin-cli upload sources' and show output
- run_command(['crowdin-cli', '--config=crowdin/crowdin_' + default_branch + '_aosp.yaml', '--identity=crowdin/config_aosp.yaml', 'upload', 'sources'])
-else:
- print('\nSkipping source translations upload')
+ check_run(['crowdin-cli',
+ '--config=crowdin/crowdin_%s.yaml' % branch,
+ '--identity=crowdin/config.yaml',
+ 'upload', 'sources'])
-if not args.no_download:
+ print('\nUploading Crowdin source translations '
+ '(non-AOSP supported languages)')
+ # Execute 'crowdin-cli upload sources' and show output
+ check_run(['crowdin-cli',
+ '--config=crowdin/crowdin_%s_aosp.yaml' % branch,
+ '--identity=crowdin/config_aosp.yaml',
+ 'upload', 'sources'])
+
+
+def download_crowdin(branch, xml, username, no_download=False):
print('\nSTEP 2: Download Crowdin translations')
- print('Downloading Crowdin translations (AOSP supported languages)')
+ if no_download:
+ print('Skipping translations download')
+ return
+
+ print('\nDownloading Crowdin translations (AOSP supported languages)')
# Execute 'crowdin-cli download' and show output
- run_command(['crowdin-cli', '--config=crowdin/crowdin_' + default_branch + '.yaml', '--identity=crowdin/config.yaml', 'download', '--ignore-match'])
+ check_run(['crowdin-cli',
+ '--config=crowdin/crowdin_%s.yaml' % branch,
+ '--identity=crowdin/config.yaml',
+ 'download', '--ignore-match'])
print('\nDownloading Crowdin translations (non-AOSP supported languages)')
# Execute 'crowdin-cli download' and show output
- run_command(['crowdin-cli', '--config=crowdin/crowdin_' + default_branch + '_aosp.yaml', '--identity=crowdin/config_aosp.yaml', 'download', '--ignore-match'])
+ check_run(['crowdin-cli',
+ '--config=crowdin/crowdin_%s_aosp.yaml' % branch,
+ '--identity=crowdin/config_aosp.yaml',
+ 'download', '--ignore-match'])
print('\nSTEP 3: Remove useless empty translations')
- # Some line of code that I found to find all XML files
- result = [os.path.join(dp, f) for dp, dn, filenames in os.walk(os.getcwd()) for f in filenames if os.path.splitext(f)[1] == '.xml']
- empty_contents = {'<resources/>', '<resources xmlns:android="http://schemas.android.com/apk/res/android"/>', '<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>', '<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>', '<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>'}
- for xml_file in result:
+ empty_contents = {
+ '<resources/>',
+ '<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>',
+ ('<resources xmlns:android='
+ '"http://schemas.android.com/apk/res/android"/>'),
+ ('<resources xmlns:android="http://schemas.android.com/apk/res/android"'
+ ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>'),
+ ('<resources xmlns:tools="http://schemas.android.com/tools"'
+ ' xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>')
+ }
+ xf = None
+ for xml_file in find_xml():
+ xf = open(xml_file).read()
for line in empty_contents:
- if line in open(xml_file).read():
+ if line in xf:
print('Removing ' + xml_file)
os.remove(xml_file)
break
+ del xf
print('\nSTEP 4: Create a list of pushable translations')
# Get all files that Crowdin pushed
- proc = subprocess.Popen(['crowdin-cli --config=crowdin/crowdin_' + default_branch + '.yaml --identity=crowdin/config.yaml list sources | grep "' + default_branch + '" | sed "s#/' + default_branch + '##g" && crowdin-cli --config=crowdin/crowdin_' + default_branch + '_aosp.yaml --identity=crowdin/config_aosp.yaml list sources | grep "' + default_branch + '" | sed "s#/' + default_branch + '##g"'], stdout=subprocess.PIPE, shell=True)
- proc.wait() # Wait for the above to finish
+ paths = []
+ files = [
+ ('crowdin/crowdin_%s.yaml' % branch, 'crowdin/config.yaml'),
+ ('crowdin/crowdin_%s_aosp.yaml' % branch, 'crowdin/config_aosp.yaml')
+ ]
+ for c, i in files:
+ cmd = ['crowdin-cli', '--config=%s' % c, '--identity=%s' % i,
+ 'list', 'sources']
+ comm, ret = run_subprocess(cmd)
+ if ret != 0:
+ sys.exit(ret)
+ for p in str(comm[0]).split("\n"):
+ paths.append(p.replace('/%s' % branch, ''))
print('\nSTEP 5: Upload to Gerrit')
- xml_extra = minidom.parse('crowdin/extra_packages_' + default_branch + '.xml')
- items = xml_android.getElementsByTagName('project')
- items += xml_extra.getElementsByTagName('project')
+ items = [x for sub in xml for x in sub.getElementsByTagName('project')]
all_projects = []
- for path in iter(proc.stdout.readline,''):
- # Remove the \n at the end of each line
- path = path.rstrip()
-
+ for path in paths:
+ path = path.strip()
if not path:
continue
- # Get project root dir from Crowdin's output by regex
- m = re.search('/(.*Superuser)/Superuser.*|/(.*LatinIME).*|/(frameworks/base).*|/(.*CMFileManager).*|/(.*CMHome).*|/(device/.*/.*)/.*/res/values.*|/(hardware/.*/.*)/.*/res/values.*|/(.*)/res/values.*|/(.*)/res_cm/values.*|/(.*)/res_p/values.*', path)
-
- if not m.groups():
- # Regex result is empty, warn the user
- print('WARNING: Cannot determine project root dir of [' + path + '], skipping')
+ if "/res" not in path:
+ print('WARNING: Cannot determine project root dir of '
+ '[%s], skipping.' % path)
continue
-
- for i in m.groups():
- if not i:
- continue
- result = i
- break
+ result = path.split('/res')[0].strip('/')
+ if result == path.strip('/'):
+ print('WARNING: Cannot determine project root dir of '
+ '[%s], skipping.' % path)
+ continue
if result in all_projects:
- # Already committed for this project, go to next project
continue
- # When a project has multiple translatable files, Crowdin will give duplicates.
- # We don't want that (useless empty commits), so we save each project in all_projects
- # and check if it's already in there.
+ # When a project has multiple translatable files, Crowdin will
+ # give duplicates.
+ # We don't want that (useless empty commits), so we save each
+ # project in all_projects and check if it's already in there.
all_projects.append(result)
- # Search in android/default.xml or crowdin/extra_packages_' + default_branch + '.xml for the project's name
- for project_item in items:
- if project_item.attributes['path'].value != result:
- # No match found, go to next item
+ # Search android/default.xml or crowdin/extra_packages_%(branch).xml
+ # for the project's name
+ for project in items:
+ path = project.attributes['path'].value
+ if not (result + '/').startswith(path +'/'):
continue
+ if result != path:
+ if path in all_projects:
+ break
+ result = path
+ all_projects.append(result)
- # Define branch (custom branch if defined in xml file, otherwise the default one)
- if project_item.hasAttribute('revision'):
- branch = project_item.attributes['revision'].value
- else:
- branch = default_branch
+ br = project.getAttribute('revision') or branch
- push_as_commit(result, project_item.attributes['name'].value, branch, username)
-else:
- print('\nSkipping translations download')
+ push_as_commit(result, project.getAttribute('name'), br, username)
+ break
-############################################### DONE ###############################################
-print('\nDone!')
+def main():
+ if not check_dependencies():
+ sys.exit(1)
+
+ args = parse_args()
+ default_branch = args.branch
+
+ print('Welcome to the CM Crowdin sync script!')
+
+ xml_android = load_xml()
+ if xml_android is None:
+ sys.exit(1)
+
+ xml_extra = load_xml(x='crowdin/extra_packages_%s.xml' % default_branch)
+ if xml_extra is None:
+ sys.exit(1)
+
+ if not check_files(default_branch):
+ sys.exit(1)
+
+ upload_crowdin(default_branch, args.no_upload)
+ download_crowdin(default_branch, (xml_android, xml_extra),
+ args.username, args.no_download)
+ print('\nDone!')
+
+if __name__ == '__main__':
+ main()