|
|
@@ -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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|