chiark / gitweb /
0f9e7c7ea7b3482806c8760f28229489eab1c68e
[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.WebSite = url  # by default, we might override it
83     if url.startswith('git://'):
84         projecttype = 'git'
85         repo = url
86         repotype = 'git'
87         app.SourceCode = ""
88         app.WebSite = ""
89     elif url.startswith('https://github.com'):
90         projecttype = 'github'
91         repo = url
92         repotype = 'git'
93         app.SourceCode = url
94         app.IssueTracker = url + '/issues'
95         app.WebSite = ""
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.SourceCode = 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.SourceCode = url + '/src'
111         app.IssueTracker = url + '/issues'
112         # Figure out the repo type and adddress...
113         repotype, repo = getrepofrompage(app.SourceCode)
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     build_dir = os.path.join(tmp_dir, 'importer')
136     if os.path.exists(build_dir):
137         shutil.rmtree(build_dir)
138     vcs = common.getvcs(repotype, repo, build_dir)
139     vcs.gotorevision(options.rev)
140     root_dir = get_subdir(build_dir)
141
142     app.RepoType = repotype
143     app.Repo = repo
144
145     return root_dir, build_dir
146
147
148 config = None
149 options = None
150
151
152 def get_subdir(build_dir):
153     if options.subdir:
154         return os.path.join(build_dir, options.subdir)
155
156     return build_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     app = metadata.get_default_app_info()
178     app.id = None
179     app.UpdateCheckMode = "Tags"
180
181     root_dir = None
182     build_dir = None
183
184     if options.url:
185         root_dir, build_dir = get_metadata_from_url(app, options.url)
186     elif os.path.isdir('.git'):
187         if options.url:
188             app.WebSite = options.url
189         root_dir = get_subdir(os.getcwd())
190     else:
191         logging.error("Specify project url.")
192         sys.exit(1)
193
194     # Extract some information...
195     paths = common.manifest_paths(root_dir, [])
196     if paths:
197
198         version, vercode, package = common.parse_androidmanifests(paths, app)
199         if not package:
200             logging.error("Couldn't find package ID")
201             sys.exit(1)
202         if not version:
203             logging.warn("Couldn't find latest version name")
204         if not vercode:
205             logging.warn("Couldn't find latest version code")
206     else:
207         spec = os.path.join(root_dir, 'buildozer.spec')
208         if os.path.exists(spec):
209             defaults = {'orientation': 'landscape', 'icon': '',
210                         'permissions': '', 'android.api': "18"}
211             bconfig = ConfigParser(defaults, allow_no_value=True)
212             bconfig.read(spec)
213             package = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
214             version = bconfig.get('app', 'version')
215             vercode = None
216         else:
217             logging.error("No android or kivy project could be found. Specify --subdir?")
218             sys.exit(1)
219
220     # Make sure it's actually new...
221     if package in apps:
222         logging.error("Package " + package + " already exists")
223         sys.exit(1)
224
225     # Create a build line...
226     build = metadata.Build()
227     build.version = version or '?'
228     build.vercode = vercode or '?'
229     build.commit = '?'
230     build.disable = 'Generated by import.py - check/set version fields and commit id'
231     if options.subdir:
232         build.subdir = options.subdir
233     if os.path.exists(os.path.join(root_dir, 'jni')):
234         build.buildjni = ['yes']
235
236     app.builds.append(build)
237
238     # Keep the repo directory to save bandwidth...
239     if not os.path.exists('build'):
240         os.mkdir('build')
241     if build_dir is not None:
242         shutil.move(build_dir, os.path.join('build', package))
243     with open('build/.fdroidvcs-' + package, 'w') as f:
244         f.write(app.RepoType + ' ' + app.Repo)
245
246     metadatapath = os.path.join('metadata', package + '.txt')
247     with open(metadatapath, 'w') as f:
248         metadata.write_metadata('txt', f, app)
249     logging.info("Wrote " + metadatapath)
250
251
252 if __name__ == "__main__":
253     main()