Uploaded image for project: 'Qbs ("Cubes")'
  1. Qbs ("Cubes")
  2. QBS-1150

Feature idea: Rule overriding

    XMLWordPrintable

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

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

              kandeler Christian Kandeler
              jakepetroules Jake Petroules (DO NOT ASSIGN ISSUES)
              Votes:
              1 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:

                Gerrit Reviews

                  There are no open Gerrit changes