/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmGlobalGhsMultiGenerator.h" #include "cmDocumentationEntry.h" #include "cmGeneratedFileStream.h" #include "cmGeneratorTarget.h" #include "cmGhsMultiGpj.h" #include "cmLocalGenerator.h" #include "cmLocalGhsMultiGenerator.h" #include "cmMakefile.h" #include "cmState.h" #include "cmStateTypes.h" #include "cmSystemTools.h" #include "cmVersion.h" #include "cmake.h" #include #include #include #include #include const char* cmGlobalGhsMultiGenerator::FILE_EXTENSION = ".gpj"; #ifdef __linux__ const char* cmGlobalGhsMultiGenerator::DEFAULT_BUILD_PROGRAM = "gbuild"; const char* cmGlobalGhsMultiGenerator::DEFAULT_TOOLSET_ROOT = "/usr/ghs"; #elif defined(_WIN32) const char* cmGlobalGhsMultiGenerator::DEFAULT_BUILD_PROGRAM = "gbuild.exe"; const char* cmGlobalGhsMultiGenerator::DEFAULT_TOOLSET_ROOT = "C:/ghs"; #endif cmGlobalGhsMultiGenerator::cmGlobalGhsMultiGenerator(cmake* cm) : cmGlobalGenerator(cm) { cm->GetState()->SetGhsMultiIDE(true); } cmGlobalGhsMultiGenerator::~cmGlobalGhsMultiGenerator() = default; cmLocalGenerator* cmGlobalGhsMultiGenerator::CreateLocalGenerator( cmMakefile* mf) { return new cmLocalGhsMultiGenerator(this, mf); } void cmGlobalGhsMultiGenerator::GetDocumentation(cmDocumentationEntry& entry) { entry.Name = GetActualName(); entry.Brief = "Generates Green Hills MULTI files (experimental, work-in-progress)."; } void cmGlobalGhsMultiGenerator::ComputeTargetObjectDirectory( cmGeneratorTarget* gt) const { // Compute full path to object file directory for this target. std::string dir; dir += gt->LocalGenerator->GetCurrentBinaryDirectory(); dir += "/"; dir += gt->LocalGenerator->GetTargetDirectory(gt); dir += "/"; gt->ObjectDirectory = dir; } bool cmGlobalGhsMultiGenerator::SetGeneratorToolset(std::string const& ts, cmMakefile* mf) { std::string tsp; /* toolset path */ this->GetToolset(mf, tsp, ts); /* no toolset was found */ if (tsp.empty()) { return false; } if (ts.empty()) { std::string message; message = "Green Hills MULTI: -T not specified; defaulting to \""; message += tsp; message += "\""; cmSystemTools::Message(message); /* store the full toolset for later use * -- already done if -T was specified */ mf->AddCacheDefinition("CMAKE_GENERATOR_TOOLSET", tsp.c_str(), "Location of generator toolset.", cmStateEnums::INTERNAL); } /* set the build tool to use */ std::string gbuild(tsp + ((tsp.back() == '/') ? "" : "/") + DEFAULT_BUILD_PROGRAM); const char* prevTool = mf->GetDefinition("CMAKE_MAKE_PROGRAM"); /* check if the toolset changed from last generate */ if (prevTool != nullptr && (gbuild != prevTool)) { std::string message = "toolset build tool: "; message += gbuild; message += "\nDoes not match the previously used build tool: "; message += prevTool; message += "\nEither remove the CMakeCache.txt file and CMakeFiles " "directory or choose a different binary directory."; cmSystemTools::Error(message); return false; } /* store the toolset that is being used for this build */ mf->AddCacheDefinition("CMAKE_MAKE_PROGRAM", gbuild.c_str(), "build program to use", cmStateEnums::INTERNAL, true); mf->AddDefinition("CMAKE_SYSTEM_VERSION", tsp.c_str()); return true; } bool cmGlobalGhsMultiGenerator::SetGeneratorPlatform(std::string const& p, cmMakefile* mf) { std::string arch; if (p.empty()) { cmSystemTools::Message( "Green Hills MULTI: -A not specified; defaulting to \"arm\""); arch = "arm"; /* store the platform name for later use * -- already done if -A was specified */ mf->AddCacheDefinition("CMAKE_GENERATOR_PLATFORM", arch.c_str(), "Name of generator platform.", cmStateEnums::INTERNAL); } else { arch = p; } /* check if OS location has been updated by platform scripts */ std::string platform = mf->GetSafeDefinition("GHS_TARGET_PLATFORM"); std::string osdir = mf->GetSafeDefinition("GHS_OS_DIR"); if (cmSystemTools::IsOff(osdir.c_str()) && platform.find("integrity") != std::string::npos) { if (!this->CMakeInstance->GetIsInTryCompile()) { /* required OS location is not found */ std::string m = "Green Hills MULTI: GHS_OS_DIR not specified; No OS found in \""; m += mf->GetSafeDefinition("GHS_OS_ROOT"); m += "\""; cmSystemTools::Message(m); } osdir = "GHS_OS_DIR-NOT-SPECIFIED"; } else if (!this->CMakeInstance->GetIsInTryCompile() && cmSystemTools::IsOff(this->OsDir) && !cmSystemTools::IsOff(osdir)) { /* OS location was updated by auto-selection */ std::string m = "Green Hills MULTI: GHS_OS_DIR not specified; found \""; m += osdir; m += "\""; cmSystemTools::Message(m); } this->OsDir = osdir; // Determine GHS_BSP_NAME std::string bspName = mf->GetSafeDefinition("GHS_BSP_NAME"); if (cmSystemTools::IsOff(bspName.c_str()) && platform.find("integrity") != std::string::npos) { bspName = "sim" + arch; /* write back the calculate name for next time */ mf->AddCacheDefinition("GHS_BSP_NAME", bspName.c_str(), "Name of GHS target platform.", cmStateEnums::STRING, true); std::string m = "Green Hills MULTI: GHS_BSP_NAME not specified; defaulting to \""; m += bspName; m += "\""; cmSystemTools::Message(m); } return true; } void cmGlobalGhsMultiGenerator::EnableLanguage( std::vector const& l, cmMakefile* mf, bool optional) { mf->AddDefinition("CMAKE_SYSTEM_NAME", "GHS-MULTI"); mf->AddDefinition("GHSMULTI", "1"); // identifier for user CMake files const char* tgtPlatform = mf->GetDefinition("GHS_TARGET_PLATFORM"); if (!tgtPlatform) { cmSystemTools::Message("Green Hills MULTI: GHS_TARGET_PLATFORM not " "specified; defaulting to \"integrity\""); tgtPlatform = "integrity"; } /* store the platform name for later use */ mf->AddCacheDefinition("GHS_TARGET_PLATFORM", tgtPlatform, "Name of GHS target platform.", cmStateEnums::STRING); /* store original OS location */ this->OsDir = mf->GetSafeDefinition("GHS_OS_DIR"); this->cmGlobalGenerator::EnableLanguage(l, mf, optional); } bool cmGlobalGhsMultiGenerator::FindMakeProgram(cmMakefile* /*mf*/) { // The GHS generator only knows how to lookup its build tool // during generation of the project files, but this // can only be done after the toolset is specified. return true; } void cmGlobalGhsMultiGenerator::GetToolset(cmMakefile* mf, std::string& tsd, const std::string& ts) { const char* ghsRoot = mf->GetDefinition("GHS_TOOLSET_ROOT"); if (!ghsRoot || ghsRoot[0] == '\0') { ghsRoot = DEFAULT_TOOLSET_ROOT; } tsd = ghsRoot; if (ts.empty()) { std::vector output; // Use latest? version if (tsd.back() != '/') { tsd += "/"; } cmSystemTools::Glob(tsd, "comp_[^;]+", output); if (output.empty()) { std::string msg = "No GHS toolsets found in GHS_TOOLSET_ROOT \"" + tsd + "\"."; cmSystemTools::Error(msg); tsd = ""; } else { tsd += output.back(); } } else { std::string tryPath; tryPath = cmSystemTools::CollapseFullPath(ts, tsd); if (!cmSystemTools::FileExists(tryPath)) { std::string msg = "GHS toolset \"" + tryPath + "\" not found."; cmSystemTools::Error(msg); tsd = ""; } else { tsd = tryPath; } } } void cmGlobalGhsMultiGenerator::WriteFileHeader(std::ostream& fout) { fout << "#!gbuild" << std::endl; fout << "#" << std::endl << "# CMAKE generated file: DO NOT EDIT!" << std::endl << "# Generated by \"" << GetActualName() << "\"" << " Generator, CMake Version " << cmVersion::GetMajorVersion() << "." << cmVersion::GetMinorVersion() << std::endl << "#" << std::endl << std::endl; } void cmGlobalGhsMultiGenerator::WriteCustomRuleBOD(std::ostream& fout) { fout << "Commands {\n" " Custom_Rule_Command {\n" " name = \"Custom Rule Command\"\n" " exec = \""; #ifdef _WIN32 fout << "cmd.exe"; #else fout << "/bin/sh"; #endif fout << "\"\n" " options = {\"SpecialOptions\"}\n" " }\n" "}\n"; fout << "\n\n"; fout << "FileTypes {\n" " CmakeRule {\n" " name = \"Custom Rule\"\n" " action = \"&Run\"\n" " extensions = {\""; #ifdef _WIN32 fout << "bat"; #else fout << "sh"; #endif fout << "\"}\n" " grepable = false\n" " command = \"Custom Rule Command\"\n" " commandLine = \"$COMMAND "; #ifdef _WIN32 fout << "/c"; #endif fout << " $INPUTFILE\"\n" " progress = \"Processing Custom Rule\"\n" " promoteToFirstPass = true\n" " outputType = \"None\"\n" " color = \"#800080\"\n" " }\n" "}\n"; } void cmGlobalGhsMultiGenerator::WriteCustomTargetBOD(std::ostream& fout) { fout << "FileTypes {\n" " CmakeTarget {\n" " name = \"Custom Target\"\n" " action = \"&Execute\"\n" " grepable = false\n" " outputType = \"None\"\n" " color = \"#800080\"\n" " }\n" "}\n"; } void cmGlobalGhsMultiGenerator::WriteTopLevelProject(std::ostream& fout, cmLocalGenerator* root) { this->WriteFileHeader(fout); this->WriteMacros(fout, root); this->WriteHighLevelDirectives(root, fout); GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fout); fout << "# Top Level Project File" << std::endl; // Specify BSP option if supplied by user const char* bspName = this->GetCMakeInstance()->GetCacheDefinition("GHS_BSP_NAME"); if (!cmSystemTools::IsOff(bspName)) { fout << " -bsp " << bspName << std::endl; } // Specify OS DIR if supplied by user // -- not all platforms require this entry in the project file if (!cmSystemTools::IsOff(this->OsDir.c_str())) { const char* osDirOption = this->GetCMakeInstance()->GetCacheDefinition("GHS_OS_DIR_OPTION"); std::replace(this->OsDir.begin(), this->OsDir.end(), '\\', '/'); fout << " "; if (cmSystemTools::IsOff(osDirOption)) { fout << ""; } else { fout << osDirOption; } fout << "\"" << this->OsDir << "\"" << std::endl; } } void cmGlobalGhsMultiGenerator::WriteSubProjects(std::ostream& fout, std::string& all_target) { fout << "CMakeFiles/" << all_target << " [Project]" << std::endl; // All known targets for (cmGeneratorTarget const* target : this->ProjectTargets) { if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || target->GetType() == cmStateEnums::MODULE_LIBRARY || target->GetType() == cmStateEnums::SHARED_LIBRARY || (target->GetType() == cmStateEnums::GLOBAL_TARGET && target->GetName() != GetInstallTargetName())) { continue; } fout << "CMakeFiles/" << target->GetName() + ".tgt" + FILE_EXTENSION << " [Project]" << std::endl; } } void cmGlobalGhsMultiGenerator::WriteProjectLine( std::ostream& fout, cmGeneratorTarget const* target, cmLocalGenerator* root, std::string& rootBinaryDir) { const char* projName = target->GetProperty("GENERATOR_FILE_NAME"); const char* projType = target->GetProperty("GENERATOR_FILE_NAME_EXT"); if (projName && projType) { cmLocalGenerator* lg = target->GetLocalGenerator(); std::string dir = lg->GetCurrentBinaryDirectory(); dir = root->MaybeConvertToRelativePath(rootBinaryDir, dir); if (dir == ".") { dir.clear(); } else { if (dir.back() != '/') { dir += "/"; } } std::string projFile = dir + projName + FILE_EXTENSION; fout << projFile; fout << " " << projType << std::endl; } else { /* Should never happen */ std::string message = "The project file for target [" + target->GetName() + "] is missing.\n"; cmSystemTools::Error(message); fout << "{comment} " << target->GetName() << " [missing project file]\n"; } } void cmGlobalGhsMultiGenerator::WriteTargets(cmLocalGenerator* root) { std::string rootBinaryDir = root->GetCurrentBinaryDirectory(); rootBinaryDir += "/CMakeFiles"; // All known targets for (cmGeneratorTarget const* target : this->ProjectTargets) { if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || target->GetType() == cmStateEnums::MODULE_LIBRARY || target->GetType() == cmStateEnums::SHARED_LIBRARY || (target->GetType() == cmStateEnums::GLOBAL_TARGET && target->GetName() != GetInstallTargetName())) { continue; } // create target build file std::string name = target->GetName() + ".tgt" + FILE_EXTENSION; std::string fname = rootBinaryDir + "/" + name; cmGeneratedFileStream fbld(fname); fbld.SetCopyIfDifferent(true); this->WriteFileHeader(fbld); GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fbld); std::vector build; if (ComputeTargetBuildOrder(target, build)) { std::string message = "The inter-target dependency graph for target [" + target->GetName() + "] had a cycle.\n"; cmSystemTools::Error(message); } else { for (auto& tgt : build) { WriteProjectLine(fbld, tgt, root, rootBinaryDir); } } fbld.Close(); } } void cmGlobalGhsMultiGenerator::WriteAllTarget( cmLocalGenerator* root, std::vector& generators, std::string& all_target) { this->ProjectTargets.clear(); // create target build file all_target = root->GetProjectName() + "." + this->GetAllTargetName() + ".tgt" + FILE_EXTENSION; std::string fname = root->GetCurrentBinaryDirectory() + "/CMakeFiles/" + all_target; cmGeneratedFileStream fbld(fname); fbld.SetCopyIfDifferent(true); this->WriteFileHeader(fbld); GhsMultiGpj::WriteGpjTag(GhsMultiGpj::PROJECT, fbld); // Collect all targets under this root generator and the transitive // closure of their dependencies. TargetDependSet projectTargets; TargetDependSet originalTargets; this->GetTargetSets(projectTargets, originalTargets, root, generators); OrderedTargetDependSet sortedProjectTargets(projectTargets, ""); std::vector defaultTargets; for (cmGeneratorTarget const* t : sortedProjectTargets) { /* save list of all targets in sorted order */ this->ProjectTargets.push_back(t); } for (cmGeneratorTarget const* t : sortedProjectTargets) { if (t->GetType() == cmStateEnums::INTERFACE_LIBRARY) { continue; } if (!cmSystemTools::IsOn(t->GetProperty("EXCLUDE_FROM_ALL"))) { defaultTargets.push_back(t); } } std::vector build; if (ComputeTargetBuildOrder(defaultTargets, build)) { std::string message = "The inter-target dependency graph for project [" + root->GetProjectName() + "] had a cycle.\n"; cmSystemTools::Error(message); } else { // determine the targets for ALL target std::string rootBinaryDir = root->GetCurrentBinaryDirectory(); rootBinaryDir += "/CMakeFiles"; for (cmGeneratorTarget const* target : build) { if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY || target->GetType() == cmStateEnums::MODULE_LIBRARY || target->GetType() == cmStateEnums::SHARED_LIBRARY) { continue; } this->WriteProjectLine(fbld, target, root, rootBinaryDir); } } fbld.Close(); } void cmGlobalGhsMultiGenerator::Generate() { std::string fname; // first do the superclass method this->cmGlobalGenerator::Generate(); // output top-level projects for (auto& it : this->ProjectMap) { this->OutputTopLevelProject(it.second[0], it.second); } // create custom rule BOD file fname = this->GetCMakeInstance()->GetHomeOutputDirectory() + "/CMakeFiles/custom_rule.bod"; cmGeneratedFileStream frule(fname); frule.SetCopyIfDifferent(true); this->WriteFileHeader(frule); this->WriteCustomRuleBOD(frule); frule.Close(); // create custom target BOD file fname = this->GetCMakeInstance()->GetHomeOutputDirectory() + "/CMakeFiles/custom_target.bod"; cmGeneratedFileStream ftarget(fname); ftarget.SetCopyIfDifferent(true); this->WriteFileHeader(ftarget); this->WriteCustomTargetBOD(ftarget); ftarget.Close(); } void cmGlobalGhsMultiGenerator::OutputTopLevelProject( cmLocalGenerator* root, std::vector& generators) { std::string fname; std::string all_target; if (generators.empty()) { return; } /* Name top-level projects as filename.top.gpj to avoid name clashes * with target projects. This avoid the issue where the project has * the same name as the executable target. */ fname = root->GetCurrentBinaryDirectory(); fname += "/"; fname += root->GetProjectName(); fname += ".top"; fname += FILE_EXTENSION; cmGeneratedFileStream top(fname); top.SetCopyIfDifferent(true); this->WriteTopLevelProject(top, root); this->WriteAllTarget(root, generators, all_target); this->WriteTargets(root); this->WriteSubProjects(top, all_target); top.Close(); } std::vector cmGlobalGhsMultiGenerator::GenerateBuildCommand( const std::string& makeProgram, const std::string& projectName, const std::string& projectDir, std::vector const& targetNames, const std::string& /*config*/, bool /*fast*/, int jobs, bool /*verbose*/, std::vector const& makeOptions) { GeneratedMakeCommand makeCommand = {}; std::string gbuild; if (const char* gbuildCached = this->CMakeInstance->GetCacheDefinition("CMAKE_MAKE_PROGRAM")) { gbuild = gbuildCached; } makeCommand.Add(this->SelectMakeProgram(makeProgram, gbuild)); if (jobs != cmake::NO_BUILD_PARALLEL_LEVEL) { makeCommand.Add("-parallel"); if (jobs != cmake::DEFAULT_BUILD_PARALLEL_LEVEL) { makeCommand.Add(std::to_string(jobs)); } } makeCommand.Add(makeOptions.begin(), makeOptions.end()); /* determine which top-project file to use */ std::string proj = projectName + ".top" + FILE_EXTENSION; std::vector files; cmSystemTools::Glob(projectDir, ".*\\.top\\.gpj", files); if (!files.empty()) { /* if multiple top-projects are found in build directory * then prefer projectName top-project. */ auto p = std::find(files.begin(), files.end(), proj); if (p == files.end()) { proj = files.at(0); } } makeCommand.Add("-top", proj); if (!targetNames.empty()) { if (std::find(targetNames.begin(), targetNames.end(), "clean") != targetNames.end()) { makeCommand.Add("-clean"); } else { for (const auto& tname : targetNames) { if (!tname.empty()) { makeCommand.Add(tname + ".tgt.gpj"); } } } } else { /* transform name to default build */; std::string all = proj; all.replace(all.end() - 7, all.end(), std::string(this->GetAllTargetName()) + ".tgt.gpj"); makeCommand.Add(all); } return { makeCommand }; } void cmGlobalGhsMultiGenerator::WriteMacros(std::ostream& fout, cmLocalGenerator* root) { fout << "macro PROJ_NAME=" << root->GetProjectName() << std::endl; char const* ghsGpjMacros = this->GetCMakeInstance()->GetCacheDefinition("GHS_GPJ_MACROS"); if (nullptr != ghsGpjMacros) { std::vector expandedList; cmSystemTools::ExpandListArgument(std::string(ghsGpjMacros), expandedList); for (std::string const& arg : expandedList) { fout << "macro " << arg << std::endl; } } } void cmGlobalGhsMultiGenerator::WriteHighLevelDirectives( cmLocalGenerator* root, std::ostream& fout) { /* set primary target */ std::string tgt; const char* t = this->GetCMakeInstance()->GetCacheDefinition("GHS_PRIMARY_TARGET"); if (t && *t != '\0') { tgt = t; this->GetCMakeInstance()->MarkCliAsUsed("GHS_PRIMARY_TARGET"); } else { const char* a = this->GetCMakeInstance()->GetCacheDefinition("CMAKE_GENERATOR_PLATFORM"); const char* p = this->GetCMakeInstance()->GetCacheDefinition("GHS_TARGET_PLATFORM"); tgt = (a ? a : ""); tgt += "_"; tgt += (p ? p : ""); tgt += ".tgt"; } fout << "primaryTarget=" << tgt << std::endl; fout << "customization=" << root->GetBinaryDirectory() << "/CMakeFiles/custom_rule.bod" << std::endl; fout << "customization=" << root->GetBinaryDirectory() << "/CMakeFiles/custom_target.bod" << std::endl; char const* const customization = this->GetCMakeInstance()->GetCacheDefinition("GHS_CUSTOMIZATION"); if (nullptr != customization && strlen(customization) > 0) { fout << "customization=" << this->TrimQuotes(customization) << std::endl; this->GetCMakeInstance()->MarkCliAsUsed("GHS_CUSTOMIZATION"); } } std::string cmGlobalGhsMultiGenerator::TrimQuotes(std::string const& str) { std::string result; result.reserve(str.size()); for (const char* ch = str.c_str(); *ch != '\0'; ++ch) { if (*ch != '"') { result += *ch; } } return result; } bool cmGlobalGhsMultiGenerator::TargetCompare::operator()( cmGeneratorTarget const* l, cmGeneratorTarget const* r) const { // Make sure a given named target is ordered first, // e.g. to set ALL_BUILD as the default active project. // When the empty string is named this is a no-op. if (r->GetName() == this->First) { return false; } if (l->GetName() == this->First) { return true; } return l->GetName() < r->GetName(); } cmGlobalGhsMultiGenerator::OrderedTargetDependSet::OrderedTargetDependSet( TargetDependSet const& targets, std::string const& first) : derived(TargetCompare(first)) { this->insert(targets.begin(), targets.end()); } bool cmGlobalGhsMultiGenerator::ComputeTargetBuildOrder( cmGeneratorTarget const* tgt, std::vector& build) { std::vector t{ tgt }; return ComputeTargetBuildOrder(t, build); } bool cmGlobalGhsMultiGenerator::ComputeTargetBuildOrder( std::vector& tgt, std::vector& build) { std::set temp; std::set perm; for (auto ti : tgt) { bool r = VisitTarget(temp, perm, build, ti); if (r) { return r; } } return false; } bool cmGlobalGhsMultiGenerator::VisitTarget( std::set& temp, std::set& perm, std::vector& order, cmGeneratorTarget const* ti) { /* check if permanent mark is set*/ if (perm.find(ti) == perm.end()) { /* set temporary mark; check if revisit*/ if (temp.insert(ti).second) { /* sort targets lexicographically to ensure that nodes are always visited * in the same order */ OrderedTargetDependSet sortedTargets(this->GetTargetDirectDepends(ti), ""); for (auto& di : sortedTargets) { if (this->VisitTarget(temp, perm, order, di)) { return true; } } /* mark as complete; insert into beginning of list*/ perm.insert(ti); order.push_back(ti); return false; } /* revisiting item - not a DAG */ return true; } /* already complete */ return false; }