Skip to content

Commit

Permalink
qmake: Fix overlong command lines for static Qt builds on Windows
Browse files Browse the repository at this point in the history
Linker response files for the MinGW and Unix makefile generators are
controlled by the variable QMAKE_LINK_OBJECT_MAX.  This variable holds a
number.  If the number of object files passed to the linker exceeds this
number, a linker response file containing object file paths is created.

This heuristic is extremely imprecise.  It doesn't take into account the
length of object file names nor the length of $$OBJECTS_DIR.

Also, when using a static Qt, a big part of the linker command line are
libraries.  A relatively small example can fail to link with "The
command line is too long" on Windows, even with the object files being
in a response file.

The MinGW makefile generator already reads the variable
QMAKE_RESPONSEFILE_THRESHOLD for compiler response files.  Re-use this
variable for the linker response file of the Unix and MinGW makefile
generators.

If QMAKE_RESPONSEFILE_THRESHOLD is set, use it to determine whether to
create a response file.  QMAKE_LINK_OBJECT_MAX is then ignored.  The
response file contains objects and libraries.

If QMAKE_RESPONSEFILE_THRESHOLD is not set, use QMAKE_LINK_OBJECT_MAX to
determine whether to create a response file.  The response file contains
only object files.

QMAKE_LINK_OBJECT_SCRIPT is used in both cases to specify a common base
name of all linker response files.

Pick-to: 6.2 6.3
Task-number: QTBUG-100559
Change-Id: I3c78354fa5ebb1a86438ec804679e0ee776c3f49
Reviewed-by: Alexandru Croitor <[email protected]>
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Kai Koehne <[email protected]>
  • Loading branch information
jobor committed Feb 21, 2022
1 parent 5731b83 commit 1d3b190
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 28 deletions.
44 changes: 43 additions & 1 deletion qmake/generators/makefile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3498,7 +3498,7 @@ ProKey MakefileGenerator::fullTargetVariable() const
QString MakefileGenerator::createResponseFile(
const QString &baseName,
const ProStringList &objList,
const QString &prefix)
const QString &prefix) const
{
QString fileName = baseName + '.' + var("QMAKE_ORIG_TARGET");
if (!var("BUILD_NAME").isEmpty())
Expand Down Expand Up @@ -3531,4 +3531,46 @@ QString MakefileGenerator::createResponseFile(
return fileName;
}

/*
* If the threshold for response files are overstepped, create a response file for the linker
* command line.
*/
MakefileGenerator::LinkerResponseFileInfo MakefileGenerator::maybeCreateLinkerResponseFile() const
{
bool useLinkObjectMax = false;
bool ok;
int threshold = project->first("QMAKE_RESPONSEFILE_THRESHOLD").toInt(&ok);
if (!ok) {
threshold = project->first("QMAKE_LINK_OBJECT_MAX").toInt(&ok);
if (ok)
useLinkObjectMax = true;
}
if (!ok)
return {};

ProStringList linkerInputs = project->values("OBJECTS");
if (useLinkObjectMax) {
// When using QMAKE_LINK_OBJECT_MAX, the number of object files (regardless of their path
// length) decides whether to use a response file. This is far from being a useful
// heuristic but let's keep this behavior for backwards compatibility.
if (linkerInputs.count() < threshold)
return {};
} else {
// When using QMAKE_REPONSEFILE_THRESHOLD, try to determine the command line length of the
// inputs.
linkerInputs += project->values("LIBS");
int totalLength = std::accumulate(linkerInputs.cbegin(), linkerInputs.cend(), 0,
[](int total, const ProString &input) {
return total + input.size() + 1;
});
if (totalLength < threshold)
return {};
}

return {
createResponseFile(fileVar("OBJECTS_DIR") + var("QMAKE_LINK_OBJECT_SCRIPT"), linkerInputs),
useLinkObjectMax
};
}

QT_END_NAMESPACE
12 changes: 11 additions & 1 deletion qmake/generators/makefile.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,17 @@ class MakefileGenerator : protected QMakeSourceFileInfo
const QString &fixedFile);
QString createResponseFile(const QString &baseName,
const ProStringList &objList,
const QString &prefix = QString());
const QString &prefix = QString()) const;

