zip.bzl 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # Copied from google/nomulus project as we don't want to import the whole repository.
  2. ZIPPER = "@bazel_tools//tools/zip:zipper"
  3. def long_path(ctx, file_):
  4. """Constructs canonical runfile path relative to TEST_SRCDIR.
  5. Args:
  6. ctx: A Skylark rule context.
  7. file_: A File object that should appear in the runfiles for the test.
  8. Returns:
  9. A string path relative to TEST_SRCDIR suitable for use in tests and
  10. testing infrastructure.
  11. """
  12. if file_.short_path.startswith("../"):
  13. return file_.short_path[3:]
  14. if file_.owner and file_.owner.workspace_root:
  15. return file_.owner.workspace_root + "/" + file_.short_path
  16. return ctx.workspace_name + "/" + file_.short_path
  17. def collect_runfiles(targets):
  18. """Aggregates runfiles from targets.
  19. Args:
  20. targets: A list of Bazel targets.
  21. Returns:
  22. A list of Bazel files.
  23. """
  24. data = depset()
  25. for target in targets:
  26. if hasattr(target, "runfiles"):
  27. data += target.runfiles.files
  28. continue
  29. if hasattr(target, "data_runfiles"):
  30. data += target.data_runfiles.files
  31. if hasattr(target, "default_runfiles"):
  32. data += target.default_runfiles.files
  33. return data
  34. def _get_runfiles(target, attribute):
  35. runfiles = getattr(target, attribute, None)
  36. if runfiles:
  37. return runfiles.files
  38. return []
  39. def _zip_file(ctx):
  40. """Implementation of zip_file() rule."""
  41. for s, d in ctx.attr.mappings.items():
  42. if (s.startswith("/") or s.endswith("/") or
  43. d.startswith("/") or d.endswith("/")):
  44. fail("mappings should not begin or end with slash")
  45. srcs = depset()
  46. srcs += ctx.files.srcs
  47. srcs += ctx.files.data
  48. srcs += collect_runfiles(ctx.attr.data)
  49. mapped = _map_sources(ctx, srcs, ctx.attr.mappings)
  50. cmd = [
  51. "#!/bin/sh",
  52. "set -e",
  53. 'repo="$(pwd)"',
  54. 'zipper="${repo}/%s"' % ctx.file._zipper.path,
  55. 'archive="${repo}/%s"' % ctx.outputs.out.path,
  56. 'tmp="$(mktemp -d "${TMPDIR:-/tmp}/zip_file.XXXXXXXXXX")"',
  57. 'cd "${tmp}"',
  58. ]
  59. cmd += [
  60. '"${zipper}" x "${repo}/%s"' % dep.zip_file.path
  61. for dep in ctx.attr.deps
  62. ]
  63. cmd += ["rm %s" % filename for filename in ctx.attr.exclude]
  64. cmd += [
  65. 'mkdir -p "${tmp}/%s"' % zip_path
  66. for zip_path in depset(
  67. [
  68. zip_path[:zip_path.rindex("/")]
  69. for _, zip_path in mapped
  70. if "/" in zip_path
  71. ],
  72. )
  73. ]
  74. cmd += [
  75. 'ln -sf "${repo}/%s" "${tmp}/%s"' % (path, zip_path)
  76. for path, zip_path in mapped
  77. ]
  78. cmd += [
  79. ("find . | sed 1d | cut -c 3- | LC_ALL=C sort" +
  80. ' | xargs "${zipper}" cC "${archive}"'),
  81. 'cd "${repo}"',
  82. 'rm -rf "${tmp}"',
  83. ]
  84. script = ctx.new_file(ctx.bin_dir, "%s.sh" % ctx.label.name)
  85. ctx.file_action(output = script, content = "\n".join(cmd), executable = True)
  86. inputs = [ctx.file._zipper]
  87. inputs += [dep.zip_file for dep in ctx.attr.deps]
  88. inputs += list(srcs)
  89. ctx.action(
  90. inputs = inputs,
  91. outputs = [ctx.outputs.out],
  92. executable = script,
  93. mnemonic = "zip",
  94. progress_message = "Creating zip with %d inputs %s" % (
  95. len(inputs),
  96. ctx.label,
  97. ),
  98. )
  99. return struct(files = depset([ctx.outputs.out]), zip_file = ctx.outputs.out)
  100. def _map_sources(ctx, srcs, mappings):
  101. """Calculates paths in zip file for srcs."""
  102. # order mappings with more path components first
  103. mappings = sorted([
  104. (-len(source.split("/")), source, dest)
  105. for source, dest in mappings.items()
  106. ])
  107. # get rid of the integer part of tuple used for sorting
  108. mappings = [(source, dest) for _, source, dest in mappings]
  109. mappings_indexes = range(len(mappings))
  110. used = {i: False for i in mappings_indexes}
  111. mapped = []
  112. for file_ in srcs:
  113. run_path = long_path(ctx, file_)
  114. zip_path = None
  115. for i in mappings_indexes:
  116. source = mappings[i][0]
  117. dest = mappings[i][1]
  118. if not source:
  119. if dest:
  120. zip_path = dest + "/" + run_path
  121. else:
  122. zip_path = run_path
  123. elif source == run_path:
  124. if dest:
  125. zip_path = dest
  126. else:
  127. zip_path = run_path
  128. elif run_path.startswith(source + "/"):
  129. if dest:
  130. zip_path = dest + run_path[len(source):]
  131. else:
  132. zip_path = run_path[len(source) + 1:]
  133. else:
  134. continue
  135. used[i] = True
  136. break
  137. if not zip_path:
  138. fail("no mapping matched: " + run_path)
  139. mapped += [(file_.path, zip_path)]
  140. for i in mappings_indexes:
  141. if not used[i]:
  142. fail('superfluous mapping: "%s" -> "%s"' % mappings[i])
  143. return mapped
  144. pkg_zip = rule(
  145. implementation = _zip_file,
  146. attrs = {
  147. "out": attr.output(mandatory = True),
  148. "srcs": attr.label_list(allow_files = True),
  149. "data": attr.label_list(allow_files = True),
  150. "deps": attr.label_list(providers = ["zip_file"]),
  151. "exclude": attr.string_list(),
  152. "mappings": attr.string_dict(),
  153. "_zipper": attr.label(default = Label(ZIPPER), single_file = True),
  154. },
  155. )