gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/website/defs.bzl (about) 1 """Wrappers for website documentation.""" 2 3 load("//tools:defs.bzl", "short_path") 4 5 # DocInfo is a provider which simple adds sufficient metadata to the source 6 # files (and additional data files) so that a jeyll header can be constructed 7 # dynamically. This is done the via BUILD system so that the plain 8 # documentation files can be viewable without non-compliant markdown headers. 9 DocInfo = provider( 10 "Encapsulates information for a documentation page.", 11 fields = [ 12 "layout", 13 "description", 14 "permalink", 15 "category", 16 "subcategory", 17 "weight", 18 "editpath", 19 "authors", 20 "include_in_menu", 21 ], 22 ) 23 24 def _doc_impl(ctx): 25 return [ 26 DefaultInfo( 27 files = depset(ctx.files.src + ctx.files.data), 28 ), 29 DocInfo( 30 layout = ctx.attr.layout, 31 description = ctx.attr.description, 32 permalink = ctx.attr.permalink, 33 category = ctx.attr.category, 34 subcategory = ctx.attr.subcategory, 35 weight = ctx.attr.weight, 36 editpath = short_path(ctx.files.src[0].short_path), 37 authors = ctx.attr.authors, 38 include_in_menu = ctx.attr.include_in_menu, 39 ), 40 ] 41 42 doc = rule( 43 implementation = _doc_impl, 44 doc = "Annotate a document for jekyll headers.", 45 attrs = { 46 "src": attr.label( 47 doc = "The markdown source file.", 48 mandatory = True, 49 allow_single_file = True, 50 ), 51 "data": attr.label_list( 52 doc = "Additional data files (e.g. images).", 53 allow_files = True, 54 ), 55 "layout": attr.string( 56 doc = "The document layout.", 57 default = "docs", 58 ), 59 "description": attr.string( 60 doc = "The document description.", 61 default = "", 62 ), 63 "permalink": attr.string( 64 doc = "The document permalink.", 65 mandatory = True, 66 ), 67 "category": attr.string( 68 doc = "The document category.", 69 default = "", 70 ), 71 "subcategory": attr.string( 72 doc = "The document subcategory.", 73 default = "", 74 ), 75 "weight": attr.string( 76 doc = "The document weight.", 77 default = "50", 78 ), 79 "authors": attr.string_list(), 80 "include_in_menu": attr.bool( 81 doc = "Include document in the navigation menu.", 82 default = True, 83 ), 84 }, 85 ) 86 87 def _docs_impl(ctx): 88 # Tarball is the actual output. 89 tarball = ctx.actions.declare_file(ctx.label.name + ".tgz") 90 91 # But we need an intermediate builder to translate the files. 92 builder = ctx.actions.declare_file("%s-builder" % ctx.label.name) 93 builder_content = [ 94 "#!/bin/bash", 95 "set -euo pipefail", 96 "declare -r T=$(mktemp -d)", 97 "function cleanup {", 98 " rm -rf $T", 99 "}", 100 "trap cleanup EXIT", 101 ] 102 for dep in ctx.attr.deps: 103 doc = dep[DocInfo] 104 105 # Sanity check the permalink. 106 if not doc.permalink.endswith("/"): 107 fail("permalink %s for target %s should end with /" % ( 108 doc.permalink, 109 ctx.label.name, 110 )) 111 112 # Construct the header. 113 header = """\ 114 description: {description} 115 permalink: {permalink} 116 category: {category} 117 subcategory: {subcategory} 118 weight: {weight} 119 editpath: {editpath} 120 authors: {authors} 121 layout: {layout} 122 include_in_menu: {include_in_menu}""" 123 124 for f in dep.files.to_list(): 125 # Is this a markdown file? If not, then we ensure that it ends up 126 # in the same path as the permalink for relative addressing. 127 if not f.basename.endswith(".md"): 128 builder_content.append("mkdir -p $T/%s" % doc.permalink) 129 builder_content.append("cp %s $T/%s" % (f.path, doc.permalink)) 130 continue 131 132 # Is this a post? If yes, then we must put this in the _posts 133 # directory. This directory is treated specially with respect to 134 # pagination and page generation. 135 dest = f.short_path 136 if doc.layout == "post": 137 dest = "_posts/" + f.basename 138 builder_content.append("echo Processing %s... >&2" % f.short_path) 139 builder_content.append("mkdir -p $T/$(dirname %s)" % dest) 140 141 # Construct the header dynamically. We include the title field from 142 # the markdown itself, as this is the g3doc format required. The 143 # title will be injected by the web layout however, so we don't 144 # want this to appear in the document. 145 args = dict([(k, getattr(doc, k)) for k in dir(doc)]) 146 builder_content.append("title=\"$(grep -E '^# ' %s | head -n 1 | cut -d'#' -f2- || true)\"" % f.path) 147 builder_content.append("cat >$T/%s <<EOF" % dest) 148 builder_content.append("---") 149 builder_content.append("title: $title") 150 builder_content.append(header.format(**args)) 151 builder_content.append("---") 152 builder_content.append("EOF") 153 154 # To generate the final page, we need to strip out the title (which 155 # was pulled above to generate the annotation in the frontmatter, 156 # and substitute the [TOC] tag with the {% toc %} plugin tag. Note 157 # that the pipeline here is almost important, as the grep will 158 # return non-zero if the file is empty, but we ignore that within 159 # the pipeline. 160 builder_content.append("grep -v -E '^# ' %s | sed -e 's|^\\[TOC\\]$|- TOC\\n{:toc}|' >>$T/%s" % 161 (f.path, dest)) 162 163 builder_content.append("declare -r filename=$(readlink -m %s)" % tarball.path) 164 builder_content.append("(cd $T && tar -zcf \"${filename}\" .)\n") 165 ctx.actions.write(builder, "\n".join(builder_content), is_executable = True) 166 167 # Generate the tarball. 168 ctx.actions.run( 169 inputs = depset(ctx.files.deps), 170 outputs = [tarball], 171 progress_message = "Generating %s" % ctx.label, 172 executable = builder, 173 toolchain = None, 174 ) 175 return [DefaultInfo( 176 files = depset([tarball]), 177 )] 178 179 docs = rule( 180 implementation = _docs_impl, 181 doc = "Construct a site tarball from doc dependencies.", 182 attrs = { 183 "deps": attr.label_list( 184 doc = "All document dependencies.", 185 ), 186 }, 187 )