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