struct LinkerResponseFileInfo
{
QString filePath;
bool onlyObjects;

bool isValid() const { return !filePath.isEmpty(); }
};

LinkerResponseFileInfo maybeCreateLinkerResponseFile() const;

public:
QMakeProject *projectFile() const;
Expand Down
2 changes: 1 addition & 1 deletion qmake/generators/unix/unixmake.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class UnixMakefileGenerator : public MakefileGenerator
void writeSubTargets(QTextStream &t, QList<SubTarget*> subtargets, int flags) override;
void writeMakeParts(QTextStream &);
bool writeMakefile(QTextStream &) override;
std::pair<bool, QString> writeObjectsPart(QTextStream &, bool do_incremental);
bool writeObjectsPart(QTextStream &, bool do_incremental);
private:
void init2();
ProStringList libdirToFlags(const ProKey &key);
Expand Down
32 changes: 16 additions & 16 deletions qmake/generators/unix/unixmake2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
t << "####### Files\n\n";
// This is used by the dist target.
t << "SOURCES = " << fileVarList("SOURCES") << ' ' << fileVarList("GENERATED_SOURCES") << Qt::endl;
auto objectParts = writeObjectsPart(t, do_incremental);
src_incremental = objectParts.first;

src_incremental = writeObjectsPart(t, do_incremental);
if(do_incremental && !src_incremental)
do_incremental = false;
t << "DIST = " << valList(fileFixify(project->values("DISTFILES").toQStringList())) << " "
Expand Down Expand Up @@ -395,6 +395,7 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
}
}
}
LinkerResponseFileInfo linkerResponseFile = maybeCreateLinkerResponseFile();
QString deps = escapeDependencyPath(fileFixify(Option::output.fileName()));
QString allDeps;
if (!project->values("QMAKE_APP_FLAG").isEmpty() || project->first("TEMPLATE") == "aux") {
Expand Down Expand Up @@ -481,8 +482,13 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
t << mkdir_p_asstring(destdir) << "\n\t";
if (!project->isEmpty("QMAKE_PRE_LINK"))
t << var("QMAKE_PRE_LINK") << "\n\t";
t << "$(LINK) $(LFLAGS) " << var("QMAKE_LINK_O_FLAG") << "$(TARGET) "
<< objectParts.second << " $(OBJCOMP) $(LIBS)";
t << "$(LINK) $(LFLAGS) " << var("QMAKE_LINK_O_FLAG") << "$(TARGET) ";
if (!linkerResponseFile.isValid())
t << " $(OBJECTS) $(OBJCOMP) $(LIBS)";
else if (linkerResponseFile.onlyObjects)
t << " @" << linkerResponseFile.filePath << " $(OBJCOMP) $(LIBS)";
else
t << " $(OBJCOMP) @" << linkerResponseFile.filePath;
if (!project->isEmpty("QMAKE_POST_LINK"))
t << "\n\t" << var("QMAKE_POST_LINK");
}
Expand Down Expand Up @@ -557,7 +563,10 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
<< incr_deps << " $(SUBLIBS) " << target_deps << ' ' << depVar("POST_TARGETDEPS");
} else {
ProStringList &cmd = project->values("QMAKE_LINK_SHLIB_CMD");
cmd[0] = cmd.at(0).toQString().replace(QLatin1String("$(OBJECTS)"), objectParts.second);
if (linkerResponseFile.isValid()) {
cmd[0] = cmd.at(0).toQString().replace(QLatin1String("$(OBJECTS)"),
"@" + linkerResponseFile.filePath);
}
t << destdir_d << depVar("TARGET") << ": " << depVar("PRE_TARGETDEPS")
<< " $(OBJECTS) $(SUBLIBS) $(OBJCOMP) " << target_deps
<< ' ' << depVar("POST_TARGETDEPS");
Expand Down Expand Up @@ -1550,7 +1559,7 @@ UnixMakefileGenerator::writeLibtoolFile()
"libdir='" << Option::fixPathToTargetOS(install_dir.toQString(), false) << "'\n";
}

