diff --git a/build/generate-services.go b/build/generate-services.go new file mode 100644 index 0000000000000..03af9a1b1b721 --- /dev/null +++ b/build/generate-services.go @@ -0,0 +1,213 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +// +build ignore + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/format" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + "strings" + "text/template" +) + +func main() { + if len(os.Args) != 5 { + log.Fatal("Insufficient number of arguments. Need: root filename package functionname") + } + + root, filename, packageName, fnName := os.Args[1], os.Args[2], os.Args[3], os.Args[4] + + goPackageName := os.Getenv("GOPACKAGE") + + fmt.Printf("generating imports for services in %s\n", goPackageName) + + // Generate the imports by walking the provided directory + imports := generateImports(root, packageName, fnName) + + buf := &bytes.Buffer{} + + // Create the importing template + importerTemplate.Execute(buf, struct { + Package string + Imports []string + }{ + Package: goPackageName, + Imports: imports, + }) + + // Format the importing file + bs, err := format.Source(buf.Bytes()) + if err != nil { + log.Fatal(err) + } + + // Write it out the filename + if err := ioutil.WriteFile(filename, bs, 0644); err != nil { + log.Fatal(err) + } +} + +// generateImports walks from the root looking for go files that register a service +func generateImports(root, packageName, fnName string) []string { + pkgNamesMap := map[string]bool{} + quotedPackageName := fmt.Sprintf("%q", packageName) + defaultAlias := packageName[strings.LastIndex(packageName, "/")+1:] + + err := filepath.Walk(root, func(walkpath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + // skip files we'll deal with them later + return nil + } + + // These directories do not contain useful files + if strings.HasPrefix(walkpath, root+"/.") || + strings.HasPrefix(walkpath, root+"/integrations") || + strings.HasPrefix(walkpath, root+"/node_modules") || + strings.HasPrefix(walkpath, root+"/web_src") || + strings.HasPrefix(walkpath, root+"/vendor") { + return filepath.SkipDir + } + + // use build to import the dir + pkg, err := build.ImportDir(walkpath, build.IgnoreVendor) + if err != nil { + if _, ok := err.(*build.NoGoError); ok { + return nil + } + return err + } + + // Now only include dirs that import our important package + hasImport := false + for _, importName := range pkg.Imports { + if importName == packageName { + hasImport = true + break + } + } + if !hasImport { + return nil + } + + // Walk the go files that are in the directory to see if they have the important service call + serviceCall := false + for _, filename := range pkg.GoFiles { + if strings.HasSuffix(filename, "_test.go") { + continue + } + if hasServiceCall(walkpath+"/"+filename, quotedPackageName, defaultAlias, fnName) { + serviceCall = true + break + } + } + + if serviceCall { + pkgNamesMap[path.Clean("code.gitea.io/gitea/"+path.Clean("/"+filepath.ToSlash(walkpath)))] = true + } + return nil + }) + if err != nil { + log.Fatal(err) + } + + pkgNames := make([]string, 0, len(pkgNamesMap)) + for key := range pkgNamesMap { + pkgNames = append(pkgNames, key) + } + return pkgNames +} + +func hasServiceCall(filename, quotedPackageName, defaultAlias, fnName string) bool { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors) + if err != nil { + log.Fatal(err) + } + hasServiceCall := false + + alias := "" + for _, importSpec := range file.Imports { + if importSpec.Path.Value == quotedPackageName { + if importSpec.Name == nil { + alias = defaultAlias + } else { + alias = importSpec.Name.Name + } + break + } + } + if alias == "" || alias == "_" { + return false + } + + visitor := &serviceVisitor{ + alias: alias, + fnName: fnName, + } + ast.Walk(visitor, file) + if visitor.hasServiceCall { + hasServiceCall = true + } + + return hasServiceCall +} + +type serviceVisitor struct { + alias string + fnName string + hasServiceCall bool +} + +func (v *serviceVisitor) Visit(node ast.Node) ast.Visitor { + if !isCall(node, v.alias, v.fnName) { + return v + } + + v.hasServiceCall = true + return nil +} + +func isCall(node ast.Node, alias, fn string) bool { + call, ok := node.(*ast.CallExpr) + if !ok { + return false + } + + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return false + } + + aliasId, ok := sel.X.(*ast.Ident) + if !ok { + return false + } + + return aliasId.Name == alias && sel.Sel.Name == fn +} + +var importerTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT. +// This file was generated by generate-services.go +package {{.Package}} + +import ( + {{range .Imports}}_ "{{.}}" +{{end}} +) +`)) diff --git a/routers/generated_init.go b/routers/generated_init.go new file mode 100644 index 0000000000000..85a53cef2dfab --- /dev/null +++ b/routers/generated_init.go @@ -0,0 +1,33 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by generate-services.go +package routers + +import ( + _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/modules/auth/sso" + _ "code.gitea.io/gitea/modules/cache" + _ "code.gitea.io/gitea/modules/cron" + _ "code.gitea.io/gitea/modules/eventsource" + _ "code.gitea.io/gitea/modules/highlight" + _ "code.gitea.io/gitea/modules/indexer/code" + _ "code.gitea.io/gitea/modules/indexer/issues" + _ "code.gitea.io/gitea/modules/indexer/stats" + _ "code.gitea.io/gitea/modules/markup" + _ "code.gitea.io/gitea/modules/markup/external" + _ "code.gitea.io/gitea/modules/migrations" + _ "code.gitea.io/gitea/modules/notification/action" + _ "code.gitea.io/gitea/modules/notification/indexer" + _ "code.gitea.io/gitea/modules/notification/mail" + _ "code.gitea.io/gitea/modules/notification/ui" + _ "code.gitea.io/gitea/modules/notification/webhook" + _ "code.gitea.io/gitea/modules/setting" + _ "code.gitea.io/gitea/modules/ssh" + _ "code.gitea.io/gitea/modules/storage" + _ "code.gitea.io/gitea/modules/svg" + _ "code.gitea.io/gitea/modules/task" + _ "code.gitea.io/gitea/services/mailer" + _ "code.gitea.io/gitea/services/mirror" + _ "code.gitea.io/gitea/services/pull" + _ "code.gitea.io/gitea/services/repository" + _ "code.gitea.io/gitea/services/webhook" +) diff --git a/routers/init.go b/routers/init.go index af8d83ba3e36d..0136a3eb448ea 100644 --- a/routers/init.go +++ b/routers/init.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. +//go:generate go run -mod=vendor ../build/generate-services.go .. generated_init.go code.gitea.io/gitea/modules/services RegisterService package routers import (