1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-29 19:45:01 +02:00

Bug 575145 - Importing CMake/Meson Projects

Add API to o.e.tools.templates.ui that hooks into the smart import
feature and allows easy implementation of configurators for various
CDT project types.

Project types that have a IGenerator may use this API to offer smart
import functionality by registering their implementation using the
org.eclipse.ui.ide.projectConfigurator extension point.

This change includes project import implementations for Meson and
CMake project types.

For these project types users can use the normal project import
workflow for their existing non-Eclipse CMake projects instead of
using the New Project Wizard. As an additional benefit, users
can now also import more than one project at a time, even nested
projects.

Change also includes SWTBot tests to exercise the feature.

Signed-off-by: Mat Booth <mat.booth@gmail.com>
Change-Id: I96589e86bee561aa200a4a4487549305765d6409
This commit is contained in:
Mat Booth 2021-07-31 15:39:24 +01:00
parent 172d3a25dc
commit a957e8121d
32 changed files with 586 additions and 69 deletions

1
.gitattributes vendored
View file

@ -17,6 +17,7 @@ CONTRIBUTING text
*.cc text
*.cpp text
*.h text
*.in text
*.s text
*.S text
*.elf binary

View file

@ -2,13 +2,13 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name.0
Bundle-SymbolicName: org.eclipse.cdt.meson.core;singleton:=true
Bundle-Version: 1.1.200.qualifier
Bundle-Version: 1.1.300.qualifier
Bundle-Activator: org.eclipse.cdt.meson.core.Activator
Bundle-Vendor: %provider
Require-Bundle: org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.cdt.core;bundle-version="6.4.0",
org.eclipse.tools.templates.freemarker
org.eclipse.tools.templates.freemarker;bundle-version="1.2.200"
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Bundle-Localization: plugin

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name.0
Bundle-SymbolicName: org.eclipse.cdt.meson.ui;singleton:=true
Bundle-Version: 1.1.200.qualifier
Bundle-Version: 1.1.300.qualifier
Bundle-Vendor: %vendorName
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-Activator: org.eclipse.cdt.meson.ui.Activator
@ -11,10 +11,9 @@ Bundle-Localization: plugin
Require-Bundle: org.eclipse.core.runtime;bundle-version="3.13.0",
org.eclipse.ui;bundle-version="3.109.0",
org.eclipse.cdt.meson.core;bundle-version="1.0.0",
org.eclipse.tools.templates.core;bundle-version="1.1.0",
org.eclipse.tools.templates.ui;bundle-version="1.1.1",
org.eclipse.tools.templates.ui;bundle-version="1.3.0",
org.eclipse.tools.templates.freemarker;bundle-version="1.2.200",
org.eclipse.ui.ide;bundle-version="3.13.1",
org.eclipse.tools.templates.freemarker;bundle-version="1.0.0",
org.eclipse.cdt.core;bundle-version="6.4.0",
org.eclipse.core.resources;bundle-version="3.12.0",
org.eclipse.debug.core;bundle-version="3.11.0",

View file

@ -147,7 +147,12 @@
</command>
</menuContribution>
</extension>
<extension
point="org.eclipse.ui.ide.projectConfigurators">
<projectConfigurator
class="org.eclipse.cdt.internal.meson.ui.MesonProjectConfigurator"
label="Meson Project">
</projectConfigurator>
</extension>
</plugin>

View file

