فهرست منبع

Start working on automatic dependencies

Billy Barrow 1 ماه پیش
والد
کامیت
de6e815bc0

+ 2 - 2
MANIFEST.usm

@@ -19,7 +19,7 @@
       "lib:libglib-2.0.so.0",
       "lib:libgobject-2.0.so.0",
       "lib:libgio-2.0.so.0",
-      "lib:libinvercargill.so",
+      "lib:libinvercargill-1.so",
       "lib:libinvercargill-json.so",
       "lib:libc.so.6",
       "lib:libpcre2-8.so.0",
@@ -40,7 +40,7 @@
       "pc:gio-2.0.pc",
       "pc:gee-0.8.pc",
       "pc:json-glib-1.0.pc",
-      "pc:invercargill.pc",
+      "pc:invercargill-1.pc",
       "pc:invercargill-json.pc",
       "pc:gobject-introspection-1.0.pc"
     ],

+ 45 - 4
src/cli/Manifest.vala

@@ -1,3 +1,5 @@
+using Invercargill.DataStructures;
+
 Usm.Manifest manifest = null;
 string? build_path = null;
 
@@ -394,16 +396,55 @@ private int autoprovides() {
 
     try {
         var provides = autoprovides_scan_tree(install_dir.get_path(), install_dir.get_path());
-
-        var json = "\"provides\": {\n";
+        
+        // Use the new dependency detection system
+        var detector = new Usm.DependencyDetector(paths);
+        var detected_deps = detector.detect_dependencies(Environment.get_current_dir(), build_path, install_dir.get_path());
+
+        var json = "{\n";
+        
+        // Output provides section
+        json += "  \"provides\": {\n";
         foreach (var item in provides) {
             json += @"    \"$item\": \"as-expected\",\n";
         }
         if(provides.any()) {
             json = json.substring(0, json.length - 2);
         }
+        json += "\n  },\n";
+        
+        // Output dependencies section in USM schema format
+        json += "  \"depends\": {\n";
+        
+        // Build dependencies
+        json += "    \"build\": [\n";
+        var build_count = 0;
+        foreach (var dep in detected_deps.build_deps) {
+            build_count++;
+            json += @"      \"$dep\"";
+            if (build_count < detected_deps.build_deps.count()) {
+                json += ",";
+            }
+            json += "\n";
+        }
+        json += "    ],\n";
+        
+        // Runtime dependencies
+        json += "    \"runtime\": [\n";
+        var runtime_count = 0;
+        foreach (var dep in detected_deps.runtime_deps) {
+            runtime_count++;
+            json += @"      \"$dep\"";
+            if (runtime_count < detected_deps.build_deps.count()) {
+                json += ",";
+            }
+            json += "\n";
+        }
+        json += "    ]\n";
+        
+        json += "  }\n";
 
-        print(@"$json\n}\n");
+        print(@"$json}\n");
         return 0;
     }
     catch(Error e) {
@@ -450,4 +491,4 @@ public static Invercargill.DataStructures.Vector<string> autoprovides_scan_tree(
     }
     
     return provides;
-}
+}

+ 248 - 0
src/lib/BinaryAnalyzer.vala

