chiark / gitweb /
Add --quiet option, restricting output to warnings/errors
[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("-q", "--quiet", action="store_true", default=False,
100                       help="Restrict output to warnings and errors")
101     parser.add_option("-u", "--url", default=None,
102                       help="Project URL to import from.")
103     parser.add_option("-s", "--subdir", default=None,
104                       help="Path to main android project subdirectory, if not in root.")
105     parser.add_option("-r", "--repo", default=None,
106                       help="Allows a different repo to be specified for a multi-repo google code project")
107     parser.add_option("--rev", default=None,
108                       help="Allows a different revision (or git branch) to be specified for the initial import")
109     (options, args) = parser.parse_args()
110
111     config = common.read_config(options)
112
113     if not options.url:
114         logging.info("Specify project url.")
115         sys.exit(1)
116     url = options.url
117
118     tmp_dir = 'tmp'
119     if not os.path.isdir(tmp_dir):
120         logging.info("Creating temporary directory")
121         os.makedirs(tmp_dir)
122
123     # Get all apps...
124     apps = metadata.read_metadata()
125
126     # Figure out what kind of project it is...
127     projecttype = None
128     issuetracker = None
129     license = None
130     website = url #by default, we might override it
131     if url.startswith('git://'):
132         projecttype = 'git'
133         repo = url
134         repotype = 'git'
135         sourcecode = ""
136         website = ""
137     elif url.startswith('https://github.com'):
138         if url.endswith('/'):
139             url = url[:-1]
140         if url.endswith('.git'):
141             logging.info("A github URL should point to the project, not the git repo")
142             sys.exit(1)
143         projecttype = 'github'
144         repo = url + '.git'
145         repotype = 'git'
146         sourcecode = url
147         issuetracker = url + '/issues'
148     elif url.startswith('https://gitorious.org/'):
149         projecttype = 'gitorious'
150         repo = 'https://git.gitorious.org/' + url[22:] + '.git'
151         repotype = 'git'
152         sourcecode = url
153     elif url.startswith('https://bitbucket.org/'):
154         if url.endswith('/'):
155             url = url[:-1]
156         projecttype = 'bitbucket'
157         sourcecode = url + '/src'
158         issuetracker = url + '/issues'
159         # Figure out the repo type and adddress...
160         repotype, repo = getrepofrompage(sourcecode)
161         if not repotype:
162             logging.info("Unable to determine vcs type. " + repo)
163             sys.exit(1)
164     elif url.startswith('http://code.google.com/p/'):
165         if not url.endswith('/'):
166             url += '/';
167         projecttype = 'googlecode'
168         sourcecode = url + 'source/checkout'
169         if options.repo:
170             sourcecode += "?repo=" + options.repo
171         issuetracker = url + 'issues/list'
172
173         # Figure out the repo type and adddress...
174         repotype, repo = getrepofrompage(sourcecode)
175         if not repotype:
176             logging.info("Unable to determine vcs type. " + repo)
177             sys.exit(1)
178
179         # Figure out the license...
180         req = urllib.urlopen(url)
181         if req.getcode() != 200:
182             logging.info('Unable to find project page at ' + sourcecode + ' - return code ' + str(req.getcode()))
183             sys.exit(1)
184         page = req.read()
185         index = page.find('Code license')
186         if index == -1:
187             logging.info("Couldn't find license data")
188             sys.exit(1)
189         ltext = page[index:]
190         lprefix = 'rel="nofollow">'
191         index = ltext.find(lprefix)
192         if index == -1:
193             logging.info("Couldn't find license text")
194             sys.exit(1)
195         ltext = ltext[index + len(lprefix):]
196         index = ltext.find('<')
197         if index == -1:
198             logging.info("License text not formatted as expected")
199             sys.exit(1)
200         ltext = ltext[:index]
201         if ltext == 'GNU GPL v3':
202             license = 'GPLv3'
203         elif ltext == 'GNU GPL v2':
204             license = 'GPLv2'
205         elif ltext == 'Apache License 2.0':
206             license = 'Apache2'
207         elif ltext == 'MIT License':
208             license = 'MIT'
209         elif ltext == 'GNU Lesser GPL':
210             license = 'LGPL'
211         elif ltext == 'Mozilla Public License 1.1':
212             license = 'MPL'
213         elif ltext == 'New BSD License':
214             license = 'NewBSD'
215         else:
216             logging.info("License " + ltext + " is not recognised")
217             sys.exit(1)
218
219     if not projecttype:
220         logging.info("Unable to determine the project type.")
221         logging.info("The URL you supplied was not in one of the supported formats. Please consult")
222         logging.info("the manual for a list of supported formats, and supply one of those.")
223         sys.exit(1)
224
225     # Get a copy of the source so we can extract some info...
226     logging.info('Getting source from ' + repotype + ' repo at ' + repo)
227     src_dir = os.path.join(tmp_dir, 'importer')
228     if os.path.exists(src_dir):
229         shutil.rmtree(src_dir)
230     vcs = common.getvcs(repotype, repo, src_dir)
231     vcs.gotorevision(options.rev)
232     if options.subdir:
233         root_dir = os.path.join(src_dir, options.subdir)
234     else:
235         root_dir = src_dir
236
237     # Extract some information...
238     paths = common.manifest_paths(root_dir, None)
239     if paths:
240
241         version, vercode, package = common.parse_androidmanifests(paths)
242         if not package:
243             logging.info("Couldn't find package ID")
244             sys.exit(1)
245         if not version:
246             logging.info("WARNING: Couldn't find latest version name")
247         if not vercode:
248             logging.info("WARNING: Couldn't find latest version code")
249     else:
250         spec = os.path.join(root_dir, 'buildozer.spec')
251         if os.path.exists(spec):
252             defaults = {'orientation': 'landscape', 'icon': '',
253                     'permissions': '', 'android.api': "18"}
254             bconfig = ConfigParser(defaults, allow_no_value=True)
255             bconfig.read(spec)
256             package = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
257             version = bconfig.get('app', 'version')
258             vercode = None
259         else:
260             logging.info("No android or kivy project could be found. Specify --subdir?")
261             sys.exit(1)
262
263     # Make sure it's actually new...
264     for app in apps:
265         if app['id'] == package:
266             logging.info("Package " + package + " already exists")
267             sys.exit(1)
268
269     # Construct the metadata...
270     app = metadata.parse_metadata(None)
271     app['id'] = package
272     app['Web Site'] = website
273     app['Source Code'] = sourcecode
274     if issuetracker:
275         app['Issue Tracker'] = issuetracker
276     if license:
277         app['License'] = license
278     app['Repo Type'] = repotype
279     app['Repo'] = repo
280     app['Update Check Mode'] = "Tags"
281
282     # Create a build line...
283     build = {}
284     build['version'] = version if version else '?'
285     build['vercode'] = vercode if vercode else '?'
286     build['commit'] = '?'
287     build['disable'] = 'Generated by import.py - check/set version fields and commit id'
288     if options.subdir:
289         build['subdir'] = options.subdir
290     if os.path.exists(os.path.join(root_dir, 'jni')):
291         build['buildjni'] = ['yes']
292     app['builds'].append(build)
293
294     # Keep the repo directory to save bandwidth...
295     if not os.path.exists('build'):
296         os.mkdir('build')
297     shutil.move(src_dir, os.path.join('build', package))
298     with open('build/.fdroidvcs-' + package, 'w') as f:
299         f.write(repotype + ' ' + repo)
300
301     metafile = os.path.join('metadata', package + '.txt')
302     metadata.write_metadata(metafile, app)
303     logging.info("Wrote " + metafile)
304
305
306 if __name__ == "__main__":
307     main()
308