Use Go modules with delocate.

This makes running go test, etc., in util/fipstools/delocate work! This
adds a go_executable command to CMake like:

  go_executable(delocate boringssl.googlesource.com/boringssl/util/fipstools/delocate)

which internally gets dependencies and whatnot so it behaves like usual
Go.

Update-Note: delocate has been rearranged a bit.
Change-Id: I244a7317dd8d4f2ab77a0daa624ed3e0b385faef
Reviewed-on: https://boringssl-review.googlesource.com/31885
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c614a65..1586d34 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -334,6 +334,43 @@
   add_definitions(-DOPENSSL_SMALL)
 endif()
 
+function(go_executable dest package)
+  set(godeps "${CMAKE_SOURCE_DIR}/util/godeps.go")
+  if(${CMAKE_VERSION} VERSION_LESS "3.7" OR
+     NOT ${CMAKE_GENERATOR} STREQUAL "Ninja")
+    # The DEPFILE parameter to add_custom_command is new as of CMake 3.7 and
+    # only works with Ninja. Query the sources at configure time. Additionally,
+    # everything depends on go.mod. That affects what external packages to use.
+    execute_process(COMMAND ${GO_EXECUTABLE} run ${godeps} -format cmake
+                            -pkg ${package}
+                    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+                    OUTPUT_VARIABLE sources
+                    RESULT_VARIABLE godeps_result)
+    add_custom_command(OUTPUT ${dest}
+                       COMMAND ${GO_EXECUTABLE} build
+                               -o ${CMAKE_CURRENT_BINARY_DIR}/${dest} ${package}
+                       WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+                       DEPENDS ${sources} ${CMAKE_SOURCE_DIR}/go.mod)
+  else()
+    # Ninja expects the target in the depfile to match the output. This is a
+    # relative path from the build directory.
+    string(LENGTH "${CMAKE_BINARY_DIR}" root_dir_length)
+    math(EXPR root_dir_length "${root_dir_length} + 1")
+    string(SUBSTRING "${CMAKE_CURRENT_BINARY_DIR}" ${root_dir_length} -1 target)
+    set(target "${target}/${dest}")
+
+    set(depfile "${CMAKE_CURRENT_BINARY_DIR}/${dest}.d")
+    add_custom_command(OUTPUT ${dest}
+                       COMMAND ${GO_EXECUTABLE} build
+                               -o ${CMAKE_CURRENT_BINARY_DIR}/${dest} ${package}
+                       COMMAND ${GO_EXECUTABLE} run ${godeps} -format depfile
+                               -target ${target} -pkg ${package} -out ${depfile}
+                       WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+                       DEPENDS ${godeps} ${CMAKE_SOURCE_DIR}/go.mod
+                       DEPFILE ${depfile})
+  endif()
+endfunction()
+
 # CMake's iOS support uses Apple's multiple-architecture toolchain. It takes an
 # architecture list from CMAKE_OSX_ARCHITECTURES, leaves CMAKE_SYSTEM_PROCESSOR
 # alone, and expects all architecture-specific logic to be conditioned within
diff --git a/crypto/fipsmodule/CMakeLists.txt b/crypto/fipsmodule/CMakeLists.txt
index 1242aa2..9868dd8 100644
--- a/crypto/fipsmodule/CMakeLists.txt
+++ b/crypto/fipsmodule/CMakeLists.txt
@@ -141,21 +141,12 @@
   set_target_properties(bcm_c_generated_asm PROPERTIES COMPILE_OPTIONS "-S")
   set_target_properties(bcm_c_generated_asm PROPERTIES POSITION_INDEPENDENT_CODE ON)
 
