chiark / gitweb /
eddb3238cc7bf4baf139a5cdfe66e8e3420ab63a
[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 argparse import ArgumentParser
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 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 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     return (None, "No information found." + page)
68
69 config = None
70 options = None
71
72
73 def get_metadata_from_url(app, url):
74
75     tmp_dir = 'tmp'
76     if not os.path.isdir(tmp_dir):
77         logging.info("Creating temporary directory")
78         os.makedirs(tmp_dir)
79
80     # Figure out what kind of project it is...
81     projecttype = None
82     app['Web Site'] = url  # by default, we might override it
83     if url.startswith('git://'):
84         projecttype = 'git'
85         repo = url
86         repotype = 'git'
87         app['Source Code'] = ""
88         app['Web Site'] = ""
89     elif url.startswith('https://github.com'):
90         projecttype = 'github'
91         repo = url
92         repotype = 'git'
93         app['Source Code'] = url
94         app['issuetracker'] = url + '/issues'
95         app['Web Site'] = ""
96     elif url.startswith('https://gitlab.com/'):
97         projecttype = 'gitlab'
98         # git can be fussy with gitlab URLs unless they end in .git
99         if url.endswith('.git'):
100             repo = url
101         else:
102             repo = url + '.git'
103         repotype = 'git'
104         app['Source Code'] = url + '/tree/HEAD'
105         app['issuetracker'] = url + '/issues'
106     elif url.startswith('https://bitbucket.org/'):
107         if url.endswith('/'):
108             url = url[:-1]
109         projecttype = 'bitbucket'
110         app['Source Code'] = url + '/src'
111         app['issuetracker'] = url + '/issues'
112         # Figure out the repo type and adddress...
113         repotype, repo = getrepofrompage(app['Source Code'])
114         if not repotype:
115             logging.error("Unable to determine vcs type. " + repo)
116             sys.exit(1)
117     if not projecttype:
118         logging.error("Unable to determine the project type.")
119         logging.error("The URL you supplied was not in one of the supported formats. Please consult")
120         logging.error("the manual for a list of supported formats, and supply one of those.")
121         sys.exit(1)
122
123     # Ensure we have a sensible-looking repo address at this point. If not, we
124     # might have got a page format we weren't expecting. (Note that we
125     # specifically don't want git@...)
126     if ((repotype != 'bzr' and (not repo.startswith('http://') and
127         not repo.startswith('https://') and
128         not repo.startswith('git://'))) or
129             ' ' in repo):
130         logging.error("Repo address '{0}' does not seem to be valid".format(repo))
131         sys.exit(1)
132
133     # Get a copy of the source so we can extract some info...
134     logging.info('Getting source from ' + repotype + ' repo at ' + repo)
135     src_dir = os.path.join(tmp_dir, 'importer')
136     if os.path.exists(src_dir):
137         shutil.rmtree(src_dir)
138     vcs = common.getvcs(repotype, repo, src_dir)
139     vcs.gotorevision(options.rev)
140     if options.subdir:
141         root_dir = os.path.join(src_dir, options.subdir)
142     else:
143         root_dir = src_dir
144
145     app['Repo Type'] = repotype
146     app['Repo'] = repo
147
148     return root_dir, src_dir
149
150
151 config = None
152 options = None
153
154
155 def main():
156
157     global config, options
158
159     # Parse command line...
160     parser = ArgumentParser()
161     common.setup_global_opts(parser)
162     parser.add_argument("-u", "--url", default=None,
163                         help="Project URL to import from.")
164     parser.add_argument("-s", "--subdir", default=None,
165                         help="Path to main android project subdirectory, if not in root.")
166     parser.add_argument("--rev", default=None,
167                         help="Allows a different revision (or git branch) to be specified for the initial import")
168     options = parser.parse_args()
169
170     config = common.read_config(options)
171
172     apps = metadata.read_metadata()
173     package, app = metadata.get_default_app_info_list(apps)
174     app['Update Check Mode'] = "Tags"
175
176     if os.path.isdir('.git'):
177         if options.url:
178             app['Web Site'] = options.url
179     elif options.url:
180         root_dir, src_dir = get_metadata_from_url(app, options.url)
181     else:
182         logging.error("Specify project url.")
183         sys.exit(1)
184
185     # Extract some information...
186     paths = common.manifest_paths(root_dir, [])
187     if paths:
188
189         version, vercode, package = common.parse_androidmanifests(paths)
190         if not package:
191             logging.error("Couldn't find package ID")
192             sys.exit(1)
193         if not version:
194             logging.warn("Couldn't find latest version name")
195         if not vercode:
196             logging.warn("Couldn't find latest version code")
197     else:
198         spec = os.path.join(root_dir, 'buildozer.spec')
199         if os.path.exists(spec):
200             defaults = {'orientation': 'landscape', 'icon': '',
201                         'permissions': '', 'android.api': "18"}
202             bconfig = ConfigParser(defaults, allow_no_value=True)
203             bconfig.read(spec)
204             package = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
205             version = bconfig.get('app', 'version')
206             vercode = None
207         else:
208             logging.error("No android or kivy project could be found. Specify --subdir?")
209             sys.exit(1)
210
211     # Make sure it's actually new...
212     if package in apps:
213         logging.error("Package " + package + " already exists")
214         sys.exit(1)
215
216     # Create a build line...
217     build = {}
218     build['version'] = version or '?'
219     build['vercode'] = vercode or '?'
220     build['commit'] = '?'
221     build['disable'] = 'Generated by import.py - check/set version fields and commit id'
222     if options.subdir:
223         build['subdir'] = options.subdir
224     if os.path.exists(os.path.join(root_dir, 'jni')):
225         build['buildjni'] = ['yes']
226
227     for flag, value in metadata.flag_defaults.iteritems():
228         if flag in build:
229             continue
230         build[flag] = value
231
232     app['builds'].append(build)
233
234     # Keep the repo directory to save bandwidth...
235     if not os.path.exists('build'):
236         os.mkdir('build')
237     if src_dir is not None:
238         shutil.move(src_dir, os.path.join('build', package))
239     with open('build/.fdroidvcs-' + package, 'w') as f:
240         f.write(app['Repo Type'] + ' ' + app['Repo'])
241
242     metadatapath = os.path.join('metadata', package + '.txt')
243     metadata.write_metadata(metadatapath, app)
244     logging.info("Wrote " + metadatapath)
245
246
247 if __name__ == "__main__":
248     main()