[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] scratch/javaimp-gradle edc0e55: packages/javaimp: Support gradle
From: |
Filipp Gunbin |
Subject: |
[elpa] scratch/javaimp-gradle edc0e55: packages/javaimp: Support gradle (wip) |
Date: |
Wed, 23 Oct 2019 22:10:28 -0400 (EDT) |
branch: scratch/javaimp-gradle
commit edc0e55b4292985fa0ceaf47dc767fbb524f6e8d
Author: Filipp Gunbin <address@hidden>
Commit: Filipp Gunbin <address@hidden>
packages/javaimp: Support gradle (wip)
---
packages/javaimp/javaimp-gradle.el | 135 ++++++++++++++
packages/javaimp/javaimp-maven.el | 170 +++++++++++++++++
packages/javaimp/javaimp-tests.el | 14 +-
packages/javaimp/javaimp-util.el | 131 +++++++++++++
packages/javaimp/javaimp.el | 366 +++++++------------------------------
5 files changed, 505 insertions(+), 311 deletions(-)
diff --git a/packages/javaimp/javaimp-gradle.el
b/packages/javaimp/javaimp-gradle.el
new file mode 100644
index 0000000..2bf8e8e
--- /dev/null
+++ b/packages/javaimp/javaimp-gradle.el
@@ -0,0 +1,135 @@
+;;; 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'.")
+
+(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* ((init-file (make-temp-file "javaimp" nil ".kts"
+ javaimp--gradle-init-file-contents))
+ (modules
+ (javaimp--call-build-tool javaimp-gradle-program
+ #'javaimp--gradle-handler
+ "-q"
+ "-b" (javaimp-cygpath-convert-maybe file)
+ "-I" (javaimp-cygpath-convert-maybe
init-file)
+ "javaimpTask")))
+ (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 (modules alist pair sym val)
+ (while (not (eobp))
+ (setq pair (split-string (thing-at-point 'line) "="))
+ (unless (= (length pair) 2)
+ (error "Invalid pair from gradle output: %s" pair))
+ (setq sym (intern (car pair))
+ val (cadr pair))
+ (when (and (eq sym 'id) alist) ;start of next module
+ (push (javaimp--gradle-module-from-alist alist) modules)
+ (setq alist nil))
+ (push (cons sym val) alist)
+ (forward-line 1))
+ (when alist ;last module
+ (push (javaimp--gradle-module-from-alist alist) modules))
+ modules))
+
+(defun javaimp--gradle-module-from-alist (alist)
+ (make-javaimp-module
+ :id (javaimp--gradle-id-from-colon-separated (cadr (assq 'id alist)))
+ :parent-id (javaimp--gradle-id-from-colon-separated (cadr (assq 'parent-id
alist)))
+ :file (cadr (assq 'file alist))
+ :final-name (cadr (assq 'final-name alist))
+ :packaging "jar" ;TODO
+ :source-dir (file-name-as-directory
+ (javaimp-cygpath-convert-maybe
+ (cadr (assq 'source-dir alist))))
+ :test-source-dir (file-name-as-directory
+ (javaimp-cygpath-convert-maybe
+ (cadr (assq 'test-source-dir alist))))
+ :build-dir (file-name-as-directory
+ (javaimp-cygpath-convert-maybe
+ (cadr (assq 'build-dir alist))))
+ :dep-jars (javaimp--split-native-path (cadr (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 ":")))
+ (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 (file)
+ (let ((init-file (make-temp-file "javaimp" nil ".kts"
+
javaimp--gradle-init-file-contents-dep-jars-only)))
+ (javaimp--call-build-tool javaimp-gradle-program
+ (lambda ()
+ ;; expect just a single line
+ (thing-at-point 'line))
+ "-q"
+ "-p" (javaimp-cygpath-convert-maybe file)
+ "-I" (javaimp-cygpath-convert-maybe init-file)
+ "javaimpTask")))
+
+
+(defconst javaimp--gradle-init-file-contents
+ "allprojects {
+ tasks.register(\"javaimpTask\") {
+ 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}\")
+ println(\"final-name=${project.archivesBaseName}\")
+
println(\"source-dir=${sourceSets.main.java.sourceDirectories.asPath}\")
+
println(\"test-source-dir=${sourceSets.test.java.sourceDirectories.asPath}\")
+ println(\"build-dir=${project.buildDir}\")
+ println(\"dep-jars=${configurations.testCompileClasspath.asPath}\")
+ }
+ }
+}")
+
+(defconst javaimp--gradle-init-file-contents-dep-jars-only
+ "allprojects {
+ tasks.register(\"javaimpTask\") {
+ doLast {
+ println(\"${configurations.testCompileClasspath.asPath}\")
+ }
+ }
+}")
+
+(provide 'javaimp-gradle)
diff --git a/packages/javaimp/javaimp-maven.el
b/packages/javaimp/javaimp-maven.el
new file mode 100644
index 0000000..ee63acb
--- /dev/null
+++ b/packages/javaimp/javaimp-maven.el
@@ -0,0 +1,170 @@
+;;; 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'.")
+
+
+(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 #'javaimp--maven-module-from-xml 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)
+ (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
+ :final-name (javaimp--xml-first-child
+ (javaimp--xml-child 'finalName build-elt))
+ :packaging (javaimp--xml-first-child
+ (javaimp--xml-child 'packaging elt))
+ :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)
+ :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 (file)
+ (javaimp--call-build-tool javaimp-mvn-program
+ (lambda ()
+ (goto-char (point-min))
+ (search-forward "Dependencies classpath:")
+ (forward-line 1)
+ (thing-at-point 'line))
+ "-f" (javaimp-cygpath-convert-maybe file)
+ "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..67e9786
--- /dev/null
+++ b/packages/javaimp/javaimp-util.el
@@ -0,0 +1,131 @@
+;;; 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)
+
+(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'.")
+
+
+(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\\'"))))
+
+
+;; 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 (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 %s\" on args: %s" program target 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
+ `(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 "Build tool target \"%s\" failed with status \"%s\"" target
status))
+ (goto-char (point-min))
+ (funcall handler))))
+
+(defun javaimp--split-native-path (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..29e3f2a 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,21 +38,22 @@
;;
;; 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)
;;
@@ -66,32 +67,25 @@
;; (local-set-key "\C-ci" 'javaimp-add-import)
;; (local-set-key "\C-co" 'javaimp-organize-imports)))
;;
-;;
-;; 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-util)
+(require 'javaimp-maven)
+(require 'javaimp-gradle)
+
;; User options
+;; TODO add :type for defcustoms
+
(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))
@@ -131,15 +125,6 @@ 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'.")
-
(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'.")
@@ -174,7 +159,8 @@ to the completion alternatives list.")
packaging
source-dir test-source-dir build-dir
dep-jars
- load-ts)
+ load-ts
+ dep-jars-path-fetcher)
(cl-defstruct javaimp-id
group artifact version)
@@ -182,262 +168,46 @@ to the completion alternatives list.")
(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 / 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))
+ ((file-readable-p (setq build-file (concat exp-dir
"build.gradle.kts")))
+ (javaimp--gradle-visit build-file))
+ (error "Could not find build file in dir %s" dir))))
+ (when tree
+ ;; delete previous loaded tree, if any
+ (setq javaimp-project-forest
+ (seq-remove (lambda (tree)
+ (equal (javaimp-module-file (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))))
+;; Dep jars
-
-
-;; 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)))))
-
-
-;;; 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
+ ;; check if any build-file up to the top one has changed
(let ((tmp node))
(while (and tmp
(not need-update))
@@ -445,32 +215,18 @@ the temporary buffer and returns its result"
(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))
+ (message "Reloading dependencies for %s (build-file changed)"
+ (javaimp-module-id checked))
(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)
+ (javaimp-module-file 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 +315,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,
@@ -575,7 +333,7 @@ classes in the current module."
(or (string-prefix-p (javaimp-module-source-dir m) file)
(string-prefix-p (javaimp-module-test-source-dir m)
file))))
(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: "
@@ -694,7 +452,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)"