added interdependence graph generation argument

This commit is contained in:
Rob1103 2023-01-29 18:30:39 +01:00
parent 6b5ed45943
commit b5ec3c3329
4 changed files with 229 additions and 44 deletions

View file

@ -7,7 +7,6 @@
package buildtool package buildtool
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -106,6 +105,32 @@ func parseMakeOutput(output string) string {
return sb.String() return sb.String()
} }
// addConfigFiles adds user-provided configuration files to the app unikernel folder.
func addConfigFiles(configFiles []string, selectedFiles *[]string, includeFolder,
appFolder string) {
for _, configFilePath := range configFiles {
configFile := filepath.Base(configFilePath)
fileExt := filepath.Ext(configFile)
// Copy config file
if fileExt == ".h" || fileExt == ".hpp" || fileExt == ".hcc" {
if err := u.CopyFileContents(configFilePath, includeFolder+configFile); err != nil {
u.PrintErr(err)
}
} else if fileExt == ".c" || fileExt == ".cpp" || fileExt == ".cc" {
if err := u.CopyFileContents(configFilePath, appFolder+configFile); err != nil {
u.PrintErr(err)
}
// Add Makefile.uk entry
*selectedFiles = append(*selectedFiles, configFile)
} else {
u.PrintWarning("Unsupported extension for file: " + configFile)
}
}
}
// -------------------------------------Run------------------------------------- // -------------------------------------Run-------------------------------------
// RunBuildTool runs the automatic build tool to build a unikernel of a // RunBuildTool runs the automatic build tool to build a unikernel of a
@ -123,8 +148,6 @@ func RunBuildTool(homeDir string, data *u.Data) {
u.PrintErr(err) u.PrintErr(err)
} }
fmt.Println(args)
fmt.Println(*args.StringListArg[configArg])
// Get program Name // Get program Name
programName := *args.StringArg[programArg] programName := *args.StringArg[programArg]
@ -216,25 +239,7 @@ func RunBuildTool(homeDir string, data *u.Data) {
} }
// Move config files to Unikraft folder // Move config files to Unikraft folder
configArg := *args.StringListArg[configArg] addConfigFiles(*args.StringListArg[configArg], &selectedFiles, *includeFolder, appFolder)
for _, configFilePath := range configArg {
pathSplit := strings.Split(configFilePath, "/")
file := pathSplit[len(pathSplit)-1]
fileSplit := strings.Split(file, ".")
ext := fileSplit[len(fileSplit)-1]
if ext == "h" || ext == "hpp" || ext == "hcc" {
if err = u.CopyFileContents(configFilePath, *includeFolder+file); err != nil {
u.PrintErr(err)
}
} else if ext == "c" || ext == "cpp" || ext == "cc" {
if err = u.CopyFileContents(configFilePath, appFolder+file); err != nil {
u.PrintErr(err)
}
} else {
u.PrintWarning("Unsupported extension for file: " + file)
}
}
// Match micro-libs // Match micro-libs
matchedLibs, externalLibs, err := matchLibs(unikraftPath+"lib"+u.SEP, data) matchedLibs, externalLibs, err := matchLibs(unikraftPath+"lib"+u.SEP, data)
@ -242,8 +247,6 @@ func RunBuildTool(homeDir string, data *u.Data) {
u.PrintErr(err) u.PrintErr(err)
} }
fmt.Println("\nPREFINAL\n")
fmt.Println(matchedLibs)
// Clone the external git repositories // Clone the external git repositories
cloneLibsFolders(workspacePath, matchedLibs, externalLibs) cloneLibsFolders(workspacePath, matchedLibs, externalLibs)
@ -257,8 +260,6 @@ func RunBuildTool(homeDir string, data *u.Data) {
u.PrintOk("Match lib: " + lib) u.PrintOk("Match lib: " + lib)
} }
fmt.Println("\nFINAL\n")
fmt.Println(matchedLibs)
// Clone the external git repositories (if changed) // Clone the external git repositories (if changed)
cloneLibsFolders(workspacePath, matchedLibs, externalLibs) cloneLibsFolders(workspacePath, matchedLibs, externalLibs)

View file

@ -7,9 +7,10 @@
package dependtool package dependtool
import ( import (
"github.com/akamensky/argparse"
"os" "os"
u "tools/srcs/common" u "tools/srcs/common"
"github.com/akamensky/argparse"
) )
const ( const (
@ -22,6 +23,7 @@ const (
fullDepsArg = "fullDeps" fullDepsArg = "fullDeps"
fullStaticAnalysis = "fullStaticAnalysis" fullStaticAnalysis = "fullStaticAnalysis"
typeAnalysis = "typeAnalysis" typeAnalysis = "typeAnalysis"
interdependArg = "interdepend"
) )
// parseLocalArguments parses arguments of the application. // parseLocalArguments parses arguments of the application.
@ -53,6 +55,9 @@ func parseLocalArguments(p *argparse.Parser, args *u.Arguments) error {
args.InitArgParse(p, args, u.INT, "", typeAnalysis, args.InitArgParse(p, args, u.INT, "", typeAnalysis,
&argparse.Options{Required: false, Default: 0, &argparse.Options{Required: false, Default: 0,
Help: "Kind of analysis (0: both; 1: static; 2: dynamic)"}) Help: "Kind of analysis (0: both; 1: static; 2: dynamic)"})
args.InitArgParse(p, args, u.BOOL, "i", interdependArg,
&argparse.Options{Required: false, Default: false,
Help: "Create the source files interdependence graph"})
return u.ParserWrapper(p, os.Args) return u.ParserWrapper(p, os.Args)
} }

