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