amalgomate/astfuncs.go
func repackage(config Config, outputDir string) error {
	if outputDirInfo, err := os.Stat(outputDir); err != nil {
		return errors.Wrapf(err, "failed to stat output directory: %s", outputDir)
	} else if !outputDirInfo.IsDir() {
		return errors.Wrapf(err, "not a directory: %s", outputDir)
	}

	vendorDir := path.Join(outputDir, internalDir)
	// remove output directory if it already exists
	if err := os.RemoveAll(vendorDir); err != nil {
		return errors.Wrapf(err, "failed to remove directory: %s", vendorDir)
	}

	if err := os.Mkdir(vendorDir, 0755); err != nil {
		return errors.Wrapf(err, "failed to create %s directory at %s", internalDir, vendorDir)
	}

	processedPkgs := make(map[SrcPkg]bool, len(config.Pkgs))
	for _, currName := range sortedKeys(config.Pkgs) {
		currPkg := config.Pkgs[currName]

		// if multiple keys specify the exact same source package, only process once
		if processedPkgs[currPkg] {
			continue
		}

		mainPkg, err := build.Import(currPkg.MainPkg, outputDir, build.FindOnly)
		if err != nil {
			return errors.Wrapf(err, "failed to get information for package %s for output directory %s", currPkg.MainPkg, outputDir)
		}

		// get location of main package on disk
		mainDir := mainPkg.Dir

		// get project import path and location of project package directory
		projectRootDir := mainDir
		projectImportPath := currPkg.MainPkg
		for i := 0; i < currPkg.DistanceToProjectPkg; i++ {
			projectRootDir = path.Dir(projectRootDir)
			projectImportPath = path.Dir(projectImportPath)
		}

		// copy project package into vendor directory in output dir if it does not already exist
		projectDstDir := path.Join(vendorDir, projectImportPath)

		if _, err := os.Stat(projectDstDir); os.IsNotExist(err) {
			if err := shutil.CopyTree(projectRootDir, projectDstDir, vendorCopyOptions(currPkg.OmitVendorDirs)); err != nil {
				return errors.Wrapf(err, "failed to copy directory %s to %s", projectRootDir, projectDstDir)
			}
			if _, err := removeEmptyDirs(projectDstDir); err != nil {
				return errors.Wrapf(err, "failed to remove empty directories in destination %s", projectDstDir)
			}
		} else if err != nil {
			return errors.Wrapf(err, "failed to stat %s", projectDstDir)
		}

		projectDstDirImport, err := build.ImportDir(projectDstDir, build.FindOnly)
		if err != nil {
			return errors.Wrapf(err, "unable to import project destination directory %s", projectDstDir)
		}
		projectDstDirImportPath := projectDstDirImport.ImportPath

		// rewrite imports for all files in copied directory
		fileSet := token.NewFileSet()
		foundMain := false
		goFiles := make(map[string]*ast.File)

		flagPkgImported := false
		if err := filepath.Walk(projectDstDir, func(currPath string, currInfo os.FileInfo, err error) error {
			if !currInfo.IsDir() && strings.HasSuffix(currInfo.Name(), ".go") {
				fileNode, err := parser.ParseFile(fileSet, currPath, nil, parser.ParseComments)
				if err != nil {
					return errors.Wrapf(err, "failed to parse file %s", currPath)
				}
				goFiles[currPath] = fileNode

				for _, currImport := range fileNode.Imports {
					currImportPathUnquoted, err := strconv.Unquote(currImport.Path.Value)
					if err != nil {
						return errors.Wrapf(err, "unable to unquote import %s", currImport.Path.Value)
					}

					updatedImport := ""
					if currImportPathUnquoted == "flag" {
						flagPkgImported = true
						updatedImport = path.Join(projectDstDirImportPath, "amalgomated_flag")
					} else if strings.HasPrefix(currImportPathUnquoted, projectImportPath) {
						updatedImport = strings.Replace(currImportPathUnquoted, projectImportPath, projectDstDirImportPath, -1)
					}

					if updatedImport != "" {
						if !astutil.RewriteImport(fileSet, fileNode, currImportPathUnquoted, updatedImport) {
							return errors.Errorf("failed to rewrite import from %s to %s", currImportPathUnquoted, updatedImport)
						}
					}
				}

				removeImportPathChecking(fileNode)

				// change package name for main packages
				if fileNode.Name.Name == "main" {
					fileNode.Name = ast.NewIdent(amalgomatedPackage)

					// find the main function
					mainFunc := findFunction(fileNode, "main")
					if mainFunc != nil {
						err = renameFunction(fileNode, "main", amalgomatedMain)
						if err != nil {
							return errors.Wrapf(err, "failed to rename function in file %s", currPath)
						}
						foundMain = true
					}
				}
			}
			return nil
		}); err != nil {
			return err
		}

		if !foundMain {
			return errors.Errorf("main method not found in package %s", currPkg.MainPkg)
		}

		if flagPkgImported {
			// if "flag" package is imported, add "flag" as a rewritten vendored dependency. This is done
			// because flag.CommandLine is a global variable that is often used by programs and problems can
			// arise if multiple amalgomated programs use it. A custom rewritten import is used rather than
			// vendoring so that the amalgomated program can itself be vendored.
			goRoot, err := dirs.GoRoot()
			if err != nil {
				return errors.WithStack(err)
			}
			fmtSrcDir := path.Join(goRoot, "src", "flag")
			fmtDstDir := path.Join(projectDstDir, "amalgomated_flag")
			if err := shutil.CopyTree(fmtSrcDir, fmtDstDir, vendorCopyOptions(currPkg.OmitVendorDirs)); err != nil {
				return errors.Wrapf(err, "failed to copy directory %s to %s", fmtSrcDir, fmtDstDir)
			}
			if _, err := removeEmptyDirs(fmtDstDir); err != nil {
				return errors.Wrapf(err, "failed to remove empty directories in destination %s", fmtDstDir)
			}
		}

		for currGoFile, currNode := range goFiles {
			if err = writeAstToFile(currGoFile, currNode, fileSet); err != nil {
				return errors.Wrapf(err, "failed to write rewritten file %s", config)
			}
		}

		processedPkgs[currPkg] = true
	}
	return nil
}
func repackage(config Config, outputDir string) error {
	if outputDirInfo, err := os.Stat(outputDir); err != nil {
		return errors.Wrapf(err, "failed to stat output directory: %s", outputDir)
	} else if !outputDirInfo.IsDir() {
		return errors.Wrapf(err, "not a directory: %s", outputDir)
	}

	vendorDir := path.Join(outputDir, internalDir)
	// remove output directory if it already exists
	if err := os.RemoveAll(vendorDir); err != nil {
		return errors.Wrapf(err, "failed to remove directory: %s", vendorDir)
	}

	if err := os.Mkdir(vendorDir, 0755); err != nil {
		return errors.Wrapf(err, "failed to create %s directory at %s", internalDir, vendorDir)
	}

	processedPkgs := make(map[SrcPkg]bool, len(config.Pkgs))
	for _, currName := range sortedKeys(config.Pkgs) {
		currPkg := config.Pkgs[currName]

		// if multiple keys specify the exact same source package, only process once
		if processedPkgs[currPkg] {
			continue
		}

		mainPkg, err := build.Import(currPkg.MainPkg, outputDir, build.FindOnly)
		if err != nil {
			return errors.Wrapf(err, "failed to get information for package %s for output directory %s", currPkg.MainPkg, outputDir)
		}

		// get location of main package on disk
		mainDir := mainPkg.Dir

		// get project import path and location of project package directory
		projectRootDir := mainDir
		projectImportPath := currPkg.MainPkg
		for i := 0; i < currPkg.DistanceToProjectPkg; i++ {
			projectRootDir = path.Dir(projectRootDir)
			projectImportPath = path.Dir(projectImportPath)
		}

		// copy project package into vendor directory in output dir if it does not already exist
		projectDstDir := path.Join(vendorDir, projectImportPath)

		if _, err := os.Stat(projectDstDir); os.IsNotExist(err) {
			if err := shutil.CopyTree(projectRootDir, projectDstDir, vendorCopyOptions(currPkg.OmitVendorDirs)); err != nil {
				return errors.Wrapf(err, "failed to copy directory %s to %s", projectRootDir, projectDstDir)
			}
			if _, err := removeEmptyDirs(projectDstDir); err != nil {
				return errors.Wrapf(err, "failed to remove empty directories in destination %s", projectDstDir)
			}
		} else if err != nil {
			return errors.Wrapf(err, "failed to stat %s", projectDstDir)
		}

		projectDstDirImport, err := build.ImportDir(projectDstDir, build.FindOnly)
		if err != nil {
			return errors.Wrapf(err, "unable to import project destination directory %s", projectDstDir)
		}
		projectDstDirImportPath := projectDstDirImport.ImportPath

		// rewrite imports for all files in copied directory
		fileSet := token.NewFileSet()
		foundMain := false
		goFiles := make(map[string]*ast.File)

		flagPkgImported := false
		if err := filepath.Walk(projectDstDir, func(currPath string, currInfo os.FileInfo, err error) error {
			if !currInfo.IsDir() && strings.HasSuffix(currInfo.Name(), ".go") {
				fileNode, err := parser.ParseFile(fileSet, currPath, nil, parser.ParseComments)
				if err != nil {
					return errors.Wrapf(err, "failed to parse file %s", currPath)
				}
				goFiles[currPath] = fileNode

				for _, currImport := range fileNode.Imports {
					currImportPathUnquoted, err := strconv.Unquote(currImport.Path.Value)
					if err != nil {
						return errors.Wrapf(err, "unable to unquote import %s", currImport.Path.Value)
					}

					updatedImport := ""
					if currImportPathUnquoted == "flag" {
						flagPkgImported = true
						updatedImport = path.Join(projectDstDirImportPath, "amalgomated_flag")
					} else if strings.HasPrefix(currImportPathUnquoted, projectImportPath) {
						updatedImport = strings.Replace(currImportPathUnquoted, projectImportPath, projectDstDirImportPath, -1)
					}

					if updatedImport != "" {
						if !astutil.RewriteImport(fileSet, fileNode, currImportPathUnquoted, updatedImport) {
							return errors.Errorf("failed to rewrite import from %s to %s", currImportPathUnquoted, updatedImport)
						}
					}
				}

				removeImportPathChecking(fileNode)

				// change package name for main packages
				if fileNode.Name.Name == "main" {
					fileNode.Name = ast.NewIdent(amalgomatedPackage)

					// find the main function
					mainFunc := findFunction(fileNode, "main")
					if mainFunc != nil {
						err = renameFunction(fileNode, "main", amalgomatedMain)
						if err != nil {
							return errors.Wrapf(err, "failed to rename function in file %s", currPath)
						}
						foundMain = true
					}
				}
			}
			return nil
		}); err != nil {
			return err
		}

		if !foundMain {
			return errors.Errorf("main method not found in package %s", currPkg.MainPkg)
		}

		if flagPkgImported {
			// if "flag" package is imported, add "flag" as a rewritten vendored dependency. This is done
			// because flag.CommandLine is a global variable that is often used by programs and problems can
			// arise if multiple amalgomated programs use it. A custom rewritten import is used rather than
			// vendoring so that the amalgomated program can itself be vendored.
			goRoot, err := dirs.GoRoot()
			if err != nil {
				return errors.WithStack(err)
			}
			fmtSrcDir := path.Join(goRoot, "src", "flag")
			fmtDstDir := path.Join(projectDstDir, "amalgomated_flag")
			if err := shutil.CopyTree(fmtSrcDir, fmtDstDir, vendorCopyOptions(currPkg.OmitVendorDirs)); err != nil {
				return errors.Wrapf(err, "failed to copy directory %s to %s", fmtSrcDir, fmtDstDir)
			}
			if _, err := removeEmptyDirs(fmtDstDir); err != nil {
				return errors.Wrapf(err, "failed to remove empty directories in destination %s", fmtDstDir)
			}
		}

		for currGoFile, currNode := range goFiles {
			if err = writeAstToFile(currGoFile, currNode, fileSet); err != nil {
				return errors.Wrapf(err, "failed to write rewritten file %s", config)
			}
		}

		processedPkgs[currPkg] = true
	}
	return nil
}
// repackage repackages the main package specified in the provided configuration and re-writes them into the provided
// output directory. The repackaged files are placed into a directory called "vendor" that is created in the provided
// directory. This function assumes and verifies that the provided "outputDir" is a directory that exists. The provided
// configuration is processed based on the natural ordering of the name of the commands. If multiple commands specify
// the same package, it is only processed for the first command.