xpybuild.pathsets

PathSets are used throughout xpybuild to specify the source files and directories to build, how they are to be checked for up-to-dateness, and (for targets where it’s relevant) the destination for each source file.

Here’s an example showing some of the most common PathSets and how to use them:

Copy('${OUTPUT_DIR}/docs/', PathSet(
        './README.txt', 
        './dev/CHANGELOG.txt',
        DirBasedPathSet('legal/', [
                'LICENSE.txt',
                'EULA.txt',
        ]),
        FindPaths('samples/', includes=['**'], excludes=['**/*.pyc']),
        FindPaths(DirGeneratedByTarget('${OUTPUT_DIR}/api-docs/')+'/html'),
        AddDestPrefix('community/', FindPaths('samples-community/')),
))

The main PathSet classes

For simple cases where you just need to specify a few files statically, use PathSet which accepts any number of strings (which can be nested inside lists). Like all PathSet classes, relative paths are automatically resolved relative to the directory of the .xpybuild.py build file where the PathSet appears. If you want to add several paths from the same directory, DirBasedPathSet is usually the most convenient way to to that.

Sometimes it’s not practical to specify the sources statically, and for those cases FindPaths can be used. FindPaths dynamically walks a directory tree, and can be configured with Ant-style glob **/* expressions to indicate what files (and/or empty sub-directories) to include and exclude. Importantly, FindPaths delays walking the directory until actually needed, to avoid slowing down the initial parsing of the build files and determining inter-target dependencies. However since somewhat expensive to walk the file system, never use FindPaths when a more efficient static DirBasedPathSet would be sufficient.

If you need to use the output of another target in a PathSet, it is very important to make sure xpybuild knows the name of the underlying target so that it can calculate the dependencies correctly. Failure to do this can result in unreliable builds. For targets that generate a file this happens automatically, but it’s not possible to (efficiently!) auto-detect when the build file specifies a subdirectory or file from a target that generates a directory - so you need to do that explicitly using DirGeneratedByTarget.

Important

Always use DirGeneratedByTarget() for any path that was generated by a directory target.

This is shown in the above example.

There are also other classes such as TargetsWithTag that can use the output of some targets as the input to another.

The main PathSet classes are therefore:

PathSet(*items)

Factory method that creates a single BasePathSet instance containing the specified strings and/or other PathSets.

DirBasedPathSet(dir, *children)

Constructs a pathset using a basedir and a list of (statically defined, non-globbed) basedir-relative paths within it.

FindPaths(dir[, excludes, includes])

A lazily-evaluated PathSet that uses * and ** (ant-style) globbing to dynamically discover files (and optionally directories) under a common parent directory.

DirGeneratedByTarget(dirTargetName)

This special PathSet must be used for the base dir when specifying a PathSet for any paths located under a directory that is generated as part of the build process.

TargetsWithTag(targetTag[, …])

A special PathSet that resolves to all build target output paths marked with the specified tag.

TargetsWithinDir(parentDir)

A special PathSet that resolves to all build target output paths that are descendents of the specified parent dir (which is not a target itself, but somewhere under a build output directory).

PathSet destinations and filtering

PathSets have a built-in concept of a destination for each source path. This isn’t relevant for every target (e.g. Java source compilation) but is very useful for others such as xpybuild.targets.copy.Copy or xpybuild.targets.archive.Zip where there’s a natural concept of a destination for each file/directory. The destination for each path is always a relative (not absolute) path, so that the same PathSet can be used in different places. The default destination for each source path is either the basename (without any directory prefix), or for PathSets like DirBasedPathSet and FindPaths that have a base directory, it’s the path relative to that directory.

There are a variety of derived PathSets that wrap around another PathSet and add logic to change the the destination paths - for example AddDestPrefix - or filter the results:

AddDestPrefix(prefix, pathSet)

Adds a specified prefix on to the destinations of the specified PathSet.

FlattenDest(pathSet)

Removes all prefixes from the destinations of the specified PathSet.

RemoveDestParents(dirsToRemove, pathSet)

Strips one or more parent directory elements from the start of the destinations of the specified PathSet.

SingletonDestRenameMapper(newDestRelPath, …)

Uses the specified hardcoded destination name for the specific file (and checks only a single input is supplied).

MapDest(fn, pathSet)

Applies a functor to the destination paths of the enclosed PathSet (the PathSet’s source paths are unaffected)

MapDestFromSrc(fn, pathSet)

Applies a functor to the src paths of the enclosed PathSet to get new destinations (the PathSet’s source paths are unaffected)

FilteredPathSet(includeDecider, pathSet[, …])

Filters the contents of another PathSet using a lambda includeDecider function.

MapSrc

xpybuild.pathsets.MapSrc = <class 'xpybuild.pathsets.MapDestFromSrc'>
Deprecated

Legacy name for MapDestFromSrc.

BasePathSet

class xpybuild.pathsets.BasePathSet[source]

Bases: object

Base class for PathSet implementations.

This is a stub class and should not be used directly.

resolve(context)[source]

Use the specified context to resolve the contents of this pathset to a list of normalized absolute paths (using OS-dependent slashes).

Note that unless you actually need a list it is usually more efficient to iterate over resolveWithDestinations which avoids taking a copy of the data structure.

All directory paths must end with “/” or “”.

Some PathSet implementations will cache the results of resolve, if expensive (e.g. file system globbing); in such case it is essential to ensure that the implementation is thread-safe.

PathSets can contain duplicate entries (with same source and/or destination).

resolveWithDestinations(context)[source]

Use the specified context to resolve the contents of this pathset to a list of (srcabs, destrel) pairs specifying the absolute and normalized path of each source path, and a relative normalized delimited path indicating the destination of that path (interpreted in a target-specific way by certain targets such as copy and zip).

All paths use OS-dependent slash characters (os.path.sep), and all directory paths must end with a slash to avoid confusion with file paths.

Some PathSet implementations will cache the results of resolve, if expensive (e.g. file system globbing); in such case it is essential to ensure that the implementation is thread-safe.

May raise BuildException if the resolution fails.

PathSets can contain duplicate entries (with same source and/or destination).

DirBasedPathSet

class xpybuild.pathsets.DirBasedPathSet(dir, *children)[source]

Bases: xpybuild.pathsets.BasePathSet

Constructs a pathset using a basedir and a list of (statically defined, non-globbed) basedir-relative paths within it.

If it is not possible to statically specify the files to be included and globbing is required, use FindPaths instead to perform a dynamic search; but since FindPaths is a lot slower due to the additional file system operations it is better to use DirBasedPathSet where possible.

e.g. DirBasedPathSet(‘${MY_DIR}/’, ‘a’, ‘b/’, ‘${MY_JARS[]}’, ‘x, y, ${Z[]}’)

>>> str(DirBasedPathSet('${MY_DIR}', 'a', 'b/c/', '${MY_JARS[]}', 'd').resolveWithDestinations(BaseContext({'MY_DIR':'MY_DIR/', 'MY_JARS[]':'  1 , 2/3, 4/5/'}))).replace('\\\\','/')
"[('BUILD_DIR/MY_DIR/a', 'a'), ('BUILD_DIR/MY_DIR/b/c/', 'b/c/'), ('BUILD_DIR/MY_DIR/1', '1'), ('BUILD_DIR/MY_DIR/2/3', '2/3'), ('BUILD_DIR/MY_DIR/4/5/', '4/5/'), ('BUILD_DIR/MY_DIR/d', 'd')]"
>>> DirBasedPathSet('mydir', 'a*b').resolve(BaseContext({})) 
Traceback (most recent call last):
...
xpybuild.utils.buildexceptions.BuildException:
>>> str(PathSet('a', DirBasedPathSet(DirGeneratedByTarget('4/5/6/'), '7/8')))
'PathSet("a", DirBasedPathSet(DirGeneratedByTarget("4/5/6/"), [\'7/8\']))'
>>> str([path for (path,pathset) in PathSet('a', DirBasedPathSet(DirGeneratedByTarget('4/5/6/'), '7/8'))._resolveUnderlyingDependencies(BaseContext({}))]).replace('\\\\','/')
"['BUILD_DIR/a', 'BUILD_DIR/4/5/6/']"
Parameters
  • dir – the base directory, which may include substitution variables. When fully expanded, it is essential that dir ends with a ‘/’. May be a string or a DirGeneratedByTarget.

  • children – strings defining the child files or dirs, which may include ${…} variables but not ‘*’ expansions. Can be specified nested inside tuples or lists if desired. If any of the child strings contains a ${…[]} variable, it will be expanded early and split around the ‘,’ character.

FindPaths

class xpybuild.pathsets.FindPaths(dir, excludes=None, includes=None)[source]

Bases: xpybuild.pathsets.BasePathSet

A lazily-evaluated PathSet that uses * and ** (ant-style) globbing to dynamically discover files (and optionally directories) under a common parent directory.

As FindPaths performs a dynamic search of the file system during the dependency checking phase of each build it is considerably slower than other PathSets, so should only be used for specifying the contents of a directory with lots of entries or a file or directory where it is not possible to statically specify the filenames in the build script - for which cases always use PathSet or DirBasedPathSet instead.

FindPaths matching is always case-sensitive, will give an error if the dir does not exist or any of the includes fail to match anything. Sorts its output to ensure determinism.

Includes/excludes are specified using * and ** wildcards (similar to ant), where * represents any path element and ** represents zero or more path elements. Path elements may not begin with a slash, or contain any backslash characters. They match paths underneath the base dir.

Each include/exclude applies either to files OR directories, depending on whether it ends with a /. File include/excludes (e.g. foo/**) are the most common (and the file ** pattern is the default include if none is specified). But if used for a target such as a Copy, note that empty directories will NOT be returned by file patterns, so if you wish to copy all empty directories as well as all files, use:

FindPaths(..., includes=['**', '**/']). 

