1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package main 16 17import ( 18 "android/soong/android" 19 "bytes" 20 "html/template" 21 "io/ioutil" 22 "path/filepath" 23 "sort" 24 25 "github.com/google/blueprint/bootstrap" 26 "github.com/google/blueprint/bootstrap/bpdoc" 27) 28 29type perPackageTemplateData struct { 30 Name string 31 Modules []moduleTypeTemplateData 32} 33 34type moduleTypeTemplateData struct { 35 Name string 36 Synopsis template.HTML 37 Properties []bpdoc.Property 38} 39 40// The properties in this map are displayed first, according to their rank. 41// TODO(jungjw): consider providing module type-dependent ranking 42var propertyRank = map[string]int{ 43 "name": 0, 44 "src": 1, 45 "srcs": 2, 46 "exclude_srcs": 3, 47 "defaults": 4, 48 "host_supported": 5, 49 "device_supported": 6, 50} 51 52// For each module type, extract its documentation and convert it to the template data. 53func moduleTypeDocsToTemplates(moduleTypeList []*bpdoc.ModuleType) []moduleTypeTemplateData { 54 result := make([]moduleTypeTemplateData, 0) 55 56 // Combine properties from all PropertyStruct's and reorder them -- first the ones 57 // with rank, then the rest of the properties in alphabetic order. 58 for _, m := range moduleTypeList { 59 item := moduleTypeTemplateData{ 60 Name: m.Name, 61 Synopsis: m.Text, 62 Properties: make([]bpdoc.Property, 0), 63 } 64 props := make([]bpdoc.Property, 0) 65 for _, propStruct := range m.PropertyStructs { 66 props = append(props, propStruct.Properties...) 67 } 68 sort.Slice(props, func(i, j int) bool { 69 if rankI, ok := propertyRank[props[i].Name]; ok { 70 if rankJ, ok := propertyRank[props[j].Name]; ok { 71 return rankI < rankJ 72 } else { 73 return true 74 } 75 } 76 if _, ok := propertyRank[props[j].Name]; ok { 77 return false 78 } 79 return props[i].Name < props[j].Name 80 }) 81 // Eliminate top-level duplicates. TODO(jungjw): improve bpdoc to handle this. 82 previousPropertyName := "" 83 for _, prop := range props { 84 if prop.Name == previousPropertyName { 85 oldProp := &item.Properties[len(item.Properties)-1].Properties 86 bpdoc.CollapseDuplicateProperties(oldProp, &prop.Properties) 87 } else { 88 item.Properties = append(item.Properties, prop) 89 } 90 previousPropertyName = prop.Name 91 } 92 result = append(result, item) 93 } 94 sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name }) 95 return result 96} 97 98func getPackages(ctx *android.Context, config interface{}) ([]*bpdoc.Package, error) { 99 moduleTypeFactories := android.ModuleTypeFactoriesForDocs() 100 return bootstrap.ModuleTypeDocs(ctx.Context, config, moduleTypeFactories) 101} 102 103func writeDocs(ctx *android.Context, config interface{}, filename string) error { 104 packages, err := getPackages(ctx, config) 105 if err != nil { 106 return err 107 } 108 109 // Produce the top-level, package list page first. 110 tmpl := template.Must(template.Must(template.New("file").Parse(packageListTemplate)).Parse(copyBaseUrl)) 111 buf := &bytes.Buffer{} 112 err = tmpl.Execute(buf, packages) 113 if err == nil { 114 err = ioutil.WriteFile(filename, buf.Bytes(), 0666) 115 } 116 117 // Now, produce per-package module lists with detailed information, and a list 118 // of keywords. 119 keywordsTmpl := template.Must(template.New("file").Parse(keywordsTemplate)) 120 keywordsBuf := &bytes.Buffer{} 121 for _, pkg := range packages { 122 // We need a module name getter/setter function because I couldn't 123 // find a way to keep it in a variable defined within the template. 124 currentModuleName := "" 125 tmpl := template.Must( 126 template.Must(template.New("file").Funcs(map[string]interface{}{ 127 "setModule": func(moduleName string) string { 128 currentModuleName = moduleName 129 return "" 130 }, 131 "getModule": func() string { 132 return currentModuleName 133 }, 134 }).Parse(perPackageTemplate)).Parse(copyBaseUrl)) 135 buf := &bytes.Buffer{} 136 modules := moduleTypeDocsToTemplates(pkg.ModuleTypes) 137 data := perPackageTemplateData{Name: pkg.Name, Modules: modules} 138 err = tmpl.Execute(buf, data) 139 if err != nil { 140 return err 141 } 142 pkgFileName := filepath.Join(filepath.Dir(filename), pkg.Name+".html") 143 err = ioutil.WriteFile(pkgFileName, buf.Bytes(), 0666) 144 if err != nil { 145 return err 146 } 147 err = keywordsTmpl.Execute(keywordsBuf, data) 148 if err != nil { 149 return err 150 } 151 } 152 153 // Write out list of keywords. This includes all module and property names, which is useful for 154 // building syntax highlighters. 155 keywordsFilename := filepath.Join(filepath.Dir(filename), "keywords.txt") 156 err = ioutil.WriteFile(keywordsFilename, keywordsBuf.Bytes(), 0666) 157 158 return err 159} 160 161// TODO(jungjw): Consider ordering by name. 162const ( 163 packageListTemplate = ` 164<html> 165<head> 166<title>Build Docs</title> 167<style> 168#main { 169 padding: 48px; 170} 171 172table{ 173 table-layout: fixed; 174} 175 176td { 177 word-wrap:break-word; 178} 179 180/* The following entries are copied from source.android.com's css file. */ 181td,td code { 182 color: #202124 183} 184 185th,th code { 186 color: #fff; 187 font: 500 16px/24px Roboto,sans-serif 188} 189 190td,table.responsive tr:not(.alt) td td:first-child,table.responsive td tr:not(.alt) td:first-child { 191 background: rgba(255,255,255,.95); 192 vertical-align: top 193} 194 195td,td code { 196 padding: 7px 8px 8px 197} 198 199tr { 200 border: 0; 201 background: #78909c; 202 border-top: 1px solid #cfd8dc 203} 204 205th,td { 206 border: 0; 207 margin: 0; 208 text-align: left 209} 210 211th { 212 height: 48px; 213 padding: 8px; 214 vertical-align: middle 215} 216 217table { 218 border: 0; 219 border-collapse: collapse; 220 border-spacing: 0; 221 font: 14px/20px Roboto,sans-serif; 222 margin: 16px 0; 223 width: 100% 224} 225 226h1 { 227 color: #80868b; 228 font: 300 34px/40px Roboto,sans-serif; 229 letter-spacing: -0.01em; 230 margin: 40px 0 20px 231} 232 233h1,h2,h3,h4,h5,h6 { 234 overflow: hidden; 235 padding: 0; 236 text-overflow: ellipsis 237} 238 239:link,:visited { 240 color: #039be5; 241 outline: 0; 242 text-decoration: none 243} 244 245body,html { 246 color: #202124; 247 font: 400 16px/24px Roboto,sans-serif; 248 -moz-osx-font-smoothing: grayscale; 249 -webkit-font-smoothing: antialiased; 250 height: 100%; 251 margin: 0; 252 -webkit-text-size-adjust: 100%; 253 -moz-text-size-adjust: 100%; 254 -ms-text-size-adjust: 100%; 255 text-size-adjust: 100% 256} 257 258html { 259 -webkit-box-sizing: border-box; 260 box-sizing: border-box 261} 262 263*,*::before,*::after { 264 -webkit-box-sizing: inherit; 265 box-sizing: inherit 266} 267 268body,div,dl,dd,form,img,input,figure,menu { 269 margin: 0; 270 padding: 0 271} 272</style> 273{{template "copyBaseUrl"}} 274</head> 275<body> 276<div id="main"> 277<H1>Soong Modules Reference</H1> 278The latest versions of Android use the Soong build system, which greatly simplifies build 279configuration over the previous Make-based system. This site contains the generated reference 280files for the Soong build system. 281 282<table class="module_types" summary="Table of Soong module types sorted by package"> 283 <thead> 284 <tr> 285 <th style="width:20%">Package</th> 286 <th style="width:80%">Module types</th> 287 </tr> 288 </thead> 289 <tbody> 290 {{range $pkg := .}} 291 <tr> 292 <td>{{.Path}}</td> 293 <td> 294 {{range $i, $mod := .ModuleTypes}}{{if $i}}, {{end}}<a href="{{$pkg.Name}}.html#{{$mod.Name}}">{{$mod.Name}}</a>{{end}} 295 </td> 296 </tr> 297 {{end}} 298 </tbody> 299</table> 300</div> 301</body> 302</html> 303` 304 305 perPackageTemplate = ` 306<html> 307<head> 308<title>Build Docs</title> 309<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"> 310<style> 311.accordion,.simple{margin-left:1.5em;text-indent:-1.5em;margin-top:.25em} 312.collapsible{border-width:0 0 0 1;margin-left:.25em;padding-left:.25em;border-style:solid; 313 border-color:grey;display:none;} 314span.fixed{display: block; float: left; clear: left; width: 1em;} 315ul { 316 list-style-type: none; 317 margin: 0; 318 padding: 0; 319 width: 30ch; 320 background-color: #f1f1f1; 321 position: fixed; 322 height: 100%; 323 overflow: auto; 324} 325li a { 326 display: block; 327 color: #000; 328 padding: 8px 16px; 329 text-decoration: none; 330} 331 332li a.active { 333 background-color: #4CAF50; 334 color: white; 335} 336 337li a:hover:not(.active) { 338 background-color: #555; 339 color: white; 340} 341</style> 342{{template "copyBaseUrl"}} 343</head> 344<body> 345{{- /* Fixed sidebar with module types */ -}} 346<ul> 347<li><h3>{{.Name}} package</h3></li> 348{{range $moduleType := .Modules}}<li><a href="{{$.Name}}.html#{{$moduleType.Name}}">{{$moduleType.Name}}</a></li> 349{{end -}} 350</ul> 351{{/* Main panel with H1 section per module type */}} 352<div style="margin-left:30ch;padding:1px 16px;"> 353{{range $moduleType := .Modules}} 354 {{setModule $moduleType.Name}} 355 <p> 356 <h2 id="{{$moduleType.Name}}">{{$moduleType.Name}}</h2> 357 {{if $moduleType.Synopsis }}{{$moduleType.Synopsis}}{{else}}<i>Missing synopsis</i>{{end}} 358 {{- /* Comma-separated list of module attributes' links module attributes */ -}} 359 <div class="breadcrumb"> 360 {{range $i,$prop := $moduleType.Properties }} 361 {{ if gt $i 0 }}, {{end -}} 362 <a href={{$.Name}}.html#{{getModule}}.{{$prop.Name}}>{{$prop.Name}}</a> 363 {{- end -}} 364 </div> 365 {{- /* Property description */ -}} 366 {{- template "properties" $moduleType.Properties -}} 367{{- end -}} 368 369{{define "properties" -}} 370 {{range .}} 371 {{if .Properties -}} 372 <div class="accordion" id="{{getModule}}.{{.Name}}"> 373 <span class="fixed">⊕</span><b>{{.Name}}</b> 374 {{- range .OtherNames -}}, {{.}}{{- end -}} 375 </div> 376 <div class="collapsible"> 377 {{- .Text}} {{range .OtherTexts}}{{.}}{{end}} 378 {{template "properties" .Properties -}} 379 </div> 380 {{- else -}} 381 <div class="simple" id="{{getModule}}.{{.Name}}"> 382 <span class="fixed"> </span><b>{{.Name}} {{range .OtherNames}}, {{.}}{{end -}}</b> 383 <i>{{.Type}}</i> 384 {{- if .Text -}}{{if ne .Text "\n"}}, {{end}}{{.Text}}{{- end -}} 385 {{- with .OtherTexts -}}{{.}}{{- end -}} 386 {{- if .Default -}}<i>Default: {{.Default}}</i>{{- end -}} 387 </div> 388 {{- end}} 389 {{- end -}} 390{{- end -}} 391</div> 392<script> 393 accordions = document.getElementsByClassName('accordion'); 394 for (i=0; i < accordions.length; ++i) { 395 accordions[i].addEventListener("click", function() { 396 var panel = this.nextElementSibling; 397 var child = this.firstElementChild; 398 if (panel.style.display === "block") { 399 panel.style.display = "none"; 400 child.textContent = '\u2295'; 401 } else { 402 panel.style.display = "block"; 403 child.textContent = '\u2296'; 404 } 405 }); 406 } 407</script> 408</body> 409` 410 411 copyBaseUrl = ` 412{{define "copyBaseUrl"}} 413<script type="text/javascript"> 414window.addEventListener('message', (e) => { 415 if (e != null && e.data != null && e.data.type === "SET_BASE" && e.data.base != null) { 416 const existingBase = document.querySelector('base'); 417 if (existingBase != null) { 418 existingBase.parentElement.removeChild(existingBase); 419 } 420 421 const base = document.createElement('base'); 422 base.setAttribute('href', e.data.base); 423 document.head.appendChild(base); 424 } 425}); 426</script> 427{{end}} 428` 429 430 keywordsTemplate = ` 431{{range $moduleType := .Modules}}{{$moduleType.Name}}:{{range $property := $moduleType.Properties}}{{$property.Name}},{{end}} 432{{end}} 433` 434) 435