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