SCons and EiffelStudio 5.6

This is a SCons tool for building EiffelStudio 5.6 projects. It's superseded by EiffelStudioTool, which builds current versions of EiffelStudio. It has been tested on Windows XP and 2003, and on Mac OS X 10.4.7.

Put the builder in a script called Eiffel.py, in a directory that you reference with toolpath, as shown in the example below.

It requires Python 2.4 or above, because it uses the subprocess module which was new in Python 2.4.

It does not attempt to scan the Eiffel project's Ace configuration file to figure out dependencies. This means that, if you modify an Eiffel class, this tool will not know that it needs to rebuild the project. It should be fairly easy to writer a Scanner to do this, but I won't bother because EiffelStudio 5.7 has replaced Eiffel's venerable Ace files with a completely new XML-based ECF file format. So, after editing some classes, you need to run SCons with the -c option to clean the targets before doing a rebuild. Alternatively, you can explicitly build the list of dependencies yourself, using classes_in_cluster(), as in the following example.

Example Usage

   1 # This example assumes that Eiffel.py is in the same directory as the SConstruct.
   2 
   3 import os
   4 from Eiffel import classes_in_cluster
   5 env = Environment(ENV = os.environ, tools = ['Eiffel'], toolpath = ['.'])
   6 
   7 # Build a precompiled library for the Gobo clusters.
   8 gobo = Alias('gobo', env.Eiffel('gobo/precomp', 'gobo.ace'))
   9 
  10 # Build some applications that depend on the gobo precompiled library.
  11 # Each application will be built under the same directory containing its Ace file.
  12 # If finalizing, install them into "/InstallDir".
  13 for name, ace, classes in [
  14         ['app1', 'app1/app1.ace', classes_in_cluster('app1')],
  15         ['app2', 'app2/app2.ace', classes_in_cluster('app2')]
  16 ]:
  17         target = env.Eiffel(name, [ace, gobo] + classes)
  18         Default(target)
  19         Alias(name, target)
  20         if len(target) > 2: Install('/InstallDir', target[2])