-  function(prepend_path values prefix output)
-    set(result)
-    foreach(value ${values})
-      list(APPEND result "${prefix}/${value}")
-    endforeach(value)
-    set(${output} ${result} PARENT_SCOPE)
-  endfunction()
-
-  prepend_path("${BCM_ASM_SOURCES}" "${CMAKE_CURRENT_BINARY_DIR}" DELOCATE_ASM_ARGS)
-
+  go_executable(delocate boringssl.googlesource.com/boringssl/util/fipstools/delocate)
   add_custom_command(
     OUTPUT bcm-delocated.S
-    COMMAND ${GO_EXECUTABLE} run util/fipstools/delocate.go util/fipstools/delocate.peg.go util/fipstools/ar.go util/fipstools/const.go -a $<TARGET_FILE:bcm_c_generated_asm> -o ${CMAKE_CURRENT_BINARY_DIR}/bcm-delocated.S ${DELOCATE_ASM_ARGS}
-    DEPENDS bcm_c_generated_asm ${BCM_ASM_SOURCES} ${CMAKE_SOURCE_DIR}/util/fipstools/delocate.go ${CMAKE_SOURCE_DIR}/util/fipstools/delocate.peg.go ${CMAKE_SOURCE_DIR}/util/fipstools/ar.go ${CMAKE_SOURCE_DIR}/util/fipstools/const.go
-    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+    COMMAND ./delocate -a $<TARGET_FILE:bcm_c_generated_asm> -o bcm-delocated.S ${BCM_ASM_SOURCES}
+    DEPENDS bcm_c_generated_asm delocate ${BCM_ASM_SOURCES}
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
   )
 
   add_library(
@@ -171,11 +162,13 @@
   set_target_properties(bcm_hashunset PROPERTIES POSITION_INDEPENDENT_CODE ON)
   set_target_properties(bcm_hashunset PROPERTIES LINKER_LANGUAGE C)
 
+  go_executable(inject-hash
+	        boringssl.googlesource.com/boringssl/util/fipstools/inject-hash)
   add_custom_command(
     OUTPUT bcm.o
-    COMMAND ${GO_EXECUTABLE} run util/fipstools/inject-hash.go util/fipstools/ar.go util/fipstools/const.go -o ${CMAKE_CURRENT_BINARY_DIR}/bcm.o -in-archive $<TARGET_FILE:bcm_hashunset>
-    DEPENDS bcm_hashunset ${CMAKE_SOURCE_DIR}/util/fipstools/inject-hash.go ${CMAKE_SOURCE_DIR}/util/fipstools/ar.go ${CMAKE_SOURCE_DIR}/util/fipstools/const.go
-    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+    COMMAND ./inject-hash -o bcm.o -in-archive $<TARGET_FILE:bcm_hashunset>
+    DEPENDS bcm_hashunset inject-hash
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
   )
 
   # The outputs of add_custom_command cannot be referenced outside of the
diff --git a/util/fipstools/delocate.go b/util/fipstools/delocate/delocate.go
similarity index 99%
rename from util/fipstools/delocate.go
rename to util/fipstools/delocate/delocate.go
index d58e5be..a8c4fd0 100644
--- a/util/fipstools/delocate.go
+++ b/util/fipstools/delocate/delocate.go
@@ -25,6 +25,8 @@
 	"sort"
 	"strconv"
 	"strings"
+
+	"boringssl.googlesource.com/boringssl/util/fipstools/fipscommon"
 )
 
 // inputFile represents a textual assembly file.
@@ -1405,7 +1407,7 @@
 	w.WriteString(".type BORINGSSL_bcm_text_hash, @object\n")
 	w.WriteString(".size BORINGSSL_bcm_text_hash, 64\n")
 	w.WriteString("BORINGSSL_bcm_text_hash:\n")
-	for _, b := range uninitHashValue {
+	for _, b := range fipscommon.UninitHashValue {
 		w.WriteString(".byte 0x" + strconv.FormatUint(uint64(b), 16) + "\n")
 	}
 
@@ -1423,7 +1425,7 @@
 			}
 			defer arFile.Close()
 
-			ar, err := ParseAR(arFile)
+			ar, err := fipscommon.ParseAR(arFile)
 			if err != nil {
 				return err
 			}
