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