updated package manager to use KDL instead of INI
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 21 Nov 2023 23:46:26 +0000 (17:46 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 21 Nov 2023 23:46:26 +0000 (17:46 -0600)
scripts/onyx-pkg.onyx

index 1dc60aa162ca2112dda17ba9573ea10d72eaa58c..0e106f752ff30e315697d2ed1a930164a6ccd22b 100644 (file)
@@ -37,11 +37,12 @@ Version :: SemVer.{0, 1, 1}
 
 
 use core {package, *}
+use core.encoding {kdl}
 use runtime
 
 global_arguments: struct {
     #tag "--config-file"
-    config_file := "./onyx-pkg.ini";
+    config_file := "./onyx-pkg.kdl";
 } = .{};
 
 main :: (args: [] cstr) {
@@ -152,11 +153,11 @@ run_init_command :: (args: [] cstr) {
 
     // @TODO // Validation for these fields.
     r := io.reader_make(&stdio.stream);
-    read_field("Package name: ", &config.metadata.name, "");
-    read_field("Package description: ", &config.metadata.description, "");
-    read_field("Package url: ", &config.metadata.url, "");
-    read_field("Package author: ", &config.metadata.author, "");
-    read_field("Package version (0.0.1): ", &config.metadata.version, .{0, 0, 1});
+    read_field("Package name: ", &config.package_name, "");
+    read_field("Package description: ", &config.package_description, "");
+    read_field("Package url: ", &config.package_url, "");
+    read_field("Package author: ", &config.package_author, "");
+    read_field("Package version (0.0.1): ", &config.package_version, .{0, 0, 1});
 }
 
 #tag Command.{ "add", "Add a new dependency to the project.", "package-url [version]",
@@ -173,8 +174,8 @@ run_add_command :: (args: [] cstr) {
         return;
     }
 
-    dep     := string.as_str(args[0]);
-    dep      = Git.get_full_repo_uri(dep);
+    dep_name := string.as_str(args[0]);
+    dep_repo := Git.get_full_repo_uri(dep_name);
 
     version: SemVer;
     if args.count > 1 {
@@ -184,18 +185,22 @@ run_add_command :: (args: [] cstr) {
         }
 
     } else {
-        version = Git.get_latest_version(dep);
+        version = Git.get_latest_version(dep_repo);
     }
 
-    if config.dependencies.dependencies->has(dep) {
-        error_print("Dependency '{}' already specified at version '{}'.\n", dep, config.dependencies.dependencies[dep]);
+    if config.dependencies->has(dep_name) {
+        error_print("Dependency '{}' already specified at version '{}'.\n", dep_name, config.dependencies[dep_name]?.version);
 
     } elseif version->is_zero() {
         error_print("Unable to find latest version of '{}'\n", string.as_str(args[0]));
 
     } else {
-        config.dependencies.dependencies[dep] = version;
-        info_print("Added", "'{}' version {}\n", dep, version);
+        config.dependencies[dep_name] = .{
+            name = dep_name,
+            version = version,
+            source = .{ Git = dep_repo }
+        };
+        info_print("Added", "'{}' version {}\n", dep_name, version);
     }
 }
 
@@ -212,8 +217,10 @@ run_remove_command :: (args: [] cstr) {
 
     dep := string.as_str(args[0]);
 
-    if config.dependencies.dependencies->has(dep) {
-        config.dependencies.dependencies->delete(dep);
+    if config.dependencies->has(dep) {
+        version := config.dependencies->get(dep)->unwrap().version;
+        config.dependencies->delete(dep);
+        info_print("Removed", "'{}' version {}\n", dep, version);
         return;
     }
 
@@ -222,20 +229,20 @@ run_remove_command :: (args: [] cstr) {
 
 #tag Command.{ "show", "Show dependencies and versions.", "" }
 run_show_command :: (args: [] cstr) {
-    printf("Package name        : {}\n", config.metadata.name);
-    printf("Package description : {}\n", config.metadata.description);
-    printf("Package url         : {}\n", config.metadata.url);
-    printf("Package author      : {}\n", config.metadata.author);
-    printf("Package version     : {}\n", config.metadata.version);
+    printf("Package name        : {}\n", config.package_name);
+    printf("Package description : {}\n", config.package_description);
+    printf("Package url         : {}\n", config.package_url);
+    printf("Package author      : {}\n", config.package_author);
+    printf("Package version     : {}\n", config.package_version);
     print("\n");
 
-    max_width := array.fold(config.dependencies.dependencies.entries, 0, (d, w) => {
+    max_width := array.fold(config.dependencies.entries, 0, (d, w) => {
         return math.max(d.key.count, w);
     });
     format_str := tprintf("    {{w{}}} | {{}}\n", max_width);
 
     print("Dependencies:\n");
-    for config.dependencies.dependencies.entries {
+    for config.dependencies.entries {
         printf(format_str, it.key, it.value);
     }
     print("\n");
@@ -245,14 +252,15 @@ run_show_command :: (args: [] cstr) {
 // @Feature // Add "locked" dependencies that will never update?
 run_update_command :: (args: [] cstr) {
     info_print("Info", "Updating dependencies to newest compatible versions.\n");
-    for& config.dependencies.dependencies.entries {
-        new_version := Git.get_latest_compatible_version(it.key, it.value);
+    for& config.dependencies.entries {
+        repo := it.value.source.Git ?? [] { continue; };
+        new_version := Git.get_latest_compatible_version(repo, it.value.version);
 
-        if it.value != new_version {
-            info_print("Update", "{}  {} -> {}\n", it.key, it.value, new_version);
+        if it.value.version != new_version {
+            info_print("Update", "{}  {} -> {}\n", it.key, it.value.version, new_version);
         }
 
-        it.value = new_version;
+        it.value.version = new_version;
     }
 }
 
@@ -276,8 +284,8 @@ run_sync_command :: (args: [] cstr) {
     arg_parse.arg_parse(args, &options);
 
     if options.clean {
-        info_print("Cleaning", "Removing {} directory\n", config.config.lib_source_directory);
-        os.remove_directory(config.config.lib_source_directory);
+        info_print("Cleaning", "Removing {} directory\n", config.dependency_source_path);
+        os.remove_directory(config.dependency_source_path);
     }
 
     To_Install :: struct {
@@ -292,15 +300,19 @@ run_sync_command :: (args: [] cstr) {
         delete(&dependencies_installed);
     }
 
-    for& config.dependencies.dependencies.entries {
-        dependencies_to_install << .{ .{it.key, it.value}, true };
+    for& config.dependencies.entries {
+        dependencies_to_install << .{
+            .{it.value.source, it.value.version}, true
+        };
     }
 
     while dependencies_to_install.count > 0 {
         alloc.clear_temp_allocator();
         to_install := array.delete(&dependencies_to_install, 0);
 
-        if dependencies_installed->has(to_install.repo) {
+        repo := to_install.source.Git ?? [] { continue; };
+
+        if dependencies_installed->has(repo) {
             continue;
         }
 
@@ -311,36 +323,37 @@ run_sync_command :: (args: [] cstr) {
         }
 
         inner_config := read_config_from_installed_dependency(installed_folder) ?? [] {
-            error_print("Misconfigured onyx-pkg.ini in '{}'. Omitting.\n", to_install.repo);
+            error_print("Misconfigured onyx-pkg.kdl in '{}'. Omitting.\n", repo);
             continue;
         };
 
 
-        if inner_config.metadata.version->is_zero() {
-            error_print("Expected a version for '{}' that is not '0.0.0'.\n", to_install.repo);
+        if inner_config.package_version->is_zero() {
+            error_print("Expected a version for '{}' that is not '0.0.0'.\n", repo);
             continue;
         }
 
-        for& inner_config.dependencies.dependencies.entries {
-            dep := dependencies_installed[it.key];
+        for& inner_config.dependencies.entries {
+            key := it.value.source.Git ?? [] { continue; };
+            dep := dependencies_installed[key];
             if dep {
                 // TODO : Check if this is right? Could this accidentally forcefully upgrade a package?
-                if it.value->is_newer(dep->unwrap()) {
-                    uninstall_package(.{it.key, it.value});
-                    dependencies_installed->delete(it.key);
-                    dependencies_to_install << .{ .{it.key, it.value}, false };
+                if it.value.version->is_newer(dep->unwrap()) {
+                    uninstall_package(.{it.value.source, it.value.version});
+                    dependencies_installed->delete(key);
+                    dependencies_to_install << .{ .{it.value.source, it.value.version}, false };
 
-                } elseif !(it.value->is_compatible(dep->unwrap())) {
+                } elseif !(it.value.version->is_compatible(dep->unwrap())) {
                     // TODO: Explain this more
                     error_print("Different major versions of '{}' being used!\n", it.key);
                     os.exit(1);
                 }
             } else {
-                dependencies_to_install << .{ .{it.key, it.value}, false };
+                dependencies_to_install << .{ .{it.value.source, it.value.version}, false };
             }
         }
 
-        dependencies_installed[to_install.repo] = to_install.version;
+        dependencies_installed[repo] = to_install.version;
     }
 
     build_package_file_to_load();
@@ -391,17 +404,17 @@ run_publish_command :: (args: [] cstr) {
         switch input {
             case "a" {
                 // Major version bump
-                config.metadata.version->bump_major();
+                config.package_version->bump_major();
             }
 
             case "i" {
                 // Minor version bump
-                config.metadata.version->bump_minor();
+                config.package_version->bump_minor();
             }
 
             case "p" {
                 // Patch version bump
-                config.metadata.version->bump_patch();
+                config.package_version->bump_patch();
             }
 
             case "c" {
@@ -443,25 +456,13 @@ run_list_versions :: (args: [] cstr) {
     }
 }
 
-#tag Command.{ "run", "Run the command provided in `config.run_cmd`." }
-run_run_command :: (args: [] cstr) {
-    if !string.empty(config.config.run_cmd) {
-        run_command_and_forward_output(config.config.run_cmd);
-    }
-}
-
-#tag Command.{ "debug", "Run the command provided in `config.debug_cmd`." }
-run_debug_command :: (args: [] cstr) {
-    if !string.empty(config.config.debug_cmd) {
-        run_command_and_forward_output(config.config.debug_cmd);
-    }
+#tag Command.{
+    "migrate", "Migrate an old onyx-pkg.ini to the new onyx-pkg.kdl", "",
+    require_config_file = false,
 }
-
-#tag Command.{ "test", "Run the command provided in `config.test_cmd`." }
-run_test_command :: (args: [] cstr) {
-    if !string.empty(config.config.test_cmd) {
-        run_command_and_forward_output(config.config.test_cmd);
-    }
+run_migrate_command :: (args: [] cstr) {
+    config = read_old_config("./onyx-pkg.ini")->unwrap();
+    store_config_file();
 }
 
 #tag Command.{
@@ -606,29 +607,34 @@ run_new_command :: (args: [] cstr) {
 
 
 install_package :: (pack: Package, downgrade_if_necessary := false, skip_native_compilation := false) -> (bool, installed_folder: str) {
-    package_folder := get_install_path_of_repo(pack.repo);
+    //
+    // Currently this only supports Git-based packages.
+    repo := pack.source.Git ?? [] {
+        return return false, "";
+    };
+    package_folder := get_install_path_of_repo(repo);
 
     if os.file_exists(package_folder) {
-        installed_version := get_installed_version_of_package(pack.repo);
+        installed_version := get_installed_version_of_package(repo);
 
         if installed_version == pack.version {
-            info_print("Exists", "{}  {}\n", pack.repo, installed_version);
+            info_print("Exists", "{}  {}\n", repo, installed_version);
             return true, package_folder;
         }
 
         if installed_version->is_newer(pack.version) && !downgrade_if_necessary {
-            error_print("Refusing to downgrade '{}' from {} to {}.\n", pack.repo, installed_version, pack.version);
+            error_print("Refusing to downgrade '{}' from {} to {}.\n", repo, installed_version, pack.version);
             return false, "";
         }
 
         // :PRETTY
         verb := "Upgrading" if pack.version->is_newer(installed_version) else "Downgrading";
-        info_print(verb, "{}  {} -> {}\n", pack.repo, installed_version, pack.version);
+        info_print(verb, "{}  {} -> {}\n", repo, installed_version, pack.version);
         uninstall_package(pack);
     }
 
-    if !Git.clone_version(pack.repo, pack.version) {
-        error_print("Failed to fetch {} version {}.\n", pack.repo, pack.version);
+    if !Git.clone_version(repo, pack.version) {
+        error_print("Failed to fetch {} version {}.\n", repo, pack.version);
         return false, "";
     }
     
@@ -639,8 +645,9 @@ install_package :: (pack: Package, downgrade_if_necessary := false, skip_native_
 }
 
 uninstall_package :: (pack: Package) -> bool {
-    folder_name := strip_protocol_and_www_from_repo(pack.repo);
-    package_folder := os.path_join(config.config.lib_source_directory, folder_name);
+    repo := pack.source.Git?;
+    folder_name := strip_protocol_and_www_from_repo(repo);
+    package_folder := os.path_join(config.dependency_source_path, folder_name);
 
     if os.file_exists(package_folder) {
         // Should this check if the version to be deleted is the one that is actually installed?
@@ -657,9 +664,10 @@ uninstall_package :: (pack: Package) -> bool {
 attempt_remove_native_library :: (package_folder: str) -> bool {
     inner_config := read_config_from_installed_dependency(package_folder)?;
 
-    if string.empty(inner_config.native_library.library) do return false;
+    if !inner_config.native_library do return false;
 
-    os.remove_file(os.path_join(config.config.lib_bin_directory, tprintf("{}{}", inner_config.native_library.library, native_library_suffix)));
+    target := os.path_join(config.dependency_binary_path, tprintf("{}{}", inner_config.native_library->unwrap(), native_library_suffix));
+    os.remove_file(target);
     return true;
 }
 
@@ -674,24 +682,11 @@ rebuild_native_library :: (folder: str) -> (bool, str) {
 
 get_installed_version_of_package :: (package_path: str) -> SemVer {
     inner_config := read_config_from_installed_dependency(get_install_path_of_repo(package_path));
-    return inner_config?.metadata.version;
+    return inner_config?.package_version;
 }
 
 read_config_from_installed_dependency :: (dependency_folder: str) -> ? Config {
-    for os.with_file(tprintf("{}/onyx-pkg.ini", dependency_folder)) {
-        r := io.reader_make(it);
-        defer io.reader_free(&r);
-
-        inner_config: Config;
-        result, error := encoding.ini.parse_ini_file(&r, &inner_config);
-
-        if result != .Success {
-            return .{};
-
-        } else {
-            return inner_config;
-        }
-    }
+    return load_config(tprintf("{}/onyx-pkg.kdl", dependency_folder));
 }
 
 strip_protocol_and_www_from_repo :: (repo: str) -> str {
@@ -713,19 +708,20 @@ strip_protocol_and_www_from_repo :: (repo: str) -> str {
 }
 
 get_install_path_of_repo :: (repo: str) -> str {
-    return os.path_join(config.config.lib_source_directory, strip_protocol_and_www_from_repo(repo));
+    return os.path_join(config.dependency_source_path, strip_protocol_and_www_from_repo(repo));
 }
 
 run_native_library_installation :: (folder: str) -> (bool, str) {
     inner_config := read_config_from_installed_dependency(folder) ?? [] {
+        error_print("Failed to parse onyx-pkg.kdl in '{}'.\n", folder);
         return return false, "";
     };
 
-    if string.empty(inner_config.native_library.build_cmd) do return true, "";
+    if !inner_config.native_library_build do return true, "";
 
     info_print("Install", "Running installation of '{}'\n", folder);    
 
-    args := string.split(inner_config.native_library.build_cmd, #char " ", context.temp_allocator);
+    args := string.split(inner_config.native_library_build->unwrap(), #char " ", context.temp_allocator);
     cmd  := args[0];
     args  = args[1 .. args.count];
 
@@ -743,15 +739,15 @@ run_native_library_installation :: (folder: str) -> (bool, str) {
         }
     }
 
-    if !os.dir_exists(config.config.lib_bin_directory) {
-        if !os.dir_create(config.config.lib_bin_directory) {
-            error_print("Failed to create native library directory, {}.\n", config.config.lib_bin_directory);
+    if !os.dir_exists(config.dependency_binary_path) {
+        if !os.dir_create(config.dependency_binary_path) {
+            error_print("Failed to create native library directory, {}.\n", config.dependency_binary_path);
             return false, "";
         }
     }
 
-    source_path := tprintf("{}/{}{}", folder, inner_config.native_library.library, native_library_suffix);
-    dest_path   := tprintf("{}/{}{}", config.config.lib_bin_directory, inner_config.native_library.library, native_library_suffix);
+    source_path := tprintf("{}/{}{}", folder, inner_config.native_library->unwrap(), native_library_suffix);
+    dest_path   := tprintf("{}/{}{}", config.dependency_binary_path, inner_config.native_library->unwrap(), native_library_suffix);
     success := os.rename_file(source_path, dest_path);
 
     if !success {
@@ -778,11 +774,11 @@ run_command_and_forward_output :: (cmd: str) => {
 }
 
 build_package_file_to_load :: () {
-    if !os.dir_exists(config.config.lib_source_directory) {
-        os.dir_create(config.config.lib_source_directory);
+    if !os.dir_exists(config.dependency_source_path) {
+        os.dir_create(config.dependency_source_path);
     }
 
-    filepath := os.path_join(config.config.lib_source_directory, "packages.onyx");
+    filepath := os.path_join(config.dependency_source_path, "packages.onyx");
 
     if os.file_exists(filepath) {
         os.remove_file(filepath);
@@ -801,8 +797,8 @@ build_package_file_to_load :: () {
 // PACKAGE LOADING
 """);
 
-        for config.dependencies.dependencies->as_iter() {
-            dependency_repo := it.key;
+        for config.dependencies->as_iter() {
+            dependency_repo := it.value.source.Git ?? [] { continue; };
             dependency_folder := strip_protocol_and_www_from_repo(dependency_repo);
 
             io.write_format(&w,
@@ -812,7 +808,7 @@ build_package_file_to_load :: () {
 
         io.write(&w, "\n\n// NATIVE LIBRARY PATH\n");
 
-        io.write_format(&w, "#library_path \"{}\"\n", config.config.lib_bin_directory);
+        io.write_format(&w, "#library_path \"{}\"\n", config.dependency_binary_path);
     }
 }
 
@@ -881,7 +877,7 @@ SemVer :: struct {
 #operator != macro (s1, s2: SemVer) => !(s1 == s2);
 
 Package :: struct {
-    repo: str;
+    source: DependencySource;
     version: SemVer;
 }
 
@@ -893,10 +889,10 @@ Package :: struct {
 }
 
 Git :: struct {
-    get_full_repo_uri :: (repo: str) -> str {
+    get_full_repo_uri :: (package_search: str) -> str {
         for Known_Repositories {
             for proto: Protocols {
-                r := tprintf("{}{}", proto, tprintf(it, repo));
+                r := tprintf("{}{}", proto, tprintf(it, package_search));
                 git_proc := os.process_spawn(git_path, .["ls-remote", "--tags", r]);
                 if os.process_wait(&git_proc) == .Success {
                     return r |> string.alloc_copy();
@@ -960,7 +956,7 @@ Git :: struct {
         info_print("Fetch", "{}  {}\n", repo, version);
 
         version_str := tprintf("v{}", version);
-        temporary_dest := os.path_join(config.config.lib_source_directory, ".cloned");
+        temporary_dest := os.path_join(config.dependency_source_path, ".cloned");
 
         os.remove_directory(temporary_dest);
 
@@ -968,7 +964,7 @@ Git :: struct {
             for proto: Protocols {
                 // Use 'git clone' to clone the bare minimum amount to get the released version.
                 proto_repo  := tprintf("{}{}", proto, repo);
-                git_proc    := os.process_spawn(git_path, .["clone", "--depth", "1", "-b", version_str, proto_repo, temporary_dest]);
+                git_proc    := os.process_spawn(git_path, .["clone", "--single-branch", "--depth", "1", "-b", version_str, proto_repo, temporary_dest]);
                 result      := os.process_wait(&git_proc);
 
                 if result == .Success do return true;
@@ -981,7 +977,7 @@ Git :: struct {
             install_dest := strip_protocol_and_www_from_repo(repo);
 
             // Move the cloned repository to its permanent location.
-            actual_dest := os.path_join(config.config.lib_source_directory, install_dest);
+            actual_dest := os.path_join(config.dependency_source_path, install_dest);
             if os.dir_exists(actual_dest) {
                 error_print("Expected {} to not exist when fetching '{}'.\n", actual_dest, repo);
                 os.remove_directory(temporary_dest);
@@ -1025,8 +1021,8 @@ Git :: struct {
         }
 
         run_command(git_path, .["add", global_arguments.config_file]);
-        run_command(git_path, .["commit", "-m", tprintf("version {}", config.metadata.version)]);
-        run_command(git_path, .["tag", tprintf("v{}", config.metadata.version)]);
+        run_command(git_path, .["commit", "-m", tprintf("version {}", config.package_version)]);
+        run_command(git_path, .["tag", tprintf("v{}", config.package_version)]);
         run_command(git_path, .["push", "--tags"]);
         run_command(git_path, .["push"]);
         return true;
@@ -1037,6 +1033,241 @@ Git :: struct {
 
 config: Config;
 Config :: struct {
+    package_name: str;
+    package_description: str;
+    package_url: str;
+    package_author: str;
+    package_version: SemVer;
+
+    dependency_source_path: str;
+    dependency_binary_path: str;
+
+    native_library: ? str;
+    native_library_build: ? str;
+
+    build_configs: Map(str, BuildConfig);
+
+    dependencies: Map(str, Dependency);
+
+    _source_doc: kdl.Document;
+}
+
+Dependency :: struct {
+    name: str;
+    version: SemVer;
+    source: DependencySource;
+}
+
+DependencySource :: union {
+    Unknown: void;
+    Git: str;
+}
+
+BuildConfig :: struct {
+    include: [..] str;
+    args: [..] str;
+    runtime: str;
+    target: str;
+}
+
+load_config_file :: () -> bool {
+    _config := load_config(global_arguments.config_file);
+    if !_config {
+        return false;
+    }
+
+    config = _config->unwrap();
+    return true;
+}
+
+store_config_file :: () -> bool {
+    return store_config(global_arguments.config_file);
+}
+
+load_config :: (path: str) -> ? Config {
+    contents := os.get_contents(path);
+    if !contents do return .{};
+
+    defer delete(&contents);
+    doc := kdl.parse(contents).Ok?;
+
+    c: Config;
+    c.dependency_source_path = "./lib";
+    c.dependency_binary_path = "./bin";
+
+    doc->query("top() > package")->with([p] {
+        pack := p;
+
+        load_string(pack, "name", &c.package_name);
+        load_string(pack, "author", &c.package_author);
+        load_string(pack, "description", &c.package_description);
+        load_string(pack, "url", &c.package_url);
+
+        version: str;
+        load_string(pack, "version", &version);
+
+        conv.parse_any(&c.package_version, version);
+    });
+
+    doc->query("top() > config")->with([p] {
+        load_string(p, "dependency_source_path", &c.dependency_source_path);
+        load_string(p, "dependency_binary_path", &c.dependency_binary_path);
+    });
+
+    doc->query("top() > native")->with([p] {
+        load_string(p, "library", &c.native_library);
+        load_string(p, "build", &c.native_library_build);
+    });
+
+    for doc->query_all("top() > dependencies > []") {
+        d: Dependency;
+        d.name = it.node;
+
+        version_str := it->value_or_null()->as_str() ?? "";
+        conv.parse_any(&d.version, version_str);
+
+        it.props->get("git")->with([src] {
+            d.source = .{ Git = src->as_str() ?? "" };
+        });
+
+        c.dependencies[d.name] = d;
+    }
+
+    return c;
+
+    load_string :: (p: &kdl.Node, field: str, target: &$T) {
+        p->query(field)->with([n] {
+            n->value_or_null()->as_str()->with([s] {
+                *target = s;
+            });
+        });
+    }
+}
+
+store_config :: (path: str) -> bool {
+    doc := kdl.new_doc();
+
+    package_node := doc->create_node("package");
+    doc.nodes << package_node;
+    {
+        name_node := doc->create_node("name");
+        name_node->add_value(.{ String = config.package_name });
+
+        author_node := doc->create_node("author");
+        author_node->add_value(.{ String = config.package_author });
+
+        url_node := doc->create_node("url");
+        url_node->add_value(.{ String = config.package_url });
+
+        description_node := doc->create_node("description");
+        description_node->add_value(.{ String = config.package_description });
+
+        version_node := doc->create_node("version");
+        version_node->add_value(.{ String = tprintf("{}", config.package_version) });
+
+        array.concat(&package_node.children, .[
+            name_node, author_node, url_node, description_node, version_node
+        ]);
+    }
+
+    config_node := doc->create_node("config");
+    doc.nodes << config_node;
+    {
+        source_path_node := doc->create_node("dependency_source_path");
+        source_path_node->add_value(.{ String = config.dependency_source_path });
+        config_node.children << source_path_node;
+
+        binary_path_node := doc->create_node("dependency_binary_path");
+        binary_path_node->add_value(.{ String = config.dependency_binary_path });
+        config_node.children << binary_path_node;
+    }
+
+    if config.native_library {
+        native_node := doc->create_node("native");
+        doc.nodes << native_node;
+
+        library_node := doc->create_node("library");
+        library_node->add_value(.{ String = config.native_library->unwrap() });
+        native_node.children << library_node;
+
+        build_node := doc->create_node("build");
+        build_node->add_value(.{ String = config.native_library_build->unwrap() });
+        native_node.children << build_node;
+    }
+
+    if !config.dependencies->empty() {
+        dependency_node := doc->create_node("dependencies");
+        doc.nodes << dependency_node;
+        for config.dependencies->as_iter() {
+            dep_node := doc->create_node(it.key);
+            dependency_node.children << dep_node;
+
+            dep_node->add_value(.{ String = tprintf("{}", it.value.version) });
+
+            switch it.value.source {
+                case s: .Git {
+                    dep_node.props["git"] = .{ data = .{ String = s } };
+                }
+
+                case #default ---
+            }
+        }
+    }
+
+    file := os.open(path, .Write)->or_return(false);
+    defer os.close(&file);
+
+    w := io.writer_make(&file);
+    defer io.writer_free(&w);
+
+    kdl.write(&doc, &w);
+
+    return true;
+}
+
+
+//  Old INI config code
+read_old_config :: (path: str) -> ? Config {
+    for os.with_file(path) {
+        r := io.reader_make(it);
+        defer io.reader_free(&r);
+
+        inner_config: IniConfig;
+        result, error := encoding.ini.parse_ini_file(&r, &inner_config);
+
+        if result != .Success {
+            return .{};
+        }
+
+        c: Config;
+
+        c.package_name = inner_config.metadata.name;
+        c.package_description = inner_config.metadata.description;
+        c.package_url = inner_config.metadata.url;
+        c.package_author = inner_config.metadata.author;
+        c.package_version = inner_config.metadata.version;
+
+        c.dependency_binary_path = inner_config.config.lib_bin_directory;
+        c.dependency_source_path = inner_config.config.lib_source_directory;
+
+        if inner_config.native_library.library {
+            c.native_library = inner_config.native_library.library;
+            c.native_library_build = inner_config.native_library.build_cmd;
+        }
+
+        for inner_config.dependencies.dependencies.entries {
+            c.dependencies->put(it.key[string.last_index_of(it.key, '/')+1 .. it.key.length], .{
+                name = it.key,
+                version = it.value,
+                source = .{ Git = it.key }
+            });
+        }
+
+        return c;
+    }
+}
+
+IniConfig :: struct {
     Metadata :: struct {
         name:        str;
         description: str;
@@ -1079,7 +1310,7 @@ Config :: struct {
     dependency_folders: Dependency_Folders;
 }
 
-#local parse_dependencies :: (dependencies: &Config.Dependencies, r: &io.Reader) -> bool {
+#local parse_dependencies :: (dependencies: &IniConfig.Dependencies, r: &io.Reader) -> bool {
     while true {
         r->skip_whitespace();
         if r->is_empty() do return true;
@@ -1098,7 +1329,7 @@ Config :: struct {
     return true;
 }
 
-#local write_dependencies :: (dependencies: &Config.Dependencies, w: &io.Writer) -> bool {
+#local write_dependencies :: (dependencies: &IniConfig.Dependencies, w: &io.Writer) -> bool {
     for& dependencies.dependencies.entries {
         io.write_format(w, "{}={}\n", it.key, it.value);
     }
@@ -1106,7 +1337,7 @@ Config :: struct {
     return true;
 }
 
-#local parse_dependency_folders :: (dependencies: &Config.Dependency_Folders, r: &io.Reader) -> bool {
+#local parse_dependency_folders :: (dependencies: &IniConfig.Dependency_Folders, r: &io.Reader) -> bool {
     while true {
         r->skip_whitespace();
         if r->is_empty() do return true;
@@ -1123,7 +1354,7 @@ Config :: struct {
     return true;
 }
 
-#local write_dependency_folders :: (dependencies: &Config.Dependency_Folders, w: &io.Writer) -> bool {
+#local write_dependency_folders :: (dependencies: &IniConfig.Dependency_Folders, w: &io.Writer) -> bool {
     for& dependencies.folders.entries {
         io.write_format(w, "{}={}\n", it.key, it.value);
     }
@@ -1131,8 +1362,7 @@ Config :: struct {
     return true;
 }
 
-
-load_config_file :: () -> bool {
+load_old_config_file :: () -> bool {
     file_data := os.get_contents(global_arguments.config_file);
     if string.empty(file_data) {
         return false;
@@ -1150,7 +1380,7 @@ load_config_file :: () -> bool {
     return true;
 }
 
-store_config_file :: () -> bool {
+store_old_config_file :: () -> bool {
     for os.with_file(global_arguments.config_file, .Write) {
         writer := io.writer_make(it);
         defer io.writer_free(&writer);