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