chiark / gitweb /
Switch all headers to python3
[fdroidserver.git] / fdroidserver / import.py
1 #!/usr/bin/env python3
2 #
3 # import.py - part of the FDroid server tools
4 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
5 # Copyright (C) 2013-2014 Daniel Martí <mvdan@mvdan.cc>
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Affero General Public License for more details.
16 #
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import sys
21 import os
22 import shutil
23 import urllib
24 from argparse import ArgumentParser
25 from ConfigParser import ConfigParser
26 import logging
27 import common
28 import metadata
29
30
31 # Get the repo type and address from the given web page. The page is scanned
32 # in a rather naive manner for 'git clone xxxx', 'hg clone xxxx', etc, and
33 # when one of these is found it's assumed that's the information we want.
34 # Returns repotype, address, or None, reason
35 def getrepofrompage(url):
36
37     req = urllib.urlopen(url)
38     if req.getcode() != 200:
39         return (None, 'Unable to get ' + url + ' - return code ' + str(req.getcode()))
40     page = req.read()
41
42     # Works for BitBucket
43     index = page.find('hg clone')
44     if index != -1:
45         repotype = 'hg'
46         repo = page[index + 9:]
47         index = repo.find('<')
48         if index == -1:
49             return (None, "Error while getting repo address")
50         repo = repo[:index]
51         repo = repo.split('"')[0]
52         return (repotype, repo)
53
54     # Works for BitBucket
55     index = page.find('git clone')
56     if index != -1:
57         repotype = 'git'
58         repo = page[index + 10:]
59         index = repo.find('<')
60         if index == -1:
61             return (None, "Error while getting repo address")
62         repo = repo[:index]
63         repo = repo.split('"')[0]
64         return (repotype, repo)
65
66     return (None, "No information found." + page)
67
68 config = None
69 options = None
70
71
72 def get_metadata_from_url(app, url):
73
74     tmp_dir = 'tmp'
75     if not os.path.isdir(tmp_dir):
76         logging.info("Creating temporary directory")
77         os.makedirs(tmp_dir)
78
79     # Figure out what kind of project it is...
80     projecttype = None
81     app.WebSite = url  # by default, we might override it
82     if url.startswith('git://'):
83         projecttype = 'git'
84         repo = url
85         repotype = 'git'
86         app.SourceCode = ""
87         app.WebSite = ""
88     elif url.startswith('https://github.com'):
89         projecttype = 'github'
90         repo = url
91         repotype = 'git'
92         app.SourceCode = url
93         app.IssueTracker = url + '/issues'
94         app.WebSite = ""
95     elif url.startswith('https://gitlab.com/'):
96         projecttype = 'gitlab'
97         # git can be fussy with gitlab URLs unless they end in .git
98         if url.endswith('.git'):
99             url = url[:-4]
100         repo = url + '.git'
101         repotype = 'git'
102         app.WebSite = url
103         app.SourceCode = url + '/tree/HEAD'
104         app.IssueTracker = url + '/issues'
105     elif url.startswith('https://bitbucket.org/'):
106         if url.endswith('/'):
107             url = url[:-1]
108         projecttype = 'bitbucket'
109         app.SourceCode = url + '/src'
110         app.IssueTracker = url + '/issues'
111         # Figure out the repo type and adddress...
112         repotype, repo = getrepofrompage(app.SourceCode)
113         if not repotype:
114             logging.error("Unable to determine vcs type. " + repo)
115             sys.exit(1)
116     if not projecttype:
117         logging.error("Unable to determine the project type.")
118         logging.error("The URL you supplied was not in one of the supported formats. Please consult")
119         logging.error("the manual for a list of supported formats, and supply one of those.")
120         sys.exit(1)
121
122     # Ensure we have a sensible-looking repo address at this point. If not, we
123     # might have got a page format we weren't expecting. (Note that we
124     # specifically don't want git@...)
125     if ((repotype != 'bzr' and (not repo.startswith('http://') and
126         not repo.startswith('https://') and
127         not repo.startswith('git://'))) or
128             ' ' in repo):
129         logging.error("Repo address '{0}' does not seem to be valid".format(repo))
130         sys.exit(1)
131
132     # Get a copy of the source so we can extract some info...
133     logging.info('Getting source from ' + repotype + ' repo at ' + repo)
134     build_dir = os.path.join(tmp_dir, 'importer')
135     if os.path.exists(build_dir):
136         shutil.rmtree(build_dir)
137     vcs = common.getvcs(repotype, repo, build_dir)
138     vcs.gotorevision(options.rev)
139     root_dir = get_subdir(build_dir)
140
141     app.RepoType = repotype
142     app.Repo = repo
143
144     return root_dir, build_dir
145
146
147 config = None
148 options = None
149
150
151 def get_subdir(build_dir):
152     if options.subdir:
153         return os.path.join(build_dir, options.subdir)
154
155     return build_dir
156
157
158 def main():
159
160     global config, options
161
162     # Parse command line...
163     parser = ArgumentParser()
164     common.setup_global_opts(parser)
165     parser.add_argument("-u", "--url", default=None,
166                         help="Project URL to import from.")
167     parser.add_argument("-s", "--subdir", default=None,
168                         help="Path to main android project subdirectory, if not in root.")
169     parser.add_argument("--rev", default=None,
170                         help="Allows a different revision (or git branch) to be specified for the initial import")
171     options = parser.parse_args()
172
173     config = common.read_config(options)
174
175     apps = metadata.read_metadata()
176     app = metadata.App()
177     app.UpdateCheckMode = "Tags"
178
179     root_dir = None
180     build_dir = None
181
182     if options.url:
183         root_dir, build_dir = get_metadata_from_url(app, options.url)
184     elif os.path.isdir('.git'):
185         if options.url:
186             app.WebSite = options.url
187         root_dir = get_subdir(os.getcwd())
188     else:
189         logging.error("Specify project url.")
190         sys.exit(1)
191
192     # Extract some information...
193     paths = common.manifest_paths(root_dir, [])
194     if paths:
195
196         version, vercode, package = common.parse_androidmanifests(paths, app)
197         if not package:
198             logging.error("Couldn't find package ID")
199             sys.exit(1)
200         if not version:
201             logging.warn("Couldn't find latest version name")
202         if not vercode:
203             logging.warn("Couldn't find latest version code")
204     else:
205         spec = os.path.join(root_dir, 'buildozer.spec')
206         if os.path.exists(spec):
207             defaults = {'orientation': 'landscape', 'icon': '',
208                         'permissions': '', 'android.api': "18"}
209             bconfig = ConfigParser(defaults, allow_no_value=True)
210             bconfig.read(spec)
211             package = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
212             version = bconfig.get('app', 'version')
213             vercode = None
214         else:
215             logging.error("No android or kivy project could be found. Specify --subdir?")
216             sys.exit(1)
217
218     # Make sure it's actually new...
219     if package in apps:
220         logging.error("Package " + package + " already exists")
221         sys.exit(1)
222
223     # Create a build line...
224     build = metadata.Build()
225     build.version = version or '?'
226     build.vercode = vercode or '?'
227     build.commit = '?'
228     build.disable = 'Generated by import.py - check/set version fields and commit id'
229     if options.subdir:
230         build.subdir = options.subdir
231     if os.path.exists(os.path.join(root_dir, 'jni')):
232         build.buildjni = ['yes']
233
234     app.builds.append(build)
235
236     # Keep the repo directory to save bandwidth...
237     if not os.path.exists('build'):
238         os.mkdir('build')
239     if build_dir is not None:
240         shutil.move(build_dir, os.path.join('build', package))
241     with open('build/.fdroidvcs-' + package, 'w') as f:
242         f.write(app.RepoType + ' ' + app.Repo)
243
244     metadatapath = os.path.join('metadata', package + '.txt')
245     with open(metadatapath, 'w') as f:
246         metadata.write_metadata('txt', f, app)
247     logging.info("Wrote " + metadatapath)
248
249
250 if __name__ == "__main__":
251     main()