Builder

   1 # EiffelStudio 5.6 support for SCons
   2 # Written by Peter Gummer, July 2006
   3 # Amended by Peter Gummer, December 2006
   4 
   5 """
   6 Tool-specific initialisation for EiffelStudio 5.6.
   7 This probably also works with earlier versions of EiffelStudio.
   8 """
   9 
  10 import os, glob, sys, shutil, datetime, subprocess, SCons
  11         
  12 log_file = None
  13 
  14 def log_open(env):
  15         global log_file
  16 
  17         if env['ECLOG'] == '':
  18                 log_file = sys.stdout
  19         elif log_file == None:
  20                 log_file = open(env['ECLOG'], 'w+')
  21         elif log_file.closed:
  22                 log_file = open(env['ECLOG'], 'a+')
  23 
  24 def log(s):
  25         log_file.write(str(s) + '\n')
  26 
  27 def log_date():
  28         log(datetime.datetime.now())
  29 
  30 def log_process(args, working_directory):
  31         commandline = subprocess.list2cmdline(args)
  32         if log_file != sys.stdout: print '  ' + commandline
  33         log(commandline)
  34         log_file.flush()
  35         return subprocess.call(args, cwd = working_directory, stdout = log_file, stderr = subprocess.STDOUT)
  36 
  37 def ec_string(target, source, env):
  38         return env['ECFLAGS'] + ' ' + os.path.basename(str(target[0]))
  39 
  40 def ec(target, source, env):
  41         """
  42         Function to be used as the Eiffel Builder's action.
  43         Build target[0] (the Eiffel project file) and target[1] (the workbench executable) from source[0] (the Ace file).
  44         Also build target[2] (the finalised executable) if specified.
  45         All compiler output is logged to a file.
  46         Note that ec's return code is unreliable: it returns 0 if C compilation fails.
  47         We return 0 (success) if target[1] (the workbench executable) is built.
  48         """
  49         result = 0
  50         epr = str(target[0])
  51         exe = str(target[1])
  52         ace = os.path.abspath(str(source[0]))
  53         project = os.path.abspath(os.path.dirname(epr))
  54 
  55         log_open(env)
  56         log('=================== ' + epr + ' ===================')
  57         log_date()
  58 
  59         shutil.rmtree(project + '/EIFGEN')
  60 
  61         command = ['ec', '-batch', '-ace', ace, '-project_path', project]
  62         if os.path.basename(epr) == 'precomp.epr': command += ['-precompile']
  63         log_process(command + env['ECFLAGS'].split() + ['-c_compile'], None)
  64 
  65         if len(target) > 2 and os.path.exists(os.path.dirname(exe)):
  66                 log('--------------------------')
  67                 log_date()
  68                 log_process(['finish_freezing', '-silent'], os.path.dirname(exe))
  69 
  70         if not os.path.exists(exe):
  71                 if log_file != sys.stdout:
  72                         if log_file.tell() > 1000:
  73                                 log_file.seek(-1000, 1)
  74                         else:
  75                                 log_file.seek(0)
  76 
  77                         print '... ' + log_file.read()
  78                         log_file.seek(0, 2)
  79 
  80                 subprocess.Popen(['estudio', '-create', project, '-ace', ace])
  81                 result = 1
  82 
  83         if log_file != sys.stdout: log_file.close()
  84         return result
  85 
  86 def ace_to_epr(target, source, env):
  87         """
  88         Function to be used as the Eiffel Builder's emitter.
  89         Parameters:
  90         1. target[0]: the name of the executable (application or dll) produced by the Eiffel project.
  91         2. target[1]: the Eiffel project directory. This parameter is optional.
  92         2. source[0]: the Ace file. If target[1] is not given, this also gives the Eiffel project directory.
  93         3. Additional source items optionally specify other dependencies, e.g. a precompiled library.
  94         The result specifies the target and source that will be passed to ec():
  95         1. Result target[0]: the given project's .epr file, with a directory part.
  96         2. Result target[1]: the workbench executable target to be built ("driver" if precompiling).
  97         3. Result target[2]: if finalising and not precompiling, the finalised executable target.
  98         4. Result source[0]: the given Ace file.
  99         5. Additional given source items, if any.
 100         Note: the "driver" executable does not exist for .NET precompiled libraries, so it probably doesn't work.
 101         """
 102         result = None, source
 103         exe = str(target[0])
 104         epr = os.path.splitext(exe)[0] + '.epr'
 105 
 106         if len(source) == 0:
 107                 print 'No source .ace file specified: cannot build ' + exe
 108         elif not env.Detect('ec'):
 109                 print 'Please add "ec" to your path: cannot build ' + exe
 110         else:
 111                 is_precompiling = epr == 'precomp.epr'
 112                 if is_precompiling: exe = env['ISE_C_COMPILER'] + '/driver' + env['PROGSUFFIX']
 113 
 114                 project_dir = os.path.dirname(str(source[0]))
 115                 if len(target) > 1: project_dir = str(target[1])
 116 
 117                 epr = project_dir + '/' + epr
 118                 exe = project_dir + '/EIFGEN/?_code/' + exe
 119                 result = [epr, exe.replace('?', 'W')]
 120 
 121                 if '-finalize' in env['ECFLAGS'] and not is_precompiling:
 122                         result += [exe.replace('?', 'F')]
 123 
 124                 result = result, source
 125 
 126         return result
 127 
 128 def c_compiler(env):
 129         """
 130         The given Environment's ISE_C_COMPILER variable, if set.
 131         The Microsoft compiler is the default because ISE_C_COMPILER is normally set on all platforms but Windows.
 132         """
 133         if env['ENV'].has_key('ISE_C_COMPILER'):
 134                 return env['ENV']['ISE_C_COMPILER']
 135         else:
 136                 return 'msc'
 137 
 138 def generate(env):
 139         """Add a Builder and options for Eiffel to the given Environment."""
 140         opts = SCons.Script.Options()
 141         opts.Add('ECFLAGS', '"-freeze" for a workbench build.', '-finalize')
 142         opts.Add('ECLOG', 'File to log Eiffel compiler output.', 'SCons.Eiffel.log')
 143         opts.Add('ISE_C_COMPILER', 'msc = Microsoft, bcc = Borland, etc.', c_compiler(env))
 144         opts.Update(env)
 145         SCons.Script.Help(opts.GenerateHelpText(env))
 146 
 147         ec_action = SCons.Script.Action(ec, ec_string)
 148         env['BUILDERS']['Eiffel'] = SCons.Script.Builder(action = ec_action, emitter = ace_to_epr, suffix = env['PROGSUFFIX'])
 149 
 150 def exists(env):
 151         """Is the Eiffel compiler available?"""
 152         return env.Detect('ec')
 153 
 154 # Utility functions.
 155 
 156 def files(pattern):
 157         """All files matching a pattern, excluding directories."""
 158         return [file for file in glob.glob(pattern) if os.path.isfile(file)]
 159 
 160 def classes_in_cluster(cluster):
 161         """All Eiffel class files in the given cluster and its subclusters."""
 162         result = []
 163 
 164         for root, dirnames, filenames in os.walk(cluster):
 165                 if '.svn' in dirnames: dirnames.remove('.svn')
 166                 result += files(root + '/*.e')
 167 
 168         return result

EiffelStudio56Tool (last edited 2008-05-28 23:07:42 by PeterGummer)