chiark / gitweb /
import: fix import -u from fdroiddata
[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     root_dir = get_subdir(src_dir)
141
142     app['Repo Type'] = repotype
143     app['Repo'] = repo
144
145     return root_dir, src_dir
146
147
148 config = None
149 options = None
150
151
152 def get_subdir(src_dir):
153     if options.subdir:
154         return os.path.join(src_dir, options.subdir)
155
156     return src_dir
157
158
159 def main():
160
161     global config, options
162
163     # Parse command line...
164     parser = ArgumentParser()
165     common.setup_global_opts(parser)
166     parser.add_argument("-u", "--url", default=None,
167                         help="Project URL to import from.")
168     parser.add_argument("-s", "--subdir", default=None,
169                         help="Path to main android project subdirectory, if not in root.")
170     parser.add_argument("--rev", default=None,
171                         help="Allows a different revision (or git branch) to be specified for the initial import")
172     options = parser.parse_args()
173
174     config = common.read_config(options)
175
176     apps = metadata.read_metadata()
177     package, app = metadata.get_default_app_info_list(apps)
178     app['Update Check Mode'] = "Tags"
179
180     root_dir = None
181     src_dir = None
182
183     if options.url:
184         root_dir, src_dir = get_metadata_from_url(app, options.url)
185     elif os.path.isdir('.git'):
186         if options.url:
187             app['Web Site'] = options.url
188         root_dir = get_subdir(os.getcwd())
189     else:
190         logging.error("Specify project url.")
191         sys.exit(1)
192
193     # Extract some information...
194     paths = common.manifest_paths(root_dir, [])
195     if paths:
196
197         version, vercode, package = common.parse_androidmanifests(paths)
198         if not package:
199             logging.error("Couldn't find package ID")
200             sys.exit(1)
201         if not version:
202             logging.warn("Couldn't find latest version name")
203         if not vercode:
204             logging.warn("Couldn't find latest version code")
205     else:
206         spec = os.path.join(root_dir, 'buildozer.spec')
207         if os.path.exists(spec):
208             defaults = {'orientation': 'landscape', 'icon': '',
209                         'permissions': '', 'android.api': "18"}
210             bconfig = ConfigParser(defaults, allow_no_value=True)
211             bconfig.read(spec)
212             package = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
213             version = bconfig.get('app', 'version')
214             vercode = None
215         else:
216             logging.error("No android or kivy project could be found. Specify --subdir?")
217             sys.exit(1)
218
219     # Make sure it's actually new...
220     if package in apps:
221         logging.error("Package " + package + " already exists")
222         sys.exit(1)
223
224     # Create a build line...
225     build = {}
226     build['version'] = version or '?'
227     build['vercode'] = vercode or '?'
228     build['commit'] = '?'
229     build['disable'] = 'Generated by import.py - check/set version fields and commit id'
230     if options.subdir:
231         build['subdir'] = options.subdir
232     if os.path.exists(os.path.join(root_dir, 'jni')):
233         build['buildjni'] = ['yes']
234
235     for flag, value in metadata.flag_defaults.iteritems():
236         if flag in build:
237             continue
238         build[flag] = value
239
240     app['builds'].append(build)
241
242     # Keep the repo directory to save bandwidth...
243     if not os.path.exists('build'):
244         os.mkdir('build')
245     if src_dir is not None:
246         shutil.move(src_dir, os.path.join('build', package))
247     with open('build/.fdroidvcs-' + package, 'w') as f:
248         f.write(app['Repo Type'] + ' ' + app['Repo'])
249
250     metadatapath = os.path.join('metadata', package + '.txt')
251     metadata.write_metadata(metadatapath, app)
252     logging.info("Wrote " + metadatapath)
253
254
255 if __name__ == "__main__":
256     main()