blob: 466f11d9625129a34e8e3db0f14cfa6d3ce0cda6 [file] [log] [blame]
// Copyright 2020 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 main
import (
"errors"
"flag"
"fmt"
"io/fs"
"log"
"os"
"path"
"path/filepath"
"go.fuchsia.dev/fuchsia/tools/fidl/fidlgen_go/codegen"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
type flagsDef struct {
jsonPath *string
outputImplPath *string
outputRootForIDEPath *string
outputPkgNamePath *string
}
var flags = flagsDef{
jsonPath: flag.String("json", "",
"relative path to the FIDL intermediate representation."),
outputImplPath: flag.String("output-impl", "",
"output path for the generated Go implementation."),
outputRootForIDEPath: flag.String(
"output-root-for-ide",
"",
"directory within which to output a symlink to the generated Go implementation "+
"in order to faciliate IDE cross-references.",
),
outputPkgNamePath: flag.String("output-pkg-name", "",
"output path for the generated Go implementation."),
}
// valid returns true if the parsed flags are valid.
func (f flagsDef) valid() bool {
return *f.jsonPath != ""
}
func printUsage() {
program := path.Base(os.Args[0])
message := `Usage: ` + program + ` [flags]
Go FIDL backend, used to generate Go bindings from JSON IR input (the
intermediate representation of a FIDL library).
Flags:
`
fmt.Fprint(flag.CommandLine.Output(), message)
flag.PrintDefaults()
}
func main() {
flag.Usage = printUsage
flag.Parse()
if !flags.valid() {
printUsage()
os.Exit(1)
}
root, err := fidlgen.ReadJSONIr(*flags.jsonPath)
if err != nil {
log.Fatal(err)
}
generator := codegen.NewGenerator()
tree := codegen.Compile(root)
if outputImplPath := *flags.outputImplPath; outputImplPath != "" {
if err := generator.GenerateImplFile(tree, outputImplPath); err != nil {
log.Fatalf("Error generating impl file: %v", err)
}
if outputRootForIDEPath := *flags.outputRootForIDEPath; outputRootForIDEPath != "" {
if err := makeIDEFriendlySymlinks(ideFriendlySymlinksArgs{
outputImplPath: outputImplPath,
outputRootForIDEPath: outputRootForIDEPath,
goPackageName: tree.PackageName,
}); err != nil {
log.Fatal(err)
}
}
}
if outputPkgNamePath := *flags.outputPkgNamePath; outputPkgNamePath != "" {
if err := generator.GeneratePkgNameFile(tree, outputPkgNamePath); err != nil {
log.Fatalf("Error generating pkg-name file: %v", err)
}
}
}
type ideFriendlySymlinksArgs struct {
outputImplPath string
outputRootForIDEPath string
goPackageName string
}
func makeIDEFriendlySymlinks(args ideFriendlySymlinksArgs) error {
const permUserReadWriteExecute fs.FileMode = 0700
ideOutputDir := filepath.Join(args.outputRootForIDEPath, args.goPackageName)
if err := os.MkdirAll(ideOutputDir, permUserReadWriteExecute); err != nil {
return fmt.Errorf("Error creating IDE-friendly directory layout: %w", err)
}
absOutputImplPath, err := filepath.Abs(args.outputImplPath)
if err != nil {
return fmt.Errorf(
"Error getting absolute path of generated Go implementation (relative path is %q): %w",
args.outputImplPath,
err,
)
}
absIDEOutputDir, err := filepath.Abs(ideOutputDir)
if err != nil {
return fmt.Errorf(
"Error getting absolute path of IDE-friendly directory (relative path is %q): %w",
ideOutputDir,
err,
)
}
symlinkPathname := filepath.Join(absIDEOutputDir, "impl.go")
if err := os.Remove(symlinkPathname); err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("Error removing old symlink at %q: %w", symlinkPathname, err)
}
relOutputImplPath, err := filepath.Rel(absIDEOutputDir, absOutputImplPath)
if err != nil {
return fmt.Errorf(
"Error getting relative path from %q to %q: %w",
absIDEOutputDir,
absOutputImplPath,
err,
)
}
if err := os.Symlink(relOutputImplPath, symlinkPathname); err != nil {
// Ignore os.ErrExist here because if multiple targets are generating the Go
// impl in parallel, we may race with another fidlgen_go invocation trying
// to create an identical symlink.
if !errors.Is(err, os.ErrExist) {
return fmt.Errorf(
"Error adding symlink at %q to %q: %w",
absOutputImplPath,
symlinkPathname,
err,
)
}
}
return nil
}