Qt Installer Framework的解决方法不会覆盖现有安装

时间:2017-09-27 18:59:18

标签: qt qt-installer

这个问题是关于Qt安装程序框架的2.0版本。

此时,使用Qt安装程序框架的人员常识,如果没有自定义,您只能通过安装程序覆盖现有安装。这显然是为了解决在使用Qt框架时发生的一些问题。

然而,对于较小的,相对简单的项目,覆盖是完全正常的,比预先手动运行维护工具更方便。

我正在寻找一个涉及自定义UI +组件脚本的解决方案,该脚本向目标目录页面添加一个按钮,允许用户

  1. 删除指定的目录(如果存在)或
  2. 在该目录中运行维护工具。
  3. 最好是能够在目标目录中运行维护工具,让它自动删除一个给定的包,但是我意识到这需要一点点。

    我已阅读本网站上有关解决同一问题的其他问题的答案,但没有一个解决方案正常工作。我还想提一下,我已经启动并运行了一个组件脚本,但没有自定义UI。

5 个答案:

答案 0 :(得分:7)

我终于找到了一个可行的解决方案。

你需要做三件事:

  1. 组件脚本
  2. 目标目录页面的自定义UI,以及
  3. 自动点击卸载程序的控制器脚本。
  4. 我现在将逐字列出对我有用的东西(用我的项目特定的东西)。我的组件名为Atlas4500 Tuner

    config.xml中

    <?xml version="1.0" encoding="UTF-8"?>
    <Installer>
        <Name>Atlas4500 Tuner</Name>
        <Version>1.0.0</Version>
        <Title>Atlas4500 Tuner Installer</Title>
        <Publisher>EF Johnson Technologies</Publisher>
        <StartMenuDir>EF Johnson</StartMenuDir>
        <TargetDir>C:\Program Files (x86)\EF Johnson\Atlas4500 Tuner</TargetDir>
    </Installer>
    

    packages / Atlas4500 Tuner / meta / package.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <Package>
        <DisplayName>Atlas4500Tuner</DisplayName>
        <Description>Install the Atlas4500 Tuner</Description>
        <Version>1.0.0</Version>
        <ReleaseDate></ReleaseDate>
        <Default>true</Default>
        <Required>true</Required>
        <Script>installscript.qs</Script>
        <UserInterfaces>
            <UserInterface>targetwidget.ui</UserInterface>
        </UserInterfaces>
    </Package>
    

    自定义组件脚本包/ Atlas4500 Tuner / meta / installscript.qs

    var targetDirectoryPage = null;
    
    function Component() 
    {
        installer.gainAdminRights();
        component.loaded.connect(this, this.installerLoaded);
    }
    
    Component.prototype.createOperations = function() 
    {
        // Add the desktop and start menu shortcuts.
        component.createOperations();
        component.addOperation("CreateShortcut",
                               "@TargetDir@/Atlas4500Tuner.exe",
                               "@DesktopDir@/Atlas4500 Tuner.lnk",
                               "workingDirectory=@TargetDir@");
    
        component.addOperation("CreateShortcut",
                               "@TargetDir@/Atlas4500Tuner.exe",
                               "@StartMenuDir@/Atlas4500 Tuner.lnk",
                               "workingDirectory=@TargetDir@");
    }
    
    Component.prototype.installerLoaded = function()
    {
        installer.setDefaultPageVisible(QInstaller.TargetDirectory, false);
        installer.addWizardPage(component, "TargetWidget", QInstaller.TargetDirectory);
    
        targetDirectoryPage = gui.pageWidgetByObjectName("DynamicTargetWidget");
        targetDirectoryPage.windowTitle = "Choose Installation Directory";
        targetDirectoryPage.description.setText("Please select where the Atlas4500 Tuner will be installed:");
        targetDirectoryPage.targetDirectory.textChanged.connect(this, this.targetDirectoryChanged);
        targetDirectoryPage.targetDirectory.setText(installer.value("TargetDir"));
        targetDirectoryPage.targetChooser.released.connect(this, this.targetChooserClicked);
    
        gui.pageById(QInstaller.ComponentSelection).entered.connect(this, this.componentSelectionPageEntered);
    }
    
    Component.prototype.targetChooserClicked = function()
    {
        var dir = QFileDialog.getExistingDirectory("", targetDirectoryPage.targetDirectory.text);
        targetDirectoryPage.targetDirectory.setText(dir);
    }
    
    Component.prototype.targetDirectoryChanged = function()
    {
        var dir = targetDirectoryPage.targetDirectory.text;
        if (installer.fileExists(dir) && installer.fileExists(dir + "/maintenancetool.exe")) {
            targetDirectoryPage.warning.setText("<p style=\"color: red\">Existing installation detected and will be overwritten.</p>");
        }
        else if (installer.fileExists(dir)) {
            targetDirectoryPage.warning.setText("<p style=\"color: red\">Installing in existing directory. It will be wiped on uninstallation.</p>");
        }
        else {
            targetDirectoryPage.warning.setText("");
        }
        installer.setValue("TargetDir", dir);
    }
    
    Component.prototype.componentSelectionPageEntered = function()
    {
        var dir = installer.value("TargetDir");
        if (installer.fileExists(dir) && installer.fileExists(dir + "/maintenancetool.exe")) {
            installer.execute(dir + "/maintenancetool.exe", "--script=" + dir + "/scripts/auto_uninstall.qs");
        }
    }
    

    自定义目标目录小部件 packages / Atlas4500 Tuner / meta / targetwidget.ui

    <?xml version="1.0" encoding="UTF-8"?>
    <ui version="4.0">
     <class>TargetWidget</class>
     <widget class="QWidget" name="TargetWidget">
      <property name="geometry">
       <rect>
        <x>0</x>
        <y>0</y>
        <width>491</width>
        <height>190</height>
       </rect>
      </property>
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="minimumSize">
       <size>
        <width>491</width>
        <height>190</height>
       </size>
      </property>
      <property name="windowTitle">
       <string>Form</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout">
       <item>
        <widget class="QLabel" name="description">
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout">
         <item>
          <widget class="QLineEdit" name="targetDirectory">
           <property name="readOnly">
            <bool>true</bool>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QToolButton" name="targetChooser">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="minimumSize">
            <size>
             <width>0</width>
             <height>0</height>
            </size>
           </property>
           <property name="text">
            <string>...</string>
           </property>
          </widget>
         </item>
        </layout>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_2">
         <property name="topMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QLabel" name="warning">
           <property name="enabled">
            <bool>true</bool>
           </property>
           <property name="text">
            <string>TextLabel</string>
           </property>
          </widget>
         </item>
         <item>
          <spacer name="horizontalSpacer">
           <property name="orientation">
            <enum>Qt::Horizontal</enum>
           </property>
           <property name="sizeHint" stdset="0">
            <size>
             <width>40</width>
             <height>20</height>
            </size>
           </property>
          </spacer>
         </item>
        </layout>
       </item>
       <item>
        <spacer name="verticalSpacer">
         <property name="orientation">
          <enum>Qt::Vertical</enum>
         </property>
         <property name="sizeHint" stdset="0">
          <size>
           <width>20</width>
           <height>122</height>
          </size>
         </property>
        </spacer>
       </item>
      </layout>
     </widget>
     <resources/>
     <connections/>
    </ui>
    

    packages / Atlas4500 Tuner / data / scripts / auto_uninstall.qs

    // Controller script to pass to the uninstaller to get it to run automatically.
    // It's passed to the maintenance tool during installation if there is already an
    // installation present with: <target dir>/maintenancetool.exe --script=<target dir>/scripts/auto_uninstall.qs.
    // This is required so that the user doesn't have to see/deal with the uninstaller in the middle of
    // an installation.
    
    function Controller()
    {
        gui.clickButton(buttons.NextButton);
        gui.clickButton(buttons.NextButton);
    
        installer.uninstallationFinished.connect(this, this.uninstallationFinished);
    }
    
    Controller.prototype.uninstallationFinished = function()
    {
        gui.clickButton(buttons.NextButton);
    }
    
    Controller.prototype.FinishedPageCallback = function()
    {
        gui.clickButton(buttons.FinishButton);
    }
    

    这里的想法是检测当前目录是否包含安装,如果是,则使用只需单击它的控制器脚本运行该目录中的维护工具。

    请注意,我将控制器脚本放在作为实际组件数据一部分的脚本目录中。如果你有多个组件,你可能会做更干净的事情,但这就是我正在使用的。

    您应该能够自己复制这些文件,只需调整字符串即可使其正常工作。

