diff options
author | Paolo Bonzini <pbonzini@redhat.com> | 2013-01-09 18:23:54 +0100 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2013-01-09 18:23:54 +0100 |
commit | 0523a90f210bd443c64b92119de0ea30ae8bcaa0 (patch) | |
tree | 63fbe40850f951aa83c729f94e57df4f728f491a /tools | |
parent | a53f42abfbf6d3c0eb05c7cb7210dd537e55194a (diff) | |
download | msitools-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.vala | 590 | ||||
-rw-r--r-- | tools/wixl/msi.vala | 642 | ||||
-rw-r--r-- | tools/wixl/preprocessor.vala | 131 | ||||
-rw-r--r-- | tools/wixl/util.vala | 139 | ||||
-rw-r--r-- | tools/wixl/wix.vala | 598 | ||||
-rw-r--r-- | tools/wixl/wixl.vala | 92 |
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 |