1#!/usr/bin/env python
2#  create-win-installer.py
3#
4#  This file is part of Charm, a task-based time tracking application.
5#
6#  Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
7#
8# Author: Hannah von Reth <hannah.vonreth@kdab.com>
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
23from __future__ import print_function
24
25import os
26import subprocess
27import sys
28import shutil
29import argparse
30import glob
31
32class DeployHelper(object):
33    def __init__(self, args):
34        self.args = args
35        self.gitDir = os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))
36
37        self.deployImage = os.path.realpath("./deployImage")
38        if not os.path.exists(self.args.buildDir):
39            self._die("Build dir %s does not exist" % self.args.buildDir)
40
41
42    def _die(self, message):
43        print(message, file=sys.stderr)
44        exit(1)
45
46    def _logExec(self, command):
47        print(command)
48        sys.stdout.flush()
49        subprocess.check_call(command, shell=True)
50
51    def _copyToImage(self, src, destDir = None, deploy = True):
52        if destDir is None:
53            destDir = self.deployImage
54        print("Copy %s to %s" % (src, destDir))
55        shutil.copy(src, destDir)
56        if deploy:
57            self._logExec("windeployqt --%s --dir \"%s\" --qmldir \"%s\"  \"%s\"" % (self.args.buildType, self.deployImage, self.gitDir, src))
58
59    def _sign(self, fileName):
60        if self.args.sign:
61            self._logExec("signtool.exe sign  -t http://timestamp.globalsign.com/scripts/timestamp.dll  -fd SHA256 -v \"%s\"" % fileName)
62
63    def cleanImage(self):
64        if os.path.exists(self.deployImage):
65            shutil.rmtree(self.deployImage)
66        os.makedirs(self.deployImage)
67
68    def _locateDll(self, dllName, fatal=True):
69        pathext = os.environ.get("PATHEXT")
70        os.environ["PATHEXT"] = ".dll"
71        found = shutil.which(dllName, path=os.pathsep.join([self.deployImage, os.environ.get("PATH")]))
72        os.environ["PATHEXT"] = pathext
73        if not found and fatal:
74            self._die("Unable to locate %s" % dllName)
75        return found
76
77    def deploy(self):
78        if self.args.deployDlls:
79            for dll in self.args.deployDlls.split(";"):
80                self._copyToImage(self._locateDll(dll))
81
82        app = os.path.join(self.args.buildDir, self.args.applicationFileName)
83        shutil.copy(app, self.deployImage)
84        self._logExec("windeployqt --%s --compiler-runtime --dir \"%s\" --qmldir \"%s\" \"%s\"" % (self.args.buildType, self.deployImage, self.gitDir, app))
85
86        for folder in self.args.pluginFolders.split(";"):
87            for f in glob.glob(os.path.join(self.args.buildDir, folder, "*.dll")):
88                self._copyToImage(f)
89
90        if self.args.deployOpenSSL:
91            foundSomething = False
92            for dll in ["libeay32.dll", "libssl32.dll", "ssleay32.dll" ]:
93                src = os.path.join(self.args.deployOpenSSL, dll )
94                if not os.path.exists(src):
95                    src = self._locateDll(dll, fatal=False)
96                if src:
97                    foundSomething = True
98                    print(src)
99                    self._copyToImage(src, deploy=False)
100            if not foundSomething:
101                self._die("Failed to deploy openssl")
102
103        if self.args.sign:
104            for f in glob.glob(os.path.join(self.deployImage, "**/*.exe"), recursive=True):
105                self._sign(f)
106            for f in glob.glob(os.path.join(self.deployImage, "**/*.dll"), recursive=True):
107                self._sign(f)
108
109    def makeInstaller(self):
110        if self.args.architecture == "x64":
111            programDir = "$PROGRAMFILES64"
112        else:
113            programDir = "$PROGRAMFILES"
114
115        defines = {}
116        defines["productName"] = self.args.productName
117        defines["companyName"] = self.args.companyName
118        defines["productVersion"] = self.args.productVersion
119        defines["setupname"] = self.args.installerName
120        defines["applicationName"] = os.path.basename(self.args.applicationFileName)
121        defines["applicationIcon"] = self.args.applicationIcon
122        defines["programFilesDir"] = programDir
123        defines["deployDir"] = self.deployImage
124        defines["productLicence"] = "" if not self.args.productLicence else "!insertmacro MUI_PAGE_LICENSE \"%s\"" % self.args.productLicence
125
126        redist = "vcredist_%s.exe" % self.args.architecture
127        if os.path.exists(os.path.join(self.deployImage, redist)):
128            defines["vcredist"] = "vcredist_%s.exe" % self.args.architecture
129        else:
130            defines["vcredist"] = "none"
131
132
133        definestring = ""
134        for key in defines:
135            definestring += " /D%s=\"%s\"" % (key, defines[key])
136
137        command = "makensis /NOCD %s %s" %\
138                  (definestring, os.path.join(os.path.dirname(__file__), "NullsoftInstaller.nsi"))
139        self._logExec(command)
140        installer = os.path.realpath(self.args.installerName)
141        self._sign(installer)
142        print("""Generated package file: %s """ % installer)
143
144
145if __name__ == "__main__":
146    parser = argparse.ArgumentParser()
147    parser.add_argument("--architecture", action = "store", default="x64" )
148    parser.add_argument("--buildType", action = "store", default="release" )
149    parser.add_argument("--installerName", action = "store" )
150    parser.add_argument("--applicationFileName", action = "store" )
151    parser.add_argument("--applicationIcon", action = "store" )
152    parser.add_argument("--buildDir", action = "store" )
153    parser.add_argument("--pluginFolders", action = "store", default="" )
154    parser.add_argument("--productName", action = "store" )
155    parser.add_argument("--companyName", action = "store" )
156    parser.add_argument("--productVersion", action = "store" )
157    parser.add_argument("--productLicence", action = "store" )
158    parser.add_argument("--deployDlls", action = "store" )
159    parser.add_argument("--deployOpenSSL", action = "store" )
160
161    parser.add_argument("--sign", action = "store_true", default=False)
162
163
164    args = parser.parse_args()
165
166    helper = DeployHelper(args)
167    helper.cleanImage()
168    helper.deploy()
169    helper.makeInstaller()
170