Details
-
Suggestion
-
Resolution: Unresolved
-
Not Evaluated
-
None
-
None
-
None
Description
In response to a user suggestion on IRC, I thought of an interesting feature possibility. The user was wanting to set the visibility of functions in a dynamic library built entirely from static libraries.
However, -fvisibility is not respected by the toolchain in regards to object files coming from static libraries. So the solution is to extract all of the object files from the static libraries, and then pass the object files to the linker directly.
The solution in Qbs requires an indirect product - one which takes the static libraries as input (possibly the target artifacts of other product(s)) and uses a Rule to extract the object files ("obj" being the product type), and another which actually creates the shared library. To collapse the latter two products into a single one is not possible because the dynamiclibrary linker rule takes staticlibrary as input, so the linker would end up taking both the original static libraries AND the extracted object files as inputs.
For example:
import qbs import qbs.File import qbs.FileInfo import qbs.Process Project { StaticLibrary { Depends { name: "cpp" } bundle.isBundle: false name: "lib1" files: ["lib1.c"] } StaticLibrary { Depends { name: "cpp" } bundle.isBundle: false name: "lib2" files: ["lib2.c"] } StaticLibrary { Depends { name: "cpp" } bundle.isBundle: false name: "lib3" files: ["lib3.c"] } Product { name: "intermediate" Depends { name: "lib1" } Depends { name: "lib2" } Depends { name: "lib3" } // dummy product needed to avoid lib4 seeing inputs tagged // 'staticlibrary' and linking them as well as the extracted object files type: ["staticlibrary_duplicate"] Rule { inputsFromDependencies: ["staticlibrary"] outputFileTags: ["staticlibrary_duplicate"] outputArtifacts: { var artifacts = []; var libs = inputs["staticlibrary"]; for (var i = 0; i < libs.length; ++i) { artifacts.push({ filePath: FileInfo.joinPaths(product.buildDirectory, libs[i].fileName), fileTags: ["staticlibrary_duplicate"], qbs: { _originalFilePath: libs[i].filePath } }); } return artifacts; } prepare: { var cmds = []; var libs = outputs["staticlibrary_duplicate"]; for (var i = 0; i < libs.length; ++i) { var cmd = new JavaScriptCommand(); cmd.silent = true; cmd.src = libs[i].qbs._originalFilePath; cmd.dst = libs[i].filePath; cmd.sourceCode = function () { File.copy(src, dst); } cmds.push(cmd); } return cmds; } } } DynamicLibrary { name: "lib4" bundle.isBundle: false Depends { name: "cpp" } // Depends { name: "lib1" } // Depends { name: "lib2" } // Depends { name: "lib3" } Depends { name: "intermediate" } Rule { // inputsFromDependencies: ["staticlibrary"] inputsFromDependencies: ["staticlibrary_duplicate"] outputFileTags: ["obj"] outputArtifacts: { var artifacts = []; // var libs = inputs["staticlibrary"]; var libs = inputs["staticlibrary_duplicate"]; for (var i = 0; i < libs.length; ++i) { var process; try { process = new Process(); process.exec(product.cpp.archiverPath, ["t", libs[i].filePath], true); process.readStdOut().trim().split(/\r?\n/g).map(function (line) { if (line && !["__.SYMDEF", "__.SYMDEF SORTED"].contains(line)) { artifacts.push({ filePath: FileInfo.joinPaths(product.buildDirectory, ".obj", libs[i].fileName, line), fileTags: ["obj"] }) } }); } finally { if (process) process.close(); } } return artifacts; } prepare: { var cmds = []; // var libs = inputs["staticlibrary"]; var libs = inputs["staticlibrary_duplicate"]; for (var i = 0; i < libs.length; ++i) { var cmd = new Command(product.cpp.archiverPath, ["x", libs[i].filePath]); cmd.workingDirectory = FileInfo.joinPaths(product.buildDirectory, ".obj", libs[i].fileName); cmd.description = "extracting " + libs[i].fileName; cmds.push(cmd); } return cmds; } } } }
To solve this, consider the possibility of Rule overriding. If a Rule whose id matches the id of a rule coming from a Module (or perhaps by matching a 'name' property, or perhaps some other means TBD), then a new Rule would not be defined, but any properties of that Rule would replace the properties of the "inherited" Rule as defined in the corresponding Module.
The user's product could then look like:
import qbs import qbs.File import qbs.FileInfo import qbs.Process Project { StaticLibrary { Depends { name: "cpp" } bundle.isBundle: false name: "lib1" files: ["lib1.c"] } StaticLibrary { Depends { name: "cpp" } bundle.isBundle: false name: "lib2" files: ["lib2.c"] } StaticLibrary { Depends { name: "cpp" } bundle.isBundle: false name: "lib3" files: ["lib3.c"] } DynamicLibrary { name: "lib4" bundle.isBundle: false Depends { name: "cpp" } Depends { name: "lib1" } Depends { name: "lib2" } Depends { name: "lib3" } Rule { id: dynamicLibraryLinker // overrides inputsFromDependencies property of dynamicLibraryLinker Rule from cpp module; // now it doesn't include staticlibrary so the linker rule will only consider the object // file inputs from this rule and not the static libraries from lib1, lib2, lib3 inputsFromDependencies: ["dynamiclibrary_copy"] } Rule { inputsFromDependencies: ["staticlibrary"] outputFileTags: ["obj"] outputArtifacts: { var artifacts = []; var libs = inputs["staticlibrary"]; for (var i = 0; i < libs.length; ++i) { var process; try { process = new Process(); process.exec(product.cpp.archiverPath, ["t", libs[i].filePath], true); process.readStdOut().trim().split(/\r?\n/g).map(function (line) { if (line && !["__.SYMDEF", "__.SYMDEF SORTED"].contains(line)) { artifacts.push({ filePath: FileInfo.joinPaths(product.buildDirectory, ".obj", libs[i].fileName, line), fileTags: ["obj"] }) } }); } finally { if (process) process.close(); } } return artifacts; } prepare: { var cmds = []; var libs = inputs["staticlibrary"]; for (var i = 0; i < libs.length; ++i) { var cmd = new Command(product.cpp.archiverPath, ["x", libs[i].filePath]); cmd.workingDirectory = FileInfo.joinPaths(product.buildDirectory, ".obj", libs[i].fileName); cmd.description = "extracting " + libs[i].fileName; cmds.push(cmd); } return cmds; } } } }
Note the replacement of the "intermediate" Product with the minimal "dynamicLibraryLinker" Rule. Possibly a magic variable could access the original property value, such as inputsFromDependencies: original.indexOf("staticlibrary") >= 0 ? original.splice(original.indexOf("staticlibrary"), 1) : original
Note that this proposal only covers overriding at the Product level, since there can be no conflicts here, as there would be with a module overriding a rule in another module.
Attachments
Issue Links
- relates to
-
QBS-5 create possibility to inject rules
-
- Open
-