Usm.Manifest manifest = null; string? build_path = null; bool replace_provides = false; bool debug_mode = false; Usm.ResourceFinder resfinder = null; Usm.ResourceFinder destresfinder = null; public static int manifest_main(string[] args) { paths = new Usm.Paths(); var resfinder_paths = paths.clone(); resfinder_paths.destination = "/"; resfinder = new Usm.ResourceFinder(resfinder_paths); destresfinder = new Usm.ResourceFinder(paths); if(args.length < 2) { manifest_usage(); return 255; } var verb = args[1]; // Check for --replace and --debug flags for autoprovides if(verb == "autoprovides") { for(int i = 2; i < args.length; i++) { if(args[i] == "--replace") { replace_provides = true; } else if(args[i] == "--debug") { debug_mode = true; } else if(build_path == null) { build_path = args[i]; } } } else if(verb == "build") { // Optional for data packages (a no-op); enforced for regular packages in build() if(args.length >= 3) { build_path = args[2]; } } else if(verb != "remove" && verb != "acquire" && verb != "install" && verb != "package" && verb != "deploy" && verb != "test" && verb != "validate") { if(args.length < 3) { manifest_usage(); return 255; } build_path = args[2]; } if(!File.new_for_path("MANIFEST.usm").query_exists()) { printerr("No MANIFEST.usm file found in current directory.\n"); return 254; } try { var element = new InvercargillJson.JsonElement.from_file("MANIFEST.usm"); manifest = Usm.Manifest.get_mapper().materialise(element.as()); manifest.validate(); } catch (Error e) { printerr(@"Could not read MANIFEST.usm: $(e.message)\n"); return 253; } // Data packages define no build step: nothing is staged, no build directory // or build.tar.xz is ever created for them if(manifest.is_data_package && verb == "build") { printerr(@"Data package \"$(manifest.name)\" has no build step, nothing to build.\n"); return 0; } // Automatically create a temporary directory for these commands if one was not provided. if(!manifest.is_data_package && (verb == "install" || verb == "autoprovides" || verb == "test" || verb == "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(); } } // Automatically create the specified directory for these commands if it doesn't exist if(!manifest.is_data_package && build_path != null && (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"); try { build_dir.make_directory_with_parents(); } catch(Error e) { printerr(@"Could not create build directory: $(e.message)\n"); return 255; } } } if(verb == "build") { return build(); } if(verb == "install") { return install(); } if(verb == "acquire") { return acquire(); } if(verb == "remove") { return uininstall(); } if(verb == "package") { return package(); } if(verb == "deploy") { return manifest_deploy(args); } if(verb == "autoprovides") { return autoprovides(); } if(verb == "test") { return test(); } if(verb == "validate") { return validate(); } manifest_usage(); return 255; } private void manifest_usage() { printerr("USAGE:\n\tusm manifest build \n\tusm manifest install \n\tusm manifest remove\nusm manifest acquire\nusm manifest package\nusm manifest deploy [--exec CMD] [--base IMAGE] [--repository FILE]... [--no-build] [--installer-url URL]\nusm manifest autoprovides [--replace] [--debug] [build path]\nusm manifest test [build path]\nusm manifest validate [build path]\n"); } /** * Checks one dependency phase of the loaded manifest against the resources * {@link finder} can see, printing the same "Missing … dependency" lines as * before for flat phases. * * Grouped phases choose the first group whose members are all present (this * flow installs nothing, so every group costs nothing and manifest order * wins); when no group is viable an itemised per-group report is printed. * Returns false when the phase's requirements are unmet. */ private bool phase_dependencies_satisfied(Usm.DependencyPhase? phase, string label, Usm.ResourceFinder finder) { if(phase == null) { return true; } if(!phase.is_grouped) { var sane = true; foreach(var resource in phase.ordered_required()) { if(!finder.has_resource(resource)) { printerr(@"Missing $label dependency \"$resource\".\n"); sane = false; } } return sane; } var groups = phase.ordered_groups(); foreach(var group in groups) { var missing = group.where(r => !finder.has_resource(r)).to_vector(); if(missing.length == 0) { return true; } } printerr(@"No viable $label dependency group:\n"); for(uint index = 0; index < groups.length; index++) { printerr(@" Group $(index + 1) ($(groups[index].to_string(r => r.to_string(), ", "))):\n"); foreach(var resource in groups[index].where(r => !finder.has_resource(r))) { printerr(@" Missing $label dependency \"$resource\".\n"); } } return false; } private int build() { if(build_path == null) { manifest_usage(); return 255; } // Create build directory if it doesn't exist var sane = phase_dependencies_satisfied(manifest.dependencies.manage, "management", resfinder) & phase_dependencies_satisfied(manifest.dependencies.build, "build", resfinder); if(!sane) { printerr("Could not build manifest, missing dependencies\n"); return 252; } try { var proc = manifest.run_build(build_path, paths, SubprocessFlags.INHERIT_FDS); proc.wait_check(); } catch(Error e) { printerr(@"Error running build exec: $(e.message)\n"); return 251; } return 0; } private int install() { // Data packages define no build or install execs; their provides are // copied directly from the source tree if(manifest.is_data_package) { return install_data_package(); } // 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 sane = phase_dependencies_satisfied(manifest.dependencies.manage, "management", resfinder) & phase_dependencies_satisfied(manifest.dependencies.build, "build", resfinder) & phase_dependencies_satisfied(manifest.dependencies.runtime, "runtime", destresfinder); if(!sane) { printerr("Could not install manifest, missing dependencies\n"); return 252; } try { var proc = manifest.run_build(build_path, paths, SubprocessFlags.INHERIT_FDS, frac => printerr(@"Building '$(manifest.name)': $((int)(frac*100))%\r")); 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.INHERIT_FDS).wait_check(); } catch(Error e) { printerr(@"Error running install exec: $(e.message)\n"); return 249; } } else { install_dir = null; } try { manifest.install_resources(Environment.get_current_dir(), build_path, install_dir?.get_path(), paths, (r, c, t, f) => { if(f == 0.0f) { printerr(@"Installing resource $(c+1)/$t: \"$(r)\"\n"); } }); } catch(Error e) { printerr(@"Error installing manifest resources: $(e.message)\n"); return 248; } try { var proc = manifest.run_post_install(build_path, Usm.InstallType.FRESH, SubprocessFlags.INHERIT_FDS); if(proc != null) { proc.wait_check(); } } catch(Error e) { printerr(@"Error running post install exec: $(e.message)\n"); return 247; } return 0; } /** * Installs a data package: every provide is copied directly from the source * tree to its suggested path, with no build or install staging involved. */ private int install_data_package() { if(!phase_dependencies_satisfied(manifest.dependencies.runtime, "runtime", destresfinder)) { printerr("Could not install manifest, missing dependencies\n"); return 252; } try { manifest.install_resources(Environment.get_current_dir(), Environment.get_current_dir(), null, paths, (r, c, t, f) => { if(f == 0.0f) { printerr(@"Installing resource $(c+1)/$t: \"$(r)\"\n"); } }); } catch(Error e) { printerr(@"Error installing manifest resources: $(e.message)\n"); return 248; } return 0; } private int acquire() { if(manifest.executables.acquire == null) { printerr(@"Manifest does not reference an acquire script\n"); return 248; } // The acquire phase's own refs gate acquisition (it runs on the host) if(!phase_dependencies_satisfied(manifest.dependencies.acquire, "acquire", resfinder)) { printerr("Could not acquire manifest, missing dependencies\n"); return 247; } try { var proc = manifest.run_acquire(SubprocessFlags.INHERIT_FDS); proc.wait_check(); } catch(Error e) { printerr(@"Error running build exec: $(e.message)\n"); return 251; } return 0; } private int uininstall() { if(!phase_dependencies_satisfied(manifest.dependencies.manage, "management", resfinder)) { printerr("Could not remove manifest, missing dependencies\n"); return 252; } try { var proc = manifest.run_remove(Usm.RemoveType.FINAL, SubprocessFlags.INHERIT_FDS); if(proc != null) { proc.wait_check(); } } catch(Error e) { printerr(@"Error running remove exec: $(e.message)\n"); return 249; } // Do files and symlinks first foreach (var resource in manifest.provides.where(r => r.value.file_type != Usm.ManifestFileType.DIRECTORY)) { var path = paths.get_suggested_path_for_resource(resource.key); printerr(@"Deleting resource $(resource.key) from $path..."); try { var file = File.new_for_path(path); if(file.query_exists()) { file.delete(); printerr(" done\n"); } else { printerr(" file missing\n"); } } catch(Error e) { printerr(@" $(e.message)\n"); return 250; } } // Do directories last foreach (var resource in manifest.provides.where(r => r.value.file_type == Usm.ManifestFileType.DIRECTORY)) { var path = paths.get_suggested_path_for_resource(resource.key); printerr(@"Deleting resource $(resource.key) from $path..."); try { var file = File.new_for_path(path); if(file.query_exists()) { file.delete(); printerr(" done\n"); } else { printerr(" file missing\n"); } } catch(IOError.NOT_EMPTY e) { printerr(" directory not empty\n"); } catch(Error e) { printerr(@" $(e.message)\n"); return 250; } } return 0; } private int package() { printerr("Writing archive...\n"); var output = @"../$(manifest.name)-$(manifest.version).usmc"; try { // Collect everything not excluded by the root .usmignore (falling // back to the .git-only default), pruning ignored directories var ignore = new Usm.UsmIgnore.from_root(Environment.get_current_dir()); var entries = new Invercargill.DataStructures.Vector(); package_collect(".", ignore, entries); var list_file = File.new_build_filename("/tmp", Uuid.string_random()); try { var stream = new DataOutputStream(list_file.replace(null, false, FileCreateFlags.REPLACE_DESTINATION)); foreach(var entry in entries) { stream.put_string(@"./$entry\n"); } stream.close(); var proc = new Subprocess.newv(new string[] { "tar", "-cJf", output, "--no-recursion", "-T", list_file.get_path() }, SubprocessFlags.INHERIT_FDS); proc.wait_check(); } finally { try { list_file.delete(); } catch(Error e) { // The temporary file list is disposable; leaving it behind is harmless } } printerr(@"Wrote package to path \"$(output)\"\n"); return 0; } catch(Error e) { printerr("Failed to write package\n"); return 172; } } /** * Walks the package tree rooted at {@link relative_dir}, collecting the * relative paths of every file and directory not ignored by {@link ignore}. * Ignored directories are pruned: nothing beneath them is visited. */ private static void package_collect(string relative_dir, Usm.UsmIgnore ignore, Invercargill.DataStructures.Vector entries) throws Error { var folder = File.new_for_path(relative_dir); var enumerator = folder.enumerate_children("*", FileQueryInfoFlags.NOFOLLOW_SYMLINKS); while(true) { FileInfo? info = enumerator.next_file(); if(info == null) { break; } var relative_path = relative_dir == "." ? info.get_name() : @"$relative_dir/$(info.get_name())"; var is_directory = info.get_file_type() == FileType.DIRECTORY; if(ignore.matches(relative_path, is_directory)) { continue; } entries.add(relative_path); if(is_directory) { package_collect(relative_path, ignore, entries); } } } private int autoprovides() { // 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; } } if(manifest.executables.install == null) { printerr(@"Autoprovides command only works with manifests that specify an installation executable.\n"); return 1; } var sane = phase_dependencies_satisfied(manifest.dependencies.manage, "management", resfinder) & phase_dependencies_satisfied(manifest.dependencies.build, "build", resfinder) & phase_dependencies_satisfied(manifest.dependencies.runtime, "runtime", destresfinder); if(!sane) { printerr("Could not install manifest, missing dependencies\n"); return 252; } try { Usm.ProgressDelegate? progress_callback = null; SubprocessFlags build_flags = SubprocessFlags.STDOUT_SILENCE; if (!debug_mode) { progress_callback = frac => printerr(@"Building '$(manifest.name)': $((int)(frac*100))%\r"); } else { build_flags = SubprocessFlags.INHERIT_FDS; } var proc = manifest.run_build(build_path, paths, build_flags, progress_callback); proc.wait_check(); if (!debug_mode) { 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()); try { install_dir.make_directory(); } catch(Error e) { printerr(@"Could not create temporary install directory: $(e.message)\n"); return 250; } try { SubprocessFlags install_flags = debug_mode ? SubprocessFlags.INHERIT_FDS : SubprocessFlags.STDOUT_SILENCE; manifest.run_install(build_path, install_dir.get_path(), paths, Usm.InstallType.FRESH, install_flags).wait_check(); } catch(Error e) { printerr(@"Error running install exec: $(e.message)\n" ); return 249; } try { var provides = autoprovides_scan_tree(install_dir.get_path(), install_dir.get_path()); // Create a JSON representation of the provides dictionary var json_object = new InvercargillJson.JsonObject(); foreach (var entry in provides) { // Handle special case: when pathBase is as-expected, path is empty, and type is reg // output string "as-expected" instead of full JSON object if(entry.value.path_base == Usm.ManifestFilePathBase.AS_EXPECTED && entry.value.path == "" && entry.value.file_type == Usm.ManifestFileType.REGULAR) { json_object.set_native(entry.key.to_string(), "as-expected"); } else { var mapper = Usm.ManifestFile.get_mapper(); json_object.set_native(entry.key.to_string(), mapper.map_from(entry.value)); } } var json_string = json_object.as_element().stringify_pretty(); if(replace_provides) { try { // Update the manifest's provides property manifest.provides = provides; // Serialize the updated manifest back to JSON var mapper = Usm.Manifest.get_mapper(); var properties = mapper.map_from(manifest); var manifest_json_element = new InvercargillJson.JsonElement.from_properties(properties); var manifest_json_string = manifest_json_element.stringify_pretty(); // Write the updated manifest to file var file = File.new_for_path("MANIFEST.usm"); var data_stream = new DataOutputStream(file.replace(null, false, FileCreateFlags.REPLACE_DESTINATION)); data_stream.put_string(manifest_json_string); printerr("Successfully updated 'provides' property in MANIFEST.usm\n"); } catch(Error e) { printerr(@"Error updating MANIFEST.usm: $(e.message)\n"); printerr("Original file left unchanged. Generated provides:\n"); print(@"$json_string\n"); return 249; } } else { print(@"$json_string\n"); } return 0; } catch(Error e) { printerr(@"Error scanning installation result: $(e.message)\n"); return 249; } } public static Invercargill.DataStructures.Dictionary 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.NOFOLLOW_SYMLINKS); var provides = new Invercargill.DataStructures.Dictionary(); // 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) { var file = folder.get_child(info.get_name()); var true_path = file.get_path().substring(install_root.length); var type = guess_resource_type(true_path); var relative_path = get_relative_path_for_type(true_path, type.to_string()); // Remove leading slash from relative_path if present if (relative_path.has_prefix("/")) { relative_path = relative_path.substring(1); } // Create ResourceRef and ManifestFile var resource_ref = new Usm.ResourceRef.with_type(type, relative_path); var manifest_file = new Usm.ManifestFile.from_string("as-expected"); provides[resource_ref] = manifest_file; } else if (info.get_file_type() == FileType.SYMBOLIC_LINK) { var file = folder.get_child(info.get_name()); var true_path = file.get_path().substring(install_root.length); var type = guess_resource_type(true_path); var relative_path = get_relative_path_for_type(true_path, type.to_string()); // Remove leading slash from relative_path if present if (relative_path.has_prefix("/")) { relative_path = relative_path.substring(1); } // Create ResourceRef and ManifestFile var resource_ref = new Usm.ResourceRef.with_type(type, relative_path); var manifest_file = new Usm.ManifestFile() { path_base = Usm.ManifestFilePathBase.AS_EXPECTED, file_type = Usm.ManifestFileType.SYMBOLIC_LINK, destination = info.get_symlink_target() }; provides[resource_ref] = manifest_file; } else if (info.get_file_type() == FileType.DIRECTORY) { var subfolder = folder.get_child(info.get_name()); var sub_provides = autoprovides_scan_tree(install_root, subfolder.get_path()); // Add all entries from subdirectory foreach (var entry in sub_provides) { provides[entry.key] = entry.value; } } } return provides; } private static Usm.ResourceType guess_resource_type(string path) { // Remove leading slash if present string clean_path = path.has_prefix("/") ? path.substring(1) : path; // Create a temporary Paths object to get the default paths var default_paths = new Usm.Paths(); // Find all matching resource types and calculate directory traversals var matching_types = new Invercargill.DataStructures.Vector(); var traversal_counts = new Invercargill.DataStructures.Vector(); // Define all resource types to check var types_to_check = new Invercargill.Composition(); types_to_check.append(Invercargill.Iterate.these( Usm.ResourceType.BINARY, Usm.ResourceType.SUPER_BINARY, Usm.ResourceType.LIBRARY, Usm.ResourceType.LIBRARY_RESOURCE, Usm.ResourceType.LIBRARY_EXECUTABLE, Usm.ResourceType.INCLUDE, Usm.ResourceType.APPLICATION, Usm.ResourceType.INFO_PAGE, Usm.ResourceType.MANUAL_PAGE, Usm.ResourceType.LOCALE, Usm.ResourceType.VALA_API, Usm.ResourceType.GOBJECT_IR, Usm.ResourceType.TAG, Usm.ResourceType.RESOURCE, Usm.ResourceType.PKG_CONFIG, Usm.ResourceType.TYPELIB, Usm.ResourceType.CONFIGURATION, Usm.ResourceType.OPTIONAL )); // If the canonlib directory is different to the regular library path, add the canonical checks if(paths.canonlib != paths.lib) { types_to_check.append(Invercargill.Iterate.these( Usm.ResourceType.CANONICAL_LIBRARY, Usm.ResourceType.CANONICAL_LIBRARY_RESOURCE )); } foreach (var type in types_to_check) { var suggested_path = default_paths.get_suggested_base_path_for_type(type); // Remove the destination prefix (usually "/") to get the relative path var relative_suggested_path = suggested_path.has_prefix("/") ? suggested_path.substring(1) : suggested_path; if (clean_path.has_prefix(relative_suggested_path)) { var relative_path = clean_path.substring(relative_suggested_path.length); // Count slashes (directory traversals) in the relative path int traversal_count = 0; for (int j = 0; j < relative_path.length; j++) { if (relative_path[j] == '/') { traversal_count++; } } // Library and Library resource have the same path. Try to have actual libraries get 'lib:' type, and all others 'libres:' var basename = Path.get_basename(path); var is_library = basename.has_suffix(".so") || basename.contains(".so.") || basename.has_suffix(".a") || basename.has_suffix(".o"); if(!is_library && (type == Usm.ResourceType.LIBRARY || type == Usm.ResourceType.CANONICAL_LIBRARY)) { continue; } if(is_library && (type == Usm.ResourceType.LIBRARY_RESOURCE || type == Usm.ResourceType.CANONICAL_LIBRARY_RESOURCE)) { continue; } matching_types.add(type); traversal_counts.add(traversal_count); } } // If no matches found, default to "rootpath" if (matching_types.peek_count() == 0) { return Usm.ResourceType.ROOT_PATH; } // Find the type with the fewest directory traversals int min_traversals = traversal_counts[0]; Usm.ResourceType best_type = matching_types[0]; for (int i = 1; i < traversal_counts.peek_count(); i++) { if (traversal_counts[i] < min_traversals) { min_traversals = traversal_counts[i]; best_type = matching_types[i]; } } return best_type; } private static string get_relative_path_for_type(string path, string type) { // Remove leading slash if present string clean_path = path.has_prefix("/") ? path.substring(1) : path; // Create a temporary Paths object to get the default paths var default_paths = new Usm.Paths(); try { // Convert string type to ResourceType enum var resource_type = Usm.ResourceType.from_string(type); var suggested_path = default_paths.get_suggested_base_path_for_type(resource_type); // Remove the destination prefix (usually "/") to get the relative path var relative_suggested_path = suggested_path.has_prefix("/") ? suggested_path.substring(1) : suggested_path; if (clean_path.has_prefix(relative_suggested_path)) { return clean_path.substring(relative_suggested_path.length); } } catch (Error e) { // Type not found, fall through to default case } // Default case - return the path as-is return clean_path; } private int test() { if(manifest.is_data_package) { printerr(@"Data packages define no test script.\n"); return 248; } 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 sane = phase_dependencies_satisfied(manifest.dependencies.manage, "management", resfinder) & phase_dependencies_satisfied(manifest.dependencies.build, "build", resfinder); if(!sane) { printerr("Could not test manifest, missing dependencies\n"); return 252; } try { // First build the package var build_proc = manifest.run_build(build_path, paths, SubprocessFlags.INHERIT_FDS, 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; } if(manifest.executables.test == null) { printerr(@"Manifest does not reference a test script\n"); return 248; } try { var test_proc = manifest.run_test(build_path, SubprocessFlags.INHERIT_FDS); test_proc.wait_check(); } catch(Error e) { printerr(@"Error running test exec: $(e.message)\n"); 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 sane = phase_dependencies_satisfied(manifest.dependencies.manage, "management", resfinder) & phase_dependencies_satisfied(manifest.dependencies.build, "build", resfinder) & phase_dependencies_satisfied(manifest.dependencies.runtime, "runtime", destresfinder); 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"); } // Check for markdown if(manifest.markdown_path == null) { printerr("Warning: No markdown description has been provided in the manifest.\n"); } // Check for url if(manifest.url == null) { printerr("Warning: No URL has been provided in the manifest.\n"); } // Data packages define no build or install execs; their provides resolve // directly from the source tree, so validate them there and stop if(manifest.is_data_package) { return validate_data_package(); } 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(); 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(); 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; } /** * Validates a data package: every provide must exist in the source tree at * its declared path; no build or install staging is involved. */ private int validate_data_package() { var missing_resources = new Invercargill.DataStructures.Vector(); foreach (var expected in manifest.provides) { var expected_path = Path.build_filename(Environment.get_current_dir(), expected.value.path ?? ""); if(!File.new_for_path(expected_path).query_exists()) { missing_resources.add(expected.key); } } if(missing_resources.any()) { printerr("Error: Expected resources not found in the source tree:\n"); foreach (var resource in missing_resources) { printerr(@" - $(resource.to_string()) (in source directory at \"$(manifest.provides[resource].path ?? "")\")\n"); } return 248; } printerr("Manifest validation completed successfully.\n"); return 0; }