std::pair<bool, QString> UnixMakefileGenerator::writeObjectsPart(QTextStream &t, bool do_incremental)
bool UnixMakefileGenerator::writeObjectsPart(QTextStream &t, bool do_incremental)
{
bool src_incremental = false;
QString objectsLinkLine;
Expand Down Expand Up @@ -1584,18 +1593,9 @@ std::pair<bool, QString> UnixMakefileGenerator::writeObjectsPart(QTextStream &t,
<< escapeFilePaths(incrs_out).join(QString(" \\\n\t\t")) << Qt::endl;
}
} else {
const ProString &objMax = project->first("QMAKE_LINK_OBJECT_MAX");
// Used all over the place in both deps and commands.
if (objMax.isEmpty() || project->values("OBJECTS").count() < objMax.toInt()) {
objectsLinkLine = "$(OBJECTS)";
} else {
const QString ld_response_file = createResponseFile(
fileVar("OBJECTS_DIR") + var("QMAKE_LINK_OBJECT_SCRIPT"), objs);
objectsLinkLine = "@" + escapeFilePath(ld_response_file);
}
t << "OBJECTS = " << valList(escapeDependencyPaths(objs)) << Qt::endl;
}
return std::make_pair(src_incremental, objectsLinkLine);
return src_incremental;
}

QT_END_NAMESPACE
18 changes: 9 additions & 9 deletions qmake/generators/win32/mingw_make.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,18 @@ void MingwMakefileGenerator::writeLibsPart(QTextStream &t)

void MingwMakefileGenerator::writeObjectsPart(QTextStream &t)
{
const ProString &objmax = project->first("QMAKE_LINK_OBJECT_MAX");
if (objmax.isEmpty() || project->values("OBJECTS").count() < objmax.toInt()) {
linkerResponseFile = maybeCreateLinkerResponseFile();
if (!linkerResponseFile.isValid()) {
objectsLinkLine = "$(OBJECTS)";
} else if (project->isActiveConfig("staticlib") && project->first("TEMPLATE") == "lib") {
// QMAKE_LIB is used for win32, including mingw, whereas QMAKE_AR is used on Unix.
QString ar_cmd = var("QMAKE_LIB");
if (ar_cmd.isEmpty())
ar_cmd = "ar -rc";
const QString ar_response_file =
createResponseFile(var("QMAKE_LINK_OBJECT_SCRIPT"), project->values("OBJECTS"));
objectsLinkLine = ar_cmd + ' ' + var("DEST_TARGET") + " @" + escapeFilePath(ar_response_file);
objectsLinkLine = ar_cmd + ' ' + var("DEST_TARGET") + " @"
+ escapeFilePath(linkerResponseFile.filePath);
} else {
const QString ld_response_file =
createResponseFile(var("QMAKE_LINK_OBJECT_SCRIPT"), project->values("OBJECTS"));
objectsLinkLine = "@" + escapeFilePath(ld_response_file);
objectsLinkLine = "@" + escapeFilePath(linkerResponseFile.filePath);
}
Win32MakefileGenerator::writeObjectsPart(t);
}
Expand Down Expand Up @@ -284,7 +281,10 @@ void MingwMakefileGenerator::writeBuildRulesPart(QTextStream &t)
t << "\n\t" << objectsLinkLine << " " ;
}
} else {
t << "\n\t$(LINKER) $(LFLAGS) " << var("QMAKE_LINK_O_FLAG") << "$(DESTDIR_TARGET) " << objectsLinkLine << " $(LIBS)";
t << "\n\t$(LINKER) $(LFLAGS) " << var("QMAKE_LINK_O_FLAG") << "$(DESTDIR_TARGET) "
<< objectsLinkLine;
if (!linkerResponseFile.isValid() || linkerResponseFile.onlyObjects)
t << " $(LIBS)";
}
if(!project->isEmpty("QMAKE_POST_LINK"))
t << "\n\t" <<var("QMAKE_POST_LINK");
Expand Down
1 change: 1 addition & 0 deletions qmake/generators/win32/mingw_make.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class MingwMakefileGenerator : public Win32MakefileGenerator
LibFlagType parseLibFlag(const ProString &flag, ProString *arg) override;

QString objectsLinkLine;
LinkerResponseFileInfo linkerResponseFile;
};

QT_END_NAMESPACE
Expand Down

0 comments on commit 1d3b190

Please sign in to comment.