答案 1 :(得分:2)

我找到了一种无需嵌入式控制器脚本即可使用 rationalcoder's solution 的方法!

您所要做的就是使用 purge 命令启动维护工具并将 yes 发送到其标准输入。 因此,将组件脚本中的这一行 installer.execute(dir + "/maintenancetool.exe", "--script=" + dir + "/scripts/auto_uninstall.qs"); 替换为这一行 installer.execute(dir + "/maintenancetool.exe", ["purge"], "yes");

这样,安装被替换,Windows 中的添加/删除程序 UI 不包含任何重复项。

对于 Windows 用户,请确保原始安装目录不是通过终端打开的。如果是,则不会删除该目录并且安装将失败。该目录将一直处于被窃听的状态,在您重新启动会话之前,您无法删除或访问它。

答案 2 :(得分:0)

您需要做的事情很少:

  • 通过添加此代码可以实现TargetDirectoryPage的过去 installer.setValue("RemoveTargetDir", false)

  • 允许您运行此代码的自定义UI(或消息框)。应在TargetDirectoryPage之后插入此UI。 // you need to append .exe on the maintenance for windows installation installer.execute(installer.findPath(installer.value("MaintenanceToolName"), installer.value("TargetDir")));

答案 3 :(得分:0)

我四处乱窜。将其放在installscript.qs的末尾。

