nitpm: keep going if a package is already installed
[nit.git] / src / picnit.nit
index f5e28c8..8875896 100644 (file)
@@ -22,56 +22,6 @@ import curl
 
 import picnit_shared
 
-redef class Text
-
-       # Does `self` look like a package name?
-       #
-       # ~~~
-       # assert "gamnit".is_package_name
-       # assert "n1t".is_package_name
-       # assert not ".".is_package_name
-       # assert not "./gamnit".is_package_name
-       # assert not "https://github.com/nitlang/nit.git".is_package_name
-       # assert not "git://github.com/nitlang/nit".is_package_name
-       # assert not "git@gitlab.com:xymus/gamnit.git".is_package_name
-       # assert not "4it".is_package_name
-       # ~~~
-       private fun is_package_name: Bool
-       do
-               if is_empty then return false
-               if not chars.first.is_alpha then return false
-
-               for c in chars do
-                       if not (c.is_alphanumeric or c == '_') then return false
-               end
-
-               return true
-       end
-
-       # Get package name from the Git address `self`
-       #
-       # Return `null` on failure.
-       #
-       # ~~~
-       # assert "https://github.com/nitlang/nit.git".git_name == "nit"
-       # assert "git://github.com/nitlang/nit".git_name == "nit"
-       # assert "gamnit".git_name == "gamnit"
-       # assert "///".git_name == null
-       # assert "file:///".git_name == "file:"
-       # ~~~
-       private fun git_name: nullable String
-       do
-               var parts = split("/")
-               for part in parts.reverse_iterator do
-                       if not part.is_empty then
-                               return part.strip_extension(".git")
-                       end
-               end
-
-               return null
-       end
-end
-
 # Command line action, passed after `picnit`
 abstract class Command
 
@@ -105,17 +55,53 @@ class CommandInstall
        super Command
 
        redef fun name do return "install"
-       redef fun usage do return "picnit install [package or git-repository]"
-       redef fun description do return "Install a package by its name or from a git-repository"
+       redef fun usage do return "picnit install [package0[=version] [package1 ...]]"
+       redef fun description do return "Install packages by name, Git repository address or from the local package.ini"
+
+       # Packages installed in this run (identified by the full path)
+       private var installed = new Array[String]
 
        redef fun apply(args)
        do
-               if args.length != 1 then
-                       print_local_help
-                       exit 1
+               if args.not_empty then
+                       # Install each package
+                       for arg in args do
+                               # Parse each arg as an import string, with versions and commas
+                               install_packages arg
+                       end
+               else
+                       # Install packages from local package.ini
+                       var ini_path = "package.ini"
+                       if not ini_path.file_exists then
+                               print_error "Local `package.ini` not found."
+                               print_local_help
+                               exit 1
+                       end
+
+                       var ini = new ConfigTree(ini_path)
+                       var import_line = ini["package.import"]
+                       if import_line == null then
+                               print_error "The local `package.ini` declares no external dependencies."
+                               exit 0
+                               abort
+                       end
+
+                       install_packages import_line
+               end
+       end
+
+       # Install packages defined by the `import_line`
+       private fun install_packages(import_line: String)
+       do
+               var imports = import_line.parse_import
+               for name, ext_package in imports do
+                       install_package(ext_package.id, ext_package.version)
                end
+       end
 
-               var package_id = args.first
+       # Install the `package_id` at `version`
+       private fun install_package(package_id: String, version: nullable String)
+       do
                if package_id.is_package_name then
                        # Ask a centralized server
                        # TODO choose a future safe URL
@@ -138,7 +124,7 @@ class CommandInstall
 
                        assert response isa CurlFileResponseSuccess
                        if response.status_code == 404 then
-                               print_error "Package not found by the server"
+                               print_error "Package '{package_id}' not found on the server"
                                exit 1
                        else if response.status_code != 200 then
                                print_error "Server side error: {response.status_code}"
@@ -158,12 +144,12 @@ class CommandInstall
                                abort
                        end
 
-                       install_from_git(git_repo, package_id)
+                       install_from_git(git_repo, package_id, version)
                else
                        var name = package_id.git_name
                        if name != null and name != "." and not name.is_empty then
                                name = name.to_lower
-                               install_from_git(package_id, name)
+                               install_from_git(package_id, name, version)
                        else
                                print_error "Failed to infer the package name"
                                exit 1
@@ -171,30 +157,49 @@ class CommandInstall
                end
        end
 
-       private fun install_from_git(git_repo, name: String)
+       private fun install_from_git(git_repo, name: String, version: nullable String)
        do
                check_git
 
                var target_dir = picnit_lib_dir / name
-               if target_dir.file_exists then
-                       print_error "Already installed"
-                       exit 1
+               if version != null then target_dir += "=" + version
+               if installed.has(target_dir) then
+                       # Ignore packages installed in this run
+                       return
                end
+               installed.add target_dir
 
-               var cmd = "git clone {git_repo.escape_to_sh} {target_dir.escape_to_sh}"
-               if verbose then print "+ {cmd}"
+               if target_dir.file_exists then
+                       # Warn about packages previously installed,
+                       # install dependencies anyway in case of a previous error.
+                       print_error "Package '{name}' is already installed"
+               else
+                       # Actually install it
+                       var cmd_branch = ""
+                       if version != null then cmd_branch = "--branch '{version}'"
 
-               if "NIT_TESTING".environ == "true" then
-                       # Silence git output when testing
-                       cmd += " 2> /dev/null"
-               end
+                       var cmd = "git clone --depth 1 {cmd_branch} {git_repo.escape_to_sh} {target_dir.escape_to_sh}"
+                       if verbose then print "+ {cmd}"
 
-               var proc = new Process("sh", "-c", cmd)
-               proc.wait
+                       if "NIT_TESTING".environ == "true" then
+                               # Silence git output when testing
+                               cmd += " 2> /dev/null"
+                       end
 
-               if proc.status != 0 then
-                       print_error "Install failed"
-                       exit 1
+                       var proc = new Process("sh", "-c", cmd)
+                       proc.wait
+
+                       if proc.status != 0 then
+                               print_error "Install of '{name}' failed"
+                               exit 1
+                       end
+               end
+
+               # Recursive install
+               var ini = new ConfigTree(target_dir/"package.ini")
+               var import_line = ini["package.import"]
+               if import_line != null then
+                       install_packages import_line
                end
        end
 end