In addition, global (non-overridable) excludes may be specified by setting the FindPaths.Options.globalExcludesFunction option.

Destination paths (where needed) are generated from the path underneath the base dir.

FindPaths will return file or directory symlinks (with / suffix if directory), but will not recurse into directory symlinks.

Parameters
  • dir – The base directory to search (relative or absolute, may contain ${…} variables). May be a simple string, or a DirGeneratedByTarget to glob under a directory generated as part of the build. To find paths from a set of targets use TargetsWithinDir (though only use this when that dynamism is truly required, as this will be slower than statically listing the targets individually or using TargetsWithTag).

  • includes – a list of glob patterns for the files to include (excluding all others)

  • excludes – a list of glob patterns to exclude after processing any includes.

>>> str(FindPaths('a/b/c', includes=['*.x', 'y/**/z/foo.*'], excludes=['xx', '**/y']))
'FindPaths("a/b/c", includes=["*.x", "y/**/z/foo.*"], excludes=["xx", "**/y"])'
>>> str(PathSet('a', FindPaths(DirGeneratedByTarget('4/5/6/'), includes='*.xml')))
'PathSet("a", FindPaths(DirGeneratedByTarget("4/5/6/"), includes=["*.xml"], excludes=[]))'
>>> FindPaths('x', includes=['*.x', 'c:\d'], excludes=[])
Traceback (most recent call last):
...
xpybuild.utils.buildexceptions.BuildException:
>>> FindPaths('x', includes=['*.x', '${foo}'], excludes=[])
Traceback (most recent call last):
...
xpybuild.utils.buildexceptions.BuildException:
class Options[source]

