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