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).

InstallFiles is a pseudo-builder that scans a source directory for files matching a certain pattern and installs the results in a destination direction. Support for glob patterns, exclude patterns, and recursion are provided. Glob patterns only apply to files, but the exclude patterns also apply to directories. If the source is a file, it is copied to the destination. If the source is a directory, it is scanned, and items in that directory are copied to the destination. This uses the InstallAs builder, so using --install-sandbox=/path/to/sandbox will affect it as well.

Sample usage:

   1 TOOL_INSTALL(env)
   2 
   3 # Prevent installation of certain undesired items
   4 env.InstallExclude('.*', '*~', '*.tmp', '*.bak', '*.pyc', '*.pyo')
   5 
   6 # Install a file to a destination.  When the source is a file
   7 # glob/exclude/recursion is not important
   8 env.InstallFiles('$PREFIX/bin', program)
   9 
  10 # Install contents of a directory.  When the source is a directory
  11 # glob/exclude/recursion is important and will affect items in it
  12 env.InstallFiles('$PREFIX/share/myapp/pixmaps', '#data/pixmaps')
  13 
  14 # You can also specify a glob to match or additional excludes in
  15 # addition to those specified with InstallExclude
  16 env.InstallFiles('$PREFIX/share/myapp/help', '#data/help', glob=['*.html', '*.png'], exclude=['*.svg'])

In addition you can tell it to create named packages of files and then later install those packages into the correct location.

   1 # File: data/SConscript:
   2 
   3 # Make files under 'pixmaps' and 'pixmaps-extra' install to the 'pixmaps' directory of the 'data' package
   4 env.InstallPackageAccum('data', 'pixmaps', 'pixmaps', scan=1)
   5 env.InstallPackageAccum('data', 'pixmaps', 'pixmaps-extra', scan=1)
   6 
   7 # File: installers/linux/SConscript:
   8 
   9 # Install the 'data' package to the data directory
  10 env.InstallPackage('/usr/share/myapplication', 'data')