@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2021 Mat Booth and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.cdt.internal.meson.ui;
import java.util.List;
import org.eclipse.cdt.meson.core.MesonProjectGenerator;
import org.eclipse.core.resources.IProject;
import org.eclipse.tools.templates.core.IGenerator;
import org.eclipse.tools.templates.ui.ProjectImportConfigurator;
/**
* Smart-import strategy for importing pre-existing Meson projects.
*/
public class MesonProjectConfigurator extends ProjectImportConfigurator {
@Override
protected List<String> getProjectFileNames() {
return List.of("meson.build"); //$NON-NLS-1$
}
@Override
protected IGenerator getGenerator(IProject project) {
// Don't pass any template to the generator, we are importing an existing project
MesonProjectGenerator generator = new MesonProjectGenerator(null);
generator.setProjectName(project.getName());
generator.setLocationURI(project.getLocationURI());
return generator;
}
}

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.cdt.cmake.core;singleton:=true
Bundle-Version: 1.4.200.qualifier
Bundle-Version: 1.4.300.qualifier
Bundle-Activator: org.eclipse.cdt.cmake.core.internal.Activator
Bundle-Vendor: %providerName
Require-Bundle: org.eclipse.core.runtime,
@ -10,7 +10,7 @@ Require-Bundle: org.eclipse.core.runtime,
org.eclipse.debug.core;bundle-version="3.10.0",
org.eclipse.launchbar.core;bundle-version="2.0.0",
org.eclipse.cdt.core;bundle-version="5.12.0",
org.eclipse.tools.templates.freemarker;bundle-version="1.0.0";visibility:=reexport,
org.eclipse.tools.templates.freemarker;bundle-version="1.2.200",
com.google.gson,
org.eclipse.cdt.jsoncdb.core,
org.yaml.snakeyaml;bundle-version="1.14.0"

View file

@ -11,3 +11,5 @@ Require-Bundle: org.eclipse.swtbot.go;bundle-version="2.7.0",
org.eclipse.cdt.cmake.core;bundle-version="1.2.0"
Automatic-Module-Name: org.eclipse.cdt.cmake.ui.tests
Bundle-Localization: plugin
Import-Package: org.junit.jupiter.api;version="5.8.1",
org.junit.jupiter.api.io;version="5.8.1"

View file

@ -4,5 +4,7 @@ bin.includes = META-INF/,\
.,\
plugin.properties,\
about.html,\
swtbot-test-plugin.properties
src.includes = about.html
swtbot-test-plugin.properties,\
projects/
src.includes = about.html,\
projects/

View file

@ -30,7 +30,6 @@
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>target-platform-configuration</artifactId>
<version>${tycho-version}</version>
<configuration>
<dependency-resolution>
<extraRequirements>
@ -50,4 +49,4 @@
</plugin>
</plugins>
</build>
</project>
</project>

View file

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.10)
# Set the project name and version
project(App1 VERSION 1.0)
set(CMAKE_EXPORT_COMPILE_COMMANDS "true")
# Configuration header
configure_file(app1.h.in app1.h)
# Add project executable
add_executable(${PROJECT_NAME} app1.c)
# Include the configuration header
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_BINARY_DIR}")

View file

@ -0,0 +1,8 @@
#include <stdio.h>
#include "app1.h"
int main() {
printf("Welcome to App1\n");
printf("v%d.%d\n", App1_VERSION_MAJOR, App1_VERSION_MINOR);
return 0;
}

View file

@ -0,0 +1,2 @@
#define App1_VERSION_MAJOR @App1_VERSION_MAJOR@
#define App1_VERSION_MINOR @App1_VERSION_MINOR@

View file

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.10)
# Set the project name and version
project(App2 VERSION 2.0)
set(CMAKE_EXPORT_COMPILE_COMMANDS "true")
# Configuration header
configure_file(app2.h.in app2.h)
# Add project executable
add_executable(${PROJECT_NAME} app2.c)
# Include the configuration header
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_BINARY_DIR}")

View file

@ -0,0 +1,8 @@
#include <stdio.h>
#include "app2.h"
int main() {
printf("Welcome to App2\n");
printf("v%d.%d\n", App2_VERSION_MAJOR, App2_VERSION_MINOR);
return 0;
}

View file

@ -0,0 +1,2 @@
#define App2_VERSION_MAJOR @App2_VERSION_MAJOR@
#define App2_VERSION_MINOR @App2_VERSION_MINOR@

