nitpackage: generate and check Makefiles
authorAlexandre Terrasa <alexandre@moz-code.org>
Tue, 8 May 2018 15:30:08 +0000 (11:30 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Tue, 15 May 2018 15:53:32 +0000 (11:53 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

share/man/nitpackage.md
src/nitpackage.nit

index f49bf26..2ec886b 100644 (file)
@@ -54,6 +54,12 @@ Generate package.ini files.
 ### `--check-ini`
 Check package.ini files.
 
+### `--gen-makefile`
+Generate Makefile files.
+
+### `--check-makefile`
+Check Makefile files.
+
 ### `-f`, `--force`
 Force update of existing files.
 
index a2c2666..794c69f 100644 (file)
@@ -16,6 +16,7 @@
 module nitpackage
 
 import frontend
+import doc::commands::commands_main
 
 redef class ToolContext
        # --expand
@@ -30,6 +31,12 @@ redef class ToolContext
        # --force
        var opt_force = new OptionBool("Force update of existing files", "-f", "--force")
 
+       # --check-makefile
+       var opt_check_makefile = new OptionBool("Check Makefile files", "--check-makefile")
+
+       # --gen-makefile
+       var opt_gen_makefile = new OptionBool("Generate Makefile files", "--gen-makefile")
+
        # nitpackage phase
        var nitpackage_phase: Phase = new NitPackagePhase(self, null)
 
@@ -37,6 +44,7 @@ redef class ToolContext
                super
                option_context.add_option(opt_expand, opt_force)
                option_context.add_option(opt_check_ini, opt_gen_ini)
+               option_context.add_option(opt_check_makefile, opt_gen_makefile)
        end
 end
 
@@ -60,6 +68,12 @@ private class NitPackagePhase
                                continue
                        end
 
+                       # Check package Makefiles
+                       if toolcontext.opt_check_makefile.value then
+                               mpackage.check_makefile(toolcontext, mainmodule)
+                               continue
+                       end
+
                        # Expand packages
                        if toolcontext.opt_expand.value and not mpackage.is_expanded then
                                var path = mpackage.expand
@@ -78,6 +92,16 @@ private class NitPackagePhase
                                        toolcontext.info("generated INI file `{path}`", 0)
                                end
                        end
+
+                       # Create Makefile
+                       if toolcontext.opt_gen_makefile.value then
+                               if not mpackage.has_makefile or toolcontext.opt_force.value then
+                                       var path = mpackage.gen_makefile(toolcontext.modelbuilder.model, mainmodule)
+                                       if path != null then
+                                               toolcontext.info("generated Makefile `{path}`", 0)
+                                       end
+                               end
+                       end
                end
        end
 
@@ -253,6 +277,94 @@ redef class MPackage
                ini.save
                return ini_path
        end
+
+       # Makefile
+
+       # The path to `self` Makefile
+       fun makefile_path: nullable String do
+               var path = package_path
+               if path == null then return null
+               if not is_expanded then return null
+               return path / "Makefile"
+       end
+
+       # Does `self` have a Makefile?
+       fun has_makefile: Bool do
+               var makefile_path = self.makefile_path
+               if makefile_path == null then return false
+               return makefile_path.file_exists
+       end
+
+       private fun check_makefile(toolcontext: ToolContext, mainmodule: MModule) do
+               var model = toolcontext.modelbuilder.model
+               var filter = new ModelFilter(accept_example = false, accept_test = false)
+               var view = new ModelView(model, mainmodule, filter)
+
+               var cmd_bin = new CmdMains(view, mentity = self)
+               var res_bin = cmd_bin.init_command
+               if not res_bin isa CmdSuccess then return
+
+               for mmodule in cmd_bin.results.as(not null) do
+                       if not mmodule isa MModule then continue
+
+                       if mmodule.makefile_path == null then
+                               toolcontext.warning(location, "missing-makefile",
+                                       "Warning: no Makefile for executable module `{mmodule.full_name}`")
+                       end
+               end
+       end
+
+       private fun gen_makefile(model: Model, mainmodule: MModule): nullable String do
+               var filter = new ModelFilter(accept_example = false, accept_test = false)
+               var view = new ModelView(model, mainmodule, filter)
+
+               var pkg_path = package_path.as(not null)
+               var makefile_path = makefile_path.as(not null)
+
+               var bins = new Array[String]
+               var cmd_bin = new CmdMains(view, mentity = self)
+               var res_bin = cmd_bin.init_command
+               if res_bin isa CmdSuccess then
+                       for mmodule in cmd_bin.results.as(not null) do
+                               if not mmodule isa MModule then continue
+                               var mmodule_makefile = mmodule.makefile_path
+                               if mmodule_makefile != null and mmodule_makefile != makefile_path then continue
+
+                               var file = mmodule.location.file
+                               if file == null then continue
+                               # Remove package path prefix
+                               var bin_path = file.filename
+                               if pkg_path.has_suffix("/") then
+                                       bin_path = bin_path.replace(pkg_path, "")
+                               else
+                                       bin_path = bin_path.replace("{pkg_path}/", "")
+                               end
+                               bins.add bin_path
+                       end
+               end
+
+               if  bins.is_empty then return null
+
+               var make = new NitMakefile(bins)
+               make.render.write_to_file(makefile_path)
+               return makefile_path
+       end
+end
+
+redef class MModule
+       private fun makefile_path: nullable String do
+               var file = location.file
+               if file == null then return null
+
+               var dir = file.filename.dirname
+               var makefile = (dir / "Makefile")
+               if not makefile.file_exists then return null
+
+               for line in makefile.to_path.read_lines do
+                       if line.has_prefix("{name}:") then return makefile
+               end
+               return null
+       end
 end
 
 redef class ConfigTree
@@ -287,6 +399,115 @@ redef class ConfigTree
        end
 end
 
+# A Makefile for the Nit project
+class NitMakefile
+
+       # Nit files to compile
+       var nit_files: Array[String]
+
+       # List of rules to add in the Makefile
+       fun rules: Array[MakeRule] do
+               var rules = new Array[MakeRule]
+
+               var rule_all = new MakeRule("all", is_phony = true)
+               rules.add rule_all
+
+               for file in nit_files do
+                       var bin = file.basename.strip_extension
+
+                       rule_all.deps.add "bin/{bin}"
+
+                       var rule = new MakeRule("bin/{bin}")
+                       rule.deps.add "$(shell $(NITLS) -M {file})"
+                       rule.lines.add "mkdir -p bin/"
+                       rule.lines.add "$(NITC) {file} -o bin/{bin}"
+                       rules.add rule
+               end
+
+               var rule_check = new MakeRule("check", is_phony = true)
+               rule_check.lines.add "$(NITUNIT) ."
+               rules.add rule_check
+
+               var rule_doc = new MakeRule("doc", is_phony = true)
+               rule_doc.lines.add "$(NITDOC) . -o doc/"
+               rules.add rule_doc
+
+               var rule_clean = new MakeRule("clean", is_phony = true)
+               if nit_files.not_empty then
+                       rule_clean.lines.add "rm -rf bin/"
+               end
+               rule_clean.lines.add "rm -rf doc/"
+               rules.add rule_clean
+
+               return rules
+       end
+
+       # Render `self`
+       fun render: Writable do
+               var tpl = new Template
+               tpl.addn """
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.\n"""
+
+               if nit_files.not_empty then
+                       tpl.addn "NITC ?= nitc"
+                       tpl.addn "NITLS ?= nitls"
+               end
+               tpl.addn "NITUNIT ?= nitunit"
+               tpl.addn "NITDOC ?= nitdoc"
+
+               for rule in rules do
+                       tpl.add "\n{rule.render.write_to_string}"
+               end
+
+               return tpl
+       end
+end
+
+# A rule that goes into a Makefile
+class MakeRule
+
+       # Rule name
+       var name: String
+
+       # Is this rule a `.PHONY` one?
+       var is_phony: Bool = false is optional
+
+       # Rule dependencies
+       var deps = new Array[String]
+
+       # Rule lines
+       var lines = new Array[String]
+
+       # Render `self`
+       fun render: Writable do
+               var tpl = new Template
+               if is_phony then
+                       tpl.addn ".PHONY: {name}"
+               end
+               tpl.add "{name}:"
+               if deps.not_empty then
+                       tpl.add " {deps.join(" ")}"
+               end
+               tpl.add "\n"
+               for line in lines do
+                       tpl.addn "\t{line}"
+               end
+               return tpl
+       end
+end
+
 # build toolcontext
 var toolcontext = new ToolContext
 var tpl = new Template