• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }},&nbsp;{{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">&#x2295</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">&nbsp;</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