View file

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.10)
# Set the project name
project(NestedProject)
include(ExternalProject)
ExternalProject_Add(
App1
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/App1"
PREFIX App1
INSTALL_COMMAND ""
)
ExternalProject_Add(
App2
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/App2"
PREFIX App2
INSTALL_COMMAND ""
)

View file

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.10)
# Set the project name and version
project(SimpleProject VERSION 1.0)
set(CMAKE_EXPORT_COMPILE_COMMANDS "true")
# Configuration header
configure_file(simple.h.in simple.h)
# Add project executable
add_executable(${PROJECT_NAME} simple.c)
# Include the configuration header
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_BINARY_DIR}")

View file

@ -0,0 +1,8 @@
#include <stdio.h>
#include "simple.h"
int main() {
printf("Hello, World!\n");
printf("v%d.%d\n", SimpleProject_VERSION_MAJOR, SimpleProject_VERSION_MINOR);
return 0;
}

View file

@ -0,0 +1,2 @@
#define SimpleProject_VERSION_MAJOR @SimpleProject_VERSION_MAJOR@
#define SimpleProject_VERSION_MINOR @SimpleProject_VERSION_MINOR@

View file

@ -1,20 +0,0 @@
/*******************************************************************************
* Copyright (c) 2017 QNX Software Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.cdt.cmake.ui.internal.tests;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({ NewCMakeProjectTest.class })
public class AutomatedIntegrationSuite {
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 QNX Software Systems and others.
* Copyright (c) 2017, 2021 QNX Software Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@ -11,7 +11,16 @@
package org.eclipse.cdt.cmake.ui.internal.tests;
import static org.eclipse.swtbot.eclipse.finder.matchers.WidgetMatcherFactory.withPartName;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import org.eclipse.cdt.cmake.core.CMakeNature;
import org.eclipse.cdt.core.CCorePlugin;
@ -19,45 +28,82 @@ import org.eclipse.cdt.core.index.IIndexManager;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.ICoreRunnable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.waits.Conditions;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotPerspective;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTableItem;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.io.TempDir;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
@SuppressWarnings("nls")
public class NewCMakeProjectTest {
private static SWTWorkbenchBot bot;
@BeforeClass
@TempDir
public static Path TEMP_DIR;
@BeforeAll
public static void beforeClass() {
SWTBotPreferences.KEYBOARD_LAYOUT = "EN_US";
SWTBotPreferences.TIMEOUT = 10000;
bot = new SWTWorkbenchBot();
}
@Before
@BeforeEach
public void before() {
bot.resetWorkbench();
for (SWTBotView view : bot.views(withPartName("Welcome"))) {
view.close();
}
SWTBotPerspective perspective = bot.perspectiveById("org.eclipse.cdt.ui.CPerspective");
perspective.activate();
bot.shell().activate();
}
@Test(timeout = 60000)
public void createCMakeProject() throws Exception {
// open C++ perspective
if (!"C/C++".equals(bot.activePerspective().getLabel())) {
bot.perspectiveByLabel("C/C++").activate();
@AfterEach
public void after() {
// Delete created projects after we are done with them
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject[] projects = workspace.getRoot().getProjects();
ICoreRunnable runnable = new ICoreRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
for (IProject project : projects) {
project.delete(true, true, null);
}
}
};
try {
workspace.run(runnable, workspace.getRoot(), IWorkspace.AVOID_UPDATE, new NullProgressMonitor());
} catch (CoreException e) {
fail(e);
}
}
@Test
@Timeout(value = 2, unit = TimeUnit.MINUTES)
void createCMakeProject() throws Exception {
// Activate C/C++ wizard
bot.menu("File").menu("New").menu("C/C++ Project").click();
@ -86,22 +132,113 @@ public class NewCMakeProjectTest {
bot.button("Finish").click();
bot.waitUntil(Conditions.shellCloses(newProjectShell));
verifyProjectInExplorer(projectName);
}
@Test
@Timeout(value = 2, unit = TimeUnit.MINUTES)
void importSimpleCMakeProject() throws Exception {
importCMakeProject("SimpleProject", "SimpleProject");
}
@Test
@Timeout(value = 2, unit = TimeUnit.MINUTES)
void importNestedCMakeProject() throws Exception {
importCMakeProject("NestedProject", "NestedProject", "NestedProject/App1", "NestedProject/App2");
}
private void importCMakeProject(String projectDir, String... expectedProjects) throws Exception {
// Copy test project out of the bundle to a temp location first
Bundle bundle = FrameworkUtil.getBundle(getClass());
URL url = FileLocator
.toFileURL(FileLocator.find(bundle, new org.eclipse.core.runtime.Path("projects/" + projectDir), null));
copyDir(Paths.get(url.getPath()), TEMP_DIR.resolve(projectDir));
// Activate import wizard
bot.menu("File").menu("Import...").click();
bot.shell("Import").activate();
// Open the smart import wizard
SWTBotTree wizTree = bot.tree();
SWTBotTreeItem generalItem = wizTree.getTreeItem("General").expand();
generalItem.getNode("Projects from Folder or Archive").doubleClick();
// Select path that contains projects to import and check the project type detection works
bot.comboBox().setText(TEMP_DIR.resolve(projectDir).toString());
SWTBotTree projectProposalTree = bot.tree();
assertEquals(expectedProjects.length, projectProposalTree.getAllItems().length);
for (SWTBotTreeItem item : projectProposalTree.getAllItems()) {
assertEquals("CMake Project", item.cell(1), "Project type was not detected");
}
// Import and verify the project(s)
SWTBotShell wizShell = bot.activeShell();
bot.button("Finish").click();
bot.waitUntil(Conditions.shellCloses(wizShell));
for (String expectedProject : expectedProjects) {
verifyProjectInExplorer(expectedProject);
}
}
private void verifyProjectInExplorer(String projectName) throws Exception {
// Make sure it shows up in Project Explorer
SWTBotView explorer = bot.viewByPartName("Project Explorer");
explorer.show();
explorer.setFocus();
bot.tree().getTreeItem(projectName);
// Make sure the project indexer completes. At that point we're stable.
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
ICProject cproject = CoreModel.getDefault().create(project);
IIndexManager indexManager = CCorePlugin.getIndexManager();
while (!indexManager.isProjectContentSynced(cproject)) {
Thread.sleep(1000);
// If the project name is a path with >1 segment it was nested, so we need expand the
// parent project node to make the child project nodes visible
Path path = Paths.get(projectName);
SWTBotTreeItem projectNode;
if (path.getNameCount() > 1) {
SWTBotTreeItem node = explorer.bot().tree().expandNode(path.getName(0).toString());
projectNode = node.getNode(path.getFileName().toString());
} else {
projectNode = explorer.bot().tree().getTreeItem(path.getFileName().toString());
}
// Tests can be unstable if we are too quick, so make sure the project indexer completes
// and project natures have been assigned before continuing with post-creation verification.
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectNode.getText());
bot.waitUntil(new DefaultCondition() {
@Override
public boolean test() throws Exception {
int natures = project.getDescription().getNatureIds().length;
ICProject cproject = CoreModel.getDefault().create(project);
IIndexManager indexManager = CCorePlugin.getIndexManager();
return natures > 0 && indexManager.isProjectContentSynced(cproject);
}
@Override
public String getFailureMessage() {
return "Indexer never finished or natures never assigned for project " + project.getName();
}
});
// Make sure it has the right nature
assertTrue(project.hasNature(CMakeNature.ID));
assertTrue(project.hasNature(CMakeNature.ID), "Project does not have expected nature");
// Ensure CMakeLists exists
assertTrue(project.getFile("CMakeLists.txt").exists(), "Project does not have a build file");
}
/**
* Utility to perform a depth-first copy of a directory tree.
*/
private static void copyDir(Path src, Path dest) throws IOException {
Files.walk(src).forEach(a -> {
if (!a.equals(src)) {
Path b = dest.resolve(a.subpath(src.getNameCount(), a.getNameCount()));
try {
if (!Files.isDirectory(a)) {
Files.createDirectories(b.getParent());
Files.copy(a, b);
}
} catch (IOException e) {
fail(e);
}
}
});
}
}

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.cdt.cmake.ui;singleton:=true
Bundle-Version: 1.3.200.qualifier
Bundle-Version: 1.3.300.qualifier
Bundle-Activator: org.eclipse.cdt.cmake.ui.internal.Activator
Bundle-Vendor: %providerName
Require-Bundle: org.eclipse.core.runtime,
@ -10,7 +10,8 @@ Require-Bundle: org.eclipse.core.runtime,
org.eclipse.ui,
org.eclipse.ui.ide,
org.eclipse.cdt.cmake.core,
org.eclipse.tools.templates.ui;bundle-version="1.1.0",
org.eclipse.tools.templates.ui;bundle-version="1.3.0",
org.eclipse.tools.templates.freemarker;bundle-version="1.2.200",
org.eclipse.cdt.core;bundle-version="6.1.0",
org.eclipse.debug.ui;bundle-version="3.11.200",
org.eclipse.cdt.launch;bundle-version="9.1.0",

