blob: 29046ed846aac54833a22611ba6f948208d981d3 [file] [log] [blame]
// Copyright 2015 The Vanadium 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 swift implements Swift code generation from compiled VDL packages.
package swift
import (
"bytes"
"log"
"path"
"strings"
"v.io/v23/vdl"
"v.io/x/ref/lib/vdl/build"
"v.io/x/ref/lib/vdl/compile"
"v.io/x/ref/lib/vdl/vdlutil"
)
const (
// The data passed into every template must include a FileDoc field, which
// contains the comment for each generated file; e.g. the boilerplate copyright
// header.
header = `{{.FileDoc}}
// This file was auto-generated by the vanadium vdl tool.
`
// fileTmpl is the template to produce a Swift generated file
fileTmpl = header + `// Source: {{ .Source }}
{{ range $import := .ImportedModules }}
import {{ $import }}
{{ end }}
{{ range $tdef := .Tdefs }}
{{ $tdef }}
{{ end }}`
)
// pkgPathXlator is the function used to translate a VDL package path
// into a Swift package path. If nil, no translation takes place.
var pkgPathXlator func(vdlPath string) (swiftPath string)
// SetPkgPathXlator sets the function used to translate a VDL package
// path into a Swift package path.
func SetPkgPathXlator(xlator func(vdlPath string) string) {
pkgPathXlator = xlator
}
// swiftGenPkgPath returns the Swift package path given the VDL package path.
func swiftGenPkgPath(vdlPkgPath string) string {
if pkgPathXlator == nil {
return vdlPkgPath
}
return pkgPathXlator(vdlPkgPath)
}
// SwiftFileInfo stores the name and contents of the generated Swift file.
type SwiftFileInfo struct {
Data []byte
Dir string
Name string
Module string
}
type swiftContext struct {
env *compile.Env
pkg *compile.Package
// genPathToDir allows us to lookup the filesystem dir for any pkg.GenPath
genPathToDir map[string]string
// srcDirs is the combination of VDLROOT & VDLPATHs to provide a set of
// root source directories. These directories provide a stop condition when
// traversing a package's path's parents. In particular, we do this when
// looking for a vdl.config to that specifies a SwiftModule.
srcDirs map[string]bool
// Cache
memoizedTypeNames map[*compile.TypeDef]string
}
// Generate generates Swift files for all VDL files in the provided package,
// returning the list of generated Swift files as a slice. We generate Swift
// files to match the original VDL file layout, with the exception that
// constants are smushed into a <PackageName>.swift that consolidates the
// errors, package documentation (if any), and constants for a given package
// into a struct. This package struct provides a similar context to what
// would be provided by an import in VDL or Java.
// TODO(azinman): Run Swift formatters on the generated files.
func Generate(pkg *compile.Package, env *compile.Env, genPathToDir map[string]string) (ret []SwiftFileInfo) {
srcDirs := map[string]bool{}
for _, dir := range build.SrcDirs(env.Errors) {
srcDirs[dir] = true
}
ctx := &swiftContext{env, pkg, genPathToDir, srcDirs, map[*compile.TypeDef]string{}}
validateSwiftConfig(ctx)
// One file for pkg documentation (if any), metadata, errors and constants.
if pkgData := genSwiftPackageFile(pkg, ctx); pkgData != "" {
ret = append(ret, SwiftFileInfo{
Data: []byte(pkgData),
Name: ctx.swiftPackageName(pkg) + "Package.swift",
Module: ctx.swiftModule(pkg),
})
}
for _, file := range pkg.Files {
if len(file.TypeDefs) == 0 {
// Nothing to generate
continue
}
// Separate file for all typedefs.
tdefs := []string{}
for _, tdef := range file.TypeDefs {
switch tdef.Type.Kind() {
case vdl.Enum:
tdefs = append(tdefs, genSwiftEnumTdef(tdef, ctx))
case vdl.Union:
tdefs = append(tdefs, genSwiftUnionTdef(tdef, ctx))
case vdl.Struct:
tdefs = append(tdefs, genSwiftStructTdef(tdef, ctx))
default:
tdefs = append(tdefs, genSwiftPrimitiveTdef(tdef, ctx))
}
}
// TODO(zinman): loop through file.Interfaces for RPC generation
source := path.Join(ctx.genPathToDir[pkg.GenPath], file.BaseName)
vdlRoot, err := ctx.vdlRootForFilePath(source)
if err != nil {
log.Fatalf("vdl: couldn't find vdl root for path %v: %v", source, err)
}
source = strings.TrimPrefix(source, vdlRoot+"/")
data := struct {
FileDoc string
ImportedModules []string
Tdefs []string
PackagePath string
Source string
}{
FileDoc: pkg.FileDoc,
ImportedModules: ctx.importedModules(file.TypeDefs),
Tdefs: tdefs,
PackagePath: swiftGenPkgPath(pkg.GenPath),
Source: source,
}
var buf bytes.Buffer
err = parseTmpl("swift file", fileTmpl).Execute(&buf, data)
if err != nil {
log.Fatalf("vdl: couldn't execute union template: %v", err)
}
baseName := strings.Replace(file.BaseName, path.Ext(file.BaseName), "", 1)
ret = append(ret, SwiftFileInfo{
Data: buf.Bytes(),
Name: ctx.swiftPackageName(pkg) + vdlutil.FirstRuneToUpper(baseName) + ".swift",
Module: ctx.swiftModule(pkg),
})
}
return
}
// The native types feature is hard to use correctly. E.g. the wire type
// must be statically registered in Swift vdl package in order for the
// wire<->native conversion to work, which is hard to ensure.
//
// Restrict the feature to these whitelisted VDL packages for now.
var nativeTypePackageWhitelist = map[string]bool{
"time": true,
}
func validateSwiftConfig(ctx *swiftContext) {
vdlconfig := path.Join(ctx.pkg.GenPath, "vdl.config")
// Validate native type configuration. Since native types are hard to use, we
// restrict them to a built-in whitelist of packages for now.
if len(ctx.pkg.Config.Swift.WireToNativeTypes) > 0 && !nativeTypePackageWhitelist[ctx.pkg.Path] {
ctx.env.Errors.Errorf("%s: Swift.WireToNativeTypes is restricted to whitelisted VDL packages", vdlconfig)
}
// Look for a Swift module by walking up... if none found then panic
if ctx.swiftModule(ctx.pkg) == "" {
log.Fatalf("Could not find a swift module for package %v/%v; make sure to have a file named swiftmodule "+
"defined at the root that is a text file that ONLY contains the name of your Swift module.",
ctx.pkg.Path, ctx.pkg.Name)
}
}