msubprojects.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import os, subprocess
  2. from . import mlog
  3. from .mesonlib import Popen_safe
  4. from .wrap.wrap import API_ROOT, PackageDefinition, Resolver, WrapException
  5. from .wrap import wraptool
  6. def update_wrapdb_file(wrap, repo_dir, options):
  7. patch_url = wrap.get('patch_url')
  8. branch, revision = wraptool.parse_patch_url(patch_url)
  9. new_branch, new_revision = wraptool.get_latest_version(wrap.name)
  10. if new_branch == branch and new_revision == revision:
  11. mlog.log(' -> Up to date.')
  12. return
  13. wraptool.update_wrap_file(wrap.filename, wrap.name, new_branch, new_revision)
  14. msg = [' -> New wrap file downloaded.']
  15. # Meson reconfigure won't use the new wrap file as long as the source
  16. # directory exists. We don't delete it ourself to avoid data loss in case
  17. # user has changes in their copy.
  18. if os.path.isdir(repo_dir):
  19. msg += ['To use it, delete', mlog.bold(repo_dir), 'and run', mlog.bold('meson --reconfigure')]
  20. mlog.log(*msg)
  21. def update_file(wrap, repo_dir, options):
  22. patch_url = wrap.values.get('patch_url', '')
  23. if patch_url.startswith(API_ROOT):
  24. update_wrapdb_file(wrap, repo_dir, options)
  25. elif not os.path.isdir(repo_dir):
  26. # The subproject is not needed, or it is a tarball extracted in
  27. # 'libfoo-1.0' directory and the version has been bumped and the new
  28. # directory is 'libfoo-2.0'. In that case forcing a meson
  29. # reconfigure will download and use the new tarball.
  30. mlog.log(' -> Subproject has not been checked out. Run', mlog.bold('meson --reconfigure'), 'to fetch it if needed.')
  31. else:
  32. # The subproject has not changed, or the new source and/or patch
  33. # tarballs should be extracted in the same directory than previous
  34. # version.
  35. mlog.log(' -> Subproject has not changed, or the new source/patch needs to be extracted on the same location.\n' +
  36. ' In that case, delete', mlog.bold(repo_dir), 'and run', mlog.bold('meson --reconfigure'))
  37. def git(cmd, workingdir):
  38. return subprocess.check_output(['git', '-C', workingdir] + cmd,
  39. stderr=subprocess.STDOUT).decode()
  40. def git_show(repo_dir):
  41. commit_message = git(['show', '--quiet', '--pretty=format:%h%n%d%n%s%n[%an]'], repo_dir)
  42. parts = [s.strip() for s in commit_message.split('\n')]
  43. mlog.log(' ->', mlog.yellow(parts[0]), mlog.red(parts[1]), parts[2], mlog.blue(parts[3]))
  44. def update_git(wrap, repo_dir, options):
  45. if not os.path.isdir(repo_dir):
  46. mlog.log(' -> Not used.')
  47. return
  48. revision = wrap.get('revision')
  49. ret = git(['rev-parse', '--abbrev-ref', 'HEAD'], repo_dir).strip()
  50. if ret == 'HEAD':
  51. try:
  52. # We are currently in detached mode, just checkout the new revision
  53. git(['fetch'], repo_dir)
  54. git(['checkout', revision], repo_dir)
  55. except subprocess.CalledProcessError as e:
  56. out = e.output.decode().strip()
  57. mlog.log(' -> Could not checkout revision', mlog.cyan(revision))
  58. mlog.log(mlog.red(out))
  59. mlog.log(mlog.red(str(e)))
  60. return
  61. elif ret == revision:
  62. try:
  63. # We are in the same branch, pull latest commits
  64. git(['-c', 'rebase.autoStash=true', 'pull', '--rebase'], repo_dir)
  65. except subprocess.CalledProcessError as e:
  66. out = e.output.decode().strip()
  67. mlog.log(' -> Could not rebase', mlog.bold(repo_dir), 'please fix and try again.')
  68. mlog.log(mlog.red(out))
  69. mlog.log(mlog.red(str(e)))
  70. return
  71. else:
  72. # We are in another branch, probably user created their own branch and
  73. # we should rebase it on top of wrap's branch.
  74. if options.rebase:
  75. try:
  76. git(['fetch'], repo_dir)
  77. git(['-c', 'rebase.autoStash=true', 'rebase', revision], repo_dir)
  78. except subprocess.CalledProcessError as e:
  79. out = e.output.decode().strip()
  80. mlog.log(' -> Could not rebase', mlog.bold(repo_dir), 'please fix and try again.')
  81. mlog.log(mlog.red(out))
  82. mlog.log(mlog.red(str(e)))
  83. return
  84. else:
  85. mlog.log(' -> Target revision is', mlog.bold(revision), 'but currently in branch is', mlog.bold(ret), '\n' +
  86. ' To rebase your branch on top of', mlog.bold(revision), 'use', mlog.bold('--rebase'), 'option.')
  87. return
  88. git(['submodule', 'update'], repo_dir)
  89. git_show(repo_dir)
  90. def update_hg(wrap, repo_dir, options):
  91. if not os.path.isdir(repo_dir):
  92. mlog.log(' -> Not used.')
  93. return
  94. revno = wrap.get('revision')
  95. if revno.lower() == 'tip':
  96. # Failure to do pull is not a fatal error,
  97. # because otherwise you can't develop without
  98. # a working net connection.
  99. subprocess.call(['hg', 'pull'], cwd=repo_dir)
  100. else:
  101. if subprocess.call(['hg', 'checkout', revno], cwd=repo_dir) != 0:
  102. subprocess.check_call(['hg', 'pull'], cwd=repo_dir)
  103. subprocess.check_call(['hg', 'checkout', revno], cwd=repo_dir)
  104. def update_svn(wrap, repo_dir, options):
  105. if not os.path.isdir(repo_dir):
  106. mlog.log(' -> Not used.')
  107. return
  108. revno = wrap.get('revision')
  109. p, out = Popen_safe(['svn', 'info', '--show-item', 'revision', repo_dir])
  110. current_revno = out
  111. if current_revno == revno:
  112. return
  113. if revno.lower() == 'head':
  114. # Failure to do pull is not a fatal error,
  115. # because otherwise you can't develop without
  116. # a working net connection.
  117. subprocess.call(['svn', 'update'], cwd=repo_dir)
  118. else:
  119. subprocess.check_call(['svn', 'update', '-r', revno], cwd=repo_dir)
  120. def update(wrap, repo_dir, options):
  121. mlog.log('Updating %s...' % wrap.name)
  122. if wrap.type == 'file':
  123. update_file(wrap, repo_dir, options)
  124. elif wrap.type == 'git':
  125. update_git(wrap, repo_dir, options)
  126. elif wrap.type == 'hg':
  127. update_hg(wrap, repo_dir, options)
  128. elif wrap.type == 'svn':
  129. update_svn(wrap, repo_dir, options)
  130. else:
  131. mlog.log(' -> Cannot update', wrap.type, 'subproject')
  132. def checkout(wrap, repo_dir, options):
  133. if wrap.type != 'git' or not os.path.isdir(repo_dir):
  134. return
  135. branch_name = options.branch_name if options.branch_name else wrap.get('revision')
  136. cmd = ['checkout', branch_name, '--']
  137. if options.b:
  138. cmd.insert(1, '-b')
  139. mlog.log('Checkout %s in %s...' % (branch_name, wrap.name))
  140. try:
  141. git(cmd, repo_dir)
  142. git_show(repo_dir)
  143. except subprocess.CalledProcessError as e:
  144. out = e.output.decode().strip()
  145. mlog.log(' -> ', mlog.red(out))
  146. def download(wrap, repo_dir, options):
  147. mlog.log('Download %s...' % wrap.name)
  148. if os.path.isdir(repo_dir):
  149. mlog.log(' -> Already downloaded')
  150. return
  151. try:
  152. r = Resolver(os.path.dirname(repo_dir))
  153. r.resolve(wrap.name)
  154. mlog.log(' -> done')
  155. except WrapException as e:
  156. mlog.log(' ->', mlog.red(str(e)))
  157. def add_common_arguments(p):
  158. p.add_argument('--sourcedir', default='.',
  159. help='Path to source directory')
  160. p.add_argument('subprojects', nargs='*',
  161. help='List of subprojects (default: all)')
  162. def add_arguments(parser):
  163. subparsers = parser.add_subparsers(title='Commands', dest='command')
  164. subparsers.required = True
  165. p = subparsers.add_parser('update', help='Update all subprojects from wrap files')
  166. p.add_argument('--rebase', default=False, action='store_true',
  167. help='Rebase your branch on top of wrap\'s revision (git only)')
  168. add_common_arguments(p)
  169. p.set_defaults(subprojects_func=update)
  170. p = subparsers.add_parser('checkout', help='Checkout a branch (git only)')
  171. p.add_argument('-b', default=False, action='store_true',
  172. help='Create a new branch')
  173. p.add_argument('branch_name', nargs='?',
  174. help='Name of the branch to checkout or create (default: revision set in wrap file)')
  175. add_common_arguments(p)
  176. p.set_defaults(subprojects_func=checkout)
  177. p = subparsers.add_parser('download', help='Ensure subprojects are fetched, even if not in use. ' +
  178. 'Already downloaded subprojects are not modified. ' +
  179. 'This can be used to pre-fetch all subprojects and avoid downloads during configure.')
  180. add_common_arguments(p)
  181. p.set_defaults(subprojects_func=download)
  182. def run(options):
  183. src_dir = os.path.relpath(os.path.realpath(options.sourcedir))
  184. if not os.path.isfile(os.path.join(src_dir, 'meson.build')):
  185. mlog.error('Directory', mlog.bold(src_dir), 'does not seem to be a Meson source directory.')
  186. return 1
  187. subprojects_dir = os.path.join(src_dir, 'subprojects')
  188. if not os.path.isdir(subprojects_dir):
  189. mlog.log('Directory', mlog.bold(src_dir), 'does not seem to have subprojects.')
  190. return 0
  191. files = []
  192. for name in options.subprojects:
  193. f = os.path.join(subprojects_dir, name + '.wrap')
  194. if not os.path.isfile(f):
  195. mlog.error('Subproject', mlog.bold(name), 'not found.')
  196. return 1
  197. else:
  198. files.append(f)
  199. if not files:
  200. for f in os.listdir(subprojects_dir):
  201. if f.endswith('.wrap'):
  202. files.append(os.path.join(subprojects_dir, f))
  203. for f in files:
  204. wrap = PackageDefinition(f)
  205. directory = wrap.values.get('directory', wrap.name)
  206. repo_dir = os.path.join(subprojects_dir, directory)
  207. options.subprojects_func(wrap, repo_dir, options)
  208. return 0