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
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
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}"
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
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