From 2ad7f6ddf5042d7442c97a89b083ca2853cf5721 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 24 Feb 2017 16:22:53 +0100 Subject: Preserve last modification timestamps of installed files On non-windows platforms, we use the "-p" parameter of install(1) to preserve the last modification timestamps of files. On Windows the use of copy does not preserve them. As a cross-platform solution, this patch introduces a simple built-in install command in qmake to copy files. Task-number: QTBUG-59004 Change-Id: I3064d29a2b8c7b009a1efbf8f00b84c079ea5417 Reviewed-by: Oswald Buddenhagen --- qmake/generators/makefile.cpp | 7 ++-- qmake/generators/unix/unixmake.cpp | 4 +- qmake/generators/win32/winmakefile.cpp | 1 + qmake/library/ioutils.cpp | 70 ++++++++++++++++++++++++++++++++++ qmake/library/ioutils.h | 3 ++ qmake/library/qmakebuiltins.cpp | 60 ++--------------------------- qmake/main.cpp | 57 ++++++++++++++++++++++++++- tests/auto/tools/qmake/tst_qmake.cpp | 1 + 8 files changed, 140 insertions(+), 63 deletions(-) diff --git a/qmake/generators/makefile.cpp b/qmake/generators/makefile.cpp index 2845888dde..5f3b8c9047 100644 --- a/qmake/generators/makefile.cpp +++ b/qmake/generators/makefile.cpp @@ -1294,7 +1294,7 @@ MakefileGenerator::writeInstalls(QTextStream &t, bool noBuild) else if (is_target || fi.isExecutable()) cmd = "-$(INSTALL_PROGRAM)"; else - cmd = "-$(INSTALL_FILE)"; + cmd = "-$(QINSTALL_FILE)"; cmd += " " + escapeFilePath(wild) + " " + escapeFilePath(dst_file); inst << cmd; if (!project->isActiveConfig("debug_info") && !project->isActiveConfig("nostrip") && @@ -1320,7 +1320,7 @@ MakefileGenerator::writeInstalls(QTextStream &t, bool noBuild) } else if (installConfigValues.contains("executable")) { cmd = QLatin1String("-$(INSTALL_PROGRAM)"); } else { - cmd = QLatin1String("-$(INSTALL_FILE)"); + cmd = QLatin1String("-$(QINSTALL_FILE)"); } cmd += " " + escapeFilePath(wild) + " " + escapeFilePath(dst_file); inst << cmd; @@ -1336,7 +1336,7 @@ MakefileGenerator::writeInstalls(QTextStream &t, bool noBuild) dst_file += Option::dir_sep; dst_file += fi.fileName(); } - QString cmd = QString(fi.isDir() ? "-$(INSTALL_DIR)" : "-$(INSTALL_FILE)") + " " + + QString cmd = QString(fi.isDir() ? "-$(INSTALL_DIR)" : "-$(QINSTALL_FILE)") + " " + escapeFilePath(dirstr + file) + " " + escapeFilePath(dst_file); inst << cmd; if (!project->isActiveConfig("debug_info") && !project->isActiveConfig("nostrip") && @@ -2248,6 +2248,7 @@ MakefileGenerator::writeDefaultVariables(QTextStream &t) t << "INSTALL_FILE = " << var("QMAKE_INSTALL_FILE") << endl; t << "INSTALL_PROGRAM = " << var("QMAKE_INSTALL_PROGRAM") << endl; t << "INSTALL_DIR = " << var("QMAKE_INSTALL_DIR") << endl; + t << "QINSTALL_FILE = " << var("QMAKE_QMAKE") << " -install qinstall file" << endl; t << "DEL_FILE = " << var("QMAKE_DEL_FILE") << endl; t << "SYMLINK = " << var("QMAKE_SYMBOLIC_LINK") << endl; t << "DEL_DIR = " << var("QMAKE_DEL_DIR") << endl; diff --git a/qmake/generators/unix/unixmake.cpp b/qmake/generators/unix/unixmake.cpp index 794d04a6e9..0f5e6dc8c0 100644 --- a/qmake/generators/unix/unixmake.cpp +++ b/qmake/generators/unix/unixmake.cpp @@ -584,7 +584,7 @@ UnixMakefileGenerator::defaultInstall(const QString &t) dst = escapeFilePath(filePrefixRoot(root, targetdir + src.section('/', -1))); if(!ret.isEmpty()) ret += "\n\t"; - ret += "-$(INSTALL_FILE) " + escapeFilePath(Option::fixPathToTargetOS(src, false)) + ' ' + dst; + ret += "-$(QINSTALL_FILE) " + escapeFilePath(Option::fixPathToTargetOS(src, false)) + ' ' + dst; if(!uninst.isEmpty()) uninst.append("\n\t"); uninst.append("-$(DEL_FILE) " + dst); @@ -622,7 +622,7 @@ UnixMakefileGenerator::defaultInstall(const QString &t) if (bundle == SolidBundle) { copy_cmd += "-$(INSTALL_DIR) " + src_targ + ' ' + plain_targ; } else if (project->first("TEMPLATE") == "lib" && project->isActiveConfig("staticlib")) { - copy_cmd += "-$(INSTALL_FILE) " + src_targ + ' ' + dst_targ; + copy_cmd += "-$(QINSTALL_FILE) " + src_targ + ' ' + dst_targ; } else if (!isAux) { if (bundle == SlicedBundle) { if (!ret.isEmpty()) diff --git a/qmake/generators/win32/winmakefile.cpp b/qmake/generators/win32/winmakefile.cpp index 3c029c8004..b6dd734bc2 100644 --- a/qmake/generators/win32/winmakefile.cpp +++ b/qmake/generators/win32/winmakefile.cpp @@ -530,6 +530,7 @@ void Win32MakefileGenerator::writeStandardParts(QTextStream &t) t << "INSTALL_FILE = " << var("QMAKE_INSTALL_FILE") << endl; t << "INSTALL_PROGRAM = " << var("QMAKE_INSTALL_PROGRAM") << endl; t << "INSTALL_DIR = " << var("QMAKE_INSTALL_DIR") << endl; + t << "QINSTALL_FILE = " << var("QMAKE_QMAKE") << " -install qinstall file" << endl; t << endl; t << "####### Output directory\n\n"; diff --git a/qmake/library/ioutils.cpp b/qmake/library/ioutils.cpp index 67d2f541a1..39264952c6 100644 --- a/qmake/library/ioutils.cpp +++ b/qmake/library/ioutils.cpp @@ -37,8 +37,13 @@ # include # include # include +# include +# include +# include #endif +#define fL1S(s) QString::fromLatin1(s) + QT_BEGIN_NAMESPACE using namespace QMakeInternal; @@ -183,4 +188,69 @@ QString IoUtils::shellQuoteWin(const QString &arg) return ret; } +#if defined(PROEVALUATOR_FULL) + +# if defined(Q_OS_WIN) +static QString windowsErrorCode() +{ + wchar_t *string = 0; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&string, + 0, + NULL); + QString ret = QString::fromWCharArray(string); + LocalFree((HLOCAL)string); + return ret.trimmed(); +} +# endif + +bool IoUtils::touchFile(const QString &targetFileName, const QString &referenceFileName, QString *errorString) +{ +# ifdef Q_OS_UNIX + struct stat st; + if (stat(referenceFileName.toLocal8Bit().constData(), &st)) { + *errorString = fL1S("Cannot stat() reference file %1: %2.").arg(referenceFileName, fL1S(strerror(errno))); + return false; + } +# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809L + const struct timespec times[2] = { { 0, UTIME_NOW }, st.st_mtim }; + const bool utimeError = utimensat(AT_FDCWD, targetFileName.toLocal8Bit().constData(), times, 0) < 0; +# else + struct utimbuf utb; + utb.actime = time(0); + utb.modtime = st.st_mtime; + const bool utimeError= utime(targetFileName.toLocal8Bit().constData(), &utb) < 0; +# endif + if (utimeError) { + *errorString = fL1S("Cannot touch %1: %2.").arg(targetFileName, fL1S(strerror(errno))); + return false; + } +# else + HANDLE rHand = CreateFile((wchar_t*)referenceFileName.utf16(), + GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (rHand == INVALID_HANDLE_VALUE) { + *errorString = fL1S("Cannot open reference file %1: %2").arg(referenceFileName, windowsErrorCode()); + return false; + } + FILETIME ft; + GetFileTime(rHand, 0, 0, &ft); + CloseHandle(rHand); + HANDLE wHand = CreateFile((wchar_t*)targetFileName.utf16(), + GENERIC_WRITE, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (wHand == INVALID_HANDLE_VALUE) { + *errorString = fL1S("Cannot open %1: %2").arg(targetFileName, windowsErrorCode()); + return false; + } + SetFileTime(wHand, 0, 0, &ft); + CloseHandle(wHand); +# endif + return true; +} +#endif + QT_END_NAMESPACE diff --git a/qmake/library/ioutils.h b/qmake/library/ioutils.h index a806f3d2eb..905974b7cb 100644 --- a/qmake/library/ioutils.h +++ b/qmake/library/ioutils.h @@ -62,6 +62,9 @@ public: #else { return shellQuoteWin(arg); } #endif +#if defined(PROEVALUATOR_FULL) + static bool touchFile(const QString &targetFileName, const QString &referenceFileName, QString *errorString); +#endif }; } // namespace ProFileEvaluatorInternal diff --git a/qmake/library/qmakebuiltins.cpp b/qmake/library/qmakebuiltins.cpp index 461f46e705..2bb6f2e12d 100644 --- a/qmake/library/qmakebuiltins.cpp +++ b/qmake/library/qmakebuiltins.cpp @@ -56,9 +56,7 @@ #ifdef Q_OS_UNIX #include -#include #include -#include #include #include #include @@ -256,23 +254,6 @@ QMakeEvaluator::getMemberArgs(const ProKey &func, int srclen, const ProStringLis return true; } -#if defined(Q_OS_WIN) && defined(PROEVALUATOR_FULL) -static QString windowsErrorCode() -{ - wchar_t *string = 0; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&string, - 0, - NULL); - QString ret = QString::fromWCharArray(string); - LocalFree((HLOCAL)string); - return ret.trimmed(); -} -#endif - QString QMakeEvaluator::quoteValue(const ProString &val) { @@ -1813,46 +1794,11 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional( #ifdef PROEVALUATOR_FULL const QString &tfn = resolvePath(args.at(0).toQString(m_tmp1)); const QString &rfn = resolvePath(args.at(1).toQString(m_tmp2)); -#ifdef Q_OS_UNIX - struct stat st; - if (stat(rfn.toLocal8Bit().constData(), &st)) { - evalError(fL1S("Cannot stat() reference file %1: %2.").arg(rfn, fL1S(strerror(errno)))); + QString error; + if (!IoUtils::touchFile(tfn, rfn, &error)) { + evalError(error); return ReturnFalse; } -#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200809L - const struct timespec times[2] = { { 0, UTIME_NOW }, st.st_mtim }; - const bool utimeError = utimensat(AT_FDCWD, tfn.toLocal8Bit().constData(), times, 0) < 0; -#else - struct utimbuf utb; - utb.actime = time(0); - utb.modtime = st.st_mtime; - const bool utimeError = utime(tfn.toLocal8Bit().constData(), &utb) < 0; -#endif - if (utimeError) { - evalError(fL1S("Cannot touch %1: %2.").arg(tfn, fL1S(strerror(errno)))); - return ReturnFalse; - } -#else - HANDLE rHand = CreateFile((wchar_t*)rfn.utf16(), - GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (rHand == INVALID_HANDLE_VALUE) { - evalError(fL1S("Cannot open reference file %1: %2").arg(rfn, windowsErrorCode())); - return ReturnFalse; - } - FILETIME ft; - GetFileTime(rHand, 0, 0, &ft); - CloseHandle(rHand); - HANDLE wHand = CreateFile((wchar_t*)tfn.utf16(), - GENERIC_WRITE, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (wHand == INVALID_HANDLE_VALUE) { - evalError(fL1S("Cannot open %1: %2").arg(tfn, windowsErrorCode())); - return ReturnFalse; - } - SetFileTime(wHand, 0, 0, &ft); - CloseHandle(wHand); -#endif #endif return ReturnTrue; } diff --git a/qmake/main.cpp b/qmake/main.cpp index 6d7e023b41..0b0b5ff128 100644 --- a/qmake/main.cpp +++ b/qmake/main.cpp @@ -47,6 +47,8 @@ # include #endif +using namespace QMakeInternal; + QT_BEGIN_NAMESPACE #ifdef Q_OS_WIN @@ -232,20 +234,73 @@ static int doLink(int argc, char **argv) return 0; } +#endif + +static int installFile(const QString &source, const QString &targetFileOrDirectory) +{ + QFile sourceFile(source); + + QString target(targetFileOrDirectory); + if (QFileInfo(target).isDir()) + target += QDir::separator() + QFileInfo(sourceFile.fileName()).fileName(); + + if (QFile::exists(target)) + QFile::remove(target); + + QDir::root().mkpath(QFileInfo(target).absolutePath()); + + if (!sourceFile.copy(target)) { + fprintf(stderr, "Error copying %s to %s: %s\n", source.toLatin1().constData(), qPrintable(target), qPrintable(sourceFile.errorString())); + return 3; + } + // Copy file times + QString error; + if (!IoUtils::touchFile(target, sourceFile.fileName(), &error)) { + fprintf(stderr, "%s", qPrintable(error)); + return 3; + } + return 0; +} + +static int doQInstall(int argc, char **argv) +{ + if (argc != 3) { + fprintf(stderr, "Error: this qinstall command requires exactly three arguments (type, source, destination)\n"); + return 3; + } + + const QString source = QString::fromLocal8Bit(argv[1]); + const QString target = QString::fromLocal8Bit(argv[2]); + + if (!strcmp(argv[0], "file")) + return installFile(source, target); + + fprintf(stderr, "Error: Unsupported qinstall command type %s\n", argv[0]); + return 3; +} + + static int doInstall(int argc, char **argv) { if (!argc) { fprintf(stderr, "Error: -install requires further arguments\n"); return 3; } +#ifdef Q_OS_WIN if (!strcmp(argv[0], "sed")) return doSed(argc - 1, argv + 1); if (!strcmp(argv[0], "ln")) return doLink(argc - 1, argv + 1); +#endif + if (!strcmp(argv[0], "qinstall")) + return doQInstall(argc - 1, argv + 1); fprintf(stderr, "Error: unrecognized -install subcommand '%s'\n", argv[0]); return 3; } + +#ifdef Q_OS_WIN + static int dumpMacros(const wchar_t *cmdline) { // from http://stackoverflow.com/questions/3665537/how-to-find-out-cl-exes-built-in-macros @@ -300,11 +355,11 @@ int runQMake(int argc, char **argv) // This is particularly important for things like QtCreator and scripted builds. setvbuf(stdout, (char *)NULL, _IONBF, 0); -#ifdef Q_OS_WIN // Workaround for inferior/missing command line tools on Windows: make our own! if (argc >= 2 && !strcmp(argv[1], "-install")) return doInstall(argc - 2, argv + 2); +#ifdef Q_OS_WIN { // Support running as Visual C++'s compiler const wchar_t *cmdline = _wgetenv(L"MSC_CMD_FLAGS"); diff --git a/tests/auto/tools/qmake/tst_qmake.cpp b/tests/auto/tools/qmake/tst_qmake.cpp index eed1fe1a56..10aabcf196 100644 --- a/tests/auto/tools/qmake/tst_qmake.cpp +++ b/tests/auto/tools/qmake/tst_qmake.cpp @@ -286,6 +286,7 @@ void tst_qmake::install_files() QVERIFY( test_compiler.make( workDir, "install" )); QVERIFY( test_compiler.exists( workDir + "/dist", "foo", Exe, "1.0.0" )); QVERIFY( test_compiler.exists( workDir + "/dist", "test.txt", Plain, "1.0.0" )); + QCOMPARE(QFileInfo(workDir + "/test.txt").lastModified(), QFileInfo(workDir + "/dist/test.txt").lastModified()); QVERIFY( test_compiler.make( workDir, "uninstall" )); QVERIFY( test_compiler.makeDistClean( workDir )); -- cgit v1.2.3