Explorar o código

feat(cli): add validate command to manifest

Add a new validate verb to the manifest CLI that validates manifest
dependencies, runs the build process, performs installation, and verifies
that all expected resources are present in their expected locations.

The validate command also warns about:
- Missing license information
- Unexpected resources in the install directory
- Missing install scripts

This provides comprehensive manifest validation to catch issues before
deployment.
clanker hai 1 mes
pai
achega
588b1e8fd4
Modificáronse 1 ficheiros con 217 adicións e 4 borrados
  1. 217 4
      src/cli/Manifest.vala

+ 217 - 4
src/cli/Manifest.vala

@@ -31,7 +31,7 @@ public static int manifest_main(string[] args) {
                 build_path = args[i];
             }
         }
-    } else if(verb != "remove" && verb != "acquire" && verb != "install" && verb != "package" && verb != "test") {
+    } else if(verb != "remove" && verb != "acquire" && verb != "install" && verb != "package" && verb != "test" && verb != "validate") {
         if(args.length < 3) {
             manifest_usage();
             return 255;
@@ -54,7 +54,7 @@ public static int manifest_main(string[] args) {
     }
 
     // Automatically create a temporary directory for these commands if one was not provided.
-    if(verb == "install" || verb == "autoprovides" || verb == "test") {
+    if(verb == "install" || verb == "autoprovides" || verb == "test" || verb == "validate") {
         if(build_path == null) {
             var dir = File.new_build_filename("/tmp", Uuid.string_random());
             try {
@@ -69,7 +69,7 @@ public static int manifest_main(string[] args) {
     }
 
     // Automatically create the specified directory for these commands if it doesn't exist
-    if(verb == "install" || verb == "autoprovides" || verb == "test" || verb == "build") {
+    if(verb == "install" || verb == "autoprovides" || verb == "test" || verb == "build" || verb == "validate") {
         var build_dir = File.new_for_path(build_path);
         if(!build_dir.query_exists()) {
             printerr(@"Creating build directory: $build_path\n");
@@ -111,12 +111,16 @@ public static int manifest_main(string[] args) {
         return test();
     }
 
+    if(verb == "validate") {
+        return validate();
+    }
+
     manifest_usage();
     return 255;
 }
 
 private void manifest_usage() {
-    printerr("USAGE:\n\tusm manifest build <build path>\n\tusm manifest install <build path>\n\tusm manifest remove\nusm manifest acquire\nusm manifest autoprovides [--replace] [--debug] [build path]\nusm manifest test [build path]\n");
+    printerr("USAGE:\n\tusm manifest build <build path>\n\tusm manifest install <build path>\n\tusm manifest remove\nusm manifest acquire\nusm manifest autoprovides [--replace] [--debug] [build path]\nusm manifest test [build path]\nusm manifest validate [build path]\n");
 }
 
 
@@ -786,5 +790,214 @@ private int test() {
         return 251;
     }
 
+    return 0;
+}
+
+private int validate() {
+    if(build_path == null) {
+        var dir = File.new_build_filename("/tmp", Uuid.string_random());
+        try {
+            dir.make_directory();
+        }
+        catch(Error e) {
+            printerr(@"Could not create temporary build directory, try specifying one instead: $(e.message)\n");
+            return 255;
+        }
+        build_path = dir.get_path();
+    } else {
+        // Create build directory if it doesn't exist
+        var build_dir = File.new_for_path(build_path);
+        if(!build_dir.query_exists()) {
+            printerr(@"Creating build directory: $build_path\n");
+            try {
+                build_dir.make_directory_with_parents();
+            }
+            catch(Error e) {
+                printerr(@"Could not create build directory: $(e.message)\n");
+                return 255;
+            }
+        }
+    }
+
+    var missing_management_deps = manifest.dependencies.manage.where(d => !resfinder.has_resource(d));
+    var missing_build_deps = manifest.dependencies.build.where(d => !resfinder.has_resource(d));
+    var missing_runtime_deps = manifest.dependencies.runtime.where(d => !destresfinder.has_resource(d));
+
+    var sane = true;
+    if(missing_management_deps.any()) {
+        sane = false;
+        foreach (var item in missing_management_deps) {
+            printerr(@"Missing management dependency \"$item\".\n");
+        }
+    }
+    if(missing_build_deps.any()) {
+        sane = false;
+        foreach (var item in missing_build_deps) {
+            printerr(@"Missing build dependency \"$item\".\n");
+        }
+    }
+    if(missing_runtime_deps.any()) {
+        sane = false;
+        foreach (var item in missing_runtime_deps) {
+            printerr(@"Missing runtime dependency \"$item\".\n");
+        }
+    }
+
+    if(!sane) {
+        printerr("Could not validate manifest, missing dependencies\n");
+        return 252;
+    }
+
+    // Check for license
+    if(manifest.licences == null || manifest.licences.count() == 0) {
+        printerr("Warning: No licence has been provided in the manifest.\n");
+    }
+
+    try {
+        // Run build
+        var build_proc = manifest.run_build(build_path, paths, SubprocessFlags.STDOUT_SILENCE, frac => printerr(@"Building '$(manifest.name)': $((int)(frac*100))%\r"));
+        build_proc.wait_check();
+        printerr("\n");
+    }
+    catch(Error e) {
+        printerr(@"\nError running build exec: $(e.message)\n");
+        return 251;
+    }
+
+    var install_dir = File.new_build_filename("/tmp", Uuid.string_random());
+    if(manifest.executables.install != null) {
+        try {
+            install_dir.make_directory();
+        }
+        catch(Error e) {
+            printerr(@"Could not create temporary install directory: $(e.message)\n");
+            return 250;
+        }
+
+        try {
+            manifest.run_install(build_path, install_dir.get_path(), paths, Usm.InstallType.FRESH, SubprocessFlags.STDOUT_SILENCE).wait_check();
+        }
+        catch(Error e) {
+            printerr(@"Error running install exec: $(e.message)\n");
+            return 249;
+        }
+    }
+    else {
+        printerr("Warning: No install script specified, cannot validate installation.\n");
+        return 0;
+    }
+
+    // Validate each expected resource exists in its expected location
+    var missing_resources = new Invercargill.DataStructures.Vector<Usm.ResourceRef>();
+    foreach (var expected in manifest.provides) {
+        bool found = false;
+        string expected_path = "";
+        
+        try {
+            switch(expected.value.path_base) {
+                case Usm.ManifestFilePathBase.AS_EXPECTED:
+                    // For as-expected, check in install directory at suggested path
+                    var install_paths = paths.clone();
+                    install_paths.destination = install_dir.get_path();
+                    expected_path = install_paths.get_suggested_path_for_resource(expected.key);
+                    var file = File.new_for_path(expected_path);
+                    found = file.query_exists();
+                    break;
+                    
+                case Usm.ManifestFilePathBase.BUILD:
+                    // For build:, check in build directory at specified path
+                    expected_path = Path.build_filename(build_path, expected.value.path ?? "");
+                    var file = File.new_for_path(expected_path);
+                    found = file.query_exists();
+                    break;
+                    
+                case Usm.ManifestFilePathBase.SOURCE:
+                    // For source:, check in source directory at specified path
+                    expected_path = Path.build_filename(Environment.get_current_dir(), expected.value.path ?? "");
+                    var file = File.new_for_path(expected_path);
+                    found = file.query_exists();
+                    break;
+                    
+                case Usm.ManifestFilePathBase.INSTALL:
+                    // For install:, check in install directory at specified path
+                    expected_path = Path.build_filename(install_dir.get_path(), expected.value.path ?? "");
+                    var file = File.new_for_path(expected_path);
+                    found = file.query_exists();
+                    break;
+                    
+                default:
+                    assert_not_reached();
+            }
+        }
+        catch(Error e) {
+            printerr(@"Error validating resource $(expected.key.to_string()): $(e.message)\n");
+            found = false;
+        }
+        
+        if(!found) {
+            missing_resources.add(expected.key);
+        }
+    }
+    
+    if(missing_resources.any()) {
+        printerr("Error: Expected resources not found:\n");
+        foreach (var resource in missing_resources) {
+            var expected_resource = manifest.provides[resource];
+            string location_desc = "";
+            switch(expected_resource.path_base) {
+                case Usm.ManifestFilePathBase.AS_EXPECTED:
+                    location_desc = "at expected install location";
+                    break;
+                case Usm.ManifestFilePathBase.BUILD:
+                    location_desc = @"in build directory at \"$(expected_resource.path ?? "")\"";
+                    break;
+                case Usm.ManifestFilePathBase.SOURCE:
+                    location_desc = @"in source directory at \"$(expected_resource.path ?? "")\"";
+                    break;
+                case Usm.ManifestFilePathBase.INSTALL:
+                    location_desc = @"in install directory at \"$(expected_resource.path ?? "")\"";
+                    break;
+                default:
+                    location_desc = "at unknown location";
+                    break;
+            }
+            printerr(@"  - $(resource.to_string()) ($location_desc)\n");
+        }
+        return 248;
+    }
+    
+    // Scan for unexpected resources in the install directory
+    try {
+        var installed_resources = autoprovides_scan_tree(install_dir.get_path(), install_dir.get_path());
+        var expected_resources = manifest.provides;
+        
+        // Check for unexpected resources
+        var unexpected_resources = new Invercargill.DataStructures.Vector<Usm.ResourceRef>();
+        foreach (var installed in installed_resources) {
+            bool found = false;
+            foreach (var expected in expected_resources) {
+                if(installed.key.to_string() == expected.key.to_string()) {
+                    found = true;
+                    break;
+                }
+            }
+            if(!found) {
+                unexpected_resources.add(installed.key);
+            }
+        }
+        
+        if(unexpected_resources.any()) {
+            printerr("Warning: Unexpected resources found in install directory:\n");
+            foreach (var resource in unexpected_resources) {
+                printerr(@"  - $(resource.to_string())\n");
+            }
+        }
+    }
+    catch(Error e) {
+        printerr(@"Error validating installation result: $(e.message)\n");
+        return 247;
+    }
+
+    printerr("Manifest validation completed successfully.\n");
     return 0;
 }