//-------------------------------------------------------------------------------------------------- static void GenerateApiBindConfig ( std::ofstream& cfgStream, ///< Stream to send the configuration to. legato::App& app, ///< The application being built. const legato::ExeToExeApiBind& binding ///< Binding to internal exe.component.interface. ) //-------------------------------------------------------------------------------------------------- { const std::string& clientInterfaceId = binding.ClientInterface(); std::string clientServiceName; // If the binding is a wildcard binding (applies to everything with a given service name), if (clientInterfaceId.compare(0, 2, "*.") == 0) { // Strip off the "*." wildcard specifier to get the service name. clientServiceName = clientInterfaceId.substr(2); } // Otherwise, look-up the client interface and take the service name from there. else { const auto& interface = app.FindClientInterface(clientInterfaceId); clientServiceName = interface.ExternalName(); } GenerateSingleApiBindingToApp( cfgStream, clientServiceName, app.Name(), binding.ServerInterface() ); }
//-------------------------------------------------------------------------------------------------- static void GenerateIpcBindingConfig ( std::ofstream& cfgStream, legato::App& app, const legato::BuildParams_t& buildParams ///< Build parameters, such as the "is verbose" flag. ) //-------------------------------------------------------------------------------------------------- { // Create nodes under "bindings", where each binding has its own node, named with the client // interface service name. cfgStream << " \"bindings\"" << std::endl; cfgStream << " {" << std::endl; // If cross-building for an embedded target (not "localhost"), if (buildParams.Target() != "localhost") { // Add a bind to the Log Client interface of the Log Control Daemon (which runs as root). GenerateSingleApiBindingToUser(cfgStream, "LogClient", "root", "LogClient"); } // Add all the binds that were specified in the .adef file or .sdef file for this app. for (const auto& mapEntry : app.ExternalApiBinds()) { GenerateApiBindConfig(cfgStream, app, mapEntry.second); } for (const auto& mapEntry : app.InternalApiBinds()) { GenerateApiBindConfig(cfgStream, app, mapEntry.second); } cfgStream << " }" << std::endl << std::endl; }
//-------------------------------------------------------------------------------------------------- void CheckForLimitsConflicts ( const legato::App& app ) //-------------------------------------------------------------------------------------------------- { size_t maxMemoryBytes = app.MaxMemoryBytes().Get(); size_t maxFileSystemBytes = app.MaxFileSystemBytes().Get(); for (const auto& procEnv : app.ProcEnvironments()) { size_t maxLockedMemoryBytes = procEnv.MaxLockedMemoryBytes().Get(); if (maxLockedMemoryBytes > maxMemoryBytes) { std::stringstream warning; warning << "maxLockedMemoryBytes (" << maxLockedMemoryBytes << ") will be limited by the maxMemoryBytes limit (" << maxMemoryBytes << ")."; PrintWarning(app, warning.str()); } size_t maxFileBytes = procEnv.MaxFileBytes().Get(); size_t maxCoreDumpFileBytes = procEnv.MaxCoreDumpFileBytes().Get(); if (maxCoreDumpFileBytes > maxFileBytes) { std::stringstream warning; warning << "maxCoreDumpFileBytes (" << maxCoreDumpFileBytes << ") will be limited by the maxFileBytes limit (" << maxFileBytes << ")."; PrintWarning(app, warning.str()); } if (maxCoreDumpFileBytes > maxFileSystemBytes) { std::stringstream warning; warning << "maxCoreDumpFileBytes (" << maxCoreDumpFileBytes << ") will be limited by the maxFileSystemBytes limit (" << maxFileSystemBytes << ") if the core file is inside the sandbox temporary" " file system."; PrintWarning(app, warning.str()); } if (maxFileBytes > maxFileSystemBytes) { std::stringstream warning; warning << "maxFileBytes (" << maxFileBytes << ") will be limited by the maxFileSystemBytes limit (" << maxFileSystemBytes << ") if the file is inside the sandbox temporary" " file system."; PrintWarning(app, warning.str()); } } }
//-------------------------------------------------------------------------------------------------- static void GenerateAppVersionConfig ( std::ofstream& cfgStream, const legato::App& app ) //-------------------------------------------------------------------------------------------------- { if (app.Version() != "") { cfgStream << " \"version\" \"" << app.Version() << "\"" << std::endl; } }
//-------------------------------------------------------------------------------------------------- static void GenerateConfigTreeAclConfig ( std::ofstream& cfgStream, legato::App& app ) //-------------------------------------------------------------------------------------------------- { const char readable[] = "read"; const char writeable[] = "write"; const char* accessModePtr; // Create nodes under "configLimits/acl", where each tree has its own node, named with the // tree name, that contains either the word "read" or the word "write". cfgStream << " \"configLimits\"" << std::endl; cfgStream << " {" << std::endl; cfgStream << " \"acl\"" << std::endl; cfgStream << " {" << std::endl; // Add all the trees that were specified in the .adef file. for (const auto& mapEntry : app.ConfigTrees()) { if (mapEntry.second & legato::PERMISSION_WRITEABLE) { accessModePtr = writeable; } else { accessModePtr = readable; } cfgStream << " \"" << mapEntry.first << "\" \"" << accessModePtr << "\"" << std::endl; } cfgStream << " }" << std::endl << std::endl; cfgStream << " }" << std::endl << std::endl; }
//-------------------------------------------------------------------------------------------------- static void BuildExecutables ( legato::App& app, const legato::BuildParams_t& buildParams ) //-------------------------------------------------------------------------------------------------- { // Create an Executable Builder object. ExecutableBuilder_t exeBuilder(buildParams); // For each executable, auto& exeList = app.Executables(); for (auto i = exeList.begin(); i != exeList.end(); i++) { legato::Executable& exe = i->second; // Put the intermediate build output files under a directory named after the executable. std::string objOutputDir = legato::CombinePath(buildParams.ObjOutputDir(), exe.CName()); // Auto-generate the source code file containing main() and add it to the default component. exeBuilder.GenerateMain(exe, objOutputDir); // Build the executable. exeBuilder.Build(exe, objOutputDir); } }
//-------------------------------------------------------------------------------------------------- static void GenerateGroupsConfig ( std::ofstream& cfgStream, const legato::App& app ) //-------------------------------------------------------------------------------------------------- { const auto& groupsList = app.Groups(); // If the groups list is empty, nothing needs to be done. if (groupsList.empty()) { return; } // Group names are specified by inserting empty leaf nodes under the "groups" branch // of the application's configuration tree. cfgStream << " \"groups\"" << std::endl; cfgStream << " {" << std::endl; for (auto groupName : groupsList) { cfgStream << " \"" << groupName << "\" \"\"" << std::endl; } cfgStream << " }" << std::endl << std::endl; }
//-------------------------------------------------------------------------------------------------- static void GenerateApiBindConfig ( std::ofstream& cfgStream, ///< Stream to send the configuration to. legato::App& app, ///< The application being built. const legato::ExeToUserApiBind& binding ///< Binding to external user name and service name. ) //-------------------------------------------------------------------------------------------------- { const std::string& clientInterfaceId = binding.ClientInterface(); std::string clientServiceName; // If the binding is a wildcard binding (applies to everything with a given service name), if (clientInterfaceId.compare(0, 2, "*.") == 0) { // Strip off the "*." wildcard specifier to get the service name. clientServiceName = clientInterfaceId.substr(2); } // Otherwise, look-up the client interface and take the service name from there. else { const auto& interface = app.FindClientInterface(clientInterfaceId); clientServiceName = interface.ExternalName(); } // If there is no server user name, if (binding.ServerUserName().empty()) { // Make sure there's a server app name. if (binding.ServerAppName().empty()) { throw legato::Exception("INTERNAL ERROR: Neither user name nor app name provided" " for server in binding of '" + binding.ClientInterface() + "'."); } GenerateSingleApiBindingToApp( cfgStream, clientServiceName, binding.ServerAppName(), binding.ServerInterfaceName() ); } // If there is a server user name, else { // Make sure there isn't also a server app name. if (!binding.ServerAppName().empty()) { throw legato::Exception("INTERNAL ERROR: Both user name and app name provided" " for server in binding of '" + binding.ClientInterface() + "'."); } GenerateSingleApiBindingToUser( cfgStream, clientServiceName, binding.ServerUserName(), binding.ServerInterfaceName() ); } }
//-------------------------------------------------------------------------------------------------- static void PrintWarning ( const legato::App& app, const std::string& warning ) //-------------------------------------------------------------------------------------------------- { std::cerr << "** Warning: application '" << app.Name() << "': " << warning << std::endl; }
//-------------------------------------------------------------------------------------------------- static void GenerateProcessEnvVarsConfig ( std::ofstream& cfgStream, const legato::App& app, const legato::ProcessEnvironment& procEnv ) //-------------------------------------------------------------------------------------------------- { // The PATH environment variable has to be handled specially. If no PATH variable is // specified in the .adef, we must provide one. bool pathSpecified = false; // Any environment variables are declared under a node called "envVars". // Each env var has its own node, with the name of the node being the name of // the environment variable. cfgStream << " \"envVars\"" << std::endl; cfgStream << " {" << std::endl; for (const auto& pair : procEnv.EnvVarList()) { if (pair.first == "PATH") { pathSpecified = true; } cfgStream << " \"" << pair.first << "\" \"" << pair.second << "\"" << std::endl; } if (!pathSpecified) { // The default path depends on whether the application is sandboxed or not. std::string path = "/usr/local/bin:/usr/bin:/bin"; if (app.IsSandboxed() == false) { path = "/opt/legato/apps/" + app.Name() + "/bin:" + path; } cfgStream << " \"PATH\" \"" << path << "\"" << std::endl; } cfgStream << " }" << std::endl; }
//-------------------------------------------------------------------------------------------------- static legato::Component& GetComponent ( const std::string& name ///< Name of the component. ) //-------------------------------------------------------------------------------------------------- { auto& map = App.ComponentMap(); // Check if we have already parsed a component with the same name. // If so, it's an error! if (map.find(name) != map.end()) { throw legato::Exception("Multiple components with the same name (" + name + ")."); } // Tell the App to create a new Component object for us and then get the parser to populate it. legato::Component& component = App.CreateComponent(name); legato::parser::ParseComponent(component, BuildParams); return component; }
//-------------------------------------------------------------------------------------------------- static void GenerateSystemConfig ( const std::string& stagingDirPath, ///< Path to the root of the app's staging directory. legato::App& app, ///< The app to generate the configuration for. const legato::BuildParams_t& buildParams ///< Build parameters, such as the "is verbose" flag. ) //-------------------------------------------------------------------------------------------------- { // TODO: Rename this file to something that makes more sense (like "system.cfg", because it // gets installed in the "system" config tree). std::string path = stagingDirPath + "/root.cfg"; if (buildParams.IsVerbose()) { std::cout << "Generating system configuration data for app '" << app.Name() << "' in file '" << path << "'." << std::endl; } std::ofstream cfgStream(path, std::ofstream::trunc); cfgStream << "{" << std::endl; GenerateAppVersionConfig(cfgStream, app); GenerateAppLimitsConfig(cfgStream, app); GenerateGroupsConfig(cfgStream, app); GenerateFileMappingConfig(cfgStream, app); GenerateProcessConfig(cfgStream, app); GenerateIpcBindingConfig(cfgStream, app, buildParams); GenerateConfigTreeAclConfig(cfgStream, app); cfgStream << "}" << std::endl; }
//-------------------------------------------------------------------------------------------------- void ApplicationBuilder_t::Build ( legato::App& app, std::string outputDirPath ///< Directory into which the generated app bundle should be put. ) //-------------------------------------------------------------------------------------------------- { CheckForLimitsConflicts(app); // Construct the working directory structure, which consists of an "work" directory and // a "staging" directory. Inside the "staging" directory, there is "lib", "bin", and any // other directories required to hold files bundled by the application or one of its components. // The "work" directory is for intermediate build output, like generated .c files and .o files. // The "staging" directory will get tar-compressed to become the actual application file. if (m_Params.IsVerbose()) { std::cout << "Creating working directories under '" << m_Params.ObjOutputDir() << "'." << std::endl; } legato::BuildParams_t buildParams(m_Params); const std::string& stagingDirPath = m_Params.StagingDir(); buildParams.LibOutputDir(stagingDirPath + "/lib"); buildParams.ExeOutputDir(stagingDirPath + "/bin"); buildParams.ObjOutputDir(m_Params.ObjOutputDir() + "/work"); // Clean the staging area. legato::CleanDir(stagingDirPath); // Create directories. legato::MakeDir(buildParams.ObjOutputDir()); legato::MakeDir(buildParams.LibOutputDir()); legato::MakeDir(buildParams.ExeOutputDir()); // Build all the components in the application, each with its own working directory // to avoid file name conflicts between .o files in different components, and copy all // generated and bundled files into the application staging area. // NOTE: Components have to be built before any other components that depend on them. // They also need to be bundled into the app in the same order, so that higher-layer // components can override files bundled by lower-layer components. ComponentBuilder_t componentBuilder(buildParams); auto& map = app.ComponentMap(); for (auto& mapEntry : map) { auto& componentPtr = mapEntry.second; BuildAndBundleComponent(*componentPtr, componentBuilder, buildParams.ObjOutputDir()); } // Build all the executables and their IPC libs. BuildExecutables(app, buildParams); // Copy in any bundled files and directories from the "bundles:" section of the .adef. // Note: do the directories first, in case the files list adds files to those directories. for (auto& fileMapping : app.BundledDirs()) { mk::CopyToStaging( fileMapping.m_SourcePath, stagingDirPath, fileMapping.m_DestPath, m_Params.IsVerbose() ); } for (auto& fileMapping : app.BundledFiles()) { mk::CopyToStaging( fileMapping.m_SourcePath, stagingDirPath, fileMapping.m_DestPath, m_Params.IsVerbose() ); } // Generate the app-specific configuration data that tells the framework what limits to place // on the app when it is run, etc. GenerateSystemConfig(stagingDirPath, app, m_Params); // TODO: Generate the application's configuration tree (containing all its pool sizes, // and anything else listed under the "config:" section of the .adef.) // TODO: Copy in the metadata (.adef and Component.cdef) files so they can be retrieved // by Developer Studio. // Zip it all up. std::string outputPath = legato::CombinePath( outputDirPath, app.Name() + "." + buildParams.Target() ); if (!legato::IsAbsolutePath(outputPath)) { outputPath = legato::GetWorkingDir() + "/" + outputPath; } std::string tarCommandLine = "tar cjf \"" + outputPath + "\" -C \"" + stagingDirPath + "\" ."; if (m_Params.IsVerbose()) { std::cout << "Packaging application into '" << outputPath << "'." << std::endl; std::cout << std::endl << "$ " << tarCommandLine << std::endl << std::endl; } mk::ExecuteCommandLine(tarCommandLine); }
//-------------------------------------------------------------------------------------------------- static void GenerateFileMappingConfig ( std::ofstream& cfgStream, const legato::App& app ) //-------------------------------------------------------------------------------------------------- { size_t index = 0; // Create nodes under "files", where each node is named with an index, starting a 0, // and contains a "src" node and a "dest" node. cfgStream << " \"files\"" << std::endl; cfgStream << " {" << std::endl; // Import the files specified in the .adef file. for (const auto& mapping : app.RequiredFiles()) { GenerateSingleFileMappingConfig(cfgStream, index++, mapping); } // Bundled files also need to be imported into the application sandbox. for (const auto& mapping : app.BundledFiles()) { GenerateBundledObjectMappingConfig(cfgStream, index++, mapping); } // Bundled directories also need to be imported into the application sandbox. for (const auto& mapping : app.BundledDirs()) { GenerateBundledObjectMappingConfig(cfgStream, index++, mapping); } // Map into the sandbox all the files for all the components. for (const auto& pair : app.ComponentMap()) { const auto& componentPtr = pair.second; // External files... for (const auto& mapping : componentPtr->RequiredFiles()) { GenerateSingleFileMappingConfig(cfgStream, index++, mapping); } // External directories... for (const auto& mapping : componentPtr->RequiredDirs()) { GenerateSingleFileMappingConfig(cfgStream, index++, mapping); } // NOTE: Bundled files and directories also need to be mapped into the application sandbox // because the application's on-target install directory is outside its runtime sandbox. // Bundled files... for (const auto& mapping : componentPtr->BundledFiles()) { GenerateBundledObjectMappingConfig(cfgStream, index++, mapping); } // Bundled directories... for (const auto& mapping : componentPtr->BundledDirs()) { GenerateBundledObjectMappingConfig(cfgStream, index++, mapping); } } cfgStream << " }" << std::endl << std::endl; }
//-------------------------------------------------------------------------------------------------- static void GenerateProcessConfig ( std::ofstream& cfgStream, const legato::App& app ) //-------------------------------------------------------------------------------------------------- { // Create nodes under "procs", where each process has its own node, named after the process. cfgStream << " \"procs\"" << std::endl; cfgStream << " {" << std::endl; for (const auto& procEnv : app.ProcEnvironments()) { for (const auto& process : procEnv.ProcessList()) { cfgStream << " \"" << process.Name() << "\"" << std::endl; cfgStream << " {" << std::endl; // The command-line argument list is an indexed list of arguments under a node called // "args", where the first argument (0) must be the executable to run. cfgStream << " \"args\"" << std::endl; cfgStream << " {" << std::endl; cfgStream << " \"0\" \"" << process.ExePath() << "\"" << std::endl; int argIndex = 1; for (const auto& arg : process.CommandLineArgs()) { cfgStream << " \"" << argIndex << "\" \"" << arg << "\"" << std::endl; argIndex++; } cfgStream << " }" << std::endl; GenerateProcessEnvVarsConfig(cfgStream, app, procEnv); // Generate the priority, fault action, and limits configuration. if (procEnv.FaultAction().IsSet()) { cfgStream << " \"faultAction\" \"" << procEnv.FaultAction().Get() << "\"" << std::endl; } if (procEnv.StartPriority().IsSet()) { cfgStream << " \"priority\" \"" << procEnv.StartPriority().Get() << "\"" << std::endl; } cfgStream << " \"maxCoreDumpFileBytes\" [" << procEnv.MaxCoreDumpFileBytes().Get() << "]" << std::endl; cfgStream << " \"maxFileBytes\" [" << procEnv.MaxFileBytes().Get() << "]" << std::endl; cfgStream << " \"maxLockedMemoryBytes\" [" << procEnv.MaxLockedMemoryBytes().Get() << "]" << std::endl; cfgStream << " \"maxFileDescriptors\" [" << procEnv.MaxFileDescriptors().Get() << "]" << std::endl; if (procEnv.WatchdogTimeout().IsSet()) { cfgStream << " \"watchdogTimeout\" [" << procEnv.WatchdogTimeout().Get() << "]" << std::endl; } if (procEnv.WatchdogAction().IsSet()) { cfgStream << " \"watchdogAction\" \"" << procEnv.WatchdogAction().Get() << "\"" << std::endl; } cfgStream << " }" << std::endl; } } cfgStream << " }" << std::endl << std::endl; }
//-------------------------------------------------------------------------------------------------- static void GenerateAppLimitsConfig ( std::ofstream& cfgStream, const legato::App& app ) //-------------------------------------------------------------------------------------------------- { if (app.IsSandboxed() == false) { cfgStream << " \"sandboxed\" !f" << std::endl; } if (app.StartMode() == legato::App::MANUAL) { cfgStream << " \"startManual\" !t" << std::endl; } cfgStream << " \"maxThreads\" [" << app.MaxThreads().Get() << "]" << std::endl; cfgStream << " \"maxMQueueBytes\" [" << app.MaxMQueueBytes().Get() << "]" << std::endl; cfgStream << " \"maxQueuedSignals\" [" << app.MaxQueuedSignals().Get() << "]" << std::endl; cfgStream << " \"maxMemoryBytes\" [" << app.MaxMemoryBytes().Get() << "]" << std::endl; cfgStream << " \"cpuShare\" [" << app.CpuShare().Get() << "]" << std::endl; if (app.MaxFileSystemBytes().IsSet()) { // This is not supported for unsandboxed apps. if (app.IsSandboxed() == false) { std::cerr << "**** Warning: File system size limit being ignored for unsandboxed" << " application '" << app.Name() << "'." << std::endl; } else { cfgStream << " \"maxFileSystemBytes\" [" << app.MaxFileSystemBytes().Get() << "]" << std::endl; } } if (app.WatchdogTimeout().IsSet()) { cfgStream << " \"watchdogTimeout\" [" << app.WatchdogTimeout().Get() << "]" << std::endl; } if (app.WatchdogAction().IsSet()) { cfgStream << " \"watchdogAction\" \"" << app.WatchdogAction().Get() << "\"" << std::endl; } }
//-------------------------------------------------------------------------------------------------- static legato::Executable& ConstructObjectModel ( void ) //-------------------------------------------------------------------------------------------------- { bool errorFound = false; // Create a new Executable object. legato::Executable& exe = App.CreateExecutable(ExePath); if (BuildParams.IsVerbose()) { std::cout << "Making executable '" << exe.OutputPath() << "'" << std::endl << "\t(using exe name '" << exe.CName() << "')" << std::endl << "\tcontaining:" << std::endl; } // For each item of content, we have to figure out what type of content it is and // handle it accordingly. for (auto contentName: ContentNames) { const char* contentType; if (legato::IsCSource(contentName)) { contentType = "C source code"; // Add the source code file to the default component. exe.AddCSourceFile(legato::FindFile(contentName, BuildParams.ComponentDirs())); } else if (legato::IsLibrary(contentName)) { contentType = "library"; // Add the library file to the list of libraries to be linked with the default // component. exe.AddLibrary(contentName); } else if (legato::IsComponent(contentName, BuildParams.ComponentDirs())) { contentType = "component"; // Find the component and add it to the executable's list of component instances. // NOTE: For now, we only support one instance of a component per executable, and it is // identified by the file system path to that component (relative to a directory // somewhere in the component search path). legato::Component& component = GetComponent(contentName); exe.AddComponentInstance(legato::ComponentInstance(component)); } else { contentType = "** unknown **"; std::cerr << "** ERROR: Couldn't identify content item '" << contentName << "'." << std::endl; errorFound = true; } if (BuildParams.IsVerbose()) { std::cout << "\t\t'" << contentName << "' (" << contentType << ")" << std::endl; } } if (errorFound) { throw std::runtime_error("Unable to identify requested content."); } return exe; }