about summary refs log tree commit diff
path: root/external/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'external/main.go')
-rw-r--r--external/main.go159
1 files changed, 159 insertions, 0 deletions
diff --git a/external/main.go b/external/main.go
new file mode 100644
index 000000000000..0f8834fc99c9
--- /dev/null
+++ b/external/main.go
@@ -0,0 +1,159 @@
+// Copyright 2019 Google LLC.
+// SPDX-License-Identifier: Apache-2.0
+
+// This tool analyses external (i.e. not built with `buildGo.nix`) Go
+// packages to determine a build plan that Nix can import.
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"go/build"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// Path to a JSON file describing all standard library import paths.
+// This file is generated and set here by Nix during the build
+// process.
+var stdlibList string
+
+// pkg describes a single Go package within the specified source
+// directory.
+//
+// Return information includes the local (relative from project root)
+// and external (none-stdlib) dependencies of this package.
+type pkg struct {
+	Name        string   `json:"name"`
+	Source      string   `json:"source"`
+	Files       []string `json:"files"`
+	LocalDeps   []string `json:"localDeps"`
+	ForeignDeps []string `json:"foreignDeps"`
+}
+
+// findGoDirs returns a filepath.WalkFunc that identifies all
+// directories that contain Go source code in a certain tree.
+func findGoDirs(at string) ([]string, error) {
+	var goDirs []string
+	dir := ""
+
+	err := filepath.Walk(at, func(path string, info os.FileInfo, err error) error {
+		// Skip testdata
+		if info.IsDir() && info.Name() == "testdata" {
+			return filepath.SkipDir
+		}
+
+		// Keep track of the last seen directory.
+		if info.IsDir() {
+			dir = path
+			return nil
+		}
+
+		// If the directory has already been "popped", nothing else needs
+		// to happen.
+		if dir == "" {
+			return nil
+		}
+
+		// If the current file is a Go file, then the directory is popped
+		// (i.e. marked as a Go directory).
+		if strings.HasSuffix(info.Name(), ".go") && !strings.HasSuffix(info.Name(), "_test.go") {
+			goDirs = append(goDirs, dir)
+			dir = ""
+			return nil
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	return goDirs, nil
+}
+
+// analysePackage loads and analyses the imports of a single Go
+// package, returning the data that is required by the Nix code to
+// generate a derivation for this package.
+func analysePackage(root, source, path string, stdlib map[string]bool) (pkg, error) {
+	ctx := build.Default
+
+	p, err := ctx.ImportDir(source, build.IgnoreVendor)
+	if err != nil {
+		return pkg{}, err
+	}
+
+	local := []string{}
+	foreign := []string{}
+
+	for _, i := range p.Imports {
+		if stdlib[i] {
+			continue
+		}
+
+		if strings.HasPrefix(i, path) {
+			local = append(local, i)
+		} else {
+			foreign = append(foreign, i)
+		}
+	}
+
+	analysed := pkg{
+		Name:        strings.TrimPrefix(source, root+"/"),
+		Source:      source,
+		Files:       p.GoFiles,
+		LocalDeps:   local,
+		ForeignDeps: foreign,
+	}
+
+	return analysed, nil
+}
+
+func loadStdlibPkgs(from string) (pkgs map[string]bool, err error) {
+	f, err := ioutil.ReadFile(from)
+	if err != nil {
+		return
+	}
+
+	err = json.Unmarshal(f, &pkgs)
+	return
+}
+
+func main() {
+	// TODO(tazjin): Remove default values
+	source := flag.String("source", "/nix/store/fzp67ris29zg5zfs65z0q245x0fahgll-source", "path to directory with sources to process")
+	path := flag.String("path", "github.com/golang/protobuf", "import path for the package")
+
+	flag.Parse()
+
+	if *source == "" {
+		log.Fatalf("-source flag must be specified")
+	}
+
+	stdlibPkgs, err := loadStdlibPkgs(stdlibList)
+	if err != nil {
+		log.Fatalf("failed to load standard library index from %q: %s\n", stdlibList, err)
+	}
+
+	goDirs, err := findGoDirs(*source)
+	if err != nil {
+		log.Fatalf("failed to walk source directory '%s': %s\n", source, err)
+	}
+
+	all := []pkg{}
+	for _, d := range goDirs {
+		analysed, err := analysePackage(*source, d, *path, stdlibPkgs)
+		if err != nil {
+			log.Fatalf("failed to analyse package at %q: %s", d, err)
+		}
+		all = append(all, analysed)
+	}
+
+	j, _ := json.MarshalIndent(all, "", "  ") // TODO: no indent
+	fmt.Println(string(j))
+}