Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-09-24 22:51:19

0001 // -*- C++ -*-
0002 //
0003 // Package:     ParameterSet
0004 // Class  :     ConfigurationDescriptions
0005 //
0006 // Implementation:
0007 //     <Notes on implementation>
0008 //
0009 // Original Author:  W. David Dagenhart
0010 //         Created:  17 December 2008
0011 //
0012 
0013 #include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h"
0014 #include "FWCore/ParameterSet/interface/DocFormatHelper.h"
0015 #include "FWCore/ParameterSet/interface/defaultModuleLabel.h"
0016 #include "FWCore/Utilities/interface/Algorithms.h"
0017 #include "FWCore/Utilities/interface/EDMException.h"
0018 
0019 #include <fstream>
0020 #include <iostream>
0021 #include <iomanip>
0022 #include <sstream>
0023 #include <cstring>
0024 #include <cerrno>
0025 #include <cstring>
0026 
0027 namespace {
0028   void matchLabel(std::pair<std::string, edm::ParameterSetDescription> const& thePair,
0029                   std::string const& moduleLabel,
0030                   edm::ParameterSetDescription const*& psetDesc) {
0031     if (thePair.first == moduleLabel) {
0032       psetDesc = &thePair.second;
0033     }
0034   }
0035 }  // namespace
0036 
0037 static const char* const kSource = "Source";
0038 static const char* const kService = "Service";
0039 static const char* const k_source = "source";
0040 
0041 namespace edm {
0042 
0043   ConfigurationDescriptions::ConfigurationDescriptions(std::string const& baseType, std::string const& pluginName)
0044       : baseType_(baseType), pluginName_(pluginName), defaultDescDefined_(false) {}
0045 
0046   ConfigurationDescriptions::~ConfigurationDescriptions() {}
0047 
0048   void ConfigurationDescriptions::setComment(std::string const& value) { comment_ = value; }
0049 
0050   void ConfigurationDescriptions::setComment(char const* value) { comment_ = value; }
0051 
0052   void ConfigurationDescriptions::add(char const* label, ParameterSetDescription const& psetDescription) {
0053     std::string labelString(label);
0054     add(labelString, psetDescription);
0055   }
0056 
0057   void ConfigurationDescriptions::add(std::string const& label, ParameterSetDescription const& psetDescription) {
0058     if (0 == strcmp(baseType_.c_str(), kSource)) {
0059       if (0 != strcmp(label.c_str(), k_source)) {
0060         throw edm::Exception(edm::errors::LogicError,
0061                              "ConfigurationDescriptions::add, when adding a ParameterSetDescription for a source the "
0062                              "label must be \"source\"\n");
0063       }
0064       if (!descriptions_.empty() || defaultDescDefined_ == true) {
0065         throw edm::Exception(
0066             edm::errors::LogicError,
0067             "ConfigurationDescriptions::add, for a source only 1 ParameterSetDescription may be added\n");
0068       }
0069     } else if (0 == strcmp(baseType_.c_str(), kService)) {
0070       if (!descriptions_.empty() || defaultDescDefined_ == true) {
0071         throw edm::Exception(
0072             edm::errors::LogicError,
0073             "ConfigurationDescriptions::add, for a service only 1 ParameterSetDescription may be added\n");
0074       }
0075     }
0076 
0077     // To minimize the number of copies involved create an empty description first
0078     // and push it into the vector.  Then perform the copy.
0079     std::pair<std::string, ParameterSetDescription> pairWithEmptyDescription;
0080     descriptions_.push_back(pairWithEmptyDescription);
0081     std::pair<std::string, ParameterSetDescription>& pair = descriptions_.back();
0082 
0083     pair.first = label;
0084     pair.second = psetDescription;
0085   }
0086 
0087   void ConfigurationDescriptions::addWithDefaultLabel(ParameterSetDescription const& psetDescription) {
0088     std::string label;
0089     if (kService == baseType_) {
0090       label = pluginName_;
0091     } else if (kSource == baseType_) {
0092       label = "source";
0093     } else {
0094       label = defaultModuleLabel(pluginName_);
0095     }
0096     add(label, psetDescription);
0097   }
0098 
0099   void ConfigurationDescriptions::addDefault(ParameterSetDescription const& psetDescription) {
0100     if (0 == strcmp(baseType_.c_str(), kSource) || 0 == strcmp(baseType_.c_str(), kService)) {
0101       if (!descriptions_.empty() || defaultDescDefined_ == true) {
0102         throw edm::Exception(edm::errors::LogicError,
0103                              "ConfigurationDescriptions::addDefault, for a source or service only 1 "
0104                              "ParameterSetDescription may be added\n");
0105       }
0106     }
0107 
0108     defaultDescDefined_ = true;
0109     defaultDesc_ = psetDescription;
0110   }
0111 
0112   ParameterSetDescription* ConfigurationDescriptions::defaultDescription() {
0113     if (defaultDescDefined_) {
0114       return &defaultDesc_;
0115     }
0116     return nullptr;
0117   }
0118 
0119   ConfigurationDescriptions::iterator ConfigurationDescriptions::begin() { return descriptions_.begin(); }
0120 
0121   ConfigurationDescriptions::iterator ConfigurationDescriptions::end() { return descriptions_.end(); }
0122 
0123   void ConfigurationDescriptions::validate(ParameterSet& pset, std::string const& moduleLabel) const {
0124     ParameterSetDescription const* psetDesc = nullptr;
0125     for_all(descriptions_, std::bind(&matchLabel, std::placeholders::_1, std::cref(moduleLabel), std::ref(psetDesc)));
0126 
0127     // If there is a matching label
0128     if (psetDesc != nullptr) {
0129       psetDesc->validate(pset);
0130     }
0131     // Is there an explicit description to be used for a non standard label
0132     else if (defaultDescDefined_) {
0133       defaultDesc_.validate(pset);
0134     }
0135     // Otherwise use the first one.
0136     else if (!descriptions_.empty()) {
0137       descriptions_[0].second.validate(pset);
0138     }
0139     // It is possible for no descriptions to be defined and no validation occurs
0140     // for this module ever.
0141   }
0142 
0143   void ConfigurationDescriptions::writeCfis(std::set<std::string>& usedCfiFileNames) const {
0144     bool wroteClassFile = false;
0145     cfi::Paths paths;
0146     if (defaultDescDefined_) {
0147       paths = writeClassFile(defaultDesc_, not descriptions_.empty());
0148       wroteClassFile = true;
0149     } else if (descriptions_.size() == 1) {
0150       paths = writeClassFile(descriptions_.begin()->second, true);
0151       wroteClassFile = true;
0152     }
0153     CfiOptions ops = wroteClassFile ? CfiOptions{cfi::Untyped{paths}} : CfiOptions{cfi::Typed{}};
0154     for (auto& d : descriptions_) {
0155       writeCfiForLabel(
0156           d, baseType_, pluginName_, (not defaultDescDefined_) and (1 == descriptions_.size()), ops, usedCfiFileNames);
0157     }
0158   }
0159 
0160   namespace {
0161     std::string modifyPluginName(std::string iName) {
0162       auto found = iName.find("::");
0163       while (found != std::string::npos) {
0164         iName.replace(found, 2, "_");
0165         found = iName.find("::");
0166       }
0167       //Symbols that can appear in our plugin names but can't in python function names
0168       const std::string toReplace("@<>,");
0169       found = iName.find_first_of(toReplace);
0170       while (found != std::string::npos) {
0171         iName.replace(found, 1, "_");
0172         found = iName.find_first_of(toReplace, found);
0173       }
0174       return iName;
0175     }
0176   }  // namespace
0177 
0178   cfi::Paths ConfigurationDescriptions::writeClassFile(ParameterSetDescription const& iDesc,
0179                                                        bool willUseWithCfis) const {
0180     std::string pluginName = modifyPluginName(pluginName_);
0181 
0182     std::string fileName = pluginName + ".py";
0183     std::ofstream outFile(fileName.c_str());
0184     if (outFile.fail()) {
0185       edm::Exception ex(edm::errors::LogicError, "Creating class file failed.\n");
0186       ex << "Opening a file '" << fileName << "' failed.\n";
0187       ex << "Error code from errno " << errno << ": " << std::strerror(errno) << "\n";
0188 
0189       ex.addContext("Executing function ConfigurationDescriptions::writeDefault");
0190       throw ex;
0191     }
0192     outFile << "import FWCore.ParameterSet.Config as cms\n\n";
0193     outFile << "def " << pluginName
0194             << "(*args, **kwargs):\n"
0195                "  mod = cms."
0196             << baseType_ << "('" << pluginName_ << "'";
0197 
0198     bool startWithComma = true;
0199     int indentation = 4;
0200     CfiOptions ops = willUseWithCfis ? CfiOptions{cfi::ClassFile{}} : CfiOptions{cfi::Typed{}};
0201     iDesc.writeCfi(outFile, startWithComma, indentation, ops);
0202 
0203     outFile << ")\n"
0204                "  for a in args:\n"
0205                "    mod.update_(a)\n"
0206                "  mod.update_(kwargs)\n"
0207                "  return mod\n";
0208 
0209     outFile.close();
0210     return std::holds_alternative<cfi::ClassFile>(ops) ? std::get<cfi::ClassFile>(ops).releasePaths() : cfi::Paths{};
0211   }
0212 
0213   void ConfigurationDescriptions::writeCfiForLabel(std::pair<std::string, ParameterSetDescription> const& labelAndDesc,
0214                                                    std::string const& baseType,
0215                                                    std::string const& pluginName,
0216                                                    bool isSameAsDefault,
0217                                                    CfiOptions& options,
0218                                                    std::set<std::string>& usedCfiFileNames) {
0219     if (0 == strcmp(baseType.c_str(), kService) && labelAndDesc.first != pluginName) {
0220       throw edm::Exception(edm::errors::LogicError,
0221                            "ConfigurationDescriptions::writeCfiForLabel\nFor a service the label and the plugin name "
0222                            "must be the same.\n")
0223           << "This error is probably caused by an incorrect label being passed\nto the ConfigurationDescriptions::add "
0224              "function earlier.\n"
0225           << "plugin name = \"" << pluginName << "\"  label name = \"" << labelAndDesc.first << "\"\n";
0226     }
0227 
0228     std::string cfi_filename;
0229     if (0 == strcmp(baseType.c_str(), kSource)) {
0230       cfi_filename = pluginName + "_cfi.py";
0231     } else {
0232       cfi_filename = labelAndDesc.first + "_cfi.py";
0233     }
0234     if (!usedCfiFileNames.insert(cfi_filename).second) {
0235       edm::Exception ex(edm::errors::LogicError,
0236                         "Two cfi files are being generated with the same name in the same directory.\n");
0237       ex << "The cfi file name is '" << cfi_filename << "' and\n"
0238          << "the module label is \'" << labelAndDesc.first << "\'.\n"
0239          << "This error is probably caused by an error in one or more fillDescriptions functions\n"
0240          << "where duplicate module labels are being passed to the ConfigurationDescriptions::add\n"
0241          << "function. All such module labels must be unique within a package.\n"
0242          << "If you do not want the generated cfi file and do not need more than one\n"
0243          << "description for a plugin, then a way to fix this is to use the addDefault\n"
0244          << "function instead of the add function.\n"
0245          << "There are 3 common ways this problem can happen.\n"
0246          << "1. This can happen when a module label is explicitly duplicated in one or more\n"
0247          << "fillDescriptions functions. Fix these by changing the module labels to be unique.\n"
0248          << "2. This can also happen when a module class is a template class and plugins are\n"
0249          << "defined by instantiations with differing template parameters and these plugins\n"
0250          << "share the same fillDescriptions function. Fix these by specializing the fillDescriptions\n"
0251          << "function for each template instantiation.\n"
0252          << "3. This can also happen when there is an inheritance heirarchy and multiple plugin modules\n"
0253          << "are defined using derived classes and the base class which share the same fillDescriptions\n"
0254          << "function. Fix these by redefining the fillDescriptions function in each derived class.\n";
0255       ex.addContext("Executing function ConfigurationDescriptions::writeCfiForLabel");
0256       throw ex;
0257     }
0258     std::ofstream outFile(cfi_filename.c_str());
0259     if (outFile.fail()) {
0260       edm::Exception ex(edm::errors::LogicError, "Creating cfi file failed.\n");
0261       ex << "Opening a file '" << cfi_filename << "' for module '" << labelAndDesc.first << "' failed.\n";
0262       ex << "Error code from errno " << errno << ": " << std::strerror(errno) << "\n";
0263 
0264       ex.addContext("Executing function ConfigurationDescriptions::writeCfiForLabel");
0265       throw ex;
0266     }
0267 
0268     bool startWithComma = true;
0269     if (not shouldWriteUntyped(options)) {
0270       outFile << "import FWCore.ParameterSet.Config as cms\n\n";
0271       outFile << labelAndDesc.first << " = cms." << baseType << "('" << pluginName << "'";
0272     } else {
0273       outFile << "import FWCore.ParameterSet.Config as cms\n\n";
0274 
0275       auto pythonName = modifyPluginName(pluginName);
0276       outFile << "from ." << pythonName << " import " << pythonName << "\n\n";
0277       outFile << labelAndDesc.first << " = " << pythonName << "(";
0278       startWithComma = false;
0279     }
0280     if (not isSameAsDefault) {
0281       int indentation = 2;
0282       labelAndDesc.second.writeCfi(outFile, startWithComma, indentation, options);
0283     }
0284     outFile << ")\n";
0285 
0286     outFile.close();
0287 
0288     if (0 == strcmp(baseType.c_str(), kSource)) {
0289       std::cout << pluginName << "\n";
0290     } else {
0291       std::cout << labelAndDesc.first << "\n";
0292     }
0293   }
0294 
0295   void ConfigurationDescriptions::print(std::ostream& os,
0296                                         std::string const& moduleLabel,
0297                                         bool brief,
0298                                         bool printOnlyLabels,
0299                                         size_t lineWidth,
0300                                         int indentation,
0301                                         int iPlugin) const {
0302     if (!brief) {
0303       if (!comment().empty()) {
0304         DocFormatHelper::wrapAndPrintText(os, comment(), indentation, lineWidth);
0305       }
0306       os << "\n";
0307     }
0308 
0309     if (descriptions_.empty() && !defaultDescDefined_) {
0310       char oldFill = os.fill();
0311       indentation += DocFormatHelper::offsetModuleLabel();
0312       os << std::setfill(' ') << std::setw(indentation) << "";
0313       os << "There are no PSet descriptions defined for this plugin.\n";
0314       os << std::setfill(' ') << std::setw(indentation) << "";
0315       os << "PSets will not be validated and no cfi files will be generated.\n";
0316       os << std::setfill(oldFill);
0317       if (!brief)
0318         os << "\n";
0319       return;
0320     }
0321 
0322     if (descriptions_.empty() && defaultDescDefined_ && defaultDesc_.isUnknown()) {
0323       indentation += DocFormatHelper::offsetModuleLabel();
0324       char oldFill = os.fill();
0325       os << std::setfill(' ') << std::setw(indentation) << "";
0326       os << "This plugin has not implemented the function which defines its\n";
0327       os << std::setfill(' ') << std::setw(indentation) << "";
0328       os << "configuration descriptions yet. No descriptions are available.\n";
0329       os << std::setfill(' ') << std::setw(indentation) << "";
0330       os << "Its PSets will not be validated, and no cfi files will be generated.\n";
0331       os << std::setfill(oldFill);
0332       if (!brief)
0333         os << "\n";
0334       return;
0335     }
0336 
0337     if (!brief) {
0338       std::stringstream ss;
0339       if (defaultDescDefined_) {
0340         if (descriptions_.empty()) {
0341           ss << "This plugin has only one PSet description. "
0342              << "This description is always used to validate configurations. "
0343              << "Because this configuration has no label, no cfi files will be generated.";
0344         } else {
0345           ss << "This plugin has " << (descriptions_.size() + 1U) << " PSet descriptions. "
0346              << "The description used to validate a configuration is selected by "
0347              << "matching the module labels. If none match, then the last description, "
0348              << "which has no label, is selected. "
0349              << "A cfi file will be generated for each configuration with a module label.";
0350         }
0351       } else {
0352         if (descriptions_.size() == 1U) {
0353           ss << "This plugin has " << descriptions_.size() << " PSet description. "
0354              << "This description is always used to validate configurations. "
0355              << "The label below is used when generating the cfi file.";
0356         } else {
0357           ss << "This plugin has " << descriptions_.size() << " PSet descriptions. "
0358              << "The description used to validate a configuration is selected by "
0359              << "matching the module labels. If none match the first description below is used. "
0360              << "The module labels below are also used when generating the cfi files.";
0361         }
0362       }
0363       DocFormatHelper::wrapAndPrintText(os, ss.str(), indentation, lineWidth);
0364       os << "\n";
0365     }
0366 
0367     indentation += DocFormatHelper::offsetModuleLabel();
0368 
0369     DescriptionCounter counter;
0370     counter.iPlugin = iPlugin;
0371     counter.iSelectedModule = 0;
0372     counter.iModule = 0;
0373 
0374     for (auto const& d : descriptions_) {
0375       printForLabel(d, os, moduleLabel, brief, printOnlyLabels, lineWidth, indentation, counter);
0376     }
0377 
0378     if (defaultDescDefined_) {
0379       printForLabel(os,
0380                     std::string("@default"),
0381                     defaultDesc_,
0382                     moduleLabel,
0383                     brief,
0384                     printOnlyLabels,
0385                     lineWidth,
0386                     indentation,
0387                     counter);
0388     }
0389   }
0390 
0391   void ConfigurationDescriptions::printForLabel(std::pair<std::string, ParameterSetDescription> const& labelAndDesc,
0392                                                 std::ostream& os,
0393                                                 std::string const& moduleLabel,
0394                                                 bool brief,
0395                                                 bool printOnlyLabels,
0396                                                 size_t lineWidth,
0397                                                 int indentation,
0398                                                 DescriptionCounter& counter) const {
0399     printForLabel(os,
0400                   labelAndDesc.first,
0401                   labelAndDesc.second,
0402                   moduleLabel,
0403                   brief,
0404                   printOnlyLabels,
0405                   lineWidth,
0406                   indentation,
0407                   counter);
0408   }
0409 
0410   void ConfigurationDescriptions::printForLabel(std::ostream& os,
0411                                                 std::string const& label,
0412                                                 ParameterSetDescription const& description,
0413                                                 std::string const& moduleLabel,
0414                                                 bool brief,
0415                                                 bool printOnlyLabels,
0416                                                 size_t lineWidth,
0417                                                 int indentation,
0418                                                 DescriptionCounter& counter) const {
0419     ++counter.iModule;
0420     if (!moduleLabel.empty() && label != moduleLabel)
0421       return;
0422     ++counter.iSelectedModule;
0423 
0424     std::stringstream ss;
0425     ss << counter.iPlugin << "." << counter.iSelectedModule;
0426     std::string section = ss.str();
0427 
0428     char oldFill = os.fill();
0429     os << std::setfill(' ') << std::setw(indentation) << "" << std::setfill(oldFill);
0430     os << section << " ";
0431     if (label == std::string("@default")) {
0432       os << "description without a module label\n";
0433     } else {
0434       if (!brief) {
0435         if (0 == strcmp(baseType_.c_str(), kSource) || 0 == strcmp(baseType_.c_str(), kService)) {
0436           os << "label: ";
0437         } else {
0438           os << "module label: ";
0439         }
0440       }
0441       os << label << "\n";
0442     }
0443 
0444     if (!brief) {
0445       if (!description.comment().empty()) {
0446         DocFormatHelper::wrapAndPrintText(os, description.comment(), indentation, lineWidth - indentation);
0447       }
0448       os << "\n";
0449     }
0450     if (printOnlyLabels)
0451       return;
0452 
0453     DocFormatHelper dfh;
0454     dfh.setBrief(brief);
0455     dfh.setLineWidth(lineWidth);
0456     dfh.setIndentation(indentation + DocFormatHelper::offsetTopLevelPSet());
0457     dfh.setSection(section);
0458     dfh.setParent(DocFormatHelper::TOP);
0459 
0460     description.print(os, dfh);
0461   }
0462 }  // namespace edm