View file

@ -4,6 +4,8 @@ import (
"debug/elf" "debug/elf"
"errors" "errors"
"fmt" "fmt"
"os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
u "tools/srcs/common" u "tools/srcs/common"
@ -11,6 +13,110 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
) )
// sourceFileIncludesAnalysis collects all the include directives from a C/C++ source file.
//
// It returns a slice containing the found header names.
func sourceFileIncludesAnalysis(sourceFile string) []string {
var fileIncludes []string
fileLines, err := u.ReadLinesFile(sourceFile)
if err != nil {
u.PrintErr(err)
}
for _, line := range fileLines {
if strings.Contains(line, "#include") {
line = strings.ReplaceAll(line, " ", "")
line := strings.Split(line, "#include")[1]
if strings.HasPrefix(line, "\"") {
fileIncludes = append(fileIncludes, line[1:strings.Index(line[1:], "\"")+1])
} else if strings.HasPrefix(line, "<") {
fileIncludes = append(fileIncludes, line[1:strings.Index(line[1:], ">")+1])
}
}
}
return fileIncludes
}
// gccSourceFileIncludesAnalysis collects all the include directives from a C/C++ source file using
// the gcc preprocessor.
//
// It returns a slice containing the found header names.
func gccSourceFileIncludesAnalysis(sourceFile, tmpFolder string) []string {
var fileIncludes []string
outputStr, _ := ExecuteCommand("gcc", []string{"-E", sourceFile, "-I", tmpFolder})
outputSlice := strings.Split(outputStr, "\n")
for _, line := range outputSlice {
// Only interested in headers not coming from the standard library
if strings.Contains(line, "\""+tmpFolder) {
line = strings.Split(line, "\""+tmpFolder)[1]
includeDirective := line[0:strings.Index(line[0:], "\"")]
if !u.Contains(fileIncludes, includeDirective) {
fileIncludes = append(fileIncludes, includeDirective)
}
}
}
return fileIncludes
}
// runInterdependAnalyser collects all the included headers names (i.e., dependencies) from each
// C/C++ source file of a program and builds an interdependence graph (dot file) between all these
// source files.
func runInterdependAnalyser(programPath, programName, outFolder string) {
// Find all program source files
sourceFiles, err := findSourcesFiles(getProgramFolder(programPath))
if err != nil {
u.PrintErr(err)
}
// Create a temporary folder and copy all source files into it for use with the gcc
// preprocessor
tmpFolder := "tmp/"
_, err = u.CreateFolder(tmpFolder)
if err != nil {
u.PrintErr(err)
}
var tmpFiles []string
for _, sourceFilePath := range sourceFiles {
if err := u.CopyFileContents(sourceFilePath,
tmpFolder+filepath.Base(sourceFilePath)); err != nil {
u.PrintErr(err)
}
tmpFiles = append(tmpFiles, tmpFolder+filepath.Base(sourceFilePath))
}
// Analyse source files include directives and collect header names. Source files are first
// analysed "by hand" to get all their include directives and then by gcc to make sure to avoid
// directives that are commented or subjected to a macro.
interdependMap := make(map[string][]string)
for _, tmpFile := range tmpFiles {
interdependMap[filepath.Base(tmpFile)] = make([]string, 0)
analysis := sourceFileIncludesAnalysis(tmpFile)
gccAnalysis := gccSourceFileIncludesAnalysis(tmpFile, tmpFolder)
for _, includeDirective := range gccAnalysis {
if u.Contains(analysis, includeDirective) {
interdependMap[filepath.Base(tmpFile)] =
append(interdependMap[filepath.Base(tmpFile)], includeDirective)
}
}
}
// Create dot file
u.GenerateGraph(programName, outFolder+programName, interdependMap, nil)
// Remove tmp folder
u.PrintInfo("Remove folder " + tmpFolder)
_ = os.RemoveAll(tmpFolder)
}
// RunAnalyserTool allows to run the dependency analyser tool. // RunAnalyserTool allows to run the dependency analyser tool.
func RunAnalyserTool(homeDir string, data *u.Data) { func RunAnalyserTool(homeDir string, data *u.Data) {
@ -95,6 +201,11 @@ func RunAnalyserTool(homeDir string, data *u.Data) {
if *args.BoolArg[fullDepsArg] { if *args.BoolArg[fullDepsArg] {
saveGraph(programName, outFolder, data) saveGraph(programName, outFolder, data)
} }
// Create source files interdependence graph if interdependence option is set
if *args.BoolArg[interdependArg] {
runInterdependAnalyser(programPath, programName, outFolder)
}
} }
// displayProgramDetails display various information such path, background, ... // displayProgramDetails display various information such path, background, ...
@ -157,8 +268,8 @@ func checkElf(programPath *string) (*elf.File, bool) {
} }
// runStaticAnalyser runs the static analyser // runStaticAnalyser runs the static analyser
func runStaticAnalyser(elfFile *elf.File, isDynamic, isLinux bool, args *u.Arguments, programName, programPath, func runStaticAnalyser(elfFile *elf.File, isDynamic, isLinux bool, args *u.Arguments, programName,
outFolder string, data *u.Data) { programPath, outFolder string, data *u.Data) {
staticAnalyser(elfFile, isDynamic, isLinux, *args, data, programPath) staticAnalyser(elfFile, isDynamic, isLinux, *args, data, programPath)
@ -228,3 +339,64 @@ func saveGraph(programName, outFolder string, data *u.Data) {
programName+"_shared_libs", data.DynamicData.SharedLibs, nil) programName+"_shared_libs", data.DynamicData.SharedLibs, nil)
} }
} }
/*
// /!\ MISSING "/" !!!
stringFile := "#include<stdlib.h>\n/* #include ta mère *\nint main() {\n\t// Salut bitch !\n\treturn 0;\n}"
for {
comStartIndex := strings.Index(stringFile, "/*")
if comStartIndex != -1 {
comEndIndex := strings.Index(stringFile, "*")
stringFile = strings.Join([]string{stringFile[:comStartIndex],
stringFile[comEndIndex+2:]}, "")
} else {
break
}
}
//what to do with "\t" in lines ?
var finalFile []string
sliceFile := strings.Split(stringFile, "\n")
for i := 0; i < len(sliceFile); i++ {
if !strings.HasPrefix(sliceFile[i], "//") {
finalFile = append(finalFile, sliceFile[i])
}
}
}
// Remove dependencies whose files are not in program directory (e.g., stdio, stdlib, ...)
for internalFile, dependencies := range interdependMap {
var internalDep []string
for _, dependency := range dependencies {
if _, ok := interdependMap[dependency]; ok {
a++
internalDep = append(internalDep, dependency)
}
}
interdependMap[internalFile] = internalDep
}
// Detect and print removable program source files (i.e., files that no other file depends
// on)
var removableFiles []string
for internalFile := range interdependMap {
depends := false
for _, dependencies := range interdependMap {
for _, dependency := range dependencies {
if internalFile == dependency {
depends = true
break
}
}
if depends {
break
}
}
if !depends {
removableFiles = append(removableFiles, internalFile)
}
}
fmt.Println("Removable program source files of ", programName, ":")
fmt.Println(removableFiles)
*/