View file

@ -74,5 +74,12 @@
tabClass="org.eclipse.cdt.cmake.ui.internal.CMakeBuildTab">
</provider>
</extension>
<extension
point="org.eclipse.ui.ide.projectConfigurators">
<projectConfigurator
class="org.eclipse.cdt.cmake.ui.internal.CMakeProjectConfigurator"
label="CMake Project">
</projectConfigurator>
</extension>
</plugin>

View file

@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2021 Mat Booth and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.cdt.cmake.ui.internal;
import java.util.List;
import org.eclipse.cdt.cmake.core.CMakeProjectGenerator;
import org.eclipse.core.resources.IProject;
import org.eclipse.tools.templates.core.IGenerator;
import org.eclipse.tools.templates.ui.ProjectImportConfigurator;
/**
* Smart-import strategy for importing pre-existing CMake projects.
*/
public class CMakeProjectConfigurator extends ProjectImportConfigurator {
@Override
protected List<String> getProjectFileNames() {
return List.of("CMakeLists.txt"); //$NON-NLS-1$
}
@Override
protected IGenerator getGenerator(IProject project) {
// Don't pass any template to the generator, we are importing an existing project
CMakeProjectGenerator generator = new CMakeProjectGenerator(null);
generator.setProjectName(project.getName());
generator.setLocationURI(project.getLocationURI());
return generator;
}
}

