Explorar el Código

Initial commit

Billy Barrow hace 10 meses
commit
2aa0c9dcbb

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+/build
+/build2

+ 55 - 0
MANIFEST.usm

@@ -0,0 +1,55 @@
+{
+  "name": "usm",
+  "version": "0.0.1",
+  "summary": "Universal Source Manifest",
+  "licences": [ {"name": "GPLv3", "category": "libre", "text": "src/LICENSE"} ],
+  "provides": {
+    "bin:usm": "cli/usm",
+    "lib:libusm.so": "lib/libusm.so",
+    "inc:usm.h": "lib/usm.h",
+    "vapi:usm.vapi": "lib/usm.vapi",
+    "libres:girepository-1.0/usm-1.0.typelib": "lib/usm-1.0.typelib",
+    "res:share/gir-1.0/usm-1.0.gir": "lib/usm-1.0.gir",
+    "pc:usm.pc": "meson-private/usm.pc"
+  },
+  "depends": {
+    "runtime": [
+      "bin:ldconfig",
+      "lib:libglib-2.0.so.0",
+      "lib:libgobject-2.0.so.0",
+      "lib:libgio-2.0.so.0",
+      "lib:libinvercargill.so",
+      "lib:libinvercargill-json.so",
+      "lib:libc.so.6",
+      "lib:libpcre2-8.so.0",
+      "lib:libffi.so.8",
+      "lib:libgmodule-2.0.so.0",
+      "lib:libz.so.1",
+      "lib:libmount.so.1",
+      "lib:libselinux.so.1",
+      "lib:libgee-0.8.so.2",
+      "lib:libjson-glib-1.0.so.0",
+      "lib:libblkid.so.1"
+    ],
+    "build": [
+      "bin:valac",
+      "bin:meson",
+      "pc:glib-2.0.pc",
+      "pc:gobject-2.0.pc",
+      "pc:gio-2.0.pc",
+      "pc:gee-0.8.pc",
+      "pc:json-glib-1.0.pc",
+      "pc:invercargill.pc",
+      "pc:invercargill-json.pc",
+      "pc:gobject-introspection-1.0.pc"
+    ],
+    "manage": [
+      "bin:bash"
+    ]
+  },
+  "execs": {
+    "install": "scripts/install",
+    "remove": "scripts/remove",
+    "build": "scripts/build.sh"
+  }
+}

+ 58 - 0
README.md

@@ -0,0 +1,58 @@
+# Universal Source Manifest
+
+## Manifest format (MANIFEST.usm)
+
+```json
+{
+  "name": "my-package",
+  "version": "1.0.5+2",
+  "summary": "A sample package",
+  "licences": [ {"name": "GPLv3", "category": "libre", "text": "src/LICENSE"} ],
+  "provides": {
+    "bin:hello-world": "hello-world",
+    "app:hello-world.desktop": "hello-world.desktop",
+    "res:usr/share/icons/hicolor/scalable/hello-world.svg": "icons/hello-world.svg"
+  },
+  "depends": {
+    "runtime": [
+      "lib:libgtk-4.so.1",
+    ],
+    "build": [
+      "bin:valac",
+      "bin:meson",
+      "inc:gtk-4.0"
+    ],
+    "manage": [
+      "bin:bash"
+    ]
+  },
+  "execs": {
+    "install": "usp-exec/install",
+    "remove": "usp-exec/remove",
+    "build": "usp-exec/build",
+    "rebuild": "usp-exec/rebuild"
+  }
+}
+```
+
+## Optional extended properties
+
+```json
+{
+  "md": "src/DESCRIPTION.md",
+  "url": "https://my.package.com",
+  "screenshots": [ "hello-world-screenshot.png" ],
+  "icon": "hello-world.svg",
+  "git": { "origin": "https://git.my.package.com/hello-world", "commit": "8d9f3b198e3a767d41f3c33fac2fbd8496f23ed2" },
+  "metainfo": "hello-world.appdata.xml",
+  "extras": { "customData": "whatever" }
+}
+```
+
+## Resource types
+
+- "res": Generic resource: file path without leading "/"
+- "bin": Binary executable: file name locatable in `$PATH`
+- "lib": Shared library: file name locatable by LD
+- "app": Desktop entry: file name in /usr/share/applications
+- "inc": Include: file name in /usr/include

+ 11 - 0
scripts/build.sh

