| /***************************************************************************** |
| * Copyright (c) 2019 CEA LIST. |
| * |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * Ansgar Radermacher ansgar.radermacher@cea.fr |
| * |
| *****************************************************************************/ |
| |
| package org.eclipse.papyrus.robotics.ros2.codegen.utils |
| |
| import java.io.File |
| import java.util.ArrayList |
| import java.util.List |
| import org.eclipse.cdt.core.CCProjectNature |
| import org.eclipse.cdt.core.CCorePlugin |
| import org.eclipse.cdt.core.dom.IPDOMManager |
| import org.eclipse.cdt.core.envvar.EnvironmentVariable |
| import org.eclipse.cdt.core.model.CoreModel |
| import org.eclipse.cdt.core.settings.model.CIncludePathEntry |
| import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry |
| import org.eclipse.cdt.core.settings.model.ICSettingEntry |
| import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager |
| import org.eclipse.cdt.newmake.core.IMakeBuilderInfo |
| import org.eclipse.core.resources.IProject |
| import org.eclipse.core.runtime.CoreException |
| import org.eclipse.core.runtime.NullProgressMonitor |
| import org.eclipse.emf.common.util.UniqueEList |
| import org.eclipse.papyrus.designer.languages.common.extensionpoints.LanguageProjectSupport |
| import org.eclipse.papyrus.designer.languages.cpp.codegen.utils.CppClassUtils |
| import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.External |
| import org.eclipse.papyrus.designer.transformation.base.utils.ProjectManagement |
| import org.eclipse.papyrus.designer.transformation.base.utils.TransformationException |
| import org.eclipse.papyrus.designer.transformation.core.transformations.ExecuteTransformationChain |
| import org.eclipse.papyrus.designer.transformation.core.transformations.TransformationContext |
| import org.eclipse.papyrus.robotics.ros2.base.EnvironmentUtils |
| import org.eclipse.papyrus.robotics.ros2.base.Ros2Constants |
| import org.eclipse.papyrus.robotics.ros2.codegen.handlers.RewriteCDTHandler |
| import org.eclipse.papyrus.robotics.ros2.preferences.Ros2PreferenceUtils |
| import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil |
| import org.eclipse.uml2.uml.Class |
| import org.eclipse.uml2.uml.Package |
| import org.eclipse.papyrus.robotics.ros2.codegen.component.RoboticsCppCreator |
| |
| /** |
| * get or create a CDT project with a given name |
| */ |
| class ProjectTools { |
| /** |
| * get a CDT project with a given name. If the CDT project does not |
| * exist, it will be created. If the project exists, but is not a C++ |
| * CDT project, it will be converted to CDT. |
| * The project will be configured for a colcon build. |
| */ |
| static def IProject getProject(String projectName) { |
| var genProject = ProjectManagement.getNamedProject(projectName) |
| if ((genProject !== null && genProject.exists() && |
| genProject.getNature(CCProjectNature.CC_NATURE_ID) === null) || |
| RewriteCDTHandler.rewriteProject(projectName)) { |
| // not a CDT project (or forced rewrite), force conversion into C++ |
| genProject = null |
| } |
| // genProject = ProjectManagement.getNamedProject(projectName) |
| if ((genProject === null) || !genProject.exists()) { |
| val projectSupport = LanguageProjectSupport.getProjectSupport("C++") |
| val currentIndexer = CCorePlugin.getIndexManager().getDefaultIndexerId(); |
| CCorePlugin.indexManager.defaultIndexerId = IPDOMManager.ID_NO_INDEXER |
| genProject = projectSupport.createProject(projectName, TransformationContext.current.modelRoot) |
| CCorePlugin.indexManager.defaultIndexerId = currentIndexer; |
| |
| if (genProject !== null && !genProject.exists()) { |
| throw new RuntimeException(String.format( |
| "project does not exist" |
| )) |
| } |
| if (genProject !== null) { |
| configureCDT(genProject, projectName.toLowerCase) |
| } else { |
| throw new TransformationException(ExecuteTransformationChain.USER_CANCEL) |
| } |
| } |
| return genProject |
| } |
| |
| /** |
| * Basic configuration of a CDT project for colcon |
| */ |
| static def configureCDT(IProject project, String pkgName) { |
| |
| val mngr = CoreModel.getDefault().getProjectDescriptionManager() |
| var cdesc = mngr.getProjectDescription(project, true) |
| |
| try { |
| val env = EnvironmentUtils.getenv() |
| val amentPrefix = new EnvironmentVariable(Ros2Constants.AMENT_PREFIX_PATH, |
| env.get(Ros2Constants.AMENT_PREFIX_PATH)) |
| val pythonPath = new EnvironmentVariable(Ros2Constants.PYTHON_PATH, env.get(Ros2Constants.PYTHON_PATH)) |
| |
| // loop over all configurations |
| for (configDescr : cdesc.getConfigurations()) { |
| |
| val contribEnv = CCorePlugin.getDefault().getBuildEnvironmentManager().getContributedEnvironment(); |
| contribEnv.addVariable(amentPrefix, configDescr); |
| contribEnv.addVariable(pythonPath, configDescr); |
| |
| val main = ManagedBuildManager.getConfigurationForDescription(configDescr) |
| |
| main.setBuildCommand("colcon") |
| val builder = main.getBuilder() |
| builder.setUseDefaultBuildCmd(false) |
| builder.setBuildPath("${workspace_loc:}") |
| var buildCmd = String.format( |
| "build %s %s %s", |
| Ros2PreferenceUtils.colconOptions, |
| Ros2PreferenceUtils.colconPackageOptions, |
| pkgName |
| ) |
| if (configDescr.name == "Debug") { |
| // pass debug arguments to cmake |
| buildCmd += " --cmake-args -DCMAKE_BUILD_TYPE=Debug" |
| } |
| builder.setBuildAttribute(IMakeBuilderInfo.BUILD_TARGET_INCREMENTAL, buildCmd) |
| // managedBuild must be set to false, otherwise build path is not taken into account |
| // but once set to false, we can't change include directories via the UI - but |
| // they are still taken into account |
| main.setManagedBuildOn(false) |
| } |
| mngr.setProjectDescription(project, cdesc, true, null) |
| |
| // CoreModel.getDefault.setProjectDescription(project, cdesc); |
| ManagedBuildManager.saveBuildInfo(project, true) |
| TransformationContext.monitor.subTask("waiting for CDT to finish project setup") |
| waitForCDT |
| } catch (CoreException ce) { |
| throw new RuntimeException(ce.getMessage()) |
| } |
| } |
| |
| /** |
| * Wait up to 10 seconds for the CDT indexer to finish |
| */ |
| static def waitForCDT() { |
| if (!CCorePlugin.getIndexManager().indexerIdle) { |
| TransformationContext.monitor.subTask("waiting for CDT indexer") |
| var int i = 0; |
| do { |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e1) { |
| } |
| i++; |
| } |
| while (!CCorePlugin.getIndexManager().indexerIdle && i < 100); |
| } |
| } |
| |
| /** |
| * Configure the include paths of a CDT project |
| */ |
| static def configureIncludes(IProject project, List<String> depPkgList) { |
| |
| val mngr = CoreModel.getDefault().getProjectDescriptionManager() |
| var cdesc = mngr.getProjectDescription(project, true) |
| |
| if (cdesc !== null) { |
| // cdesc may be null if project setup is incomplete during test |
| try { |
| // loop over all configurations |
| for (configDescr : cdesc.getConfigurations()) { |
| |
| val folderDescription = configDescr.getRootFolderDescription() |
| val languageSettings = folderDescription.getLanguageSettings() |
| |
| val icIncludePaths = new ArrayList<ICLanguageSettingEntry> |
| icIncludePaths.add(new CIncludePathEntry("${workspace_loc:/${ProjName}/src-gen}", 0)) |
| // put path into settings to enable working with unset environment variables |
| val amentPrefixPath = EnvironmentUtils.get(Ros2Constants.AMENT_PREFIX_PATH) |
| if (amentPrefixPath !== null) { |
| for (amentEntry : amentPrefixPath.split(":")) { |
| icIncludePaths.add(new CIncludePathEntry(String.format("%s/include", amentEntry), 0)) |
| } |
| } |
| for (depPkg : depPkgList) { |
| icIncludePaths.add(new CIncludePathEntry(getIncludePath(depPkg), 0)) |
| } |
| |
| // now set include path and preprocessor code |
| for (lang : languageSettings) { |
| for (String ext : lang.getSourceExtensions()) { |
| if (ext.equals("cpp")) { |
| lang.setSettingEntries(ICSettingEntry.INCLUDE_PATH, icIncludePaths) |
| } |
| } |
| } |
| } |
| mngr.setProjectDescription(project, cdesc, true, null) |
| ManagedBuildManager.saveBuildInfo(project, true) |
| } catch (CoreException ce) { |
| throw new RuntimeException(ce.getMessage()) |
| } |
| } |
| } |
| |
| /** |
| * |
| */ |
| static def genCode(RoboticsCppCreator codeGen, Class component) { |
| val packagesToGenerate = new UniqueEList<Package>(); |
| packagesToGenerate.add(component.nearestPackage); |
| for (incCl : CppClassUtils.includedClassifiers(component)) { |
| if (!StereotypeUtil.isApplied(incCl, External)) { |
| packagesToGenerate.add(incCl.nearestPackage); |
| } |
| } |
| for (pkg : packagesToGenerate) { |
| codeGen.createPackageableElement(pkg, new NullProgressMonitor) |
| } |
| } |
| |
| /** |
| * defaults to workspace path |
| */ |
| static def getIncludePath(String pkgName) { |
| val prefixPath = Ros2PreferenceUtils.standardMessagePath |
| for (pathEntry : prefixPath.split(":")) { |
| val testPath = '''«pathEntry»/include/«pkgName»''' |
| if (new File(testPath).exists()) { |
| return testPath |
| } |
| } |
| return '''${WorkspaceDirPath}/install/«pkgName»/include''' |
| } |
| } |