View file

@ -194,6 +194,25 @@ func executeDependAptCache(programName string, data *u.StaticData,
return nil return nil
} }
// getProgramFolder gets the folder path in which the given program is located, according to the
// Unikraft standard (e.g., /home/.../apps/programFolder/.../program).
//
// It returns the folder containing the program files according to the standard described above.
func getProgramFolder(programPath string) string {
tmp := strings.Split(programPath, "/")
i := 2
for ; i < len(tmp); i++ {
if tmp[len(tmp)-i] == "apps" {
break
}
}
folderPath := strings.Join(tmp[:len(tmp)-i+2], "/")
return folderPath
}
// findSourcesFiles puts together all C/C++ source files found in a given application folder. // findSourcesFiles puts together all C/C++ source files found in a given application folder.
// //
// It returns a slice containing the found source file names and an error if any, otherwise it // It returns a slice containing the found source file names and an error if any, otherwise it
@ -228,10 +247,7 @@ func findSourcesFiles(workspace string) ([]string, error) {
// it returns nil. // it returns nil.
func ExecuteCommand(command string, arguments []string) (string, error) { func ExecuteCommand(command string, arguments []string) (string, error) {
out, err := exec.Command(command, arguments...).CombinedOutput() out, err := exec.Command(command, arguments...).CombinedOutput()
if err != nil { return string(out), err
return "", err
}
return string(out), nil
} }
// addSourceFileSymbols adds all the symbols present in 'output' to the static data field in // addSourceFileSymbols adds all the symbols present in 'output' to the static data field in
@ -275,21 +291,12 @@ func extractPrototype(sourcesFiltered []string, data *u.Data) error {
// It returns an error if any, otherwise it returns nil. // It returns an error if any, otherwise it returns nil.
func gatherSourceFileSymbols(data *u.Data, programPath string) error { func gatherSourceFileSymbols(data *u.Data, programPath string) error {
tmp := strings.Split(programPath, "/") sourceFiles, err := findSourcesFiles(getProgramFolder(programPath))
i := 2
for ; i < len(tmp); i++ {
if tmp[len(tmp)-i] == "apps" {
break
}
}
folderPath := strings.Join(tmp[:len(tmp)-i+2], "/")
files, err := findSourcesFiles(folderPath)
if err != nil { if err != nil {
u.PrintErr(err) u.PrintErr(err)
} }
if err := extractPrototype(files, data); err != nil { if err := extractPrototype(sourceFiles, data); err != nil {
u.PrintErr(err) u.PrintErr(err)
} }
return nil return nil