emacs-elpa-diffs
[Top][All Lists]
Advanced

[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)"



reply via email to

[Prev in Thread] Current Thread [Next in Thread]