diff --git a/util/fipstools/delocate.peg b/util/fipstools/delocate/delocate.peg
similarity index 100%
rename from util/fipstools/delocate.peg
rename to util/fipstools/delocate/delocate.peg
diff --git a/util/fipstools/delocate.peg.go b/util/fipstools/delocate/delocate.peg.go
similarity index 100%
rename from util/fipstools/delocate.peg.go
rename to util/fipstools/delocate/delocate.peg.go
diff --git a/util/fipstools/delocate_test.go b/util/fipstools/delocate/delocate_test.go
similarity index 100%
rename from util/fipstools/delocate_test.go
rename to util/fipstools/delocate/delocate_test.go
diff --git a/util/fipstools/testdata/ppc64le-GlobalEntry/in.s b/util/fipstools/delocate/testdata/ppc64le-GlobalEntry/in.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-GlobalEntry/in.s
rename to util/fipstools/delocate/testdata/ppc64le-GlobalEntry/in.s
diff --git a/util/fipstools/testdata/ppc64le-GlobalEntry/out.s b/util/fipstools/delocate/testdata/ppc64le-GlobalEntry/out.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-GlobalEntry/out.s
rename to util/fipstools/delocate/testdata/ppc64le-GlobalEntry/out.s
diff --git a/util/fipstools/testdata/ppc64le-LoadToR0/in.s b/util/fipstools/delocate/testdata/ppc64le-LoadToR0/in.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-LoadToR0/in.s
rename to util/fipstools/delocate/testdata/ppc64le-LoadToR0/in.s
diff --git a/util/fipstools/testdata/ppc64le-LoadToR0/out.s b/util/fipstools/delocate/testdata/ppc64le-LoadToR0/out.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-LoadToR0/out.s
rename to util/fipstools/delocate/testdata/ppc64le-LoadToR0/out.s
diff --git a/util/fipstools/testdata/ppc64le-Sample/in.s b/util/fipstools/delocate/testdata/ppc64le-Sample/in.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-Sample/in.s
rename to util/fipstools/delocate/testdata/ppc64le-Sample/in.s
diff --git a/util/fipstools/testdata/ppc64le-Sample/out.s b/util/fipstools/delocate/testdata/ppc64le-Sample/out.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-Sample/out.s
rename to util/fipstools/delocate/testdata/ppc64le-Sample/out.s
diff --git a/util/fipstools/testdata/ppc64le-Sample2/in.s b/util/fipstools/delocate/testdata/ppc64le-Sample2/in.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-Sample2/in.s
rename to util/fipstools/delocate/testdata/ppc64le-Sample2/in.s
diff --git a/util/fipstools/testdata/ppc64le-Sample2/out.s b/util/fipstools/delocate/testdata/ppc64le-Sample2/out.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-Sample2/out.s
rename to util/fipstools/delocate/testdata/ppc64le-Sample2/out.s
diff --git a/util/fipstools/testdata/ppc64le-TOCWithOffset/in.s b/util/fipstools/delocate/testdata/ppc64le-TOCWithOffset/in.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-TOCWithOffset/in.s
rename to util/fipstools/delocate/testdata/ppc64le-TOCWithOffset/in.s
diff --git a/util/fipstools/testdata/ppc64le-TOCWithOffset/out.s b/util/fipstools/delocate/testdata/ppc64le-TOCWithOffset/out.s
similarity index 100%
rename from util/fipstools/testdata/ppc64le-TOCWithOffset/out.s
rename to util/fipstools/delocate/testdata/ppc64le-TOCWithOffset/out.s
diff --git a/util/fipstools/testdata/x86_64-BSS/in.s b/util/fipstools/delocate/testdata/x86_64-BSS/in.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-BSS/in.s
rename to util/fipstools/delocate/testdata/x86_64-BSS/in.s
diff --git a/util/fipstools/testdata/x86_64-BSS/out.s b/util/fipstools/delocate/testdata/x86_64-BSS/out.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-BSS/out.s
rename to util/fipstools/delocate/testdata/x86_64-BSS/out.s
diff --git a/util/fipstools/testdata/x86_64-Basic/in.s b/util/fipstools/delocate/testdata/x86_64-Basic/in.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-Basic/in.s
rename to util/fipstools/delocate/testdata/x86_64-Basic/in.s
diff --git a/util/fipstools/testdata/x86_64-Basic/out.s b/util/fipstools/delocate/testdata/x86_64-Basic/out.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-Basic/out.s
rename to util/fipstools/delocate/testdata/x86_64-Basic/out.s
diff --git a/util/fipstools/testdata/x86_64-GOTRewrite/in.s b/util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-GOTRewrite/in.s
rename to util/fipstools/delocate/testdata/x86_64-GOTRewrite/in.s
diff --git a/util/fipstools/testdata/x86_64-GOTRewrite/out.s b/util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-GOTRewrite/out.s
rename to util/fipstools/delocate/testdata/x86_64-GOTRewrite/out.s
diff --git a/util/fipstools/testdata/x86_64-LabelRewrite/in1.s b/util/fipstools/delocate/testdata/x86_64-LabelRewrite/in1.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-LabelRewrite/in1.s
rename to util/fipstools/delocate/testdata/x86_64-LabelRewrite/in1.s
diff --git a/util/fipstools/testdata/x86_64-LabelRewrite/in2.s b/util/fipstools/delocate/testdata/x86_64-LabelRewrite/in2.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-LabelRewrite/in2.s
rename to util/fipstools/delocate/testdata/x86_64-LabelRewrite/in2.s
diff --git a/util/fipstools/testdata/x86_64-LabelRewrite/out.s b/util/fipstools/delocate/testdata/x86_64-LabelRewrite/out.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-LabelRewrite/out.s
rename to util/fipstools/delocate/testdata/x86_64-LabelRewrite/out.s
diff --git a/util/fipstools/testdata/x86_64-Sections/in.s b/util/fipstools/delocate/testdata/x86_64-Sections/in.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-Sections/in.s
rename to util/fipstools/delocate/testdata/x86_64-Sections/in.s
diff --git a/util/fipstools/testdata/x86_64-Sections/out.s b/util/fipstools/delocate/testdata/x86_64-Sections/out.s
similarity index 100%
rename from util/fipstools/testdata/x86_64-Sections/out.s
rename to util/fipstools/delocate/testdata/x86_64-Sections/out.s
diff --git a/util/fipstools/ar.go b/util/fipstools/fipscommon/ar.go
similarity index 99%
rename from util/fipstools/ar.go
rename to util/fipstools/fipscommon/ar.go
index 51e7aa5..85b378d 100644
--- a/util/fipstools/ar.go
+++ b/util/fipstools/fipscommon/ar.go
@@ -14,7 +14,7 @@
 
 // ar.go contains functions for parsing .a archive files.
 
