Merge "Add go2bp tool"
This commit is contained in:
26
cmd/go2bp/Android.bp
Normal file
26
cmd/go2bp/Android.bp
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
blueprint_go_binary {
|
||||
name: "go2bp",
|
||||
deps: [
|
||||
"blueprint-proptools",
|
||||
"bpfix-lib",
|
||||
],
|
||||
srcs: ["go2bp.go"],
|
||||
}
|
361
cmd/go2bp/go2bp.go
Normal file
361
cmd/go2bp/go2bp.go
Normal file
@@ -0,0 +1,361 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/google/blueprint/proptools"
|
||||
|
||||
"android/soong/bpfix/bpfix"
|
||||
)
|
||||
|
||||
type RewriteNames []RewriteName
|
||||
type RewriteName struct {
|
||||
prefix string
|
||||
repl string
|
||||
}
|
||||
|
||||
func (r *RewriteNames) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *RewriteNames) Set(v string) error {
|
||||
split := strings.SplitN(v, "=", 2)
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("Must be in the form of <prefix>=<replace>")
|
||||
}
|
||||
*r = append(*r, RewriteName{
|
||||
prefix: split[0],
|
||||
repl: split[1],
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RewriteNames) GoToBp(name string) string {
|
||||
ret := name
|
||||
for _, r := range *r {
|
||||
prefix := r.prefix
|
||||
if name == prefix {
|
||||
ret = r.repl
|
||||
break
|
||||
}
|
||||
prefix += "/"
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
ret = r.repl + "-" + strings.TrimPrefix(name, prefix)
|
||||
}
|
||||
}
|
||||
return strings.ReplaceAll(ret, "/", "-")
|
||||
}
|
||||
|
||||
var rewriteNames = RewriteNames{}
|
||||
|
||||
type Exclude map[string]bool
|
||||
|
||||
func (e Exclude) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e Exclude) Set(v string) error {
|
||||
e[v] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
var excludes = make(Exclude)
|
||||
var excludeDeps = make(Exclude)
|
||||
var excludeSrcs = make(Exclude)
|
||||
|
||||
type GoModule struct {
|
||||
Dir string
|
||||
}
|
||||
|
||||
type GoPackage struct {
|
||||
Dir string
|
||||
ImportPath string
|
||||
Name string
|
||||
Imports []string
|
||||
GoFiles []string
|
||||
TestGoFiles []string
|
||||
TestImports []string
|
||||
|
||||
Module *GoModule
|
||||
}
|
||||
|
||||
func (g GoPackage) IsCommand() bool {
|
||||
return g.Name == "main"
|
||||
}
|
||||
|
||||
func (g GoPackage) BpModuleType() string {
|
||||
if g.IsCommand() {
|
||||
return "blueprint_go_binary"
|
||||
}
|
||||
return "bootstrap_go_package"
|
||||
}
|
||||
|
||||
func (g GoPackage) BpName() string {
|
||||
if g.IsCommand() {
|
||||
return filepath.Base(g.ImportPath)
|
||||
}
|
||||
return rewriteNames.GoToBp(g.ImportPath)
|
||||
}
|
||||
|
||||
func (g GoPackage) BpDeps(deps []string) []string {
|
||||
var ret []string
|
||||
for _, d := range deps {
|
||||
// Ignore stdlib dependencies
|
||||
if !strings.Contains(d, ".") {
|
||||
continue
|
||||
}
|
||||
if _, ok := excludeDeps[d]; ok {
|
||||
continue
|
||||
}
|
||||
name := rewriteNames.GoToBp(d)
|
||||
ret = append(ret, name)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (g GoPackage) BpSrcs(srcs []string) []string {
|
||||
var ret []string
|
||||
prefix, err := filepath.Rel(g.Module.Dir, g.Dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, f := range srcs {
|
||||
f = filepath.Join(prefix, f)
|
||||
if _, ok := excludeSrcs[f]; ok {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, f)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// AllImports combines Imports and TestImports, as blueprint does not differentiate these.
|
||||
func (g GoPackage) AllImports() []string {
|
||||
imports := append([]string(nil), g.Imports...)
|
||||
imports = append(imports, g.TestImports...)
|
||||
|
||||
if len(imports) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sort and de-duplicate
|
||||
sort.Strings(imports)
|
||||
j := 0
|
||||
for i := 1; i < len(imports); i++ {
|
||||
if imports[i] == imports[j] {
|
||||
continue
|
||||
}
|
||||
j++
|
||||
imports[j] = imports[i]
|
||||
}
|
||||
return imports[:j+1]
|
||||
}
|
||||
|
||||
var bpTemplate = template.Must(template.New("bp").Parse(`
|
||||
{{.BpModuleType}} {
|
||||
name: "{{.BpName}}",
|
||||
{{- if not .IsCommand}}
|
||||
pkgPath: "{{.ImportPath}}",
|
||||
{{- end}}
|
||||
{{- if .BpDeps .AllImports}}
|
||||
deps: [
|
||||
{{- range .BpDeps .AllImports}}
|
||||
"{{.}}",
|
||||
{{- end}}
|
||||
],
|
||||
{{- end}}
|
||||
{{- if .BpSrcs .GoFiles}}
|
||||
srcs: [
|
||||
{{- range .BpSrcs .GoFiles}}
|
||||
"{{.}}",
|
||||
{{- end}}
|
||||
],
|
||||
{{- end}}
|
||||
{{- if .BpSrcs .TestGoFiles}}
|
||||
testSrcs: [
|
||||
{{- range .BpSrcs .TestGoFiles}}
|
||||
"{{.}}",
|
||||
{{- end}}
|
||||
],
|
||||
{{- end}}
|
||||
}
|
||||
`))
|
||||
|
||||
func rerunForRegen(filename string) error {
|
||||
buf, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(buf))
|
||||
|
||||
// Skip the first line in the file
|
||||
for i := 0; i < 2; i++ {
|
||||
if !scanner.Scan() {
|
||||
if scanner.Err() != nil {
|
||||
return scanner.Err()
|
||||
} else {
|
||||
return fmt.Errorf("unexpected EOF")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the old args from the file
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "// go2bp ") {
|
||||
line = strings.TrimPrefix(line, "// go2bp ")
|
||||
} else {
|
||||
return fmt.Errorf("unexpected second line: %q", line)
|
||||
}
|
||||
args := strings.Split(line, " ")
|
||||
lastArg := args[len(args)-1]
|
||||
args = args[:len(args)-1]
|
||||
|
||||
// Append all current command line args except -regen <file> to the ones from the file
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
if os.Args[i] == "-regen" || os.Args[i] == "--regen" {
|
||||
i++
|
||||
} else {
|
||||
args = append(args, os.Args[i])
|
||||
}
|
||||
}
|
||||
args = append(args, lastArg)
|
||||
|
||||
cmd := os.Args[0] + " " + strings.Join(args, " ")
|
||||
// Re-exec pom2bp with the new arguments
|
||||
output, err := exec.Command("/bin/sh", "-c", cmd).Output()
|
||||
if exitErr, _ := err.(*exec.ExitError); exitErr != nil {
|
||||
return fmt.Errorf("failed to run %s\n%s", cmd, string(exitErr.Stderr))
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filename, output, 0666)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, `go2bp, a tool to create Android.bp files from go modules
|
||||
|
||||
The tool will extract the necessary information from Go files to create an Android.bp that can
|
||||
compile them. This needs to be run from the same directory as the go.mod file.
|
||||
|
||||
Usage: %s [--rewrite <pkg-prefix>=<replace>] [-exclude <package>] [-regen <file>]
|
||||
|
||||
-rewrite <pkg-prefix>=<replace>
|
||||
rewrite can be used to specify mappings between go package paths and Android.bp modules. The -rewrite
|
||||
option can be specified multiple times. When determining the Android.bp module for a given Go
|
||||
package, mappings are searched in the order they were specified. The first <pkg-prefix> matching
|
||||
either the package directly, or as the prefix '<pkg-prefix>/' will be replaced with <replace>.
|
||||
After all replacements are finished, all '/' characters are replaced with '-'.
|
||||
-exclude <package>
|
||||
Don't put the specified go package in the Android.bp file.
|
||||
-exclude-deps <package>
|
||||
Don't put the specified go package in the dependency lists.
|
||||
-exclude-srcs <module>
|
||||
Don't put the specified source files in srcs or testSrcs lists.
|
||||
-regen <file>
|
||||
Read arguments from <file> and overwrite it.
|
||||
|
||||
`, os.Args[0])
|
||||
}
|
||||
|
||||
var regen string
|
||||
|
||||
flag.Var(&excludes, "exclude", "Exclude go package")
|
||||
flag.Var(&excludeDeps, "exclude-dep", "Exclude go package from deps")
|
||||
flag.Var(&excludeSrcs, "exclude-src", "Exclude go file from source lists")
|
||||
flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names")
|
||||
flag.StringVar(®en, "regen", "", "Rewrite specified file")
|
||||
flag.Parse()
|
||||
|
||||
if regen != "" {
|
||||
err := rerunForRegen(regen)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if flag.NArg() != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Unused argument detected: %v\n", flag.Args())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := os.Stat("go.mod"); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "go.mod file not found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "list", "-json", "./...")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to dump the go packages: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(output))
|
||||
|
||||
pkgs := []GoPackage{}
|
||||
for decoder.More() {
|
||||
pkg := GoPackage{}
|
||||
err := decoder.Decode(&pkg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to parse json: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
fmt.Fprintln(buf, "// Automatically generated with:")
|
||||
fmt.Fprintln(buf, "// go2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " "))
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
if excludes[pkg.ImportPath] {
|
||||
continue
|
||||
}
|
||||
if len(pkg.BpSrcs(pkg.GoFiles)) == 0 && len(pkg.BpSrcs(pkg.TestGoFiles)) == 0 {
|
||||
continue
|
||||
}
|
||||
err := bpTemplate.Execute(buf, pkg)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error writing", pkg.Name, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
out, err := bpfix.Reformat(buf.String())
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error formatting output", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Stdout.WriteString(out)
|
||||
}
|
Reference in New Issue
Block a user