| // 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) |
| } |
| } |