Bases: object

Options for customizing the behaviour of this type of PathSet. Can be configured with xpybuild.propertysupport.setGlobalOption.

static defaultGlobalExcludesFunction(name)[source]

The default function used for the FindPaths.Options.globalExcludesFunction option.

The current implementation excludes temporary NFS (Network File System) files matching the pattern “.nfs[0-9]”.

This function must be very fast to execute as it’s highly performance-critical for the dependency checking and build process, so simple string operations should be used instead of regular expressions.

Parameters

name (str) – A base file/directory name (without path).

Returns

True if this file or directory should be excluded.

globalExcludesFunction = Option "FindPaths.globalExcludesFunction" (default: <function FindPaths.Options.defaultGlobalExcludesFunction>)

Global (non-overridable) excludes may be specified by setting this option to a function that accepts a full path and returns True if it should be ignored.

Needs to be as fast as possible.

resolveWithDestinations(context)[source]

Uses the file system to returns a list of relative paths for files matching the specified include/exclude patterns, throwing a BuildException if none can be found.

This method will cache its result after being called the first time.

Note that it is possible the destinations may contain “../” elements - targets for which that could be a problem should check for and disallow such destinations (e.g. for copy we would not want to allow copying to destinations outside the specified root directory).

TargetsWithTag

class xpybuild.pathsets.TargetsWithTag(targetTag, allowDirectories=False, walkDirectories=False)[source]