The new builder code that also scans the build nodes as well

   1 # File:         install.py
   2 # Author:       Brian A. Vanderburg II
   3 # Purpose:      An install builder to install subdirectories and files
   4 # Copyright:    This file is placed in the public domain.
   5 ##############################################################################
   6 
   7 
   8 # Requirements
   9 ##############################################################################
  10 import os
  11 import fnmatch
  12 
  13 import SCons
  14 import SCons.Node.FS
  15 import SCons.Errors
  16 from SCons.Script import *
  17 
  18 
  19 # Install files code
  20 ##############################################################################
  21 
  22 def _is_excluded(name, exclude):
  23     """
  24     Determine if the name is to be excluded based on the exclusion list
  25     """
  26     if not exclude:
  27         return False
  28     return any( (fnmatch.fnmatchcase(name, i) for i in exclude) )
  29 
  30 
  31 def _is_globbed(name, glob):
  32     """
  33     Determine if the name is globbed based on the glob list
  34     """
  35     if not glob:
  36         return True
  37     return any( (fnmatch.fnmatchcase(name, i) for i in glob) )
  38 
  39 
  40 def _get_files(env, source, exclude, glob, recursive, reldir=os.curdir):
  41     """
  42     Find files that match the criteria.
  43 
  44     source - absolute path to source file or directory (not a node)
  45     exclude - additional exclusion masks in addition to default
  46     glob - glob masks
  47     recursive - scan recursively or not
  48 
  49     Returns a list of 2-element tuples where the first element is
  50     relative to the source, the second is the absolute file name.
  51     """
  52 
  53     # Make sure it exists and is not a link
  54     if not os.path.exists(source):
  55         return []
  56 
  57     if os.path.islink(source):
  58         return []
  59 
  60     # Handle file directly
  61     if os.path.isfile(source):
  62         return [ (os.path.join(reldir, os.path.basename(source)), source) ]
  63 
  64     # Scan source files on disk
  65     if not os.path.isdir(source):
  66         return []
  67 
  68     results = []
  69     for entry in os.listdir(source):
  70         fullpath = os.path.join(source, entry)
  71         if os.path.islink(fullpath):
  72             continue
  73 
  74         # Excluded (both files and directories)
  75         if _is_excluded(entry, exclude):
  76             continue
  77 
  78         # File
  79         if os.path.isfile(fullpath):
  80             if _is_globbed(entry, glob):
  81                 results.append( (os.path.join(reldir, entry), fullpath) )
  82         elif os.path.isdir(fullpath):
  83             if recursive:
  84                 newrel = os.path.join(reldir, entry)
  85                 results.extend(_get_files(env, fullpath, exclude, glob, recursive, newrel))
  86 
  87     return results
  88 
  89 
  90 def _get_built_files(env, source, exclude, glob, recursive, reldir=os.curdir):
  91     """
  92     Find files that match the criteria.
  93 
  94     source - source file or directory node
  95     exclude - additional exclusion masks in addition to default
  96     glob - glob masks
  97     recursive - scan recursively or not
  98 
  99     Returns a list of 2-element tuples where the first element is
 100     relative to the source, the second is the absolute file name.
 101     """
 102 
 103     # Source
 104     source = source.disambiguate()
 105 
 106     # If a file, return it without scanning
 107     if isinstance(source, SCons.Node.FS.File):
 108         if source.is_derived():
 109             source = source.abspath
 110             return [ (os.path.join(reldir, os.path.basename(source)), source) ]
 111         else:
 112             return []
 113 
 114     if not isinstance(source, SCons.Node.FS.Dir):
 115         return []
 116 
 117     # Walk the children
 118     results = []
 119 
 120     for child in source.children():
 121         child = child.disambiguate()
 122         name = os.path.basename(child.abspath)
 123 
 124         # Ignore '.' and '..'
 125         if name == '.' or name == '..':
 126             continue
 127 
 128         # Exclude applies to files and directories
 129         if _is_excluded(name, exclude):
 130             continue
 131 
 132         if isinstance(child, SCons.Node.FS.File):
 133             if child.is_derived() and _is_globbed(name, glob):
 134                 results.append( (os.path.join(reldir, name), child.abspath) )
 135         elif isinstance(child, SCons.Node.FS.Dir):
 136             if recursive:
 137                 newrel = os.path.join(reldir, name)
 138                 results.extend(_get_built_files(env, child, exclude, glob, recursive, newrel))
 139 
 140     return results
 141 
 142 
 143 def _get_both(env, source, exclude, glob, recursive, scan):
 144     """
 145     Get both the built and source files that match the criteria.  Built
 146     files take priority over a source file of the same path and name.
 147     """
 148 
 149     src_nodes = []
 150     results = []
 151 
 152     # Get built files
 153     if scan == 0 or scan == 2:
 154         results.extend(_get_built_files(env, source, exclude, glob, recursive))
 155         for (relsrc, src) in results:
 156             node = env.File(src)
 157             src_nodes.append(node.srcnode())
 158 
 159     # Get source files
 160     if scan == 1 or scan == 2:
 161         files = _get_files(env, source.srcnode().abspath, exclude, glob, recursive)
 162         for (relsrc, src) in files:
 163             node = env.File(src)
 164             if not node in src_nodes:
 165                 results.append( (relsrc, src) )
 166 
 167     return results
 168                 
 169 
 170 def InstallFiles(env, target, source, exclude=None, glob=None, recursive=True, scan=2):
 171     """
 172     InstallFiles pseudo-builder
 173 
 174     target - target directory to install to
 175     source - source file or directory to scan
 176     exclude - a list of patterns to exclude in files and directories
 177     glob - a list of patterns to include in files
 178     recursive - scan directories recursively
 179     scan - 0=scan built nodes, 1=scan source files, 2=both
 180 
 181     All argument except target and source should be used as keyword arguments
 182     """
 183 
 184     # Information
 185     if exclude:
 186         exclude = Flatten(exclude)
 187     else:
 188         exclude = []
 189     exclude.extend(env['INSTALLFILES_EXCLUDES'])
 190 
 191     if glob:
 192         glob = Flatten(glob)
 193     else:
 194         glob = []
 195 
 196     # Flatten source/target
 197     target = Flatten(target)
 198     source = Flatten(source)
 199 
 200     if len(target) != len(source):
 201         if len(target) == 1:
 202             # If only one target, assume it is for all the sources
 203             target = target * len(source)
 204         else:
 205             raise SCons.Errors.UserError('InstallFiles expects only one target directory or one for each source')
 206 
 207     # Scan
 208     files = []
 209     for (t, s) in zip(target, source):
 210         if not isinstance(t, SCons.Node.FS.Base):
 211             t = env.Dir(t)
 212         if not isinstance(s, SCons.Node.FS.Base):
 213             s = env.Entry(s)
 214 
 215         for (relsrc, src) in _get_both(env, s, exclude, glob, recursive, scan):
 216             dest = os.path.normpath(os.path.join(t.abspath, relsrc))
 217             files.append( (dest, src) )
 218 
 219     # Install
 220     results = []
 221     for (dest, src) in files:
 222         results.extend(env.InstallAs(dest, src))
 223 
 224     # Done
 225     return results
 226 
 227 
 228 def InstallPackageAccum(env, name, target, source, exclude=None, glob=None, recursive=True, scan=2):
 229     """
 230     InstallPackageAccum accumulate files for a package name
 231 
 232     name - the name of the package of files
 233     target - relative target directory under the package directory, can be '.'
 234     source - source file or directory to scan
 235     exclude - a list of patterns to exclude in files and directories
 236     glob - a list of patterns to include in files
 237     recursive - scan directories recursively
 238     scan - 0=scan built nodes, 1=scan source files, 2=both
 239 
 240     All argument except target and source should be used as keyword arguments and
 241     target should NOT be nodes but strings such as '.'
 242     """
 243     
 244     # Information
 245     if exclude:
 246         exclude = Flatten(exclude)
 247     else:
 248         exclude = []
 249     exclude.extend(env['INSTALLFILES_EXCLUDES'])
 250 
 251     if glob:
 252         glob = Flatten(glob)
 253     else:
 254         glob = []
 255 
 256     # Flatten target/source
 257     target = Flatten(target)
 258     source = Flatten(source)
 259 
 260     if len(target) != len(source):
 261         if len(target) == 1:
 262             target = target * len(source)
 263         else:
 264             raise SCons.Errors.UserError('InstallPackageAccum expects only one target directory or one for each source')
 265 
 266     # Scan
 267     files = []
 268     for (t, s) in zip(target, source):
 269         t = env.subst(t)
 270         if not isinstance(s, SCons.Node.FS.Base):
 271             s = env.Entry(s)
 272 
 273         for (relsrc, src) in _get_both(env, s, exclude, glob, recursive, scan):
 274             dest = os.path.normpath(os.path.join(t, relsrc))
 275             files.append( (dest, src) )
 276 
 277     # Add package if needed
 278     packages = env['INSTALLFILES_PACKAGES']
 279     if not name in packages:
 280         packages[name] = []
 281     packages[name].extend(files)
 282 
 283 
 284 def InstallPackage(env, target, name):
 285     """
 286     Install the files of a given package to a certain location
 287 
 288     target - the directory to install the package to
 289     name - the name of the package to install
 290     """
 291 
 292     # Flatten target/name
 293     target = Flatten(target)
 294     name = Flatten(name)
 295 
 296     if len(target) != len(name):
 297         if len(target) == 1:
 298             target = target * len(name)
 299         else:
 300             raise SCons.Errors.UserError('InstallPackage expects only one target directory or one for each package')
 301 
 302     # Install
 303     results = []
 304     packages = env['INSTALLFILES_PACKAGES']
 305 
 306     for (t, n) in zip(target, name):
 307         if not n in packages:
 308             raise SCons.Errors.UserError('InstallPackage package name does not exist: ' + n)
 309 
 310         for (relsrc, src) in packages[n]:
 311             dest = os.path.normpath(os.path.join(t, relsrc))
 312             results.extend(env.InstallAs(dest, src))
 313 
 314     # Done
 315     return results
 316 
 317 
 318 def InstallExclude(env, *args):
 319     env['INSTALLFILES_EXCLUDES'] = []
 320 
 321     for i in args:
 322         env['INSTALLFILES_EXCLUDES'].extend(Flatten(i))
 323 
 324 
 325 # Register this with the environment
 326 def TOOL_INSTALL(env):
 327     env.AddMethod(InstallFiles)
 328     env.AddMethod(InstallPackageAccum)
 329     env.AddMethod(InstallPackage)
 330     env.AddMethod(InstallExclude)
 331 
 332     env['INSTALLFILES_EXCLUDES'] = []
 333     env['INSTALLFILES_PACKAGES'] = {}

