blob: b0f05b0c9aeb2e649a46af7822bc356f732438dd [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package codegen
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
cpp "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen_cpp"
)
// CodeGenerationMode controls which subset of bindings code to generate.
type CodeGenerationMode int
const (
_ CodeGenerationMode = iota
// Monolithic generates all of HLCPP bindings in one header/source pair.
Monolithic
// OnlyGenerateDomainObjects generates only the domain objects
// (structs, tables, unions, enums, bits, request/response codecs, etc.)
OnlyGenerateDomainObjects
// TODO(fxbug.dev/64093): Add a third generation mode which only generates
// proxies and stubs.
)
// Config is the configuration data passed to the HLCPP generator.
type Config interface {
Header() string
Source() string
TestBase() string
// The directory to which C and C++ includes should be relative.
IncludeBase() string
// The path suffix after the library path when referencing includes.
IncludeStem() string
}
type FidlGenerator struct {
tmpls *template.Template
CodeGenerationMode
}
func NewFidlGenerator(mode CodeGenerationMode) *FidlGenerator {
tmpls := template.New("CPPTemplates").Funcs(cpp.MergeFuncMaps(cpp.CommonTemplateFuncs,
template.FuncMap{
"IncludeDomainObjects": func() bool {
return mode == Monolithic ||
mode == OnlyGenerateDomainObjects
},
"IncludeProxiesAndStubs": func() bool { return mode == Monolithic },
}))
// Natural types templates
template.Must(tmpls.Parse(bitsTemplate))
template.Must(tmpls.Parse(constTemplate))
template.Must(tmpls.Parse(enumTemplate))
template.Must(tmpls.Parse(protocolTemplateNaturalTypes))
template.Must(tmpls.Parse(structTemplate))
template.Must(tmpls.Parse(tableTemplate))
template.Must(tmpls.Parse(unionTemplate))
// Proxies and stubs templates
template.Must(tmpls.Parse(protocolTemplateProxiesAndStubs))
template.Must(tmpls.Parse(serviceTemplate))
// File templates
template.Must(tmpls.Parse(headerTemplate))
template.Must(tmpls.Parse(implementationTemplate))
template.Must(tmpls.Parse(testBaseTemplate))
return &FidlGenerator{
tmpls: tmpls,
CodeGenerationMode: mode,
}
}
// GenerateHeader generates the C++ bindings header.
func (gen *FidlGenerator) GenerateHeader(wr io.Writer, tree cpp.Root) error {
return gen.tmpls.ExecuteTemplate(wr, "Header", tree)
}
// GenerateSource generates the C++ bindings source, i.e. implementation.
func (gen *FidlGenerator) GenerateSource(wr io.Writer, tree cpp.Root) error {
return gen.tmpls.ExecuteTemplate(wr, "Implementation", tree)
}
func (gen *FidlGenerator) GenerateTestBase(wr io.Writer, tree cpp.Root) error {
return gen.tmpls.ExecuteTemplate(wr, "TestBase", tree)
}
func (gen *FidlGenerator) generateFile(filepath, clangFormatPath string, generate func(w io.Writer) error) error {
file, err := fidlgen.NewLazyWriter(filepath)
if err != nil {
return err
}
formatterPipe, err := cpp.NewClangFormatter(clangFormatPath).FormatPipe(file)
if err != nil {
return err
}
if err := generate(formatterPipe); err != nil {
return err
}
return formatterPipe.Close()
}
// GenerateFidl generates all files required for the C++ bindings.
func (gen *FidlGenerator) GenerateFidl(fidl fidlgen.Root, opts Config, clangFormatPath string) error {
primaryHeader, err := cpp.CalcPrimaryHeader(opts, fidl.Name.Parts())
if err != nil {
log.Fatal(err)
}
tree := cpp.CompileHL(fidl, cpp.HeaderOptions{
PrimaryHeader: primaryHeader,
IncludeStem: opts.IncludeStem(),
})
if err := os.MkdirAll(filepath.Dir(opts.Header()), os.ModePerm); err != nil {
return err
}
err = gen.generateFile(opts.Header(), clangFormatPath, func(w io.Writer) error {
return gen.GenerateHeader(w, tree)
})
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(opts.Source()), os.ModePerm); err != nil {
return err
}
err = gen.generateFile(opts.Source(), clangFormatPath, func(w io.Writer) error {
return gen.GenerateSource(w, tree)
})
if err != nil {
return err
}
if gen.CodeGenerationMode == OnlyGenerateDomainObjects {
// TestBase header only applies to protocols definitions.
return nil
}
if err := os.MkdirAll(filepath.Dir(opts.TestBase()), os.ModePerm); err != nil {
return err
}
return gen.generateFile(opts.TestBase(), clangFormatPath, func(w io.Writer) error {
return gen.GenerateTestBase(w, tree)
})
}