chiark / gitweb /
Import support for kivy projects (with buildozer spec)
[fdroidserver.git] / fdroidserver / import.py
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3 #
4 # import.py - part of the FDroid server tools
5 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero 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 Affero General Public License for more details.
16 #
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import sys
21 import os
22 import shutil
23 import urllib
24 from optparse import OptionParser
25 from ConfigParser import ConfigParser
26 import common, metadata
27
28 # Get the repo type and address from the given web page. The page is scanned
29 # in a rather naive manner for 'git clone xxxx', 'hg clone xxxx', etc, and
30 # when one of these is found it's assumed that's the information we want.
31 # Returns repotype, address, or None, reason
32 def getrepofrompage(url):
33
34     req = urllib.urlopen(url)
35     if req.getcode() != 200:
36         return (None, 'Unable to get ' + url + ' - return code ' + str(req.getcode()))
37     page = req.read()
38
39     # Works for Google Code and BitBucket...
40     index = page.find('hg clone')
41     if index != -1:
42         repotype = 'hg'
43         repo = page[index + 9:]
44         index = repo.find('<')
45         if index == -1:
46             return (None, "Error while getting repo address")
47         repo = repo[:index]
48         repo = repo.split('"')[0]
49         return (repotype, repo)
50
51     # Works for Google Code and BitBucket...
52     index=page.find('git clone')
53     if index != -1:
54         repotype = 'git'
55         repo = page[index + 10:]
56         index = repo.find('<')
57         if index == -1:
58             return (None, "Error while getting repo address")
59         repo = repo[:index]
60         repo = repo.split('"')[0]
61         return (repotype, repo)
62
63     # Google Code only...
64     index=page.find('svn checkout')
65     if index != -1:
66         repotype = 'git-svn'
67         repo = page[index + 13:]
68         prefix = '<strong><em>http</em></strong>'
69         if not repo.startswith(prefix):
70             return (None, "Unexpected checkout instructions format")
71         repo = 'http' + repo[len(prefix):]
72         index = repo.find('<')
73         if index == -1:
74             return (None, "Error while getting repo address - no end tag? '" + repo + "'")
75             sys.exit(1)
76         repo = repo[:index]
77         index = repo.find(' ')
78         if index == -1:
79             return (None, "Error while getting repo address - no space? '" + repo + "'")
80         repo = repo[:index]
81         repo = repo.split('"')[0]
82         return (repotype, repo)
83
84     return (None, "No information found." + page)
85
86 config = None
87 options = None
88
89 def main():
90
91     global config, options
92
93     # Parse command line...
94     parser = OptionParser()
95     parser.add_option("-u", "--url", default=None,
96                       help="Project URL to import from.")
97     parser.add_option("-s", "--subdir", default=None,
98                       help="Path to main android project subdirectory, if not in root.")
99     parser.add_option("-r", "--repo", default=None,
100                       help="Allows a different repo to be specified for a multi-repo google code project")
101     parser.add_option("--rev", default=None,
102                       help="Allows a different revision (or git branch) to be specified for the initial import")
103     (options, args) = parser.parse_args()
104
105     config = common.read_config(options)
106
107     if not options.url:
108         print "Specify project url."
109         sys.exit(1)
110     url = options.url
111
112     tmp_dir = 'tmp'
113     if not os.path.isdir(tmp_dir):
114         print "Creating temporary directory"
115         os.makedirs(tmp_dir)
116
117     # Get all apps...
118     apps = metadata.read_metadata()
119
120     # Figure out what kind of project it is...
121     projecttype = None
122     issuetracker = None
123     license = None
124     website = url #by default, we might override it
125     if url.startswith('git://'):
126         projecttype = 'git'
127         repo = url
128         repotype = 'git'
129         sourcecode = ""
130         website = ""
131     elif url.startswith('https://github.com'):
132         if url.endswith('/'):
133             url = url[:-1]
134         if url.endswith('.git'):
135             print "A github URL should point to the project, not the git repo"
136             sys.exit(1)
137         projecttype = 'github'
138         repo = url + '.git'
139         repotype = 'git'
140         sourcecode = url
141         issuetracker = url + '/issues'
142     elif url.startswith('https://gitorious.org/'):
143         projecttype = 'gitorious'
144         repo = 'https://git.gitorious.org/' + url[22:] + '.git'
145         repotype = 'git'
146         sourcecode = url
147     elif url.startswith('https://bitbucket.org/'):
148         if url.endswith('/'):
149             url = url[:-1]
150         projecttype = 'bitbucket'
151         sourcecode = url + '/src'
152         issuetracker = url + '/issues'
153         # Figure out the repo type and adddress...
154         repotype, repo = getrepofrompage(sourcecode)
155         if not repotype:
156             print "Unable to determine vcs type. " + repo
157             sys.exit(1)
158     elif url.startswith('http://code.google.com/p/'):
159         if not url.endswith('/'):
160             url += '/';
161         projecttype = 'googlecode'
162         sourcecode = url + 'source/checkout'
163         if options.repo:
164             sourcecode += "?repo=" + options.repo
165         issuetracker = url + 'issues/list'
166
167         # Figure out the repo type and adddress...
168         repotype, repo = getrepofrompage(sourcecode)
169         if not repotype:
170             print "Unable to determine vcs type. " + repo
171             sys.exit(1)
172
173         # Figure out the license...
174         req = urllib.urlopen(url)
175         if req.getcode() != 200:
176             print 'Unable to find project page at ' + sourcecode + ' - return code ' + str(req.getcode())
177             sys.exit(1)
178         page = req.read()
179         index = page.find('Code license')
180         if index == -1:
181             print "Couldn't find license data"
182             sys.exit(1)
183         ltext = page[index:]
184         lprefix = 'rel="nofollow">'
185         index = ltext.find(lprefix)
186         if index == -1:
187             print "Couldn't find license text"
188             sys.exit(1)
189         ltext = ltext[index + len(lprefix):]
190         index = ltext.find('<')
191         if index == -1:
192             print "License text not formatted as expected"
193             sys.exit(1)
194         ltext = ltext[:index]
195         if ltext == 'GNU GPL v3':
196             license = 'GPLv3'
197         elif ltext == 'GNU GPL v2':
198             license = 'GPLv2'
199         elif ltext == 'Apache License 2.0':
200             license = 'Apache2'
201         elif ltext == 'MIT License':
202             license = 'MIT'
203         elif ltext == 'GNU Lesser GPL':
204             license = 'LGPL'
205         elif ltext == 'Mozilla Public License 1.1':
206             license = 'MPL'
207         elif ltext == 'New BSD License':
208             license = 'NewBSD'
209         else:
210             print "License " + ltext + " is not recognised"
211             sys.exit(1)
212
213     if not projecttype:
214         print "Unable to determine the project type."
215         print "The URL you supplied was not in one of the supported formats. Please consult"
216         print "the manual for a list of supported formats, and supply one of those."
217         sys.exit(1)
218
219     # Get a copy of the source so we can extract some info...
220     print 'Getting source from ' + repotype + ' repo at ' + repo
221     src_dir = os.path.join(tmp_dir, 'importer')
222     if os.path.exists(src_dir):
223         shutil.rmtree(src_dir)
224     vcs = common.getvcs(repotype, repo, src_dir)
225     vcs.gotorevision(options.rev)
226     if options.subdir:
227         root_dir = os.path.join(src_dir, options.subdir)
228     else:
229         root_dir = src_dir
230
231     # Extract some information...
232     paths = common.manifest_paths(root_dir, None)
233     if paths:
234
235         version, vercode, package = common.parse_androidmanifests(paths)
236         if not package:
237             print "Couldn't find package ID"
238             sys.exit(1)
239         if not version:
240             print "WARNING: Couldn't find latest version name"
241         if not vercode:
242             print "WARNING: Couldn't find latest version code"
243     else:
244         spec = os.path.join(root_dir, 'buildozer.spec')
245         if os.path.exists(spec):
246             defaults = {'orientation': 'landscape', 'icon': '', 
247                     'permissions': '', 'android.api': "18"}
248             bconfig = ConfigParser(defaults, allow_no_value=True)
249             bconfig.read(spec)
250             package = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
251             version = bconfig.get('app', 'version')
252             vercode = None
253         else:
254             print "No android or kivy project could be found. Specify --subdir?"
255             sys.exit(1)
256
257     # Make sure it's actually new...
258     for app in apps:
259         if app['id'] == package:
260             print "Package " + package + " already exists"
261             sys.exit(1)
262
263     # Construct the metadata...
264     app = metadata.parse_metadata(None)
265     app['id'] = package
266     app['Web Site'] = website
267     app['Source Code'] = sourcecode
268     if issuetracker:
269         app['Issue Tracker'] = issuetracker
270     if license:
271         app['License'] = license
272     app['Repo Type'] = repotype
273     app['Repo'] = repo
274     app['Update Check Mode'] = "Tags"
275
276     # Create a build line...
277     build = {}
278     build['version'] = version if version else '?'
279     build['vercode'] = vercode if vercode else '?'
280     build['commit'] = '?'
281     build['disable'] = 'Generated by import.py - check/set version fields and commit id'
282     if options.subdir:
283         build['subdir'] = options.subdir
284     if os.path.exists(os.path.join(root_dir, 'jni')):
285         build['buildjni'] = 'yes'
286     app['builds'].append(build)
287
288     # Keep the repo directory to save bandwidth...
289     if not os.path.exists('build'):
290         os.mkdir('build')
291     shutil.move(src_dir, os.path.join('build', package))
292     with open('build/.fdroidvcs-' + package, 'w') as f:
293         f.write(repotype + ' ' + repo)
294
295     metafile = os.path.join('metadata', package + '.txt')
296     metadata.write_metadata(metafile, app)
297     print "Wrote " + metafile
298
299
300 if __name__ == "__main__":
301     main()
302