| // 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 ( |
| "fmt" |
| "io/ioutil" |
| "log" |
| "path/filepath" |
| "sort" |
| "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 V23SwiftFrameworkName = "VanadiumCore" |
| |
| var ( |
| memoizedSwiftModule = map[string]string{} |
| memoizedPackageName = map[*compile.Package]string{} |
| ) |
| |
| func (ctx *swiftContext) pkgIsSameModule(pkg *compile.Package) bool { |
| pkgModule := ctx.swiftModule(pkg) |
| ctxModule := ctx.swiftModule(ctx.pkg) |
| return pkgModule == ctxModule |
| } |
| |
| func (ctx *swiftContext) swiftModule(pkg *compile.Package) string { |
| fileDir := ctx.genPathToDir[pkg.GenPath] |
| if fileDir == "" { |
| if pkg.GenPath == "_builtin" { |
| return "" |
| } |
| panic(fmt.Sprintf("Unable to find source directory for genPath ", pkg.GenPath)) |
| } |
| if !strings.HasPrefix(fileDir, "/") { |
| if strings.HasPrefix(pkg.GenPath, "v.io/v23/vdlroot") { |
| panic("You must have the VDLROOT env var to generate VanadiumCore swift files") |
| } |
| panic(fmt.Sprintf("Unsupported file system for dir path: %v", fileDir)) |
| } |
| if swiftModule, ok := memoizedSwiftModule[fileDir]; ok { |
| return swiftModule |
| } |
| _, module := ctx.findSwiftModule(pkg) |
| memoizedSwiftModule[fileDir] = module |
| return module |
| } |
| |
| func (ctx *swiftContext) findSwiftModule(pkg *compile.Package) (path string, module string) { |
| fileDir := ctx.genPathToDir[pkg.GenPath] |
| for { |
| configPath := filepath.Join(fileDir, "swiftmodule") |
| module, err := ioutil.ReadFile(configPath) |
| if err == nil && module != nil { |
| return fileDir, strings.TrimSpace(string(module)) |
| } |
| foundInSrcDirs := ctx.srcDirs[fileDir] |
| if foundInSrcDirs && strings.Contains(fileDir, "v.io/v23/vdlroot") { |
| // Special case VDLROOT which will be in a srcDir (so normally |
| // it would stop here), but the swiftmodule file is defined below it. |
| foundInSrcDirs = false |
| } |
| if fileDir == "/" || fileDir == "" || foundInSrcDirs { |
| // We're at the root, nothing else to check. |
| return "", "" |
| } |
| // Go up a level |
| s := strings.Split(fileDir, "/") |
| s = s[:len(s)-1] |
| fileDir = "/" + filepath.Join(s...) |
| } |
| } |
| |
| // swiftPackageName returns a Swift package name that is a mashup of this |
| // package name and its path ancestors. It traverses towards the root package |
| // until it finds the Swift Module, and concatenates the child paths. |
| // For example, v.io/v23/syncbase/nosql => SyncbaseNosql where v23 forms the root. |
| // If no Swift Module is found, we presume the entire path is valid and the |
| // entirety becomes concatenated using the pkg.Path. This exception allows |
| // for third-party code to conservatively work (since we can assume nothing |
| // about the root package we don't want to throw away information) and |
| // vdlroot's packages to be correctly named (signature => signature). |
| func (ctx *swiftContext) swiftPackageName(pkg *compile.Package) string { |
| if name, ok := memoizedPackageName[pkg]; ok { |
| return name |
| } |
| // Remove the package path of the swift module from our pkg.Path |
| // First find that package path |
| moduleDir, _ := ctx.findSwiftModule(pkg) |
| modulePkgPath := "__INVALID__" |
| if moduleDir != "" { |
| // moduleDir is the full path to our swiftmodule file |
| // find the VDLROOT/VDLPATH that contains this |
| srcDirs := build.SrcDirs(ctx.env.Errors) |
| for _, dir := range srcDirs { |
| if strings.HasPrefix(moduleDir, dir) { |
| // We found the VDLPATH/VDLROOT that corresponds to this SwiftModule. |
| // So lop off the VDLPATH. For example: |
| // Before moduleDir = /Users/aaron/v23/release/go/src/v.io/v23/services |
| // Before dir (srcDir) = /Users/aaron/v23/release/go/src |
| modulePkgPath = strings.TrimPrefix(moduleDir, dir) |
| modulePkgPath = strings.TrimPrefix(modulePkgPath, "/") |
| // After: modulePkgPath = v.io/v23/services |
| break |
| } |
| } |
| if modulePkgPath == "__INVALID__" { |
| log.Fatalf("Could not find pkg path for module in dir %v", moduleDir) |
| } |
| } |
| // Remove module package path, for example: |
| // Before pkg.Path = v.io/v23/services/syncbase |
| // Before modulePkgPath = v.io/v23/services |
| modulePkgPath = strings.TrimPrefix(pkg.Path, modulePkgPath) |
| modulePkgPath = strings.TrimPrefix(modulePkgPath, "/") |
| // After modulePkgPath = syncbase |
| // CamelCaseThePath |
| name := "" |
| for _, pkg := range strings.Split(modulePkgPath, "/") { |
| // Remove any periods (e.g. v.io/ -> Vio) |
| pkg = strings.Replace(pkg, ".", "", -1) |
| name = name + vdlutil.FirstRuneToUpper(pkg) |
| } |
| memoizedPackageName[pkg] = name |
| return name |
| } |
| |
| func (ctx *swiftContext) importedModules(tdefs []*compile.TypeDef) []string { |
| // Find the transitive closure of tdefs' dependencies. |
| walkedTdefs := map[*compile.TypeDef]bool{} |
| modules := map[string]bool{} |
| for _, tdef := range tdefs { |
| walkTdef(tdef, ctx.env, walkedTdefs) |
| } |
| // Extract the modules that aren't the same as the context's |
| for tdef := range walkedTdefs { |
| module := ctx.swiftModule(tdef.File.Package) |
| if module != "" && !ctx.pkgIsSameModule(tdef.File.Package) { |
| modules[module] = true |
| continue |
| } |
| } |
| if ctx.swiftModule(ctx.pkg) != V23SwiftFrameworkName { |
| // Make sure VanadiumCore is imported as that's where we define VError, VdlPrimitive, etc |
| modules[V23SwiftFrameworkName] = true |
| } |
| return flattenImportedModuleSet(modules) |
| } |
| |
| func walkTdef(tdef *compile.TypeDef, env *compile.Env, seen map[*compile.TypeDef]bool) { |
| // If this typedef has a native type, then we can ignore the import because |
| // that information is replaced anyway. |
| pkg := tdef.File.Package |
| if _, ok := pkg.Config.Swift.WireToNativeTypes[tdef.Name]; ok { |
| return |
| } |
| seen[tdef] = true |
| switch tdef.Type.Kind() { |
| case vdl.Struct, vdl.Union: |
| for i := 0; i < tdef.Type.NumField(); i++ { |
| fld := tdef.Type.Field(i) |
| if innerTdef := env.FindTypeDef(fld.Type); innerTdef != nil { |
| walkTdef(innerTdef, env, seen) |
| } |
| } |
| |
| case vdl.List, vdl.Array, vdl.Map, vdl.Optional: |
| if innerTdef := env.FindTypeDef(tdef.Type.Elem()); innerTdef != nil { |
| walkTdef(innerTdef, env, seen) |
| } |
| } |
| switch tdef.Type.Kind() { |
| case vdl.Map, vdl.Set: |
| if innerTdef := env.FindTypeDef(tdef.Type.Key()); innerTdef != nil { |
| walkTdef(innerTdef, env, seen) |
| } |
| } |
| } |
| |
| func flattenImportedModuleSet(modules map[string]bool) []string { |
| imports := make([]string, len(modules)) |
| i := 0 |
| for moduleName := range modules { |
| if moduleName != "" { |
| imports[i] = moduleName |
| i++ |
| } |
| } |
| // Make it deterministic |
| sort.Strings(imports) |
| return imports |
| } |