@@ -0,0 +1,11 @@
+#!/bin/bash
+set -e
+
+src_dir=$(pwd)
+build_dir=$1
+
+cd ${build_dir}
+
+meson setup ${src_dir}/src --prefix=${PREFIX} --libdir=${LIBDIR} --bindir=${BINDIR} --includedir=${INCLUDEDIR}
+ninja
+

+ 159 - 0
src/cli/Cli.vala

@@ -0,0 +1,159 @@
+Usm.Manifest manifest = null;
+Usm.Paths paths;
+string? build_path = null;
+
+
+public static int main(string[] args) {
+    paths = new Usm.Paths.usm_environ();
+
+    if(args.length < 2) {
+        usage();
+        return 255;
+    }
+
+    var verb = args[1];
+    var cwd = Environment.get_current_dir();
+
+    if(verb != "remove") {
+        if(args.length < 3) {
+            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<Invercargill.Properties>());
+    }
+    catch (Error e) {
+        printerr(@"Could not read MANIFEST.usm: $(e.message)\n");
+        return 253;
+    }
+
+    if(verb == "build") {
+        return build();
+    }
+
+    if(verb == "install") {
+        return install();
+    }
+
+
+    usage();
+    return 255;
+}
+
+private void usage() {
+    printerr("USAGE:\n\tusm build <build path>\n\tusm rebuild <build path>\n\tusm install <build path>\n\tusm remove\n");
+}
+
+
+private int build() {
+    var missing_management_deps = manifest.dependencies.manage.where(d => !d.is_satisfied());
+    var missing_build_deps = manifest.dependencies.build.where(d => !d.is_satisfied());
+
+    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(!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() {
+    var missing_management_deps = manifest.dependencies.manage.where(d => !d.is_satisfied());
+    var missing_build_deps = manifest.dependencies.build.where(d => !d.is_satisfied());
+    var missing_runtime_deps = manifest.dependencies.runtime.where(d => !d.is_satisfied());
+
+    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_build_deps) {
+            printerr(@"Missing runtime dependency \"$item\".\n");
+        }
+    }
+
+    if(!sane) {
+        printerr("Could not install 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;
+    }
+
+    foreach (var resource in manifest.provides) {
+        var path = paths.get_suggested_path(resource.key);
+        printerr(@"Installing $(resource.key) to $path...");
+        try {
+            var src = File.new_build_filename(build_path, resource.value);
+            var dest = File.new_for_path(path);
+            src.copy(dest, FileCopyFlags.OVERWRITE);
+        }
+        catch(Error e) {
+            printerr(@" $(e.message)\n");
+            return 249;
+        }
+        printerr(" done\n");
+    }
+
+    try {
+        var proc = manifest.run_install(build_path, Usm.InstallType.FRESH, SubprocessFlags.INHERIT_FDS);
+        if(proc != null) {
+            proc.wait_check();
+        }
+    }
+    catch(Error e) {
+        printerr(@"Error running install exec: $(e.message)\n");
+        return 250;
+    }
+
+    return 0;
+}
+

+ 7 - 0
src/cli/meson.build

@@ -0,0 +1,7 @@
+
+sources = files('Cli.vala')
+
+deps = dependencies
+deps += usm_dep
+
+executable('usm', sources, dependencies: deps, install: true)

+ 18 - 0
src/lib/Dependencies.vala

@@ -0,0 +1,18 @@
+using Invercargill;
+namespace Usm {
+
+    public class Dependencies {
+        public Set<ResourceRef> runtime { get; set; }
+        public Set<ResourceRef> build { get; set; }
+        public Set<ResourceRef> manage { get; set; }
+
+        public static PropertyMapper<Dependencies> get_mapper() {
+            return new PropertyMapperBuilder<Dependencies>()
+                .map_many<string>("runtime", o => o.runtime.select<string>(i => i.to_string()), (o, v) => o.runtime = v.select<ResourceRef>(i => new ResourceRef(i)).to_set())
+                .map_many<string>("build", o => o.build.select<string>(i => i.to_string()), (o, v) => o.build = v.select<ResourceRef>(i => new ResourceRef(i)).to_set())
+                .map_many<string>("manage", o => o.manage.select<string>(i => i.to_string()), (o, v) => o.manage = v.select<ResourceRef>(i => new ResourceRef(i)).to_set())
+                .set_constructor(() => new Dependencies())
+                .build();
+        }
+    }
+}

+ 21 - 0
src/lib/Exectuables.vala

@@ -0,0 +1,21 @@
+using Invercargill;
+namespace Usm {
+
+    public class Executables {
+        public string build { get; set; }
+        public string install { get; set; }
+        public string remove { get; set; }
+        public string? rebuild { get; set; }
+
+        public static PropertyMapper<Executables> get_mapper() {
+            return new PropertyMapperBuilder<Executables>()
+                .map<string>("build", o => o.build, (o, v) => o.build = v)
+                .map<string?>("install", o => o.install, (o, v) => o.remove = v)
+                .map<string?>("remove", o => o.remove, (o, v) => o.remove = v)
+                .map<string?>("rebuild", o => o.rebuild, (o, v) => o.rebuild = v, false)
+                .set_constructor(() => new Executables())
+                .build();
+        }
+    }
+
+}

+ 16 - 0
src/lib/Git.vala

@@ -0,0 +1,16 @@
+using Invercargill;
+namespace Usm {
+
+    public class Git {
+        public string origin { get; set; }
+        public string commit { get; set; }
+
+        public static PropertyMapper<Git> get_mapper() {
+            return new PropertyMapperBuilder<Git>()
+                .map<string>("origin", o => o.origin, (o, v) => o.origin = v)
+                .map<string>("commit", o => o.commit, (o, v) => o.commit = v)
+                .set_constructor(() => new Git())
+                .build();
+        }
+    }
+}

+ 55 - 0
src/lib/Licence.vala

@@ -0,0 +1,55 @@
+using Invercargill;
+namespace Usm {
+
+    public enum LicenceCategory {
+        PROPRIETARY = 0,
+        SOURCE_AVAILABLE = 1,
+        OPEN_SOURCE = 2,
+        LIBRE = 3;
+
+        public static LicenceCategory from_string(string str) throws ManifestError {
+            switch (str) {
+                case "libre":
+                    return LicenceCategory.LIBRE;
+                case "open-source":
+                    return LicenceCategory.OPEN_SOURCE;
+                case "source-available":
+                    return LicenceCategory.SOURCE_AVAILABLE;
+                case "proprietary":
+                    return LicenceCategory.PROPRIETARY;
+                default:
+                    throw new ManifestError.INVALID_LICENCE_CATEGORY(@"Unknown licence category \"$str\".");
+            }
+        }
+
+        public string to_string() {
+            switch (this) {
+                case LicenceCategory.LIBRE:
+                    return "libre";
+                case LicenceCategory.OPEN_SOURCE:
+                    return "open-source";
+                case LicenceCategory.SOURCE_AVAILABLE:
+                    return "source-available";
+                case LicenceCategory.PROPRIETARY:
+                    return "proprietary";
+                default:
+                    assert_not_reached();
+            }
+        }
+    }
+
+    public class Licence {
+        public string name { get; set; }
+        public LicenceCategory category { get; set; }
+        public string text_path { get; set; }
+
+        public static PropertyMapper<Licence> get_mapper() {
+            return new PropertyMapperBuilder<Licence>()
+                .map<string>("name", o => o.name, (o, v) => o.name = v)
+                .map<string>("category", o => o.category.to_string(), (o, v) => o.category = LicenceCategory.from_string(v))
+                .map<string>("text", o => o.text_path, (o, v) => o.text_path = v)
+                .set_constructor(() => new Licence())
+                .build();
+        }
+    }
+}

+ 145 - 0
src/lib/Manifest.vala

@@ -0,0 +1,145 @@
+using Invercargill;
+
+namespace Usm {
+
+    public errordomain ManifestError {
+        MISSING_FIELD,
+        INVALID_VERSION,
+        INVALID_LICENCE_CATEGORY,
+        INVALID_RESOURCE_TYPE,
+    }
+
+    public class Manifest {
+        public string name { get; set; }
+        public string summary { get; set; }
+        public Version version { get; set; }
+        public Vector<Licence> licences { get; set; }
+        public Dictionary<ResourceRef, string> provides { get; set; }
+        public Dependencies dependencies { get; set; }
+        public Executables executables { get; set; }
+
+        public string? markdown_path { get; set; }
+        public string? url { get; set; }
+        public Vector<string>? screenshot_paths { get; set; }
+        public string? icon_path { get; set; }
+        public string? metainfo_path { get; set; }
+        public Git? git { get; set; }
+        public Properties? extra_properties { get; set; }
+
+        public static PropertyMapper<Manifest> get_mapper() {
+            return new PropertyMapperBuilder<Manifest>()
+                .map<string>("name", o => o.name, (o, v) => o.name = v)
+                .map<string>("version", o => o.version.to_string(), (o, v) => o.version = new Version.from_string(v))
+                .map<string>("summary", o => o.summary, (o, v) => o.summary = v)
+                .map_many_with<Licence>("licences", o => o.licences, (o, v) => o.licences = v.to_vector(), Licence.get_mapper())
+                .map<Properties>("provides", o => o.map_from_provides_dict(), (o, v) => o.build_provides_dict(v))
+                .map_with<Dependencies>("depends", o => o.dependencies, (o, v) => o.dependencies = v, Dependencies.get_mapper())
+                .map_with<Executables>("execs", o => o.executables, (o, v) => o.executables = v, Executables.get_mapper())
+                .map<string>("md", o => o.markdown_path, (o, v) => o.markdown_path = v, false)
+                .map<string>("url", o => o.url, (o, v) => o.url = v, false)
+                .map_many<string>("screenshots", o => o.screenshot_paths, (o, v) => o.screenshot_paths = v.to_vector(), false)
+                .map<string>("icon", o => o.icon_path, (o, v) => o.icon_path = v, false)
+                .map<string>("metainfo", o => o.metainfo_path, (o, v) => o.metainfo_path = v, false)
+                .map<Properties>("extras", o => o.extra_properties, (o, v) => o.extra_properties = v, false)
+                .set_constructor(() => new Manifest())
+                .build();
+        }
+
+        private void build_provides_dict(Properties obj) throws ManifestError, ElementError {
+            provides = new Dictionary<ResourceRef, string>();
+            foreach (var pair in obj) {
+                provides[new ResourceRef(pair.key)] = pair.value.as<string>();
+            }
+        }
+
+        private Properties map_from_provides_dict() {
+            var dict = new PropertiesDictionary();
+            foreach (var pair in provides) {
+                try {
+                    dict.set_native<string>(pair.key.to_string(), pair.value);
+                }
+                catch(Error e) {
+                    assert_not_reached();
+                }
+            }
+            return dict;
+        }
+
+        
+        public Subprocess run_build(string build_path, Paths paths, SubprocessFlags flags) throws Error {
+            var path = Path.build_filename(Environment.get_current_dir(), executables.build);
+            paths.set_envs();
+            var proc = new Subprocess.newv(new string[] { path, build_path }, flags);
+            return proc;
+        }
+
+        public Subprocess? run_rebuild(string build_path, SubprocessFlags flags) throws Error {
+            if(executables.rebuild == null) {
+                return null;
+            }
+            var path = Path.build_filename(Environment.get_current_dir(), executables.rebuild);
+            var proc = new Subprocess.newv(new string[] { path, build_path }, flags);
+            return proc;
+        }
+
+        public Subprocess? run_install(string build_path, InstallType type, SubprocessFlags flags) throws Error {
+            if(executables.install == null) {
+                return null;
+            }
+            var path = Path.build_filename(Environment.get_current_dir(), executables.install);
+            var proc = new Subprocess.newv(new string[] { path, build_path, type.to_string() }, flags);
+            return proc;
+        }
+
+        public Subprocess? run_remove(string build_path, RemoveType type, SubprocessFlags flags) throws Error {
+            if(executables.remove == null) {
+                return null;
+            }
+            var path = Path.build_filename(Environment.get_current_dir(), executables.remove);
+            var proc = new Subprocess.newv(new string[] { path, build_path, type.to_string() }, flags);
+            return proc;
+        }
+
+        
+
+    }
+
+
+    public enum InstallType {
+        FRESH,
+        UPGRADE,
+        DOWNGRADE;
+
+        public string to_string() {
+            switch (this) {
+                case InstallType.FRESH:
+                    return "fresh";
+                case InstallType.UPGRADE:
+                    return "upgrade";
+                case InstallType.DOWNGRADE:
+                    return "downgrade";
+                default:
+                    assert_not_reached();
+            }
+        }
+    }
+
+    public enum RemoveType {
+        FINAL,
+        UPGRADE,
+        DOWNGRADE;
+
+        public string to_string() {
+            switch (this) {
+                case RemoveType.FINAL:
+                    return "final";
+                case RemoveType.UPGRADE:
+                    return "upgrade";
+                case RemoveType.DOWNGRADE:
+                    return "downgrade";
+                default:
+                    assert_not_reached();
+            }
+        }
+    }
+}

+ 103 - 0
src/lib/Paths.vala

@@ -0,0 +1,103 @@
+namespace Usm {
+
+    public class Paths {
+
+        public string prefix { get; set; }
+        public string bin { get; set; }
+        public string include { get; set; }
+        public string data { get; set; }
+        public string info { get; set; }
+        public string lib { get; set; }
+        public string man { get; set; }
+        public string libexec { get; set; }
+        public string locale { get; set; }
+        public string local_state { get; set;}
+        public string sbin { get; set; }
+        public string shared_state { get; set; }
+        public string sys_config { get; set; }
+
+        public void set_envs() {
+            Environment.set_variable("PREFIX", prefix, true);
+            Environment.set_variable("BINDIR", bin, true);
+            Environment.set_variable("INCLUDEDIR", include, true);
+            Environment.set_variable("DATADIR", data, true);
+            Environment.set_variable("INFODIR", info, true);
+            Environment.set_variable("LIBDIR", lib, true);
+            Environment.set_variable("MANDIR", man, true);
+            Environment.set_variable("LIBEXECDIR", libexec, true);
+            Environment.set_variable("LOCALEDIR", locale, true);
+            Environment.set_variable("LOCALSTATEDIR", local_state, true);
+            Environment.set_variable("SBINDIR", sbin, true);
+            Environment.set_variable("SHAREDSTATEDIR", shared_state, true);
+            Environment.set_variable("SYSCONFIGDIR", sys_config, true);
+        }
+
+        public string get_suggested_path(ResourceRef resource) {
+            switch (resource.resource_type) {
+                case Usm.ResourceType.RESOURCE:
+                    return resource.resource;
+                case Usm.ResourceType.BINARY:
+                    return Path.build_filename(prefix, bin, resource.resource);
+                case Usm.ResourceType.SUPER_BINARY:
+                    return Path.build_filename(prefix, sbin, resource.resource);
+                case Usm.ResourceType.LIBRARY:
+                    return Path.build_filename(prefix, lib, resource.resource);
+                case Usm.ResourceType.LIBRARY_EXECUTABLE:
+                    return Path.build_filename(prefix, libexec, resource.resource);
+                case Usm.ResourceType.LIBRARY_RESOURCE:
+                    return Path.build_filename(prefix, lib, resource.resource);
+                case Usm.ResourceType.INFO_PAGE:
+                    return Path.build_filename(prefix, info, resource.resource);
+                case Usm.ResourceType.MANUAL_PAGE:
+                    return Path.build_filename(prefix, man, resource.resource);
+                case Usm.ResourceType.LOCALE:
+                    return Path.build_filename(prefix, locale, resource.resource);
+                case Usm.ResourceType.APPLICATION:
+                    return Path.build_filename(prefix, "share", "applications", resource.resource);
+                case Usm.ResourceType.INCLUDE:
+                    return Path.build_filename(prefix, include, resource.resource);
+                case Usm.ResourceType.PKG_CONFIG:
+                    return Path.build_filename(prefix, lib, "pkgconfig", resource.resource);
+                case Usm.ResourceType.VALA_API:
+                    return Path.build_filename(prefix, "share", "vala", "vapi", resource.resource);
+                default:
+                    assert_not_reached();
+            }
+        }
+
+        public Paths.defaults() {
+            prefix = "/usr";
+            bin = "bin";
+            include = "include";
+            data = "share";
+            info = "share/info";
+            lib = sizeof(void*) == 8 ? "lib64" : "lib";
+            man = "share/man";
+            libexec = "libexec";
+            locale = "share/locale";
+            local_state = "var";
+            sbin = "sbin";
+            shared_state = "com";
+            sys_config = "etc";
+        }
+
+        public Paths.usm_environ() {
+            var defaults = new Paths.defaults();
+            
+            prefix = Environment.get_variable("USM_PREFIX") ?? defaults.prefix;
+            bin = Environment.get_variable("USM_BINDIR") ?? defaults.bin;
+            include = Environment.get_variable("USM_INCLUDEDIR") ?? defaults.include;
+            data = Environment.get_variable("USM_DATADIR") ?? defaults.data;
+            info = Environment.get_variable("USM_INFODIR") ?? defaults.info;
+            lib = Environment.get_variable("USM_LIBDIR") ?? defaults.lib;
+            man = Environment.get_variable("USM_MANDIR") ?? defaults.man;
+            libexec = Environment.get_variable("USM_LIBEXECDIR") ?? defaults.libexec;
+            locale = Environment.get_variable("USM_LOCALEDIR") ?? defaults.locale;
+            local_state = Environment.get_variable("USM_LOCALSTATEDIR") ?? defaults.local_state;
+            sbin = Environment.get_variable("USM_SBINDIR") ?? defaults.sbin;
+            shared_state = Environment.get_variable("USM_SHAREDSTATEDIR") ?? defaults.shared_state;
+            sys_config = Environment.get_variable("USM_SYSCONFIGDIR") ?? defaults.sys_config;
+        }
+    }
+
+}

+ 257 - 0
src/lib/ResourceRef.vala

@@ -0,0 +1,257 @@
+using Invercargill;
+using Invercargill.Convert;
+namespace Usm {
+
+    public enum ResourceType {
+        RESOURCE,
+        BINARY,
+        SUPER_BINARY,
+        LIBRARY,
+        LIBRARY_EXECUTABLE,
+        LIBRARY_RESOURCE,
+        INFO_PAGE,
+        MANUAL_PAGE,
+        LOCALE,
+        APPLICATION,
+        INCLUDE,
+        PKG_CONFIG,
+        VALA_API;
+
+        public string to_string() {
+            switch (this) {
+                case ResourceType.RESOURCE:
+                    return "res";
+                case ResourceType.BINARY:
+                    return "bin";
+                case ResourceType.SUPER_BINARY:
+                    return "sbin";
+                case ResourceType.LIBRARY:
+                    return "lib";
+                case ResourceType.LIBRARY_EXECUTABLE:
+                    return "libexec";
+                case ResourceType.LIBRARY_RESOURCE:
+                    return "libres";
+                case ResourceType.INFO_PAGE:
+                    return "info";
+                case ResourceType.MANUAL_PAGE:
+                    return "man";
+                case ResourceType.LOCALE:
+                    return "locale";
+                case ResourceType.APPLICATION:
+                    return "app";
+                case ResourceType.INCLUDE:
+                    return "inc";
+                case ResourceType.PKG_CONFIG:
+                    return "pc";
+                case ResourceType.VALA_API:
+                    return "vapi";
+                default:
+                    assert_not_reached();
+            }
+        }
+
+        public static ResourceType from_string(string str) throws ManifestError {
+            switch (str) {
+                case "res":
+                    return ResourceType.RESOURCE;
+                case "bin":
+                    return ResourceType.BINARY;
+                case "sbin":
+                    return ResourceType.SUPER_BINARY;
+                case "lib":
+                    return ResourceType.LIBRARY;
+                case "libexec":
+                    return ResourceType.LIBRARY_EXECUTABLE;
+                case "libres":
+                    return ResourceType.LIBRARY_RESOURCE;
+                case "info":
+                    return ResourceType.INFO_PAGE;
+                case "man":
+                    return ResourceType.MANUAL_PAGE;
+                case "locale":
+                    return ResourceType.LOCALE;
+                case "app":
+                    return ResourceType.APPLICATION;
+                case "inc":
+                    return ResourceType.INCLUDE;
+                case "pc":
+                    return ResourceType.PKG_CONFIG;
+                case "vapi":
+                    return ResourceType.VALA_API;
+                default:
+                    throw new ManifestError.INVALID_RESOURCE_TYPE(@"Unknown resource type \"$str\".");
+            }
+        }
+    }
+
+    public class ResourceRef : Hashable, Equatable<ResourceRef> {
+        public string resource { get; set; }
+        public ResourceType resource_type { get; set; }
+
+        public ResourceRef(string str) throws ManifestError {
+            var parts = str.split(":", 2);
+            resource_type = ResourceType.from_string(parts[0]);
+            resource = parts[1];
+        }
+        
+        public uint hash_code() {
+            return to_string().hash();
+        }
+        public bool equals(ResourceRef other) {
+            return this == other || (this.resource == other.resource && this.resource_type == other.resource_type);
+        }
+        public string to_string() {
+            return @"$resource_type:$resource";
+        }
+        
+        public bool is_satisfied() {
+            switch (resource_type) {
+                case ResourceType.RESOURCE:
+                    return check_res();
+                case ResourceType.BINARY:
+                    return check_bin();
+                case ResourceType.SUPER_BINARY:
+                    return check_sbin();
+                case ResourceType.LIBRARY:
+                    return check_lib();
+                case ResourceType.LIBRARY_EXECUTABLE:
+                    return check_libexec();
+                case ResourceType.LIBRARY_RESOURCE:
+                    return check_libres();
+                case ResourceType.INFO_PAGE:
+                    return check_info();
+                case ResourceType.MANUAL_PAGE:
+                    return check_man();
+                case ResourceType.LOCALE:
+                    return check_locale();
+                case ResourceType.APPLICATION:
+                    return check_app();
+                case ResourceType.INCLUDE:
+                    return check_inc();
+                case ResourceType.PKG_CONFIG:
+                    return check_pc();
+                case ResourceType.VALA_API:
+                    return check_vapi();
+                default:
+                    assert_not_reached();
+            }
+        }
+
+        private bool check_lib() {
+            try {
+                var proc = new Subprocess.newv(new string[] { "ldconfig", "-p" }, SubprocessFlags.STDOUT_PIPE);
+                var pipe = new DataInputStream(proc.get_stdout_pipe());
+                string line = null;
+                while((line = pipe.read_line()) != null) {
+                    if(line.has_prefix("\t")) {
+                        var name = line.substring(1).split(" ", 2)[0];
+                        if(resource == name) {
+                            return true;
+                        }
+                    }
+                }
+                proc.wait_check();
+            }
+            catch(Error e) {
+                warning(@"Error checking for resource $this: $(e.message)");
+            }
+            return false;
+        }
+
+        private bool check_bin() {
+            var paths = Environment.get_variable("PATH").split(":");
+            foreach (var path in paths) {
+                var file = File.new_build_filename(path, resource);
+                if(file.query_exists()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private bool check_res() {
+            var file = File.new_build_filename("/", resource);
+            return file.query_exists();
+        }
+
+        private bool check_libres() {
+            var paths = new string[] { "/usr/lib", "/usr/lib64", "/lib", "/lib64" };
+            foreach (var path in paths) {
+                var file = File.new_build_filename(path, resource);
+                if(file.query_exists()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private bool check_app() {
+            var file = File.new_build_filename("/usr/share/applications", resource);
+            return file.query_exists();
+        }
+
+        private bool check_inc() {
+            var file = File.new_build_filename("/usr/include", resource);
+            return file.query_exists();
+        }
+
+        private bool check_sbin() {
+            var paths = new string[] { "/usr/sbin", "/sbin" };
+            foreach (var path in paths) {
+                var file = File.new_build_filename(path, resource);
+                if(file.query_exists()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private bool check_libexec() {
+            var file = File.new_build_filename("/usr/libexec", resource);
+            return file.query_exists();
+        }
+
+        private bool check_info() {
+            var file = File.new_build_filename("/usr/share/info", resource);
+            return file.query_exists();
+        }
+
+        private bool check_man() {
+            var file = File.new_build_filename("/usr/share/man", resource);
+            return file.query_exists();
+        }
+
+        private bool check_locale() {
+            var file = File.new_build_filename("/usr/share/locale", resource);
+            return file.query_exists();
+        }
+
+        private bool check_vapi() {
+            var paths = new string[] { "/usr/share/vala/vapi", "/usr/share/vala-0.56/vapi" };
+            foreach (var path in paths) {
+                var file = File.new_build_filename(path, resource);
+                if(file.query_exists()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private bool check_pc() {
+            var paths = ate(new string[] { "/usr/lib/pkgconfig", "/usr/lib64/pkgconfig", "/usr/share/pkgconfig" });
+            var env = Environment.get_variable("PKG_CONFIG_PATH");
+            if(env != null) {
+                paths = paths.concat(ate(env.split(":")));
+            }
+
+            foreach (var path in paths) {
+                var file = File.new_build_filename(path, resource);
+                if(file.query_exists()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+}

+ 120 - 0
src/lib/Version.vala

@@ -0,0 +1,120 @@
+namespace Usm {
+
+    public class Version {
+        public int[] version_nums { get; set; }
+        public int? release { get; set; }
+
+        public Version.from_string(string ver) throws ManifestError {
+            var ver_rel = ver.chomp().chug().split("+", 2);
+            if(ver_rel.length == 2) {
+                var release = 0;
+                if(!int.try_parse(ver_rel[1], out release)) {
+                    throw new ManifestError.INVALID_VERSION("Could not parse release number");
+                }
+                this.release = release;
+            }
+
+
+            if(ver_rel[0].length == 0) {
+                if(this.release == null) {
+                    throw new ManifestError.INVALID_VERSION("Version string must have a version number or release number at minimum");
+                }
+                return;
+            }
+
+            var version_strs = ver_rel[0].split(".");
+            version_nums = new int[version_strs.length];
+            for(int i = 0; i < version_nums.length; i++) {
+                var num = 0;
+                if(!int.try_parse(version_strs[i], out num)) {
+                    throw new ManifestError.INVALID_VERSION(@"Could not parse version number part $i");
+                }
+                version_nums[i] = num;
+            }
+        }
+
+        public string to_string() {
+            var str = "";
+            bool first = true;
+            foreach (var num in version_nums) {
+                if(first) {
+                    first = false;
+                }
+                else {
+                    str += ".";
+                }
+                str += num.to_string();
+            }
+            if(release != null) {
+                str += @"+$release";
+            }
+            return str;
+        }
+
+        public bool equals(Version other) {
+            var parts = int.min(other.version_nums.length, version_nums.length);
+            for(int i = 0; i < parts; i++) {
+                if(version_nums[i] != other.version_nums[i]) {
+                    return false;
+                }
+            }
+            return release == other.release;
+        }
+
+        public bool greater_than(Version other) {
+            var parts = int.min(other.version_nums.length, version_nums.length);
+            for(int i = 0; i < parts; i++) {
+                if(version_nums[i] > other.version_nums[i]) {
+                    return true;
+                }
+                if(version_nums[i] < other.version_nums[i]) {
+                    return false;
+                }
+            }
+            // So far, equal
+            if(release == null && other.release == null) {
+                return false;
+            }
+            if(release == null && other.release != null) {
+                return false;
+            }
+            if(release != null && other.release == null) {
+                return true;
+            }
+            return release > other.release;
+        }
+
+        public bool less_than(Version other) {
+            var parts = int.min(other.version_nums.length, version_nums.length);
+            for(int i = 0; i < parts; i++) {
+                if(version_nums[i] < other.version_nums[i]) {
+                    return true;
+                }
+                if(version_nums[i] > other.version_nums[i]) {
+                    return false;
+                }
+            }
+            // So far, equal
+            if(release == null && other.release == null) {
+                return false;
+            }
+            if(release == null && other.release != null) {
+                return true;
+            }
+            if(release != null && other.release == null) {
+                return false;
+            }
+            return release < other.release;
+        }
+
+        public int compare(Version other) {
+            if(less_than(other)) {
+                return -1;
+            }
+            if(greater_than(other)) {
+                return 1;
+            }
+            return 0;
+        }
+    }
+}

+ 42 - 0
src/lib/meson.build

@@ -0,0 +1,42 @@
+
+add_project_arguments(['--debug', '--disable-warnings', '--enable-checking','--vapidir', vapi_dir], language: 'vala')
+
+sources = files('Manifest.vala')
+sources += files('Dependencies.vala')
+sources += files('Exectuables.vala')
+sources += files('Git.vala')
+sources += files('Licence.vala')
+sources += files('Paths.vala')
+sources += files('ResourceRef.vala')
+sources += files('Version.vala')
+
+
+dependencies = [
+    dependency('glib-2.0'),
+    dependency('gobject-2.0'),
+    dependency('gio-2.0'),
+    dependency('gee-0.8'),
+    dependency('json-glib-1.0'),
+    dependency('invercargill'),
+    dependency('invercargill-json'),
+]
+
+usm = shared_library('usm', sources,
+    dependencies: dependencies,
+    install: true,
+    vala_gir: 'usm-1.0.gir',
+    install_dir: [true, true, true, true]
+)
+usm_dep = declare_dependency(link_with: usm, include_directories: include_directories('.'))
+
+pkg = import('pkgconfig')
+pkg.generate(usm,
+    version : '0.1',
+    name : 'usm',)
+    
+g_ir_compiler = find_program('g-ir-compiler')
+custom_target('usm typelib', command: [g_ir_compiler, '--shared-library=libusm.so', '--output', '@OUTPUT@', meson.current_build_dir() / 'usm-1.0.gir'],
+              output: 'usm-1.0.typelib',
+              depends: usm,
+              install: true,
+              install_dir: get_option('libdir') / 'girepository-1.0')

+ 5 - 0
src/meson.build

@@ -0,0 +1,5 @@
+project('Universal Source Manifest', 'vala', 'c')
+vapi_dir = meson.current_source_dir() / 'vapi'
+
+subdir('lib')
+subdir('cli')