diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml index 85fc9375..1ae25fe3 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/plugin.xml @@ -7,7 +7,7 @@ - + diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java index b981e177..f66ac38b 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/CommandHandler.java @@ -31,8 +31,8 @@ public Object executeCommand(String commandId, List arguments, IProgress return PackageCommand.getChildren(arguments, monitor); case "java.resolvePath": return PackageCommand.resolvePath(arguments, monitor); - case "java.project.getMainMethod": - return ProjectCommand.getMainMethod(arguments, monitor); + case "java.project.getMainClasses": + return ProjectCommand.getMainClasses(arguments, monitor); case "java.project.generateJar": return ProjectCommand.exportJar(arguments, monitor); default: diff --git a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java index 168710f8..401eca70 100644 --- a/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java +++ b/jdtls.ext/com.microsoft.jdtls.ext.core/src/com/microsoft/jdtls/ext/core/ProjectCommand.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -67,9 +68,7 @@ public final class ProjectCommand { private static class MainClassInfo { - public String name; - public String path; public MainClassInfo(String name, String path) { @@ -78,6 +77,12 @@ public MainClassInfo(String name, String path) { } } + private static class Classpath { + public String source; + public String destination; + public boolean isArtifact; + } + private static class ExportResult { public boolean result; public String message; @@ -150,50 +155,36 @@ public static ExportResult exportJar(List arguments, IProgressMonitor mo if (arguments.size() < 3) { return new ExportResult(false, "Invalid export Arguments"); } - String mainMethod = gson.fromJson(gson.toJson(arguments.get(0)), String.class); - String[] classpaths = gson.fromJson(gson.toJson(arguments.get(1)), String[].class); + String mainClass = gson.fromJson(gson.toJson(arguments.get(0)), String.class); + Classpath[] classpaths = gson.fromJson(gson.toJson(arguments.get(1)), Classpath[].class); String destination = gson.fromJson(gson.toJson(arguments.get(2)), String.class); Manifest manifest = new Manifest(); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - if (mainMethod.length() > 0) { - manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, mainMethod); + if (mainClass.length() > 0) { + manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, mainClass); } try (JarOutputStream target = new JarOutputStream(new FileOutputStream(destination), manifest)) { Set directories = new HashSet<>(); - for (String classpath : classpaths) { - if (classpath != null) { - if (classpath.endsWith(".jar")) { - ZipFile zip = new ZipFile(classpath); - writeArchive(zip, true, true, target, directories, monitor); - } else { - File folder = new File(classpath); - writeFileRecursively(folder, target, directories, folder.getAbsolutePath().length() + 1); + for (Classpath classpath : classpaths) { + if (classpath.isArtifact) { + writeArchive(new ZipFile(classpath.source), /* areDirectoryEntriesIncluded = */true, + /* isCompressed = */true, target, directories, monitor); + } else { + try { + writeFile(new File(classpath.source), new Path(classpath.destination), /* areDirectoryEntriesIncluded = */true, + /* isCompressed = */true, target, directories); + } catch (CoreException e) { + // TODO: Collect reports } } } - } catch (Exception e) { + } catch (IOException e) { return new ExportResult(false, e.getMessage()); } return new ExportResult(true); } - private static void writeFileRecursively(File folder, JarOutputStream jarOutputStream, Set directories, - int len) { - File[] files = folder.listFiles(); - for (File file : files) { - if (file.isDirectory()) { - writeFileRecursively(file, jarOutputStream, directories, len); - } else if (file.isFile()) { - try { - writeFile(file, new Path(file.getAbsolutePath().substring(len)), true, true, jarOutputStream, directories); - } catch (Exception e) { - // do nothing - } - } - } - } - - public static List getMainMethod(List arguments, IProgressMonitor monitor) throws Exception { + public static List getMainClasses(List arguments, IProgressMonitor monitor) throws Exception { List projectList = listProjects(arguments, monitor); final List res = new ArrayList<>(); List searchRoots = new ArrayList<>(); diff --git a/package-lock.json b/package-lock.json index 311c474b..d1120616 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,29 @@ "js-tokens": "^4.0.0" } }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, "@types/fancy-log": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@types/fancy-log/-/fancy-log-1.3.0.tgz", @@ -818,6 +841,11 @@ } } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -1114,7 +1142,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -2101,6 +2128,21 @@ } } }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + } + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -2583,12 +2625,44 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "requires": { + "reusify": "^1.0.4" + } + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -2606,7 +2680,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" }, @@ -2615,7 +2688,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -3426,7 +3498,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -3524,6 +3595,19 @@ } } }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, "glogg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", @@ -4591,6 +4675,11 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -4745,8 +4834,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -4761,7 +4849,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -4775,8 +4862,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-plain-object": { "version": "2.0.4", @@ -5645,6 +5731,11 @@ "readable-stream": "^2.0.1" } }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -6739,8 +6830,7 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pify": { "version": "2.3.0", @@ -7551,6 +7641,11 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -7570,6 +7665,11 @@ "inherits": "^2.0.1" } }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -7700,6 +7800,11 @@ "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -8564,9 +8669,9 @@ } }, "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true }, "uri-js": { diff --git a/package.json b/package.json index 57997f69..da3a0e65 100644 --- a/package.json +++ b/package.json @@ -466,6 +466,48 @@ "contents": "You can create a new Java project by clicking the below button.\n[Create new Java project](command:java.project.create)", "when": "!java:projectManagerActivated || java:serverMode != Standard" } + ], + "taskDefinitions": [ + { + "type": "java", + "properties": { + "label": { + "type": "string", + "description": "%taskDefinitions.java.project.exportJar.label%" + }, + "mainClass": { + "type": "string", + "description": "%taskDefinitions.java.project.exportJar.mainClass%" + }, + "targetPath": { + "type": "string", + "description": "%taskDefinitions.java.project.exportJar.targetPath%" + }, + "elements": { + "type": "array", + "items": { + "anyOf": [ + { + "enum": [ + "${compileOutput}", + "${testCompileOutput}", + "${dependencies}", + "${testDependencies}" + ], + "enumDescriptions": [ + "%taskDefinitions.java.project.exportJar.compileOutput%", + "%taskDefinitions.java.project.exportJar.testCompileOutput%", + "%taskDefinitions.java.project.exportJar.dependencies%", + "%taskDefinitions.java.project.exportJar.testDependencies%" + ] + }, + "string" + ] + }, + "description": "%taskDefinitions.java.project.exportJar.elements%" + } + } + } ] }, "scripts": { @@ -496,6 +538,7 @@ "webpack-cli": "^3.3.12" }, "dependencies": { + "globby": "11.0.1", "fs-extra": "^7.0.1", "lodash": "^4.17.20", "minimatch": "^3.0.4", diff --git a/package.nls.json b/package.nls.json index 03b064b6..2493e500 100644 --- a/package.nls.json +++ b/package.nls.json @@ -24,5 +24,13 @@ "configuration.java.dependency.autoRefresh": "Synchronize Java Projects explorer with changes", "configuration.java.dependency.refreshDelay": "The delay time (ms) the auto refresh is invoked when changes are detected", "configuration.java.dependency.packagePresentation": "Package presentation mode: flat or hierarchical", - "configuration.java.project.exportJar.targetPath": "The default output path of export jar" + "configuration.java.project.exportJar.targetPath": "The default output path of export jar.", + "taskDefinitions.java.project.exportJar.label": "The label of export jar task.", + "taskDefinitions.java.project.exportJar.elements": "The content list of the exported jar.", + "taskDefinitions.java.project.exportJar.mainClass": "The main class in the manifest of the exported jar.", + "taskDefinitions.java.project.exportJar.targetPath": "The output path of the exported jar. This will override configuration.java.project.exportJar.targetPath.", + "taskDefinitions.java.project.exportJar.compileOutput": "The folders containing output class files in the runtime scope.", + "taskDefinitions.java.project.exportJar.testCompileOutput": "The folders containing output class files in the test scope.", + "taskDefinitions.java.project.exportJar.dependencies": "The artifact dependencies in the runtime scope.", + "taskDefinitions.java.project.exportJar.testDependencies": "The artifact dependencies in the test scope." } diff --git a/package.nls.zh.json b/package.nls.zh.json index 96693f7f..e5d6149f 100644 --- a/package.nls.zh.json +++ b/package.nls.zh.json @@ -24,5 +24,13 @@ "configuration.java.dependency.autoRefresh": "在 Java 项目管理器中自动同步修改", "configuration.java.dependency.refreshDelay": "控制 Java 项目管理器刷新的延迟时间 (毫秒)", "configuration.java.dependency.packagePresentation": "Java 包显示方式: 平行显示或者分层显示", - "configuration.java.project.exportJar.targetPath": "导出 Jar 文件的默认路径" + "configuration.java.project.exportJar.targetPath": "导出 Jar 文件的输出路径", + "taskDefinitions.java.project.exportJar.label": "导出 Jar 任务的名称。", + "taskDefinitions.java.project.exportJar.elements": "导出 Jar 文件的内容列表。", + "taskDefinitions.java.project.exportJar.mainClass": "导出 Jar 文件的 main 函数所在的类。", + "taskDefinitions.java.project.exportJar.targetPath": "导出 Jar 文件的输出路径。将会覆盖配置项: configuration.java.project.exportJar.targetPath 的值。", + "taskDefinitions.java.project.exportJar.compileOutput": "在 runtime scope 内包含输出的 class 文件的文件夹。", + "taskDefinitions.java.project.exportJar.testCompileOutput": "在 test scope 内包含输出的 class 文件的文件夹。", + "taskDefinitions.java.project.exportJar.dependencies": "在 runtime scope 内的依赖。", + "taskDefinitions.java.project.exportJar.testDependencies": "在 test scope 内的依赖。" } diff --git a/src/commands.ts b/src/commands.ts index 7c206f27..4467fad2 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -68,7 +68,7 @@ export namespace Commands { export const JAVA_RESOLVEPATH = "java.resolvePath"; - export const JAVA_PROJECT_GETMAINMETHOD = "java.project.getMainMethod"; + export const JAVA_PROJECT_GETMAINCLASSES = "java.project.getMainClasses"; export const JAVA_PROJECT_GENERATEJAR = "java.project.generateJar"; diff --git a/src/exportJarFileCommand.ts b/src/exportJarFileCommand.ts index 2f0bcef7..f1393f38 100644 --- a/src/exportJarFileCommand.ts +++ b/src/exportJarFileCommand.ts @@ -8,21 +8,21 @@ import { GenerateJarExecutor } from "./exportJarSteps/GenerateJarExecutor"; import { IExportJarStepExecutor } from "./exportJarSteps/IExportJarStepExecutor"; import { IStepMetadata } from "./exportJarSteps/IStepMetadata"; import { ResolveJavaProjectExecutor } from "./exportJarSteps/ResolveJavaProjectExecutor"; -import { ResolveMainMethodExecutor } from "./exportJarSteps/ResolveMainMethodExecutor"; +import { ResolveMainClassExecutor } from "./exportJarSteps/ResolveMainClassExecutor"; import { failMessage, successMessage } from "./exportJarSteps/utility"; import { isStandardServerReady } from "./extension"; import { INodeData } from "./java/nodeData"; export enum ExportJarStep { ResolveJavaProject = "RESOLVEJAVAPROJECT", - ResolveMainMethod = "RESOLVEMAINMETHOD", + ResolveMainClass = "RESOLVEMAINCLASS", GenerateJar = "GENERATEJAR", Finish = "FINISH", } const stepMap: Map = new Map([ [ExportJarStep.ResolveJavaProject, new ResolveJavaProjectExecutor()], - [ExportJarStep.ResolveMainMethod, new ResolveMainMethodExecutor()], + [ExportJarStep.ResolveMainClass, new ResolveMainClassExecutor()], [ExportJarStep.GenerateJar, new GenerateJarExecutor()], ]); diff --git a/src/exportJarSteps/ExportJarTaskProvider.ts b/src/exportJarSteps/ExportJarTaskProvider.ts index 851e84a0..f6151ac1 100644 --- a/src/exportJarSteps/ExportJarTaskProvider.ts +++ b/src/exportJarSteps/ExportJarTaskProvider.ts @@ -1,25 +1,43 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +import { lstat } from "fs-extra"; +import * as globby from "globby"; +import * as _ from "lodash"; +import { platform } from "os"; +import { dirname, extname, isAbsolute, join, relative } from "path"; import { CustomExecution, Event, EventEmitter, Pseudoterminal, Task, TaskDefinition, - TaskProvider, TaskRevealKind, TaskScope, TerminalDimensions, + TaskProvider, TaskRevealKind, TaskScope, TerminalDimensions, Uri, workspace, WorkspaceFolder, } from "vscode"; import { createJarFile } from "../exportJarFileCommand"; +import { Jdtls } from "../java/jdtls"; +import { INodeData } from "../java/nodeData"; import { Settings } from "../settings"; -import { IStepMetadata } from "./IStepMetadata"; +import { IUriData, Trie, TrieNode } from "../views/nodeCache/Trie"; +import { IClasspathResult } from "./GenerateJarExecutor"; +import { IClasspath, IStepMetadata } from "./IStepMetadata"; +import { IMainClassInfo } from "./ResolveMainClassExecutor"; +import { ExportJarConstants, failMessage, getExtensionApi, toPosixPath, toWinPath } from "./utility"; + +interface IExportJarTaskDefinition extends TaskDefinition { + label?: string; + mainClass?: string; + targetPath?: string; + elements?: string[]; +} export class ExportJarTaskProvider implements TaskProvider { public static exportJarType: string = "java"; public static getTask(stepMetadata: IStepMetadata): Task { - const targetPathSetting: string = Settings.getExportJarTargetPath(); const defaultDefinition: IExportJarTaskDefinition = { type: ExportJarTaskProvider.exportJarType, - targetPath: targetPathSetting, + label: `${ExportJarTaskProvider.exportJarType}: exportjar:default`, + targetPath: Settings.getExportJarTargetPath(), elements: [], - mainMethod: undefined, + mainClass: undefined, }; const task: Task = new Task(defaultDefinition, TaskScope.Workspace, "exportjar:default", ExportJarTaskProvider.exportJarType, new CustomExecution(async (resolvedDefinition: TaskDefinition): Promise => { @@ -29,14 +47,67 @@ export class ExportJarTaskProvider implements TaskProvider { return task; } - public async resolveTask(_task: Task): Promise { - return _task; + private tasks: Task[] | undefined; + + public async resolveTask(task: Task): Promise { + const definition: IExportJarTaskDefinition = task.definition; + const folder: WorkspaceFolder = task.scope; + const resolvedTask: Task = new Task(definition, folder, task.name, ExportJarTaskProvider.exportJarType, + new CustomExecution(async (resolvedDefinition: TaskDefinition): Promise => { + const stepMetadata: IStepMetadata = { + entry: undefined, + workspaceFolder: folder, + projectList: await Jdtls.getProjects(folder.uri.toString()), + steps: [], + }; + return new ExportJarTaskTerminal(resolvedDefinition, stepMetadata); + })); + resolvedTask.presentationOptions.reveal = TaskRevealKind.Never; + return resolvedTask; } public async provideTasks(): Promise { - return []; + if (!_.isEmpty(this.tasks)) { + return this.tasks; + } + this.tasks = []; + for (const folder of workspace.workspaceFolders) { + const projectList: INodeData[] = await Jdtls.getProjects(folder.uri.toString()); + const elementList: string[] = []; + if (_.isEmpty(projectList)) { + continue; + } else if (projectList.length === 1) { + elementList.push("${" + ExportJarConstants.COMPILE_OUTPUT + "}", + "${" + ExportJarConstants.DEPENDENCIES + "}"); + } else { + for (const project of projectList) { + elementList.push("${" + ExportJarConstants.COMPILE_OUTPUT + ":" + project.name + "}", + "${" + ExportJarConstants.DEPENDENCIES + ":" + project.name + "}"); + } + } + const mainClasses: IMainClassInfo[] = await Jdtls.getMainClasses(folder.uri.toString()); + const defaultDefinition: IExportJarTaskDefinition = { + type: ExportJarTaskProvider.exportJarType, + label: `${ExportJarTaskProvider.exportJarType}: exportjar:${folder.name}`, + mainClass: (mainClasses.length === 1) ? mainClasses[0].name : undefined, + targetPath: Settings.getExportJarTargetPath(), + elements: elementList, + }; + const defaultTask: Task = new Task(defaultDefinition, folder, `exportjar:${folder.name}`, + ExportJarTaskProvider.exportJarType, new CustomExecution(async (resolvedDefinition: TaskDefinition): Promise => { + const stepMetadata: IStepMetadata = { + entry: undefined, + workspaceFolder: folder, + projectList: await Jdtls.getProjects(folder.uri.toString()), + steps: [], + }; + return new ExportJarTaskTerminal(resolvedDefinition, stepMetadata); + }), undefined); + defaultTask.presentationOptions.reveal = TaskRevealKind.Never; + this.tasks.push(defaultTask); + } + return this.tasks; } - } class ExportJarTaskTerminal implements Pseudoterminal { @@ -51,23 +122,167 @@ class ExportJarTaskTerminal implements Pseudoterminal { constructor(exportJarTaskDefinition: IExportJarTaskDefinition, stepMetadata: IStepMetadata) { this.stepMetadata = stepMetadata; - this.stepMetadata.mainMethod = exportJarTaskDefinition.mainMethod; + this.stepMetadata.mainClass = exportJarTaskDefinition.mainClass; this.stepMetadata.outputPath = exportJarTaskDefinition.targetPath; this.stepMetadata.elements = exportJarTaskDefinition.elements; } - public async open(initialDimensions: TerminalDimensions | undefined): Promise { - await createJarFile(this.stepMetadata); - this.closeEmitter.fire(); + public async open(_initialDimensions: TerminalDimensions | undefined): Promise { + try { + if (this.stepMetadata.outputPath === undefined) { + // TODO: get resolved path from setting configuration.java.project.exportJar.targetPath. + // For the tasks whose targetPath is undefined, the user will select the output location manually. + this.stepMetadata.outputPath = ""; + } + if (!_.isEmpty(this.stepMetadata.elements)) { + const outputFolderMap: Map = new Map(); + const artifactMap: Map = new Map(); + const testOutputFolderMap: Map = new Map(); + const testArtifactMap: Map = new Map(); + const projectList: INodeData[] = await Jdtls.getProjects(this.stepMetadata.workspaceFolder.uri.toString()); + for (const project of projectList) { + await this.setClasspathMap(project, "runtime", outputFolderMap, artifactMap); + await this.setClasspathMap(project, "test", testOutputFolderMap, testArtifactMap); + } + this.stepMetadata.classpaths = await this.resolveClasspaths(outputFolderMap, + artifactMap, testOutputFolderMap, testArtifactMap); + } + await createJarFile(this.stepMetadata); + } catch (err) { + if (err) { + failMessage(`${err}`); + } + } finally { + this.closeEmitter.fire(); + } } public close(): void { } -} -interface IExportJarTaskDefinition extends TaskDefinition { - elements?: string[]; - mainMethod?: string; - targetPath?: string; + private async setClasspathMap(project: INodeData, classpathScope: string, + outputFolderMap: Map, artifactMap: Map): Promise { + const extensionApi: any = await getExtensionApi(); + const classpathResult: IClasspathResult = await extensionApi.getClasspaths(project.uri, { scope: classpathScope }); + const outputFolders: string[] = []; + const artifacts: string[] = []; + for (const classpath of [...classpathResult.classpaths, ...classpathResult.modulepaths]) { + if (extname(classpath) === ".jar") { + artifacts.push(classpath); + } else { + outputFolders.push(classpath); + } + } + outputFolderMap.set(project.name, outputFolders); + artifactMap.set(project.name, artifacts); + } + + private async resolveClasspaths(outputFolderMap: Map, + artifactMap: Map, + testOutputFolderMap: Map, + testArtifactMap: Map): Promise { + const regExp: RegExp = /\${(.*?)(:.*)?}/; + let outputElements: string[] = []; + let artifacts: string[] = []; + for (const element of this.stepMetadata.elements) { + if (element.length === 0) { + continue; + } + const matchResult = element.match(regExp); + if (_.isEmpty(matchResult) || matchResult.length <= 2) { + if (extname(element) === ".jar") { + artifacts.push(this.toAbsolutePosixPath(element)); + } else { + outputElements.push(this.toAbsolutePosixPath(element)); + } + continue; + } + const projectName: string = (matchResult[2] === undefined) ? undefined : matchResult[2].substring(1); + switch (matchResult[1]) { + case ExportJarConstants.DEPENDENCIES: + artifacts = artifacts.concat(this.getJarElementsFromClasspathMapping(matchResult, artifactMap, projectName)); + break; + case ExportJarConstants.TEST_DEPENDENCIES: + artifacts = artifacts.concat(this.getJarElementsFromClasspathMapping(matchResult, testArtifactMap, projectName)); + break; + case ExportJarConstants.COMPILE_OUTPUT: + outputElements = outputElements.concat(this.getJarElementsFromClasspathMapping(matchResult, outputFolderMap, projectName)); + break; + case ExportJarConstants.TEST_COMPILE_OUTPUT: + outputElements = outputElements.concat(this.getJarElementsFromClasspathMapping(matchResult, testOutputFolderMap, projectName)); + break; + } + } + const trie: Trie = new Trie(); + const globPatterns: string[] = []; + for (const outputElement of outputElements) { + if (outputElement.length === 0) { + continue; + } + if (outputElement[0] !== "!") { + const uri: Uri = Uri.file(platform() === "win32" ? toWinPath(outputElement) : outputElement); + const uriData: IUriData = { + uri: uri.toString(), + }; + trie.insert(uriData); + } + globPatterns.push(outputElement); + } + const sources: IClasspath[] = []; + for (const glob of await globby(globPatterns)) { + const tireNode: TrieNode = trie.find(Uri.file(platform() === "win32" ? toWinPath(glob) : glob).fsPath, /* returnEarly = */true); + if (tireNode === undefined) { + continue; + } + let fsPath = Uri.parse(tireNode.value.uri).fsPath; + if ((await lstat(fsPath)).isFile()) { + fsPath = dirname(fsPath); + } + if (!_.isEmpty(tireNode)) { + const classpath: IClasspath = { + source: glob, + destination: relative(fsPath, glob), + isArtifact: false, + }; + sources.push(classpath); + } + } + for (const artifact of await globby(artifacts)) { + const classpath: IClasspath = { + source: artifact, + destination: undefined, + isArtifact: true, + }; + sources.push(classpath); + } + return sources; + } + + private getJarElementsFromClasspathMapping(matchResult: RegExpMatchArray, rawClasspathEntries: Map, + projectName: string | undefined): string[] { + const result: string[] = []; + if (projectName !== undefined) { + for (const classpath of rawClasspathEntries.get(projectName)) { + result.push(this.toAbsolutePosixPath(matchResult.input.replace(matchResult[0], classpath))); + } + } else { + for (const classpaths of rawClasspathEntries.values()) { + for (const classpath of classpaths) { + result.push(this.toAbsolutePosixPath(matchResult.input.replace(matchResult[0], classpath))); + } + } + } + return result; + } + + private toAbsolutePosixPath(path: string): string { + const negative: boolean = (path[0] === "!"); + let positivePath: string = negative ? path.substring(1) : path; + if (!isAbsolute(positivePath)) { + positivePath = join(this.stepMetadata.workspaceFolder.uri.fsPath, positivePath); + } + positivePath = toPosixPath(positivePath); + return negative ? "!" + positivePath : positivePath; + } } diff --git a/src/exportJarSteps/GenerateJarExecutor.ts b/src/exportJarSteps/GenerateJarExecutor.ts index d5789ef9..7daadb08 100644 --- a/src/exportJarSteps/GenerateJarExecutor.ts +++ b/src/exportJarSteps/GenerateJarExecutor.ts @@ -2,14 +2,17 @@ // Licensed under the MIT license. import { ensureDir, pathExists } from "fs-extra"; +import globby = require("globby"); import * as _ from "lodash"; -import { basename, dirname, extname, isAbsolute, join, normalize } from "path"; -import { Disposable, Extension, extensions, ProgressLocation, QuickInputButtons, QuickPickItem, Uri, window } from "vscode"; +import { basename, dirname, extname, isAbsolute, join, normalize, relative } from "path"; +import { Disposable, ProgressLocation, QuickInputButtons, QuickPickItem, Uri, window } from "vscode"; +import { sendInfo } from "vscode-extension-telemetry-wrapper"; import { ExportJarStep } from "../exportJarFileCommand"; import { Jdtls } from "../java/jdtls"; import { IExportJarStepExecutor } from "./IExportJarStepExecutor"; -import { IStepMetadata } from "./IStepMetadata"; -import { createPickBox, resetStepMetadata, saveDialog, SETTING_ASKUSER } from "./utility"; +import { IClasspath, IStepMetadata } from "./IStepMetadata"; +import { createPickBox, ExportJarTargets, failMessage, getExtensionApi, + resetStepMetadata, saveDialog, toPosixPath } from "./utility"; export class GenerateJarExecutor implements IExportJarStepExecutor { @@ -28,14 +31,16 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { private async generateJar(stepMetadata: IStepMetadata): Promise { if (_.isEmpty(stepMetadata.elements)) { - stepMetadata.elements = []; + stepMetadata.classpaths = []; if (!(await this.generateElements(stepMetadata))) { return false; } } - stepMetadata.outputPath = normalize(stepMetadata.outputPath); let destPath = ""; - if (stepMetadata.outputPath === SETTING_ASKUSER) { + if (stepMetadata.outputPath === ExportJarTargets.SETTING_ASKUSER || stepMetadata.outputPath === "") { + if (stepMetadata.outputPath === ExportJarTargets.SETTING_ASKUSER) { + sendInfo("", { exportJarPath: stepMetadata.outputPath }); + } const outputUri: Uri = await saveDialog(stepMetadata.workspaceFolder.uri, "Generate"); if (outputUri === undefined) { return Promise.reject(); @@ -53,6 +58,7 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { } await ensureDir(dirname(destPath)); } + destPath = normalize(destPath); return window.withProgress({ location: ProgressLocation.Window, title: "Exporting Jar : Generating jar...", @@ -62,7 +68,7 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { token.onCancellationRequested(() => { return reject(); }); - const exportResult: IExportResult = await Jdtls.exportJar(basename(stepMetadata.mainMethod), stepMetadata.elements, destPath); + const exportResult: IExportResult = await Jdtls.exportJar(basename(stepMetadata.mainClass), stepMetadata.classpaths, destPath); if (exportResult.result === true) { stepMetadata.outputPath = destPath; return resolve(true); @@ -74,8 +80,7 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { } private async generateElements(stepMetadata: IStepMetadata): Promise { - const extension: Extension | undefined = extensions.getExtension("redhat.java"); - const extensionApi: any = await extension?.activate(); + const extensionApi: any = await getExtensionApi(); const dependencyItems: IJarQuickPickItem[] = await window.withProgress({ location: ProgressLocation.Window, title: "Exporting Jar : Resolving classpaths...", @@ -87,14 +92,15 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { }); const pickItems: IJarQuickPickItem[] = []; const uriSet: Set = new Set(); - for (const rootNode of stepMetadata.projectList) { - const classPaths: ClasspathResult = await extensionApi.getClasspaths(rootNode.uri, { scope: "runtime" }); - pickItems.push(...await this.parseDependencyItems(classPaths.classpaths, uriSet, stepMetadata.workspaceFolder.uri.fsPath, true), - ...await this.parseDependencyItems(classPaths.modulepaths, uriSet, stepMetadata.workspaceFolder.uri.fsPath, true)); - const classPathsTest: ClasspathResult = await extensionApi.getClasspaths(rootNode.uri, { scope: "test" }); - pickItems.push(...await this.parseDependencyItems(classPathsTest.classpaths, uriSet, - stepMetadata.workspaceFolder.uri.fsPath, false), - ...await this.parseDependencyItems(classPathsTest.modulepaths, uriSet, stepMetadata.workspaceFolder.uri.fsPath, false)); + for (const project of stepMetadata.projectList) { + const classpaths: IClasspathResult = await extensionApi.getClasspaths(project.uri, { scope: "runtime" }); + pickItems.push( + ...await this.parseDependencyItems(classpaths.classpaths, uriSet, stepMetadata.workspaceFolder.uri.fsPath, "runtime"), + ...await this.parseDependencyItems(classpaths.modulepaths, uriSet, stepMetadata.workspaceFolder.uri.fsPath, "runtime")); + const testClasspaths: IClasspathResult = await extensionApi.getClasspaths(project.uri, { scope: "test" }); + pickItems.push( + ...await this.parseDependencyItems(testClasspaths.classpaths, uriSet, stepMetadata.workspaceFolder.uri.fsPath, "test"), + ...await this.parseDependencyItems(testClasspaths.modulepaths, uriSet, stepMetadata.workspaceFolder.uri.fsPath, "test")); } return resolve(pickItems); }); @@ -102,7 +108,7 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { if (_.isEmpty(dependencyItems)) { throw new Error("No classpath found. Please make sure your java project is valid."); } else if (dependencyItems.length === 1) { - stepMetadata.elements.push(dependencyItems[0].path); + this.setStepMetadataFromOutputFolder(dependencyItems[0].path, stepMetadata); return true; } dependencyItems.sort((node1, node2) => { @@ -133,12 +139,21 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { return resolve(false); } }), - pickBox.onDidAccept(() => { + pickBox.onDidAccept(async () => { if (_.isEmpty(pickBox.selectedItems)) { return; } for (const item of pickBox.selectedItems) { - stepMetadata.elements.push(item.path); + if (item.type === "artifact") { + const classpath: IClasspath = { + source: item.path, + destination: undefined, + isArtifact: true, + }; + stepMetadata.classpaths.push(classpath); + } else { + this.setStepMetadataFromOutputFolder(item.path, stepMetadata); + } } return resolve(true); }), @@ -157,7 +172,19 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { return result; } - private async parseDependencyItems(paths: string[], uriSet: Set, projectPath: string, isRuntime: boolean): Promise { + private async setStepMetadataFromOutputFolder(folderPath: string, stepMetadata: IStepMetadata): Promise { + const posixPath: string = toPosixPath(folderPath); + for (const path of await globby(posixPath)) { + const classpath: IClasspath = { + source: path, + destination: relative(posixPath, path), + isArtifact: false, + }; + stepMetadata.classpaths.push(classpath); + } + } + + private async parseDependencyItems(paths: string[], uriSet: Set, projectPath: string, scope: string): Promise { const dependencyItems: IJarQuickPickItem[] = []; for (const classpath of paths) { if (await pathExists(classpath) === false) { @@ -165,17 +192,16 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { } const extName = extname(classpath); const baseName = Uri.parse(classpath).fsPath.startsWith(Uri.parse(projectPath).fsPath) ? - classpath.substring(projectPath.length + 1) : basename(classpath); - const descriptionValue = (isRuntime) ? "Runtime" : "Test"; - const typeValue = (extName === ".jar") ? "external" : "internal"; + relative(projectPath, classpath) : basename(classpath); + const typeValue = (extName === ".jar") ? "artifact" : "outputFolder"; if (!uriSet.has(classpath)) { uriSet.add(classpath); dependencyItems.push({ label: baseName, - description: descriptionValue, + description: scope, path: classpath, type: typeValue, - picked: isRuntime, + picked: scope === "runtime", }); } } @@ -184,10 +210,10 @@ export class GenerateJarExecutor implements IExportJarStepExecutor { } -class ClasspathResult { - public projectRoot: string; - public classpaths: string[]; - public modulepaths: string[]; +export interface IClasspathResult { + projectRoot: string; + classpaths: string[]; + modulepaths: string[]; } interface IJarQuickPickItem extends QuickPickItem { diff --git a/src/exportJarSteps/IStepMetadata.ts b/src/exportJarSteps/IStepMetadata.ts index fc699499..c42fd0dc 100644 --- a/src/exportJarSteps/IStepMetadata.ts +++ b/src/exportJarSteps/IStepMetadata.ts @@ -9,8 +9,15 @@ export interface IStepMetadata { entry?: INodeData; workspaceFolder?: WorkspaceFolder; projectList?: INodeData[]; - mainMethod?: string; - outputPath?: string; + mainClass?: string; elements?: string[]; + classpaths?: IClasspath[]; + outputPath?: string; steps: ExportJarStep[]; } + +export interface IClasspath { + source: string; + destination: string | undefined; + isArtifact: boolean; +} diff --git a/src/exportJarSteps/ResolveJavaProjectExecutor.ts b/src/exportJarSteps/ResolveJavaProjectExecutor.ts index b44e6a38..a23910c1 100644 --- a/src/exportJarSteps/ResolveJavaProjectExecutor.ts +++ b/src/exportJarSteps/ResolveJavaProjectExecutor.ts @@ -14,11 +14,13 @@ import { createPickBox } from "./utility"; export class ResolveJavaProjectExecutor implements IExportJarStepExecutor { public getNextStep(): ExportJarStep { - return ExportJarStep.ResolveMainMethod; + return ExportJarStep.ResolveMainClass; } public async execute(stepMetadata: IStepMetadata): Promise { - await this.resolveJavaProject(stepMetadata); + if (stepMetadata.workspaceFolder === undefined) { + await this.resolveJavaProject(stepMetadata); + } return this.getNextStep(); } diff --git a/src/exportJarSteps/ResolveMainMethodExecutor.ts b/src/exportJarSteps/ResolveMainClassExecutor.ts similarity index 74% rename from src/exportJarSteps/ResolveMainMethodExecutor.ts rename to src/exportJarSteps/ResolveMainClassExecutor.ts index af85d312..13541597 100644 --- a/src/exportJarSteps/ResolveMainMethodExecutor.ts +++ b/src/exportJarSteps/ResolveMainClassExecutor.ts @@ -9,9 +9,9 @@ import { IExportJarStepExecutor } from "./IExportJarStepExecutor"; import { IStepMetadata } from "./IStepMetadata"; import { createPickBox, resetStepMetadata } from "./utility"; -export class ResolveMainMethodExecutor implements IExportJarStepExecutor { +export class ResolveMainClassExecutor implements IExportJarStepExecutor { - private static getName(data: MainMethodInfo) { + private static getName(data: IMainClassInfo) { return data.name.substring(data.name.lastIndexOf(".") + 1); } @@ -20,10 +20,10 @@ export class ResolveMainMethodExecutor implements IExportJarStepExecutor { } public async execute(stepMetadata: IStepMetadata): Promise { - if (stepMetadata.mainMethod !== undefined) { + if (stepMetadata.mainClass !== undefined) { return this.getNextStep(); } - if (await this.resolveMainMethod(stepMetadata)) { + if (await this.resolveMainClass(stepMetadata)) { return this.getNextStep(); } const lastStep: ExportJarStep = stepMetadata.steps.pop(); @@ -31,28 +31,28 @@ export class ResolveMainMethodExecutor implements IExportJarStepExecutor { return lastStep; } - private async resolveMainMethod(stepMetadata: IStepMetadata): Promise { - const mainMethods: MainMethodInfo[] = await window.withProgress({ + private async resolveMainClass(stepMetadata: IStepMetadata): Promise { + const mainClasses: IMainClassInfo[] = await window.withProgress({ location: ProgressLocation.Window, title: "Exporting Jar : Resolving main classes...", cancellable: true, }, (progress, token) => { - return new Promise(async (resolve, reject) => { + return new Promise(async (resolve, reject) => { token.onCancellationRequested(() => { return reject(); }); - resolve(await Jdtls.getMainMethod(stepMetadata.workspaceFolder.uri.toString())); + resolve(await Jdtls.getMainClasses(stepMetadata.workspaceFolder.uri.toString())); }); }); - if (mainMethods === undefined || mainMethods.length === 0) { - stepMetadata.mainMethod = ""; + if (mainClasses === undefined || mainClasses.length === 0) { + stepMetadata.mainClass = ""; return true; } const pickItems: QuickPickItem[] = []; - for (const mainMethod of mainMethods) { + for (const mainClass of mainClasses) { pickItems.push({ - label: ResolveMainMethodExecutor.getName(mainMethod), - description: mainMethod.name, + label: ResolveMainClassExecutor.getName(mainClass), + description: mainClass.name, }); } const noMainClassItem: QuickPickItem = { @@ -76,8 +76,8 @@ export class ResolveMainMethodExecutor implements IExportJarStepExecutor { if (_.isEmpty(pickBox.selectedItems)) { return; } - stepMetadata.mainMethod = pickBox.selectedItems[0].description; - stepMetadata.steps.push(ExportJarStep.ResolveMainMethod); + stepMetadata.mainClass = pickBox.selectedItems[0].description; + stepMetadata.steps.push(ExportJarStep.ResolveMainClass); return resolve(true); }), pickBox.onDidHide(() => { @@ -96,7 +96,7 @@ export class ResolveMainMethodExecutor implements IExportJarStepExecutor { } } -export class MainMethodInfo { - public name: string; - public path: string; +export interface IMainClassInfo { + name: string; + path: string; } diff --git a/src/exportJarSteps/utility.ts b/src/exportJarSteps/utility.ts index 57f3f3cb..58ee7e51 100644 --- a/src/exportJarSteps/utility.ts +++ b/src/exportJarSteps/utility.ts @@ -2,20 +2,32 @@ // Licensed under the MIT license. import { EOL, platform } from "os"; -import { commands, QuickInputButtons, QuickPick, QuickPickItem, SaveDialogOptions, Uri, window } from "vscode"; +import { posix, win32 } from "path"; +import { commands, Extension, extensions, QuickInputButtons, QuickPick, QuickPickItem, SaveDialogOptions, Uri, window } from "vscode"; import { sendOperationError } from "vscode-extension-telemetry-wrapper"; import { ExportJarStep } from "../exportJarFileCommand"; import { IStepMetadata } from "./IStepMetadata"; -export const SETTING_ASKUSER: string = "askUser"; +export namespace ExportJarTargets { + export const SETTING_ASKUSER: string = "askUser"; + // tslint:disable-next-line: no-invalid-template-strings + export const DEFAULT_OUTPUT_PATH: string = "${workspaceFolder}/${workspaceFolderBasename}.jar"; +} + +export namespace ExportJarConstants { + export const DEPENDENCIES: string = "dependencies"; + export const TEST_DEPENDENCIES: string = "testDependencies"; + export const COMPILE_OUTPUT: string = "compileOutput"; + export const TEST_COMPILE_OUTPUT: string = "testCompileOutput"; +} export function resetStepMetadata(resetTo: ExportJarStep, stepMetadata: IStepMetadata): void { if (resetTo === ExportJarStep.ResolveJavaProject) { stepMetadata.workspaceFolder = undefined; stepMetadata.projectList = undefined; - stepMetadata.mainMethod = undefined; - } else if (resetTo === ExportJarStep.ResolveMainMethod) { - stepMetadata.mainMethod = undefined; + stepMetadata.mainClass = undefined; + } else if (resetTo === ExportJarStep.ResolveMainClass) { + stepMetadata.mainClass = undefined; } } @@ -81,3 +93,23 @@ export function successMessage(outputFileName: string) { } }); } + +export function toPosixPath(inputPath: string): string { + return inputPath.split(win32.sep).join(posix.sep); +} + +export function toWinPath(inputPath: string): string { + return inputPath.split(posix.sep).join(win32.sep); +} + +export async function getExtensionApi(): Promise { + const extension: Extension | undefined = extensions.getExtension("redhat.java"); + if (extension === undefined) { + throw new Error("Language Support for Java(TM) by Red Hat isn't running, the export process will be aborted."); + } + const extensionApi: any = await extension.activate(); + if (extensionApi.getClasspaths === undefined) { + throw new Error("Export jar is not supported in the current version of language server, please check and update your Language Support for Java(TM) by Red Hat."); + } + return extensionApi; +} diff --git a/src/extension.ts b/src/extension.ts index 16718c50..1711d13e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { commands, Event, Extension, ExtensionContext, extensions, Uri } from "vscode"; +import { commands, Event, Extension, ExtensionContext, extensions, tasks, Uri } from "vscode"; import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation, instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper"; import { Commands } from "./commands"; import { Build, Context } from "./constants"; @@ -9,6 +9,7 @@ import { contextManager } from "./contextManager"; import { LibraryController } from "./controllers/libraryController"; import { ProjectController } from "./controllers/projectController"; import { init as initExpService } from "./ExperimentationService"; +import { ExportJarTaskProvider } from "./exportJarSteps/ExportJarTaskProvider"; import { Settings } from "./settings"; import { syncHandler } from "./syncHandler"; import { DependencyExplorer } from "./views/dependencyExplorer"; @@ -61,6 +62,7 @@ async function activateExtension(_operationId: string, context: ExtensionContext context.subscriptions.push(new DependencyExplorer(context)); context.subscriptions.push(contextManager); context.subscriptions.push(syncHandler); + context.subscriptions.push(tasks.registerTaskProvider(ExportJarTaskProvider.exportJarType, new ExportJarTaskProvider())); contextManager.setContextValue(Context.EXTENSION_ACTIVATED, true); contextManager.setContextValue(Context.SUPPORTED_BUILD_FILES, Build.FILE_NAMES); diff --git a/src/java/jdtls.ts b/src/java/jdtls.ts index 20ecd0de..04d22d9e 100644 --- a/src/java/jdtls.ts +++ b/src/java/jdtls.ts @@ -4,7 +4,8 @@ import { commands } from "vscode"; import { Commands, executeJavaLanguageServerCommand } from "../commands"; import { IExportResult } from "../exportJarSteps/GenerateJarExecutor"; -import { MainMethodInfo } from "../exportJarSteps/ResolveMainMethodExecutor"; +import { IClasspath } from "../exportJarSteps/IStepMetadata"; +import { IMainClassInfo } from "../exportJarSteps/ResolveMainClassExecutor"; import { INodeData } from "./nodeData"; export namespace Jdtls { @@ -24,12 +25,13 @@ export namespace Jdtls { return commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_RESOLVEPATH, params); } - export function getMainMethod(params: string): Thenable { - return commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_PROJECT_GETMAINMETHOD, params); + export function getMainClasses(params: string): Thenable { + return commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_PROJECT_GETMAINCLASSES, params); } - export function exportJar(mainMethod: string, elements: string[], destination: string): Thenable { - return commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_PROJECT_GENERATEJAR, mainMethod, elements, destination); + export function exportJar(mainClass: string, classpaths: IClasspath[], destination: string): Thenable { + return commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.JAVA_PROJECT_GENERATEJAR, + mainClass, classpaths, destination); } export enum CompileWorkspaceStatus { diff --git a/src/views/nodeCache/Trie.ts b/src/views/nodeCache/Trie.ts index e2587f9e..731a2d4b 100644 --- a/src/views/nodeCache/Trie.ts +++ b/src/views/nodeCache/Trie.ts @@ -29,7 +29,7 @@ export class Trie { currentNode.value = input; } - public find(fsPath: string): TrieNode | undefined { + public find(fsPath: string, returnEarly: boolean = false): TrieNode | undefined { let currentNode = this.root; const segments: string[] = fsPath.split(path.sep); @@ -37,6 +37,9 @@ export class Trie { if (!segment) { continue; } + if (returnEarly && currentNode.value) { + return currentNode; + } if (currentNode.children[segment]) { currentNode = currentNode.children[segment]; } else {