summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2013-01-09 18:23:54 +0100
committerPaolo Bonzini <pbonzini@redhat.com>2013-01-09 18:23:54 +0100
commit0523a90f210bd443c64b92119de0ea30ae8bcaa0 (patch)
tree63fbe40850f951aa83c729f94e57df4f728f491a /tools
parenta53f42abfbf6d3c0eb05c7cb7210dd537e55194a (diff)
downloadmsitools-0523a90f210bd443c64b92119de0ea30ae8bcaa0.tar.gz
msitools-0523a90f210bd443c64b92119de0ea30ae8bcaa0.tar.xz
msitools-0523a90f210bd443c64b92119de0ea30ae8bcaa0.zip
move wixl source to tools/wixl/
Fixes in-tree build.
Diffstat (limited to 'tools')
-rw-r--r--tools/wixl/builder.vala590
-rw-r--r--tools/wixl/msi.vala642
-rw-r--r--tools/wixl/preprocessor.vala131
-rw-r--r--tools/wixl/util.vala139
-rw-r--r--tools/wixl/wix.vala598
-rw-r--r--tools/wixl/wixl.vala92
6 files changed, 2192 insertions, 0 deletions
diff --git a/tools/wixl/builder.vala b/tools/wixl/builder.vala
new file mode 100644
index 0000000..80c2ff6
--- /dev/null
+++ b/tools/wixl/builder.vala
@@ -0,0 +1,590 @@
+namespace Wixl {
+
+ class WixBuilder: WixNodeVisitor {
+
+ public WixBuilder () {
+ add_path (".");
+ }
+
+ WixRoot root;
+ MsiDatabase db;
+ HashTable<string, string> variables;
+
+ construct {
+ variables = new HashTable<string, string> (str_hash, str_equal);
+ }
+
+ public void define_variable (string name, string value) {
+ variables.insert (name, value);
+ }
+
+ List<File> path;
+ public void add_path (string p) {
+ var file = File.new_for_path (p);
+ path.append (file);
+ }
+
+ List<WixRoot> roots;
+ public void load_doc (Xml.Doc doc) throws GLib.Error {
+ for (var child = doc.children; child != null; child = child->next) {
+ switch (child->type) {
+ case Xml.ElementType.ELEMENT_NODE:
+ if (child->name != "Wix")
+ warning ("unhandled node %s", child->name);
+ var root = new WixRoot ();
+ root.load (child);
+ roots.append (root);
+ break;
+ }
+ }
+ }
+
+ public void load_file (File file, bool preproc_only = false) throws GLib.Error {
+ string data;
+ FileUtils.get_contents (file.get_path (), out data);
+
+ var p = new Preprocessor (variables);
+ var doc = p.preprocess (data, file);
+ if (preproc_only) {
+ doc.dump_format (FileStream.fdopen (1, "w"));
+ return;
+ }
+
+ load_doc (doc);
+ }
+
+ public G? find_element<G> (string Id) {
+ foreach (var r in roots) {
+ var e = r.find_element<G> (Id);
+ if (e != null)
+ return e;
+ }
+
+ return null;
+ }
+
+ public G[] get_elements<G> () {
+ G[] elems = {};
+ foreach (var r in roots)
+ elems = r.add_elements<G> (elems);
+
+ return elems;
+ }
+
+ delegate void AddSequence (string action, int sequence) throws GLib.Error;
+
+ private void sequence_actions () throws GLib.Error {
+ MsiTableSequence? table = null;
+ AddSequence add = (action, sequence) => {
+ var seq = table.get_action (action);
+ seq.sequence = sequence;
+ };
+
+ // AdminExecuteSequence
+ table = db.table_admin_execute_sequence;
+ add ("CostInitialize", 800);
+ add ("FileCost", 900);
+ add ("CostFinalize", 1000);
+ add ("InstallValidate", 1400);
+ add ("InstallInitialize", 1500);
+ add ("InstallAdminPackage", 3900);
+ add ("InstallFiles", 4000);
+ add ("InstallFinalize", 6600);
+ table.add_sorted_actions ();
+
+ // AdminUISequence
+ table = db.table_admin_ui_sequence;
+ add ("CostInitialize", 800);
+ add ("FileCost", 900);
+ add ("CostFinalize", 1000);
+ add ("ExecuteAction", 1300);
+ table.add_sorted_actions ();
+
+ table = db.table_advt_execute_sequence;
+ add ("CostInitialize", 800);
+ add ("CostFinalize", 1000);
+ add ("InstallValidate", 1400);
+ add ("InstallInitialize", 1500);
+ if (db.table_shortcut.records.length () > 0)
+ add ("CreateShortcuts", 4500);
+ add ("PublishFeatures", 6300);
+ add ("PublishProduct", 6400);
+ add ("InstallFinalize", 6600);
+ table.add_sorted_actions ();
+
+ // InstallExecuteSequence
+ table = db.table_install_execute_sequence;
+ if (db.table_upgrade.records.length () > 0)
+ add ("FindRelatedProducts", 25);
+ if (db.table_launch_condition.records.length () > 0)
+ add ("LaunchConditions", 100);
+ add ("ValidateProductID", 700);
+ add ("CostInitialize", 800);
+ add ("FileCost", 900);
+ add ("CostFinalize", 1000);
+ add ("InstallValidate", 1400);
+ add ("InstallInitialize", 1500);
+ add ("ProcessComponents", 1600);
+ add ("UnpublishFeatures", 1800);
+ if (db.table_registry.records.length () > 0)
+ add ("RemoveRegistryValues", 2600);
+ if (db.table_shortcut.records.length () > 0)
+ add ("RemoveShortcuts", 3200);
+ if (db.table_file.records.length () > 0 ||
+ db.table_remove_file.records.length () > 0)
+ add ("RemoveFiles", 3500);
+ if (db.table_file.records.length () > 0)
+ add ("InstallFiles", 4000);
+ if (db.table_shortcut.records.length () > 0)
+ add ("CreateShortcuts", 4500);
+ if (db.table_registry.records.length () > 0)
+ add ("WriteRegistryValues", 5000);
+ add ("RegisterUser", 6000);
+ add ("RegisterProduct", 6100);
+ add ("PublishFeatures", 6300);
+ add ("PublishProduct", 6400);
+ add ("InstallFinalize", 6600);
+ table.add_sorted_actions ();
+
+ table = db.table_install_ui_sequence;
+ if (db.table_upgrade.records.length () > 0)
+ add ("FindRelatedProducts", 25);
+ if (db.table_launch_condition.records.length () > 0)
+ add ("LaunchConditions", 100);
+ add ("ValidateProductID", 700);
+ add ("CostInitialize", 800);
+ add ("FileCost", 900);
+ add ("CostFinalize", 1000);
+ add ("ExecuteAction", 1300);
+ table.add_sorted_actions ();
+ }
+
+ private void build_cabinet () throws GLib.Error {
+ var sequence = 0;
+ var medias = get_elements<WixMedia> ();
+ var files = get_elements<WixFile> ();
+
+ foreach (var m in medias) {
+ var folder = new GCab.Folder (GCab.Compression.MSZIP);
+
+ foreach (var f in files) {
+ if (f.DiskId != m.Id)
+ continue;
+
+ folder.add_file (new GCab.File.with_file (f.Id, f.file), false);
+ var rec = f.record;
+ sequence += 1;
+ MsiTableFile.set_sequence (rec, sequence);
+ }
+
+ var cab = new GCab.Cabinet ();
+ cab.add_folder (folder);
+ var output = new MemoryOutputStream (null, realloc, free);
+ cab.write (output, null, null, null);
+ var input = new MemoryInputStream.from_data (output.get_data ()[0:output.data_size], null);
+ if (parse_yesno (m.EmbedCab))
+ db.table_streams.add (m.Cabinet, input, output.data_size);
+
+ db.table_media.set_last_sequence (m.record, sequence);
+ }
+ }
+
+ private void shortcut_target () throws GLib.Error {
+ var shortcuts = get_elements<WixShortcut> ();
+
+ foreach (var sc in shortcuts) {
+ var component = sc.get_component ();
+ var feature = component.in_feature.first ().data;
+ MsiTableShortcut.set_target (sc.record, feature.Id);
+ }
+ }
+
+ string[] secureProperties;
+
+ public void property_update () throws GLib.Error {
+ if (secureProperties.length != 0) {
+ var prop = string.joinv (";", secureProperties);
+ db.table_property.add ("SecureCustomProperties", prop);
+ }
+ }
+
+ public MsiDatabase build () throws GLib.Error {
+ db = new MsiDatabase ();
+
+ foreach (var r in roots) {
+ root = r;
+ root.accept (this);
+ }
+ root = null;
+
+ property_update ();
+ shortcut_target ();
+ sequence_actions ();
+ build_cabinet ();
+
+ return db;
+ }
+
+ public override void visit_product (WixProduct product) throws GLib.Error {
+ if (product.Codepage != null)
+ db.info.set_codepage (int.parse (product.Codepage));
+
+ if (product.Name != null)
+ db.info.set_subject (product.Name);
+
+ db.info.set_author (product.Manufacturer);
+
+ db.table_property.add ("Manufacturer", product.Manufacturer);
+ db.table_property.add ("ProductLanguage", product.Language);
+ db.table_property.add ("ProductCode", get_uuid (product.Id));
+ db.table_property.add ("ProductName", product.Name);
+ db.table_property.add ("ProductVersion", product.Version);
+ db.table_property.add ("UpgradeCode", add_braces (product.UpgradeCode));
+ }
+
+ public override void visit_package (WixPackage package) throws GLib.Error {
+ db.info.set_comments (package.Comments);
+
+ if (package.Description != null)
+ db.info.set_subject (package.Description);
+
+ if (package.Keywords != null)
+ db.info.set_keywords (package.Keywords);
+
+ if (package.InstallerVersion != null)
+ db.info.set_property (Libmsi.Property.VERSION, int.parse (package.InstallerVersion));
+
+ }
+
+ public override void visit_icon (WixIcon icon) throws GLib.Error {
+ FileInfo info;
+
+ icon.file = find_file (icon.SourceFile, out info);
+ db.table_icon.add (icon.Id, icon.file.get_path ());
+ }
+
+ public override void visit_property (WixProperty prop) throws GLib.Error {
+ db.table_property.add (prop.Id, prop.Value);
+ }
+
+ public override void visit_media (WixMedia media) throws GLib.Error {
+ var cabinet = media.Cabinet;
+
+ if (parse_yesno (media.EmbedCab))
+ cabinet = "#" + cabinet;
+
+ var rec = db.table_media.add (media.Id, media.DiskPrompt, cabinet);
+ media.record = rec;
+ }
+
+ public override void visit_directory (WixDirectory dir) throws GLib.Error {
+ var defaultdir = dir.Name ?? ".";
+
+ if (dir.parent.get_type () == typeof (WixProduct)) {
+ if (dir.Id != "TARGETDIR")
+ throw new Wixl.Error.FAILED ("Invalid root directory");
+ db.table_directory.add (dir.Id, null, defaultdir);
+ } else if (dir.parent.get_type () == typeof (WixDirectory)) {
+ var parent = dir.parent as WixDirectory;
+ db.table_directory.add (dir.Id, parent.Id, defaultdir);
+ } else
+ warning ("unhandled parent type %s", dir.parent.name);
+ }
+
+ [Flags]
+ enum ComponentAttribute {
+ LOCAL_ONLY = 0,
+ SOURCE_ONLY,
+ OPTIONAL,
+ REGISTRY_KEY_PATH,
+ SHARED_DLL_REF_COUNT,
+ PERMANENT,
+ ODBC_DATA_SOURCE,
+ TRANSITIVE,
+ NEVER_OVERWRITE,
+ 64BIT,
+ REGISTRY_REFLECTION,
+ UNINSTALL_ON_SUPERSEDENCE,
+ SHARED,
+ }
+
+ G? resolve<G> (WixElement element) throws GLib.Error {
+ if (element.get_type () == typeof (G))
+ return element;
+ else if (element is WixElementRef) {
+ var ref = element as WixElementRef<G>;
+ if (ref.ref_type != typeof (G))
+ return null;
+ if (ref.resolved != null)
+ return ref.resolved;
+ ref.resolved = find_element<G> (element.Id);
+ return ref.resolved;
+ }
+
+ throw new Wixl.Error.FAILED ("couldn't resolve %s", element.Id);
+ }
+
+ public override void visit_component (WixComponent comp) throws GLib.Error {
+ var attr = 0;
+
+ if (comp.key is WixRegistryValue)
+ attr |= ComponentAttribute.REGISTRY_KEY_PATH;
+
+ var parent = resolve<WixDirectory> (comp.parent);
+ db.table_component.add (comp.Id, add_braces (comp.Guid), parent.Id, attr,
+ comp.key != null ? comp.key.Id : null);
+
+ }
+
+ enum FeatureDisplay {
+ HIDDEN = 0,
+ EXPAND,
+ COLLAPSE
+ }
+ WixFeature? feature_root;
+ int feature_display;
+
+ public override void visit_feature (WixFeature feature, VisitState state) throws GLib.Error {
+ if (state == VisitState.ENTER && feature_root == null) {
+ feature_display = 0;
+ feature_root = feature;
+ } else if (state == VisitState.LEAVE && feature_root == feature) {
+ feature_root = null;
+ }
+
+ if (state != VisitState.ENTER)
+ return;
+
+ int display = FeatureDisplay.COLLAPSE;
+ if (feature.Display != null) {
+ try {
+ display = enum_from_string (typeof (FeatureDisplay), feature.Display);
+ } catch (GLib.Error error) {
+ display = int.parse (feature.Display);
+ if (display != 0)
+ feature_display = display;
+ }
+ }
+
+ switch (display) {
+ case FeatureDisplay.COLLAPSE:
+ display = feature_display = (feature_display | 1) + 1;
+ break;
+ case FeatureDisplay.EXPAND:
+ display = feature_display = (feature_display + 1) | 1;
+ break;
+ }
+
+ string? parent = (feature.parent is WixFeature) ? feature.parent.Id : null;
+
+ db.table_feature.add (feature.Id, display, int.parse (feature.Level), 0, parent, feature.Title, feature.Description, feature.ConfigurableDirectory);
+
+ }
+
+ public override void visit_component_ref (WixComponentRef ref) throws GLib.Error {
+ if (ref.parent is WixFeature) {
+ var feature = ref.parent as WixFeature;
+
+ var component = resolve<WixComponent> (@ref);
+ component.in_feature.append (feature);
+ db.table_feature_components.add (feature.Id, @ref.Id);
+ } else
+ warning ("unhandled parent type %s", @ref.parent.name);
+ }
+
+ enum RemoveFileInstallMode {
+ INSTALL = 1,
+ UNINSTALL,
+ BOTH
+ }
+
+ public override void visit_remove_folder (WixRemoveFolder rm) throws GLib.Error {
+ var on = enum_from_string (typeof (RemoveFileInstallMode), rm.On);
+ var comp = rm.parent as WixComponent;
+ var dir = comp.parent as WixDirectory;
+
+ db.table_remove_file.add (rm.Id, comp.Id, dir.Id, on);
+ }
+
+ void visit_key_element (WixKeyElement key) throws GLib.Error {
+ var component = key.parent as WixComponent;
+
+ if (component.key == null || parse_yesno (key.KeyPath))
+ component.key = key;
+ }
+
+ enum RegistryValueType {
+ STRING,
+ INTEGER,
+ BINARY,
+ EXPANDABLE,
+ MULTI_STRING
+ }
+
+ enum RegistryRoot {
+ HKCR,
+ HKCU,
+ HKLM,
+ HKU,
+ HKMU
+ }
+
+ public override void visit_registry_value (WixRegistryValue reg) throws GLib.Error {
+ var comp = reg.parent as WixComponent;
+ var value = reg.Value;
+ var t = enum_from_string (typeof (RegistryValueType), reg.Type);
+ var r = enum_from_string (typeof (RegistryRoot), reg.Root.down ());
+ if (reg.Id == null) {
+ reg.Id = generate_id ("reg", 4,
+ comp.Id,
+ reg.Root,
+ reg.Key != null ? reg.Key.down () : null,
+ reg.Name != null ? reg.Name.down () : null);
+ }
+
+ switch (t) {
+ case RegistryValueType.STRING:
+ value = value[0] == '#' ? "#" + value : value;
+ break;
+ }
+
+ db.table_registry.add (reg.Id, r, reg.Key, comp.Id);
+
+ visit_key_element (reg);
+ }
+
+ [Flags]
+ enum FileAttribute {
+ READ_ONLY = 1 << 0,
+ HIDDEN = 1 << 1,
+ SYSTEM = 1 << 2,
+ VITAL = 1 << 9,
+ CHECKSUM = 1 << 10,
+ PATCH_ADDED = 1 << 11,
+ NON_COMPRESSED = 1 << 12,
+ COMPRESSED = 1 << 13
+ }
+
+ File? find_file (string name, out FileInfo info) throws GLib.Error {
+ info = null;
+
+ foreach (var p in path) {
+ var file = p.get_child (name);
+ try {
+ info = file.query_info ("standard::*", 0, null);
+ if (info != null)
+ return file;
+ } catch (IOError error) {
+ if (error is IOError.NOT_FOUND)
+ continue;
+ throw error;
+ }
+ }
+
+ throw new Wixl.Error.FAILED ("Couldn't find file %s", name);
+ }
+
+ public override void visit_file (WixFile file) throws GLib.Error {
+ file.DiskId = file.DiskId ?? "1";
+ return_if_fail (file.DiskId == "1");
+
+ var name = file.Id;
+ if (file.Name != null)
+ name = file.Name;
+ else if (file.Source != null)
+ name = Path.get_basename (file.Source);
+
+ var comp = file.parent as WixComponent;
+ FileInfo info;
+ file.file = find_file (name, out info);
+ var attr = FileAttribute.VITAL;
+
+ var rec = db.table_file.add (file.Id, comp.Id, name, (int)info.get_size (), attr);
+ file.record = rec;
+
+ visit_key_element (file);
+ }
+
+ public override void visit_shortcut (WixShortcut shortcut) throws GLib.Error {
+ if (!parse_yesno (shortcut.Advertise))
+ throw new Wixl.Error.FIXME ("unimplemented");
+
+ var component = shortcut.get_component ();
+ var rec = db.table_shortcut.add (shortcut.Id, shortcut.Directory, shortcut.Name, component.Id);
+ shortcut.record = rec;
+
+ if (shortcut.Icon != null)
+ MsiTableShortcut.set_icon (rec, shortcut.Icon, int.parse (shortcut.IconIndex));
+ if (shortcut.WorkingDirectory != null)
+ MsiTableShortcut.set_working_dir (rec, shortcut.WorkingDirectory);
+ }
+
+ public override void visit_sequence (WixSequence sequence) throws GLib.Error {
+ }
+
+ public override void visit_condition (WixCondition condition) throws GLib.Error {
+ return_if_fail (condition.children.length () == 1);
+ var text = condition.children.first ().data as WixText;
+
+ db.table_launch_condition.add (text.Text, condition.Message);
+ }
+
+ [Flags]
+ enum UpgradeAttribute {
+ MIGRATE_FEATURES = 1 << 0,
+ ONLY_DETECT = 1 << 1,
+ IGNORE_REMOVE_FAILURE = 1 << 2,
+ VERSION_MIN_INCLUSIVE = 1 << 8,
+ VERSION_MAX_INCLUSIVE = 1 << 9,
+ LANGUAGES_EXCLUSIVE = 1 << 10
+ }
+
+ public override void visit_upgrade (WixUpgrade upgrade) throws GLib.Error {
+ }
+
+ public override void visit_upgrade_version (WixUpgradeVersion version) throws GLib.Error {
+ var upgrade = version.parent as WixUpgrade;
+ UpgradeAttribute attributes = 0;
+
+ if (parse_yesno (version.OnlyDetect))
+ attributes |= UpgradeAttribute.ONLY_DETECT;
+
+ if (parse_yesno (version.IncludeMinimum, true))
+ attributes |= UpgradeAttribute.VERSION_MIN_INCLUSIVE;
+
+ db.table_upgrade.add (get_uuid (upgrade.Id), version.Minimum, version.Maximum, attributes, version.Property);
+
+ secureProperties += version.Property;
+ }
+
+ public override void visit_action (WixAction action) throws GLib.Error {
+ var parent = action.parent as WixSequence;
+ var table = db.tables.lookup (parent.name) as MsiTableSequence;
+
+ var node = table.get_action (action.name);
+ warn_if_fail (node.action == null);
+ node.action = action;
+
+ if (action.After != null)
+ node.add_dep (table.get_action (action.After));
+
+ if (action.Before != null) {
+ var before = table.get_action (action.Before);
+ before.add_dep (node);
+ }
+ }
+
+ public override void visit_create_folder (WixCreateFolder folder) throws GLib.Error {
+ }
+
+ public override void visit_fragment (WixFragment fragment) throws GLib.Error {
+ }
+
+ public override void visit_directory_ref (WixDirectoryRef ref) throws GLib.Error {
+ }
+
+ public override void visit_text (WixText text) throws GLib.Error {
+ }
+ }
+
+} // Wixl
diff --git a/tools/wixl/msi.vala b/tools/wixl/msi.vala
new file mode 100644
index 0000000..b8700b0
--- /dev/null
+++ b/tools/wixl/msi.vala
@@ -0,0 +1,642 @@
+namespace Wixl {
+
+ abstract class MsiTable: Object {
+ public class string name;
+ public List<Libmsi.Record> records;
+
+ public class string sql_create;
+ public class string sql_insert;
+
+ public virtual void create (Libmsi.Database db) throws GLib.Error {
+ var query = new Libmsi.Query (db, sql_create);
+ query.execute (null);
+
+ if (sql_insert == null)
+ return;
+
+ query = new Libmsi.Query (db, sql_insert);
+ foreach (var r in records)
+ query.execute (r);
+ }
+ }
+
+ class MsiTableIcon: MsiTable {
+ static construct {
+ name = "Icon";
+ sql_create = "CREATE TABLE `Icon` (`Name` CHAR(72) NOT NULL, `Data` OBJECT NOT NULL PRIMARY KEY `Name`)";
+ sql_insert = "INSERT INTO `Icon` (`Name`, `Data`) VALUES (?, ?)";
+ }
+
+ public void add (string id, string filename) throws GLib.Error {
+ var rec = new Libmsi.Record (2);
+
+ if (!rec.set_string (1, id) ||
+ !rec.load_stream (2, filename))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ abstract class MsiTableSequence: MsiTable {
+ private void add (string action, int sequence) throws GLib.Error {
+ var rec = new Libmsi.Record (2);
+
+ if (!rec.set_string (1, action) ||
+ !rec.set_int (2, sequence))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+
+ protected class void set_sequence_table_name (string table) {
+ name = table;
+ sql_create = "CREATE TABLE `%s` (`Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` INT PRIMARY KEY `Action`)".printf (table);
+ sql_insert = "INSERT INTO `%s` (`Action`, `Sequence`) VALUES (?, ?)".printf (table);
+ }
+
+ public class Action {
+ public string name;
+ public int sequence = -1;
+ public WixAction? action;
+
+ public bool visited = false;
+ public bool incoming_deps = false;
+
+ // Use it as a set, so value is not refcounted please vala
+ public HashTable<Action, Action*> depends_on = new HashTable<Action, Action*> (direct_hash, direct_equal);
+
+ public void add_dep (Action a) {
+ depends_on.add (a);
+ a.incoming_deps = true;
+ }
+ }
+
+ HashTable<string, Action> actions = new HashTable<string, Action> (str_hash, str_equal);
+
+
+ void sort_topological_visit (Action action, ref List<Action> sorted) {
+ if (action.visited)
+ return;
+
+ action.visited = true;
+
+ Action dep;
+ var it = HashTableIter <Action, Action*> (action.depends_on);
+ while (it.next (null, out dep))
+ sort_topological_visit (dep, ref sorted);
+
+ sorted.append (action);
+ }
+
+ List<Action> sort_topological () {
+ List<Action> sorted = null;
+
+ Action action;
+ var it = HashTableIter <string, Action> (actions);
+ while (it.next (null, out action)) {
+ if (action.incoming_deps)
+ continue;
+ sort_topological_visit (action, ref sorted);
+ }
+
+ return sorted;
+ }
+
+ void add_implicit_deps () {
+ CompareFunc<Action> cmp = (a, b) => {
+ return a.sequence - b.sequence;
+ };
+ var list = actions.get_values ();
+ list.sort (cmp);
+
+ Action? prev = null;
+ foreach (var a in list) {
+ if (a.sequence == -1)
+ continue;
+ if (prev != null)
+ a.add_dep (prev);
+ prev = a;
+ }
+ }
+
+ public void add_sorted_actions () throws GLib.Error {
+ add_implicit_deps ();
+ var sorted = sort_topological ();
+
+ int sequence = 0;
+ foreach (var action in sorted) {
+ if (action.sequence == -1)
+ action.sequence = ++sequence;
+
+ sequence = action.sequence;
+ add (action.name, action.sequence);
+ }
+ }
+
+ public Action get_action (string name) {
+ var action = actions.lookup (name);
+ if (action != null)
+ return action;
+
+ action = new Action ();
+ actions.insert (name, action);
+ action.name = name;
+
+ return action;
+ }
+ }
+
+ class MsiTableAdminExecuteSequence: MsiTableSequence {
+ static construct {
+ set_sequence_table_name ("AdminExecuteSequence");
+ }
+ }
+
+ class MsiTableAdminUISequence: MsiTableSequence {
+ static construct {
+ set_sequence_table_name ("AdminUISequence");
+ }
+ }
+
+ class MsiTableAdvtExecuteSequence: MsiTableSequence {
+ static construct {
+ set_sequence_table_name ("AdvtExecuteSequence");
+ }
+ }
+
+ class MsiTableInstallExecuteSequence: MsiTableSequence {
+ static construct {
+ set_sequence_table_name ("InstallExecuteSequence");
+ }
+ }
+
+ class MsiTableInstallUISequence: MsiTableSequence {
+ static construct {
+ set_sequence_table_name ("InstallUISequence");
+ }
+ }
+
+ class MsiTableError: MsiTable {
+ static construct {
+ name = "Error";
+ sql_create = "CREATE TABLE `Error` (`Error` INT NOT NULL, `Message` CHAR(0) LOCALIZABLE PRIMARY KEY `Error`)";
+ }
+ }
+
+ class MsiTableFile: MsiTable {
+ static construct {
+ name = "File";
+ sql_create = "CREATE TABLE `File` (`File` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL, `FileName` CHAR(255) NOT NULL LOCALIZABLE, `FileSize` LONG NOT NULL, `Version` CHAR(72), `Language` CHAR(20), `Attributes` INT, `Sequence` LONG NOT NULL PRIMARY KEY `File`)";
+ sql_insert = "INSERT INTO `File` (`File`, `Component_`, `FileName`, `FileSize`, `Attributes`, `Sequence`) VALUES (?, ?, ?, ?, ?, ?)";
+ }
+
+ public Libmsi.Record add (string File, string Component, string FileName, int FileSize, int Attributes, int Sequence = 0) throws GLib.Error {
+ var rec = new Libmsi.Record (6);
+
+ if (!rec.set_string (1, File) ||
+ !rec.set_string (2, Component) ||
+ !rec.set_string (3, FileName) ||
+ !rec.set_int (4, FileSize) ||
+ !rec.set_int (5, Attributes) ||
+ !rec.set_int (6, Sequence))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+
+ return rec;
+ }
+
+ public static bool set_sequence (Libmsi.Record rec, int Sequence) {
+ return rec.set_int (6, Sequence);
+ }
+ }
+
+ class MsiTableMedia: MsiTable {
+ static construct {
+ name = "Media";
+ sql_create = "CREATE TABLE `Media` (`DiskId` INT NOT NULL, `LastSequence` LONG NOT NULL, `DiskPrompt` CHAR(64) LOCALIZABLE, `Cabinet` CHAR(255), `VolumeLabel` CHAR(32), `Source` CHAR(72) PRIMARY KEY `DiskId`)";
+ sql_insert = "INSERT INTO `Media` (`DiskId`, `LastSequence`, `DiskPrompt`, `Cabinet`) VALUES (?, ?, ?, ?)";
+ }
+
+ public bool set_last_sequence (Libmsi.Record rec, int last_sequence) {
+ return rec.set_int (2, last_sequence);
+ }
+
+ public Libmsi.Record add (string DiskId, string? DiskPrompt, string Cabinet) throws GLib.Error {
+ var rec = new Libmsi.Record (4);
+
+ if (!rec.set_int (1, int.parse (DiskId)) ||
+ !rec.set_int (2, 0) ||
+ (DiskPrompt != null && !rec.set_string (3, DiskPrompt)) ||
+ !rec.set_string (4, Cabinet))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ return rec;
+ }
+ }
+
+ class MsiTableUpgrade: MsiTable {
+ static construct {
+ name = "Upgrade";
+ sql_create = "CREATE TABLE `Upgrade` (`UpgradeCode` CHAR(38) NOT NULL, `VersionMin` CHAR(20), `VersionMax` CHAR(20), `Language` CHAR(255), `Attributes` LONG NOT NULL, `Remove` CHAR(255), `ActionProperty` CHAR(72) NOT NULL PRIMARY KEY `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes`)";
+ sql_insert = "INSERT INTO `Upgrade` (`UpgradeCode`, `VersionMin`, `VersionMax`, `Attributes`, `ActionProperty`) VALUES (?, ?, ?, ?, ?)";
+ }
+
+ public void add (string UpgradeCode, string VersionMin, string? VersionMax, int Attributes, string ActionProperty) throws GLib.Error {
+ var rec = new Libmsi.Record (5);
+
+ if (!rec.set_string (1, UpgradeCode) ||
+ !rec.set_string (2, VersionMin) ||
+ (VersionMax != null && !rec.set_string (3, VersionMax)) ||
+ !rec.set_int (4, Attributes) ||
+ !rec.set_string (5, ActionProperty))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableLaunchCondition: MsiTable {
+ static construct {
+ name = "LaunchCondition";
+ sql_create = "CREATE TABLE `LaunchCondition` (`Condition` CHAR(255) NOT NULL, `Description` CHAR(255) NOT NULL LOCALIZABLE PRIMARY KEY `Condition`)";
+ sql_insert = "INSERT INTO `LaunchCondition` (`Condition`, `Description`) VALUES (?, ?)";
+ }
+
+ public void add (string condition, string description) throws GLib.Error {
+ var rec = new Libmsi.Record (2);
+
+ if (!rec.set_string (1, condition) ||
+ !rec.set_string (2, description))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableProperty: MsiTable {
+ static construct {
+ name = "Property";
+ sql_create = "CREATE TABLE `Property` (`Property` CHAR(72) NOT NULL, `Value` CHAR(0) NOT NULL LOCALIZABLE PRIMARY KEY `Property`)";
+ sql_insert = "INSERT INTO `Property` (`Property`, `Value`) VALUES (?, ?)";
+ }
+
+ public void add (string prop, string value) throws GLib.Error {
+ var rec = new Libmsi.Record (2);
+
+ if (!rec.set_string (1, prop) ||
+ !rec.set_string (2, value))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableDirectory: MsiTable {
+ static construct {
+ name = "Directory";
+ sql_create = "CREATE TABLE `Directory` (`Directory` CHAR(72) NOT NULL, `Directory_Parent` CHAR(72), `DefaultDir` CHAR(255) NOT NULL LOCALIZABLE PRIMARY KEY `Directory`)";
+ sql_insert = "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) VALUES (?, ?, ?)";
+ }
+
+ public void add (string Directory, string? Parent, string DefaultDir) throws GLib.Error {
+ var rec = new Libmsi.Record (3);
+ if (!rec.set_string (1, Directory) ||
+ !rec.set_string (2, Parent) ||
+ !rec.set_string (3, DefaultDir))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableComponent: MsiTable {
+ static construct {
+ name = "Component";
+ sql_create = "CREATE TABLE `Component` (`Component` CHAR(72) NOT NULL, `ComponentId` CHAR(38), `Directory_` CHAR(72) NOT NULL, `Attributes` INT NOT NULL, `Condition` CHAR(255), `KeyPath` CHAR(72) PRIMARY KEY `Component`)";
+ sql_insert = "INSERT INTO `Component` (`Component`, `ComponentId`, `Directory_`, `Attributes`, `KeyPath`) VALUES (?, ?, ?, ?, ?)";
+ }
+
+ public void add (string Component, string ComponentId, string Directory, int Attributes, string? KeyPath = null) throws GLib.Error {
+ var rec = new Libmsi.Record (5);
+ if (!rec.set_string (1, Component) ||
+ !rec.set_string (2, ComponentId) ||
+ !rec.set_string (3, Directory) ||
+ !rec.set_int (4, Attributes) ||
+ !rec.set_string (5, KeyPath))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableFeatureComponents: MsiTable {
+ static construct {
+ name = "FeatureComponents";
+ sql_create = "CREATE TABLE `FeatureComponents` (`Feature_` CHAR(38) NOT NULL, `Component_` CHAR(72) NOT NULL PRIMARY KEY `Feature_`, `Component_`)";
+ sql_insert = "INSERT INTO `FeatureComponents` (`Feature_`, `Component_`) VALUES (?, ?)";
+ }
+
+ public void add (string Feature, string Component) throws GLib.Error {
+ var rec = new Libmsi.Record (2);
+ if (!rec.set_string (1, Feature) ||
+ !rec.set_string (2, Component))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableRegistry: MsiTable {
+ static construct {
+ name = "Registry";
+ sql_create = "CREATE TABLE `Registry` (`Registry` CHAR(72) NOT NULL, `Root` INT NOT NULL, `Key` CHAR(255) NOT NULL LOCALIZABLE, `Name` CHAR(255) LOCALIZABLE, `Value` CHAR(0) LOCALIZABLE, `Component_` CHAR(72) NOT NULL PRIMARY KEY `Registry`)";
+ sql_insert = "INSERT INTO `Registry` (`Registry`, `Root`, `Key`, `Component_`) VALUES (?, ?, ?, ?)";
+ }
+
+ public void add (string Registry, int Root, string Key, string Component) throws GLib.Error {
+ var rec = new Libmsi.Record (4);
+ if (!rec.set_string (1, Registry) ||
+ !rec.set_int (2, Root) ||
+ !rec.set_string (3, Key) ||
+ !rec.set_string (4, Component))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableShortcut: MsiTable {
+ static construct {
+ name = "Shortcut";
+ sql_create = "CREATE TABLE `Shortcut` (`Shortcut` CHAR(72) NOT NULL, `Directory_` CHAR(72) NOT NULL, `Name` CHAR(128) NOT NULL LOCALIZABLE, `Component_` CHAR(72) NOT NULL, `Target` CHAR(72) NOT NULL, `Arguments` CHAR(255), `Description` CHAR(255) LOCALIZABLE, `Hotkey` INT, `Icon_` CHAR(72), `IconIndex` INT, `ShowCmd` INT, `WkDir` CHAR(72), `DisplayResourceDLL` CHAR(255), `DisplayResourceId` INT, `DescriptionResourceDLL` CHAR(255), `DescriptionResourceId` INT PRIMARY KEY `Shortcut`)";
+ sql_insert = "INSERT INTO `Shortcut` (`Shortcut`, `Directory_`, `Name`, `Component_`, `Target`, `Icon_`, `IconIndex`, `WkDir`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+ }
+
+ public Libmsi.Record add (string Shortcut, string Directory, string Name, string Component) throws GLib.Error {
+ var rec = new Libmsi.Record (8);
+
+ if (!rec.set_string (1, Shortcut) ||
+ !rec.set_string (2, Directory) ||
+ !rec.set_string (3, Name) ||
+ !rec.set_string (4, Component))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+
+ return rec;
+ }
+
+ public static void set_target (Libmsi.Record rec, string Target) throws GLib.Error {
+ if (!rec.set_string (5, Target))
+ throw new Wixl.Error.FAILED ("failed to set record");
+ }
+
+ public static void set_icon (Libmsi.Record rec, string Icon, int IconIndex) throws GLib.Error {
+ if (!rec.set_string (6, Icon) ||
+ !rec.set_int (7, IconIndex))
+ throw new Wixl.Error.FAILED ("failed to set record");
+ }
+
+ public static void set_working_dir (Libmsi.Record rec, string WkDir) throws GLib.Error {
+ if (!rec.set_string (8, WkDir))
+ throw new Wixl.Error.FAILED ("failed to set record");
+ }
+ }
+
+ class MsiTableRemoveFile: MsiTable {
+ static construct {
+ name = "RemoveFile";
+ sql_create = "CREATE TABLE `RemoveFile` (`FileKey` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL, `FileName` CHAR(255) LOCALIZABLE, `DirProperty` CHAR(72) NOT NULL, `InstallMode` INT NOT NULL PRIMARY KEY `FileKey`)";
+ sql_insert = "INSERT INTO `RemoveFile` (`FileKey`, `Component_`, `DirProperty`, `InstallMode`) VALUES (?, ?, ?, ?)";
+ }
+
+ public void add (string FileKey, string Component, string DirProperty, int InstallMode) throws GLib.Error {
+ var rec = new Libmsi.Record (4);
+ if (!rec.set_string (1, FileKey) ||
+ !rec.set_string (2, Component) ||
+ !rec.set_string (3, DirProperty) ||
+ !rec.set_int (4, InstallMode))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableFeature: MsiTable {
+ static construct {
+ name = "Feature";
+ sql_create = "CREATE TABLE `Feature` (`Feature` CHAR(38) NOT NULL, `Feature_Parent` CHAR(38), `Title` CHAR(64) LOCALIZABLE, `Description` CHAR(255) LOCALIZABLE, `Display` INT, `Level` INT NOT NULL, `Directory_` CHAR(72), `Attributes` INT NOT NULL PRIMARY KEY `Feature`)";
+ sql_insert = "INSERT INTO `Feature` (`Feature`, `Display`, `Level`, `Attributes`, `Feature_Parent`, `Title`, `Description`, `Directory_`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
+ }
+
+ public void add (string Feature, int Display, int Level, int Attributes, string? Parent = null, string? Title = null, string? Description = null, string? ConfigurableDirectory = null) throws GLib.Error {
+ var rec = new Libmsi.Record (8);
+ if (!rec.set_string (1, Feature) ||
+ !rec.set_int (2, Display) ||
+ !rec.set_int (3, Level) ||
+ !rec.set_int (4, Attributes) ||
+ (Parent != null && !rec.set_string (5, Parent)) ||
+ (Title != null && !rec.set_string (6, Title)) ||
+ (Description != null && !rec.set_string (7, Description)) ||
+ (ConfigurableDirectory != null && !rec.set_string (8, ConfigurableDirectory)))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+ }
+
+ class MsiTableValidation: MsiTable {
+ static construct {
+ name = "_Validation";
+ }
+
+ public override void create (Libmsi.Database db) throws GLib.Error {
+ }
+ }
+
+ class MsiTableStreams: MsiTable {
+ static construct {
+ name = "_Streams";
+ }
+
+ public void add (string name, GLib.InputStream input, size_t count) throws GLib.Error {
+ var rec = new Libmsi.Record (2);
+ if (!rec.set_string (1, name) ||
+ !rec.set_stream (2, input, count))
+ throw new Wixl.Error.FAILED ("failed to add record");
+
+ records.append (rec);
+ }
+
+ public override void create (Libmsi.Database db) throws GLib.Error {
+ var query = new Libmsi.Query (db, "INSERT INTO `_Streams` (`Name`, `Data`) VALUES (?, ?)");
+ foreach (var r in records)
+ query.execute (r);
+ }
+ }
+
+ class MsiSummaryInfo: Object {
+ public Libmsi.SummaryInfo properties;
+
+ construct {
+ try {
+ properties = new Libmsi.SummaryInfo (null, uint.MAX);
+ } catch (GLib.Error error) {
+ critical (error.message);
+ }
+ }
+
+ public MsiSummaryInfo () {
+ }
+
+ public new void set_property (Libmsi.Property prop, Value value) throws GLib.Error {
+ if (value.type () == typeof (string))
+ properties.set_string (prop, (string) value);
+ else if (value.type () == typeof (int))
+ properties.set_int (prop, (int) value);
+ else if (value.type () == typeof (uint64))
+ properties.set_filetime (prop, (uint64) value);
+ else
+ critical ("Unhandled property type");
+ }
+
+ public void save (Libmsi.Database db) throws GLib.Error {
+ properties.save (db);
+ }
+
+ public void set_codepage (int value) throws GLib.Error {
+ set_property (Libmsi.Property.CODEPAGE, value);
+ }
+
+ public void set_author (string value) throws GLib.Error {
+ set_property (Libmsi.Property.AUTHOR, value);
+ }
+
+ public void set_keywords (string value) throws GLib.Error {
+ set_property (Libmsi.Property.KEYWORDS, value);
+ }
+
+ public void set_subject (string value) throws GLib.Error {
+ set_property (Libmsi.Property.SUBJECT, value);
+ }
+
+ public void set_comments (string value) throws GLib.Error {
+ set_property (Libmsi.Property.COMMENTS, value);
+ }
+ }
+
+ class MsiDatabase: Object {
+ public MsiSummaryInfo info;
+ public MsiTableProperty table_property;
+ public MsiTableIcon table_icon;
+ public MsiTableMedia table_media;
+ public MsiTableDirectory table_directory;
+ public MsiTableComponent table_component;
+ public MsiTableFeature table_feature;
+ public MsiTableFeatureComponents table_feature_components;
+ public MsiTableRemoveFile table_remove_file;
+ public MsiTableRegistry table_registry;
+ public MsiTableFile table_file;
+ public MsiTableAdminExecuteSequence table_admin_execute_sequence;
+ public MsiTableAdminUISequence table_admin_ui_sequence;
+ public MsiTableAdvtExecuteSequence table_advt_execute_sequence;
+ public MsiTableInstallExecuteSequence table_install_execute_sequence;
+ public MsiTableInstallUISequence table_install_ui_sequence;
+ public MsiTableStreams table_streams;
+ public MsiTableShortcut table_shortcut;
+ public MsiTableUpgrade table_upgrade;
+ public MsiTableLaunchCondition table_launch_condition;
+
+ public HashTable<string, MsiTable> tables;
+
+ construct {
+ info = new MsiSummaryInfo ();
+ try {
+ info.set_property (Libmsi.Property.TITLE, "Installation Database");
+ info.set_property (Libmsi.Property.TEMPLATE, "Intel;1033");
+ info.set_property (Libmsi.Property.KEYWORDS, "Installer");
+ info.set_property (Libmsi.Property.CODEPAGE, 1252);
+ info.set_property (Libmsi.Property.UUID, get_uuid ("*"));
+ info.set_property (Libmsi.Property.CREATED_TM,
+ time_to_filetime (now ()));
+ info.set_property (Libmsi.Property.LASTSAVED_TM,
+ time_to_filetime (now ()));
+ info.set_property (Libmsi.Property.VERSION, 100);
+ info.set_property (Libmsi.Property.SOURCE, 2);
+ info.set_property (Libmsi.Property.APPNAME, Config.PACKAGE_STRING);
+ info.set_property (Libmsi.Property.SECURITY, 2);
+ } catch (GLib.Error error) {
+ critical (error.message);
+ }
+
+ tables = new HashTable<string, MsiTable> (str_hash, str_equal);
+ table_property = new MsiTableProperty ();
+ table_icon = new MsiTableIcon ();
+ table_media = new MsiTableMedia ();
+ table_directory = new MsiTableDirectory ();
+ table_component = new MsiTableComponent ();
+ table_feature = new MsiTableFeature ();
+ table_feature_components = new MsiTableFeatureComponents ();
+ table_remove_file = new MsiTableRemoveFile ();
+ table_registry = new MsiTableRegistry ();
+ table_file = new MsiTableFile ();
+ table_admin_execute_sequence = new MsiTableAdminExecuteSequence ();
+ table_admin_ui_sequence = new MsiTableAdminUISequence ();
+ table_advt_execute_sequence = new MsiTableAdvtExecuteSequence ();
+ table_install_execute_sequence = new MsiTableInstallExecuteSequence ();
+ table_install_ui_sequence = new MsiTableInstallUISequence ();
+ table_streams = new MsiTableStreams ();
+ table_shortcut = new MsiTableShortcut ();
+ table_upgrade = new MsiTableUpgrade ();
+ table_launch_condition = new MsiTableLaunchCondition ();
+
+ foreach (var t in new MsiTable[] {
+ table_admin_execute_sequence,
+ table_admin_ui_sequence,
+ table_advt_execute_sequence,
+ table_install_execute_sequence,
+ table_install_ui_sequence,
+ table_directory,
+ table_media,
+ table_property,
+ table_icon,
+ table_component,
+ table_feature,
+ table_feature_components,
+ table_remove_file,
+ table_registry,
+ table_file,
+ table_streams,
+ table_shortcut,
+ table_upgrade,
+ table_launch_condition,
+ new MsiTableError (),
+ new MsiTableValidation ()
+ }) {
+ tables.insert (t.name, t);
+ }
+ }
+
+ public MsiDatabase () {
+ // empty ctor
+ }
+
+ public void build (string filename) throws GLib.Error {
+ string name;
+ MsiTable table;
+
+ var db = new Libmsi.Database (filename, (string)2);
+ info.save (db);
+
+ var it = HashTableIter <string, MsiTable> (tables);
+ while (it.next (out name, out table))
+ table.create (db);
+
+ db.commit ();
+ }
+ }
+
+} // Wixl \ No newline at end of file
diff --git a/tools/wixl/preprocessor.vala b/tools/wixl/preprocessor.vala
new file mode 100644
index 0000000..feafb3b
--- /dev/null
+++ b/tools/wixl/preprocessor.vala
@@ -0,0 +1,131 @@
+namespace Wixl {
+
+ class Preprocessor: Object {
+
+ HashTable<string, string> globals;
+ HashTable<string, string> variables;
+ construct {
+ variables = new HashTable<string, string> (str_hash, str_equal);
+ }
+
+ public Preprocessor (HashTable<string, string> globals) {
+ this.globals = globals;
+ }
+
+ public void define_variable (string name, string value) {
+ variables.insert (name, value);
+ }
+
+ public string? lookup_variable (string name) {
+ return variables.lookup (name) ?? globals.lookup (name);
+ }
+
+ public string eval_variable (string str, File? file) throws GLib.Error {
+ var var = str.split (".", 2);
+ if (var.length != 2)
+ throw new Wixl.Error.FAILED ("invalid variable %s", str);
+
+ switch (var[0]) {
+ case "var":
+ var val = lookup_variable (var[1]);
+ if (val == null)
+ throw new Wixl.Error.FAILED ("Undefined variable %s", var[1]);
+ return val;
+ case "env":
+ return Environment.get_variable (var[1]);
+ case "sys":
+ switch (var[1]) {
+ case "CURRENTDIR":
+ return Environment.get_current_dir ();
+ case "SOURCEFILEDIR":
+ return file.get_basename ();
+ case "SOURCEFILEPATH":
+ return file.get_path ();
+ }
+ break;
+ }
+
+ throw new Wixl.Error.FIXME ("unhandled variable type %s", str);
+ }
+
+ public string eval (string str, File? file) throws GLib.Error {
+ var result = "";
+ int end = 0;
+ int pos = 0;
+
+ while ((pos = str.index_of ("$", end)) != -1) {
+ if (end < pos)
+ result += str[end:pos];
+ end = pos + 1;
+ var remainder = str[end:str.length];
+ if (remainder.has_prefix ("$"))
+ result += "$";
+ else if (remainder.has_prefix ("(")) {
+ var closing = find_closing_paren (remainder);
+ if (closing == -1)
+ throw new Wixl.Error.FAILED ("no matching closing parenthesis");
+ var substring = remainder[1:closing];
+ if (substring.index_of ("(") != -1)
+ throw new Wixl.Error.FIXME ("unsupported function");
+ result += eval_variable (substring, file);
+ end += closing + 1;
+ }
+ }
+
+ return result + str[end:str.length];
+ }
+
+ public Xml.Doc preprocess (string data, File? file) throws GLib.Error {
+ Xml.Doc doc;
+ var writer = new Xml.TextWriter.doc (out doc);
+ var reader = new Xml.TextReader.for_doc (data, "");
+
+ writer.start_document ();
+ while (reader.read () > 0) {
+ switch (reader.node_type ()) {
+ case Xml.ReaderType.PROCESSING_INSTRUCTION:
+ switch (reader.const_local_name ()) {
+ case "define":
+ MatchInfo info;
+ var r = /^\s*(?P<name>.+?)\s*=\s*(?P<value>.+?)\s*$/;
+ if (r.match (reader.const_value (), 0, out info)) {
+ var name = remove_prefix ("var.", info.fetch_named ("name"));
+ var value = unquote (info.fetch_named ("value"));
+ define_variable (name, value);
+ } else
+ throw new Wixl.Error.FAILED ("invalid define");
+ break;
+ default:
+ warning ("unhandled preprocessor instruction %s", reader.const_local_name ());
+ break;
+ }
+ break;
+ case Xml.ReaderType.ELEMENT:
+ var empty = reader.is_empty_element () > 0;
+
+ writer.start_element (reader.const_name ());
+ while (reader.move_to_next_attribute () > 0) {
+ var value = eval (reader.const_value (), file);
+ writer.write_attribute (reader.const_name (), value);
+ }
+
+ if (empty)
+ writer.end_element ();
+ break;
+ case Xml.ReaderType.END_ELEMENT:
+ writer.end_element ();
+ break;
+ case Xml.ReaderType.TEXT:
+ writer.write_string (eval (reader.const_value(), file));
+ break;
+ case Xml.ReaderType.CDATA:
+ writer.write_cdata (eval (reader.const_value(), file));
+ break;
+ }
+ }
+ writer.end_document ();
+
+ return doc;
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/wixl/util.vala b/tools/wixl/util.vala
new file mode 100644
index 0000000..11f1588
--- /dev/null
+++ b/tools/wixl/util.vala
@@ -0,0 +1,139 @@
+namespace Wixl {
+
+ public errordomain Error {
+ FAILED,
+ FIXME,
+ }
+
+ namespace UUID {
+ [CCode (cname = "uuid_generate", cheader_filename = "uuid/uuid.h")]
+ internal extern static void generate ([CCode (array_length = false)] uchar[] uuid);
+ [CCode (cname = "uuid_unparse", cheader_filename = "uuid/uuid.h")]
+ internal extern static void unparse ([CCode (array_length = false)] uchar[] uuid,
+ [CCode (array_length = false)] uchar[] output);
+ }
+
+ string uuid_generate () {
+ var udn = new uchar[50];
+ var id = new uchar[16];
+
+ UUID.generate (id);
+ UUID.unparse (id, udn);
+
+ return (string) udn;
+ }
+
+ int enum_from_string (Type t, string str) throws GLib.Error {
+ var k = (EnumClass)t.class_ref ();
+ var v = k.get_value_by_nick (str);
+
+ if (v == null)
+ throw new Wixl.Error.FAILED ("Can't convert string to enum");
+ return v.value;
+ }
+
+ string add_braces (string str) {
+ if (str[0] == '{')
+ return str;
+
+ return "{" + str + "}";
+ }
+
+ string get_uuid (owned string uuid) throws GLib.Error {
+ if (uuid == "*")
+ uuid = uuid_generate ();
+ uuid = add_braces (uuid);
+ uuid = uuid.up ();
+ // FIXME: validate
+ return uuid;
+ }
+
+ long now () {
+ var tv = TimeVal ();
+ tv.get_current_time ();
+ return tv.tv_sec;
+ }
+
+ uint64 time_to_filetime (long t) {
+ return (t + 134774ULL * 86400ULL) * 10000000ULL;
+ }
+
+ string get_attribute_content (Xml.Attr *attr) {
+ if (attr->children == null)
+ return "";
+
+ return attr->children->content;
+ }
+
+ public string indent (string space, string text) {
+ var indented = "";
+
+ foreach (var l in text.split ("\n")) {
+ if (indented.length != 0)
+ indented += "\n";
+
+ if (l.length != 0)
+ indented += space + l;
+ }
+
+ return indented;
+ }
+
+ public string generate_id (string prefix, uint n, ...) {
+ var l = va_list ();
+ var args = new string[n];
+
+ for (var i = 0; n > 0; n--) {
+ string? val = l.arg ();
+ if (val == null)
+ continue;
+ args[i] = val; // FIXME: misc vala bug when +=
+ i += 1;
+ }
+ var data = string.joinv ("|", args);
+ var hash = Checksum.compute_for_string (ChecksumType.MD5, data);
+ var str = prefix + hash[0:32].up ();
+
+ return str;
+ }
+
+ bool parse_yesno (string? str, bool default = false) {
+ if (str == null)
+ return default;
+
+ return (str[0] == 'Y' || str[0] == 'y');
+ }
+
+ string unquote (string str) {
+ if ((str[0] == '\'' && str[str.length-1] == '\'') ||
+ (str[0] == '"' && str[str.length-1] == '"'))
+ return str[1:-1];
+
+ return str;
+ }
+
+ string remove_prefix (string prefix, string str) {
+ if (str.has_prefix (prefix))
+ return str[prefix.length:str.length];
+
+ return str;
+ }
+
+ int find_closing_paren (string str) {
+ return_val_if_fail (str[0] == '(', -1);
+
+ var open_count = 1;
+ var close_count = 0;
+ for (var pos = 1; pos < str.length; pos++) {
+ if (str[pos] == '(')
+ open_count++;
+ else if (str[pos] == ')') {
+ close_count++;
+ if (open_count == close_count)
+ return pos;
+ }
+ }
+
+ return -1;
+ }
+} // Wixl
diff --git a/tools/wixl/wix.vala b/tools/wixl/wix.vala
new file mode 100644
index 0000000..d3468c5
--- /dev/null
+++ b/tools/wixl/wix.vala
@@ -0,0 +1,598 @@
+namespace Wixl {
+
+ public enum VisitState {
+ ENTER,
+ INFIX,
+ LEAVE
+ }
+
+ public abstract class WixNodeVisitor: Object {
+ public abstract void visit_product (WixProduct product) throws GLib.Error;
+ public abstract void visit_icon (WixIcon icon) throws GLib.Error;
+ public abstract void visit_package (WixPackage package) throws GLib.Error;
+ public abstract void visit_property (WixProperty prop) throws GLib.Error;
+ public abstract void visit_media (WixMedia media) throws GLib.Error;
+ public abstract void visit_directory (WixDirectory dir) throws GLib.Error;
+ public abstract void visit_component (WixComponent comp) throws GLib.Error;
+ public abstract void visit_feature (WixFeature feature, VisitState state) throws GLib.Error;
+ public abstract void visit_component_ref (WixComponentRef ref) throws GLib.Error;
+ public abstract void visit_remove_folder (WixRemoveFolder rm) throws GLib.Error;
+ public abstract void visit_registry_value (WixRegistryValue reg) throws GLib.Error;
+ public abstract void visit_file (WixFile reg) throws GLib.Error;
+ public abstract void visit_shortcut (WixShortcut shortcut) throws GLib.Error;
+ public abstract void visit_create_folder (WixCreateFolder folder) throws GLib.Error;
+ public abstract void visit_fragment (WixFragment fragment) throws GLib.Error;
+ public abstract void visit_directory_ref (WixDirectoryRef ref) throws GLib.Error;
+ public abstract void visit_sequence (WixSequence sequence) throws GLib.Error;
+ public abstract void visit_condition (WixCondition condition) throws GLib.Error;
+ public abstract void visit_upgrade (WixUpgrade upgrade) throws GLib.Error;
+ public abstract void visit_upgrade_version (WixUpgradeVersion version) throws GLib.Error;
+ public abstract void visit_action (WixAction action) throws GLib.Error;
+ public abstract void visit_text (WixText text) throws GLib.Error;
+ }
+
+ public abstract class WixNode: Object {
+ public WixElement? parent;
+
+ static construct {
+ Value.register_transform_func (typeof (WixNode), typeof (string), (ValueTransform)WixNode.value_to_string);
+ }
+
+ public abstract string to_string ();
+
+ public static void value_to_string (Value src, out Value dest) {
+ WixNode e = value_get_node (src);
+
+ dest = e.to_string ();
+ }
+
+ public static WixNode? value_get_node (Value value) {
+ if (! value.holds (typeof (WixNode)))
+ return null;
+
+ return (WixNode)value.get_object ();
+ }
+
+ public abstract void accept (WixNodeVisitor visitor) throws GLib.Error;
+ }
+
+ public class WixText: WixNode {
+ public string Text;
+
+ public WixText (string str) {
+ Text = str;
+ }
+
+ public override string to_string () {
+ return Text;
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_text (this);
+ }
+ }
+
+ public abstract class WixElement: WixNode {
+ public class string name;
+
+ public string Id { get; set; }
+ public List<WixNode> children;
+
+ // FIXME: would be nice if vala always initialize class member to null
+ // GObject copy class init so other class hashtable will be unrefed...??
+ protected class HashTable<string, Type> *child_types = null;
+ class construct {
+ child_types = new HashTable<string, Type> (str_hash, str_equal);
+ }
+
+ public class void add_child_types (HashTable<string, Type> table, Type[] child_types) {
+ foreach (var t in child_types) {
+ var n = ((WixElement) Object.new (t)).name;
+ table.insert (n, t);
+ }
+ }
+
+ public void add_child (WixNode e) {
+ e.parent = this;
+ children.append (e);
+ }
+
+ public G[] add_elements<G> (owned G[] a) {
+ // jeez, vala, took me a while to workaround generics & array issues..
+ var array = a;
+ var type = typeof (G);
+
+ if (this.get_type ().is_a (type))
+ array += this;
+
+ foreach (var c in children) {
+ if (c is WixElement)
+ array = (c as WixElement).add_elements<G> (array);
+ else if (c.get_type ().is_a (type))
+ array += c;
+ }
+
+ return array;
+ }
+
+ public G[] get_elements<G> () {
+ return add_elements<G> ({});
+ }
+
+ public G? find_element<G> (string Id) {
+ var type = typeof (G);
+ if (this.Id == Id && this.get_type () == type)
+ return this;
+
+ foreach (var c in children) {
+ if (c is WixElement) {
+ var e = (c as WixElement).find_element<G> (Id);
+ if (e != null)
+ return e;
+ }
+ }
+
+ return null;
+ }
+
+ public virtual void load (Xml.Node *node) throws Wixl.Error {
+ if (node->name != name)
+ throw new Error.FAILED ("%s: invalid node %s".printf (name, node->name));
+
+ for (var prop = node->properties; prop != null; prop = prop->next) {
+ if (prop->type == Xml.ElementType.ATTRIBUTE_NODE)
+ set_property (prop->name, get_attribute_content (prop));
+ }
+
+ for (var child = node->children; child != null; child = child->next) {
+ switch (child->type) {
+ case Xml.ElementType.COMMENT_NODE:
+ continue;
+ case Xml.ElementType.TEXT_NODE:
+ add_child (new WixText (child->content));
+ continue;
+ case Xml.ElementType.ELEMENT_NODE:
+ var t = child_types->lookup (child->name);
+ if (t != 0) {
+ var elem = Object.new (t) as WixElement;
+ elem.load (child);
+ add_child (elem);
+ continue;
+ }
+ break;
+ }
+ debug ("unhandled child %s node %s", name, child->name);
+ }
+ }
+
+ public override string to_string () {
+ var type = get_type ();
+ var klass = (ObjectClass)type.class_ref ();
+ var str = "<" + name;
+
+ var i = 0;
+ foreach (var p in klass.list_properties ()) {
+ if (!(ParamFlags.READABLE in p.flags))
+ continue;
+ var value = Value (p.value_type);
+ get_property (p.name, ref value);
+ var valstr = value.holds (typeof (string)) ?
+ (string)value : value.strdup_contents ();
+ if (valstr != null)
+ str += " " + p.name + "=\"" + valstr + "\"";
+ i += 1;
+ }
+
+ if (children.length () != 0) {
+ str += ">\n";
+
+ foreach (var child in children) {
+ str += child.to_string () + "\n";
+ }
+
+ return str + "</" + name + ">";
+ } else
+ return str + "/>";
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ foreach (var child in children)
+ child.accept (visitor);
+ }
+ }
+
+ public class WixFragment: WixElement {
+ static construct {
+ name = "Fragment";
+
+ add_child_types (child_types, {
+ typeof (WixDirectory),
+ typeof (WixDirectoryRef),
+ });
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_fragment (this);
+ }
+ }
+
+ public class WixProperty: WixElement {
+ static construct {
+ name = "Property";
+ }
+
+ public string Value { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_property (this);
+ }
+ }
+
+ public class WixPackage: WixElement {
+ static construct {
+ name = "Package";
+ }
+
+ public string Keywords { get; set; }
+ public string InstallerDescription { get; set; }
+ public string InstallerComments { get; set; }
+ public string Manufacturer { get; set; }
+ public string InstallerVersion { get; set; }
+ public string Languages { get; set; }
+ public string Compressed { get; set; }
+ public string SummaryCodepage { get; set; }
+ public string Comments { get; set; }
+ public string Description { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_package (this);
+ }
+ }
+
+ public class WixCreateFolder: WixElement {
+ static construct {
+ name = "CreateFolder";
+ }
+
+ public string Directory { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_create_folder (this);
+ }
+ }
+
+ public class WixIcon: WixElement {
+ static construct {
+ name = "Icon";
+ }
+
+ public string SourceFile { get; set; }
+
+ public File file;
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_icon (this);
+ }
+ }
+
+ public class WixShortcut: WixElement {
+ static construct {
+ name = "Shortcut";
+ }
+
+ public string Directory { get; set; }
+ public string Name { get; set; }
+ public string IconIndex { get; set; }
+ public string WorkingDirectory { get; set; }
+ public string Icon { get; set; }
+ public string Advertise { get; set; }
+
+ public Libmsi.Record record;
+
+ public WixComponent? get_component () {
+ if (parent is WixFile || parent is WixCreateFolder)
+ return parent.parent as WixComponent;
+ else
+ return parent as WixComponent;
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_shortcut (this);
+ }
+ }
+
+ public abstract class WixKeyElement: WixElement {
+ public string KeyPath { get; set; }
+ }
+
+ public class WixFile: WixKeyElement {
+ static construct {
+ name = "File";
+
+ add_child_types (child_types, { typeof (WixShortcut) });
+ }
+
+ public string DiskId { get; set; }
+ public string Source { get; set; }
+ public string Name { get; set; }
+
+ public Libmsi.Record record;
+ public File file;
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_file (this);
+ }
+ }
+
+ public class WixRegistryValue: WixKeyElement {
+ static construct {
+ name = "RegistryValue";
+ }
+
+ public string Root { get; set; }
+ public string Key { get; set; }
+ public string Type { get; set; }
+ public string Value { get; set; }
+ public string Name { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_registry_value (this);
+ }
+ }
+
+ public class WixRemoveFolder: WixElement {
+ static construct {
+ name = "RemoveFolder";
+ }
+
+ public string On { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_remove_folder (this);
+ }
+ }
+
+ public class WixFeature: WixElement {
+ static construct {
+ name = "Feature";
+
+ add_child_types (child_types, {
+ typeof (WixComponentRef),
+ typeof (WixFeature),
+ });
+ }
+
+ public string Level { get; set; }
+ public string Title { get; set; }
+ public string Description { get; set; }
+ public string Display { get; set; }
+ public string ConfigurableDirectory { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_feature (this, VisitState.ENTER);
+ base.accept (visitor);
+ visitor.visit_feature (this, VisitState.LEAVE);
+ }
+ }
+
+ public class WixComponentRef: WixElementRef<WixComponent> {
+ static construct {
+ name = "ComponentRef";
+ ref_type = typeof (WixComponent);
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_component_ref (this);
+ }
+ }
+
+ public class WixCondition: WixElement {
+ static construct {
+ name = "Condition";
+ }
+
+ public string Message { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_condition (this);
+ }
+ }
+
+ public abstract class WixAction: WixElement {
+ public string After { get; set; }
+ public string Before { get; set; }
+ public string Overridable { get; set; }
+ public string Sequence { get; set; }
+ public string Suppress { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_action (this);
+ }
+ }
+
+ public class WixRemoveExistingProducts: WixAction {
+ static construct {
+ name = "RemoveExistingProducts";
+ }
+ }
+
+ public class WixSequence: WixElement {
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_sequence (this);
+ }
+ }
+
+ public class WixInstallExecuteSequence: WixSequence {
+ static construct {
+ name = "InstallExecuteSequence";
+
+ add_child_types (child_types, {
+ typeof (WixRemoveExistingProducts),
+ });
+ }
+ }
+
+ public class WixUpgrade: WixElement {
+ static construct {
+ name = "Upgrade";
+
+ add_child_types (child_types, {
+ typeof (WixUpgradeVersion),
+ });
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_upgrade (this);
+ }
+ }
+
+ public class WixUpgradeVersion: WixElement {
+ static construct {
+ name = "UpgradeVersion";
+ }
+
+ public string Minimum { get; set; }
+ public string Maximum { get; set; }
+ public string IncludeMinimum { get; set; }
+ public string IncludeMaximum { get; set; }
+ public string OnlyDetect { get; set; }
+ public string Property { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_upgrade_version (this);
+ }
+ }
+
+ public class WixProduct: WixElement {
+ static construct {
+ name = "Product";
+
+ add_child_types (child_types, {
+ typeof (WixCondition),
+ typeof (WixDirectory),
+ typeof (WixFeature),
+ typeof (WixIcon),
+ typeof (WixInstallExecuteSequence),
+ typeof (WixMedia),
+ typeof (WixPackage),
+ typeof (WixProperty),
+ typeof (WixUpgrade),
+ });
+ }
+
+ public string Name { get; set; }
+ public string UpgradeCode { get; set; }
+ public string Language { get; set; }
+ public string Codepage { get; set; }
+ public string Version { get; set; }
+ public string Manufacturer { get; set; }
+
+ public WixProduct () {
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_product (this);
+ }
+ }
+
+ public class WixMedia: WixElement {
+ static construct {
+ name = "Media";
+ }
+
+ public string Cabinet { get; set; }
+ public string EmbedCab { get; set; }
+ public string DiskPrompt { get; set; }
+
+ public Libmsi.Record record;
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ visitor.visit_media (this);
+ }
+ }
+
+ public class WixComponent: WixElement {
+ static construct {
+ name = "Component";
+
+ add_child_types (child_types, {
+ typeof (WixRemoveFolder),
+ typeof (WixRegistryValue),
+ typeof (WixFile)
+ });
+ }
+
+ public string Guid { get; set; }
+ public WixKeyElement? key;
+
+ public List<WixFeature> in_feature;
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_component (this);
+ }
+ }
+
+ public class WixDirectory: WixElement {
+ static construct {
+ name = "Directory";
+
+ add_child_types (child_types, {
+ typeof (WixDirectory),
+ typeof (WixComponent),
+ });
+ }
+
+ public string Name { get; set; }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_directory (this);
+ }
+ }
+
+ public class WixElementRef<G>: WixElement {
+ public class Type ref_type;
+ public G? resolved;
+
+ // protected WixElementRef () {
+ // // FIXME vala: class init/construct fails, construct fails...
+ // ref_type = typeof (G);
+ // }
+ }
+
+ public class WixDirectoryRef: WixElementRef<WixDirectory> {
+ static construct {
+ name = "DirectoryRef";
+ ref_type = typeof (WixDirectory);
+
+ add_child_types (child_types, {
+ typeof (WixDirectory),
+ typeof (WixComponent),
+ });
+ }
+
+ public override void accept (WixNodeVisitor visitor) throws GLib.Error {
+ base.accept (visitor);
+ visitor.visit_directory_ref (this);
+ }
+ }
+
+ class WixRoot: WixElement {
+ static construct {
+ name = "Wix";
+
+ add_child_types (child_types, {
+ typeof (WixProduct),
+ typeof (WixFragment),
+ });
+ }
+ }
+
+} // Wixl
diff --git a/tools/wixl/wixl.vala b/tools/wixl/wixl.vala
new file mode 100644
index 0000000..057749d
--- /dev/null
+++ b/tools/wixl/wixl.vala
@@ -0,0 +1,92 @@
+using Posix;
+
+namespace Wixl {
+
+ static bool version;
+ static bool verbose;
+ static bool preproc;
+ static string output;
+ [CCode (array_length = false, array_null_terminated = true)]
+ static string[] files;
+ [CCode (array_length = false, array_null_terminated = true)]
+ static string[] defines;
+
+ private const OptionEntry[] options = {
+ { "version", 0, 0, OptionArg.NONE, ref version, N_("Display version number"), null },
+ { "verbose", 'v', 0, OptionArg.NONE, ref verbose, N_("Verbose output"), null },
+ { "output", 'o', 0, OptionArg.FILENAME, ref output, N_("Output file"), null },
+ { "define", 'D', 0, OptionArg.STRING_ARRAY, ref defines, N_("Define variable"), null },
+ { "only-preproc", 'E', 0, OptionArg.NONE, ref preproc, N_("Stop after the preprocessing stage"), null },
+ { "", 0, 0, OptionArg.FILENAME_ARRAY, ref files, null, N_("INPUT_FILE...") },
+ { null }
+ };
+
+ int main (string[] args) {
+ Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
+ Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
+ Intl.textdomain (Config.GETTEXT_PACKAGE);
+ GLib.Environment.set_application_name (Config.PACKAGE_NAME);
+
+ var parameter_string = _("- a msi building tool");
+ var opt_context = new OptionContext (parameter_string);
+ opt_context.set_help_enabled (true);
+ opt_context.add_main_entries (options, null);
+
+ try {
+ opt_context.parse (ref args);
+ } catch (OptionError.BAD_VALUE err) {
+ GLib.stdout.printf (opt_context.get_help (true, null));
+ exit (1);
+ } catch (OptionError error) {
+ warning (error.message);
+ }
+
+ if (version) {
+ GLib.stdout.printf ("%s\n", Config.PACKAGE_VERSION);
+ exit (0);
+ }
+
+ if (files.length < 1) {
+ GLib.stderr.printf (_("Please specify input files.\n"));
+ exit (1);
+ }
+
+ if (output == null && !preproc) {
+ GLib.stderr.printf (_("Please specify the output file.\n"));
+ exit (1);
+ }
+
+ try {
+ var builder = new WixBuilder ();
+
+ foreach (var d in defines) {
+ var def = d.split ("=", 2);
+ var name = def[0];
+ var value = def.length == 2 ? def[1] : "1";
+ builder.define_variable (name, value);
+ }
+
+ foreach (var arg in files) {
+ if (verbose)
+ print ("Loading %s...\n", arg);
+ var file = File.new_for_commandline_arg (arg);
+ builder.load_file (file, preproc);
+ builder.add_path (file.get_parent ().get_path ());
+ }
+
+ if (preproc)
+ return 0;
+
+ if (verbose)
+ print ("Building %s...\n", output);
+ var msi = builder.build ();
+ msi.build (output);
+ } catch (GLib.Error error) {
+ printerr (error.message + "\n");
+ return 1;
+ }
+
+ return 0;
+ }
+
+} // Wixl