@@ -0,0 +1,248 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Usm {
+
+    public class BinaryAnalyzer {
+        private Paths paths;
+
+        public BinaryAnalyzer(Paths paths) {
+            this.paths = paths;
+        }
+
+        public Set<ResourceRef> analyze_binaries_with_ldd(string install_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+
+            // Find all ELF binaries in the install path
+            var binaries = find_binaries_recursive(install_path);
+            
+            foreach (var binary in binaries) {
+                var binary_deps = analyze_single_binary(binary);
+                foreach (var dep in binary_deps) {
+                    deps.add(dep);
+                }
+            }
+
+            return deps;
+        }
+
+        public Set<ResourceRef> analyze_scripts(string install_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+
+            // Find all script files in the install path
+            var scripts = find_scripts_recursive(install_path);
+            
+            foreach (var script in scripts) {
+                var script_deps = analyze_single_script(script);
+                if (script_deps != null) {
+                    deps.add(script_deps);
+                }
+            }
+
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_single_binary(string binary_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+
+            try {
+                // Run ldd on the binary
+                var proc = new Subprocess.newv(
+                    new string[] { "ldd", binary_path }, 
+                    SubprocessFlags.STDOUT_PIPE | SubprocessFlags.STDERR_SILENCE
+                );
+                
+                var pipe = new DataInputStream(proc.get_stdout_pipe());
+                string line;
+                
+                while ((line = pipe.read_line()) != null) {
+                    var resource_ref = paths.get_suggested_resource_ref_for_ldd_line(line);
+                    if (resource_ref != null) {
+                        deps.add(resource_ref);
+                    }
+                }
+                
+                proc.wait_check();
+            } catch (Error e) {
+                // If ldd fails, the file might not be an ELF binary
+                // Just ignore and continue
+            }
+
+            return deps;
+        }
+
+        private ResourceRef? analyze_single_script(string script_path) throws Error {
+            var file = File.new_for_path(script_path);
+            
+            try {
+                var dis = new DataInputStream(file.read());
+                string first_line = dis.read_line();
+                
+                if (first_line != null) {
+                    var res = paths.get_suggested_resource_ref_for_shebang(first_line);
+                    return res;
+                }
+            } catch (Error e) {
+                // If we can't read the file, just ignore it
+            }
+
+            return null;
+        }
+
+        private string[] find_binaries_recursive(string root_path) throws Error {
+            var binaries = new string[0];
+            var root_dir = File.new_for_path(root_path);
+            
+            var enumerator = root_dir.enumerate_children("*", FileQueryInfoFlags.NONE);
+            FileInfo file_info;
+            
+            while ((file_info = enumerator.next_file()) != null) {
+                var child = root_dir.get_child(file_info.get_name());
+                var child_path = child.get_path();
+                
+                if (file_info.get_file_type() == FileType.DIRECTORY) {
+                    // Recursively search subdirectories
+                    var child_binaries = find_binaries_recursive(child_path);
+                    foreach (var binary in child_binaries) {
+                        binaries += binary;
+                    }
+                } else if (is_elf_binary(child_path)) {
+                    binaries += child_path;
+                }
+            }
+            
+            return binaries;
+        }
+
+        private string[] find_scripts_recursive(string root_path) throws Error {
+            var scripts = new string[0];
+            var root_dir = File.new_for_path(root_path);
+            
+            var enumerator = root_dir.enumerate_children("*", FileQueryInfoFlags.NONE);
+            FileInfo file_info;
+            
+            while ((file_info = enumerator.next_file()) != null) {
+                var child = root_dir.get_child(file_info.get_name());
+                var child_path = child.get_path();
+                
+                if (file_info.get_file_type() == FileType.DIRECTORY) {
+                    // Recursively search subdirectories
+                    var child_scripts = find_scripts_recursive(child_path);
+                    foreach (var script in child_scripts) {
+                        scripts += script;
+                    }
+                } else if (is_script_file(child_path)) {
+                    scripts += child_path;
+                }
+            }
+            
+            return scripts;
+        }
+
+        private bool is_elf_binary(string file_path) {
+            try {
+                // Check file magic to see if it's an ELF binary
+                var file = File.new_for_path(file_path);
+                var stream = file.read();
+                uint8[] buffer = new uint8[4];
+                stream.read(buffer);
+                
+                // ELF files start with 0x7F 'E' 'L' 'F'
+                return buffer[0] == 0x7F && buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F';
+            } catch (Error e) {
+                return false;
+            }
+        }
+
+        private bool is_script_file(string file_path) {
+            var basename = Path.get_basename(file_path);
+            
+            // Check if file is executable
+            try {
+                var file_info = File.new_for_path(file_path).query_info("standard::type,standard::is-executable", FileQueryInfoFlags.NONE);
+                if (!file_info.get_attribute_boolean("access::can-execute")) {
+                    return false;
+                }
+            } catch (Error e) {
+                return false;
+            }
+            
+            // Check if it has a script extension or common script names
+            var script_extensions = new string[] { ".sh", ".py", ".pl", ".rb", ".php", ".bash", ".zsh", ".fish" };
+            foreach (var ext in script_extensions) {
+                if (basename.has_suffix(ext)) {
+                    return true;
+                }
+            }
+            
+            // Try to read the first line to check for shebang
+            try {
+                var file = File.new_for_path(file_path);
+                var dis = new DataInputStream(file.read());
+                string first_line = dis.read_line();
+                
+                if (first_line != null && first_line.has_prefix("#!")) {
+                    return true;
+                }
+            } catch (Error e) {
+                // Can't read the file, assume it's not a script
+            }
+            
+            return false;
+        }
+
+        public Set<ResourceRef> analyze_libraries_in_path(string lib_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            
+            // Look for .so files in standard library paths
+            var lib_dirs = new string[] {
+                "/usr/lib", "/usr/lib64", "/lib", "/lib64",
+                "/usr/local/lib", "/usr/local/lib64"
+            };
+            
+            foreach (var dir in lib_dirs) {
+                var full_path = Path.build_filename(dir, lib_path);
+                var lib_file = File.new_for_path(full_path);
+                
+                if (lib_file.query_exists()) {
+                    var resource_ref = paths.get_suggested_resource_ref_for_path(full_path);
+                    if (resource_ref != null) {
+                        deps.add(resource_ref);
+                    }
+                }
+            }
+            
+            return deps;
+        }
+
+        public Set<ResourceRef> analyze_pkgconfig_files(string install_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            
+            // Look for .pc files in standard pkg-config paths
+            var pc_dirs = new string[] {
+                Path.build_filename(install_path, "usr", "lib", "pkgconfig"),
+                Path.build_filename(install_path, "usr", "lib64", "pkgconfig"),
+                Path.build_filename(install_path, "usr", "share", "pkgconfig")
+            };
+            
+            foreach (var pc_dir in pc_dirs) {
+                var dir = File.new_for_path(pc_dir);
+                if (dir.query_exists()) {
+                    var enumerator = dir.enumerate_children("*.pc", FileQueryInfoFlags.NONE);
+                    FileInfo file_info;
+                    
+                    while ((file_info = enumerator.next_file()) != null) {
+                        if (file_info.get_name().has_suffix(".pc")) {
+                            var resource_ref = paths.get_suggested_resource_ref_for_path(file_info.get_name());
+                            if (resource_ref != null) {
+                                deps.add(resource_ref);
+                            }
+                        }
+                    }
+                }
+            }
+            
+            return deps;
+        }
+    }
+}

+ 250 - 0
src/lib/BuildAnalyzer.vala

@@ -0,0 +1,250 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Usm {
+
+    public class BuildAnalyzer {
+        private Paths paths;
+
+        public BuildAnalyzer(Paths paths) {
+            this.paths = paths;
+        }
+
+        public Set<ResourceRef> analyze_build_with_tracing(string source_path, string build_path, string[] build_command) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            
+            // Validate build directory
+            var build_dir = File.new_for_path(build_path);
+            if (!build_dir.query_exists()) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build directory '$build_path' does not exist");
+            }
+            
+            // Check if build directory is writable
+            var build_info = build_dir.query_info("access::can-write", FileQueryInfoFlags.NONE);
+            if (!build_info.get_attribute_boolean("access::can-write")) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build directory '$build_path' is not writable");
+            }
+            
+            // Create temporary trace file
+            var trace_file = Path.build_filename(build_path, ".usm_build_trace");
+            
+            // Validate build command
+            if (build_command.length == 0) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    "Build command is empty");
+            }
+            
+            var build_cmd_file = File.new_for_path(build_command[0]);
+            if (!build_cmd_file.query_exists()) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build command '$(build_command[0])' does not exist");
+            }
+            
+            var build_cmd_info = build_cmd_file.query_info("access::can-execute", FileQueryInfoFlags.NONE);
+            if (!build_cmd_info.get_attribute_boolean("access::can-execute")) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build command '$(build_command[0])' is not executable");
+            }
+            
+            // Build strace command
+            var strace_args = new string[0];
+            strace_args += "strace";
+            strace_args += "-f";  // Follow forks
+            strace_args += "-e";  // Trace specific system calls
+            strace_args += "trace=execve,open,openat,stat,stat64,lstat,lstat64,access,fstatat64,newfstatat";
+            strace_args += "-o";
+            strace_args += trace_file;
+            
+            // Add the build command
+            foreach (var arg in build_command) {
+                strace_args += arg;
+            }
+
+            // Run the build with strace
+            var proc = new Subprocess.newv(
+                strace_args,
+                SubprocessFlags.STDOUT_PIPE | SubprocessFlags.STDERR_MERGE
+            );
+            
+            // Capture output for debugging
+            var output_stream = proc.get_stdout_pipe();
+            var data_stream = new DataInputStream(output_stream);
+            string output = "";
+            string line;
+            while ((line = data_stream.read_line()) != null) {
+                output += line + "\n";
+            }
+            
+            // Wait for completion and handle errors gracefully
+            try {
+                proc.wait_check();
+            } catch (Error e) {
+                // Clean up trace file if it exists
+                if (File.new_for_path(trace_file).query_exists()) {
+                    File.new_for_path(trace_file).delete();
+                }
+                
+                // Provide detailed error information
+                var error_msg = @"Build tracing failed: $(e.message)\n";
+                error_msg += @"Command: $(string.joinv(" ", strace_args))\n";
+                if (output.length > 0) {
+                    error_msg += @"Output:\n$(output)\n";
+                } else {
+                    error_msg += "No output captured.\n";
+                }
+                error_msg += "Possible causes:\n";
+                error_msg += "- strace is not installed\n";
+                error_msg += "- Build command is invalid or not executable\n";
+                error_msg += "- Insufficient permissions to run strace\n";
+                error_msg += "- Build directory does not exist or is not writable\n";
+                
+                throw new Error(Quark.from_string("build-tracing-error"), 1, error_msg);
+            }
+            
+            // Parse the trace file
+            if (File.new_for_path(trace_file).query_exists()) {
+                printerr(@"Parsing trace file: $(trace_file)\n");
+                var trace_deps = parse_trace_file(trace_file);
+                printerr(@"Found $(trace_deps.count()) potential dependencies from trace\n");
+                foreach (var dep in trace_deps) {
+                    deps.add(dep);
+                    printerr(@"Detected build dependency: $(dep.to_string())\n");
+                }
+                
+                // Clean up trace file
+                File.new_for_path(trace_file).delete();
+            } else {
+                printerr(@"Trace file not found: $(trace_file)\n");
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> parse_trace_file(string trace_file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(trace_file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            uint line_count = 0;
+            
+            printerr("=== TRACE FILE CONTENTS ===\n");
+            while ((line = dis.read_line()) != null) {
+                line_count++;
+                printerr(@"Line $(line_count): $(line)\n");
+                var dep = parse_trace_line(line);
+                if (dep != null) {
+                    deps.add(dep);
+                    printerr(@"  -> DEPENDENCY DETECTED: $(dep.to_string())\n");
+                }
+            }
+            printerr("=== END TRACE FILE ===\n");
+            
+            return deps;
+        }
+
+        private ResourceRef? parse_trace_line(string trace_line) {
+            var line = trace_line;
+            // Skip empty lines and process IDs
+            var stripped_line = line.strip();
+            if (stripped_line == "" || stripped_line.has_prefix("[pid")) {
+                return null;
+            }
+            
+            line = stripped_line;
+            
+            // Remove PID prefix (e.g., "24048 ") from the beginning of the line
+            var space_index = line.index_of(" ");
+            if (space_index > 0) {
+                var new_line = line.substring(space_index + 1);
+                line = new_line;
+            }
+            
+            print(@"LINE:: $(line)\n");
+            // Parse execve system calls
+            if (line.has_prefix("execve(\"")) {
+                var path = extract_string_argument(line, "execve(\"");
+                if (path != null) {
+                    var basename = Path.get_basename(path);
+                    // Temporarily remove system tool filtering to see what's detected
+                    // if (!paths.is_system_tool(basename)) {
+                        return paths.get_suggested_resource_ref_for_path(path);
+                    // }
+                }
+            }
+            
+            // Parse open/openat system calls
+            if (line.has_prefix("open(") || line.has_prefix("openat(")) {
+                var path = extract_string_argument(line, "open(") ?? extract_string_argument(line, "openat(");
+
+                if (path != null /* && !is_system_path(path) && !is_temporary_file(path) */) {
+                    return paths.get_suggested_resource_ref_for_path(path);
+                }
+            }
+            
+            // Parse stat/lstat system calls
+            if (line.has_prefix("stat(") || line.has_prefix("stat64(") ||
+                line.has_prefix("lstat(") || line.has_prefix("lstat64(") ||
+                line.has_prefix("fstatat64(") || line.has_prefix("newfstatat(")) {
+                var path = extract_string_argument(line, "stat(") ?? extract_string_argument(line, "stat64(") ??
+                          extract_string_argument(line, "lstat(") ?? extract_string_argument(line, "lstat64(") ??
+                          extract_string_argument(line, "fstatat64(") ?? extract_string_argument(line, "newfstatat(");
+
+                if (path != null) {
+                    return paths.get_suggested_resource_ref_for_path(path);
+                }
+            }
+            
+            // Parse access system calls
+            if (line.has_prefix("access(")) {
+                var path = extract_string_argument(line, "access(");
+
+                if (path != null) {
+                    return paths.get_suggested_resource_ref_for_path(path);
+                }
+            }
+            
+            return null;
+        }
+
+        private string? extract_string_argument(string line, string syscall) {
+            // Find the start of the syscall
+            var start = line.index_of(syscall);
+            if (start == -1) {
+                return null;
+            }
+            
+            // Find the first quote after the syscall name
+            var quote_start = line.index_of("\"", start + syscall.length);
+            if (quote_start == -1) {
+                return null;
+            }
+            
+            // Find the closing quote
+            var quote_end = line.index_of("\"", quote_start + 1);
+            if (quote_end == -1) {
+                return null;
+            }
+            
+            var result = line.substring(quote_start + 1, quote_end - quote_start - 1);
+            printerr(@"extract_string_argument DEBUG: syscall='$(syscall)', line='$(line)', quote_start=$(quote_start), quote_end=$(quote_end), result='$(result)'\n");
+            return result;
+        }
+
+        private bool is_temporary_file(string path) {
+            var temp_patterns = new string[] {
+                "/tmp/", "/var/tmp/", ".tmp", ".temp", ".swp", ".swo"
+            };
+            
+            foreach (var pattern in temp_patterns) {
+                if (path.contains(pattern)) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+    }
+}

+ 180 - 0
src/lib/DependencyDetector.vala

@@ -0,0 +1,180 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Usm {
+
+    public class DependencyDetector {
+        private BuildAnalyzer build_analyzer;
+        private BinaryAnalyzer binary_analyzer;
+        private SourceAnalyzer source_analyzer;
+        private Paths paths;
+
+        public DependencyDetector(Paths paths) {
+            this.paths = paths;
+            this.build_analyzer = new BuildAnalyzer(paths);
+            this.binary_analyzer = new BinaryAnalyzer(paths);
+            this.source_analyzer = new SourceAnalyzer(paths);
+        }
+
+        public DetectedDependencies detect_dependencies(string source_path, string build_path, string install_path) throws Error {
+            var result = new DetectedDependencies();
+
+            // 1. Analyze build-time dependencies
+            printerr("Analyzing build-time dependencies...\n");
+            
+            // First, try to analyze with tracing if we have a build command
+            Set<ResourceRef> traced_deps = null;
+            if (has_build_command(source_path)) {
+                var build_command = get_build_command(source_path);
+                // Add build_path as an argument to the build script
+                var full_build_command = new string[build_command.length + 1];
+                for (int i = 0; i < build_command.length; i++) {
+                    full_build_command[i] = build_command[i];
+                }
+                full_build_command[build_command.length] = build_path;
+                
+                try {
+                    traced_deps = build_analyzer.analyze_build_with_tracing(source_path, build_path, full_build_command);
+                    printerr("Build tracing completed.\n");
+                } catch (Error e) {
+                    printerr(@"Build tracing failed: $(e.message)\n");
+                    printerr("Continuing with static analysis...\n");
+                    
+                    // Check if strace is available
+                    try {
+                        var strace_check = new Subprocess.newv(
+                            new string[] {"which", "strace"},
+                            SubprocessFlags.STDOUT_SILENCE | SubprocessFlags.STDERR_SILENCE
+                        );
+                        strace_check.wait_check();
+                    } catch (Error strace_error) {
+                        printerr("Warning: strace is not installed or not in PATH. Install strace for build tracing.\n");
+                    }
+                }
+            }
+            
+            // Add traced dependencies (these are more accurate)
+            if (traced_deps != null) {
+                foreach (var dep in traced_deps) {
+                    result.add_build_dependency(dep);
+                }
+            }
+
+            // 2. Analyze runtime dependencies from built binaries
+            printerr("Analyzing runtime dependencies...\n");
+            
+            // Analyze binaries with ldd
+            var binary_deps = binary_analyzer.analyze_binaries_with_ldd(install_path);
+            foreach (var dep in binary_deps) {
+                result.add_runtime_dependency(dep);
+            }
+            
+            // Analyze scripts for interpreter dependencies
+            var build_script_deps = binary_analyzer.analyze_scripts(install_path);
+            foreach (var dep in build_script_deps) {
+                print(@"Script: $(dep)\n");
+                result.add_runtime_dependency(dep);
+            }
+
+            // 3. Analyze provided resources (what the package provides)
+            printerr("Analyzing provided resources...\n");
+            var provides = analyze_provided_resources(install_path);
+            foreach (var entry in provides) {
+                result.add_provided_resource(entry.key, entry.value);
+            }
+
+            return result;
+        }
+
+        private Dictionary<ResourceRef, ManifestFile> analyze_provided_resources(string install_path) throws Error {
+            var provides = new Dictionary<ResourceRef, ManifestFile>();
+            
+            // Use the existing autoprovides_scan_tree function
+            var provided_resources = autoprovides_scan_tree(install_path, install_path);
+            
+            foreach (var resource_str in provided_resources) {
+                try {
+                    var resource_ref = new ResourceRef(resource_str);
+                    var manifest_file = new ManifestFile.from_string("as-expected");
+                    provides.set(resource_ref, manifest_file);
+                } catch (Error e) {
+                    warning(@"Could not parse resource reference '$resource_str': $(e.message)");
+                }
+            }
+            
+            return provides;
+        }
+
+        private bool has_build_command(string source_path) throws Error {
+            // Check if we can determine a build command from the manifest
+            var manifest_file = File.new_build_filename(source_path, "MANIFEST.usm");
+            if (!manifest_file.query_exists()) {
+                return false;
+            }
+            
+            var element = new InvercargillJson.JsonElement.from_file(manifest_file.get_path());
+            var manifest = Manifest.get_mapper().materialise(element.as<Invercargill.Properties>());
+            
+            return manifest.executables.build != null;
+        }
+
+        private string[] get_build_command(string source_path) throws Error {
+            var manifest_file = File.new_build_filename(source_path, "MANIFEST.usm");
+            var element = new InvercargillJson.JsonElement.from_file(manifest_file.get_path());
+            var manifest = Manifest.get_mapper().materialise(element.as<Invercargill.Properties>());
+            
+            if (manifest.executables.build == null) {
+                return new string[0];
+            }
+            
+            // Build command to execute the build script
+            // Note: We need to pass the build_path as an argument to the build script
+            // but we don't have access to it here. The build_path will be added
+            // by the caller when constructing the strace command.
+            var build_script_path = Path.build_filename(Environment.get_current_dir(), manifest.executables.build);
+            return new string[] { build_script_path };
+        }
+
+        // Import the existing autoprovides_scan_tree function
+        public static Invercargill.DataStructures.Vector<string> autoprovides_scan_tree(string install_root, string path) throws Error {
+            // Creating a new Directory object for the given folder path
+            var folder = File.new_for_path(path);
+
+            // Getting a list of all files and folders inside the directory
+            var enumerator = folder.enumerate_children("*", FileQueryInfoFlags.NONE);
+
+            var provides = new Invercargill.DataStructures.Vector<string>();
+
+            // Looping through each file/folder and removing them
+            while (true) {
+                FileInfo? info = enumerator.next_file();
+                if (info == null) {
+                    break;
+                }
+
+                // Checking if the current item is a file or a folder
+                if (info.get_file_type() == FileType.REGULAR) {
+                    // Removing the file
+                    var file = folder.get_child(info.get_name());
+                    var true_path = file.get_path().substring(install_root.length);
+                    var paths = new Paths.defaults();
+                    var type = paths.get_suggested_resource_type(true_path);
+                    if(type == Usm.ResourceType.ROOT_PATH) {
+                        provides.add("rootpath:" + true_path);
+                    }
+                    else {
+                        provides.add(@"$type:$(file.get_basename())");
+                    }
+
+                }
+                else if (info.get_file_type() == FileType.DIRECTORY) {
+                    // Removing the folder recursively
+                    var subfolder = folder.get_child(info.get_name());
+                    provides.add_all(autoprovides_scan_tree(install_root, subfolder.get_path()));
+                }
+            }
+            
+            return provides;
+        }
+    }
+}

+ 141 - 0
src/lib/DetectedDependencies.vala

@@ -0,0 +1,141 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Usm {
+
+    public class DetectedDependencies {
+        public Set<ResourceRef> build_deps { get; set; }
+        public Set<ResourceRef> runtime_deps { get; set; }
+        public Set<ResourceRef> manage_deps { get; set; }
+        public Dictionary<ResourceRef, ManifestFile> provides { get; set; }
+
+        public DetectedDependencies() {
+            build_deps = new HashSet<ResourceRef>();
+            runtime_deps = new HashSet<ResourceRef>();
+            manage_deps = new HashSet<ResourceRef>();
+            provides = new Dictionary<ResourceRef, ManifestFile>();
+        }
+
+        public void add_build_dependency(ResourceRef dep) {
+            build_deps.add(dep);
+        }
+
+        public void add_runtime_dependency(ResourceRef dep) {
+            runtime_deps.add(dep);
+        }
+
+        public void add_manage_dependency(ResourceRef dep) {
+            manage_deps.add(dep);
+        }
+
+        public void add_provided_resource(ResourceRef resource, ManifestFile file) {
+            provides.set(resource, file);
+        }
+
+        public bool has_build_dependency(ResourceRef dep) {
+            return build_deps.contains(dep);
+        }
+
+        public bool has_runtime_dependency(ResourceRef dep) {
+            return runtime_deps.contains(dep);
+        }
+
+        public bool has_manage_dependency(ResourceRef dep) {
+            return manage_deps.contains(dep);
+        }
+
+        public bool provides_resource(ResourceRef resource) {
+            return contains_key(provides, resource);
+        }
+        
+        private bool contains_key(Dictionary<ResourceRef, ManifestFile> dict, ResourceRef key) {
+            foreach (var entry in dict) {
+                if (entry.key.equals(key)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public string to_json() {
+            var json = new StringBuilder();
+            json.append("{\n");
+            
+            // Add provides section
+            json.append("  \"provides\": {\n");
+            var provides_count = 0;
+            foreach (var entry in provides) {
+                provides_count++;
+                json.append(@"    \"$(entry.key.to_string())\": \"$(entry.value.path_base.to_string())\"");
+                if (provides_count < count_entries(provides)) {
+                    json.append(",");
+                }
+                json.append("\n");
+            }
+            json.append("  },\n");
+
+            // Add dependencies section
+            json.append("  \"dependencies\": {\n");
+            
+            // Build dependencies
+            json.append("    \"build\": [\n");
+            var build_count = 0;
+            foreach (var dep in build_deps) {
+                build_count++;
+                json.append(@"      \"$(dep.to_string())\"");
+                if (build_count < count_items(build_deps)) {
+                    json.append(",");
+                }
+                json.append("\n");
+            }
+            json.append("    ],\n");
+
+            // Runtime dependencies
+            json.append("    \"runtime\": [\n");
+            var runtime_count = 0;
+            foreach (var dep in runtime_deps) {
+                runtime_count++;
+                json.append(@"      \"$(dep.to_string())\"");
+                if (runtime_count < count_items(runtime_deps)) {
+                    json.append(",");
+                }
+                json.append("\n");
+            }
+            json.append("    ],\n");
+
+            // Manage dependencies
+            json.append("    \"manage\": [\n");
+            var manage_count = 0;
+            foreach (var dep in manage_deps) {
+                manage_count++;
+                json.append(@"      \"$(dep.to_string())\"");
+                if (manage_count < count_items(manage_deps)) {
+                    json.append(",");
+                }
+                json.append("\n");
+            }
+            json.append("    ]\n");
+
+            json.append("  }\n");
+            json.append("}");
+            
+            return json.str;
+        }
+        
+        private uint count_entries(Dictionary<ResourceRef, ManifestFile> dict) {
+            uint count = 0;
+            foreach (var entry in dict) {
+                count++;
+            }
+            return count;
+        }
+        
+        private uint count_items(Set<ResourceRef> set) {
+            uint count = 0;
+            foreach (var item in set) {
+                count++;
+            }
+            return count;
+        }
+    }
+}

+ 48 - 0
src/lib/Paths.vala

@@ -89,6 +89,8 @@ namespace Usm {
         public ResourceType get_suggested_resource_type(string path) {
             var name = Path.get_basename(path);
             var types = ResourceType.get_types()
+                .debug_trace("sp", t => get_suggested_path(new ResourceRef.with_type(t, name)))
+                .debug_trace("p", t => path)
                 .where(t => get_suggested_path(new ResourceRef.with_type(t, name)) == path)
                 .to_vector();
 
@@ -172,6 +174,52 @@ namespace Usm {
                 usm_config_dir = usm_config_dir,
             };
         }
+
+        public ResourceRef? get_suggested_resource_ref_for_path(string path) {
+            if(!path.has_prefix("/")) {
+                warning("Provided path does not start with '/', returning null");
+                return null;
+            }
+            var basename = Path.get_basename(path);
+            var resource_type = get_suggested_resource_type(path);
+            if(resource_type == ResourceType.ROOT_PATH) {
+                return new ResourceRef.with_type(resource_type, path);
+            }
+            else {
+                return new ResourceRef.with_type(resource_type, basename);
+            }
+        }
+
+        public ResourceRef? get_suggested_resource_ref_for_ldd_line(string ldd_line) {
+            // Parse ldd output format: libname.so => /path/to/libname.so (0xaddress)
+            var parts = ldd_line.split("=>");
+            if (parts.length < 2) {
+                return null;
+            }
+            
+            var lib_path = parts[1].strip();
+            var paren_index = lib_path.index_of("(");
+            if (paren_index > 0) {
+                lib_path = lib_path.substring(0, paren_index).strip();
+            }
+            
+            return get_suggested_resource_ref_for_path(lib_path);
+        }
+
+        public ResourceRef? get_suggested_resource_ref_for_shebang(string shebang_line) {
+            // Parse shebang line: #!/path/to/interpreter [args]
+            if (!shebang_line.has_prefix("#!")) {
+                return null;
+            }
+            
+            var parts = shebang_line.substring(2).strip().split(" ");
+            if (parts.length == 0) {
+                return null;
+            }
+            
+            var interpreter_path = parts[0];
+            return get_suggested_resource_ref_for_path(interpreter_path);
+        }
     }
 
 }

+ 241 - 0
src/lib/ProcessAnalyzer.vala

@@ -0,0 +1,241 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Usm {
+
+    public class BuildAnalyzer {
+        private DependencyMapper mapper;
+
+        public BuildAnalyzer(DependencyMapper mapper) {
+            this.mapper = mapper;
+        }
+
+        public Set<ResourceRef> analyze_process(string source_path, string build_path, string[] build_command) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            
+            // Validate build directory
+            var build_dir = File.new_for_path(build_path);
+            if (!build_dir.query_exists()) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build directory '$build_path' does not exist");
+            }
+            
+            // Check if build directory is writable
+            var build_info = build_dir.query_info("access::can-write", FileQueryInfoFlags.NONE);
+            if (!build_info.get_attribute_boolean("access::can-write")) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build directory '$build_path' is not writable");
+            }
+            
+            // Create temporary trace file
+            var trace_file = Path.build_filename(build_path, ".usm_build_trace");
+            
+            // Validate build command
+            if (build_command.length == 0) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    "Build command is empty");
+            }
+            
+            var build_cmd_file = File.new_for_path(build_command[0]);
+            if (!build_cmd_file.query_exists()) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build command '$(build_command[0])' does not exist");
+            }
+            
+            var build_cmd_info = build_cmd_file.query_info("access::can-execute", FileQueryInfoFlags.NONE);
+            if (!build_cmd_info.get_attribute_boolean("access::can-execute")) {
+                throw new Error(Quark.from_string("build-tracing-error"), 1,
+                    @"Build command '$(build_command[0])' is not executable");
+            }
+            
+            // Build strace command
+            var strace_args = new string[0];
+            strace_args += "strace";
+            strace_args += "-f";  // Follow forks
+            strace_args += "-e";  // Trace specific system calls
+            strace_args += "trace=execve,open,openat,stat,stat64,lstat,lstat64,access,fstatat64,newfstatat";
+            strace_args += "-o";
+            strace_args += trace_file;
+            
+            // Add the build command
+            foreach (var arg in build_command) {
+                strace_args += arg;
+            }
+
+            // Run the build with strace
+            var proc = new Subprocess.newv(
+                strace_args,
+                SubprocessFlags.STDOUT_PIPE | SubprocessFlags.STDERR_MERGE
+            );
+            
+            // Capture output for debugging
+            var output_stream = proc.get_stdout_pipe();
+            var data_stream = new DataInputStream(output_stream);
+            string output = "";
+            string line;
+            while ((line = data_stream.read_line()) != null) {
+                output += line + "\n";
+            }
+            
+            // Wait for completion and handle errors gracefully
+            try {
+                proc.wait_check();
+            } catch (Error e) {
+                // Clean up trace file if it exists
+                if (File.new_for_path(trace_file).query_exists()) {
+                    File.new_for_path(trace_file).delete();
+                }
+                
+                // Provide detailed error information
+                var error_msg = @"Build tracing failed: $(e.message)\n";
+                error_msg += @"Command: $(string.joinv(" ", strace_args))\n";
+                if (output.length > 0) {
+                    error_msg += @"Output:\n$(output)\n";
+                } else {
+                    error_msg += "No output captured.\n";
+                }
+                error_msg += "Possible causes:\n";
+                error_msg += "- strace is not installed\n";
+                error_msg += "- Build command is invalid or not executable\n";
+                error_msg += "- Insufficient permissions to run strace\n";
+                error_msg += "- Build directory does not exist or is not writable\n";
+                
+                throw new Error(Quark.from_string("build-tracing-error"), 1, error_msg);
+            }
+            
+            // Parse the trace file
+            if (File.new_for_path(trace_file).query_exists()) {
+                var trace_deps = parse_trace_file(trace_file);
+                foreach (var dep in trace_deps) {
+                    deps.add(dep);
+                }
+                
+                // Clean up trace file
+                File.new_for_path(trace_file).delete();
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> parse_trace_file(string trace_file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(trace_file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            
+            while ((line = dis.read_line()) != null) {
+                var dep = parse_trace_line(line);
+                if (dep != null) {
+                    deps.add(dep);
+                }
+            }
+            
+            return deps;
+        }
+
+        private ResourceRef? parse_trace_line(string line) {
+            // Skip empty lines and process IDs
+            var stripped_line = line.strip();
+            if (stripped_line == "" || stripped_line.has_prefix("[pid")) {
+                return null;
+            }
+            
+            line = stripped_line;
+            
+            // Parse execve system calls
+            if (line.has_prefix("execve(")) {
+                var path = extract_string_argument(line, "execve(");
+                if (path != null) {
+                    var basename = Path.get_basename(path);
+                    if (!mapper.is_system_tool(basename)) {
+                        return mapper.map_path_to_resource_ref(path);
+                    }
+                }
+            }
+            
+            // Parse open/openat system calls
+            if (line.has_prefix("open(") || line.has_prefix("openat(")) {
+                var path = extract_string_argument(line, "open(") ?? extract_string_argument(line, "openat(");
+                if (path != null && !is_system_path(path) && !is_temporary_file(path)) {
+                    return mapper.map_path_to_resource_ref(path);
+                }
+            }
+            
+            // Parse stat/lstat system calls
+            if (line.has_prefix("stat(") || line.has_prefix("stat64(") ||
+                line.has_prefix("lstat(") || line.has_prefix("lstat64(") ||
+                line.has_prefix("fstatat64(") || line.has_prefix("newfstatat(")) {
+                var path = extract_string_argument(line, "stat(") ?? extract_string_argument(line, "stat64(") ??
+                          extract_string_argument(line, "lstat(") ?? extract_string_argument(line, "lstat64(") ??
+                          extract_string_argument(line, "fstatat64(") ?? extract_string_argument(line, "newfstatat(");
+                if (path != null && !is_system_path(path)) {
+                    return mapper.map_path_to_resource_ref(path);
+                }
+            }
+            
+            // Parse access system calls
+            if (line.has_prefix("access(")) {
+                var path = extract_string_argument(line, "access(");
+                if (path != null && !is_system_path(path)) {
+                    return mapper.map_path_to_resource_ref(path);
+                }
+            }
+            
+            return null;
+        }
+
+        private string? extract_string_argument(string line, string syscall) {
+            // Find the start of the syscall
+            var start = line.index_of(syscall);
+            if (start == -1) {
+                return null;
+            }
+            
+            // Find the first quote after the syscall name
+            var quote_start = line.index_of("\"", start + syscall.length);
+            if (quote_start == -1) {
+                return null;
+            }
+            
+            // Find the closing quote
+            var quote_end = line.index_of("\"", quote_start + 1);
+            if (quote_end == -1) {
+                return null;
+            }
+            
+            return line.substring(quote_start + 1, quote_end - quote_start - 1);
+        }
+
+        private bool is_system_path(string path) {
+            var system_prefixes = new string[] {
+                "/proc/", "/sys/", "/dev/", "/tmp/", "/var/tmp/",
+                "/usr/include/", "/usr/share/", "/etc/",
+                "/lib/", "/lib64/", "/usr/lib/", "/usr/lib64/",
+                "/bin/", "/sbin/", "/usr/bin/", "/usr/sbin/"
+            };
+            
+            foreach (var prefix in system_prefixes) {
+                if (path.has_prefix(prefix)) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+
+        private bool is_temporary_file(string path) {
+            var temp_patterns = new string[] {
+                "/tmp/", "/var/tmp/", ".tmp", ".temp", ".swp", ".swo"
+            };
+            
+            foreach (var pattern in temp_patterns) {
+                if (path.contains(pattern)) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+    }
+}

+ 396 - 0
src/lib/SourceAnalyzer.vala

@@ -0,0 +1,396 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Usm {
+
+    public class SourceAnalyzer {
+        private Paths paths;
+
+        public SourceAnalyzer(Paths paths) {
+            this.paths = paths;
+        }
+
+        public Set<ResourceRef> analyze_source_files(string source_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+
+            // Find all source files
+            var source_files = find_source_files_recursive(source_path);
+            
+            foreach (var source_file in source_files) {
+                var file_deps = analyze_single_source_file(source_file);
+                foreach (var dep in file_deps) {
+                    deps.add(dep);
+                }
+            }
+
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_single_source_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var basename = Path.get_basename(file_path);
+            
+            if (basename.has_suffix(".c") || basename.has_suffix(".h")) {
+                foreach (var dep in analyze_c_file(file_path)) {
+                    deps.add(dep);
+                }
+            } else if (basename.has_suffix(".cpp") || basename.has_suffix(".hpp") || basename.has_suffix(".cc") || basename.has_suffix(".cxx")) {
+                foreach (var dep in analyze_cpp_file(file_path)) {
+                    deps.add(dep);
+                }
+            } else if (basename.has_suffix(".vala")) {
+                foreach (var dep in analyze_vala_file(file_path)) {
+                    deps.add(dep);
+                }
+            } else if (basename.has_suffix(".py")) {
+                foreach (var dep in analyze_python_file(file_path)) {
+                    deps.add(dep);
+                }
+            } else if (basename.has_suffix(".pl")) {
+                foreach (var dep in analyze_perl_file(file_path)) {
+                    deps.add(dep);
+                }
+            } else if (basename.has_suffix(".rb")) {
+                foreach (var dep in analyze_ruby_file(file_path)) {
+                    deps.add(dep);
+                }
+            } else if (basename.has_suffix(".php")) {
+                foreach (var dep in analyze_php_file(file_path)) {
+                    deps.add(dep);
+                }
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_c_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            
+            while ((line = dis.read_line()) != null) {
+                // Look for #include directives
+                if (line.strip().has_prefix("#include")) {
+                    var include_file = extract_include_file(line);
+                    if (include_file != null) {
+                        var resource_type = paths.get_suggested_resource_type(include_file);
+                        var resource_ref = new ResourceRef.with_type(resource_type, include_file);
+                        deps.add(resource_ref);
+                    }
+                }
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_cpp_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            
+            // C++ files use the same include syntax as C
+            var c_deps = analyze_c_file(file_path);
+            foreach (var dep in c_deps) {
+                deps.add(dep);
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_vala_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            
+            while ((line = dis.read_line()) != null) {
+                line = line.strip();
+                
+                // Look for using directives
+                if (line.has_prefix("using")) {
+                    var parts = line.split(" ");
+                    if (parts.length >= 2) {
+                        var namespace_name = parts[1].replace(";", "");
+                        // Vala using directives don't typically map to system dependencies
+                    }
+                }
+                
+                // Look for package imports (like Gtk, GObject, etc.)
+                if (line.contains("@") && (line.contains("Gtk") || line.contains("Gee") || line.contains("Json"))) {
+                    // These might indicate vapi dependencies
+                    if (line.contains("Gtk")) {
+                        var resource_ref = paths.get_suggested_resource_ref_for_path("gtk-4.vapi");
+                        if (resource_ref != null) {
+                            deps.add(resource_ref);
+                        }
+                    }
+                    if (line.contains("Gee")) {
+                        var resource_ref = paths.get_suggested_resource_ref_for_path("gee-0.8.vapi");
+                        if (resource_ref != null) {
+                            deps.add(resource_ref);
+                        }
+                    }
+                    if (line.contains("Json")) {
+                        var resource_ref = paths.get_suggested_resource_ref_for_path("json-glib-1.0.vapi");
+                        if (resource_ref != null) {
+                            deps.add(resource_ref);
+                        }
+                    }
+                }
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_python_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            
+            while ((line = dis.read_line()) != null) {
+                line = line.strip();
+                
+                // Look for import statements
+                if (line.has_prefix("import ")) {
+                    var module_name = extract_python_import(line);
+                    if (module_name != null) {
+                        // Python imports are complex to map to system dependencies
+                        // For now, we'll just note common ones
+                        if (module_name == "gi" || module_name == "gobject") {
+                            var resource_ref = paths.get_suggested_resource_ref_for_path("gobject-introspection-1.0.pc");
+                            if (resource_ref != null) {
+                                deps.add(resource_ref);
+                            }
+                        }
+                    }
+                }
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_perl_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            
+            while ((line = dis.read_line()) != null) {
+                line = line.strip();
+                
+                // Look for use statements
+                if (line.has_prefix("use ")) {
+                    var module_name = extract_perl_use(line);
+                    if (module_name != null) {
+                        // Perl modules are complex to map to system dependencies
+                    }
+                }
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_ruby_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            
+            while ((line = dis.read_line()) != null) {
+                line = line.strip();
+                
+                // Look for require statements
+                if (line.has_prefix("require ")) {
+                    var module_name = extract_ruby_require(line);
+                    if (module_name != null) {
+                        // Ruby gems are complex to map to system dependencies
+                    }
+                }
+            }
+            
+            return deps;
+        }
+
+        private Set<ResourceRef> analyze_php_file(string file_path) throws Error {
+            var deps = new HashSet<ResourceRef>();
+            var file = File.new_for_path(file_path);
+            
+            var dis = new DataInputStream(file.read());
+            string line;
+            
+            while ((line = dis.read_line()) != null) {
+                line = line.strip();
+                
+                // Look for require/include statements
+                if (line.has_prefix("require ") || line.has_prefix("include ")) {
+                    var module_name = extract_php_require(line);
+                    if (module_name != null) {
+                        // PHP modules are complex to map to system dependencies
+                    }
+                }
+            }
+            
+            return deps;
+        }
+
+        private string? extract_include_file(string line) {
+            // Extract filename from #include directives
+            // Examples: #include <stdio.h>, #include "myheader.h"
+            
+            if (line.contains("<") && line.contains(">")) {
+                var start = line.index_of("<") + 1;
+                var end = line.index_of(">");
+                if (start > 0 && end > start) {
+                    return line.substring(start, end - start);
+                }
+            } else if (line.contains("\"") && line.index_of("\"") != line.last_index_of("\"")) {
+                var start = line.index_of("\"") + 1;
+                var end = line.last_index_of("\"");
+                if (start > 0 && end > start) {
+                    return line.substring(start, end - start);
+                }
+            }
+            
+            return null;
+        }
+
+        private string? extract_python_import(string line) {
+            // Extract module name from import statements
+            // Examples: import gtk, from gi.repository import Gtk
+            
+            if (line.has_prefix("import ")) {
+                var parts = line.split(" ");
+                if (parts.length >= 2) {
+                    return parts[1].split(",")[0].strip();
+                }
+            } else if (line.has_prefix("from ")) {
+                var parts = line.split(" ");
+                if (parts.length >= 4) {
+                    return parts[1].strip();
+                }
+            }
+            
+            return null;
+        }
+
+        private string? extract_perl_use(string line) {
+            // Extract module name from use statements
+            // Examples: use strict; use warnings; use Gtk3;
+            
+            var parts = line.split(" ");
+            if (parts.length >= 2) {
+                return parts[1].replace(";", "").strip();
+            }
+            
+            return null;
+        }
+
+        private string? extract_ruby_require(string line) {
+            // Extract module name from require statements
+            // Examples: require 'gtk3', require "json"
+            
+            var start = line.index_of("'") + 1;
+            if (start == 0) {
+                start = line.index_of("\"") + 1;
+            }
+            
+            if (start > 0) {
+                var end = line.index_of_char(line[start - 1], start);
+                if (end > start) {
+                    return line.substring(start, end - start);
+                }
+            }
+            
+            return null;
+        }
+
+        private string? extract_php_require(string line) {
+            // Extract module name from require/include statements
+            // Examples: require 'header.php', include "config.php"
+            
+            var start = line.index_of("'") + 1;
+            if (start == 0) {
+                start = line.index_of("\"") + 1;
+            }
+            
+            if (start > 0) {
+                var end = line.index_of_char(line[start - 1], start);
+                if (end > start) {
+                    return line.substring(start, end - start);
+                }
+            }
+            
+            return null;
+        }
+
+        private string[] find_source_files_recursive(string root_path) throws Error {
+            var files = new string[0];
+            var root_dir = File.new_for_path(root_path);
+            
+            var enumerator = root_dir.enumerate_children("*", FileQueryInfoFlags.NONE);
+            FileInfo file_info;
+            
+            while ((file_info = enumerator.next_file()) != null) {
+                var child = root_dir.get_child(file_info.get_name());
+                var child_path = child.get_path();
+                
+                if (file_info.get_file_type() == FileType.DIRECTORY) {
+                    // Skip common build directories
+                    if (file_info.get_name() != "build" && 
+                        file_info.get_name() != ".git" && 
+                        file_info.get_name() != "__pycache__") {
+                    var child_files = find_source_files_recursive(child_path);
+                    foreach (var file in child_files) {
+                        files += file;
+                    }
+                    }
+                } else {
+                    var basename = file_info.get_name();
+                    var extensions = new string[] {
+                        ".c", ".h", ".cpp", ".hpp", ".cc", ".cxx",
+                        ".vala", ".py", ".pl", ".rb", ".php"
+                    };
+                    
+                    foreach (var ext in extensions) {
+                        if (basename.has_suffix(ext)) {
+                            files += child_path;
+                            break;
+                        }
+                    }
+                }
+            }
+            
+            return files;
+        }
+        
+        private ResourceRef? map_vala_namespace_to_package(string namespace_name) {
+            // Map common Vala namespaces to their corresponding packages
+            var package_mappings = new Dictionary<string, string>();
+            
+            package_mappings["Gtk"] = "gtk-4.vapi";
+            package_mappings["Gdk"] = "gtk-4.vapi";
+            package_mappings["GObject"] = "gobject-2.0.vapi";
+            package_mappings["GLib"] = "glib-2.0.vapi";
+            package_mappings["Gio"] = "gio-2.0.vapi";
+            package_mappings["Gee"] = "gee-0.8.vapi";
+            package_mappings["Json"] = "json-glib-1.0.vapi";
+            package_mappings["Posix"] = null; // System library, skip
+            
+            foreach (var entry in package_mappings) {
+                if (entry.key == namespace_name) {
+                    var package_file = entry.value;
+                    if (package_file != null) {
+                        return paths.get_suggested_resource_ref_for_path(package_file);
+                    }
+                }
+            }
+            
+            return null;
+        }
+    }
+}

+ 5 - 0
src/lib/meson.build

@@ -23,6 +23,11 @@ sources += files('State/State.vala')
 sources += files('State/CachedPackage.vala')
 sources += files('State/OriginInformation.vala')
 sources += files('Tag/Tag.vala')
+sources += files('DetectedDependencies.vala')
+sources += files('BuildAnalyzer.vala')
+sources += files('BinaryAnalyzer.vala')
+sources += files('SourceAnalyzer.vala')
+sources += files('DependencyDetector.vala')
 
 
 dependencies = [