[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] master 1bc7436: [javaimp]: Support gradle, split into multiple fi
From: |
Filipp Gunbin |
Subject: |
[elpa] master 1bc7436: [javaimp]: Support gradle, split into multiple files |
Date: |
Fri, 15 Nov 2019 13:21:52 -0500 (EST) |
branch: master
commit 1bc7436e9c525c7ee8fcbe5d953241d338f6d1e0
Author: Filipp Gunbin <address@hidden>
Commit: Filipp Gunbin <address@hidden>
[javaimp]: Support gradle, split into multiple files
* packages/javaimp/javaimp.el: Move maven-specific functions to
javaimp-maven.el. Move common functions to javaimp-util.el
* packages/javaimp/javaimp-maven.el: New file.
* packages/javaimp/javaimp-gradle.el: New file.
* packages/javaimp/javaimp-util.el: New file.
* packages/javaimp/gradleTaskBody.inc.kts: New file.
---
packages/javaimp/gradleTaskBody.inc.kts | 29 ++
packages/javaimp/javaimp-gradle.el | 149 ++++++++++
packages/javaimp/javaimp-maven.el | 181 ++++++++++++
packages/javaimp/javaimp-tests.el | 14 +-
packages/javaimp/javaimp-util.el | 166 +++++++++++
packages/javaimp/javaimp.el | 480 ++++++++------------------------
6 files changed, 647 insertions(+), 372 deletions(-)
diff --git a/packages/javaimp/gradleTaskBody.inc.kts
b/packages/javaimp/gradleTaskBody.inc.kts
new file mode 100644
index 0000000..697e743
--- /dev/null
+++ b/packages/javaimp/gradleTaskBody.inc.kts
@@ -0,0 +1,29 @@
+// use syntax which is valid both in Groovy and in Kotlin
+doLast {
+ println("id=${project.group}:${project.name}:${project.version}")
+ if (project.parent != null) {
+
println("parent-id=${project.parent.group}:${project.parent.name}:${project.parent.version}")
+ }
+ println("file=${project.buildFile}")
+ if (project.hasProperty("archivesBaseName")) { // defined by java plugin
+ println("final-name=" +
project.configurations.getByName("archives").artifacts.stream()
+ .filter { it.type.equals("jar") || it.type.equals("war") }
+ .map { it.file.path }
+ .findFirst()
+ .orElse(""))
+ } else {
+ println("final-name=")
+ }
+ println("build-dir=${project.buildDir}")
+ if (project.hasProperty("sourceSets")) { // defined by java plugin
+ println("source-dirs=" + project.sourceSets.stream()
+ .flatMap { it.allJava.srcDirs.stream().map { it.path } }
+ .collect(Collectors.joining(File.pathSeparator)))
+ println("dep-jars=" + project.sourceSets.stream()
+ .flatMap { it.compileClasspath.files.stream().filter {
it.name.endsWith("jar") }.map { it.path } }
+ .collect(Collectors.joining(File.pathSeparator)))
+ } else {
+ println("source-dirs=")
+ println("dep-jars=")
+ }
+}
diff --git a/packages/javaimp/javaimp-gradle.el
b/packages/javaimp/javaimp-gradle.el
new file mode 100644
index 0000000..77c7dae
--- /dev/null
+++ b/packages/javaimp/javaimp-gradle.el
@@ -0,0 +1,149 @@
+;;; javaimp-gradle.el --- javaimp gradle support -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Free Software Foundation, Inc.
+
+;; Author: Filipp Gunbin <address@hidden>
+;; Maintainer: Filipp Gunbin <address@hidden>
+;; Version: 0.6.1
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+(require 'javaimp-util)
+
+(defcustom javaimp-gradle-program "gradle"
+ "Path to the `gradle' program. Customize it if the program is
+not on `exec-path'. If the visited project's directory contains
+gradlew program, it is used in preference."
+ :group 'javaimp
+ :type 'string)
+
+(defconst javaimp--gradle-task-body
+ (with-temp-buffer
+ (insert-file-contents (expand-file-name "gradleTaskBody.inc.kts"
javaimp--basedir))
+ (buffer-string))
+ "Task body, uses syntax which can be used both in Groovy and Kotlin")
+
+(defun javaimp--gradle-visit (file)
+ "Calls gradle on FILE to get various project information.
+
+Passes specially crafted init file as -I argument to gradle and
+invokes task contained in it. This task returns all needed
+information."
+ (message "Visiting Gradle build file %s..." file)
+ (let* ((alists (javaimp--gradle-call file
+ javaimp--gradle-task-body
+ #'javaimp--gradle-handler
+ "javaimpTask"))
+ (modules (mapcar (lambda (alist)
+ (javaimp--gradle-module-from-alist alist file))
+ alists)))
+ (prog1
+ ;; first module is always root
+ (javaimp--build-tree (car modules) nil modules)
+ (message "Loaded tree for %s" file))))
+
+(defun javaimp--gradle-handler ()
+ (goto-char (point-min))
+ (let (alist res sym val)
+ (while (re-search-forward "^\\([[:alnum:]-]+\\)=\\(.*\\)$" nil t)
+ (setq sym (intern (match-string 1))
+ val (match-string 2))
+ (if (string-blank-p val)
+ (setq val nil))
+ (when (and (eq sym 'id) alist) ;start of next module
+ (push alist res)
+ (setq alist nil))
+ (push (cons sym val) alist))
+ (when alist ;last module
+ (push alist res))
+ (nreverse res)))
+
+(defun javaimp--gradle-module-from-alist (alist file-orig)
+ (make-javaimp-module
+ :id (javaimp--gradle-id-from-colon-separated (cdr (assq 'id alist)))
+ :parent-id (javaimp--gradle-id-from-colon-separated (cdr (assq 'parent-id
alist)))
+ :file (cdr (assq 'file alist))
+ :file-orig file-orig
+ ;; jar/war supported
+ :final-name (let ((final-name (javaimp-cygpath-convert-maybe
+ (cdr (assq 'final-name alist)))))
+ (and final-name
+ (member (file-name-extension final-name) '("jar" "war"))
+ final-name))
+ :source-dirs (mapcar #'file-name-as-directory
+ (javaimp--split-native-path
+ (cdr (assq 'source-dirs alist))))
+ :build-dir (file-name-as-directory
+ (javaimp-cygpath-convert-maybe
+ (cdr (assq 'build-dir alist))))
+ :dep-jars (javaimp--split-native-path (cdr (assq 'dep-jars alist)))
+ :load-ts (current-time)
+ :dep-jars-path-fetcher #'javaimp--gradle-fetch-dep-jars-path))
+
+(defun javaimp--gradle-id-from-colon-separated (str)
+ (when str
+ (let ((parts (split-string str ":" t)))
+ (unless (= (length parts) 3)
+ (error "Invalid maven id: %s" str))
+ (make-javaimp-id :group (nth 0 parts) :artifact (nth 1 parts) :version
(nth 2 parts)))))
+
+(defun javaimp--gradle-fetch-dep-jars-path (module)
+ ;; always invoke on root file becase module's file may not exist
+ ;; (even if reported as project.buildFile property)
+ (javaimp--gradle-call (javaimp-module-file-orig module)
+ javaimp--gradle-task-body
+ (lambda ()
+ (re-search-forward "^dep-jars=\\(.*\\)$")
+ (match-string 1))
+ (concat (if (javaimp-module-parent-id module)
+ (concat ":" (javaimp-id-artifact
(javaimp-module-id module))))
+ ":javaimpTask")))
+
+(defun javaimp--gradle-call (file init-script-body handler task)
+ (let* ((is-kotlin (equal (file-name-extension file) "kts"))
+ (init-file (make-temp-file "javaimp" nil
+ (if is-kotlin ".kts")
+ (if is-kotlin
+ (javaimp--gradle-init-script-kotlin
init-script-body)
+ (javaimp--gradle-init-script
init-script-body))))
+ (local-gradlew (concat (file-name-directory file) "gradlew")))
+ (javaimp--call-build-tool (if (file-exists-p local-gradlew)
+ local-gradlew
+ javaimp-gradle-program)
+ handler
+ "-q"
+ "-b" (javaimp-cygpath-convert-maybe file)
+ "-I" (javaimp-cygpath-convert-maybe init-file)
+ task)))
+
+
+(defun javaimp--gradle-init-script (body)
+ (concat "
+import java.io.File
+import java.util.stream.Collectors
+allprojects {
+ task javaimpTask {"
+ body
+ "} }"))
+
+(defun javaimp--gradle-init-script-kotlin (body)
+ (concat "
+import java.io.File
+import java.util.stream.Collectors
+allprojects {
+ tasks.register(\"javaimpTask\") {"
+ body
+ "} }"))
+
+(provide 'javaimp-gradle)
diff --git a/packages/javaimp/javaimp-maven.el
b/packages/javaimp/javaimp-maven.el
new file mode 100644
index 0000000..b523381
--- /dev/null
+++ b/packages/javaimp/javaimp-maven.el
@@ -0,0 +1,181 @@
+;;; javaimp-maven.el --- javaimp maven support -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Free Software Foundation, Inc.
+
+;; Author: Filipp Gunbin <address@hidden>
+;; Maintainer: Filipp Gunbin <address@hidden>
+;; Version: 0.6.1
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+;;; Code:
+
+(require 'javaimp-util)
+
+(defcustom javaimp-mvn-program "mvn"
+ "Path to the `mvn' program. Customize it if the program is not
+on `exec-path'."
+ :group 'javaimp
+ :type 'string)
+
+
+(defun javaimp--maven-visit (file)
+ "Calls `mvn help:effective-pom' on FILE,
+reads project structure from the output and records which files
+belong to which modules and other module information"
+ (message "Visiting Maven POM file %s..." file)
+ (let* ((xml-tree (javaimp--call-build-tool javaimp-mvn-program
+
#'javaimp--maven-effective-pom-handler
+ "-f"
(javaimp-cygpath-convert-maybe file)
+ "help:effective-pom"))
+ (projects (javaimp--maven-projects-from-xml xml-tree))
+ (modules (mapcar (lambda (proj-elt)
+ (javaimp--maven-module-from-xml proj-elt file))
+ projects))
+ ;; first module is always root
+ (tree (javaimp--build-tree (car modules) nil modules)))
+ (when tree
+ ;; Set files in a separate step after building the tree because "real"
+ ;; parent of a child (given by <parent>) does not necessary contains the
+ ;; child in its <modules>. This is rare, but happens.
+ (javaimp--maven-fill-modules-files file tree)
+ ;; check that no :file slot is empty
+ (let ((modules-without-files
+ (mapcar #'javaimp-node-contents
+ (javaimp--select-nodes-from-tree
+ tree (lambda (m)
+ (null (javaimp-module-file m)))))))
+ (if modules-without-files
+ (error "Cannot find file for module(s): %s"
+ (mapconcat #'javaimp-module-id modules-without-files
", "))))
+ tree)))
+
+(defun javaimp--maven-effective-pom-handler ()
+ (let ((start
+ (save-excursion
+ (progn
+ (goto-char (point-min))
+ (re-search-forward "<\\?xml\\|<projects?")
+ (match-beginning 0))))
+ (end
+ (save-excursion
+ (progn
+ (goto-char (point-min))
+ (re-search-forward "<\\(projects?\\)")
+ ;; corresponding close tag is the end of parse region
+ (search-forward (concat "</" (match-string 1) ">"))
+ (match-end 0)))))
+ (xml-parse-region start end)))
+
+(defun javaimp--maven-projects-from-xml (tree)
+ "Analyzes result of `mvn help:effective-pom' and returns list
+of <project> elements"
+ (let ((project (assq 'project tree))
+ (projects (assq 'projects tree)))
+ (cond (project
+ (list project))
+ (projects
+ (javaimp--xml-children projects 'project))
+ (t
+ (error "Neither <project> nor <projects> was found in pom")))))
+
+(defun javaimp--maven-module-from-xml (elt file-orig)
+ (let ((build-elt (javaimp--xml-child 'build elt)))
+ (make-javaimp-module
+ :id (javaimp--maven-id-from-xml elt)
+ :parent-id (javaimp--maven-id-from-xml (javaimp--xml-child 'parent elt))
+ ;; <project> element does not contain pom file path, so we set this slot
+ ;; later, see javaimp--maven-fill-modules-files
+ :file nil
+ :file-orig file-orig
+ ;; jar/war supported
+ :final-name (let ((packaging (or (javaimp--xml-first-child
+ (javaimp--xml-child 'packaging
elt))
+ "jar")))
+ (when (member packaging '("jar" "war"))
+ (concat (javaimp--xml-first-child
+ (javaimp--xml-child 'finalName build-elt))
+ "." packaging)))
+ :source-dirs (list (file-name-as-directory
+ (javaimp-cygpath-convert-maybe
+ (javaimp--xml-first-child
+ (javaimp--xml-child 'sourceDirectory
build-elt))))
+ (file-name-as-directory
+ (javaimp-cygpath-convert-maybe
+ (javaimp--xml-first-child
+ (javaimp--xml-child 'testSourceDirectory
build-elt)))))
+ :build-dir (file-name-as-directory
+ (javaimp-cygpath-convert-maybe
+ (javaimp--xml-first-child (javaimp--xml-child
'directory build-elt))))
+ :dep-jars nil ; dep-jars is initialized lazily on demand
+ :load-ts (current-time)
+ :dep-jars-path-fetcher #'javaimp--maven-fetch-dep-jars-path)))
+
+(defun javaimp--maven-id-from-xml (elt)
+ (make-javaimp-id
+ :group (javaimp--xml-first-child (javaimp--xml-child 'groupId elt))
+ :artifact (javaimp--xml-first-child (javaimp--xml-child 'artifactId elt))
+ :version (javaimp--xml-first-child (javaimp--xml-child 'version elt))))
+
+(defun javaimp--maven-fill-modules-files (file tree)
+ ;; Reads module id from FILE, looks up corresponding module in TREE, sets its
+ ;; :file slot, then recurses for each submodule. A submodule file path is
+ ;; constructed by appending relative path taken from <module> to FILE's
+ ;; directory.
+ (let* ((xml-tree (with-temp-buffer
+ (insert-file-contents file)
+ (xml-parse-region (point-min) (point-max))))
+ (project-elt (assq 'project xml-tree))
+ (this-id (javaimp--maven-id-from-xml project-elt))
+ ;; seems that the only mandatory component in tested ids is
artifact, while
+ ;; group and version may be inherited and thus not presented in
pom.xml
+ (id-pred (if (or (null (javaimp-id-group this-id))
+ (null (javaimp-id-version this-id)))
+ (progn
+ (message "File %s contains incomplete id,
will check artifact only" file)
+ (lambda (tested-id)
+ (equal (javaimp-id-artifact this-id)
+ (javaimp-id-artifact
tested-id))))
+ (lambda (tested-id)
+ (equal this-id tested-id))))
+ (module
+ (javaimp-node-contents
+ (or (javaimp--find-node-in-tree
+ tree (lambda (m)
+ (funcall id-pred (javaimp-module-id m))))
+ (error "Cannot find module for id %s (taken from file %s)"
this-id file)))))
+ (setf (javaimp-module-file module) file)
+ (let ((rel-paths
+ (mapcar #'javaimp--xml-first-child
+ (javaimp--xml-children (javaimp--xml-child 'modules
project-elt) 'module))))
+ (dolist (rel-path rel-paths)
+ (javaimp--maven-fill-modules-files (concat (file-name-directory
file)
+
(file-name-as-directory rel-path)
+
"pom.xml")
+ tree)))))
+
+(defun javaimp--maven-fetch-dep-jars-path (module)
+ (javaimp--call-build-tool javaimp-mvn-program
+ (lambda ()
+ (goto-char (point-min))
+ (search-forward "Dependencies classpath:")
+ (forward-line 1)
+ (thing-at-point 'line))
+ ;; always invoke for this module's pom.ml
+ "-f" (javaimp-cygpath-convert-maybe
+ (javaimp-module-file module))
+ "dependency:build-classpath"))
+
+(provide 'javaimp-maven)
diff --git a/packages/javaimp/javaimp-tests.el
b/packages/javaimp/javaimp-tests.el
index cd8acb2..0dbe86f 100644
--- a/packages/javaimp/javaimp-tests.el
+++ b/packages/javaimp/javaimp-tests.el
@@ -1,23 +1,23 @@
-;;; javaimp-tests.el --- javaimp module tests -*- lexical-binding: t; -*-
+;;; javaimp-tests.el --- javaimp tests -*- lexical-binding: t; -*-
-;; Copyright (C) 2016 Free Software Foundation, Inc.
+;; Copyright (C) 2016-2019 Free Software Foundation, Inc.
;; Author: Filipp Gunbin <address@hidden>
;; Maintainer: Filipp Gunbin <address@hidden>
(require 'ert)
-(require 'javaimp)
+(require 'javaimp-maven)
-(ert-deftest javaimp-test--maven-xml-extract-projects--project ()
+(ert-deftest javaimp-test--maven-projects-from-xml--project ()
(with-temp-buffer
(insert "<project/>")
- (let ((projects (javaimp--maven-xml-extract-projects
+ (let ((projects (javaimp--maven-projects-from-xml
(xml-parse-region (point-min) (point-max)))))
(should (eql (length projects) 1)))))
-(ert-deftest javaimp-test--maven-xml-extract-projects--projects ()
+(ert-deftest javaimp-test--maven-projects-from-xml--projects ()
(with-temp-buffer
(insert "<projects><project/><project/></projects>")
- (let ((projects (javaimp--maven-xml-extract-projects
+ (let ((projects (javaimp--maven-projects-from-xml
(xml-parse-region (point-min) (point-max)))))
(should (eql (length projects) 2)))))
diff --git a/packages/javaimp/javaimp-util.el b/packages/javaimp/javaimp-util.el
new file mode 100644
index 0000000..2284ddd
--- /dev/null
+++ b/packages/javaimp/javaimp-util.el
@@ -0,0 +1,166 @@
+;;; javaimp-util.el --- javaimp util -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Free Software Foundation, Inc.
+
+;; Author: Filipp Gunbin <address@hidden>
+;; Maintainer: Filipp Gunbin <address@hidden>
+;; Version: 0.6.1
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+;;; Code:
+
+(require 'xml)
+(require 'cl-lib)
+(require 'seq)
+
+(defcustom javaimp-cygpath-program
+ (if (eq system-type 'cygwin) "cygpath")
+ "Path to the `cygpath' program (Cygwin only). Customize it if
+the program is not on `exec-path'."
+ :group 'javaimp
+ :type 'string)
+
+(defconst javaimp-debug-buf-name "*javaimp-debug*")
+
+(defconst javaimp--basedir (file-name-directory load-file-name))
+
+;; Structs
+
+(cl-defstruct javaimp-node
+ parent children contents)
+
+(cl-defstruct javaimp-module
+ id parent-id
+ file
+ file-orig
+ final-name ;may be relative (to build-dir)
+ source-dirs build-dir
+ dep-jars
+ load-ts
+ dep-jars-path-fetcher)
+
+(cl-defstruct javaimp-id
+ group artifact version)
+
+(cl-defstruct javaimp-cached-jar
+ file read-ts classes)
+
+
+(defun javaimp--xml-children (xml-tree child-name)
+ "Returns list of children of XML-TREE filtered by CHILD-NAME"
+ (seq-filter (lambda (child)
+ (and (consp child)
+ (eq (car child) child-name)))
+ (cddr xml-tree)))
+
+(defun javaimp--xml-child (name el)
+ "Returns a child of EL named by symbol NAME"
+ (assq name (cddr el)))
+
+(defun javaimp--xml-first-child (el)
+ "Returns a first child of EL"
+ (car (cddr el)))
+
+
+(defun javaimp--get-file-ts (file)
+ (nth 5 (file-attributes file)))
+
+(defun javaimp-print-id (id)
+ (format "%s:%s:%s"
+ (javaimp-id-artifact id)
+ (javaimp-id-group id)
+ (javaimp-id-version id)))
+
+(defun javaimp--get-jdk-jars ()
+ (and javaimp-java-home
+ (file-accessible-directory-p javaimp-java-home)
+ (let ((lib-dir
+ (concat (file-name-as-directory javaimp-java-home)
+ (file-name-as-directory "jre")
+ (file-name-as-directory "lib"))))
+ (directory-files lib-dir t "\\.jar\\'"))))
+
+
+;; TODO use functions `cygwin-convert-file-name-from-windows' and
+;; `cygwin-convert-file-name-to-windows' when they are available
+;; instead of calling `cygpath'. See
+;; https://cygwin.com/ml/cygwin/2013-03/msg00228.html
+
+(defun javaimp-cygpath-convert-maybe (path &optional mode is-really-path)
+ "On Cygwin, converts PATH using cygpath according to MODE and
+IS-REALLY-PATH. If MODE is `unix' (the default), adds -u switch.
+If MODE is `windows', adds -m switch. If `is-really-path' is
+non-nil, adds `-p' switch. On other systems, PATH is returned
+unchanged."
+ (if (and path (eq system-type 'cygwin))
+ (progn
+ (unless mode (setq mode 'unix))
+ (let (args)
+ (push (cond ((eq mode 'unix) "-u")
+ ((eq mode 'windows) "-m")
+ (t (error "Invalid mode: %s" mode)))
+ args)
+ (and is-really-path (push "-p" args))
+ (push path args)
+ (car (apply #'process-lines javaimp-cygpath-program args))))
+ path))
+
+
+(defun javaimp--call-build-tool (program handler &rest args)
+ "Runs PROGRAM with ARGS, then calls HANDLER in the temporary
+buffer and returns its result"
+ (message "Calling %s on args: %s" program args)
+ (with-temp-buffer
+ (let ((status (let ((coding-system-for-read
+ (if (eq system-type 'cygwin) 'utf-8-dos)))
+ ;; TODO check
in output on Gnu/Linux
+ (apply #'process-file program nil t nil args)))
+ (buf (current-buffer)))
+ (with-current-buffer (get-buffer-create javaimp-debug-buf-name)
+ (erase-buffer)
+ (insert-buffer-substring buf))
+ (or (and (numberp status) (= status 0))
+ (error "\"%s\" failed with status \"%s\"" program status))
+ (goto-char (point-min))
+ (funcall handler))))
+
+(defun javaimp--split-native-path (path)
+ (when path
+ (let ((converted (javaimp-cygpath-convert-maybe path 'unix t))
+ (sep-regex (concat "[" path-separator "\n" "]+")))
+ (split-string converted sep-regex t))))
+
+(defun javaimp--build-tree (this parent-node all)
+ (message "Building tree for module: %s" (javaimp-module-id this))
+ (let ((children
+ ;; more or less reliable way to find children is to look for
+ ;; modules with "this" as the parent
+ (seq-filter (lambda (m)
+ (equal (javaimp-module-parent-id m) (javaimp-module-id
this)))
+ all)))
+ (let* ((this-node (make-javaimp-node
+ :parent parent-node
+ :children nil
+ :contents this))
+ ;; recursively build child nodes
+ (child-nodes
+ (mapcar (lambda (child)
+ (javaimp--build-tree child this-node all))
+ children)))
+ (setf (javaimp-node-children this-node) child-nodes)
+ this-node)))
+
+(provide 'javaimp-util)
diff --git a/packages/javaimp/javaimp.el b/packages/javaimp/javaimp.el
index 8a3f4f3..b24b243 100644
--- a/packages/javaimp/javaimp.el
+++ b/packages/javaimp/javaimp.el
@@ -1,11 +1,11 @@
-;;; javaimp.el --- Add and reorder Java import statements in Maven projects
-*- lexical-binding: t; -*-
+;;; javaimp.el --- Add and reorder Java import statements in Maven/Gradle
projects -*- lexical-binding: t; -*-
-;; Copyright (C) 2014-2018 Free Software Foundation, Inc.
+;; Copyright (C) 2014-2019 Free Software Foundation, Inc.
;; Author: Filipp Gunbin <address@hidden>
;; Maintainer: Filipp Gunbin <address@hidden>
;; Version: 0.6.1
-;; Keywords: java, maven, programming
+;; Keywords: java, maven, gradle, programming
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
@@ -22,13 +22,13 @@
;;; Commentary:
-;; Allows to manage Java import statements in Maven projects.
+;; Allows to manage Java import statements in Maven/Gradle projects.
;;
;; Quick start:
;;
;; - customize `javaimp-import-group-alist'
-;; - call `javaimp-maven-visit-project', giving it the top-level project
-;; directory where pom.xml resides
+;; - call `javaimp-visit-project', giving it the top-level project
+;; directory where pom.xml / build.gradle[.kts] resides
;;
;; Then in a Java buffer visiting a file under that project or one of its
;; submodules call `javaimp-organize-imports' or `javaimp-add-import'.
@@ -38,60 +38,47 @@
;;
;; Some details:
;;
-;; If Maven failed, you can see its output in the buffer named by
-;; `javaimp-debug-buf-name' (default is "*javaimp-debug*").
+;; If Maven/Gradle failed, you can see its output in the buffer named
+;; by `javaimp-debug-buf-name' (default is "*javaimp-debug*").
;;
-;; Contents of jar files and Maven project structures (pom.xml) are cached,
-;; so usually only the first command should take a considerable amount of
-;; time to complete. If a module's pom.xml or any of its parents' pom.xml
-;; (within visited tree) was modified after information was loaded, `mvn
-;; dependency:build-classpath' is re-run on the current module. If a jar
-;; file was changed, its contents are re-read.
+;; Contents of jar files and Maven/Gradle project structures are
+;; cached, so usually only the first command should take a
+;; considerable amount of time to complete. If a module's build file
+;; or any of its parents' build files (within visited tree) was
+;; modified after information was loaded, dependencies are fetched
+;; from the build tool again. If a jar file was changed, its contents
+;; are re-read.
;;
;; Currently inner classes are filtered out from completion alternatives.
;; You can always import top-level class and use qualified name.
;;
;;
-;; Example of initialization:
+;; Example:
;;
;; (require 'javaimp)
-;;
;; (add-to-list 'javaimp-import-group-alist
;; '("\\`\\(my\\.company\\.\\|my\\.company2\\.\\)" . 80))
-;;
;; (setq javaimp-additional-source-dirs '("generated-sources/thrift"))
-;;
;; (add-hook 'java-mode-hook
;; (lambda ()
;; (local-set-key "\C-ci" 'javaimp-add-import)
;; (local-set-key "\C-co" 'javaimp-organize-imports)))
+;; (global-set-key (kbd "C-c j v") 'javaimp-visit-project)
;;
-;;
-;; TODO:
-;;
-;; - use functions `cygwin-convert-file-name-from-windows' and
-;; `cygwin-convert-file-name-to-windows' when they are available instead of
-;; calling `cygpath'. See https://cygwin.com/ml/cygwin/2013-03/msg00228.html
-;;
-;; - save/restore state, on restore check if a root exists and delete it if
-;; not
-;;
-;; - `javaimp-add-import': without prefix arg narrow alternatives by local
name;
-;; with prefix arg include all classes in alternatives
-;;
-;; - :type for defcustom
+
;;; Code:
-(require 'cl-lib)
-(require 'seq)
-(require 'xml)
+(require 'javaimp-maven)
+(require 'javaimp-gradle)
+
;; User options
(defgroup javaimp ()
- "Add and reorder Java import statements in Maven projects"
+ "Add and reorder Java import statements in Maven/Gradle
+projects"
:group 'c)
(defcustom javaimp-import-group-alist '(("\\`java\\." . 10) ("\\`javax\\." .
15))
@@ -103,16 +90,22 @@ where CLASSNAME-REGEXP is a regexp matching the fully
qualified
class name. Lowest-order groups are placed earlier.
The order of classes which were not matched is defined by
-`javaimp-import-default-order'.")
+`javaimp-import-default-order'."
+ :group 'javaimp
+ :type '(alist :key-type string :value-type integer))
(defcustom javaimp-import-default-order 50
"Defines the order of classes which were not matched by
-`javaimp-import-group-alist'")
+`javaimp-import-group-alist'"
+ :group 'javaimp
+ :type 'integer)
(defcustom javaimp-java-home (getenv "JAVA_HOME")
"Path to the JDK. Directory jre/lib underneath this path is
searched for JDK libraries. By default, it is initialized from
-the JAVA_HOME environment variable.")
+the JAVA_HOME environment variable."
+ :group 'javaimp
+ :type 'string)
(defcustom javaimp-additional-source-dirs nil
"List of directories where additional (e.g. generated)
@@ -129,29 +122,26 @@ becomes \"generated-sources/<plugin_name>\" (note the
absence
of the leading slash.
Custom values set in plugin configuration in pom.xml are not
-supported yet.")
-
-(defcustom javaimp-mvn-program "mvn"
- "Path to the `mvn' program. Customize it if the program is not
-on `exec-path'.")
-
-(defcustom javaimp-cygpath-program
- (if (eq system-type 'cygwin) "cygpath")
- "Path to the `cygpath' program (Cygwin only). Customize it if
-the program is not on `exec-path'.")
+supported yet."
+ :group 'javaimp
+ :type '(repeat (string :tag "Relative directory")))
(defcustom javaimp-jar-program "jar"
"Path to the `jar' program used to read contents of jar files.
-Customize it if the program is not on `exec-path'.")
+Customize it if the program is not on `exec-path'."
+ :group 'javaimp
+ :type 'string)
(defcustom javaimp-include-current-module-classes t
"If non-nil, current module's classes are included into
completion alternatives. `javaimp-add-import' will find all java
files in the current project and add their fully-qualified names
-to the completion alternatives list.")
+to the completion alternatives list."
+ :group 'javaimp
+ :type 'boolean)
-;; Variables and constants
+;; Variables
(defvar javaimp-project-forest nil
"Visited projects")
@@ -160,317 +150,70 @@ to the completion alternatives list.")
"Alist of cached jars. Each element is of the form (FILE
. CACHED-JAR).")
-(defconst javaimp-debug-buf-name "*javaimp-debug*")
-
-;; Structs
-
-(cl-defstruct javaimp-node
- parent children contents)
-
-(cl-defstruct javaimp-module
- id parent-id
- file
- final-name
- packaging
- source-dir test-source-dir build-dir
- dep-jars
- load-ts)
-
-(cl-defstruct javaimp-id
- group artifact version)
-
-(cl-defstruct javaimp-cached-jar
- file read-ts classes)
-;; Utilities
-
-(defun javaimp--xml-children (xml-tree child-name)
- "Returns list of children of XML-TREE filtered by CHILD-NAME"
- (seq-filter (lambda (child)
- (and (consp child)
- (eq (car child) child-name)))
- (cddr xml-tree)))
-
-(defun javaimp--xml-child (name el)
- "Returns a child of EL named by symbol NAME"
- (assq name (cddr el)))
-
-(defun javaimp--xml-first-child (el)
- "Returns a first child of EL"
- (car (cddr el)))
-
-(defun javaimp--get-file-ts (file)
- (nth 5 (file-attributes file)))
-
-(defun javaimp--get-jdk-jars ()
- (and javaimp-java-home
- (file-accessible-directory-p javaimp-java-home)
- (let ((lib-dir
- (concat (file-name-as-directory javaimp-java-home)
- (file-name-as-directory "jre")
- (file-name-as-directory "lib"))))
- (directory-files lib-dir t "\\.jar\\'"))))
-
-(defun javaimp-cygpath-convert-maybe (path &optional mode is-really-path)
- "On Cygwin, converts PATH using cygpath according to MODE and
-IS-REALLY-PATH. If MODE is `unix' (the default), adds -u switch.
-If MODE is `windows', adds -m switch. If `is-really-path' is
-non-nil, adds `-p' switch. On other systems, PATH is returned
-unchanged."
- (if (eq system-type 'cygwin)
- (progn
- (unless mode (setq mode 'unix))
- (let (args)
- (push (cond ((eq mode 'unix) "-u")
- ((eq mode 'windows) "-m")
- (t (error "Invalid mode: %s" mode)))
- args)
- (and is-really-path (push "-p" args))
- (push path args)
- (car (apply #'process-lines javaimp-cygpath-program args))))
- path))
-
-
-;; Project loading
-
;;;###autoload
-(defun javaimp-maven-visit-project (path)
- "Loads a project and its submodules. PATH should point to a
-directory containing pom.xml.
-
-Calls `mvn help:effective-pom' on the pom.xml in the PATH, reads
-project structure from the output and records which files belong
-to which modules and other module information.
+(defun javaimp-visit-project (dir)
+ "Loads a project and its submodules. DIR should point to a
+directory containing pom.xml / build.gradle[.kts].
After being processed by this command, the module tree becomes
-known to javaimp and `javaimp-add-import' maybe called inside any
-module file."
- (interactive "DVisit maven project in directory: ")
- (let ((file (expand-file-name
- (concat (file-name-as-directory path) "pom.xml"))))
- (unless (file-readable-p file)
- (error "Cannot read file: %s" file))
- ;; delete previous loaded tree, if any
- (setq javaimp-project-forest
- (seq-remove (lambda (tree)
- (equal (javaimp-module-file (javaimp-node-contents
tree))
- file))
- javaimp-project-forest))
- (message "Loading file %s..." file)
- (let* ((xml-tree
- (javaimp--maven-call file "help:effective-pom"
- #'javaimp--maven-xml-effective-pom-handler))
- (projects (javaimp--maven-xml-extract-projects xml-tree))
- (modules (mapcar #'javaimp--maven-xml-parse-project projects))
- ;; first module is always root
- (tree (javaimp--maven-build-tree (car modules) nil modules)))
- (when tree
- ;; Set files in a separate step after building the tree because "real"
- ;; parent of a child (given by <parent>) does not necessary contains the
- ;; child in its <modules>. This is rare, but happens.
- (javaimp--maven-fill-modules-files file tree)
- ;; check that no :file slot is empty
- (let ((modules-without-files
- (mapcar #'javaimp-node-contents
- (javaimp--select-nodes-from-tree
- tree (lambda (m)
- (null (javaimp-module-file m)))))))
- (if modules-without-files
- (error "Cannot find file for module(s): %s"
- (mapconcat #'javaimp-module-id modules-without-files ",
"))))
- (push tree javaimp-project-forest)))
- (message "Loaded tree for %s" file)))
+known to javaimp and `javaimp-add-import' may be called inside
+any module file."
+ (interactive "DVisit Maven or Gradle project in directory: ")
+ (let* ((exp-dir (expand-file-name (file-name-as-directory dir)))
+ build-file
+ (tree (cond
+ ((file-readable-p (setq build-file (concat exp-dir "pom.xml")))
+ (javaimp--maven-visit build-file))
+ ((or (file-readable-p (setq build-file (concat exp-dir
"build.gradle")))
+ (file-readable-p (setq build-file (concat exp-dir
"build.gradle.kts"))))
+ (javaimp--gradle-visit build-file))
+ (t
+ (error "Could not find build file in dir %s" dir)))))
+ (when tree
+ ;; delete previous tree(s) loaded from this build file, if any
+ (setq javaimp-project-forest
+ (seq-remove (lambda (tree)
+ (equal (javaimp-module-file-orig
(javaimp-node-contents tree))
+ build-file))
+ javaimp-project-forest))
+ (push tree javaimp-project-forest)
+ (message "Loaded tree for %s" dir))))
-;; Maven XML routines
-
-(defun javaimp--maven-xml-effective-pom-handler ()
- (let ((start
- (save-excursion
- (progn
- (goto-char (point-min))
- (re-search-forward "<\\?xml\\|<projects?")
- (match-beginning 0))))
- (end
- (save-excursion
- (progn
- (goto-char (point-min))
- (re-search-forward "<\\(projects?\\)")
- ;; corresponding close tag is the end of parse region
- (search-forward (concat "</" (match-string 1) ">"))
- (match-end 0)))))
- (xml-parse-region start end)))
-
-(defun javaimp--maven-xml-extract-projects (xml-tree)
- "Analyzes result of `mvn help:effective-pom' and returns list
-of <project> elements"
- (let ((project (assq 'project xml-tree))
- (projects (assq 'projects xml-tree)))
- (cond (project
- (list project))
- (projects
- (javaimp--xml-children projects 'project))
- (t
- (error "Neither <project> nor <projects> was found in pom")))))
-
-(defun javaimp--maven-xml-parse-project (project)
- (let ((build-elt (javaimp--xml-child 'build project)))
- (make-javaimp-module
- :id (javaimp--maven-xml-extract-id project)
- :parent-id (javaimp--maven-xml-extract-id (javaimp--xml-child 'parent
project))
- ;; <project> element does not contain pom file path, so we set this slot
- ;; later, see javaimp--maven-fill-modules-files
- :file nil
- :final-name (javaimp--xml-first-child
- (javaimp--xml-child 'finalName build-elt))
- :packaging (javaimp--xml-first-child
- (javaimp--xml-child 'packaging project))
- :source-dir (file-name-as-directory
- (javaimp-cygpath-convert-maybe
- (javaimp--xml-first-child
- (javaimp--xml-child 'sourceDirectory build-elt))))
- :test-source-dir (file-name-as-directory
- (javaimp-cygpath-convert-maybe
- (javaimp--xml-first-child
- (javaimp--xml-child 'testSourceDirectory build-elt))))
- :build-dir (file-name-as-directory
- (javaimp-cygpath-convert-maybe
- (javaimp--xml-first-child (javaimp--xml-child 'directory
build-elt))))
- :dep-jars nil ; dep-jars is initialized lazily on demand
- :load-ts (current-time))))
-
-(defun javaimp--maven-xml-extract-id (elt)
- (make-javaimp-id
- :group (javaimp--xml-first-child (javaimp--xml-child 'groupId elt))
- :artifact (javaimp--xml-first-child (javaimp--xml-child 'artifactId elt))
- :version (javaimp--xml-first-child (javaimp--xml-child 'version elt))))
-
-
-
-;; Maven routines
-
-(defun javaimp--maven-call (pom-file target handler)
- "Runs Maven target TARGET on POM-FILE, then calls HANDLER in
-the temporary buffer and returns its result"
- (message "Calling \"mvn %s\" on pom: %s" target pom-file)
- (with-temp-buffer
- (let* ((pom-file (javaimp-cygpath-convert-maybe pom-file))
- (status
- ;; TODO check
in Maven output on Gnu/Linux
- (let ((coding-system-for-read
- (if (eq system-type 'cygwin) 'utf-8-dos)))
- (process-file javaimp-mvn-program nil t nil "-f" pom-file
target)))
- (buf (current-buffer)))
- (with-current-buffer (get-buffer-create javaimp-debug-buf-name)
- (erase-buffer)
- (insert-buffer-substring buf))
- (or (and (numberp status) (= status 0))
- (error "Maven target \"%s\" failed with status \"%s\"" target status))
- (goto-char (point-min))
- (funcall handler))))
-
-(defun javaimp--maven-build-tree (this parent-node all)
- (message "Building tree for module: %s" (javaimp-module-id this))
- (let ((children
- ;; reliable way to find children is to look for modules with "this" as
- ;; the parent
- (seq-filter (lambda (m)
- (equal (javaimp-module-parent-id m) (javaimp-module-id
this)))
- all)))
- (let* ((this-node (make-javaimp-node
- :parent parent-node
- :children nil
- :contents this))
- ;; recursively build child nodes
- (child-nodes
- (mapcar (lambda (child)
- (javaimp--maven-build-tree child this-node all))
- children)))
- (setf (javaimp-node-children this-node) child-nodes)
- this-node)))
-
-(defun javaimp--maven-fill-modules-files (file tree)
- ;; Reads module id from FILE, looks up corresponding module in TREE, sets its
- ;; :file slot, then recurses for each submodule. A submodule file path is
- ;; constructed by appending relative path taken from <module> to FILE's
- ;; directory.
- (let* ((xml-tree (with-temp-buffer
- (insert-file-contents file)
- (xml-parse-region (point-min) (point-max))))
- (project-elt (assq 'project xml-tree))
- (this-id (javaimp--maven-xml-extract-id project-elt))
- ;; seems that the only mandatory component in tested ids is artifact,
while
- ;; group and version may be inherited and thus not presented in pom.xml
- (id-pred (if (or (null (javaimp-id-group this-id))
- (null (javaimp-id-version this-id)))
- (progn
- (message "File %s contains incomplete id, will check
artifact only" file)
- (lambda (tested-id)
- (equal (javaimp-id-artifact this-id)
- (javaimp-id-artifact tested-id))))
- (lambda (tested-id)
- (equal this-id tested-id))))
- (module
- (javaimp-node-contents
- (or (javaimp--find-node-in-tree
- tree (lambda (m)
- (funcall id-pred (javaimp-module-id m))))
- (error "Cannot find module for id %s (taken from file %s)"
this-id file)))))
- (setf (javaimp-module-file module) file)
- (let ((rel-paths
- (mapcar #'javaimp--xml-first-child
- (javaimp--xml-children (javaimp--xml-child 'modules
project-elt) 'module))))
- (dolist (rel-path rel-paths)
- (javaimp--maven-fill-modules-files (concat (file-name-directory file)
- (file-name-as-directory
rel-path)
- "pom.xml")
- tree)))))
+;; Dependency jars
-
-;;; Loading dep-jars
-
-(defun javaimp--maven-update-module-maybe (node)
+(defun javaimp--update-module-maybe (node)
(let ((module (javaimp-node-contents node))
need-update)
;; check if deps are initialized
- (or (javaimp-module-dep-jars module)
- (progn (message "Loading dependencies: %s" (javaimp-module-id module))
- (setq need-update t)))
- ;; check if any pom up to the top one has changed
+ (unless (javaimp-module-dep-jars module)
+ (message "Loading dependencies: %s" (javaimp-module-id module))
+ (setq need-update t))
+ ;; check if this or any parent build file has changed since we
+ ;; loaded the module
(let ((tmp node))
- (while (and tmp
- (not need-update))
- (let ((checked (javaimp-node-contents tmp)))
- (if (> (float-time (javaimp--get-file-ts (javaimp-module-file
checked)))
- (float-time (javaimp-module-load-ts module)))
- (progn
- (message "Reloading %s (pom changed)" (javaimp-module-id
checked))
- (setq need-update t))))
+ (while (and tmp (not need-update))
+ (let ((cur (javaimp-node-contents tmp)))
+ (when (> (max (if (file-exists-p (javaimp-module-file cur))
+ (float-time (javaimp--get-file-ts
(javaimp-module-file cur)))
+ -1)
+ (if (file-exists-p (javaimp-module-file-orig cur))
+ (float-time (javaimp--get-file-ts
(javaimp-module-file-orig cur)))
+ -1))
+ (float-time (javaimp-module-load-ts module)))
+ (message "Reloading dependencies for %s (some build-file changed)"
+ (javaimp-module-id cur))
+ (setq need-update t)))
(setq tmp (javaimp-node-parent tmp))))
(when need-update
- (let* ((new-dep-jars (javaimp--maven-fetch-dep-jars module))
+ (let* ((path (funcall (javaimp-module-dep-jars-path-fetcher module)
module))
+ (new-dep-jars (javaimp--split-native-path path))
(new-load-ts (current-time)))
(setf (javaimp-module-dep-jars module) new-dep-jars)
(setf (javaimp-module-load-ts module) new-load-ts)))))
-(defun javaimp--maven-fetch-dep-jars (module)
- (let* ((path (javaimp--maven-call (javaimp-module-file module)
- "dependency:build-classpath"
- #'javaimp--maven-build-classpath-handler))
- (converted-path (javaimp-cygpath-convert-maybe path 'unix t))
- (path-separator-regex (concat "[" path-separator "\n" "]+")))
- (split-string converted-path path-separator-regex t)))
-
-(defun javaimp--maven-build-classpath-handler ()
- (goto-char (point-min))
- (search-forward "Dependencies classpath:")
- (forward-line 1)
- (thing-at-point 'line))
-
-
-;; Working with jar classes
-
(defun javaimp--get-jar-classes (file)
(let ((cached (cdr (assoc file javaimp-cached-jars))))
(cond ((null cached)
@@ -559,6 +302,8 @@ the temporary buffer and returns its result"
;;; Adding imports
+;; TODO narrow alternatives by class local name
+
;;;###autoload
(defun javaimp-add-import (classname)
"Imports classname in the current file. Interactively,
@@ -572,10 +317,11 @@ classes in the current module."
(error "Buffer is not visiting a
file!"))))
(node (or (javaimp--find-node
(lambda (m)
- (or (string-prefix-p (javaimp-module-source-dir m) file)
- (string-prefix-p (javaimp-module-test-source-dir m)
file))))
+ (seq-some (lambda (dir)
+ (string-prefix-p dir file))
+ (javaimp-module-source-dirs m))))
(error "Cannot find module by file: %s" file))))
- (javaimp--maven-update-module-maybe node)
+ (javaimp--update-module-maybe node)
(let ((module (javaimp-node-contents node)))
(list (completing-read
"Import: "
@@ -596,22 +342,18 @@ classes in the current module."
(defun javaimp--get-module-classes (module)
"Returns list of top-level classes in current module"
(append
- (let ((build-dir (javaimp-module-build-dir module)))
- ;; additional source dirs
- (and (seq-mapcat
- (lambda (rel-dir)
- (let ((dir (concat build-dir (file-name-as-directory rel-dir))))
- (and (file-accessible-directory-p dir)
- (javaimp--get-directory-classes dir nil))))
- javaimp-additional-source-dirs)))
- ;; source dir
- (let ((dir (javaimp-module-source-dir module)))
- (and (file-accessible-directory-p dir)
- (javaimp--get-directory-classes dir nil)))
- ;; test source dir
- (let ((dir (javaimp-module-test-source-dir module)))
- (and (file-accessible-directory-p dir)
- (javaimp--get-directory-classes dir nil)))))
+ ;; source dirs
+ (seq-mapcat (lambda (dir)
+ (and (file-accessible-directory-p dir)
+ (javaimp--get-directory-classes dir nil)))
+ (javaimp-module-source-dirs module))
+ ;; additional source dirs
+ (seq-mapcat (lambda (rel-dir)
+ (let ((dir (concat (javaimp-module-build-dir module)
+ (file-name-as-directory rel-dir))))
+ (and (file-accessible-directory-p dir)
+ (javaimp--get-directory-classes dir nil))))
+ javaimp-additional-source-dirs)))
(defun javaimp--get-directory-classes (dir prefix)
(append
@@ -694,7 +436,7 @@ is `ordinary' or `static'. Interactively, NEW-IMPORTS is
nil."
(string< (caar first) (caar second))
(< (cdr first) (cdr second))))))
(javaimp--insert-imports with-order)))
- (message "Nothing to organize!")))))
+ (message "Nothing to organize!")))))
(defun javaimp--parse-imports ()
"Returns (FIRST LAST . IMPORTS)"
@@ -747,6 +489,14 @@ is `ordinary' or `static'. Interactively, NEW-IMPORTS is
nil."
(insert (format pattern (caar import)) ?\n)
(setq last-order order)))))
+(defun javaimp-reset (arg)
+ "Forget loaded trees state. With prefix arg, also reset jars
+cache."
+ (interactive "P")
+ (setq javaimp-project-forest nil)
+ (when arg
+ (setq javaimp-cached-jars nil)))
+
(provide 'javaimp)
;;; javaimp.el ends here
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [elpa] master 1bc7436: [javaimp]: Support gradle, split into multiple files,
Filipp Gunbin <=