component.addOperation("AppendFile", "@TargetDir@/cleanup.bat", "ping 127.0.0.1 -n 4\r\ndel /F /Q maintenancetool.exe && for /F %%L in ('reg query HKEY_USERS /v /f \"@TargetDir@\\maintenancetool.exe\" /d /t REG_SZ /s /e') do reg query %%L /v DisplayName && reg delete %%L /f\r\ndel /F /Q cleanup.bat && exit\r\n")
component.addOperation("Execute", "workingdirectory=@TargetDir@", "cmd", "/C", "start", "/B", "Cleaning up", "cmd /C ping 127.0.0.1 -n 2 > nul && cleanup.bat > nul")

这将删除waiting 3 seconds之后的maintenancetool.exe,这将导致安装程序仅警告目标文件夹不为空,而不是拒绝安装。它还会删除用于卸载程序的注册表项,因此不会在添加/删除程序中累积。显然,在删除了维护工具之后,您将无法再使用它进行卸载或更新之类的操作,但是我仅通过再次运行安装程序来支持它。仅在安装完成后才编写维护工具,并且使用cmd start cmd黑客工具是为了使安装程序不会注意到仍有步骤在运行。如果您有多个可选组件,则可能需要增加延迟或使其更健壮以检查是否仍在运行。

理论上,没有必要先编写批处理文件然后执行它。您应该能够直接执行命令。实际上,我还没有找到一种方法来正确地对引号进行转义以使正确的cmd实例评估正确的部分。

答案 4 :(得分:0)

好的,此答案基于最新的安装程序框架(3.2.2),我不确定它是否适用于较早的版本。

要覆盖目标目录,只需在配置文件中将 RemoveTargetDir 设置为false:

<RemoveTargetDir>false</RemoveTargetDir>

有效。

官方文档对此元素进行了如下解释:

如果在卸载时不应删除目标目录,则设置为false。

如果您不知道它的用法,这会有些混乱:

bool TargetDirectoryPage::validatePage()
{
    m_textChangeTimer.stop();

    if (!isComplete())
        return false;

    if (!isVisible())
        return true;

    ///
    /// NOTICE HERE:
    /// If you set RemoveTargetDir to false, function return true here.
    const QString remove = packageManagerCore()->value(QLatin1String("RemoveTargetDir"));
    if (!QVariant(remove).toBool())
        return true;

    const QString targetDir = this->targetDir();
    const QDir dir(targetDir);
    // the directory exists and is empty...
    if (dir.exists() && dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty())
        return true;

    const QFileInfo fi(targetDir);
    if (fi.isDir()) {
        QString fileName = packageManagerCore()->settings().maintenanceToolName();
#if defined(Q_OS_MACOS)
        if (QInstaller::isInBundle(QCoreApplication::applicationDirPath()))
            fileName += QLatin1String(".app/Contents/MacOS/") + fileName;
#elif defined(Q_OS_WIN)
        fileName += QLatin1String(".exe");
#endif

        QFileInfo fi2(targetDir + QDir::separator() + fileName);
        ///
        /// AND NOTICE HERE:
        /// Do exists check here.
        if (fi2.exists()) {
            return failWithError(QLatin1String("TargetDirectoryInUse"), tr("The directory you selected already "
                "exists and contains an installation. Choose a different target for installation."));
        }

        return askQuestion(QLatin1String("OverwriteTargetDirectory"),
            tr("You have selected an existing, non-empty directory for installation.\nNote that it will be "
            "completely wiped on uninstallation of this application.\nIt is not advisable to install into "
            "this directory as installation might fail.\nDo you want to continue?"));
    } else if (fi.isFile() || fi.isSymLink()) {
        return failWithError(QLatin1String("WrongTargetDirectory"), tr("You have selected an existing file "
            "or symlink, please choose a different target for installation."));
    }
    return true;
}

通知注释“在此处注意:”。