zip.bzl 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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(transitive = [depset(ctx.files.srcs),depset(ctx.files.data),depset(collect_runfiles(ctx.attr.data))])
  46. mapped = _map_sources(ctx, srcs, ctx.attr.mappings)
  47. cmd = [
  48. "#!/bin/sh",
  49. "set -e",
  50. 'repo="$(pwd)"',
  51. 'zipper="${repo}/%s"' % ctx.file._zipper.path,
  52. 'archive="${repo}/%s"' % ctx.outputs.out.path,
  53. 'tmp="$(mktemp -d "${TMPDIR:-/tmp}/zip_file.XXXXXXXXXX")"',
  54. 'cd "${tmp}"',
  55. ]
  56. cmd += [
  57. '"${zipper}" x "${repo}/%s"' % dep.zip_file.path
  58. for dep in ctx.attr.deps
  59. ]
  60. cmd += ["rm %s" % filename for filename in ctx.attr.exclude]
  61. cmd += [
  62. 'mkdir -p "${tmp}/%s"' % zip_path
  63. for zip_path in depset(
  64. [
  65. zip_path[:zip_path.rindex("/")]
  66. for _, zip_path in mapped
  67. if "/" in zip_path
  68. ],
  69. ).to_list()
  70. ]
  71. cmd += [
  72. 'ln -sf "${repo}/%s" "${tmp}/%s"' % (path, zip_path)
  73. for path, zip_path in mapped
  74. ]
  75. cmd += [
  76. ("find . | sed 1d | cut -c 3- | LC_ALL=C sort" +
  77. ' | xargs "${zipper}" cC "${archive}"'),
  78. 'cd "${repo}"',
  79. 'rm -rf "${tmp}"',
  80. ]
  81. script = ctx.actions.declare_file("%s/%s.sh" % (ctx.bin_dir, ctx.label.name))
  82. ctx.actions.write(output = script, content = "\n".join(cmd), is_executable = True)
  83. inputs = [ctx.file._zipper]
  84. inputs += [dep.zip_file for dep in ctx.attr.deps]
  85. inputs += list(srcs.to_list())
  86. ctx.actions.run(
  87. inputs = inputs,
  88. outputs = [ctx.outputs.out],
  89. executable = script,
  90. mnemonic = "zip",
  91. progress_message = "Creating zip with %d inputs %s" % (
  92. len(inputs),
  93. ctx.label,
  94. ),
  95. )
  96. return struct(files = depset([ctx.outputs.out]), zip_file = ctx.outputs.out)
  97. def _map_sources(ctx, srcs, mappings):
  98. """Calculates paths in zip file for srcs."""
  99. # order mappings with more path components first
  100. mappings = sorted([
  101. (-len(source.split("/")), source, dest)
  102. for source, dest in mappings.items()
  103. ])
  104. # get rid of the integer part of tuple used for sorting
  105. mappings = [(source, dest) for _, source, dest in mappings]
  106. mappings_indexes = range(len(mappings))
  107. used = {i: False for i in mappings_indexes}
  108. mapped = []
  109. for file_ in srcs.to_list():
  110. run_path = long_path(ctx, file_)
  111. zip_path = None
  112. for i in mappings_indexes:
  113. source = mappings[i][0]
  114. dest = mappings[i][1]
  115. if not source:
  116. if dest:
  117. zip_path = dest + "/" + run_path
  118. else:
  119. zip_path = run_path
  120. elif source == run_path:
  121. if dest:
  122. zip_path = dest
  123. else:
  124. zip_path = run_path
  125. elif run_path.startswith(source + "/"):
  126. if dest:
  127. zip_path = dest + run_path[len(source):]
  128. else:
  129. zip_path = run_path[len(source) + 1:]
  130. else:
  131. continue
  132. used[i] = True
  133. break
  134. if not zip_path:
  135. fail("no mapping matched: " + run_path)
  136. mapped += [(file_.path, zip_path)]
  137. for i in mappings_indexes:
  138. if not used[i]:
  139. fail('superfluous mapping: "%s" -> "%s"' % mappings[i])
  140. return mapped
  141. pkg_zip = rule(
  142. implementation = _zip_file,
  143. attrs = {
  144. "out": attr.output(mandatory = True),
  145. "srcs": attr.label_list(allow_files = True),
  146. "data": attr.label_list(allow_files = True),
  147. "deps": attr.label_list(providers = ["zip_file"]),
  148. "exclude": attr.string_list(),
  149. "mappings": attr.string_dict(),
  150. "_zipper": attr.label(default = Label(ZIPPER), allow_single_file = True),
  151. },
  152. )