8283707: Support <major.minor.update.build> version format on Windows
Reviewed-by: almatvee
This commit is contained in:
parent
96a542feb2
commit
977e09489d
@ -129,6 +129,10 @@ ifeq ($(call isTargetOs, windows), true)
|
||||
|
||||
JPACKAGE_TARGETS += $(BUILD_LIB_JPACKAGE)
|
||||
|
||||
JPACKAGE_WIXHELPER_SRC := \
|
||||
$(call FindSrcDirsForComponent, jdk.jpackage, libwixhelper) \
|
||||
$(call FindSrcDirsForComponent, jdk.jpackage, common)
|
||||
|
||||
# Build Wix custom action helper
|
||||
# Output library in resources dir, and symbols in the object dir
|
||||
$(eval $(call SetupJdkLibrary, BUILD_LIB_WIXHELPER, \
|
||||
@ -136,11 +140,12 @@ ifeq ($(call isTargetOs, windows), true)
|
||||
OUTPUT_DIR := $(JPACKAGE_OUTPUT_DIR), \
|
||||
SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libwixhelper, \
|
||||
OPTIMIZATION := LOW, \
|
||||
SRC := $(JPACKAGE_WIXHELPER_SRC), \
|
||||
CXXFLAGS := $(call JpackageWithStaticCrt, $(CXXFLAGS_JDKLIB)) \
|
||||
$(JPACKAGE_CXXFLAGS_windows), \
|
||||
$(addprefix -I, $(JPACKAGE_WIXHELPER_SRC)) $(JPACKAGE_CXXFLAGS_windows), \
|
||||
LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK), \
|
||||
LIBS := $(LIBCXX), \
|
||||
LIBS_windows := msi.lib Shlwapi.lib User32.lib, \
|
||||
LIBS_windows := User32.lib, \
|
||||
))
|
||||
|
||||
JPACKAGE_TARGETS += $(BUILD_LIB_WIXHELPER)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -36,7 +36,6 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamWriter;
|
||||
@ -105,7 +104,7 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
}
|
||||
|
||||
// Only needed if we using CA dll, so Wix can find it
|
||||
if (withInstallDirChooserDlg) {
|
||||
if (withCustomActionsDll) {
|
||||
wixPipeline.addLightOptions("-b",
|
||||
getConfigRoot().toAbsolutePath().toString());
|
||||
}
|
||||
@ -119,7 +118,7 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
void addFilesToConfigRoot() throws IOException {
|
||||
super.addFilesToConfigRoot();
|
||||
|
||||
if (withInstallDirChooserDlg) {
|
||||
if (withCustomActionsDll) {
|
||||
String fname = "wixhelper.dll"; // CA dll
|
||||
try (InputStream is = OverridableResource.readDefault(fname)) {
|
||||
Files.copy(is, getConfigRoot().resolve(fname));
|
||||
@ -481,6 +480,7 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder {
|
||||
private boolean withInstallDirChooserDlg;
|
||||
private boolean withShortcutPromptDlg;
|
||||
private boolean withLicenseDlg;
|
||||
private boolean withCustomActionsDll = true;
|
||||
private List<CustomDialog> customDialogs;
|
||||
|
||||
private static final BundlerParamInfo<Boolean> INSTALLDIR_CHOOSER
|
||||
|
@ -31,7 +31,6 @@
|
||||
|
||||
<Fragment>
|
||||
|
||||
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
|
||||
<CustomAction Id="JpCheckInstallDir" BinaryKey="JpCaDll" DllEntry="CheckInstallDir" />
|
||||
|
||||
<UI>
|
||||
|
@ -65,6 +65,10 @@
|
||||
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
||||
<?endif?>
|
||||
|
||||
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
|
||||
|
||||
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
|
||||
|
||||
<!-- Standard required root -->
|
||||
<Directory Id="TARGETDIR" Name="SourceDir"/>
|
||||
|
||||
@ -114,13 +118,18 @@
|
||||
<?endif?>
|
||||
|
||||
<?ifndef JpAllowUpgrades ?>
|
||||
<Custom Action="JpDisallowUpgrade" After="FindRelatedProducts">JP_UPGRADABLE_FOUND</Custom>
|
||||
<Custom Action="JpDisallowUpgrade" After="JpFindRelatedProducts">JP_UPGRADABLE_FOUND</Custom>
|
||||
<?endif?>
|
||||
<?ifndef JpAllowDowngrades ?>
|
||||
<Custom Action="JpDisallowDowngrade" After="FindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
|
||||
<Custom Action="JpDisallowDowngrade" After="JpFindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
|
||||
<?endif?>
|
||||
<RemoveExistingProducts Before="CostInitialize"/>
|
||||
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<InstallUISequence>
|
||||
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
|
||||
</InstallUISequence>
|
||||
|
||||
</Product>
|
||||
</Wix>
|
||||
|
233
src/jdk.jpackage/windows/native/common/MsiCA.cpp
Normal file
233
src/jdk.jpackage/windows/native/common/MsiCA.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "MsiCA.h"
|
||||
#include "MsiDb.h"
|
||||
#include "MsiUtils.h"
|
||||
#include "FileUtils.h"
|
||||
#include "ErrorHandling.h"
|
||||
#include "Toolbox.h"
|
||||
|
||||
|
||||
#pragma comment(lib, "msi.lib")
|
||||
|
||||
|
||||
namespace msi {
|
||||
|
||||
tstring CAImpl::getProperty(const tstring& name) const {
|
||||
return getPropertyFromCustomAction(handle, name);
|
||||
}
|
||||
|
||||
|
||||
void CAImpl::setProperty(const tstring& name, const tstring& value) {
|
||||
if (value.empty()) {
|
||||
JP_THROW(tstrings::any() << "Attempt to assign empty value to '"
|
||||
<< name << "' MSI property");
|
||||
}
|
||||
|
||||
LOG_TRACE(tstrings::any() << "Setting MSI property '" << name <<
|
||||
"' to '" << value << "'");
|
||||
const UINT status = MsiSetProperty(handle, name.c_str(), value.c_str());
|
||||
if (status != ERROR_SUCCESS) {
|
||||
JP_THROW(msi::Error(tstrings::any() << "MsiSetProperty(" << name
|
||||
<< ", " << value << ") failed", status));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CAImpl::removeProperty(const tstring& name) {
|
||||
LOG_TRACE(tstrings::any() << "Removing MSI property '" << name << "'");
|
||||
const UINT status = MsiSetProperty(handle, name.c_str(), NULL);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
JP_THROW(msi::Error(tstrings::any() << "MsiSetProperty(" << name
|
||||
<< ", NULL) failed", status));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Guid CAFacade::getProductCode() const {
|
||||
return impl.getProperty(_T("ProductCode"));
|
||||
}
|
||||
|
||||
|
||||
bool CAFacade::isInMode(MSIRUNMODE v) const {
|
||||
return MsiGetMode(impl.getHandle(), v) != FALSE;
|
||||
}
|
||||
|
||||
|
||||
tstring CAFacade::getModes() const {
|
||||
tstring modes;
|
||||
// Iterate all modes in the range [MSIRUNMODE_ADMIN, MSIRUNMODE_COMMIT]
|
||||
for (int mode = MSIRUNMODE_ADMIN; mode != MSIRUNMODE_COMMIT + 1; ++mode) {
|
||||
modes.insert(modes.end(), isInMode(MSIRUNMODE(mode)) ?
|
||||
_T('1') : _T('0'));
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
|
||||
void CAFacade::doAction(const tstring& name) const {
|
||||
const UINT status = MsiDoAction(impl.getHandle(), name.c_str());
|
||||
if (status != ERROR_SUCCESS) {
|
||||
JP_THROW(msi::Error(tstrings::any() << "MsiDoAction(" << name
|
||||
<< ") failed", status));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tstring CAFacade::normalizeDirectoryPath(tstring v) {
|
||||
if (v.empty()) {
|
||||
return v;
|
||||
}
|
||||
std::replace(v.begin(), v.end(), '/', '\\');
|
||||
return FileUtils::removeTrailingSlash(v) + _T("\\");
|
||||
}
|
||||
|
||||
|
||||
CA& CA::setPropertyIfEmpty(const tstring& name, const tstring& v) {
|
||||
if (getProperty(name).empty()) {
|
||||
setProperty(name, v);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
tstring DeferredCA::getArg() const {
|
||||
if (isInMode(MSIRUNMODE_SCHEDULED) || caArgPropertyName.empty()) {
|
||||
// Details on accessing MSI properties from deferred custom actions:
|
||||
// http://blogs.technet.com/b/alexshev/archive/2008/03/25/property-does-not-exist-or-empty-when-accessed-from-deferred-custom-action.aspx
|
||||
// http://stackoverflow.com/questions/17988392/unable-to-fetch-the-install-location-property-in-a-deferred-custom-action
|
||||
// http://stackoverflow.com/questions/11233267/how-to-pass-customactiondata-to-a-customaction-using-wix
|
||||
return impl.getProperty(_T("CustomActionData"));
|
||||
}
|
||||
|
||||
return impl.getProperty(caArgPropertyName);
|
||||
}
|
||||
|
||||
|
||||
tstring DeferredCA::getParsedArg(const tstring& name) const {
|
||||
const auto entry = theParsedArgs.find(name);
|
||||
if (entry == theParsedArgs.end()) {
|
||||
JP_THROW(tstrings::any() << "Argument << '" << name
|
||||
<< "' not found.");
|
||||
}
|
||||
return entry->second;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
std::pair<tstring, tstring> parseArg(const tstring& v) {
|
||||
const auto pos = v.find(_T('='));
|
||||
if (pos == tstring::npos) {
|
||||
JP_THROW(tstrings::any() << "Missing expected '=' character in ["
|
||||
<< v << "] string.");
|
||||
}
|
||||
return std::pair<tstring, tstring>(v.substr(0, pos), v.substr(pos + 1));
|
||||
}
|
||||
|
||||
void parseArgsImpl(DeferredCA::ArgsCtnr& dst, const tstring& src) {
|
||||
const tstring_array pairs = tstrings::split(src, _T("*"));
|
||||
for(auto it = pairs.begin(), end = pairs.end(); it != end; ++it) {
|
||||
const auto pair = parseArg(*it);
|
||||
dst[pair.first] = pair.second;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
void DeferredCA::parseArgs(ArgsCtnr& dst, const tstring& src) {
|
||||
DeferredCA::ArgsCtnr tmp;
|
||||
|
||||
const auto end = src.find(_T("**"));
|
||||
if (end != tstring::npos) {
|
||||
parseArgsImpl(tmp, src.substr(0, end));
|
||||
tmp[tstring()] = src.substr(end + 2);
|
||||
} else {
|
||||
parseArgsImpl(tmp, src);
|
||||
}
|
||||
|
||||
tmp.insert(dst.begin(), dst.end());
|
||||
tmp.swap(dst);
|
||||
}
|
||||
|
||||
|
||||
MsiLogAppender::MsiLogAppender(MSIHANDLE h): handle(h),
|
||||
ctorThread(GetCurrentThreadId()) {
|
||||
|
||||
}
|
||||
|
||||
void MsiLogAppender::append(const LogEvent& v) {
|
||||
const LPCTSTR format = _T("[%02u:%02u:%02u.%03u%s%s:%u (%s)] %s: %s");
|
||||
|
||||
tstring ctxInfo = _T(" ");
|
||||
if (v.tid != ctorThread) {
|
||||
ctxInfo = (tstrings::any() << " (TID: " << v.tid << ") ").tstr();
|
||||
}
|
||||
|
||||
const tstring buf = tstrings::unsafe_format(format,
|
||||
unsigned(v.ts.wHour), unsigned(v.ts.wMinute), unsigned(v.ts.wSecond), unsigned(v.ts.wMilliseconds), // time
|
||||
ctxInfo.c_str(),
|
||||
v.fileName.c_str(), v.lineNum, v.funcName.c_str(),
|
||||
v.logLevel.c_str(),
|
||||
v.message.c_str());
|
||||
|
||||
DatabaseRecord r(1);
|
||||
r.setString(0, _T("Java [1]"));
|
||||
r.setString(1, buf);
|
||||
|
||||
MsiProcessMessage(handle, INSTALLMESSAGE_INFO, r.getHandle());
|
||||
}
|
||||
|
||||
|
||||
MsiLogTrigger::MsiLogTrigger(MSIHANDLE h):
|
||||
msiLogAppender(h),
|
||||
oldLogAppender(Logger::defaultLogger().getAppender()),
|
||||
teeLogAppender(&msiLogAppender, &oldLogAppender) {
|
||||
Logger::defaultLogger().setAppender(teeLogAppender);
|
||||
}
|
||||
|
||||
|
||||
MsiLogTrigger::~MsiLogTrigger() {
|
||||
Logger::defaultLogger().setAppender(oldLogAppender);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
MSIHANDLE openDatabase(const CA& ca) {
|
||||
MSIHANDLE h = MsiGetActiveDatabase(ca.getHandle());
|
||||
if (h == NULL) {
|
||||
JP_THROW(Error(std::string("MsiGetActiveDatabase() failed"),
|
||||
ERROR_FUNCTION_FAILED));
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Database::Database(const CA& ca): msiPath(_T("*CA*")),
|
||||
dbHandle(openDatabase(ca)) {
|
||||
}
|
||||
|
||||
} // namespace msi
|
298
src/jdk.jpackage/windows/native/common/MsiCA.h
Normal file
298
src/jdk.jpackage/windows/native/common/MsiCA.h
Normal file
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef MsiCA_h
|
||||
#define MsiCA_h
|
||||
|
||||
#include <windows.h>
|
||||
#include <Msi.h>
|
||||
#include <msidefs.h>
|
||||
#include <msiquery.h>
|
||||
#include <map>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Guid.h"
|
||||
|
||||
|
||||
/**
|
||||
* Helpers to implement custom actions (CA).
|
||||
*/
|
||||
namespace msi {
|
||||
|
||||
/**
|
||||
* Return values from CA functions.
|
||||
*/
|
||||
struct CAStatus {
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072(v=vs.85).aspx
|
||||
enum Values {
|
||||
Success = ERROR_SUCCESS,
|
||||
|
||||
// Abort installation session.
|
||||
UserExit = ERROR_INSTALL_USEREXIT,
|
||||
|
||||
// Unexpected error interrupted installation session.
|
||||
FatalError = ERROR_INSTALL_FAILURE,
|
||||
|
||||
// Complete installation session without running further actions.
|
||||
ExitNoError = ERROR_NO_MORE_ITEMS
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper around MSIHANDLE passed in CA function by MSI service.
|
||||
* Provides basic functionality to read/write property into the current MSI
|
||||
* session.
|
||||
*/
|
||||
class CAImpl {
|
||||
public:
|
||||
explicit CAImpl(MSIHANDLE h): handle(h) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value of a property with the given name.
|
||||
* Returns empty string if property with the given name doesn't exist.
|
||||
* Throws exception if error occurs.
|
||||
*/
|
||||
tstring getProperty(const tstring& name) const;
|
||||
|
||||
/**
|
||||
* Sets property value.
|
||||
* Throws exception if error occurs.
|
||||
* Throws exception if value is empty string.
|
||||
*/
|
||||
void setProperty(const tstring& name, const tstring& v);
|
||||
|
||||
/**
|
||||
* Removes property.
|
||||
* Throws exception if error occurs.
|
||||
*/
|
||||
void removeProperty(const tstring& name);
|
||||
|
||||
MSIHANDLE getHandle() const {
|
||||
return handle;
|
||||
}
|
||||
|
||||
private:
|
||||
CAImpl(const CAImpl&);
|
||||
CAImpl& operator=(const CAImpl&);
|
||||
private:
|
||||
MSIHANDLE handle;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Provides common functionality for deferred and immediate CAs.
|
||||
*/
|
||||
class CAFacade: public CAStatus {
|
||||
public:
|
||||
explicit CAFacade(MSIHANDLE h, UINT* status=NULL): impl(h), status(status) {
|
||||
}
|
||||
|
||||
Guid getProductCode() const;
|
||||
|
||||
bool isInMode(MSIRUNMODE v) const;
|
||||
|
||||
// Debug
|
||||
tstring getModes() const;
|
||||
|
||||
void exitStatus(CAStatus::Values v) {
|
||||
if (status) {
|
||||
*status = v;
|
||||
}
|
||||
}
|
||||
|
||||
void doAction(const tstring& name) const;
|
||||
|
||||
// Replaces all forward slashes with back slashes and ensures
|
||||
// the last character is a backslash.
|
||||
// Terminating directory paths with backslash is standard for MSI.
|
||||
// Do nothing if 'path' is empty string.
|
||||
static tstring normalizeDirectoryPath(tstring path);
|
||||
|
||||
protected:
|
||||
CAImpl impl;
|
||||
UINT* status;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Immediate CA.
|
||||
*/
|
||||
class CA: public CAFacade {
|
||||
public:
|
||||
CA(MSIHANDLE h, const tstring& /* name */,
|
||||
UINT* status=NULL): CAFacade(h, status) {
|
||||
}
|
||||
|
||||
tstring getProperty(const tstring& name) const {
|
||||
return impl.getProperty(name);
|
||||
}
|
||||
|
||||
CA& setProperty(const tstring& name, const tstring& v) {
|
||||
impl.setProperty(name, v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CA& removeProperty(const tstring& name) {
|
||||
impl.removeProperty(name);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like setProperty(), but do nothing if property with the given name
|
||||
* exists and its value is not empty.
|
||||
*/
|
||||
CA& setPropertyIfEmpty(const tstring& name, const tstring& v);
|
||||
|
||||
MSIHANDLE getHandle() const {
|
||||
return impl.getHandle();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deferred CA.
|
||||
*/
|
||||
class DeferredCA: public CAFacade {
|
||||
public:
|
||||
DeferredCA(MSIHANDLE h, const tstring& name,
|
||||
UINT* status=NULL): CAFacade(h, status), caArgPropertyName(name) {
|
||||
}
|
||||
|
||||
typedef std::map<tstring, tstring> ArgsCtnr;
|
||||
|
||||
DeferredCA& parseArgs() {
|
||||
parseArgs(theParsedArgs, getArg());
|
||||
return *this;
|
||||
}
|
||||
|
||||
tstring getArg() const;
|
||||
|
||||
const ArgsCtnr& parsedArgs() const {
|
||||
return theParsedArgs;
|
||||
}
|
||||
|
||||
tstring getParsedArg(const tstring& name) const;
|
||||
|
||||
static void parseArgs(ArgsCtnr& dst, const tstring& src);
|
||||
|
||||
private:
|
||||
ArgsCtnr theParsedArgs;
|
||||
tstring caArgPropertyName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write log messages into MSI log.
|
||||
*/
|
||||
class MsiLogAppender: public LogAppender {
|
||||
public:
|
||||
explicit MsiLogAppender(MSIHANDLE h);
|
||||
|
||||
virtual void append(const LogEvent& v);
|
||||
private:
|
||||
MSIHANDLE handle;
|
||||
long ctorThread;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Configures logging for the current CA.
|
||||
* Log messages that we send with LOG_INFO, LOG_ERROR, etc., go to both
|
||||
* the existing log appender and temporary MSI log file managed by
|
||||
* MSI service for the running MSI session (if any).
|
||||
*/
|
||||
class MsiLogTrigger {
|
||||
public:
|
||||
explicit MsiLogTrigger(MSIHANDLE h);
|
||||
~MsiLogTrigger();
|
||||
private:
|
||||
MsiLogAppender msiLogAppender;
|
||||
LogAppender& oldLogAppender;
|
||||
TeeLogAppender teeLogAppender;
|
||||
};
|
||||
|
||||
} // namespace msi
|
||||
|
||||
|
||||
//
|
||||
// Helpers to define CA functions.
|
||||
//
|
||||
// Sample usage:
|
||||
// Define immediate CA foo:
|
||||
// JP_CA(foo) {
|
||||
// // `ca` is a local variable of type msi::CA.
|
||||
// LOG_TRACE(ca.getProperty("Some property"));
|
||||
// }
|
||||
//
|
||||
// Define deferred CA bar:
|
||||
// JP_DEFERRED_CA(bar) {
|
||||
// // `ca` is a local variable of type msi::DeferredCA.
|
||||
// LOG_TRACE(ca.getArg());
|
||||
// }
|
||||
//
|
||||
// JP_DEFERRED_CA/JP_CA macros take care of everything related to setup CA
|
||||
// handler:
|
||||
// - define CA function with the right calling convention and arguments
|
||||
// expected by MSI;
|
||||
// - construct local instance of either DeferredCA or CA type to access data
|
||||
// in the running MSI session;
|
||||
// - setup logging, so that log messages issues with LOG_INFO, LOG_ERROR, etc.
|
||||
// macros go to MSI log file;
|
||||
// - registers CA function with linker, so there is no need to manage
|
||||
// separate .def file with the list of CA functions explicitly.
|
||||
//
|
||||
#define JP_CA_BASE(name, ca_type) \
|
||||
static void name ## Body(ca_type&); \
|
||||
extern "C" UINT __stdcall name(MSIHANDLE hInstall) { \
|
||||
__pragma(comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)); \
|
||||
const msi::MsiLogTrigger logTrigger(hInstall); \
|
||||
JP_DEBUG_BREAK(JP_CA_DEBUG_BREAK, name); \
|
||||
LOG_TRACE_FUNCTION(); \
|
||||
JP_TRY; \
|
||||
UINT status = ca_type::Success; \
|
||||
ca_type ca(hInstall, _T(#name), &status); \
|
||||
LOG_TRACE(tstrings::any() << "CA modes=[" << ca.getModes() << "]"); \
|
||||
name ## Body(ca); \
|
||||
return status; \
|
||||
JP_CATCH_ALL; \
|
||||
return ca_type::FatalError; \
|
||||
} \
|
||||
static void name ## Body(ca_type& ca)
|
||||
#define JP_CA(name) JP_CA_BASE(name, msi::CA)
|
||||
#define JP_DEFERRED_CA(name) JP_CA_BASE(name, msi::DeferredCA)
|
||||
|
||||
#define JP_CA_DECLARE(name) \
|
||||
extern "C" UINT __stdcall name(MSIHANDLE); \
|
||||
__pragma(comment(linker, "/INCLUDE:" JP_CA_MANGLED_NAME(name)))
|
||||
|
||||
#ifdef _WIN64
|
||||
#define JP_CA_MANGLED_NAME(name) #name
|
||||
#else
|
||||
#define JP_CA_MANGLED_NAME(name) "_" #name "@4"
|
||||
#endif
|
||||
|
||||
#endif // #ifndef MsiCA_h
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -124,3 +124,25 @@ std::wstring SysError::getComErrorMessage(HRESULT hr) {
|
||||
return tstrings::format(_T("COM error 0x%08X (%s)"), hrOrig,
|
||||
getSystemMessageDescription(hr, NULL));
|
||||
}
|
||||
|
||||
|
||||
void debugBreak(const SourceCodePos& location, const tstring& envVarName,
|
||||
const tstring& substr) {
|
||||
if (!SysInfo::isEnvVariableSet(envVarName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tstring v = SysInfo::getEnvVariable(std::nothrow, envVarName);
|
||||
if (v != _T("*") && v.find(substr) == tstring::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
tstring msg = tstrings::fromUtf8(makeMessage(
|
||||
std::runtime_error((tstrings::any()
|
||||
<< "debug break. Reason: environment variable "
|
||||
<< envVarName << " is set.").str()),
|
||||
location));
|
||||
tstring caption = FileUtils::basename(SysInfo::getCurrentModulePath());
|
||||
|
||||
MessageBoxW(NULL, msg.c_str(), caption.c_str(), MB_OK | MB_TASKMODAL);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -46,4 +46,18 @@ public:
|
||||
static std::wstring getComErrorMessage(HRESULT hr);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Debug break. The function tests if the given environment variable is set.
|
||||
* If it is and value of 'substr' parameter is a substring of value of
|
||||
* the given environment variable message box is popped up waiting for
|
||||
* user input.
|
||||
* If value of environment variable is '*', value of 'substr' parameter is not
|
||||
* tested and message box is popped up unconditionally.
|
||||
*/
|
||||
void debugBreak(const SourceCodePos& location, const tstring& envVarName,
|
||||
const tstring& substr);
|
||||
|
||||
#define JP_DEBUG_BREAK(env, substr) ::debugBreak(JP_SOURCE_CODE_POS, _T(#env), _T(#substr))
|
||||
|
||||
#endif // #ifndef WinErrorHandling_h
|
||||
|
117
src/jdk.jpackage/windows/native/libwixhelper/Version.cpp
Normal file
117
src/jdk.jpackage/windows/native/libwixhelper/Version.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#define NOMINMAX
|
||||
#include "Version.h"
|
||||
|
||||
|
||||
|
||||
namespace VersionDetails {
|
||||
|
||||
size_t Parser::operator ()(const tstring& str, int& buffer,
|
||||
size_t& bufferSize) const {
|
||||
if (bufferSize < 1) {
|
||||
JP_THROW(tstrings::any() << "Destination buffer can't be empty");
|
||||
}
|
||||
|
||||
tstring_array strComponents;
|
||||
|
||||
tstrings::split(strComponents, str, _T("."));
|
||||
|
||||
// Temporary storage. Needed to preserve destination buffer from
|
||||
// partial update if parsing fails.
|
||||
std::vector<int> recognizedComponents;
|
||||
|
||||
tstring_array::const_iterator it = strComponents.begin();
|
||||
tstring_array::const_iterator end =
|
||||
it + std::min(strComponents.size(), bufferSize);
|
||||
|
||||
// Number of successfully parsed characters in 'str'.
|
||||
size_t cursor = 0;
|
||||
|
||||
while (it != end) {
|
||||
const tstring& strComponent(*it);
|
||||
|
||||
try {
|
||||
recognizedComponents.push_back(parseComponent(strComponent));
|
||||
} catch (const std::exception&) {
|
||||
// error parsing version component
|
||||
break;
|
||||
}
|
||||
|
||||
cursor += strComponent.size();
|
||||
if (++it != end) {
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if (str.size() < cursor) {
|
||||
// Should never happen.
|
||||
JP_THROW(tstrings::any()
|
||||
<< "[" << cursor << " < " << str.size() << "] failed");
|
||||
}
|
||||
|
||||
// Publish results only after successful parse.
|
||||
bufferSize = recognizedComponents.size();
|
||||
if (bufferSize) {
|
||||
memcpy(&buffer, &*recognizedComponents.begin(),
|
||||
bufferSize * sizeof(buffer));
|
||||
}
|
||||
|
||||
if (!strComponents.empty() && strComponents.back().size() == 0
|
||||
&& str.size() == cursor) {
|
||||
// Input string ends with dot character (.). Mark it as unrecognized.
|
||||
--cursor;
|
||||
}
|
||||
|
||||
return (str.size() - cursor);
|
||||
}
|
||||
|
||||
|
||||
int parseComponent (const tstring& str) {
|
||||
tistringstream input(str);
|
||||
|
||||
do {
|
||||
if (str.empty() || !isdigit(str[0])) {
|
||||
break;
|
||||
}
|
||||
|
||||
int reply;
|
||||
input >> reply;
|
||||
|
||||
if (!input.eof() || input.fail()) {
|
||||
break;
|
||||
}
|
||||
|
||||
return reply;
|
||||
} while (false);
|
||||
|
||||
JP_THROW(tstrings::any()
|
||||
<< "Failed to recognize version component in [" << str << "]");
|
||||
}
|
||||
|
||||
} // namespace VersionDetails
|
173
src/jdk.jpackage/windows/native/libwixhelper/Version.h
Normal file
173
src/jdk.jpackage/windows/native/libwixhelper/Version.h
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef Version_h
|
||||
#define Version_h
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "tstrings.h"
|
||||
#include "ErrorHandling.h"
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generic version. Version is given with string.
|
||||
* String consists of version components separated with dot char (.).
|
||||
* E.g.: 1.45.6.778.89
|
||||
* Maximum number of components in version string is specified with 'N'
|
||||
* template parameter.
|
||||
*/
|
||||
|
||||
|
||||
namespace VersionDetails {
|
||||
|
||||
struct Parser {
|
||||
/**
|
||||
* Parses version components from the given string.
|
||||
* Returns number of trailing unrecognised characters or 0 if the whole
|
||||
* string has been recognized.
|
||||
*
|
||||
* @param str
|
||||
* string to parse;
|
||||
* @param buffer
|
||||
* reference to buffer accepting parsed version components;
|
||||
* @param bufferSize[in,out]
|
||||
* number of elements in the destination buffer; on return is set
|
||||
* a number of components recognized in the input string.
|
||||
*/
|
||||
size_t operator () (const tstring& str, int& buffer,
|
||||
size_t& bufferSize) const;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns parsed single version component from the given string.
|
||||
* Throws std::exception if error occurs parsing the given string.
|
||||
*
|
||||
* @param str
|
||||
* string to parse;
|
||||
*/
|
||||
int parseComponent (const tstring& str);
|
||||
|
||||
|
||||
template <int N, class Parser, int MinComponentCount=0>
|
||||
class Base {
|
||||
public:
|
||||
enum { ComponentCount = N };
|
||||
Base() {
|
||||
memset(components, 0, sizeof(components));
|
||||
}
|
||||
|
||||
private:
|
||||
bool verifyComponentCount(size_t recognizedComponentCount) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
void init(const tstring& str) {
|
||||
size_t recognizedComponentCount = N;
|
||||
const size_t unrecognisedChars = Parser()(str, *components,
|
||||
recognizedComponentCount);
|
||||
if (unrecognisedChars) {
|
||||
JP_THROW(tstrings::any()
|
||||
<< "Failed to parse [" << str << "] version string completely."
|
||||
<< " Number of unrecognized characters is " << unrecognisedChars);
|
||||
}
|
||||
|
||||
if (recognizedComponentCount < MinComponentCount || !verifyComponentCount(recognizedComponentCount)) {
|
||||
// Input string is too short.
|
||||
JP_THROW(tstrings::any() << "Failed to parse [" << str
|
||||
<< "] version string. The string is too short");
|
||||
|
||||
}
|
||||
strValue = str;
|
||||
}
|
||||
|
||||
public:
|
||||
const tstring& source() const {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
bool operator < (const Base& other) const {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
const int a = components[i];
|
||||
const int b = other.components[i];
|
||||
if (a < b) {
|
||||
return true;
|
||||
}
|
||||
if (b < a) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator <= (const Base& other) const {
|
||||
return *this == other || *this < other;
|
||||
}
|
||||
|
||||
bool operator > (const Base& other) const {
|
||||
return ! (*this <= other);
|
||||
}
|
||||
|
||||
bool operator >= (const Base& other) const {
|
||||
return ! (*this < other);
|
||||
}
|
||||
|
||||
bool operator == (const Base& other) const {
|
||||
return (0 == memcmp(components, other.components, sizeof(components)));
|
||||
}
|
||||
|
||||
bool operator != (const Base& other) const {
|
||||
return ! (*this == other);
|
||||
}
|
||||
|
||||
protected:
|
||||
int components[N];
|
||||
|
||||
private:
|
||||
tstring strValue;
|
||||
};
|
||||
|
||||
} // namespace VersionDetails
|
||||
|
||||
|
||||
template <class Base>
|
||||
struct Version: public Base {
|
||||
Version() {
|
||||
}
|
||||
|
||||
explicit Version(const tstring& str) {
|
||||
Base::init(str);
|
||||
}
|
||||
|
||||
Version(const Base& other): Base(other) {
|
||||
}
|
||||
};
|
||||
|
||||
#endif // #ifndef Version_h
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,71 +23,175 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <Windows.h>
|
||||
#include <msiquery.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
extern "C" {
|
||||
#include "MsiDb.h"
|
||||
#include "MsiCA.h"
|
||||
#include "Version.h"
|
||||
#include "FileUtils.h"
|
||||
#include "WinErrorHandling.h"
|
||||
|
||||
#ifdef JP_EXPORT_FUNCTION
|
||||
#error Unexpected JP_EXPORT_FUNCTION define
|
||||
#endif
|
||||
#define JP_EXPORT_FUNCTION comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,
|
||||
LPVOID lpvReserved) {
|
||||
return TRUE;
|
||||
JP_CA(CheckInstallDir) {
|
||||
const tstring installDir = ca.getProperty(_T("INSTALLDIR"));
|
||||
|
||||
bool canProceed = !FileUtils::isFileExists(installDir);
|
||||
if (!canProceed && FileUtils::isDirectory(installDir)) {
|
||||
canProceed = !FileUtils::isDirectoryNotEmpty(installDir);
|
||||
}
|
||||
|
||||
BOOL DirectoryExist(TCHAR *szValue) {
|
||||
DWORD attr = GetFileAttributes(szValue);
|
||||
if (attr == INVALID_FILE_ATTRIBUTES) {
|
||||
return FALSE;
|
||||
}
|
||||
ca.setProperty(_T("INSTALLDIR_VALID"), canProceed ? _T("1") : _T("0"));
|
||||
}
|
||||
|
||||
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
namespace {
|
||||
|
||||
typedef Version<
|
||||
VersionDetails::Base<10, VersionDetails::Parser, 2>
|
||||
> DottedVersion;
|
||||
|
||||
|
||||
class ProductInfo {
|
||||
public:
|
||||
explicit ProductInfo(const Guid& pc): productCode(pc),
|
||||
version(msi::getProductInfo(pc, INSTALLPROPERTY_VERSIONSTRING)) {
|
||||
}
|
||||
|
||||
UINT __stdcall CheckInstallDir(MSIHANDLE hInstall) {
|
||||
#pragma JP_EXPORT_FUNCTION
|
||||
const DottedVersion& getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
TCHAR *szValue = NULL;
|
||||
DWORD cchSize = 0;
|
||||
const Guid& getProductCode() const {
|
||||
return productCode;
|
||||
}
|
||||
|
||||
UINT result = MsiGetProperty(hInstall, TEXT("INSTALLDIR"),
|
||||
(LPTSTR)TEXT(""), &cchSize);
|
||||
if (result == ERROR_MORE_DATA) {
|
||||
cchSize = cchSize + 1; // NULL termination
|
||||
szValue = new TCHAR[cchSize];
|
||||
if (szValue) {
|
||||
result = MsiGetProperty(hInstall, TEXT("INSTALLDIR"),
|
||||
szValue, &cchSize);
|
||||
} else {
|
||||
return ERROR_INSTALL_FAILURE;
|
||||
}
|
||||
private:
|
||||
Guid productCode;
|
||||
DottedVersion version;
|
||||
};
|
||||
|
||||
|
||||
void findInstalledProducts(const Guid& upgradeCode,
|
||||
std::vector<ProductInfo>& products) {
|
||||
const LPCTSTR upgradeCodeStr = upgradeCode.toMsiString().c_str();
|
||||
for (DWORD productCodeIdx = 0; true; ++productCodeIdx) {
|
||||
TCHAR productCode[39 /* http://msdn.microsoft.com/en-us/library/aa370101(v=vs.85).aspx */];
|
||||
const UINT status = MsiEnumRelatedProducts(upgradeCodeStr, 0,
|
||||
productCodeIdx, productCode);
|
||||
if (ERROR_NO_MORE_ITEMS == status) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != ERROR_SUCCESS) {
|
||||
delete [] szValue;
|
||||
return ERROR_INSTALL_FAILURE;
|
||||
}
|
||||
|
||||
if (DirectoryExist(szValue)) {
|
||||
if (PathIsDirectoryEmpty(szValue)) {
|
||||
MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
|
||||
} else {
|
||||
MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("0"));
|
||||
}
|
||||
if (ERROR_SUCCESS == status) {
|
||||
LOG_TRACE(tstrings::any() << "Found " << productCode << " product");
|
||||
JP_NO_THROW(products.push_back(ProductInfo(Guid(productCode))));
|
||||
} else {
|
||||
MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
|
||||
LOG_WARNING(tstrings::any()
|
||||
<< "MsiEnumRelatedProducts("
|
||||
<< upgradeCodeStr << ", "
|
||||
<< productCodeIdx
|
||||
<< ") failed with error=[" << status << "]");
|
||||
if (ERROR_INVALID_PARAMETER == status) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DottedVersion getDottedVersion(const msi::DatabaseRecord& record, UINT idx) {
|
||||
if (!MsiRecordIsNull(record.getHandle(), idx)) {
|
||||
JP_NO_THROW(return DottedVersion(record.getString(idx)));
|
||||
}
|
||||
|
||||
return DottedVersion();
|
||||
}
|
||||
|
||||
bool dbContainsUpgradeTable(const msi::Database &db) {
|
||||
msi::DatabaseView view(db,
|
||||
_T("SELECT Name FROM _Tables WHERE Name = 'Upgrade'"));
|
||||
msi::DatabaseRecord record;
|
||||
while (!record.tryFetch(view).empty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
JP_CA(FindRelatedProductsEx) {
|
||||
if (ca.isInMode(MSIRUNMODE_MAINTENANCE)) {
|
||||
// MSI skips standard FindRelatedProducts action in maintenance mode,
|
||||
// so should we do for custom FindRelatedProducts action
|
||||
LOG_TRACE("Not run in maintenance mode");
|
||||
return;
|
||||
}
|
||||
|
||||
const msi::Database db(ca);
|
||||
if (!dbContainsUpgradeTable(db)) {
|
||||
LOG_TRACE("The package doesn't contain Upgrade table");
|
||||
return;
|
||||
}
|
||||
|
||||
const Guid upgradeCode = Guid(ca.getProperty(_T("UpgradeCode")));
|
||||
|
||||
std::vector<ProductInfo> installedProducts;
|
||||
findInstalledProducts(upgradeCode, installedProducts);
|
||||
|
||||
bool migratePropRemoved = false;
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/adsi/sql-dialect
|
||||
msi::DatabaseView view(db, (tstrings::any()
|
||||
<< _T("SELECT `VersionMin`,`VersionMax`,`Attributes`,`ActionProperty` FROM Upgrade WHERE `ActionProperty` <> NULL And `UpgradeCode` = '")
|
||||
<< upgradeCode.toMsiString() << _T("'")).tstr());
|
||||
msi::DatabaseRecord record;
|
||||
while (!record.tryFetch(view).empty()) {
|
||||
const tstring actionProperty = record.getString(4);
|
||||
|
||||
// Clean up properties set by the standard FindRelatedProducts action
|
||||
ca.removeProperty(actionProperty);
|
||||
if (!migratePropRemoved) {
|
||||
ca.removeProperty(_T("MIGRATE"));
|
||||
migratePropRemoved = true;
|
||||
}
|
||||
|
||||
const DottedVersion versionMin = getDottedVersion(record, 1);
|
||||
const DottedVersion versionMax = getDottedVersion(record, 2);
|
||||
|
||||
const int attrs = MsiRecordIsNull(
|
||||
record.getHandle(), 3) ? 0 : record.getInteger(3);
|
||||
|
||||
std::vector<ProductInfo>::const_iterator productIt =
|
||||
installedProducts.begin();
|
||||
std::vector<ProductInfo>::const_iterator productEnd =
|
||||
installedProducts.end();
|
||||
for (; productIt != productEnd; ++productIt) {
|
||||
bool minMatch;
|
||||
if (versionMin.source().empty()) {
|
||||
minMatch = true;
|
||||
} else if (attrs & msidbUpgradeAttributesVersionMinInclusive) {
|
||||
minMatch = (versionMin <= productIt->getVersion());
|
||||
} else {
|
||||
minMatch = (versionMin < productIt->getVersion());
|
||||
}
|
||||
|
||||
bool maxMatch;
|
||||
if (versionMax.source().empty()) {
|
||||
maxMatch = true;
|
||||
} else if (attrs & msidbUpgradeAttributesVersionMaxInclusive) {
|
||||
maxMatch = (productIt->getVersion() <= versionMax);
|
||||
} else {
|
||||
maxMatch = (productIt->getVersion() < versionMax);
|
||||
}
|
||||
|
||||
if (minMatch && maxMatch) {
|
||||
tstring value = productIt->getProductCode().toMsiString();
|
||||
ca.setProperty(actionProperty, value);
|
||||
ca.setProperty(_T("MIGRATE"), value);
|
||||
// Bail out after the first match as action
|
||||
// property has been set already.
|
||||
// There is no way to communicate multiple product codes
|
||||
// through a single property.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete [] szValue;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ public final class PackageTest extends RunnablePackageTest {
|
||||
return this;
|
||||
}
|
||||
|
||||
public final static class Group extends RunnablePackageTest {
|
||||
public static class Group extends RunnablePackageTest {
|
||||
public Group(PackageTest... tests) {
|
||||
handlers = Stream.of(tests)
|
||||
.map(PackageTest::createPackageTypeHandlers)
|
||||
|
@ -41,7 +41,8 @@ public abstract class RunnablePackageTest {
|
||||
.filter(Predicate.not(Action.INITIALIZE::equals))
|
||||
.filter(Predicate.not(Action.FINALIZE::equals))
|
||||
.collect(Collectors.toList()));
|
||||
if (hasAction(Action.PURGE) && !actionList.contains(Action.PURGE)) {
|
||||
if (hasAction(Action.PURGE) && (!actionList.contains(Action.PURGE)
|
||||
&& actionList.contains(Action.CREATE))) {
|
||||
// Default action list contains "purge" action meaning
|
||||
// packages are not needed for further processing.
|
||||
// Copy this behavior in custom action list.
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
var msi;
|
||||
if (WScript.Arguments.Count() > 0) {
|
||||
msi = WScript.Arguments(0)
|
||||
} else {
|
||||
var shell = new ActiveXObject('WScript.Shell')
|
||||
msi = shell.ExpandEnvironmentStrings('%JpMsiFile%')
|
||||
}
|
||||
|
||||
var query = "SELECT `UpgradeCode`, `VersionMin`,`VersionMax`,`Language`,`Attributes`,`Remove`,`ActionProperty` FROM Upgrade WHERE `VersionMax` = NULL"
|
||||
|
||||
var installer = new ActiveXObject('WindowsInstaller.Installer');
|
||||
var database = installer.OpenDatabase(msi, 1)
|
||||
var view = database.OpenView(query);
|
||||
view.Execute();
|
||||
|
||||
try {
|
||||
var record = view.Fetch();
|
||||
record.StringData(2) = '2.0.0.3'
|
||||
record.IntegerData(5) = 257
|
||||
view.Modify(6, record)
|
||||
view.Modify(3, record)
|
||||
database.Commit();
|
||||
} finally {
|
||||
view.Close();
|
||||
}
|
215
test/jdk/tools/jpackage/windows/WinLongVersionTest.java
Normal file
215
test/jdk/tools/jpackage/windows/WinLongVersionTest.java
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import jdk.jpackage.internal.IOUtils;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.Executor;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
import jdk.jpackage.test.PackageType;
|
||||
import jdk.jpackage.test.RunnablePackageTest.Action;
|
||||
import jdk.jpackage.test.TKit;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
* Test --app-version parameter properly supports long version numbers, i.e.
|
||||
* version numbers with more than three components.
|
||||
* Output of the test should be WinLongVersionTest-1.0.exe,
|
||||
* WinLongVersionTest-2.0.0.1.exe, and WinLongVersionTest-2.0.0.2.exe installers.
|
||||
* The output installers should provide the same functionality as
|
||||
* the default installer (see description of the default installer in
|
||||
* SimplePackageTest.java) but have the same product code and different
|
||||
* versions.
|
||||
* Test scenario:
|
||||
* - Run WinLongVersionTest-2.0.0.2.exe;
|
||||
* - Run WinLongVersionTest-1.0.exe; package installed with
|
||||
* WinLongVersionTest-2.0.0.2.exe command must remain installed
|
||||
* - Run WinLongVersionTest-2.0.0.1.exe; packages installed with
|
||||
* WinLongVersionTest-2.0.0.2.exe and WinLongVersionTest-1.0.exe installers
|
||||
* must be automatically uninstalled, only WinLongVersionTest-2.0.0.1 package
|
||||
* must remain installed
|
||||
* - Uninstall WinLongVersionTest-2.0.0.2; all packages installed in the test
|
||||
* scenario must be uninstalled
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary jpackage with long version number
|
||||
* @library ../helpers
|
||||
* @key jpackagePlatformPackage
|
||||
* @requires (jpackage.test.SQETest != null)
|
||||
* @build jdk.jpackage.test.*
|
||||
* @requires (os.family == "windows")
|
||||
* @modules jdk.jpackage/jdk.jpackage.internal
|
||||
* @compile WinLongVersionTest.java
|
||||
* @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
|
||||
* --jpt-run=WinLongVersionTest.test
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary jpackage with long version number
|
||||
* @library ../helpers
|
||||
* @key jpackagePlatformPackage
|
||||
* @requires (jpackage.test.SQETest == null)
|
||||
* @build jdk.jpackage.test.*
|
||||
* @requires (os.family == "windows")
|
||||
* @modules jdk.jpackage/jdk.jpackage.internal
|
||||
* @compile WinLongVersionTest.java
|
||||
* @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
|
||||
* --jpt-run=WinLongVersionTest
|
||||
*/
|
||||
|
||||
public class WinLongVersionTest {
|
||||
|
||||
@Test
|
||||
public static void test() throws IOException {
|
||||
Supplier<PackageTest> init = () -> {
|
||||
final UUID upgradeCode = UUID.fromString(
|
||||
"65099D7A-D5B1-4E5B-85B1-717F0DE4D5D5");
|
||||
return new PackageTest()
|
||||
.forTypes(PackageType.WINDOWS)
|
||||
.configureHelloApp()
|
||||
.addInitializer(cmd -> cmd.addArguments("--win-upgrade-uuid",
|
||||
upgradeCode.toString())) ;
|
||||
|
||||
};
|
||||
|
||||
PackageTest test1 = init.get().addInitializer(cmd -> {
|
||||
cmd.setArgumentValue("--app-version", "2.0.0.2");
|
||||
cmd.setArgumentValue("--arguments", "bar");
|
||||
cmd.setArgumentValue("--install-dir", cmd.name() + "-1");
|
||||
});
|
||||
|
||||
// Tweak Upgrade table of the second package in a way the default
|
||||
// FindRelatedProducts MSI action will find 1st package, but custom
|
||||
// jpackage's FindRelatedProductsEx action will not as it gracefuly
|
||||
// handles more than 3 components of version strings.
|
||||
// In MSI log of the 2nd package installartion session it will be something like:
|
||||
/*
|
||||
Action start 12:08:38: FindRelatedProducts.
|
||||
FindRelatedProducts: Found application: {D88EEA02-56CC-34AD-8216-C2CC244FA898}
|
||||
MSI (c) (0C:14) [12:08:38:040]: PROPERTY CHANGE: Adding JP_DOWNGRADABLE_FOUND property. Its value is '{D88EEA02-56CC-34AD-8216-C2CC244FA898}'.
|
||||
MSI (c) (0C:14) [12:08:38:040]: PROPERTY CHANGE: Adding MIGRATE property. Its value is '{D88EEA02-56CC-34AD-8216-C2CC244FA898}'.
|
||||
Action ended 12:08:38: FindRelatedProducts. Return value 1.
|
||||
...
|
||||
Action start 12:08:38: JpFindRelatedProducts.
|
||||
Java [12:08:38.180 libwixhelper.cpp:120 (FindRelatedProductsEx)] TRACE: Entering FindRelatedProductsEx
|
||||
Java [12:08:38.185 libwixhelper.cpp:85 (`anonymous-namespace'::findInstalledPackages)] TRACE: Found {D88EEA02-56CC-34AD-8216-C2CC244FA898} product
|
||||
Java [12:08:38.187 MsiCA.cpp:61 (msi::CAImpl::removeProperty)] TRACE: Removing MSI property 'JP_UPGRADABLE_FOUND'
|
||||
Java [12:08:38.187 MsiCA.cpp:61 (msi::CAImpl::removeProperty)] TRACE: Removing MSI property 'MIGRATE'
|
||||
Java [12:08:38.189 MsiCA.cpp:61 (msi::CAImpl::removeProperty)] TRACE: Removing MSI property 'JP_DOWNGRADABLE_FOUND'
|
||||
Java [12:08:38.190 libwixhelper.cpp:0 (FindRelatedProductsEx)] TRACE: Exiting FindRelatedProductsEx (entered at libwixhelper.cpp:120)
|
||||
Action ended 12:08:38: JpFindRelatedProducts. Return value 1.
|
||||
*/
|
||||
PackageTest test2 = init.get().addInstallVerifier(cmd -> {
|
||||
if (!cmd.isPackageUnpacked()) {
|
||||
// Installation of this package must NOT uninstall
|
||||
// previously installed package. Verify that.
|
||||
test1.run(Action.VERIFY_INSTALL);
|
||||
}
|
||||
}).forTypes(PackageType.WIN_EXE)
|
||||
.addInitializer(cmd -> {
|
||||
final Path resourceDir = TKit.createTempDirectory("resources");
|
||||
cmd.addArguments("--resource-dir", resourceDir);
|
||||
|
||||
Path scriptPath = resourceDir.resolve(String.format(
|
||||
"%s-post-msi.wsf", cmd.name()));
|
||||
IOUtils.createXml(scriptPath, xml -> {
|
||||
xml.writeStartElement("job");
|
||||
xml.writeAttribute("id", "main");
|
||||
xml.writeStartElement("script");
|
||||
xml.writeAttribute("language", "JScript");
|
||||
xml.writeCData(String.join("\n", Files.readAllLines(
|
||||
TKit.TEST_SRC_ROOT.resolve(String.format(
|
||||
"resources/%s-edit-msi.js", cmd.name())))));
|
||||
xml.writeEndElement();
|
||||
xml.writeEndElement();
|
||||
});
|
||||
}).forTypes(PackageType.WIN_MSI)
|
||||
.addBundleVerifier(cmd -> {
|
||||
Executor.of("cscript.exe", "//Nologo")
|
||||
.addArgument(TKit.TEST_SRC_ROOT.resolve(String.format(
|
||||
"resources/%s-edit-msi.js", cmd.name())))
|
||||
.addArgument(cmd.outputBundle())
|
||||
.execute();
|
||||
});
|
||||
|
||||
// Replace real uninstall commands for the first packages with nop action.
|
||||
// They will be uninstalled automatically when the last package will
|
||||
// be installed.
|
||||
test1.disablePackageUninstaller();
|
||||
test2.disablePackageUninstaller();
|
||||
|
||||
PackageTest test3 = init.get().addInitializer(cmd -> {
|
||||
cmd.setArgumentValue("--app-version", "2.0.0.1");
|
||||
cmd.setArgumentValue("--arguments", "buzz");
|
||||
});
|
||||
|
||||
new PackageTest.Group(test1, test2, test3).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public static void testNoUpgradeTable() throws IOException {
|
||||
new PackageTest()
|
||||
.forTypes(PackageType.WINDOWS)
|
||||
.configureHelloApp()
|
||||
.addInitializer(cmd -> {
|
||||
final Path resourceDir = TKit.createTempDirectory("resources");
|
||||
cmd.addArguments("--resource-dir", resourceDir);
|
||||
|
||||
cmd.setFakeRuntime();
|
||||
|
||||
// Create package without Upgrade table
|
||||
Document doc = IOUtils.initDocumentBuilder().parse(
|
||||
Files.newInputStream(TKit.SRC_ROOT.resolve(
|
||||
"windows/classes/jdk/jpackage/internal/resources/main.wxs")));
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList nodes = (NodeList) xPath.evaluate("/Wix/Product/Upgrade",
|
||||
doc, XPathConstants.NODESET);
|
||||
nodes.item(0).getParentNode().removeChild(nodes.item(0));
|
||||
|
||||
Source source = new DOMSource(doc);
|
||||
Result result = new StreamResult(Files.newOutputStream(
|
||||
resourceDir.resolve("main.wxs")));
|
||||
|
||||
Transformer trans = TransformerFactory.newInstance().newTransformer();
|
||||
trans.transform(source, result);
|
||||
}).run();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user