Bases: xpybuild.pathsets.BasePathSet

A special PathSet that resolves to all build target output paths marked with the specified tag.

Note that this is intended mostly for files; it can be used for directories, but only to return the target directory name itself (there is no implicit FindPaths directory searching, as would be required to copy the contents of the directory).

See also TargetsWithinDir.

Parameters
  • targetTag – the tag name

  • allowDirectories – set this to True to allow directories to be specified (by default this is False to avoid errors where a directory is used in a Copy without FindPaths, and therefore ends up empty)

  • walkDirectories – implies allowDirectories. Recursively enumerate the contents of the directory at build time.

TargetsWithinDir

class xpybuild.pathsets.TargetsWithinDir(parentDir)[source]

Bases: xpybuild.pathsets.BasePathSet

A special PathSet that resolves to all build target output paths that are descendents of the specified parent dir (which is not a target itself, but somewhere under a build output directory). When resolved, this pathset returns a single source and destination path which is the parent directory itself.

This PathSet can be wrapped in FindPaths if the contained files and directories are needed.

See also TargetsWithTag.

Parameters

parentDir – a string identifying the parent dir. When resolved, must end in a slash.

FilteredPathSet

class xpybuild.pathsets.FilteredPathSet(includeDecider, pathSet, delayFiltration=False)[source]

Bases: xpybuild.pathsets._DerivedPathSet

Filters the contents of another PathSet using a lambda includeDecider function.

>>> str(FilteredPathSet(isDirPath, PathSet('a', 'b/', 'd/e', 'e/f/', 'g${x}')).resolveWithDestinations(BaseContext({'x':'/x/'}))).replace('\\\\','/')
"[('BUILD_DIR/b/', 'b/'), ('BUILD_DIR/e/f/', 'f/'), ('BUILD_DIR/g/x/', 'x/')]"
>>> str(FilteredPathSet(isDirPath, PathSet('a', 'b/', 'd/e', 'e/f/', 'g${x}')))
'FilteredPathSet(isDirPath, PathSet("a", "b/", "d/e", "e/f/", "g${x}"))'
>>> str(PathSet('a', FilteredPathSet(lambda x: True, DirGeneratedByTarget('4/5/6/'))))
'PathSet("a", FilteredPathSet(<lambda>, DirGeneratedByTarget("4/5/6/")))'
>>> str([path for (path,pathset) in PathSet('a', FilteredPathSet(isDirPath, DirGeneratedByTarget('4/5/6/')))._resolveUnderlyingDependencies(BaseContext({}))]).replace('\\\\','/')
"['BUILD_DIR/a', 'BUILD_DIR/4/5/6/']"
>>> str([path for (path,pathset) in PathSet('a', FilteredPathSet(lambda p:p.endswith('.java'), FindPaths(DirGeneratedByTarget('4/5/6/'))))._resolveUnderlyingDependencies(BaseContext({}))]).replace('\\\\','/')
"['BUILD_DIR/a', 'BUILD_DIR/4/5/6/']"

