Please note:The SCons wiki is now restored from the attack in March 2013. All old passwords have been invalidated. Please reset your password if you have an account. If you note missing pages, please report them to webmaster@scons.org. Also, new account creation is currently disabled due to an ongoing spam flood (2013/08/27).
Differences between revisions 89 and 90
Revision 89 as of 2013-08-28 10:24:58
Size: 422
Editor: MarielSRK
Comment:
Revision 90 as of 2013-08-28 20:40:23
Size: 27047
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
Hello! My name is Melody. <<BR>>
It is a little about myself: I live in Netherlands, my city of Hilversum. <<BR>>
It's called often Eastern or cultural capital of NH. I've married 3 years ago.<<BR>>
I have 2 children - a son (Nan) and the daughter (Charlene). We all like People watching.<<BR>>
<<BR>>
Review my homepage ... [[http://www.gnosticmedia.com/members/sherriegu/activity/4909/|reality porno video clips]]
= SCons and Eiffel =
This is a SCons tool for building [[http://dev.eiffel.com|EiffelStudio]] projects. It works with [[http://dev.eiffel.com|EiffelStudio]] 5.7 and above (although the latest [[http://dev.eiffel.com|EiffelStudio]] is recommended, currently 7.1). It has been tested on Windows (XP, 2003 and 7), Mac OS X (10.4, 10.5, 10.6 and 10.7) and Linux (Ubuntu 7.10 and 10.04).

Put the builder in a script called ''Eiffel.py'', in a directory that you reference with ''toolpath'', as shown in the example below; or put it in a directory called ''site_scons/site_tools'' as mentioned in the SCons man page.

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

=== Sources and Targets ===

This Eiffel builder knows the nuts and bolts of running [[http://dev.eiffel.com|EiffelStudio]]'s command-line compiler. Each [[http://dev.eiffel.com|EiffelStudio]] project has a '''.ecf''' file (Eiffel configuration file) and builds an executable or a DLL. You feed the builder the path to the '''.ecf''' configuration file; this is the builder's ''source''. You can specify additional source dependencies, if you wish (resource files, icons, etc.).

The .ecf file contains one or more '''targets'''. A .ecf target specifies which executable you want to build and how to build it. You specify one of these to the builder.

The target passed to the builder is not the same as the target that the builder emits. The target passed in is a string (e.g., 'foo'), from which the builder computes the emitted full target path (e.g., 'C:\projects\app\EIFGENs\foo\F_code\foo.exe'). The builder's emitter computes the full target path by reading various settings inside the .ecf file. (For example, the {{{executable_name}}} setting gives the base name of the target file; sometimes this setting is missing, in which case the base name is the same as the .ecf document element's {{{name}}} attribute. The target file extension is also computed; for example, it is .dll if the {{{msil_generation_type}}} setting is {{{dll}}}.)

When calling the builder, you may omit the target if you wish. ''By default, the Eiffel builder builds the target with the same base name as the .ecf file.'' (There will be an error, of course, if the .ecf file has no such target.)

The builder usually emits one target file path (the executable or dll). In some cases, however, it emits more.
 * When building a .NET assembly (say, {{{Foo.exe}}} or {{{Foo.dll}}}), 'ec' also generates an unmanaged DLL ({{{libFoo.dll}}}) in the same directory. The emitter adds it as a second target.
 * When building a shared library on Windows (say, {{{foo.dll}}}), 'ec' generates a second file {{{foo.lib}}} in the same directory. The emitter adds it as a second target.
 * When building a precompiled library in classic mode (i.e., non-.NET), it emits a {{{.melted}}} file in the {{{W_code}}} directory, plus the actual precompiled library files in a compiler-specific subdirectory.
 * When C compilation is not performed, no executable or DLL will be built. Instead, the emitted target is the {{{Makefile.SH}}} file that controls C compilation. This is handy if the calling script needs to manipulate {{{Makefile.SH}}} prior to launching C compilation as a separate step. The emitter also adds {{{project.epr}}} as a target, because it is created at the end of the Eiffel compilation and is therefore a good indicator that compilation succeeded.

=== The Scanner ===

Apart from the .ecf file, the most important input to the Eiffel compiler is the universe of '''.e''' Eiffel class files that it has to compile. These class files are dependencies that the builder figures out for itself, by scanning the .ecf file. The Scanner looks for all ''cluster'' and ''override'' declarations inside the .ecf, and finds all .e files in each such directory. Therefore, thanks to the Scanner, if you modify an Eiffel class in one of the clusters named in the .ecf, or if you modify the .ecf itself, then the builder will know that it needs to rebuild the project.

If the cluster or override is recursive, then the Scanner scans the subdirectories too. If it's not recursive but has explicitly-named sub-clusters, the Scanner also understands how to scan these sub-clusters.

As well as '''.e''' class files, the scanner detects the following dependencies mentioned in the .ecf file:
 * '''.ecf libraries'''. (Such libraries are not recursively scanned, however. Although it's useful to detect that the library file itself has changed, a library is a stable thing, so scanning its contents would be a waste of time.)
 * '''.NET assemblies'''.
 * External object files.
 * '''.h''' or '''.hpp''' files inside external include directories.

Environment variable substitution is performed on the file and directory paths. The Scanner prints warnings if the .ecf file uses undefined environment variables. ''Construction variables are deliberately ignored, because it would be incorrect to substitute them given that the Eiffel compiler does not know about SCons construction variables.'' The following [[http://dev.eiffel.com|EiffelStudio]] environment variables are commonly used in .ecf files, so if they are undefined the builder tries to define them with sensible platform-specific assumptions:
 * '''ISE_C_COMPILER'''
 * '''ISE_EIFFEL'''
 * '''ISE_LIBRARY'''
 * '''ISE_PLATFORM'''
 * '''ECF_CONFIG_PATH'''

=== Choosing an Eiffel Compiler ===

The builder has a construction variable '''EC''', specifying the Eiffel command-line compiler. It defaults to the path to "ec" found in the '''PATH''' environment variable. Failing that, it uses the '''ISE_EIFFEL''' environment variable to construct the path to "ec". If this too fails, then it falls back simply to "ec". (Using '''ISE_EIFFEL''' is helpful, for example, when running SCons on Linux or Mac via '''sudo''', which restricts the '''PATH''' variable, or if the user has not added "ec" to the path.)

[[http://dev.eiffel.com|EiffelStudio]] 6.2 introduced an alternative compiler called "ecb". Its output is identical to the output of "ec", but it reportedly runs about 20% faster. Beware, however, that "ecb" uses its own set of precompiled libraries, rather than the ones installed with [[http://dev.eiffel.com|EiffelStudio]], so if you are using precompiled libraries you will have to build them specially for "ecb".

I haven't tested the builder with the Gobo Eiffel compiler [[http://www.gobosoft.com/eiffel/gobo/gec/index.html|gec]], nor with Helmut Brandl's Eiffel interpreter and compiler [[http://tecomp.sourceforge.net|tecomp]].

=== Eiffel Compiler Flags ===

The '''ECFLAGS''' construction variable controls what kind of build is done by the Eiffel compiler. Possible values for ECFLAGS are given by typing {{{ec -help}}} at the command line. Here are some common examples:
 * {{{scons ECFLAGS="-finalize -clean -c_compile"}}}: build a finalized (i.e. optimized) executable, from scratch, without assertions. ''This is the default.''
 * {{{scons ECFLAGS="-finalize -clean"}}}: build the C files for a finalized build, from scratch, without assertions, but do not build the executable.
 * {{{scons ECFLAGS="-finalize -keep -c_compile"}}}: do an incremental build of a finalized executable, retaining assertions.
 * {{{scons ECFLAGS="-precompile -clean" -c_compile}}}: build a precompiled library.
 * {{{scons ECFLAGS="-freeze -c_compile"}}}: do an incremental freeze (i.e., make a debug executable).
 * {{{scons ECFLAGS="-freeze -project_path 62 -c_compile"}}}: do an incremental freeze, in a subdirectory below the SConscript called "62", rather than the default location below the .ecf file.
 * {{{scons ECFLAGS="-freeze -target app_no_precompile -c_compile"}}}: do an incremental freeze, using the the .ecf target "app_no_precompile".

Note, in the last example, that specifying the {{{-target}}} flag overrides any other way of specifying the target.

'''ECFLAGS''' can be set by several means:
 * On the SCons command line (as shown in the examples above);
 * As a named parameter to your Eiffel builder call (as shown in the example below, which is probably the most convenient way to do it); or,
 * By setting {{{env['ECFLAGS']}}} explicitly, prior to calling the Eiffel Builder.

=== Logging ===

The Eiffel compiler 'ec' produces a lot of progress output. Generally you don't want to see this, so the Eiffel builder redirects it to a file, "SCons.Eiffel.log", in the same directory as the SConstruct. You can specify a different file by setting the '''ECLOG''' construction variable. If you want to let it to go to standard output, set '''ECLOG''' to nothing (e.g., on the command line, {{{scons ECLOG=}}}).

But you ''do'' want to see this output if the build fails! You can open the log file to inspect the reason for failure, which would normally be at the end of the log file. For convenience, however, the Eiffel builder echoes the last thousand characters from the log file onto standard output, which almost invariably proves to be just the right amount of detail to allow you to see what went wrong.

=== Example Usage ===

The following SConstruct assumes that you have created "app.ecf" with 3 targets:
 * '''gobo''' to precompile the Gobo library.
 * '''app''' to build an executable named "app" without any precompiled libraries.
 * '''app_using_precompile''' to build "app" with the Gobo precompiled library.

{{{
#!python
# Create an Environment that imports "Eiffel.py" from the same directory as the SConstruct.

import os
env = Environment(ENV = os.environ, tools = ['Eiffel'], toolpath = ['.'])

# Build "app" (or, on Windows, "app.exe").
# The build depends on "app.ecf" and "app.rc".
# It also depends on all of the "*.e" files, etc., found by the Scanner.
# (Note how the target does not need to be specified, because by default it builds the target with the same base name "app".)

env.Eiffel(['app.ecf', 'app.rc'])

# Build the precompiled Gobo library.
# (Note how we specify the "gobo" target explicitly.)

gobo = env.Eiffel('gobo', 'app.ecf', ECFLAGS = '-precompile -clean -c_compile')

# Here's how to build "app" with the Gobo precompiled library.
# (Note how it depends on "gobo", and the target is explicitly given.)

env.Eiffel('app_using_precompile', ['app.ecf', 'app.rc', gobo])

}}}
=== Builder ===
{{{
#!python
# EiffelStudio support for SCons
# Written by Peter Gummer, February 2007
# Scanner added - Peter Gummer, May 2007
# Scanner uses xml.dom - Peter Gummer, September 2007
# Fix scanning non-recursive clusters - Peter Gummer, March 2008
# Don't call finish_freezing - Peter Gummer, April 2008
# Support -target and -project_path compiler options and incremental builds - Peter Gummer, May 2008
# Scanner detects more dependencies - Peter Gummer, June 2008
# Emitter computes target by reading .ecf file - Peter Gummer, June 2008
# Support -c_compile compiler option rather than hard-coding it - Peter Gummer, December 2008
# Fix scanning on non-Windows platforms - Peter Gummer, August 2010
# Emitter computes target compatibly with EiffelStudio 6.5 and higher - Peter Gummer, August 2010
# Scanner detects "tests" dependencies to be compatible with EiffelStudio 6.7 - Peter Gummer, December 2010
# The EC variable defaults from the the PATH or ISE_EIFFEL environment variable - Peter Gummer, December 2010
# Scanner expands $ECF_CONFIG_PATH in dependency locations - Peter Gummer, May 2011
# Adjust manifest file path in Windows resource file - Peter Gummer, July 2012
# Remove the Files() method; use Glob() instead - Peter Gummer, January 2013

"""
Tool-specific initialisation for EiffelStudio.
This does not work with EiffelStudio 5.6 or earlier.
"""

import os, glob, sys, shutil, datetime, subprocess, re, xml.dom.minidom
from SCons.Script import *
 
log_file = None

def log_open(env):
 global log_file

 if env['ECLOG'] == '':
  log_file = sys.stdout
 elif log_file == None:
  log_file = open(env['ECLOG'], 'w+')
 elif log_file.closed:
  log_file = open(env['ECLOG'], 'a+')

def log(s):
 log_file.write(str(s) + '\n')

def log_date():
 log(datetime.datetime.now())

def log_process(args):
 commandline = subprocess.list2cmdline(args)
 if log_file != sys.stdout: print ' ' + commandline
 log(commandline)
 log_file.flush()
 subprocess.call(args, stdout = log_file, stderr = subprocess.STDOUT)

def log_file_tail():
 """The last thousand characters of the log file."""
 result = ''

 if log_file != sys.stdout:
  if log_file.tell() > 1000:
   log_file.seek(-1000, 1)
  else:
   log_file.seek(0)

  result = '... ' + log_file.read()
  log_file.seek(0, 2)

 return result

def ec_action(target, source, env):
 """
 The Eiffel Builder's action function, running the Eiffel compiler.
 Parameters are as returned by ec_emitter():
  * target: the paths to the files to be built, as generated by ec_emitter().
  * source[0]: the ECF file.
  * source[1], source[2], etc.: any additional dependencies.
  * env['ECLOG']: name of file to which all compiler output is logged (stdout if empty).
  * env['ECFLAGS']: Eiffel compiler flags: -finalize, -freeze, -clean, -project_path, -target, etc.
 Result is 0 (success) if all targets are built; else 1.
 (Note that the Eiffel compiler's return code is unreliable: it returns 0 if C compilation fails.)
 """
 result = 0

 log_open(env)
 log('=================== ' + ecf_target(target) + ' ===================')
 log_date()

 rc_copied_to_target = None

 if env['PLATFORM'] == 'win32':
  rc = os.path.splitext(str(source[0]))[0] + '.rc'

  if os.path.exists(rc):
   project_path = dirname(str(target[0]), 4)
   rc_copied_to_target = os.path.join(project_path, os.path.basename(rc))

   if rc == rc_copied_to_target:
    rc_copied_to_target = None
   else:
    f = open(rc, 'r')
    try: s = f.read()
    finally: f.close()

    if s:
     icon_pattern = r'(\w+[ \t]+ICON[ \t]+[^"]*")([^"]+")'
     manifest_pattern = r'(CREATEPROCESS_MANIFEST_RESOURCE_ID[ \t]+RT_MANIFEST[ \t]+")([^"]+")'
     substitution = r'\g<1>' + os.path.dirname(rc).replace('\\', '/') + r'/\g<2>'
     s = re.sub(icon_pattern, substitution, s)
     s = re.sub(manifest_pattern, substitution, s)
     f = open(rc_copied_to_target, 'w')
     try: f.write(s)
     finally: f.close()

 flags = env['ECFLAGS'].split()
 if not '-target' in flags: flags += ['-target', ecf_target(target)]
 log_process([env['EC'], '-batch', '-config', str(source[0])] + flags)

 for t in target:
  if result == 0 and not os.path.exists(str(t)):
   print log_file_tail()
   result = 1

 if rc_copied_to_target: os.remove(rc_copied_to_target)
 if log_file != sys.stdout: log_file.close()
 return result

def ec_emitter(target, source, env):
 """
 The Eiffel Builder's emitter function.
 Parameters:
  * target[0]: the ECF target to be built; if empty then defaults to the first target in the ECF file.
  * source[0]: the ECF file. The paths to the files to be built are computed by reading this file.
  * source[1], source[2], etc.: additional optional dependencies (precompiled libraries, ".rc" files, etc.).
  * env['ECFLAGS']: Eiffel compiler flags. The paths to the files to be built are affected by these.
 Result emits the target and source parameters passed to ec_action().
  * The source emitted is exactly the same as the source parameter passed in.
  * The target emitted contains one or more calculated file paths.
    Each target file is in the directory {-project_path}/EIFGENs/{-target}/{-finalize}, where:
  -project_path if omitted defaults to the current working directory;
  -target if omitted defaults to the base name of target[0] (or else to the first target in the ECF file);
  -finalize evaluates to "F_code", else if omitted defaults to "W_code".
    The number of target file paths emitted, and the actual file names, depend on several factors:
     * Options specified inside the ECF file;
     * The -precompile flag;
     * The -c_compile flag.
 """
 result = None

 if len(target) > 0:
  ec_target = os.path.basename(str(target[0]))
 else:
  ec_target = ""

 if len(source) == 0:
  print '****** ERROR! No source .ecf file specified: cannot build ' + ec_target
 elif not env.Detect(env['EC']):
  print '****** ERROR! The Eiffel compiler ' + env['EC'] + ' is missing from your path: cannot build ' + ec_target
 else:
  ecf = str(source[0])
  ec_path = os.getcwd()
  ec_code = 'W_code'
  exe_name = dotnet_type = is_dotnet = is_precompiling = is_c_compiling = is_shared_library = None

  flags = env['ECFLAGS'].split()

  for i, flag in enumerate(flags):
   if flag == '-project_path':
    ec_path = flags[i + 1]
   elif flag == '-target':
    ec_target = flags[i + 1]
   elif flag == '-finalize':
    ec_code = 'F_code'
   elif flag == '-precompile':
    is_precompiling = True
   elif flag == '-c_compile':
    is_c_compiling = True

  ecf_as_xml = xml.dom.minidom.parse(ecf)
  ec_target_next = ec_target

  while ec_target_next <> None:
   t = ec_target_next
   ec_target_next = None

   for element in ecf_as_xml.getElementsByTagName('target'):
    name = element.attributes['name'].value
    if ec_target == "": t = ec_target = name

    if t == name:
     if element.hasAttribute('extends'):
      ec_target_next = element.attributes['extends'].value

     for setting in element.getElementsByTagName('setting'):
      name = setting.attributes['name'].value

      if name == 'msil_generation':
       if is_dotnet == None:
        is_dotnet = setting.attributes['value'].value == 'true'
      elif name == 'msil_generation_type':
       if dotnet_type == None:
        dotnet_type = '.' + setting.attributes['value'].value
      elif name == 'executable_name':
       if exe_name == None:
        exe_name = setting.attributes['value'].value
      elif name == 'shared_library_definition':
       if is_shared_library == None:
        is_shared_library = True

  if exe_name == None:
   exe_name = str(ecf_as_xml.documentElement.attributes['name'].value)

  if dotnet_type:
   ext = dotnet_type
  elif is_precompiling:
   ext = '.melted'
  elif is_shared_library:
   exe_name = env['SHLIBPREFIX'] + exe_name
   ext = env['SHLIBSUFFIX']
  else:
   exe_name = env['PROGPREFIX'] + exe_name
   ext = env['PROGSUFFIX']

  ec_path += '/EIFGENs/' + ec_target + '/'

  if is_c_compiling:
   ec_path += ec_code + '/'
   result = [ec_path + exe_name + ext]

   if is_dotnet:
    result += [ec_path + 'lib' + exe_name + '.dll']
   elif is_precompiling:
    result += [ec_path + environment_variable(env, 'ISE_C_COMPILER') + '/' + env['PROGPREFIX'] + 'driver' + env['PROGSUFFIX']]
   elif is_shared_library and env['PLATFORM'] == 'win32':
    result += [ec_path + 'dll_' + exe_name + '.lib']
  else:
   result = [ec_path + ec_code + '/Makefile.SH', ec_path + 'project.epr']

 return result, source

ecf_environment_variable_regex = re.compile(r'(\$\||\$\(?\w*\)?|[^$]+)', re.M)

def ecf_scanner(node, env, path):
 """
 All dependencies mentioned in 'node', which is expected to be an ECF file.
 The dependencies consist of:
  * All Eiffel class files found in all clusters (including override clusters) mentioned in the ECF file.
  * All .ecf library files mentioned in the ECF file. (Such libraries are not themselves scanned).
  * All .NET assemblies mentioned in the ECF file.
  * All external object files mentioned in the ECF file.
  * All .h and .hpp files found in external include directories mentioned in the ECF file.
 Because this ignores targets and conditionals in the ECF file, it may cause unnecessary builds.
 """

 def element_location(element):
  """
  The 'location' attribute of 'element', processed to take care of:
   * Expansion of environment variables.
   * If 'location' is relative, prefixing with the directory name of 'node'.
   * If 'location' is a nested cluster, prefixing with the location of the parent element (recursively).
  """
  result = ''
  ecf_config_path = os.path.dirname(os.path.abspath(str(node)))

  for token in ecf_environment_variable_regex.findall(element.attributes['location'].value):
   if token[0] <> r'$':
    result += token
   elif token == r'$|':
    result += element_location(element.parentNode) + '/'
   elif token == r'$ECF_CONFIG_PATH' or token == r'$(ECF_CONFIG_PATH)':
    result += ecf_config_path
   else:
    s = environment_variable(env, token)

    if s:
     result += s
    else:
     print '****** WARNING!', str(node), 'uses undefined environment variable', token

  result = result.replace('\\', '/')

  if not os.path.isabs(result):
   result = os.path.join(ecf_config_path, result)

  return result

 result = []
 ecf_as_xml = xml.dom.minidom.parse(str(node))

 for tag in ['cluster', 'override', 'tests', 'library', 'assembly', 'external_include', 'external_object']:
  for element in ecf_as_xml.getElementsByTagName(tag):
   location = element_location(element)

   if os.path.isfile(location):
    result += [location]
   elif tag == 'external_include':
    result += env.Glob(location + '/*.h') + env.Glob(location + '/*.hpp')
   elif element.attributes.get('recursive', None):
    result += classes_in_cluster(env, location)
   else:
    result += env.Glob(location + '/*.e')

 return result

def ecf_target(target, source = None, env = None):
 """The ECF target corresponding to the given build target."""
 return os.path.basename(dirname(str(target[0]), 2))

def generate(env):
 """Add a Builder and construction variables for Eiffel to the given Environment."""
 default_ec_path = env.WhereIs('ec')

 if not default_ec_path:
  default_ec_path = spec_path(env, 'studio', 'bin/ec')
  if default_ec_path == '': default_ec_path = 'ec'

 vars = Variables()
 vars.Add('EC', "The Eiffel command-line compiler.", default_ec_path)
 vars.Add('ECFLAGS', "Use ec -help to see possible options.", '-finalize -clean -c_compile')
 vars.Add('ECLOG', "File to log Eiffel compiler output.", 'SCons.Eiffel.log')
 vars.Update(env)
 Help(vars.GenerateHelpText(env))

 env['BUILDERS']['Eiffel'] = Builder(action = Action(ec_action, ecf_target), emitter = ec_emitter, target_factory = Entry)
 env.Append(SCANNERS = Scanner(ecf_scanner, skeys = ['.ecf']))
 env.AddMethod(environment_variable, "EiffelEnvironmentVariable")
 env.AddMethod(classes_in_cluster, "EiffelClassesInCluster")
 env.AddMethod(spec_path, "EiffelSpecPath")

 for v in ['ISE_EIFFEL', 'ISE_PLATFORM', 'ISE_C_COMPILER']:
  if not environment_variable(env, v):
   print '****** WARNING! Undefined Eiffel environment variable ' + v + '.'

def exists(env):
 """Is the Eiffel compiler available?"""
 return env.Detect(env['EC'])

# Utility functions.

def environment_variable(env, var):
 """
 The value of the environment variable 'var' within 'env'.
 If undefined and it is one of the standard EiffelStudio variables, a sensible platform-specific assumption is used; else None.
 """
 result = None
 var = var.lstrip('$').lstrip('(').rstrip(')')

 if env['ENV'].has_key(var):
  result = env['ENV'][var]
 elif var == 'ISE_PLATFORM':
  if env['PLATFORM'] == 'win32':
   result = 'windows'
  elif env['PLATFORM'] == 'darwin':
   result = 'macosx-x86'
  else:
   result = 'linux-x86'
 elif var == 'ISE_C_COMPILER':
  if env['PLATFORM'] == 'win32':
   result = 'msc'
  else:
   result = 'gcc'
 elif var == 'ISE_EIFFEL':
  if env.has_key('EC'): result = env.WhereIs(env['EC'])
  if result: result = os.path.abspath(dirname(result, 5))
 elif var == 'ISE_LIBRARY':
  result = environment_variable(env, 'ISE_EIFFEL')

 return result

def classes_in_cluster(env, cluster):
 """All Eiffel class files in the given cluster and its subclusters."""
 result = []

 for root, dirnames, filenames in os.walk(cluster):
  if '.svn' in dirnames: dirnames.remove('.svn')
  result += env.Glob(root + '/*.e')

 return result

def spec_path(env, mid_part, tail):
 """
 A platform-dependent path in the EiffelStudio installation directory of the form:
 $ISE_EIFFEL + mid_part + '/spec/' + $ISE_PLATFORM + '/' + tail
 If either of these environment variables is undefined, then the result is an empty string.
 """
 result = ''
 ise_eiffel = environment_variable(env, 'ISE_EIFFEL')
 ise_platform = environment_variable(env, 'ISE_PLATFORM')

 if ise_eiffel and ise_platform:
  result = os.path.join(ise_eiffel, mid_part)
  result = os.path.join(result, 'spec')
  result = os.path.join(result, ise_platform)
  result = os.path.join(result, tail)
  result = os.path.abspath(result)

 return result

def dirname(path, n):
 """The directory name of 'path', called recursively 'n' times."""
 result = path
 if n > 0: result = dirname(os.path.dirname(path), n - 1)
 return result

}}}

=== Possible Enhancements ===

The builder should define '''ECCOM''' and '''ECCOMSTR''' to support that common idiom.

On Windows, if one of the ISE_* environment variables is not defined then [[http://dev.eiffel.com|EiffelStudio]] looks for it in the registry. The Scanner could simulate this better by likewise looking at the registry. The trouble with this approach (apart from the added complexity) is that it might easily break if [[http://dev.eiffel.com|EiffelStudio]]'s use of the registry changes in future versions.

When doing a .NET build, the Scanner prints a warning that '''$ISE_DOTNET_FRAMEWORK''' is undefined. This is the directory containing .NET framework assemblies. We would not normally expect the environment variable to be defined, so it would be good to suppress the warning, unless a reliable way can be found to guess at its value.

The Scanner scans all of the dependencies that the .ecf mentions, blindly ignoring any targets or conditions specified in the .ecf. This can cause unnecessary nodes in the dependency list.
 * Conditional scanning would require handling the various cases that [[http://dev.eiffel.com|EiffelStudio]] itself handles. This would be complicated and error-prone, so it may be best not even to try this one!
 * A dependency may be declared only for a particular target in the .ecf. There a couple of ways that target-specific scanning might be achieved:
   * One option would be passing the target to the Scanner. A few ideas for doing this:
     * Perhaps the {{{Builder()}}} keyword argument {{{target_scanner}}} could be used.
     * Perhaps the {{{Scanner()}}} keyword argument {{{path_function}}} could be abused.
     * Perhaps the emitter might add the target to {{{Node.attributes}}}, for the Scanner to retrieve.
   * Probably a cleaner option would be to remove the Scanner altogether, instead adding the dependencies to the {{{sources}}} list in the emitter. This would also be more efficient, because the .ecf file would be parsed only once, not twice.

SCons and Eiffel

This is a SCons tool for building EiffelStudio projects. It works with EiffelStudio 5.7 and above (although the latest EiffelStudio is recommended, currently 7.1). It has been tested on Windows (XP, 2003 and 7), Mac OS X (10.4, 10.5, 10.6 and 10.7) and Linux (Ubuntu 7.10 and 10.04).

Put the builder in a script called Eiffel.py, in a directory that you reference with toolpath, as shown in the example below; or put it in a directory called site_scons/site_tools as mentioned in the SCons man page.

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

Sources and Targets

This Eiffel builder knows the nuts and bolts of running EiffelStudio's command-line compiler. Each EiffelStudio project has a .ecf file (Eiffel configuration file) and builds an executable or a DLL. You feed the builder the path to the .ecf configuration file; this is the builder's source. You can specify additional source dependencies, if you wish (resource files, icons, etc.).

The .ecf file contains one or more targets. A .ecf target specifies which executable you want to build and how to build it. You specify one of these to the builder.

The target passed to the builder is not the same as the target that the builder emits. The target passed in is a string (e.g., 'foo'), from which the builder computes the emitted full target path (e.g., 'C:\projects\app\EIFGENs\foo\F_code\foo.exe'). The builder's emitter computes the full target path by reading various settings inside the .ecf file. (For example, the executable_name setting gives the base name of the target file; sometimes this setting is missing, in which case the base name is the same as the .ecf document element's name attribute. The target file extension is also computed; for example, it is .dll if the msil_generation_type setting is dll.)

When calling the builder, you may omit the target if you wish. By default, the Eiffel builder builds the target with the same base name as the .ecf file. (There will be an error, of course, if the .ecf file has no such target.)

The builder usually emits one target file path (the executable or dll). In some cases, however, it emits more.

  • When building a .NET assembly (say, Foo.exe or Foo.dll), 'ec' also generates an unmanaged DLL (libFoo.dll) in the same directory. The emitter adds it as a second target.

  • When building a shared library on Windows (say, foo.dll), 'ec' generates a second file foo.lib in the same directory. The emitter adds it as a second target.

  • When building a precompiled library in classic mode (i.e., non-.NET), it emits a .melted file in the W_code directory, plus the actual precompiled library files in a compiler-specific subdirectory.

  • When C compilation is not performed, no executable or DLL will be built. Instead, the emitted target is the Makefile.SH file that controls C compilation. This is handy if the calling script needs to manipulate Makefile.SH prior to launching C compilation as a separate step. The emitter also adds project.epr as a target, because it is created at the end of the Eiffel compilation and is therefore a good indicator that compilation succeeded.

The Scanner

Apart from the .ecf file, the most important input to the Eiffel compiler is the universe of .e Eiffel class files that it has to compile. These class files are dependencies that the builder figures out for itself, by scanning the .ecf file. The Scanner looks for all cluster and override declarations inside the .ecf, and finds all .e files in each such directory. Therefore, thanks to the Scanner, if you modify an Eiffel class in one of the clusters named in the .ecf, or if you modify the .ecf itself, then the builder will know that it needs to rebuild the project.

If the cluster or override is recursive, then the Scanner scans the subdirectories too. If it's not recursive but has explicitly-named sub-clusters, the Scanner also understands how to scan these sub-clusters.

As well as .e class files, the scanner detects the following dependencies mentioned in the .ecf file:

  • .ecf libraries. (Such libraries are not recursively scanned, however. Although it's useful to detect that the library file itself has changed, a library is a stable thing, so scanning its contents would be a waste of time.)

  • .NET assemblies.

  • External object files.
  • .h or .hpp files inside external include directories.

Environment variable substitution is performed on the file and directory paths. The Scanner prints warnings if the .ecf file uses undefined environment variables. Construction variables are deliberately ignored, because it would be incorrect to substitute them given that the Eiffel compiler does not know about SCons construction variables. The following EiffelStudio environment variables are commonly used in .ecf files, so if they are undefined the builder tries to define them with sensible platform-specific assumptions:

  • ISE_C_COMPILER

  • ISE_EIFFEL

  • ISE_LIBRARY

  • ISE_PLATFORM

  • ECF_CONFIG_PATH

Choosing an Eiffel Compiler

The builder has a construction variable EC, specifying the Eiffel command-line compiler. It defaults to the path to "ec" found in the PATH environment variable. Failing that, it uses the ISE_EIFFEL environment variable to construct the path to "ec". If this too fails, then it falls back simply to "ec". (Using ISE_EIFFEL is helpful, for example, when running SCons on Linux or Mac via sudo, which restricts the PATH variable, or if the user has not added "ec" to the path.)

EiffelStudio 6.2 introduced an alternative compiler called "ecb". Its output is identical to the output of "ec", but it reportedly runs about 20% faster. Beware, however, that "ecb" uses its own set of precompiled libraries, rather than the ones installed with EiffelStudio, so if you are using precompiled libraries you will have to build them specially for "ecb".

I haven't tested the builder with the Gobo Eiffel compiler gec, nor with Helmut Brandl's Eiffel interpreter and compiler tecomp.

Eiffel Compiler Flags

The ECFLAGS construction variable controls what kind of build is done by the Eiffel compiler. Possible values for ECFLAGS are given by typing ec -help at the command line. Here are some common examples:

  • scons ECFLAGS="-finalize -clean -c_compile": build a finalized (i.e. optimized) executable, from scratch, without assertions. This is the default.

  • scons ECFLAGS="-finalize -clean": build the C files for a finalized build, from scratch, without assertions, but do not build the executable.

  • scons ECFLAGS="-finalize -keep -c_compile": do an incremental build of a finalized executable, retaining assertions.

  • scons ECFLAGS="-precompile -clean" -c_compile: build a precompiled library.

  • scons ECFLAGS="-freeze -c_compile": do an incremental freeze (i.e., make a debug executable).

  • scons ECFLAGS="-freeze -project_path 62 -c_compile": do an incremental freeze, in a subdirectory below the SConscript called "62", rather than the default location below the .ecf file.

  • scons ECFLAGS="-freeze -target app_no_precompile -c_compile": do an incremental freeze, using the the .ecf target "app_no_precompile".

Note, in the last example, that specifying the -target flag overrides any other way of specifying the target.

ECFLAGS can be set by several means:

  • On the SCons command line (as shown in the examples above);
  • As a named parameter to your Eiffel builder call (as shown in the example below, which is probably the most convenient way to do it); or,
  • By setting env['ECFLAGS'] explicitly, prior to calling the Eiffel Builder.

Logging

The Eiffel compiler 'ec' produces a lot of progress output. Generally you don't want to see this, so the Eiffel builder redirects it to a file, "SCons.Eiffel.log", in the same directory as the SConstruct. You can specify a different file by setting the ECLOG construction variable. If you want to let it to go to standard output, set ECLOG to nothing (e.g., on the command line, scons ECLOG=).

But you do want to see this output if the build fails! You can open the log file to inspect the reason for failure, which would normally be at the end of the log file. For convenience, however, the Eiffel builder echoes the last thousand characters from the log file onto standard output, which almost invariably proves to be just the right amount of detail to allow you to see what went wrong.

Example Usage

The following SConstruct assumes that you have created "app.ecf" with 3 targets:

  • gobo to precompile the Gobo library.

  • app to build an executable named "app" without any precompiled libraries.

  • app_using_precompile to build "app" with the Gobo precompiled library.

   1 # Create an Environment that imports "Eiffel.py" from the same directory as the SConstruct.
   2 
   3 import os
   4 env = Environment(ENV = os.environ, tools = ['Eiffel'], toolpath = ['.'])
   5 
   6 # Build "app" (or, on Windows, "app.exe").
   7 # The build depends on "app.ecf" and "app.rc".
   8 # It also depends on all of the "*.e" files, etc., found by the Scanner.
   9 # (Note how the target does not need to be specified, because by default it builds the target with the same base name "app".)
  10 
  11 env.Eiffel(['app.ecf', 'app.rc'])
  12 
  13 # Build the precompiled Gobo library.
  14 # (Note how we specify the "gobo" target explicitly.)
  15 
  16 gobo = env.Eiffel('gobo', 'app.ecf', ECFLAGS = '-precompile -clean -c_compile')
  17 
  18 # Here's how to build "app" with the Gobo precompiled library.
  19 # (Note how it depends on "gobo", and the target is explicitly given.)
  20 
  21 env.Eiffel('app_using_precompile', ['app.ecf', 'app.rc', gobo])

Builder

   1 # EiffelStudio support for SCons
   2 # Written by Peter Gummer, February 2007
   3 # Scanner added - Peter Gummer, May 2007
   4 # Scanner uses xml.dom - Peter Gummer, September 2007
   5 # Fix scanning non-recursive clusters - Peter Gummer, March 2008
   6 # Don't call finish_freezing - Peter Gummer, April 2008
   7 # Support -target and -project_path compiler options and incremental builds - Peter Gummer, May 2008
   8 # Scanner detects more dependencies - Peter Gummer, June 2008
   9 # Emitter computes target by reading .ecf file - Peter Gummer, June 2008
  10 # Support -c_compile compiler option rather than hard-coding it - Peter Gummer, December 2008
  11 # Fix scanning on non-Windows platforms - Peter Gummer, August 2010
  12 # Emitter computes target compatibly with EiffelStudio 6.5 and higher - Peter Gummer, August 2010
  13 # Scanner detects "tests" dependencies to be compatible with EiffelStudio 6.7 - Peter Gummer, December 2010
  14 # The EC variable defaults from the the PATH or ISE_EIFFEL environment variable - Peter Gummer, December 2010
  15 # Scanner expands $ECF_CONFIG_PATH in dependency locations - Peter Gummer, May 2011
  16 # Adjust manifest file path in Windows resource file - Peter Gummer, July 2012
  17 # Remove the Files() method; use Glob() instead - Peter Gummer, January 2013
  18 
  19 """
  20 Tool-specific initialisation for EiffelStudio.
  21 This does not work with EiffelStudio 5.6 or earlier.
  22 """
  23 
  24 import os, glob, sys, shutil, datetime, subprocess, re, xml.dom.minidom
  25 from SCons.Script import *
  26         
  27 log_file = None
  28 
  29 def log_open(env):
  30         global log_file
  31 
  32         if env['ECLOG'] == '':
  33                 log_file = sys.stdout
  34         elif log_file == None:
  35                 log_file = open(env['ECLOG'], 'w+')
  36         elif log_file.closed:
  37                 log_file = open(env['ECLOG'], 'a+')
  38 
  39 def log(s):
  40         log_file.write(str(s) + '\n')
  41 
  42 def log_date():
  43         log(datetime.datetime.now())
  44 
  45 def log_process(args):
  46         commandline = subprocess.list2cmdline(args)
  47         if log_file != sys.stdout: print '  ' + commandline
  48         log(commandline)
  49         log_file.flush()
  50         subprocess.call(args, stdout = log_file, stderr = subprocess.STDOUT)
  51 
  52 def log_file_tail():
  53         """The last thousand characters of the log file."""
  54         result = ''
  55 
  56         if log_file != sys.stdout:
  57                 if log_file.tell() > 1000:
  58                         log_file.seek(-1000, 1)
  59                 else:
  60                         log_file.seek(0)
  61 
  62                 result = '... ' + log_file.read()
  63                 log_file.seek(0, 2)
  64 
  65         return result
  66 
  67 def ec_action(target, source, env):
  68         """
  69         The Eiffel Builder's action function, running the Eiffel compiler.
  70         Parameters are as returned by ec_emitter():
  71          * target: the paths to the files to be built, as generated by ec_emitter().
  72          * source[0]: the ECF file.
  73          * source[1], source[2], etc.: any additional dependencies.
  74          * env['ECLOG']: name of file to which all compiler output is logged (stdout if empty).
  75          * env['ECFLAGS']: Eiffel compiler flags: -finalize, -freeze, -clean, -project_path, -target, etc.
  76         Result is 0 (success) if all targets are built; else 1.
  77         (Note that the Eiffel compiler's return code is unreliable: it returns 0 if C compilation fails.)
  78         """
  79         result = 0
  80 
  81         log_open(env)
  82         log('=================== ' + ecf_target(target) + ' ===================')
  83         log_date()
  84 
  85         rc_copied_to_target = None
  86 
  87         if env['PLATFORM'] == 'win32':
  88                 rc = os.path.splitext(str(source[0]))[0] + '.rc'
  89 
  90                 if os.path.exists(rc):
  91                         project_path = dirname(str(target[0]), 4)
  92                         rc_copied_to_target = os.path.join(project_path, os.path.basename(rc))
  93 
  94                         if rc == rc_copied_to_target:
  95                                 rc_copied_to_target = None
  96                         else:
  97                                 f = open(rc, 'r')
  98                                 try: s = f.read()
  99                                 finally: f.close()
 100 
 101                                 if s:
 102                                         icon_pattern = r'(\w+[ \t]+ICON[ \t]+[^"]*")([^"]+")'
 103                                         manifest_pattern = r'(CREATEPROCESS_MANIFEST_RESOURCE_ID[ \t]+RT_MANIFEST[ \t]+")([^"]+")'
 104                                         substitution = r'\g<1>' + os.path.dirname(rc).replace('\\', '/') + r'/\g<2>'
 105                                         s = re.sub(icon_pattern, substitution, s)
 106                                         s = re.sub(manifest_pattern, substitution, s)
 107                                         f = open(rc_copied_to_target, 'w')
 108                                         try: f.write(s)
 109                                         finally: f.close()
 110 
 111         flags = env['ECFLAGS'].split()
 112         if not '-target' in flags: flags += ['-target', ecf_target(target)]
 113         log_process([env['EC'], '-batch', '-config', str(source[0])] + flags)
 114 
 115         for t in target:
 116                 if result == 0 and not os.path.exists(str(t)):
 117                         print log_file_tail()
 118                         result = 1
 119 
 120         if rc_copied_to_target: os.remove(rc_copied_to_target)
 121         if log_file != sys.stdout: log_file.close()
 122         return result
 123 
 124 def ec_emitter(target, source, env):
 125         """
 126         The Eiffel Builder's emitter function.
 127         Parameters:
 128          * target[0]: the ECF target to be built; if empty then defaults to the first target in the ECF file.
 129          * source[0]: the ECF file. The paths to the files to be built are computed by reading this file.
 130          * source[1], source[2], etc.: additional optional dependencies (precompiled libraries, ".rc" files, etc.).
 131          * env['ECFLAGS']: Eiffel compiler flags. The paths to the files to be built are affected by these.
 132         Result emits the target and source parameters passed to ec_action().
 133          * The source emitted is exactly the same as the source parameter passed in.
 134          * The target emitted contains one or more calculated file paths.
 135            Each target file is in the directory {-project_path}/EIFGENs/{-target}/{-finalize}, where:
 136                 -project_path if omitted defaults to the current working directory;
 137                 -target if omitted defaults to the base name of target[0] (or else to the first target in the ECF file);
 138                 -finalize evaluates to "F_code", else if omitted defaults to "W_code".
 139            The number of target file paths emitted, and the actual file names, depend on several factors:
 140             * Options specified inside the ECF file;
 141             * The -precompile flag;
 142             * The -c_compile flag.
 143         """
 144         result = None
 145 
 146         if len(target) > 0:
 147                 ec_target = os.path.basename(str(target[0]))
 148         else:
 149                 ec_target = ""
 150 
 151         if len(source) == 0:
 152                 print '****** ERROR! No source .ecf file specified: cannot build ' + ec_target
 153         elif not env.Detect(env['EC']):
 154                 print '****** ERROR! The Eiffel compiler ' + env['EC'] + ' is missing from your path: cannot build ' + ec_target
 155         else:
 156                 ecf = str(source[0])
 157                 ec_path = os.getcwd()
 158                 ec_code = 'W_code'
 159                 exe_name = dotnet_type = is_dotnet = is_precompiling = is_c_compiling = is_shared_library = None
 160 
 161                 flags = env['ECFLAGS'].split()
 162 
 163                 for i, flag in enumerate(flags):
 164                         if flag == '-project_path':
 165                                 ec_path = flags[i + 1]
 166                         elif flag == '-target':
 167                                 ec_target = flags[i + 1]
 168                         elif flag == '-finalize':
 169                                 ec_code = 'F_code'
 170                         elif flag == '-precompile':
 171                                 is_precompiling = True
 172                         elif flag == '-c_compile':
 173                                 is_c_compiling = True
 174 
 175                 ecf_as_xml = xml.dom.minidom.parse(ecf)
 176                 ec_target_next = ec_target
 177 
 178                 while ec_target_next <> None:
 179                         t = ec_target_next
 180                         ec_target_next = None
 181 
 182                         for element in ecf_as_xml.getElementsByTagName('target'):
 183                                 name = element.attributes['name'].value
 184                                 if ec_target == "": t = ec_target = name
 185 
 186                                 if t == name:
 187                                         if element.hasAttribute('extends'):
 188                                                 ec_target_next = element.attributes['extends'].value
 189 
 190                                         for setting in element.getElementsByTagName('setting'):
 191                                                 name = setting.attributes['name'].value
 192 
 193                                                 if name == 'msil_generation':
 194                                                         if is_dotnet == None:
 195                                                                 is_dotnet = setting.attributes['value'].value == 'true'
 196                                                 elif name == 'msil_generation_type':
 197                                                         if dotnet_type == None:
 198                                                                 dotnet_type = '.' + setting.attributes['value'].value
 199                                                 elif name == 'executable_name':
 200                                                         if exe_name == None:
 201                                                                 exe_name = setting.attributes['value'].value
 202                                                 elif name == 'shared_library_definition':
 203                                                         if is_shared_library == None:
 204                                                                 is_shared_library = True
 205 
 206                 if exe_name == None:
 207                         exe_name = str(ecf_as_xml.documentElement.attributes['name'].value)
 208 
 209                 if dotnet_type:
 210                         ext = dotnet_type
 211                 elif is_precompiling:
 212                         ext = '.melted'
 213                 elif is_shared_library:
 214                         exe_name = env['SHLIBPREFIX'] + exe_name
 215                         ext = env['SHLIBSUFFIX']
 216                 else:
 217                         exe_name = env['PROGPREFIX'] + exe_name
 218                         ext = env['PROGSUFFIX']
 219 
 220                 ec_path += '/EIFGENs/' + ec_target + '/'
 221 
 222                 if is_c_compiling:
 223                         ec_path += ec_code + '/'
 224                         result = [ec_path + exe_name + ext]
 225 
 226                         if is_dotnet:
 227                                 result += [ec_path + 'lib' + exe_name + '.dll']
 228                         elif is_precompiling:
 229                                 result += [ec_path + environment_variable(env, 'ISE_C_COMPILER') + '/' + env['PROGPREFIX'] + 'driver' + env['PROGSUFFIX']]
 230                         elif is_shared_library and env['PLATFORM'] == 'win32':
 231                                 result += [ec_path + 'dll_' + exe_name + '.lib']
 232                 else:
 233                         result = [ec_path + ec_code + '/Makefile.SH', ec_path + 'project.epr']
 234 
 235         return result, source
 236 
 237 ecf_environment_variable_regex = re.compile(r'(\$\||\$\(?\w*\)?|[^$]+)', re.M)
 238 
 239 def ecf_scanner(node, env, path):
 240         """
 241         All dependencies mentioned in 'node', which is expected to be an ECF file.
 242         The dependencies consist of:
 243          * All Eiffel class files found in all clusters (including override clusters) mentioned in the ECF file.
 244          * All .ecf library files mentioned in the ECF file. (Such libraries are not themselves scanned).
 245          * All .NET assemblies mentioned in the ECF file.
 246          * All external object files mentioned in the ECF file.
 247          * All .h and .hpp files found in external include directories mentioned in the ECF file.
 248         Because this ignores targets and conditionals in the ECF file, it may cause unnecessary builds.
 249         """
 250 
 251         def element_location(element):
 252                 """
 253                 The 'location' attribute of 'element', processed to take care of:
 254                  * Expansion of environment variables.
 255                  * If 'location' is relative, prefixing with the directory name of 'node'.
 256                  * If 'location' is a nested cluster, prefixing with the location of the parent element (recursively).
 257                 """
 258                 result = ''
 259                 ecf_config_path = os.path.dirname(os.path.abspath(str(node)))
 260 
 261                 for token in ecf_environment_variable_regex.findall(element.attributes['location'].value):
 262                         if token[0] <> r'$':
 263                                 result += token
 264                         elif token == r'$|':
 265                                 result += element_location(element.parentNode) + '/'
 266                         elif token == r'$ECF_CONFIG_PATH' or token == r'$(ECF_CONFIG_PATH)':
 267                                 result += ecf_config_path
 268                         else:
 269                                 s = environment_variable(env, token)
 270 
 271                                 if s:
 272                                         result += s
 273                                 else:
 274                                         print '****** WARNING!', str(node), 'uses undefined environment variable', token
 275 
 276                 result = result.replace('\\', '/')
 277 
 278                 if not os.path.isabs(result):
 279                         result = os.path.join(ecf_config_path, result)
 280 
 281                 return result
 282 
 283         result = []
 284         ecf_as_xml = xml.dom.minidom.parse(str(node))
 285 
 286         for tag in ['cluster', 'override', 'tests', 'library', 'assembly', 'external_include', 'external_object']:
 287                 for element in ecf_as_xml.getElementsByTagName(tag):
 288                         location = element_location(element)
 289 
 290                         if os.path.isfile(location):
 291                                 result += [location]
 292                         elif tag == 'external_include':
 293                                 result += env.Glob(location + '/*.h') + env.Glob(location + '/*.hpp')
 294                         elif element.attributes.get('recursive', None):
 295                                 result += classes_in_cluster(env, location)
 296                         else:
 297                                 result += env.Glob(location + '/*.e')
 298 
 299         return result
 300 
 301 def ecf_target(target, source = None, env = None):
 302         """The ECF target corresponding to the given build target."""
 303         return os.path.basename(dirname(str(target[0]), 2))
 304 
 305 def generate(env):
 306         """Add a Builder and construction variables for Eiffel to the given Environment."""
 307         default_ec_path = env.WhereIs('ec')
 308 
 309         if not default_ec_path:
 310                 default_ec_path = spec_path(env, 'studio', 'bin/ec')
 311                 if default_ec_path == '': default_ec_path = 'ec'
 312 
 313         vars = Variables()
 314         vars.Add('EC', "The Eiffel command-line compiler.", default_ec_path)
 315         vars.Add('ECFLAGS', "Use ec -help to see possible options.", '-finalize -clean -c_compile')
 316         vars.Add('ECLOG', "File to log Eiffel compiler output.", 'SCons.Eiffel.log')
 317         vars.Update(env)
 318         Help(vars.GenerateHelpText(env))
 319 
 320         env['BUILDERS']['Eiffel'] = Builder(action = Action(ec_action, ecf_target), emitter = ec_emitter, target_factory = Entry)
 321         env.Append(SCANNERS = Scanner(ecf_scanner, skeys = ['.ecf']))
 322         env.AddMethod(environment_variable, "EiffelEnvironmentVariable")
 323         env.AddMethod(classes_in_cluster, "EiffelClassesInCluster")
 324         env.AddMethod(spec_path, "EiffelSpecPath")
 325 
 326         for v in ['ISE_EIFFEL', 'ISE_PLATFORM', 'ISE_C_COMPILER']:
 327                 if not environment_variable(env, v):
 328                         print '****** WARNING! Undefined Eiffel environment variable ' + v + '.'
 329 
 330 def exists(env):
 331         """Is the Eiffel compiler available?"""
 332         return env.Detect(env['EC'])
 333 
 334 # Utility functions.
 335 
 336 def environment_variable(env, var):
 337         """
 338         The value of the environment variable 'var' within 'env'.
 339         If undefined and it is one of the standard EiffelStudio variables, a sensible platform-specific assumption is used; else None.
 340         """
 341         result = None
 342         var = var.lstrip('$').lstrip('(').rstrip(')')
 343 
 344         if env['ENV'].has_key(var):
 345                 result = env['ENV'][var]
 346         elif var == 'ISE_PLATFORM':
 347                 if env['PLATFORM'] == 'win32':
 348                         result = 'windows'
 349                 elif env['PLATFORM'] == 'darwin':
 350                         result = 'macosx-x86'
 351                 else:
 352                         result = 'linux-x86'
 353         elif var == 'ISE_C_COMPILER':
 354                 if env['PLATFORM'] == 'win32':
 355                         result = 'msc'
 356                 else:
 357                         result = 'gcc'
 358         elif var == 'ISE_EIFFEL':
 359                 if env.has_key('EC'): result = env.WhereIs(env['EC'])
 360                 if result: result = os.path.abspath(dirname(result, 5))
 361         elif var == 'ISE_LIBRARY':
 362                 result = environment_variable(env, 'ISE_EIFFEL')
 363 
 364         return result
 365 
 366 def classes_in_cluster(env, cluster):
 367         """All Eiffel class files in the given cluster and its subclusters."""
 368         result = []
 369 
 370         for root, dirnames, filenames in os.walk(cluster):
 371                 if '.svn' in dirnames: dirnames.remove('.svn')
 372                 result += env.Glob(root + '/*.e')
 373 
 374         return result
 375 
 376 def spec_path(env, mid_part, tail):
 377         """
 378         A platform-dependent path in the EiffelStudio installation directory of the form:
 379         $ISE_EIFFEL + mid_part + '/spec/' + $ISE_PLATFORM + '/' + tail
 380         If either of these environment variables is undefined, then the result is an empty string.
 381         """
 382         result = ''
 383         ise_eiffel = environment_variable(env, 'ISE_EIFFEL')
 384         ise_platform = environment_variable(env, 'ISE_PLATFORM')
 385 
 386         if ise_eiffel and ise_platform:
 387                 result = os.path.join(ise_eiffel, mid_part)
 388                 result = os.path.join(result, 'spec')
 389                 result = os.path.join(result, ise_platform)
 390                 result = os.path.join(result, tail)
 391                 result = os.path.abspath(result)
 392 
 393         return result
 394 
 395 def dirname(path, n):
 396         """The directory name of 'path', called recursively 'n' times."""
 397         result = path
 398         if n > 0: result = dirname(os.path.dirname(path), n - 1)
 399         return result

Possible Enhancements

The builder should define ECCOM and ECCOMSTR to support that common idiom.

On Windows, if one of the ISE_* environment variables is not defined then EiffelStudio looks for it in the registry. The Scanner could simulate this better by likewise looking at the registry. The trouble with this approach (apart from the added complexity) is that it might easily break if EiffelStudio's use of the registry changes in future versions.

When doing a .NET build, the Scanner prints a warning that $ISE_DOTNET_FRAMEWORK is undefined. This is the directory containing .NET framework assemblies. We would not normally expect the environment variable to be defined, so it would be good to suppress the warning, unless a reliable way can be found to guess at its value.

The Scanner scans all of the dependencies that the .ecf mentions, blindly ignoring any targets or conditions specified in the .ecf. This can cause unnecessary nodes in the dependency list.

  • Conditional scanning would require handling the various cases that EiffelStudio itself handles. This would be complicated and error-prone, so it may be best not even to try this one!

  • A dependency may be declared only for a particular target in the .ecf. There a couple of ways that target-specific scanning might be achieved:
    • One option would be passing the target to the Scanner. A few ideas for doing this:
      • Perhaps the Builder() keyword argument target_scanner could be used.

      • Perhaps the Scanner() keyword argument path_function could be abused.

      • Perhaps the emitter might add the target to Node.attributes, for the Scanner to retrieve.

    • Probably a cleaner option would be to remove the Scanner altogether, instead adding the dependencies to the sources list in the emitter. This would also be more efficient, because the .ecf file would be parsed only once, not twice.

EiffelStudioTool (last edited 2013-08-28 20:40:23 by WilliamDeegan)