Self-Extracting Installer
From qtnode
A common issue faced by developers -- especially cross-platform developers and open-source developers -- is that there isn't a wide selection of suitable installation tools. However, using a static build of Qt, you can make a simple installer that can be compiled on both UNIX and Windows platforms. This installer works as a generic self-extracting archive, so you can store any files inside of it; it could be a Qt program, of course, but it could also be a program written using a different toolkit or even something that isn't a program at all.
Note that Mac OS X doesn't need this kind of installer because the typical install procedure is to simply drag the application bundle from the disk image to the Applications folder.
This source code is provided under the GPL and is Copyright © 2006 by Adam Higerd. See the "Licensing Issues" section below for details.
Contents |
Source Files
stub/main.cpp
The stub reads its own binary and extracts any QCompressed files found within it.
/**
* Self-Extracting Installer Stub
* Copyright (C) 2006 Adam Higerd
*
* This stub is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <QByteArray>
#include <QFile>
#include <iostream>
using namespace std;
char magic[]="QCompressec";
int main(int argc, char** argv) {
char c, state=0;
int l=0;
bool first=0;
// Since the magic string can't appear directly in the binary,
// undo the sanitizing tweak made to the constant above.
magic[10]++;
// Open the current executable as a data file
QFile f(argv[0]);
f.open(QIODevice::ReadOnly);
// Ignore the stub by finding the first instance of the magic string
while(f.getChar(&c)) {
if(c==magic[state]) state++; else state=0;
if(state==11) break;
}
// If the magic string wasn't found, exit with error status
if(state!=11) return 1;
// Loop through the rest of the file
while(!f.atEnd()) {
// Consume the magic string. If this is the first file,
// skip this step because we consumed it earlier.
if(first) f.read(11); else first=1;
// Read the filename and file length
QByteArray filename = f.readLine();
filename.chop(1);
f.read((char*)(&l), sizeof(int));
// Indicate activity to user
cout << "Filename: " << filename.data() << endl;
// Open output file and write decompressed data
QFile o(filename);
o.open(QIODevice::WriteOnly);
o.write(qUncompress(f.read(l)));
o.close();
}
// If you wish to add post-extraction code, do it here.
// Return success status
return 0;
}
qcompress/main.cpp
The QCompress tool compresses and appends files to the installer stub.
/**
* Self-Extracting Installer Packing Tool
* Copyright (C) 2006 Adam Higerd
*
* This tool is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <QByteArray>
#include <QFile>
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char** argv) {
// If not provided the correct arguments, show usage text
if(argc!=3) {
cout << "Usage: " << argv[0] << " input output" << endl;
cout << "input: file to be compressed" << endl;
cout << "output: archive to which compressed file will be appended" << endl;
return 2;
}
// Open input and output files
QFile i(argv[1]), o(argv[2]);
i.open(QIODevice::ReadOnly);
o.open(QIODevice::Append);
// Read and compress input file
QByteArray ba = qCompress(i.readAll(),9);
// Write header metadata and concatenate file
o.write("QCompressed",11); // Magic string
o.write(argv[1], strlen(argv[1])); // Filename
o.write("\n",1); // Terminator
int l = ba.length();
o.write((char*)(&l),sizeof(int)); // Compressed length
o.write(ba); // Compressed data
// Close files and exit cleanly
i.close();
o.close();
return 0;
}
Building the Tools
- Build the stub project statically. If you don't customize the installer with a GUI, you can remove the QtGui library from the .pro file.
- Strip the stub binary using the strip --strip-unneeded command.
- Build the qcompress project (static or dynamic doesn't matter).
- (Optional) You may strip the qcompress binary if you like.
Making an Installer
- cp stub/stub ./installer-name
- Append each needed file to the installer using qcompress/qcompress filename ./installer-name.
- If your application is linked dynamically, don't forget to include QtCore, QtGui, and any other libraries you need.
- The installer extracts files into the paths specified in the qcompress command line. For this reason, relative paths are recommended.
Customizing the Installer
Since the installer stub is written in Qt and licensed under the GPL, you can customize it however you like. Common modifications include:
- Adding a "working" GUI to the extraction loop.
- Launching a second installer just before return 0; to configure the application.
- Alternately, putting the aforementioned configuration utility inside the stub, before return 0;.
- Adding a license agreement before the extraction loop begins.
- Moving files around according to installation preferences or system configuration when the extractor's default paths may be insufficient.
A later version of the stub core may include conditional compilation options for displaying a GUI during the installation process without requiring customization.
Note: The string "QCompressed" is magic to the extractor. You must ensure it never appears in the stub binary.
TODO: Offer Win32 and Linux builds of the basic stub and qcompress tool so developers don't need to build a static Qt library.
Licensing Issues
The installer stub and QCompress tool are provided, free of charge, under the terms of the GPL. This means that any customizations you make to the source code of the stub or compressor must also be distributed subject to the terms of the GPL.
This license, however, does not affect the contents of the self-extracting archive. The contents of the archive can be published under whatever license you like; using these tools does not impose any requirements on what you can do with them. In particular, you can use this installer to install non-GPL software, if you so desire, with no restrictions.
If you're worried about the installation procedure of your application being open-source, or if you're using another installation tool (i.e. InstallShield or MSI) that you can't use the source code from, one possible solution is to include a system() call after extraction to launch a second-stage installer.