Construct a PathSet that filters its input, e.g. allows only directories or only files.

Parameters
  • includeDecider – a function that takes an absolute resolved path and returns True if it should be included

  • delayFiltration – don’t filter for dependencies, only for the set used at build time

AddDestPrefix

class xpybuild.pathsets.AddDestPrefix(prefix, pathSet)[source]

Bases: xpybuild.pathsets._DerivedPathSet

Adds a specified prefix on to the destinations of the specified PathSet. (the PathSet’s source paths are unaffected)

See also RemoveDestParents which does the inverse.

e.g. AddDestPrefix(‘META-INF/’, mypathset)

>>> str(AddDestPrefix('lib/bar/', DirBasedPathSet('mydir/', 'b/', 'd/e', 'e/f/')).resolveWithDestinations(BaseContext({}) )).replace('\\\\','/')
"[('BUILD_DIR/mydir/b/', 'lib/bar/b/'), ('BUILD_DIR/mydir/d/e', 'lib/bar/d/e'), ('BUILD_DIR/mydir/e/f/', 'lib/bar/e/f/')]"
>>> str(AddDestPrefix('lib', DirBasedPathSet('mydir/', 'b/', 'd/e', 'e/f/')).resolveWithDestinations(BaseContext({}) )).replace('\\\\','/')
"[('BUILD_DIR/mydir/b/', 'libb/'), ('BUILD_DIR/mydir/d/e', 'libd/e'), ('BUILD_DIR/mydir/e/f/', 'libe/f/')]"
>>> str(AddDestPrefix('/lib', DirBasedPathSet('mydir/', 'b/', 'd/e', 'e/f/')).resolveWithDestinations(BaseContext({}) )).replace('\\\\','/')
"[('BUILD_DIR/mydir/b/', 'libb/'), ('BUILD_DIR/mydir/d/e', 'libd/e'), ('BUILD_DIR/mydir/e/f/', 'libe/f/')]"
>>> str(AddDestPrefix('lib/bar/', PathSet('a', 'b/', 'd/e', 'e/f/')))
'AddDestPrefix(prefix="lib/bar/", PathSet("a", "b/", "d/e", "e/f/"))'

Construct an AddDestPrefix from a PathSet, path or list of paths/path sets.

Parameters
  • prefix – a string that should be added to the beginning of each dest path. Usually this will end with a slash ‘/’.

  • pathSet – either a PathSet object of some type or something from which a path set can be constructed.

MapDest

class xpybuild.pathsets.MapDest(fn, pathSet)[source]

Bases: xpybuild.pathsets._DerivedPathSet

Applies a functor to the destination paths of the enclosed PathSet (the PathSet’s source paths are unaffected)

Do not use this pathset for adding a prefix to the destinations - for that AddDestPrefix is a better solution.

Note that paths passed to the functor will always have forward slashes.

e.g. MapDest(lambda x:x.lower(), mypathset)

>>> str(MapDest(lambda x: x.rstrip('/')+'_foo', PathSet('a', 'b/', 'c${c}')).resolveWithDestinations(BaseContext({'c':'C/'}))).replace('\\\\','/')
"[('BUILD_DIR/a', 'a_foo'), ('BUILD_DIR/b/', 'b_foo/'), ('BUILD_DIR/cC/', 'cC_foo/')]"
>>> str(MapDest(lambda x: x+'_foo', PathSet('a', 'b/', 'c${c}')))
'MapDest(<lambda>, PathSet("a", "b/", "c${c}"))'
Parameters

fn – a function that takes a resolved dest path (using forward slashes not backslashes) as input, and returns a potentially different dest path. If possible, use a named function rather than a lamba.

MapDestFromSrc

class xpybuild.pathsets.MapDestFromSrc(fn, pathSet)[source]