-package main
+package fipscommon
 
 import (
 	"bytes"
diff --git a/util/fipstools/const.go b/util/fipstools/fipscommon/const.go
similarity index 91%
rename from util/fipstools/const.go
rename to util/fipstools/fipscommon/const.go
index 2e009ac..5693414 100644
--- a/util/fipstools/const.go
+++ b/util/fipstools/fipscommon/const.go
@@ -12,11 +12,11 @@
 // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
 
-package main
+package fipscommon
 
-// uninitHashValue is the default hash value that we inject into the module.
+// UninitHashValue is the default hash value that we inject into the module.
 // This value need only be distinct, i.e. so that we can safely
 // search-and-replace it in an object file.
-var uninitHashValue = [64]byte{
+var UninitHashValue = [64]byte{
 	0xae, 0x2c, 0xea, 0x2a, 0xbd, 0xa6, 0xf3, 0xec, 0x97, 0x7f, 0x9b, 0xf6, 0x94, 0x9a, 0xfc, 0x83, 0x68, 0x27, 0xcb, 0xa0, 0xa0, 0x9f, 0x6b, 0x6f, 0xde, 0x52, 0xcd, 0xe2, 0xcd, 0xff, 0x31, 0x80, 0xa2, 0xd4, 0xc3, 0x66, 0x0f, 0xc2, 0x6a, 0x7b, 0xf4, 0xbe, 0x39, 0xa2, 0xd7, 0x25, 0xdb, 0x21, 0x98, 0xe9, 0xd5, 0x53, 0xbf, 0x5c, 0x32, 0x06, 0x83, 0x34, 0x0c, 0x65, 0x89, 0x52, 0xbd, 0x1f,
 }
diff --git a/util/fipstools/inject-hash.go b/util/fipstools/inject-hash/inject-hash.go
similarity index 94%
rename from util/fipstools/inject-hash.go
rename to util/fipstools/inject-hash/inject-hash.go
index 688024d..14418a3 100644
--- a/util/fipstools/inject-hash.go
+++ b/util/fipstools/inject-hash/inject-hash.go
@@ -28,6 +28,8 @@
 	"io"
 	"io/ioutil"
 	"os"
+
+	"boringssl.googlesource.com/boringssl/util/fipstools/fipscommon"
 )
 
 func do(outPath, oInput string, arInput string) error {
@@ -43,7 +45,7 @@
 		}
 		defer arFile.Close()
 
-		ar, err := ParseAR(arFile)
+		ar, err := fipscommon.ParseAR(arFile)
 		if err != nil {
 			return err
 		}
@@ -145,12 +147,12 @@
 	// Replace the default hash value in the object with the calculated
 	// value and write it out.
 
-	offset := bytes.Index(objectBytes, uninitHashValue[:])
+	offset := bytes.Index(objectBytes, fipscommon.UninitHashValue[:])
 	if offset < 0 {
 		return errors.New("did not find uninitialised hash value in object file")
 	}
 
-	if bytes.Index(objectBytes[offset+1:], uninitHashValue[:]) >= 0 {
+	if bytes.Index(objectBytes[offset+1:], fipscommon.UninitHashValue[:]) >= 0 {
 		return errors.New("found two occurrences of uninitialised hash value in object file")
 	}
 
diff --git a/util/godeps.go b/util/godeps.go
new file mode 100644
index 0000000..960faa4
--- /dev/null
+++ b/util/godeps.go
@@ -0,0 +1,203 @@
+// Copyright (c) 2018, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// godeps prints out dependencies of a package in either CMake or Make depfile
+// format, for incremental rebuilds.
+//
+// The depfile format is preferred. It works correctly when new files are added.
+// However, CMake only supports depfiles for custom commands with Ninja and
+// starting CMake 3.7. For other configurations, we also support CMake's format,
+// but CMake must be rerun when file lists change.
+package main
+
+import (
+	"flag"
+	"fmt"
+	"go/build"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+)
+
+var (
+	format  = flag.String("format", "cmake", "The format to output to, either 'cmake' or 'depfile'")
+	mainPkg = flag.String("pkg", "", "The package to print dependencies for")
+	target  = flag.String("target", "", "The name of the output file")
+	out     = flag.String("out", "", "The path to write the output to. If unset, this is stdout")
+)
+
+func cMakeQuote(in string) string {
+	// See https://cmake.org/cmake/help/v3.0/manual/cmake-language.7.html#quoted-argument
+	var b strings.Builder
+	b.Grow(len(in))
+	// Iterate over in as bytes.
+	for i := 0; i < len(in); i++ {
+		switch c := in[i]; c {
+		case '\\', '"':
+			b.WriteByte('\\')
+			b.WriteByte(c)
+		case '\t':
+			b.WriteString("\\t")
+		case '\r':
+			b.WriteString("\\r")
+		case '\n':
+			b.WriteString("\\n")
+		default:
+			b.WriteByte(in[i])
+		}
+	}
+	return b.String()
+}
+
+func writeCMake(outFile *os.File, files []string) error {
+	for i, file := range files {
+		if i != 0 {
+			if _, err := outFile.WriteString(";"); err != nil {
+				return err
+			}
+		}
+		if _, err := outFile.WriteString(cMakeQuote(file)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func makeQuote(in string) string {
+	// See https://www.gnu.org/software/make/manual/make.html#Rule-Syntax
+	var b strings.Builder
+	b.Grow(len(in))
+	// Iterate over in as bytes.
+	for i := 0; i < len(in); i++ {
+		switch c := in[i]; c {
+		case '$':
+			b.WriteString("$$")
+		case '#', '\\', ' ':
+			b.WriteByte('\\')
+			b.WriteByte(c)
+		default:
+			b.WriteByte(c)
+		}
+	}
+	return b.String()
+}
+
+func writeDepfile(outFile *os.File, files []string) error {
+	if _, err := fmt.Fprintf(outFile, "%s:", makeQuote(*target)); err != nil {
+		return err
+	}
+	for _, file := range files {
+		if _, err := fmt.Fprintf(outFile, " %s", makeQuote(file)); err != nil {
+			return err
+		}
+	}
+	_, err := outFile.WriteString("\n")
+	return err
+}
+
+func appendPrefixed(list, newFiles []string, prefix string) []string {
+	for _, file := range newFiles {
+		list = append(list, filepath.Join(prefix, file))
+	}
+	return list
+}
+
+func main() {
+	flag.Parse()
+
+	if len(*mainPkg) == 0 {
+		fmt.Fprintf(os.Stderr, "-pkg argument is required.\n")
+		os.Exit(1)
+	}
+
+	var isDepfile bool
+	switch *format {
+	case "depfile":
+		isDepfile = true
+	case "cmake":
+		isDepfile = false
+	default:
+		fmt.Fprintf(os.Stderr, "Unknown format: %q\n", *format)
+		os.Exit(1)
+	}
+
+	if isDepfile && len(*target) == 0 {
+		fmt.Fprintf(os.Stderr, "-target argument is required for depfile.\n")
+		os.Exit(1)
+	}
+
+	done := make(map[string]struct{})
+	var files []string
+	var recurse func(pkgName string) error
+	recurse = func(pkgName string) error {
+		pkg, err := build.Default.Import(pkgName, ".", 0)
+		if err != nil {
+			return err
+		}
+
+		// Skip standard packages.
+		if pkg.Goroot {
+			return nil
+		}
+
+		// Skip already-visited packages.
+		if _, ok := done[pkg.Dir]; ok {
+			return nil
+		}
+		done[pkg.Dir] = struct{}{}
+
+		files = appendPrefixed(files, pkg.GoFiles, pkg.Dir)
+		files = appendPrefixed(files, pkg.CgoFiles, pkg.Dir)
+		// Include ignored Go files. A subsequent change may cause them
+		// to no longer be ignored.
+		files = appendPrefixed(files, pkg.IgnoredGoFiles, pkg.Dir)
+
+		// Recurse into imports.
+		for _, importName := range pkg.Imports {
+			if err := recurse(importName); err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+	if err := recurse(*mainPkg); err != nil {
+		fmt.Fprintf(os.Stderr, "Error getting dependencies: %s\n", err)
+		os.Exit(1)
+	}
+
+	sort.Strings(files)
+
+	outFile := os.Stdout
+	if len(*out) != 0 {
+		var err error
+		outFile, err = os.Create(*out)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
+			os.Exit(1)
+		}
+		defer outFile.Close()
+	}
+
+	var err error
+	if isDepfile {
+		err = writeDepfile(outFile, files)
+	} else {
+		err = writeCMake(outFile, files)
+	}
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
+		os.Exit(1)
+	}
+}