Re: Small script to enable call to Makefiles from GPS.

Emmanuel Briot <[email protected]> Wed, 19 Jul 2006 16:18:07 +0200
Newsgroups gmane.comp.ide.gps.user
Message-ID <[email protected]>
> This small script will enable calling to makefiles from the menus in GPS.
> To enable just copy the file to a GPS plug-in directory.

Based on this nice idea from Per Sandberg, we have added a small example in 
the development version of GPS (future 4.1 release), which enhances a little 
this script, and provides a nice access to Makefiles.This example works with 
older versions of GPS as well, so here it is.

It would be nice if you have similar scripts that you would like to 
contribute. I'll try to organize them, and we'll see how to make them 
available to more people.

regards
Emmanuel

_______________________________________________
gps-users mailing list
[email protected]
http://lists.adacore.com/mailman/listinfo/gps-users
Makefile.py (application/x-python, 6.9 KB)
"""
   This script provides support for building through Makefiles in GPS.
   It defines the following new project attributes, in the "make" package of
   a .gpr file. These can of course be edited graphically through the project
   properties editor.
       "make.make": Name of the make command to run. It defaults to "make",
                    although on some systems it should be "gmake".
       "make.makefile": name of the Makefile to use. This can be an absolute
                    name, or a name relative to the directory containing the
                    root project file (ie the one loaded in GPS). This
                    attribute is optional. If unspecified, GPS will look for
                    either "Makefile" or "makefile" in the directory
                    containing the root project
       "make.switches": list of extra switches to pass the Makefile. By
                    default, "-k" is used.
   GPS will systematically set the Makefile variables corresponding with the
   scenario variables (see the menu /Tools/Views/Scenario). For instance, this
   will result in calling
       make -f Makefile VARIABLE1=VALUE target
   These scenario variables are defined in the project file, and should have
   the same name as in the Makefile.
      
   Any time a project is loaded or reloaded, GPS will update the /Build/Make
   menu to add entries corresponding to the targets available in the Makefile.
   You can conveniently set key shortcuts for these entries through the menu
      /Edit/Key shortcuts
   By default, the name in the menu will be the name of the Makefile target.
   However, if your makefile contains lines similar to:
      target: dependency1 dependency2 # menu name
   then the part after the '#' sign will be used as the menu name

   When you select one of the new menus, GPS will run make, and parse error
   messages to display them in the locations window as usual.
"""

from GPS import *
from os.path import *
import re
import os

class Console_Process (Console, Process):
   locations_category = "make results"

   def on_output (self, unmatched, matched):
      """Called when new output is available from make"""
      self.write (unmatched + matched)
      Locations.parse \
        (output   = unmatched + matched,
         category = Console_Process.locations_category)

   def on_input (self, input):
      """Called when the user has sent input in the console"""
      self.send (input)

   def on_destroy (self):
      """If the console is destroyed, we kill the make process as well"""
      self.kill ()

   def __init__ (self, process, args=""):
      Locations.remove_category (Console_Process.locations_category)
      Console.__init__ (self, "",
                        on_input=Console_Process.on_input,
                        on_destroy=Console_Process.on_destroy,
                        force=True)
      self.write (process + " " + args + "\n")
      Process.__init__ (self, process + ' ' + args, "^.+$",
                        on_match=Console_Process.on_output)

class Makefile:
   def compute_makefile (self):
      """Return the Makefile to use. By default, we look in the project itself.
         If none is specified there, we default on the Makefile or makefile found
         in the same directory as the root project"""
      root_dir = dirname (Project.root().file().name())
      self.makefile = Project.root().get_attribute_as_string ("makefile","make")
      if not isfile (self.makefile):
         self.makefile = join (root_dir, self.makefile)
      if not isfile (self.makefile):
         self.makefile = join (root_dir, "Makefile")
      if not isfile (self.makefile):
         self.makefile = join (root_dir, "makefile")
      if not isfile (self.makefile):
         self.makefile = None

   def spawn (self, target):
      project  = Project.root()
      switches = project.get_attribute_as_string ("switches", "make")
      make     = project.get_attribute_as_string ("make",     "make")

      # Change the directory instead of using -C, so that we are compatible
      # with any version of make, not only GNU make
      os.chdir (dirname (project.file().name()))

      args = switches + " " \
         + " -f " + self.makefile + " " + target \
         + project.scenario_variables_cmd_line ("")
      return Console_Process (make, args) 

   def destroy_menus (self):
      """Destroy all menus associated with the Makefile"""
      for m in self.menus:
         m.destroy()

   def edit_makefile (self, menu):
      """Open an editor to edit the current Makefile"""
      EditorBuffer.get (File (self.makefile))

   def on_build_target (self, menu):
      self.spawn (menu.target)

   def on_project_view_changed (self, hook):
      """Called when the project view has changed, and thus we should
         reparse the Makefile"""
      try:
         self.destroy_menus()
         if self.makefile:
            self.menus.append \
               (Menu.create \
                  ("/Build/Make/Edit Makefile",
                   on_activate=self.edit_makefile))
             
            matcher=re.compile ("^(.+?):[^#]*(#(.+))?")
            f = file (self.makefile)
            for line in f:
               matches=matcher.match (line)
               if matches:
                   if matches.group (3):
                      m = Menu.create \
                        ("/Build/Make/" + matches.group (3).strip(),
                         on_activate = self.on_build_target)
                      m.target = matches.group (1)
                      self.menus.append (m)
                   else:
                      ## Handle multiple targets on same line
                      for target in matches.group (1).split():
                        m = Menu.create \
                         ("/Build/Make/make: " + target.strip(),
                         on_activate = self.on_build_target)
                        m.target = target
                        self.menus.append (m)
            
            f.close()
      except:
         pass

   def on_project_changed (self, hook):
      self.compute_makefile ()

   def __init__ (self):
      self.menus = []
      self.compute_makefile ()
      parse_xml("""
        <project_attribute
          name="makefile"
          package="Make"
          editor_section="Make"
          description="Makefile to use for this project">
          <string type="file"/>
        </project_attribute>
        <project_attribute
          name="Make"
          package="Make"
          editor_section="Make"
          description="Make command to use when parsing Makefile">
          <string type="" default="make"/>
       </project_attribute>
       <project_attribute
          name="Switches"
          package="Make"
          editor_section="Make"
          description="Switches for the make command">
          <string type="" default="-k"/>
       </project_attribute>""")

      Hook ("project_view_changed").add (self.on_project_view_changed)
      Hook ("project_changed").add (self.on_project_changed)

Makefile()