@@ -38,6 +38,7 @@ const (
3838 Docker Technology = "docker"
3939 Oci Technology = "oci"
4040 Conan Technology = "conan"
41+ NoTech Technology = ""
4142)
4243const Pypi = "pypi"
4344
@@ -322,10 +323,11 @@ func detectedTechnologiesListInPath(path string, recursive bool) (technologies [
322323}
323324
324325// If recursive is true, the search will not be limited to files in the root path.
326+ // If recursive is true the search may return Technology.NoTech value
325327// If requestedTechs is empty, all technologies will be checked.
326328// If excludePathPattern is not empty, files/directories that match the wildcard pattern will be excluded from the search.
327329func DetectTechnologiesDescriptors (path string , recursive bool , requestedTechs []string , requestedDescriptors map [Technology ][]string , excludePathPattern string ) (technologiesDetected map [Technology ]map [string ][]string , err error ) {
328- filesList , err := fspatterns . ListFiles (path , recursive , false , true , true , excludePathPattern )
330+ filesList , dirsList , err := listFilesAndDirs (path , recursive , true , true , excludePathPattern )
329331 if err != nil {
330332 return
331333 }
@@ -340,12 +342,161 @@ func DetectTechnologiesDescriptors(path string, recursive bool, requestedTechs [
340342 log .Debug (fmt .Sprintf ("mapped %d working directories with indicators/descriptors:\n %s" , len (workingDirectoryToIndicators ), strJson ))
341343 }
342344 technologiesDetected , err = mapWorkingDirectoriesToTechnologies (workingDirectoryToIndicators , excludedTechAtWorkingDir , ToTechnologies (requestedTechs ), requestedDescriptors )
343- if len (technologiesDetected ) > 0 {
344- log .Debug (fmt .Sprintf ("Detected %d technologies at %s: %s." , len (technologiesDetected ), path , maps .Keys (technologiesDetected )))
345+ if err != nil {
346+ return
347+ }
348+ if recursive {
349+ // If recursive search, we need to also make sure to include directories that do not have any technology indicators.
350+ technologiesDetected = addNoTechIfNeeded (technologiesDetected , path , dirsList )
351+ }
352+ techCount := len (technologiesDetected )
353+ if _ , exist := technologiesDetected [NoTech ]; exist {
354+ techCount --
355+ }
356+ if techCount > 0 {
357+ log .Debug (fmt .Sprintf ("Detected %d technologies at %s: %s." , techCount , path , maps .Keys (technologiesDetected )))
358+ }
359+ return
360+ }
361+
362+ func listFilesAndDirs (rootPath string , isRecursive , excludeWithRelativePath , preserveSymlink bool , excludePathPattern string ) (files , dirs []string , err error ) {
363+ filesOrDirsInPath , err := fspatterns .ListFiles (rootPath , isRecursive , true , excludeWithRelativePath , preserveSymlink , excludePathPattern )
364+ if err != nil {
365+ return
366+ }
367+ for _ , path := range filesOrDirsInPath {
368+ if isDir , e := fileutils .IsDirExists (path , preserveSymlink ); e != nil {
369+ err = errors .Join (err , fmt .Errorf ("failed to check if %s is a directory: %w" , path , e ))
370+ continue
371+ } else if isDir {
372+ dirs = append (dirs , path )
373+ } else {
374+ files = append (files , path )
375+ }
376+ }
377+ return
378+ }
379+
380+ func addNoTechIfNeeded (technologiesDetected map [Technology ]map [string ][]string , rootPath string , dirsList []string ) (_ map [Technology ]map [string ][]string ) {
381+ noTechMap := map [string ][]string {}
382+ for _ , dir := range getDirNoTechList (technologiesDetected , rootPath , dirsList ) {
383+ // Convert the directories
384+ noTechMap [dir ] = []string {}
385+ }
386+ if len (technologiesDetected ) == 0 || len (noTechMap ) > 0 {
387+ // no technologies detected at all (add NoTech without any directories) or some directories were added to NoTech
388+ technologiesDetected [NoTech ] = noTechMap
389+ }
390+ return technologiesDetected
391+ }
392+
393+ func getDirNoTechList (technologiesDetected map [Technology ]map [string ][]string , dir string , dirsList []string ) (noTechList []string ) {
394+ for _ , techDirs := range technologiesDetected {
395+ if _ , exist := techDirs [dir ]; exist {
396+ // The directory is already mapped to a technology, no need to add the dir or its sub directories to NoTech
397+ return
398+ }
399+ }
400+ children := getDirChildren (dir , dirsList )
401+ childNoTechCount := 0
402+ for _ , child := range children {
403+ childNoTechList := getDirNoTechList (technologiesDetected , child , dirsList )
404+ if len (childNoTechList ) > 0 {
405+ childNoTechCount ++
406+ }
407+ noTechList = append (noTechList , childNoTechList ... )
408+ }
409+ if childNoTechCount == len (children ) {
410+ // If all children exists in childNoTechList, add only the parent directory to NoTech
411+ noTechList = []string {dir }
412+ }
413+
414+ // for _, techDirs := range technologiesDetected {
415+ // if _, exist := techDirs[dir]; exist {
416+ // // The directory is already mapped to a technology, no need to add the dir or its sub directories to NoTech
417+ // break
418+ // }
419+ // for _, child := range children {
420+ // childNoTechList := getDirNoTechList(technologiesDetected, child, dirsList)
421+ // }
422+
423+ // if len(children) == 0 {
424+ // // No children directories, add the directory to NoTech
425+ // childNoTechList = append(childNoTechList, dir)
426+ // break
427+ // }
428+ // for _, child := range children {
429+ // childNoTechList = append(childNoTechList, getDirNoTechList(technologiesDetected, child, dirsList)...)
430+ // }
431+ // // If all children exists in childNoTechList, add only the parent directory to NoTech
432+ // if len(children) == len(childNoTechList) {
433+ // childNoTechList = []string{dir}
434+ // }
435+ // }
436+ return
437+ }
438+
439+ func getDirChildren (dir string , dirsList []string ) (children []string ) {
440+ for _ , dirPath := range dirsList {
441+ if filepath .Dir (dirPath ) == dir {
442+ children = append (children , dirPath )
443+ }
345444 }
346445 return
347446}
348447
448+ // func addNoTechIfNeeded(technologiesDetected map[Technology]map[string][]string, path, excludePathPattern string) (finalMap map[Technology]map[string][]string, err error) {
449+ // finalMap = technologiesDetected
450+ // noTechMap := map[string][]string{}
451+ // // TODO: not only direct, need to see if multiple levels of directories are missing technology indicators
452+ // // if all directories in are found no need for anything else,
453+ // // if one missing need to add it to NoTech
454+ // // if not one detected add only parent directory no need for each directory
455+ // directories, err := getDirectDirectories(path, excludePathPattern)
456+ // if err != nil {
457+ // return
458+ // }
459+ // for _, dir := range directories {
460+ // // Check if the directory is already mapped to a technology
461+ // isMapped := false
462+ // for _, techDirs := range finalMap {
463+ // if _, exist := techDirs[dir]; exist {
464+ // isMapped = true
465+ // break
466+ // }
467+ // }
468+ // if !isMapped {
469+ // // Add the directory to NoTech (no indicators/descriptors were found)
470+ // noTechMap[dir] = []string{}
471+ // }
472+ // }
473+ // if len(technologiesDetected) == 0 || len(noTechMap) > 0 {
474+ // // no technologies detected at all (add NoTech without any directories) or some directories were added to NoTech
475+ // finalMap[NoTech] = noTechMap
476+ // }
477+ // return
478+ // }
479+
480+ // func getDirectDirectories(path, excludePathPattern string) (directories []string, err error) {
481+ // // Get all files and directories in the path, not recursive
482+ // filesOrDirsInPath, err := fspatterns.ListFiles(path, false, true, true, true, excludePathPattern)
483+ // if err != nil {
484+ // return
485+ // }
486+ // // Filter to directories only
487+ // for _, potentialDir := range filesOrDirsInPath {
488+ // isDir, e := fileutils.IsDirExists(potentialDir, true)
489+ // if e != nil {
490+ // err = errors.Join(err, fmt.Errorf("failed to check if %s is a directory: %w", potentialDir, e))
491+ // continue
492+ // }
493+ // if isDir {
494+ // directories = append(directories, potentialDir)
495+ // }
496+ // }
497+ // return
498+ // }
499+
349500// Map files to relevant working directories according to the technologies' indicators/descriptors and requested descriptors.
350501// files: The file paths to map.
351502// requestedDescriptors: Special requested descriptors (for example in Pip requirement.txt can have different path) for each technology.
@@ -545,6 +696,9 @@ func hasCompletePathPrefix(root, wd string) bool {
545696func DetectedTechnologiesToSlice (detected map [Technology ]map [string ][]string ) []string {
546697 keys := make ([]string , 0 , len (detected ))
547698 for tech := range detected {
699+ if tech == NoTech {
700+ continue
701+ }
548702 keys = append (keys , string (tech ))
549703 }
550704 return keys
0 commit comments