View file

@ -27,8 +27,9 @@
<modules>
<module>org.eclipse.cdt.cmake.core</module>
<module>org.eclipse.cdt.cmake.core.tests</module>
<module>org.eclipse.cdt.cmake.core.tests</module>
<module>org.eclipse.cdt.cmake.ui</module>
<module>org.eclipse.cdt.cmake.ui.tests</module>
<module>org.eclipse.cdt.cmake-feature</module>
</modules>
</project>

View file

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Automatic-Module-Name: org.eclipse.tools.templates.freemarker
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.tools.templates.freemarker
Bundle-Version: 1.2.100.qualifier
Bundle-Version: 1.2.200.qualifier
Bundle-Name: %pluginName
Bundle-Vendor: %providerName
Require-Bundle: org.eclipse.core.runtime,

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 QNX Software Systems and others.
* Copyright (c) 2021 QNX Software Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@ -74,6 +74,7 @@ public abstract class FMProjectGenerator extends FMGenerator {
project = workspace.getRoot().getProject(projectName);
if (!project.exists()) {
// Create project from scratch
IProjectDescription description = workspace.newProjectDescription(projectName);
description.setLocationURI(locationURI);
if (referencedProjects != null) {
@ -83,8 +84,11 @@ public abstract class FMProjectGenerator extends FMGenerator {
project.create(description, sub);
project.open(sub);
} else {
// TODO make sure it's got all our settings or is this an error
// condition?
// Project is already created by smart import, so just configure the description
IProjectDescription description = project.getDescription();
initProjectDescription(description);
project.setDescription(description, sub);
project.open(sub);
}
sub.worked(1);

View file

@ -3,7 +3,7 @@ Automatic-Module-Name: org.eclipse.tools.templates.ui
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.tools.templates.ui;singleton:=true
Bundle-Version: 1.2.200.qualifier
Bundle-Version: 1.3.0.qualifier
Bundle-Activator: org.eclipse.tools.templates.ui.internal.Activator
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,

View file

@ -0,0 +1,31 @@
/*******************************************************************************
* Copyright (c) 2021 Mat Booth and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tools.templates.ui;
import org.eclipse.osgi.util.NLS;
class Messages extends NLS {
public static String ProjectImportConfigurator_Checking;
public static String TemplateWizard_CannotBeCreated;
public static String TemplateWizard_ErrorCreating;
public static String TemplateWizard_FailedToOpen;
public static String TemplateWizard_Generating;
public static String TemplateWizard_InternalError;
static {
NLS.initializeMessages(Messages.class.getPackageName() + ".messages", Messages.class); //$NON-NLS-1$
}
private Messages() {
}
}

View file

@ -0,0 +1,173 @@
/*******************************************************************************
* Copyright (c) 2021 Mat Booth and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tools.templates.ui;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.tools.templates.core.IGenerator;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.wizards.datatransfer.ProjectConfigurator;
import org.osgi.framework.FrameworkUtil;
/**
* Smart-import strategy for importing pre-existing projects that are not yet
* Eclipse projects.
*
* Project types that have a {@link IGenerator} may extend this to offer smart
* import functionality by registering their implementation using the <code>
* org.eclipse.ui.ide.projectConfigurator</code> extension point.
*
* It is important that implementations of this class should be stateless. See
* {@link ProjectConfigurator} for more details.
*
* @since 1.3
*/
public abstract class ProjectImportConfigurator implements ProjectConfigurator {
private static class Collector extends SimpleFileVisitor<Path> {
private IProgressMonitor monitor;
private List<PathMatcher> matchers;
// LinkedHashSet preserves insertion order so it looks nice in the UI
private Set<File> locations = new LinkedHashSet<>();
private Collector(List<PathMatcher> matchers, IProgressMonitor monitor) {
this.monitor = monitor;
this.matchers = matchers;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// Could be a long operation, so offer opportunity to cancel
if (monitor.isCanceled()) {
return FileVisitResult.TERMINATE;
}
monitor.subTask(MessageFormat.format(Messages.ProjectImportConfigurator_Checking, dir));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// If file matches any of our patterns, save the location
Path name = file.getFileName();
boolean match = matchers.stream().anyMatch(p -> p.matches(name));
if (match) {
locations.add(file.getParent().toFile());
}
return FileVisitResult.CONTINUE;
}
public Set<File> getCollected() {
return locations;
}
}
/**
* The presence of any of these files indicates a directory contains a project
* of your type. Whole filenames or glob patterns are acceptable, e.g.
* ("build.foo", "foo.*")
*
* @return a list of filenames and/or glob patterns
*/
protected abstract List<String> getProjectFileNames();
/**
* Returns the project generator implementation to be used to configure your project
* type. The base Eclipse project will be created for you, the generator just needs to
* know how to configure it. The generator will be run during the smart import batch job.
*
* @param project the project to be configured
* @return a project generator
*/
protected abstract IGenerator getGenerator(IProject project);
/**
* Utility to create path matchers from glob patterns.
*/
private List<PathMatcher> createPathMatchers(List<String> globs) {
final List<PathMatcher> matchers = new ArrayList<>();
for (String glob : globs) {
matchers.add(FileSystems.getDefault().getPathMatcher("glob:" + glob)); //$NON-NLS-1$
}
return matchers;
}
@Override
public Set<File> findConfigurableLocations(File root, IProgressMonitor monitor) {
List<PathMatcher> matchers = createPathMatchers(getProjectFileNames());
Collector c = new Collector(matchers, monitor);
try {
Files.walkFileTree(root.toPath(), c);
} catch (IOException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR,
FrameworkUtil.getBundle(getClass()).getSymbolicName(), e.getMessage(), e));
}
return c.getCollected();
}
@Override
public boolean shouldBeAnEclipseProject(IContainer container, IProgressMonitor monitor) {
List<PathMatcher> matchers = createPathMatchers(getProjectFileNames());
// Only if the location contains a file that matches the one of the given patterns
File location = container.getLocation().toFile();
for (File f : location.listFiles()) {
if (f.isFile() && matchers.stream().anyMatch(p -> p.matches(f.toPath().getFileName()))) {
return true;
}
}
return false;
}
@Override
public Set<IFolder> getFoldersToIgnore(IProject project, IProgressMonitor monitor) {
// Default to ignoring nothing
return Set.of();
}
@Override
public boolean canConfigure(IProject project, Set<IPath> ignoredPaths, IProgressMonitor monitor) {
return shouldBeAnEclipseProject(project, monitor);
}
@Override
public void configure(IProject project, Set<IPath> ignoredPaths, IProgressMonitor monitor) {
try {
IGenerator generator = getGenerator(project);
generator.generate(monitor);
} catch (CoreException e) {
Status status = new Status(e.getStatus().getSeverity(),
FrameworkUtil.getBundle(getClass()).getSymbolicName(), e.getLocalizedMessage(), e);
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
}
}
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 QNX Software Systems and others.
* Copyright (c) 2016, 2021 QNX Software Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@ -74,7 +74,7 @@ public abstract class TemplateWizard extends BasicNewResourceWizard {
IDE.openEditor(activePage, file);
}
} catch (PartInitException e) {
log("Failed to open editor", e); //$NON-NLS-1$
log(Messages.TemplateWizard_FailedToOpen, e);
}
}
@ -91,7 +91,7 @@ public abstract class TemplateWizard extends BasicNewResourceWizard {
@Override
protected void execute(IProgressMonitor monitor)
throws CoreException, InvocationTargetException, InterruptedException {
SubMonitor sub = SubMonitor.convert(monitor, "Generating", 1);
SubMonitor sub = SubMonitor.convert(monitor, Messages.TemplateWizard_Generating, 1);
generator.generate(model, sub);
getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
@ -116,16 +116,16 @@ public abstract class TemplateWizard extends BasicNewResourceWizard {
}
private void handle(Throwable target) {
String message = "Project cannot be created";
String message = Messages.TemplateWizard_CannotBeCreated;
log(message, target);
IStatus status;
if (target instanceof CoreException) {
status = ((CoreException) target).getStatus();
} else {
status = new Status(IStatus.ERROR, FrameworkUtil.getBundle(getClass()).getSymbolicName(),
"Internal Error: ", target);
Messages.TemplateWizard_InternalError, target);
}
ErrorDialog.openError(getShell(), "Error Creating Project", message, status);
ErrorDialog.openError(getShell(), Messages.TemplateWizard_ErrorCreating, message, status);
}
private void log(String message, Throwable e) {

View file

@ -0,0 +1,6 @@
ProjectImportConfigurator_Checking=Checking: {0}
TemplateWizard_CannotBeCreated=Project cannot be created
TemplateWizard_ErrorCreating=Error Creating Project
TemplateWizard_FailedToOpen=Failed to open editor
TemplateWizard_Generating=Generating
TemplateWizard_InternalError=Internal Error: