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 "reflect" 24 "sort" 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 "defautls": 3, 48 "host_supported": 4, 49 "device_supported": 5, 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 writeDocs(ctx *android.Context, filename string) error { 99 moduleTypeFactories := android.ModuleTypeFactories() 100 bpModuleTypeFactories := make(map[string]reflect.Value) 101 for moduleType, factory := range moduleTypeFactories { 102 bpModuleTypeFactories[moduleType] = reflect.ValueOf(factory) 103 } 104 105 packages, err := bootstrap.ModuleTypeDocs(ctx.Context, bpModuleTypeFactories) 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. 119 for _, pkg := range packages { 120 // We need a module name getter/setter function because I couldn't 121 // find a way to keep it in a variable defined within the template. 122 currentModuleName := "" 123 tmpl := template.Must( 124 template.Must(template.New("file").Funcs(map[string]interface{}{ 125 "setModule": func(moduleName string) string { 126 currentModuleName = moduleName 127 return "" 128 }, 129 "getModule": func() string { 130 return currentModuleName 131 }, 132 }).Parse(perPackageTemplate)).Parse(copyBaseUrl)) 133 buf := &bytes.Buffer{} 134 modules := moduleTypeDocsToTemplates(pkg.ModuleTypes) 135 data := perPackageTemplateData{Name: pkg.Name, Modules: modules} 136 err = tmpl.Execute(buf, data) 137 if err != nil { 138 return err 139 } 140 pkgFileName := filepath.Join(filepath.Dir(filename), pkg.Name+".html") 141 err = ioutil.WriteFile(pkgFileName, buf.Bytes(), 0666) 142 if err != nil { 143 return err 144 } 145 } 146 return err 147} 148 149// TODO(jungjw): Consider ordering by name. 150const ( 151 packageListTemplate = ` 152<html> 153<head> 154<title>Build Docs</title> 155<style> 156#main { 157 padding: 48px; 158} 159 160table{ 161 table-layout: fixed; 162} 163 164td { 165 word-wrap:break-word; 166} 167 168/* The following entries are copied from source.android.com's css file. */ 169td,td code { 170 color: #202124 171} 172 173th,th code { 174 color: #fff; 175 font: 500 16px/24px Roboto,sans-serif 176} 177 178td,table.responsive tr:not(.alt) td td:first-child,table.responsive td tr:not(.alt) td:first-child { 179 background: rgba(255,255,255,.95); 180 vertical-align: top 181} 182 183td,td code { 184 padding: 7px 8px 8px 185} 186 187tr { 188 border: 0; 189 background: #78909c; 190 border-top: 1px solid #cfd8dc 191} 192 193th,td { 194 border: 0; 195 margin: 0; 196 text-align: left 197} 198 199th { 200 height: 48px; 201 padding: 8px; 202 vertical-align: middle 203} 204 205table { 206 border: 0; 207 border-collapse: collapse; 208 border-spacing: 0; 209 font: 14px/20px Roboto,sans-serif; 210 margin: 16px 0; 211 width: 100% 212} 213 214h1 { 215 color: #80868b; 216 font: 300 34px/40px Roboto,sans-serif; 217 letter-spacing: -0.01em; 218 margin: 40px 0 20px 219} 220 221h1,h2,h3,h4,h5,h6 { 222 overflow: hidden; 223 padding: 0; 224 text-overflow: ellipsis 225} 226 227:link,:visited { 228 color: #039be5; 229 outline: 0; 230 text-decoration: none 231} 232 233body,html { 234 color: #202124; 235 font: 400 16px/24px Roboto,sans-serif; 236 -moz-osx-font-smoothing: grayscale; 237 -webkit-font-smoothing: antialiased; 238 height: 100%; 239 margin: 0; 240 -webkit-text-size-adjust: 100%; 241 -moz-text-size-adjust: 100%; 242 -ms-text-size-adjust: 100%; 243 text-size-adjust: 100% 244} 245 246html { 247 -webkit-box-sizing: border-box; 248 box-sizing: border-box 249} 250 251*,*::before,*::after { 252 -webkit-box-sizing: inherit; 253 box-sizing: inherit 254} 255 256body,div,dl,dd,form,img,input,figure,menu { 257 margin: 0; 258 padding: 0 259} 260</style> 261{{template "copyBaseUrl"}} 262</head> 263<body> 264<div id="main"> 265<H1>Soong Modules Reference</H1> 266The latest versions of Android use the Soong build system, which greatly simplifies build 267configuration over the previous Make-based system. This site contains the generated reference 268files for the Soong build system. 269 270<table class="module_types" summary="Table of Soong module types sorted by package"> 271 <thead> 272 <tr> 273 <th style="width:20%">Package</th> 274 <th style="width:80%">Module types</th> 275 </tr> 276 </thead> 277 <tbody> 278 {{range $pkg := .}} 279 <tr> 280 <td>{{.Path}}</td> 281 <td> 282 {{range $i, $mod := .ModuleTypes}}{{if $i}}, {{end}}<a href="{{$pkg.Name}}.html#{{$mod.Name}}">{{$mod.Name}}</a>{{end}} 283 </td> 284 </tr> 285 {{end}} 286 </tbody> 287</table> 288</div> 289</body> 290</html> 291` 292 293 perPackageTemplate = ` 294<html> 295<head> 296<title>Build Docs</title> 297<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"> 298<style> 299.accordion,.simple{margin-left:1.5em;text-indent:-1.5em;margin-top:.25em} 300.collapsible{border-width:0 0 0 1;margin-left:.25em;padding-left:.25em;border-style:solid; 301 border-color:grey;display:none;} 302span.fixed{display: block; float: left; clear: left; width: 1em;} 303ul { 304 list-style-type: none; 305 margin: 0; 306 padding: 0; 307 width: 30ch; 308 background-color: #f1f1f1; 309 position: fixed; 310 height: 100%; 311 overflow: auto; 312} 313li a { 314 display: block; 315 color: #000; 316 padding: 8px 16px; 317 text-decoration: none; 318} 319 320li a.active { 321 background-color: #4CAF50; 322 color: white; 323} 324 325li a:hover:not(.active) { 326 background-color: #555; 327 color: white; 328} 329</style> 330{{template "copyBaseUrl"}} 331</head> 332<body> 333{{- /* Fixed sidebar with module types */ -}} 334<ul> 335<li><h3>{{.Name}} package</h3></li> 336{{range $moduleType := .Modules}}<li><a href="{{$.Name}}.html#{{$moduleType.Name}}">{{$moduleType.Name}}</a></li> 337{{end -}} 338</ul> 339{{/* Main panel with H1 section per module type */}} 340<div style="margin-left:30ch;padding:1px 16px;"> 341{{range $moduleType := .Modules}} 342 {{setModule $moduleType.Name}} 343 <p> 344 <h2 id="{{$moduleType.Name}}">{{$moduleType.Name}}</h2> 345 {{if $moduleType.Synopsis }}{{$moduleType.Synopsis}}{{else}}<i>Missing synopsis</i>{{end}} 346 {{- /* Comma-separated list of module attributes' links module attributes */ -}} 347 <div class="breadcrumb"> 348 {{range $i,$prop := $moduleType.Properties }} 349 {{ if gt $i 0 }}, {{end -}} 350 <a href={{$.Name}}.html#{{getModule}}.{{$prop.Name}}>{{$prop.Name}}</a> 351 {{- end -}} 352 </div> 353 {{- /* Property description */ -}} 354 {{- template "properties" $moduleType.Properties -}} 355{{- end -}} 356 357{{define "properties" -}} 358 {{range .}} 359 {{if .Properties -}} 360 <div class="accordion" id="{{getModule}}.{{.Name}}"> 361 <span class="fixed">⊕</span><b>{{.Name}}</b> 362 {{- range .OtherNames -}}, {{.}}{{- end -}} 363 </div> 364 <div class="collapsible"> 365 {{- .Text}} {{range .OtherTexts}}{{.}}{{end}} 366 {{template "properties" .Properties -}} 367 </div> 368 {{- else -}} 369 <div class="simple" id="{{getModule}}.{{.Name}}"> 370 <span class="fixed"> </span><b>{{.Name}} {{range .OtherNames}}, {{.}}{{end -}}</b> 371 <i>{{.Type}}</i> 372 {{- if .Text -}}{{if ne .Text "\n"}}, {{end}}{{.Text}}{{- end -}} 373 {{- with .OtherTexts -}}{{.}}{{- end -}} 374 {{- if .Default -}}<i>Default: {{.Default}}</i>{{- end -}} 375 </div> 376 {{- end}} 377 {{- end -}} 378{{- end -}} 379</div> 380<script> 381 accordions = document.getElementsByClassName('accordion'); 382 for (i=0; i < accordions.length; ++i) { 383 accordions[i].addEventListener("click", function() { 384 var panel = this.nextElementSibling; 385 var child = this.firstElementChild; 386 if (panel.style.display === "block") { 387 panel.style.display = "none"; 388 child.textContent = '\u2295'; 389 } else { 390 panel.style.display = "block"; 391 child.textContent = '\u2296'; 392 } 393 }); 394 } 395</script> 396</body> 397` 398 399 copyBaseUrl = ` 400{{define "copyBaseUrl"}} 401<script type="text/javascript"> 402window.addEventListener('message', (e) => { 403 if (e != null && e.data != null && e.data.type === "SET_BASE" && e.data.base != null) { 404 const existingBase = document.querySelector('base'); 405 if (existingBase != null) { 406 existingBase.parentElement.removeChild(existingBase); 407 } 408 409 const base = document.createElement('base'); 410 base.setAttribute('href', e.data.base); 411 document.head.appendChild(base); 412 } 413}); 414</script> 415{{end}} 416` 417) 418