nitg: Extract exec_and_check from Android
[nit.git] / src / toolcontext.nit
index e78ec77..f313063 100644 (file)
 # limitations under the License.
 
 # Common command-line tool infractructure than handle options and error messages
-package toolcontext
+module toolcontext
 
 import opts
 import location
+import version
 
 class Message
        super Comparable
        redef type OTHER: Message
 
-       readable var _location: nullable Location
-       readable var _text: String
+       var location: nullable Location
+       var text: String
 
+       # Comparisons are made on message locations.
        redef fun <(other: OTHER): Bool do
                if location == null then return true
                if other.location == null then return false
@@ -69,24 +71,24 @@ end
 # Global context for tools
 class ToolContext
        # Number of errors
-       readable var _error_count: Int = 0
+       var error_count: Int = 0
 
        # Number of warnings
-       readable var _warning_count: Int = 0
+       var warning_count: Int = 0
 
        # Directory where to generate log files
-       readable var _log_directory: String = "logs"
+       var log_directory: String = "logs"
 
        # Messages
-       var _messages: Array[Message] = new Array[Message]
-       var _message_sorter: ComparableSorter[Message] = new ComparableSorter[Message]
+       private var messages: Array[Message] = new Array[Message]
+       private var message_sorter: ComparableSorter[Message] = new ComparableSorter[Message]
 
        fun check_errors
        do
-               if _messages.length > 0 then
-                       _message_sorter.sort(_messages)
+               if messages.length > 0 then
+                       message_sorter.sort(messages)
 
-                       for m in _messages do
+                       for m in messages do
                                if opt_no_color.value then
                                        stderr.write("{m}\n")
                                else
@@ -94,7 +96,7 @@ class ToolContext
                                end
                        end
 
-                       _messages.clear
+                       messages.clear
                end
 
                if error_count > 0 then exit(1)
@@ -103,8 +105,8 @@ class ToolContext
        # Display an error
        fun error(l: nullable Location, s: String)
        do
-               _messages.add(new Message(l,s))
-               _error_count = _error_count + 1
+               messages.add(new Message(l,s))
+               error_count = error_count + 1
                if opt_stop_on_first_error.value then check_errors
        end
 
@@ -118,9 +120,9 @@ class ToolContext
        # Display a warning
        fun warning(l: nullable Location, s: String)
        do
-               if _opt_warn.value == 0 then return
-               _messages.add(new Message(l,s))
-               _warning_count = _warning_count + 1
+               if opt_warn.value == 0 then return
+               messages.add(new Message(l,s))
+               warning_count = warning_count + 1
                if opt_stop_on_first_error.value then check_errors
        end
 
@@ -131,62 +133,160 @@ class ToolContext
                        print "{s}"
                end
        end
+       
+       fun exec_and_check(args: Array[String], error: String)
+        do
+                var prog = args.first
+                args.remove_at 0
+
+                # Is the wanted program available?
+                var proc_which = new IProcess.from_a("which", [prog])
+                proc_which.wait
+                var res = proc_which.status
+                if res != 0 then
+                        print "{error}: executable \"{prog}\" not found"
+                        exit 1
+                end
+
+                # Execute the wanted program
+                var proc = new Process.from_a(prog, args)
+                proc.wait
+                res = proc.status
+                if res != 0 then
+                        print "{error}: execution of \"{prog} {args.join(" ")}\" failed"
+                        exit 1
+                end
+        end
 
        # Global OptionContext
-       readable var _option_context: OptionContext = new OptionContext
+       var option_context: OptionContext = new OptionContext
 
        # Option --warn
-       readable var _opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
+       var opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
 
        # Option --quiet
-       readable var _opt_quiet: OptionBool = new OptionBool("Do not show warnings", "-q", "--quiet")
+       var opt_quiet: OptionBool = new OptionBool("Do not show warnings", "-q", "--quiet")
 
        # Option --log
-       readable var _opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
+       var opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
 
        # Option --log-dir
-       readable var _opt_log_dir: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
+       var opt_log_dir: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
 
        # Option --help
-       readable var _opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
+       var opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
 
        # Option --version
-       readable var _opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
+       var opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
 
        # Option --verbose
-       readable var _opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
+       var opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
 
        # Option --stop-on-first-error
-       readable var _opt_stop_on_first_error: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
+       var opt_stop_on_first_error: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
 
        # Option --no-color
-       readable var _opt_no_color: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
+       var opt_no_color: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
 
        # Verbose level
-       readable var _verbose_level: Int = 0
+       var verbose_level: Int = 0
 
        init
        do
                option_context.add_option(opt_warn, opt_quiet, opt_stop_on_first_error, opt_no_color, opt_log, opt_log_dir, opt_help, opt_version, opt_verbose)
        end
 
+       # Name, usage and synopsis of the tool.
+       # It is mainly used in `usage`.
+       # Should be correctly set by the client before calling `process_options`
+       # A multi-line string is recommmended.
+       #
+       # eg. `"Usage: tool [OPTION]... [FILE]...\nDo some things."`
+       var tooldescription: String writable = "Usage: [OPTION]... [ARG]..."
+
+       # Does `process_options` should accept an empty sequence of arguments.
+       # ie. nothing except options.
+       # Is `false` by default.
+       #
+       # If required, if should be set by the client before calling `process_options`
+       var accept_no_arguments writable = false
+
+       # print the full usage of the tool.
+       # Is called by `process_option` on `--help`.
+       # It also could be called by the client.
+       fun usage
+       do
+               print tooldescription
+               option_context.usage
+       end
+
        # Parse and process the options given on the command line
-       fun process_options
+       fun process_options(args: Sequence[String])
        do
                self.opt_warn.value = 1
 
                # init options
                option_context.parse(args)
 
+               if opt_help.value then
+                       usage
+                       exit 0
+               end
+
+               if opt_version.value then
+                       print nit_version
+                       exit 0
+               end
+
+               var errors = option_context.get_errors
+               if not errors.is_empty then
+                       for e in errors do print "Error: {e}"
+                       print tooldescription
+                       print "Use --help for help"
+                       exit 1
+               end
+
+               if option_context.rest.is_empty and not accept_no_arguments then
+                       print tooldescription
+                       print "Use --help for help"
+                       exit 1
+               end
+
                # Set verbose level
-               _verbose_level = opt_verbose.value
+               verbose_level = opt_verbose.value
 
                if self.opt_quiet.value then self.opt_warn.value = 0
 
-               if opt_log_dir.value != null then _log_directory = opt_log_dir.value.as(not null)
-               if _opt_log.value then
+               if opt_log_dir.value != null then log_directory = opt_log_dir.value.as(not null)
+               if opt_log.value then
                        # Make sure the output directory exists
                        log_directory.mkdir
                end
+
+               nit_dir = compute_nit_dir
+       end
+
+       # The identified root directory of the Nit project
+       var nit_dir: nullable String
+
+       private fun compute_nit_dir: nullable String
+       do
+               # a environ variable has precedence
+               var res = "NIT_DIR".environ
+               if not res.is_empty then return res
+
+               # find the runpath of the program from argv[0]
+               res = "{sys.program_name.dirname}/.."
+               if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
+
+               # find the runpath of the process from /proc
+               var exe = "/proc/self/exe"
+               if exe.file_exists then
+                       res = exe.realpath
+                       res = res.dirname.join_path("..")
+                       if res.file_exists and "{res}/src/nit.nit".file_exists then return res.simplify_path
+               end
+
+               return null
        end
 end