PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/fdroidserver/import.py

https://gitlab.com/pserwylo/fdroidserver
Python | 313 lines | 261 code | 24 blank | 28 comment | 26 complexity | d9518f2d9b79ea9b6b00c06dc358475e MD5 | raw file
Possible License(s): AGPL-3.0
  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. import binascii
  20. import os
  21. import re
  22. import shutil
  23. import urllib.request
  24. from argparse import ArgumentParser
  25. from configparser import ConfigParser
  26. import logging
  27. from . import _
  28. from . import common
  29. from . import metadata
  30. from .exception import FDroidException
  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. req = urllib.request.urlopen(url)
  37. if req.getcode() != 200:
  38. return (None, 'Unable to get ' + url + ' - return code ' + str(req.getcode()))
  39. page = req.read().decode(req.headers.get_content_charset())
  40. # Works for BitBucket
  41. m = re.search('data-fetch-url="(.*)"', page)
  42. if m is not None:
  43. repo = m.group(1)
  44. if repo.endswith('.git'):
  45. return ('git', repo)
  46. return ('hg', repo)
  47. # Works for BitBucket (obsolete)
  48. index = page.find('hg clone')
  49. if index != -1:
  50. repotype = 'hg'
  51. repo = page[index + 9:]
  52. index = repo.find('<')
  53. if index == -1:
  54. return (None, _("Error while getting repo address"))
  55. repo = repo[:index]
  56. repo = repo.split('"')[0]
  57. return (repotype, repo)
  58. # Works for BitBucket (obsolete)
  59. index = page.find('git clone')
  60. if index != -1:
  61. repotype = 'git'
  62. repo = page[index + 10:]
  63. index = repo.find('<')
  64. if index == -1:
  65. return (None, _("Error while getting repo address"))
  66. repo = repo[:index]
  67. repo = repo.split('"')[0]
  68. return (repotype, repo)
  69. return (None, _("No information found.") + page)
  70. config = None
  71. options = None
  72. def get_metadata_from_url(app, url):
  73. tmp_dir = 'tmp'
  74. if not os.path.isdir(tmp_dir):
  75. logging.info(_("Creating temporary directory"))
  76. os.makedirs(tmp_dir)
  77. # Figure out what kind of project it is...
  78. projecttype = None
  79. app.WebSite = url # by default, we might override it
  80. if url.startswith('git://'):
  81. projecttype = 'git'
  82. repo = url
  83. repotype = 'git'
  84. app.SourceCode = ""
  85. app.WebSite = ""
  86. elif url.startswith('https://github.com'):
  87. projecttype = 'github'
  88. repo = url
  89. repotype = 'git'
  90. app.SourceCode = url
  91. app.IssueTracker = url + '/issues'
  92. app.WebSite = ""
  93. elif url.startswith('https://gitlab.com/'):
  94. projecttype = 'gitlab'
  95. # git can be fussy with gitlab URLs unless they end in .git
  96. if url.endswith('.git'):
  97. url = url[:-4]
  98. repo = url + '.git'
  99. repotype = 'git'
  100. app.WebSite = url
  101. app.SourceCode = url + '/tree/HEAD'
  102. app.IssueTracker = url + '/issues'
  103. elif url.startswith('https://notabug.org/'):
  104. projecttype = 'notabug'
  105. if url.endswith('.git'):
  106. url = url[:-4]
  107. repo = url + '.git'
  108. repotype = 'git'
  109. app.SourceCode = url
  110. app.IssueTracker = url + '/issues'
  111. app.WebSite = ""
  112. elif url.startswith('https://bitbucket.org/'):
  113. if url.endswith('/'):
  114. url = url[:-1]
  115. projecttype = 'bitbucket'
  116. app.SourceCode = url + '/src'
  117. app.IssueTracker = url + '/issues'
  118. # Figure out the repo type and adddress...
  119. repotype, repo = getrepofrompage(url)
  120. if not repotype:
  121. raise FDroidException("Unable to determine vcs type. " + repo)
  122. elif url.startswith('https://') and url.endswith('.git'):
  123. projecttype = 'git'
  124. repo = url
  125. repotype = 'git'
  126. app.SourceCode = ""
  127. app.WebSite = ""
  128. if not projecttype:
  129. raise FDroidException("Unable to determine the project type. " +
  130. "The URL you supplied was not in one of the supported formats. " +
  131. "Please consult the manual for a list of supported formats, " +
  132. "and supply one of those.")
  133. # Ensure we have a sensible-looking repo address at this point. If not, we
  134. # might have got a page format we weren't expecting. (Note that we
  135. # specifically don't want git@...)
  136. if ((repotype != 'bzr' and (not repo.startswith('http://') and
  137. not repo.startswith('https://') and
  138. not repo.startswith('git://'))) or
  139. ' ' in repo):
  140. raise FDroidException("Repo address '{0}' does not seem to be valid".format(repo))
  141. # Get a copy of the source so we can extract some info...
  142. logging.info('Getting source from ' + repotype + ' repo at ' + repo)
  143. build_dir = os.path.join(tmp_dir, 'importer')
  144. if os.path.exists(build_dir):
  145. shutil.rmtree(build_dir)
  146. vcs = common.getvcs(repotype, repo, build_dir)
  147. vcs.gotorevision(options.rev)
  148. root_dir = get_subdir(build_dir)
  149. app.RepoType = repotype
  150. app.Repo = repo
  151. return root_dir, build_dir
  152. config = None
  153. options = None
  154. def get_subdir(build_dir):
  155. if options.subdir:
  156. return os.path.join(build_dir, options.subdir)
  157. return build_dir
  158. def main():
  159. global config, options
  160. # Parse command line...
  161. parser = ArgumentParser()
  162. common.setup_global_opts(parser)
  163. parser.add_argument("-u", "--url", default=None,
  164. help=_("Project URL to import from."))
  165. parser.add_argument("-s", "--subdir", default=None,
  166. help=_("Path to main Android project subdirectory, if not in root."))
  167. parser.add_argument("-c", "--categories", default=None,
  168. help=_("Comma separated list of categories."))
  169. parser.add_argument("-l", "--license", default=None,
  170. help=_("Overall license of the project."))
  171. parser.add_argument("--rev", default=None,
  172. help=_("Allows a different revision (or git branch) to be specified for the initial import"))
  173. metadata.add_metadata_arguments(parser)
  174. options = parser.parse_args()
  175. metadata.warnings_action = options.W
  176. config = common.read_config(options)
  177. apps = metadata.read_metadata()
  178. app = metadata.App()
  179. app.UpdateCheckMode = "Tags"
  180. root_dir = None
  181. build_dir = None
  182. local_metadata_files = common.get_local_metadata_files()
  183. if local_metadata_files != []:
  184. raise FDroidException(_("This repo already has local metadata: %s") % local_metadata_files[0])
  185. if options.url is None and os.path.isdir('.git'):
  186. app.AutoName = os.path.basename(os.getcwd())
  187. app.RepoType = 'git'
  188. build = {}
  189. root_dir = get_subdir(os.getcwd())
  190. if os.path.exists('build.gradle'):
  191. build.gradle = ['yes']
  192. import git
  193. repo = git.repo.Repo(root_dir) # git repo
  194. for remote in git.Remote.iter_items(repo):
  195. if remote.name == 'origin':
  196. url = repo.remotes.origin.url
  197. if url.startswith('https://git'): # github, gitlab
  198. app.SourceCode = url.rstrip('.git')
  199. app.Repo = url
  200. break
  201. # repo.head.commit.binsha is a bytearray stored in a str
  202. build.commit = binascii.hexlify(bytearray(repo.head.commit.binsha))
  203. write_local_file = True
  204. elif options.url:
  205. root_dir, build_dir = get_metadata_from_url(app, options.url)
  206. build = metadata.Build()
  207. build.commit = '?'
  208. build.disable = 'Generated by import.py - check/set version fields and commit id'
  209. write_local_file = False
  210. else:
  211. raise FDroidException("Specify project url.")
  212. # Extract some information...
  213. paths = common.manifest_paths(root_dir, [])
  214. if paths:
  215. versionName, versionCode, package = common.parse_androidmanifests(paths, app)
  216. if not package:
  217. raise FDroidException(_("Couldn't find package ID"))
  218. if not versionName:
  219. logging.warn(_("Couldn't find latest version name"))
  220. if not versionCode:
  221. logging.warn(_("Couldn't find latest version code"))
  222. else:
  223. spec = os.path.join(root_dir, 'buildozer.spec')
  224. if os.path.exists(spec):
  225. defaults = {'orientation': 'landscape', 'icon': '',
  226. 'permissions': '', 'android.api': "18"}
  227. bconfig = ConfigParser(defaults, allow_no_value=True)
  228. bconfig.read(spec)
  229. package = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
  230. versionName = bconfig.get('app', 'version')
  231. versionCode = None
  232. else:
  233. raise FDroidException(_("No android or kivy project could be found. Specify --subdir?"))
  234. # Make sure it's actually new...
  235. if package in apps:
  236. raise FDroidException("Package " + package + " already exists")
  237. # Create a build line...
  238. build.versionName = versionName or '?'
  239. build.versionCode = versionCode or '?'
  240. if options.subdir:
  241. build.subdir = options.subdir
  242. if options.license:
  243. app.License = options.license
  244. if options.categories:
  245. app.Categories = options.categories
  246. if os.path.exists(os.path.join(root_dir, 'jni')):
  247. build.buildjni = ['yes']
  248. if os.path.exists(os.path.join(root_dir, 'build.gradle')):
  249. build.gradle = ['yes']
  250. metadata.post_metadata_parse(app)
  251. app.builds.append(build)
  252. if write_local_file:
  253. metadata.write_metadata('.fdroid.yml', app)
  254. else:
  255. # Keep the repo directory to save bandwidth...
  256. if not os.path.exists('build'):
  257. os.mkdir('build')
  258. if build_dir is not None:
  259. shutil.move(build_dir, os.path.join('build', package))
  260. with open('build/.fdroidvcs-' + package, 'w') as f:
  261. f.write(app.RepoType + ' ' + app.Repo)
  262. metadatapath = os.path.join('metadata', package + '.txt')
  263. metadata.write_metadata(metadatapath, app)
  264. logging.info("Wrote " + metadatapath)
  265. if __name__ == "__main__":
  266. main()