Bases: xpybuild.pathsets._DerivedPathSet

Applies a functor to the src paths of the enclosed PathSet to get new destinations (the PathSet’s source paths are unaffected)

Note that paths passed to the functor will always have forward slashes.

e.g. MapDestFromSrc(lambda x:x.lower(), mypathset)

Parameters

fn – a function that takes a resolved dest path (using forward slashes not backslashes) as input, and returns a potentially different dest path

FlattenDest

class xpybuild.pathsets.FlattenDest(pathSet)[source]

Bases: xpybuild.pathsets._DerivedPathSet

Removes all prefixes from the destinations of the specified PathSet.

e.g. FlattenDest(mypathset)

>>> str(FlattenDest(PathSet('a', 'b/', 'd/e', 'e/f/')))
'FlattenDest(PathSet("a", "b/", "d/e", "e/f/"))'
Parameters

pathSet – The input PathSet, path or list of path/PathSets.

RemoveDestParents

class xpybuild.pathsets.RemoveDestParents(dirsToRemove, pathSet)[source]

Bases: xpybuild.pathsets._DerivedPathSet

Strips one or more parent directory elements from the start of the destinations of the specified PathSet. (the PathSet’s source paths are unaffected)

e.g. RemoveDestParents(2, mypathset) # strips the leading 2 parent dirs

See also AddDestPrefix which does the inverse.

>>> str(RemoveDestParents(1, DirBasedPathSet('mydir/', ['d/e', 'e/f/g/'])).resolveWithDestinations(BaseContext({}) )).replace('\\\\','/')
"[('BUILD_DIR/mydir/d/e', 'e'), ('BUILD_DIR/mydir/e/f/g/', 'f/g/')]"
>>> str(RemoveDestParents(2, DirBasedPathSet('mydir/', ['a/b/c', 'a/b/c/d', 'd/e/f/', 'd/e/f/g/'])).resolveWithDestinations(BaseContext({}) )).replace('\\\\','/')
"[('BUILD_DIR/mydir/a/b/c', 'c'), ('BUILD_DIR/mydir/a/b/c/d', 'c/d'), ('BUILD_DIR/mydir/d/e/f/', 'f/'), ('BUILD_DIR/mydir/d/e/f/g/', 'f/g/')]"
>>> str(RemoveDestParents(1, DirBasedPathSet('mydir/', 'a')).resolveWithDestinations(BaseContext({}) )).replace('\\\\','/') 
Traceback (most recent call last):
...
xpybuild.utils.buildexceptions.BuildException: Cannot strip 1 parent dir(s) from "a" as it does not have that many parent directories
>>> str(RemoveDestParents(1, DirBasedPathSet('mydir/', 'b/')).resolveWithDestinations(BaseContext({}) )).replace('\\\\','/')
Traceback (most recent call last):
...
xpybuild.utils.buildexceptions.BuildException: Cannot strip 1 parent dir(s) from "b/" as it does not have that many parent directories

Construct an RemoveDestParents from a PathSet, path or list of paths/path sets.

Parameters
  • dirsToRemove – the number of parent directory elements to remove

  • pathSet – either a PathSet object of some type or something from which a path set can be constructed.

SingletonDestRenameMapper

class xpybuild.pathsets.SingletonDestRenameMapper(newDestRelPath, pathSet)[source]

Bases: xpybuild.pathsets._DerivedPathSet

Uses the specified hardcoded destination name for the specific file (and checks only a single input is supplied). Properties in the specified dest prefix string will be expanded

