diff options
| author | Marc-André Lureau <marcandre.lureau@gmail.com> | 2012-12-20 11:40:34 +0100 |
|---|---|---|
| committer | Marc-André Lureau <marcandre.lureau@gmail.com> | 2013-01-06 16:38:25 +0100 |
| commit | df135d151432f979b2ebbdb46b8f395b351fdbb1 (patch) | |
| tree | 15eefbad68bbb4bf7c1e9a4b8d7407b88546ff80 /src | |
| parent | bbe59abe52b9bccbfa31a31818a661d6e0731d3a (diff) | |
| download | msitools-df135d151432f979b2ebbdb46b8f395b351fdbb1.tar.gz msitools-df135d151432f979b2ebbdb46b8f395b351fdbb1.tar.xz msitools-df135d151432f979b2ebbdb46b8f395b351fdbb1.zip | |
Initial commit
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 31 | ||||
| -rw-r--r-- | src/config.vapi | 12 | ||||
| -rw-r--r-- | src/wixl.vala | 514 |
3 files changed, 557 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..611952f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,31 @@ +NULL = +bin_PROGRAMS = wixl + +AM_CFLAGS = -w + +AM_VALAFLAGS = \ + --vapidir=$(srcdir) --pkg config \ + --enable-experimental \ + --pkg gio-2.0 \ + --pkg libmsi-1.0 \ + --pkg libgcab-1.0 \ + --pkg libxml-2.0 \ + $(NULL) + +wixl_SOURCES = \ + util.vala \ + wixl.vala \ + $(NULL) + +AM_CPPFLAGS = \ + -include config.h \ + $(WIXL_CFLAGS) \ + -DG_LOG_DOMAIN=\""wixl"\" \ + -DLOCALEDIR=\""$(localedir)"\" \ + -DPKGDATADIR=\""$(pkgdatadir)"\" \ + -DPKGLIBDIR=\""$(pkglibdir)"\" \ + $(NULL) + +wixl_LDADD = \ + $(WIXL_LIBS) \ + $(NULL) diff --git a/src/config.vapi b/src/config.vapi new file mode 100644 index 0000000..96bf752 --- /dev/null +++ b/src/config.vapi @@ -0,0 +1,12 @@ +[CCode (prefix = "", lower_case_cprefix = "", cheader_filename = "config.h")] +namespace Config +{ + public const string PACKAGE_NAME; + public const string PACKAGE_STRING; + public const string PACKAGE_VERSION; + public const string GETTEXT_PACKAGE; + + public const string LOCALEDIR; + public const string PKGDATADIR; + public const string PKGLIBDIR; +} diff --git a/src/wixl.vala b/src/wixl.vala new file mode 100644 index 0000000..7d3784c --- /dev/null +++ b/src/wixl.vala @@ -0,0 +1,514 @@ +namespace Wixl { + + abstract class MsiTable: Object { + public string name; + + public abstract string create_sql (); + } + + class MsiTableAdminExecuteSequence: MsiTable { + construct { + name = "AdminExecuteSequence"; + } + + public override string create_sql () { + return """ +CREATE TABLE `AdminExecuteSequence` (`Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` INT PRIMARY KEY `Action`) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('CostInitialize', 800) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('FileCost', 900) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('CostFinalize', 1000) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallValidate', 1400) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallInitialize', 1500) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallAdminPackage', 3900) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallFiles', 4000) +INSERT INTO `AdminExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallFinalize', 6600) +"""; + } + } + + class MsiTableAdminUISequence: MsiTable { + construct { + name = "AdminUISequence"; + } + + public override string create_sql () { + return """ +CREATE TABLE `AdminUISequence` (`Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` INT PRIMARY KEY `Action`) +INSERT INTO `AdminUISequence` (`Action`, `Sequence`) VALUES ('CostInitialize', 800) +INSERT INTO `AdminUISequence` (`Action`, `Sequence`) VALUES ('FileCost', 900) +INSERT INTO `AdminUISequence` (`Action`, `Sequence`) VALUES ('CostFinalize', 1000) +INSERT INTO `AdminUISequence` (`Action`, `Sequence`) VALUES ('ExecuteAction', 1300) +"""; + } + } + + class MsiTableAdvtExecuteSequence: MsiTable { + construct { + name = "AdvtExecuteSequence"; + } + + public override string create_sql () { + return """ +CREATE TABLE `AdvtExecuteSequence` (`Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` INT PRIMARY KEY `Action`) +INSERT INTO `AdvtExecuteSequence` (`Action`, `Sequence`) VALUES ('CostInitialize', 800) +INSERT INTO `AdvtExecuteSequence` (`Action`, `Sequence`) VALUES ('CostFinalize', 1000) +INSERT INTO `AdvtExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallValidate', 1400) +INSERT INTO `AdvtExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallInitialize', 1500) +INSERT INTO `AdvtExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallFinalize', 6600) +INSERT INTO `AdvtExecuteSequence` (`Action`, `Sequence`) VALUES ('PublishFeatures', 6300) +INSERT INTO `AdvtExecuteSequence` (`Action`, `Sequence`) VALUES ('PublishProduct', 6400) +"""; + } + } + + class MsiTableError: MsiTable { + construct { + name = "Error"; + } + + public override string create_sql () { + return """ +CREATE TABLE `Error` (`Error` INT NOT NULL, `Message` CHAR(0) LOCALIZABLE PRIMARY KEY `Error`) +"""; + } + } + + class MsiTableFile: MsiTable { + construct { + name = "File"; + } + + public override string create_sql () { + return """ +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`) +"""; + } + } + + class MsiTableInstallExecuteSequence: MsiTable { + construct { + name = "InstallExecuteSequence"; + } + + public override string create_sql () { + return """ +CREATE TABLE `InstallExecuteSequence` (`Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` INT PRIMARY KEY `Action`) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('CostInitialize', 800) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('FileCost', 900) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('CostFinalize', 1000) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallValidate', 1400) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallInitialize', 1500) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('InstallFinalize', 6600) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('PublishFeatures', 6300) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('PublishProduct', 6400) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('ValidateProductID', 700) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('ProcessComponents', 1600) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('UnpublishFeatures', 1800) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('RegisterUser', 6000) +INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) VALUES ('RegisterProduct', 6100) +"""; + } + } + + class MsiTableInstallUISequence: MsiTable { + construct { + name = "InstallUISequence"; + } + + public override string create_sql () { + return """ +CREATE TABLE `InstallUISequence` (`Action` CHAR(72) NOT NULL, `Condition` CHAR(255), `Sequence` INT PRIMARY KEY `Action`) +INSERT INTO `InstallUISequence` (`Action`, `Sequence`) VALUES ('CostInitialize', 800) +INSERT INTO `InstallUISequence` (`Action`, `Sequence`) VALUES ('FileCost', 900) +INSERT INTO `InstallUISequence` (`Action`, `Sequence`) VALUES ('CostFinalize', 1000) +INSERT INTO `InstallUISequence` (`Action`, `Sequence`) VALUES ('ExecuteAction', 1300) +INSERT INTO `InstallUISequence` (`Action`, `Sequence`) VALUES ('ValidateProductID', 700) +"""; + } + } + + class MsiTableMedia: MsiTable { + construct { + name = "Media"; + } + + public override string create_sql () { + return """ +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`) +"""; + } + } + + class MsiTableProperty: MsiTable { + construct { + name = "Property"; + } + + public override string create_sql () { + return """ +CREATE TABLE `Property` (`Property` CHAR(72) NOT NULL, `Value` CHAR(0) NOT NULL LOCALIZABLE PRIMARY KEY `Property`) +INSERT INTO `Property` (`Property`, `Value`) VALUES ('Manufacturer', 'Acme Ltd.') +INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{ABCDABCD-86C7-4D14-AEC0-86416A69ABDE}') +INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductLanguage', '1033') +INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductName', 'Foobar 1.0') +INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductVersion', '1.0.0') +INSERT INTO `Property` (`Property`, `Value`) VALUES ('UpgradeCode', '{ABCDABCD-7349-453F-94F6-BCB5110BA4FD}') +INSERT INTO `Property` (`Property`, `Value`) VALUES ('WixPdbPath', 'SampleFirst.wixpdb') +"""; + } + } + + 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 MsiTable_Validation: MsiTable { + construct { + name = "_Validation"; + } + + public override string create_sql () { + return ""; + } + } + + class MsiDatabase: Object { + public MsiSummaryInfo info; + 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.UUID, uuid_generate ()); + 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); + + foreach (var t in new MsiTable[] { + new MsiTableAdminExecuteSequence (), + new MsiTableAdminUISequence (), + new MsiTableAdminExecuteSequence (), + new MsiTableError (), + new MsiTableFile (), + new MsiTableInstallExecuteSequence (), + new MsiTableInstallUISequence (), + new MsiTableMedia (), + new MsiTableProperty (), + new MsiTable_Validation () + }) { + 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)) { + var sql = table.create_sql (); + + foreach (var stmt in sql.split ("\n")) { + if (stmt.length == 0) + continue; + var query = new Libmsi.Query (db, stmt); + query.execute (null); + } + } + + db.commit (); + } + } + + + + public abstract class WixElementVisitor: Object { + public abstract void visit_product (WixProduct product) throws GLib.Error; + public abstract void visit_package (WixPackage package) throws GLib.Error; + } + + public class WixElement: Object { + static construct { + Value.register_transform_func (typeof (WixElement), typeof (string), (ValueTransform)WixElement.value_to_string); + } + + public string to_string () { + var type = get_type (); + var klass = (ObjectClass)type.class_ref (); + var str = type.name () + " {"; + + var i = 0; + foreach (var p in klass.list_properties ()) { + var value = Value (p.value_type); + get_property (p.name, ref value); + var valstr = value.holds (typeof (string)) ? + (string)value : value.strdup_contents (); + str += (i == 0 ? "" : ", ") + p.name + ": " + valstr; + i += 1; + } + + return str + "}"; + } + + public static void value_to_string (Value src, out Value dest) { + WixElement e = value_get_element (src); + + dest = e.to_string (); + } + + public static WixElement? value_get_element (Value value) { + if (! value.holds (typeof (WixElement))) + return null; + + return (WixElement)value.get_object (); + } + + public virtual void accept (WixElementVisitor visitor) throws GLib.Error { + } + } + + public class WixPackage: WixElement { + public string Id { get; set; } + 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 void load (Xml.Node *node) throws Wixl.Error { + if (node->name != "Package") + throw new Error.FAILED ("invalid node"); + + 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: + case Xml.ElementType.TEXT_NODE: + continue; + case Xml.ElementType.ELEMENT_NODE: + switch (child->name) { + } + break; + } + debug ("unhandled node %s", child->name); + } + } + + public override void accept (WixElementVisitor visitor) throws GLib.Error { + visitor.visit_package (this); + } + } + + public class WixProduct: WixElement { + public string Name { get; set; } + public string Id { 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 WixPackage package { get; set; } + + public WixProduct () { + } + + public void load (Xml.Node *node) throws Wixl.Error { + if (node->name != "Product") + throw new Error.FAILED ("invalid node"); + + 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: + case Xml.ElementType.TEXT_NODE: + continue; + case Xml.ElementType.ELEMENT_NODE: + switch (child->name) { + case "Package": + package = new WixPackage (); + package.load (child); + continue; + } + break; + } + debug ("unhandled node %s", child->name); + } + } + + public override void accept (WixElementVisitor visitor) throws GLib.Error { + visitor.visit_product (this); + + package.accept (visitor); + } + } + + class WixRoot: WixElement { + public string xmlns { get; set; } + public WixProduct product { get; set; } + + public WixRoot () { + } + + public void load (Xml.Doc *doc) throws Wixl.Error { + var root = doc->children; + + if (root->name != "Wix") + throw new Error.FAILED ("invalid XML document"); + + if (root->ns != null) + xmlns = root->ns->href; + + for (var child = root->children; child != null; child = child->next) { + switch (child->type) { + case Xml.ElementType.COMMENT_NODE: + case Xml.ElementType.TEXT_NODE: + continue; + case Xml.ElementType.ELEMENT_NODE: + switch (child->name) { + case "Product": + product = new WixProduct (); + product.load (child); + continue; + } + break; + } + + debug ("unhandled node %s", child->name); + } + } + + public override void accept (WixElementVisitor visitor) throws GLib.Error { + product.accept (visitor); + } + } + + class WixBuilder: WixElementVisitor { + WixRoot root; + MsiDatabase db; + + public WixBuilder (WixRoot root) { + this.root = root; + } + + public MsiDatabase build () throws GLib.Error { + db = new MsiDatabase (); + root.accept (this); + return db; + } + + public override void visit_product (WixProduct product) throws GLib.Error { + db.info.set_codepage (int.parse (product.Codepage)); + db.info.set_author (product.Manufacturer); + } + + public override void visit_package (WixPackage package) throws GLib.Error { + db.info.set_keywords (package.Keywords); + db.info.set_subject (package.Description); + db.info.set_comments (package.Comments); + } + } + + 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); + + try { + var file = File.new_for_commandline_arg (args[1]); + string data; + FileUtils.get_contents (file.get_path (), out data); + var doc = Xml.Parser.read_memory (data, data.length); + var root = new WixRoot (); + root.load (doc); + var builder = new WixBuilder (root); + var msi = builder.build (); + msi.build ("foo.msi"); + } catch (GLib.Error error) { + printerr (error.message + "\n"); + return 1; + } + + return 0; + } + +} // Wixl |
