diff --git a/srcs/buildtool/run_buildtool.go b/srcs/buildtool/run_buildtool.go index 8bfc63f..8607148 100644 --- a/srcs/buildtool/run_buildtool.go +++ b/srcs/buildtool/run_buildtool.go @@ -7,7 +7,6 @@ package buildtool import ( - "fmt" "io/ioutil" "os" "path/filepath" @@ -106,6 +105,32 @@ func parseMakeOutput(output string) 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------------------------------------- // 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) } - fmt.Println(args) - fmt.Println(*args.StringListArg[configArg]) // Get program Name programName := *args.StringArg[programArg] @@ -216,25 +239,7 @@ func RunBuildTool(homeDir string, data *u.Data) { } // Move config files to Unikraft folder - configArg := *args.StringListArg[configArg] - 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) - } - } + addConfigFiles(*args.StringListArg[configArg], &selectedFiles, *includeFolder, appFolder) // Match micro-libs matchedLibs, externalLibs, err := matchLibs(unikraftPath+"lib"+u.SEP, data) @@ -242,8 +247,6 @@ func RunBuildTool(homeDir string, data *u.Data) { u.PrintErr(err) } - fmt.Println("\nPREFINAL\n") - fmt.Println(matchedLibs) // Clone the external git repositories cloneLibsFolders(workspacePath, matchedLibs, externalLibs) @@ -257,8 +260,6 @@ func RunBuildTool(homeDir string, data *u.Data) { u.PrintOk("Match lib: " + lib) } - fmt.Println("\nFINAL\n") - fmt.Println(matchedLibs) // Clone the external git repositories (if changed) cloneLibsFolders(workspacePath, matchedLibs, externalLibs) diff --git a/srcs/dependtool/args.go b/srcs/dependtool/args.go index 8cba529..f209847 100644 --- a/srcs/dependtool/args.go +++ b/srcs/dependtool/args.go @@ -7,9 +7,10 @@ package dependtool import ( - "github.com/akamensky/argparse" "os" u "tools/srcs/common" + + "github.com/akamensky/argparse" ) const ( @@ -22,6 +23,7 @@ const ( fullDepsArg = "fullDeps" fullStaticAnalysis = "fullStaticAnalysis" typeAnalysis = "typeAnalysis" + interdependArg = "interdepend" ) // 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, &argparse.Options{Required: false, Default: 0, 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) } diff --git a/srcs/dependtool/run_deptool.go b/srcs/dependtool/run_deptool.go index 4041dd5..85ee901 100644 --- a/srcs/dependtool/run_deptool.go +++ b/srcs/dependtool/run_deptool.go @@ -4,6 +4,8 @@ import ( "debug/elf" "errors" "fmt" + "os" + "path/filepath" "runtime" "strings" u "tools/srcs/common" @@ -11,6 +13,110 @@ import ( "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. func RunAnalyserTool(homeDir string, data *u.Data) { @@ -95,6 +201,11 @@ func RunAnalyserTool(homeDir string, data *u.Data) { if *args.BoolArg[fullDepsArg] { 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, ... @@ -157,8 +268,8 @@ func checkElf(programPath *string) (*elf.File, bool) { } // runStaticAnalyser runs the static analyser -func runStaticAnalyser(elfFile *elf.File, isDynamic, isLinux bool, args *u.Arguments, programName, programPath, - outFolder string, data *u.Data) { +func runStaticAnalyser(elfFile *elf.File, isDynamic, isLinux bool, args *u.Arguments, programName, + programPath, outFolder string, data *u.Data) { 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) } } + +/* +// /!\ MISSING "/" !!! +stringFile := "#include\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) +*/ diff --git a/srcs/dependtool/static_analyser.go b/srcs/dependtool/static_analyser.go index c54ae7f..c09f630 100644 --- a/srcs/dependtool/static_analyser.go +++ b/srcs/dependtool/static_analyser.go @@ -194,6 +194,25 @@ func executeDependAptCache(programName string, data *u.StaticData, 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. // // 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. func ExecuteCommand(command string, arguments []string) (string, error) { out, err := exec.Command(command, arguments...).CombinedOutput() - if err != nil { - return "", err - } - return string(out), nil + return string(out), err } // 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. func gatherSourceFileSymbols(data *u.Data, programPath string) error { - 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], "/") - - files, err := findSourcesFiles(folderPath) + sourceFiles, err := findSourcesFiles(getProgramFolder(programPath)) if err != nil { u.PrintErr(err) } - if err := extractPrototype(files, data); err != nil { + if err := extractPrototype(sourceFiles, data); err != nil { u.PrintErr(err) } return nil