e.g. (SingletonDestRenameMapper(‘meta-inf/manifest.mf’, ‘foo/mymanifest’).

>>> str(SingletonDestRenameMapper('meta-inf/manifest.mf', 'foo/bar'))
'SingletonDestRenameMapper(meta-inf/manifest.mf, PathSet("foo/bar"))'
>>> str(SingletonDestRenameMapper('meta-inf/manifest.mf', 'foo/mymanifest').resolveWithDestinations(BaseContext({}))).replace('\\\\','/')
"[('BUILD_DIR/foo/mymanifest', 'meta-inf/manifest.mf')]"
Parameters
  • newDestRelPath – Replacement destination path (including file name)

  • pathSet – The input path or PathSet, which must contain a single file.

DirGeneratedByTarget

class xpybuild.pathsets.DirGeneratedByTarget(dirTargetName)[source]

Bases: xpybuild.pathsets.BasePathSet

This special PathSet must be used for the base dir when specifying a PathSet for any paths located under a directory that is generated as part of the build process.

This forces the evaluation of any parent PathSet to be delayed until the target dependency has actually been built.

Often used as the first argument of a DirBasedPathSet or FindPaths.

As a convenience, the “+” operator can be used to add a string representing a relative path to a DirGeneratedByTarget (which will be converted to the equivalent DirBasedPathSet expression).

>>> str(DirGeneratedByTarget('4/5/6/'))
'DirGeneratedByTarget("4/5/6/")'
>>> str(DirGeneratedByTarget('4/5/6/')+'foo/${MY_VAR}/bar')
'DirBasedPathSet(DirGeneratedByTarget("4/5/6/"), [\'foo/${MY_VAR}/bar\'])'
Parameters

dirTargetName – The directory that another target will generate.

PathSet

xpybuild.pathsets.PathSet(*items)[source]

Factory method that creates a single BasePathSet instance containing the specified strings and/or other PathSets.

An additional composite pathset instance will be constructed to hold them if needed.

Parameters

items – contains strings, targets and PathSet objects, nested as deeply as you like within lists and tuples. The strings must be absolute paths, or paths relative to the build file where this PathSet is defined, in which case the PathSet must be instantiated during the build file parsing phase (relative paths cannot be used in a PathSet that is instantiated while building or resolving dependencies for a target). Paths may not contain the ‘*’ character, and directory paths must end with an explicit ‘/’.

Returns

A BasePathSet instance.

>>> str(PathSet('a', [('b/', PathSet('1/2/3/', '4/5/6/'), ['d/e'])], 'e/f/${x}').resolveWithDestinations(BaseContext({'x':'X/'}))).replace('\\\\','/')
"[('BUILD_DIR/a', 'a'), ('BUILD_DIR/b/', 'b/'), ('BUILD_DIR/1/2/3/', '3/'), ('BUILD_DIR/4/5/6/', '6/'), ('BUILD_DIR/d/e', 'e'), ('BUILD_DIR/e/f/X/', 'X/')]"
>>> str(PathSet('a', [('b/', PathSet('1/2/3/', '4/5/6/'), ['d/e'])], 'e/f/${x}').resolve(BaseContext({'x':'X/'}))).replace('\\\\','/')
"['BUILD_DIR/a', 'BUILD_DIR/b/', 'BUILD_DIR/1/2/3/', 'BUILD_DIR/4/5/6/', 'BUILD_DIR/d/e', 'BUILD_DIR/e/f/X/']"
>>> str(PathSet('a', [('b/', PathSet('1/2/3/', '4/5/6/'), ['d/e'])], 'e/f/${x}'))
'PathSet("a", "b/", PathSet("1/2/3/", "4/5/6/"), "d/e", "e/f/${x}")'
>>> str([path for (path,pathset) in PathSet('a', [[PathSet('1/2/3/', DirGeneratedByTarget('4/5/6/'), '7/8/')]], DirGeneratedByTarget('9/'))._resolveUnderlyingDependencies(BaseContext({}))]).replace('\\\\','/')
"['BUILD_DIR/a', 'BUILD_DIR/1/2/3/', 'BUILD_DIR/4/5/6/', 'BUILD_DIR/7/8/', 'BUILD_DIR/9/']"
>>> PathSet('a/*').resolve(BaseContext({})) 
Traceback (most recent call last):
...
xpybuild.utils.buildexceptions.BuildException: