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
}
func repackage ( config Config , outputDir string ) error { if outputDirInfo , err := os . Stat ( outputDir ) ; err != nil { return errors . Wrapf ( err , " " , outputDir ) } else if ! outputDirInfo . IsDir ( ) { return errors . Wrapf ( err , " " , outputDir ) } vendorDir := path . Join ( outputDir , internalDir ) // remove output directory if it already exists if err := os . RemoveAll ( vendorDir ) ; err != nil { return errors . Wrapf ( err , " " , vendorDir ) } if err := os . Mkdir ( vendorDir , 0755 ) ; err != nil { return errors . Wrapf ( err , " " , 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 , " " , 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 , " " , projectRootDir , projectDstDir ) } if _ , err := removeEmptyDirs ( projectDstDir ) ; err != nil { return errors . Wrapf ( err , " " , projectDstDir ) } } else if err != nil { return errors . Wrapf ( err , " " , projectDstDir ) } projectDstDirImport , err := build . ImportDir ( projectDstDir , build . FindOnly ) if err != nil { return errors . Wrapf ( err , " " , 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 ( ) , " " ) { fileNode , err := parser . ParseFile ( fileSet , currPath , nil , parser . ParseComments ) if err != nil { return errors . Wrapf ( err , " " , currPath ) } goFiles [ currPath ] = fileNode for _ , currImport := range fileNode . Imports { currImportPathUnquoted , err := strconv . Unquote ( currImport . Path . Value ) if err != nil { return errors . Wrapf ( err , " " , currImport . Path . Value ) } updatedImport := " " if currImportPathUnquoted == " " { flagPkgImported = true updatedImport = path . Join ( projectDstDirImportPath , " " ) } 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 ( " " , currImportPathUnquoted , updatedImport ) } } } removeImportPathChecking ( fileNode ) // change package name for main packages if fileNode . Name . Name == " " { fileNode . Name = ast . NewIdent ( amalgomatedPackage ) // find the main function mainFunc := findFunction ( fileNode , " " ) if mainFunc != nil { err = renameFunction ( fileNode , " " , amalgomatedMain ) if err != nil { return errors . Wrapf ( err , " " , currPath ) } foundMain = true } } } return nil } ) ; err != nil { return err } if ! foundMain { return errors . Errorf ( " " , 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 , " " , " " ) fmtDstDir := path . Join ( projectDstDir , " " ) if err := shutil . CopyTree ( fmtSrcDir , fmtDstDir , vendorCopyOptions ( currPkg . OmitVendorDirs ) ) ; err != nil { return errors . Wrapf ( err , " " , fmtSrcDir , fmtDstDir ) } if _ , err := removeEmptyDirs ( fmtDstDir ) ; err != nil { return errors . Wrapf ( err , " " , fmtDstDir ) } } for currGoFile , currNode := range goFiles { if err = writeAstToFile ( currGoFile , currNode , fileSet ) ; err != nil { return errors . Wrapf ( err , " " , 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.
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 .