The old builder code that only scans source files:

   1 # File:         install.py
   2 # Author:       Brian A. Vanderburg II
   3 # Purpose:      An install builder to install subdirectories and files
   4 # Copyright:    This file is placed in the public domain.
   5 ##############################################################################
   6 
   7 
   8 # Requirements
   9 ##############################################################################
  10 import os
  11 import fnmatch
  12 
  13 import SCons
  14 import SCons.Node.FS
  15 from SCons.Script import *
  16 
  17 
  18 # Install files code
  19 ##############################################################################
  20 
  21 def _is_excluded(name, exclude):
  22     """
  23     Determine if the name is to be excluded based on the exclusion list
  24     """
  25     if not exclude:
  26         return False
  27     return any( (fnmatch.fnmatchcase(name, i) for i in exclude) )
  28 
  29 
  30 def _is_globbed(name, glob):
  31     """
  32     Determine if the name is globbed based on the glob list
  33     """
  34     if not glob:
  35         return True
  36     return any( (fnmatch.fnmatchcase(name, i) for i in glob) )
  37 
  38 
  39 def _get_files(env, target, source, exclude, glob, recursive):
  40     """
  41     Find files that match the criteria.
  42 
  43     target - target directory node
  44     source - source file or directory node
  45     exclude - additional exclusion masks in addition to default
  46     glob - glob masks
  47     recursive - scan recursively or not
  48 
  49     Returns a list of 2-element tuples where the first element is
  50     the destination file name, the second is the source file name.
  51     """
  52 
  53     # Source and target path
  54     # abspath intentionally used instead of str()
  55     spath = os.path.normpath(source.abspath)
  56     tpath = os.path.normpath(target.abspath)
  57 
  58     # Since we are scanning now, the directory must exist now,
  59     # otherwise treat it like a file
  60     if not os.path.isdir(spath):
  61         return [ (os.path.join(tpath, os.path.basename(spath)), spath) ]
  62 
  63     # Scan source files on disk
  64     results = []
  65     for(dir, dirs, files) in os.walk(spath):
  66         # Target for any file in this directory
  67         reldir = dir[len(spath):]
  68         while reldir and reldir[0] == os.sep:
  69             reldir = reldir[1:]
  70 
  71         if reldir:
  72             destdir = os.path.join(tpath, reldir)
  73         else:
  74             destdir = tpath
  75 
  76         # Files
  77         for i in files:
  78             if _is_excluded(i, exclude):
  79                 continue
  80             if not _is_globbed(i, glob):
  81                 continue
  82 
  83             results.append( (os.path.join(destdir, i), os.path.join(dir, i)) )
  84 
  85         # Directories
  86         i = 0
  87         while i < len(dirs):
  88             if recursive and not _is_excluded(dirs[i], exclude):
  89                 i += 1
  90             else:
  91                 del dirs[i]
  92 
  93     return results
  94 
  95 def InstallFiles(env, target, source, exclude=None, glob=None, recursive=True):
  96     """
  97     InstallFiles pseudo-builder
  98     """
  99 
 100     # Information
 101     if exclude:
 102         exclude = Flatten(exclude)
 103     else:
 104         exclude = []
 105     exclude.extend(env.get('INSTALL_EXCLUDES', []))
 106 
 107     if glob:
 108         glob = Flatten(glob)
 109     else:
 110         glob = []
 111 
 112     # Install
 113     target = Flatten(target)
 114     source = Flatten(source)
 115 
 116     if len(target) != len(source):
 117         if len(target) == 1:
 118             # If only one target, assume it is for all the sources
 119             target = target * len(source)
 120         else:
 121             raise SCons.Errors.UserError('InstallFiles expects only one target directory or one for each source')
 122 
 123     results = []
 124     for (t, s) in zip(target, source):
 125         if not isinstance(t, SCons.Node.FS.Base):
 126             t = env.Dir(t)
 127         if not isinstance(s, SCons.Node.FS.Base):
 128             s = env.Entry(s)
 129 
 130         files = _get_files(env, t, s, exclude, glob, recursive)
 131         for (dest, src) in files:
 132             results.extend(env.InstallAs(dest, src))
 133 
 134     # Success
 135     return results
 136 
 137 
 138 def InstallExclude(env, *args):
 139     env['INSTALL_EXCLUDES'] = []
 140 
 141     for i in args:
 142         env['INSTALL_EXCLUDES'].extend(Flatten(i))
 143 
 144 
 145 # Register this with the environment
 146 def TOOL_INSTALL(env):
 147     env.AddMethod(InstallFiles)
 148     env.AddMethod(InstallExclude)

InstallFiles (last edited 2009-08-27 06:07:21 by BrianVanderburgII)