summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--QMTest/TestSCons.py18
-rw-r--r--examples/ipk_packaging/SConstruct15
-rw-r--r--examples/ipk_packaging/main.c4
-rw-r--r--examples/msi_packaging/README1
-rw-r--r--examples/msi_packaging/SConstruct19
-rw-r--r--examples/msi_packaging/helloworld.dll1
-rw-r--r--examples/msi_packaging/main.exe1
-rw-r--r--examples/rpm_packaging/international/SConstruct25
-rw-r--r--examples/rpm_packaging/international/main.c4
-rw-r--r--examples/rpm_packaging/simple/SConstruct20
-rw-r--r--examples/rpm_packaging/simple/main.c4
-rw-r--r--examples/src_packaging/SConstruct11
-rw-r--r--examples/src_packaging/src/foobar.c0
-rw-r--r--examples/src_packaging/src/foobar.h0
-rw-r--r--runtest.py7
-rw-r--r--src/engine/SCons/Action.py2
-rw-r--r--src/engine/SCons/Builder.py12
-rw-r--r--src/engine/SCons/Defaults.py36
-rw-r--r--src/engine/SCons/Environment.py123
-rw-r--r--src/engine/SCons/EnvironmentTests.py80
-rw-r--r--src/engine/SCons/Errors.py3
-rw-r--r--src/engine/SCons/Node/FS.py31
-rw-r--r--src/engine/SCons/Options/OptionsTests.py16
-rw-r--r--src/engine/SCons/Options/PathOption.py9
-rw-r--r--src/engine/SCons/Options/__init__.py56
-rw-r--r--src/engine/SCons/Script/__init__.py4
-rw-r--r--src/engine/SCons/Tool/ToolTests.py2
-rw-r--r--src/engine/SCons/Tool/__init__.py34
-rw-r--r--src/engine/SCons/Tool/bcc32.py7
-rw-r--r--src/engine/SCons/Tool/cc.py12
-rw-r--r--src/engine/SCons/Tool/filesystem.py89
-rw-r--r--src/engine/SCons/Tool/install.py221
-rw-r--r--src/engine/SCons/Tool/ipkg.py61
-rw-r--r--src/engine/SCons/Tool/link.py2
-rw-r--r--src/engine/SCons/Tool/linkloc.py4
-rw-r--r--src/engine/SCons/Tool/mingw.py10
-rw-r--r--src/engine/SCons/Tool/packaging.xml73
-rw-r--r--src/engine/SCons/Tool/packaging/__init__.py332
-rw-r--r--src/engine/SCons/Tool/packaging/__init__.xml522
-rw-r--r--src/engine/SCons/Tool/packaging/ipk.py179
-rw-r--r--src/engine/SCons/Tool/packaging/msi.py519
-rw-r--r--src/engine/SCons/Tool/packaging/packager.py218
-rw-r--r--src/engine/SCons/Tool/packaging/rpm.py354
-rw-r--r--src/engine/SCons/Tool/packaging/src_tarbz2.py37
-rw-r--r--src/engine/SCons/Tool/packaging/src_targz.py37
-rw-r--r--src/engine/SCons/Tool/packaging/src_zip.py37
-rw-r--r--src/engine/SCons/Tool/packaging/tarbz2.py38
-rw-r--r--src/engine/SCons/Tool/packaging/targz.py38
-rw-r--r--src/engine/SCons/Tool/packaging/zip.py38
-rw-r--r--src/engine/SCons/Tool/rpm.py126
-rw-r--r--src/engine/SCons/Tool/wix.py91
-rw-r--r--src/engine/SCons/Util.py19
-rw-r--r--test/Copy.py17
-rw-r--r--test/Install/Install.py2
-rw-r--r--test/Install/directories.py4
-rw-r--r--test/Install/option--install-sandbox.py73
-rw-r--r--test/Java/JAVAH.py2
-rw-r--r--test/option-j.py3
-rw-r--r--test/option-n.py1
-rw-r--r--test/option/profile.py1
-rw-r--r--test/packaging/guess-package-name.py106
-rw-r--r--test/packaging/ipkg.py129
-rw-r--r--test/packaging/msi/file-placement.py172
-rw-r--r--test/packaging/msi/package.py138
-rw-r--r--test/packaging/option--package-type.py78
-rw-r--r--test/packaging/place-files-in-subdirectory.py108
-rw-r--r--test/packaging/rpm/cleanup.py100
-rw-r--r--test/packaging/rpm/internationalization.py180
-rw-r--r--test/packaging/rpm/package.py91
-rw-r--r--test/packaging/rpm/tagging.py97
-rw-r--r--test/packaging/sandbox-test.py73
-rw-r--r--test/packaging/strip-install-dir.py60
-rw-r--r--test/packaging/tar/bz2.py64
-rw-r--r--test/packaging/tar/gz.py64
-rw-r--r--test/packaging/use-builddir.py93
-rw-r--r--test/packaging/zip.py65
76 files changed, 5069 insertions, 254 deletions
diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py
index e1884ac..100d8ba 100644
--- a/QMTest/TestSCons.py
+++ b/QMTest/TestSCons.py
@@ -45,6 +45,7 @@ from TestCommon import __all__
SConsVersion = '0.97'
__all__.extend([ 'TestSCons',
+ 'machine',
'python',
'_exe',
'_obj',
@@ -55,6 +56,23 @@ __all__.extend([ 'TestSCons',
'_dll'
])
+machine_map = {
+ 'i686' : 'i386',
+ 'i586' : 'i386',
+ 'i486' : 'i386',
+}
+
+try:
+ uname = os.uname
+except AttributeError:
+ # Windows doesn't have a uname() function. We could use something like
+ # sys.platform as a fallback, but that's not really a "machine," so
+ # just leave it as None.
+ machine = None
+else:
+ machine = uname()[4]
+ machine = machine_map.get(machine, machine)
+
python = python_executable
_python_ = '"' + python_executable + '"'
_exe = exe_suffix
diff --git a/examples/ipk_packaging/SConstruct b/examples/ipk_packaging/SConstruct
new file mode 100644
index 0000000..c8c54eb
--- /dev/null
+++ b/examples/ipk_packaging/SConstruct
@@ -0,0 +1,15 @@
+prog = Install( '/bin/', Program( 'main.c') )
+
+Package( projectname = 'foo',
+ version = '1.2.3',
+ architecture = 'arm',
+ x_ipk_maintainer = 'user <user@somehost.net>',
+ x_ipk_priority = 'optional',
+ source_url = 'http://ftp.gnu.org/foo-1.2.3.tar.gz',
+ x_ipk_depends = 'libc6, grep',
+ type = 'ipk',
+ summary = 'bla bla bla',
+ x_ipk_section = 'extras',
+ description = 'this should be reallly really long',
+ source = [ prog ],
+ )
diff --git a/examples/ipk_packaging/main.c b/examples/ipk_packaging/main.c
new file mode 100644
index 0000000..5940dd6
--- /dev/null
+++ b/examples/ipk_packaging/main.c
@@ -0,0 +1,4 @@
+int main( int argc, char *argv[] )
+{
+ return 0;
+}
diff --git a/examples/msi_packaging/README b/examples/msi_packaging/README
new file mode 100644
index 0000000..2e54e70
--- /dev/null
+++ b/examples/msi_packaging/README
@@ -0,0 +1 @@
+This is the README file.
diff --git a/examples/msi_packaging/SConstruct b/examples/msi_packaging/SConstruct
new file mode 100644
index 0000000..cabe70e
--- /dev/null
+++ b/examples/msi_packaging/SConstruct
@@ -0,0 +1,19 @@
+#
+# Build a minimal msi installer with two features.
+#
+
+f1 = Install( '/bin/', File('main.exe') )
+f2 = Install( '/lib/', File('helloworld.dll') )
+f3 = Install( '/doc/', File('README') )
+
+Tag( f2, x_msi_feature = 'Resuable Components' )
+Tag( f3, 'doc' )
+
+Package( projectname = 'helloworld',
+ version = '1.0',
+ packageversion = '1',
+ license = 'gpl',
+ type = 'msi',
+ vendor = 'Nanosoft',
+ summary = 'A HelloWorld implementation',
+ source = [ f1, f2, f3 ], )
diff --git a/examples/msi_packaging/helloworld.dll b/examples/msi_packaging/helloworld.dll
new file mode 100644
index 0000000..1404bcf
--- /dev/null
+++ b/examples/msi_packaging/helloworld.dll
@@ -0,0 +1 @@
+a fake .dll
diff --git a/examples/msi_packaging/main.exe b/examples/msi_packaging/main.exe
new file mode 100644
index 0000000..36c366c
--- /dev/null
+++ b/examples/msi_packaging/main.exe
@@ -0,0 +1 @@
+a fake .exe
diff --git a/examples/rpm_packaging/international/SConstruct b/examples/rpm_packaging/international/SConstruct
new file mode 100644
index 0000000..0d14932
--- /dev/null
+++ b/examples/rpm_packaging/international/SConstruct
@@ -0,0 +1,25 @@
+# coding: utf-8
+import os
+
+prog_install = Install( os.path.join( ARGUMENTS.get('prefix', '/'), 'bin'), Program( 'main.c' ) )
+Tag( prog_install, unix_attr='(0755, root, users)' )
+
+Default( Package( projectname = 'foo',
+ version = '1.2.3',
+ type = 'rpm',
+ license = 'gpl',
+ summary = 'hello',
+ summary_de = 'hallo',
+ summary_fr = 'bonjour',
+ packageversion = 0,
+ x_rpm_Group = 'Application/office',
+ x_rpm_Group_de = 'Applikation/büro',
+ x_rpm_Group_fr = 'Application/bureau',
+ description = 'this should be really long',
+ description_de = 'das sollte wirklich lang sein',
+ description_fr = 'ceci devrait être vraiment long',
+ source = [ prog_install ],
+ source_url = 'http://foo.org/foo-1.2.3.tar.gz',
+ ) )
+
+Alias ( 'install', prog_install )
diff --git a/examples/rpm_packaging/international/main.c b/examples/rpm_packaging/international/main.c
new file mode 100644
index 0000000..5940dd6
--- /dev/null
+++ b/examples/rpm_packaging/international/main.c
@@ -0,0 +1,4 @@
+int main( int argc, char *argv[] )
+{
+ return 0;
+}
diff --git a/examples/rpm_packaging/simple/SConstruct b/examples/rpm_packaging/simple/SConstruct
new file mode 100644
index 0000000..77c9d9b
--- /dev/null
+++ b/examples/rpm_packaging/simple/SConstruct
@@ -0,0 +1,20 @@
+import os
+
+install_dir = os.path.join( ARGUMENTS.get('prefix', '/'), 'bin/' )
+prog_install = Install( install_dir , Program( 'main.c') )
+
+Tag( prog_install, unix_attr = '(0755, root, users)' )
+
+Package( projectname = 'foo',
+ version = '1.2.3',
+ type = 'rpm',
+ license = 'gpl',
+ summary = 'bla bla bla',
+ packageversion = 0,
+ x_rpm_Group = 'Application/office',
+ description = 'this should be reallly really long',
+ source_url = 'http://foo.org/foo-1.2.3.tar.gz',
+ source = [ prog_install ],
+ )
+
+Alias( 'install', prog_install )
diff --git a/examples/rpm_packaging/simple/main.c b/examples/rpm_packaging/simple/main.c
new file mode 100644
index 0000000..5940dd6
--- /dev/null
+++ b/examples/rpm_packaging/simple/main.c
@@ -0,0 +1,4 @@
+int main( int argc, char *argv[] )
+{
+ return 0;
+}
diff --git a/examples/src_packaging/SConstruct b/examples/src_packaging/SConstruct
new file mode 100644
index 0000000..1e662af
--- /dev/null
+++ b/examples/src_packaging/SConstruct
@@ -0,0 +1,11 @@
+from glob import glob
+
+src_files = glob( 'src/*.c' )
+include_files = glob( 'src/*.h' )
+
+SharedLibrary( 'foobar', src_files )
+
+Package( projectname = 'libfoobar',
+ version = '1.2.3',
+ type = [ 'src_zip', 'src_targz', 'src_tarbz2' ],
+ source = FindSourceFiles() )
diff --git a/examples/src_packaging/src/foobar.c b/examples/src_packaging/src/foobar.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/src_packaging/src/foobar.c
diff --git a/examples/src_packaging/src/foobar.h b/examples/src_packaging/src/foobar.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/src_packaging/src/foobar.h
diff --git a/runtest.py b/runtest.py
index ffaa68b..f99f5a1 100644
--- a/runtest.py
+++ b/runtest.py
@@ -165,8 +165,6 @@ opts, args = getopt.getopt(sys.argv[1:], "ab:df:hlno:P:p:qv:Xx:t",
['all', 'aegis', 'baseline=', 'builddir=',
'debug', 'file=', 'help',
'list', 'no-exec', 'noqmtest', 'output=',
- 'package=', 'passed', 'python=',
- 'qmtest', 'quiet', 'spe=',
'version=', 'exec=', 'time',
'verbose=', 'xml'])
@@ -525,6 +523,11 @@ os.environ['SCONS_VERSION'] = version
old_pythonpath = os.environ.get('PYTHONPATH')
+# FIXME: the following is necessary to pull in half of the testing
+# harness from $srcdir/etc. Those modules should be transfered
+# to QMTest/ once we completely cut over to using that as
+# the harness, in which case this manipulation of PYTHONPATH
+# should be able to go away.
pythonpaths = [ pythonpath_dir ]
for dir in sp:
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index a010c4a..04e68a3 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -287,6 +287,7 @@ class _ActionAction(ActionBase):
target = [target]
if not SCons.Util.is_List(source):
source = [source]
+
if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
if presub is _null:
presub = self.presub
@@ -335,6 +336,7 @@ class _ActionAction(ActionBase):
os.chdir(save_cwd)
if s and save_cwd:
print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
+
return stat
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index c4b1d1d..d21de46 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -669,6 +669,16 @@ class BuilderBase:
"""
self.emitter[suffix] = emitter
+ def push_emitter(self, emitter):
+ """Add a emitter to the beginning of the emitter list of this Builder.
+
+ This creates an empty list if the emitter is None.
+ """
+ if not self.emitter:
+ self.emitter = ListEmitter( [emitter] )
+ else:
+ self.emitter.insert(0, emitter)
+
def add_src_builder(self, builder):
"""
Add a new Builder to the list of src_builders.
@@ -704,7 +714,7 @@ class BuilderBase:
for suf in bld.src_suffixes(env):
sdict[suf] = bld
return sdict
-
+
def src_builder_sources(self, env, source, overwarn={}):
source_factory = env.get_factory(self.source_factory)
slist = env.arg2nodes(source, source_factory)
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 136c3f7..9d0ad82 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -133,7 +133,11 @@ Chmod = ActionFactory(os.chmod,
lambda dest, mode: 'Chmod("%s", 0%o)' % (dest, mode))
def copy_func(dest, src):
- if os.path.isfile(src):
+ if SCons.Util.is_List(src) and os.path.isdir(dest):
+ for file in src:
+ shutil.copy(file, dest)
+ return 0
+ elif os.path.isfile(src):
return shutil.copy(src, dest)
else:
return shutil.copytree(src, dest, 1)
@@ -173,33 +177,6 @@ Touch = ActionFactory(touch_func,
lambda file: 'Touch("%s")' % file)
# Internal utility functions
-def installFunc(dest, source, env):
- """Install a source file or directory into a destination by copying,
- (including copying permission/mode bits)."""
-
- if os.path.isdir(source):
- if os.path.exists(dest):
- if not os.path.isdir(dest):
- raise SCons.Errors.UserError, "cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source))
- else:
- parent = os.path.split(dest)[0]
- if not os.path.exists(parent):
- os.makedirs(parent)
- shutil.copytree(source, dest)
- else:
- shutil.copy2(source, dest)
- st = os.stat(source)
- os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
-
- return 0
-
-def installStr(dest, source, env):
- source = str(source)
- if os.path.isdir(source):
- type = 'directory'
- else:
- type = 'file'
- return 'Install %s: "%s" as "%s"' % (type, source, dest)
def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
"""
@@ -385,9 +362,6 @@ ConstructionEnvironment = {
'DSUFFIXES' : SCons.Tool.DSuffixes,
'ENV' : {},
'IDLSUFFIXES' : SCons.Tool.IDLSuffixes,
- 'INSTALL' : installFunc,
- 'INSTALLSTR' : installStr,
- '_installStr' : installStr,
'LATEXSUFFIXES' : SCons.Tool.LaTeXSuffixes,
'_concat' : _concat,
'_defines' : _defines,
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index bffe0ef..8a7721e 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -6,7 +6,7 @@ construction information to the build engine.
Keyword arguments supplied when the construction Environment
is created are construction variables used to initialize the
-Environment
+Environment
"""
#
@@ -72,27 +72,6 @@ CalculatorArgs = {}
# which seem to mess up its ability to reference SCons directly.
UserError = SCons.Errors.UserError
-def installFunc(target, source, env):
- """Install a source file into a target using the function specified
- as the INSTALL construction variable."""
- try:
- install = env['INSTALL']
- except KeyError:
- raise SCons.Errors.UserError('Missing INSTALL construction variable.')
- return install(target[0].path, source[0].path, env)
-
-def installString(target, source, env):
- s = env.get('INSTALLSTR', '')
- if callable(s):
- return s(target[0].path, source[0].path, env)
- else:
- return env.subst_target_source(s, 0, target, source)
-
-installAction = SCons.Action.Action(installFunc, installString)
-
-InstallBuilder = SCons.Builder.Builder(action=installAction,
- name='InstallBuilder')
-
def alias_builder(env, target, source):
pass
@@ -370,7 +349,7 @@ class SubstitutionEnvironment:
nodes.append(v)
else:
nodes.append(v)
-
+
return nodes
def gvars(self):
@@ -717,6 +696,17 @@ class SubstitutionEnvironment:
self[key] = t
return self
+# Used by the FindSourceFiles() method, below.
+# Stuck here for support of pre-2.2 Python versions.
+def build_source(ss, result):
+ for s in ss:
+ if isinstance(s, SCons.Node.FS.Dir):
+ build_source(s.all_children(), result)
+ elif s.has_builder():
+ build_source(s.sources, result)
+ elif isinstance(s.disambiguate(), SCons.Node.FS.File):
+ result.append(s)
+
class Base(SubstitutionEnvironment):
"""Base class for "real" construction Environments. These are the
primary objects used to communicate dependency and construction
@@ -895,7 +885,7 @@ class Base(SubstitutionEnvironment):
self._memo['_gsm'] = result
return result
-
+
def get_scanner(self, skey):
"""Find the appropriate scanner given a key (usually a file suffix).
"""
@@ -1006,7 +996,7 @@ class Base(SubstitutionEnvironment):
orig = self._dict[envname][name]
nv = SCons.Util.AppendPath(orig, newpath, sep)
-
+
if not self._dict.has_key(envname):
self._dict[envname] = {}
@@ -1122,7 +1112,7 @@ class Base(SubstitutionEnvironment):
for path in paths:
dir,name = os.path.split(str(path))
- if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix:
+ if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix:
return path
def ParseConfig(self, command, function=None, unique=1):
@@ -1261,7 +1251,7 @@ class Base(SubstitutionEnvironment):
orig = self._dict[envname][name]
nv = SCons.Util.PrependPath(orig, newpath, sep)
-
+
if not self._dict.has_key(envname):
self._dict[envname] = {}
@@ -1593,50 +1583,6 @@ class Base(SubstitutionEnvironment):
t.add_ignore(dlist)
return tlist
- def Install(self, dir, source):
- """Install specified files in the given directory."""
- try:
- dnodes = self.arg2nodes(dir, self.fs.Dir)
- except TypeError:
- fmt = "Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?"
- raise SCons.Errors.UserError, fmt % str(dir)
- try:
- sources = self.arg2nodes(source, self.fs.Entry)
- except TypeError:
- if SCons.Util.is_List(source):
- s = repr(map(str, source))
- else:
- s = str(source)
- fmt = "Source `%s' of Install() is neither a file nor a directory. Install() source must be one or more files or directories"
- raise SCons.Errors.UserError, fmt % s
- tgt = []
- for dnode in dnodes:
- for src in sources:
- # Prepend './' so the lookup doesn't interpret an initial
- # '#' on the file name portion as meaning the Node should
- # be relative to the top-level SConstruct directory.
- target = dnode.Entry('.'+os.sep+src.name)
- tgt.extend(InstallBuilder(self, target, src))
- return tgt
-
- def InstallAs(self, target, source):
- """Install sources as targets."""
- sources = self.arg2nodes(source, self.fs.Entry)
- targets = self.arg2nodes(target, self.fs.Entry)
- if len(sources) != len(targets):
- if not SCons.Util.is_List(target):
- target = [target]
- if not SCons.Util.is_List(source):
- source = [source]
- t = repr(map(str, target))
- s = repr(map(str, source))
- fmt = "Target (%s) and source (%s) lists of InstallAs() must be the same length."
- raise SCons.Errors.UserError, fmt % (t, s)
- result = []
- for src, tgt in map(lambda x, y: (x, y), sources, targets):
- result.extend(InstallBuilder(self, tgt, src))
- return result
-
def Literal(self, string):
return SCons.Subst.Literal(string)
@@ -1681,7 +1627,7 @@ class Base(SubstitutionEnvironment):
SCons.SConsign.File(name, dbm_module)
def SideEffect(self, side_effect, target):
- """Tell scons that side_effects are built as side
+ """Tell scons that side_effects are built as side
effects of building targets."""
side_effects = self.arg2nodes(side_effect, self.fs.Entry)
targets = self.arg2nodes(target, self.fs.Entry)
@@ -1753,6 +1699,39 @@ class Base(SubstitutionEnvironment):
"""
return SCons.Node.Python.Value(value, built_value)
+ def FindSourceFiles(self, node='.'):
+ """ returns a list of all source files.
+ """
+ node = self.arg2nodes(node, self.fs.Entry)[0]
+
+ sources = []
+ # Uncomment this and get rid of the global definition when we
+ # drop support for pre-2.2 Python versions.
+ #def build_source(ss, result):
+ # for s in ss:
+ # if isinstance(s, SCons.Node.FS.Dir):
+ # build_source(s.all_children(), result)
+ # elif s.has_builder():
+ # build_source(s.sources, result)
+ # elif isinstance(s.disambiguate(), SCons.Node.FS.File):
+ # result.append(s)
+ build_source(node.all_children(), sources)
+
+ # now strip the build_node from the sources by calling the srcnode
+ # function
+ def get_final_srcnode(file):
+ srcnode = file.srcnode()
+ while srcnode != file.srcnode():
+ srcnode = file.srcnode()
+ return srcnode
+
+ # get the final srcnode for all nodes, this means stripping any
+ # attached build node.
+ map( get_final_srcnode, sources )
+
+ # remove duplicates
+ return list(set(sources))
+
class OverrideEnvironment(Base):
"""A proxy that overrides variables in a wrapped construction
environment by returning values from an overrides dictionary in
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index d4e6bc5..20c1eac 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -2204,16 +2204,16 @@ f5: \
exc_caught = None
try:
env.Tool('does_not_exist')
- except SCons.Errors.UserError:
+ except SCons.Errors.EnvironmentError:
exc_caught = 1
- assert exc_caught, "did not catch expected UserError"
+ assert exc_caught, "did not catch expected EnvironmentError"
exc_caught = None
try:
env.Tool('$NONE')
- except SCons.Errors.UserError:
+ except SCons.Errors.EnvironmentError:
exc_caught = 1
- assert exc_caught, "did not catch expected UserError"
+ assert exc_caught, "did not catch expected EnvironmentError"
# Use a non-existent toolpath directory just to make sure we
# can call Tool() with the keyword argument.
@@ -2821,78 +2821,6 @@ def generate(env):
assert i.__class__.__name__ == 'Dir', i.__class__.__name__
assert i.path == 'dir2'
- def test_Install(self):
- """Test the Install method"""
- env = self.TestEnvironment(FOO='iii', BAR='jjj')
-
- tgt = env.Install('export', [ 'build/foo1', 'build/foo2' ])
- paths = map(str, tgt)
- paths.sort()
- expect = map(os.path.normpath, [ 'export/foo1', 'export/foo2' ])
- assert paths == expect, paths
- for tnode in tgt:
- assert tnode.builder == InstallBuilder
-
- tgt = env.Install('$FOO', [ 'build/${BAR}1', 'build/${BAR}2' ])
- paths = map(str, tgt)
- paths.sort()
- expect = map(os.path.normpath, [ 'iii/jjj1', 'iii/jjj2' ])
- assert paths == expect, paths
- for tnode in tgt:
- assert tnode.builder == InstallBuilder
-
- tgt = env.Install('export', 'build')
- paths = map(str, tgt)
- paths.sort()
- expect = [os.path.join('export', 'build')]
- assert paths == expect, paths
- for tnode in tgt:
- assert tnode.builder == InstallBuilder
-
- tgt = env.Install('export', ['build', 'build/foo1'])
- paths = map(str, tgt)
- paths.sort()
- expect = [
- os.path.join('export', 'build'),
- os.path.join('export', 'foo1'),
- ]
- assert paths == expect, paths
- for tnode in tgt:
- assert tnode.builder == InstallBuilder
-
- tgt = env.Install('export', 'subdir/#file')
- assert str(tgt[0]) == os.path.normpath('export/#file'), str(tgt[0])
-
- env.File('export/foo1')
-
- exc_caught = None
- try:
- tgt = env.Install('export/foo1', 'build/foo1')
- except SCons.Errors.UserError, e:
- exc_caught = 1
- assert exc_caught, "UserError should be thrown reversing the order of Install() targets."
- expect = "Target `export/foo1' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?"
- assert str(e) == expect, e
-
- def test_InstallAs(self):
- """Test the InstallAs method"""
- env = self.TestEnvironment(FOO='iii', BAR='jjj')
-
- tgt = env.InstallAs(target=string.split('foo1 foo2'),
- source=string.split('bar1 bar2'))
- assert len(tgt) == 2, len(tgt)
- paths = map(lambda x: str(x.sources[0]), tgt)
- paths.sort()
- expect = map(os.path.normpath, [ 'bar1', 'bar2' ])
- assert paths == expect, paths
- for tnode in tgt:
- assert tnode.builder == InstallBuilder
-
- tgt = env.InstallAs(target='${FOO}.t', source='${BAR}.s')[0]
- assert tgt.path == 'iii.t'
- assert tgt.sources[0].path == 'jjj.s'
- assert tgt.builder == InstallBuilder
-
def test_Literal(self):
"""Test the Literal() method"""
env = self.TestEnvironment(FOO='fff', BAR='bbb')
diff --git a/src/engine/SCons/Errors.py b/src/engine/SCons/Errors.py
index 3a3306e..3ee7ff4 100644
--- a/src/engine/SCons/Errors.py
+++ b/src/engine/SCons/Errors.py
@@ -48,6 +48,9 @@ class UserError(Exception):
class StopError(Exception):
pass
+class EnvironmentError(Exception):
+ pass
+
class ExplicitExit(Exception):
def __init__(self, node=None, status=None, *args):
self.node = node
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 152a389..ad21a4d 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -488,7 +488,7 @@ class EntryProxy(SCons.Util.Proxy):
def __get_dir(self):
return EntryProxy(self.get().dir)
-
+
dictSpecialAttrs = { "base" : __get_base_path,
"posix" : __get_posix_path,
"windows" : __get_windows_path,
@@ -543,7 +543,7 @@ class Base(SCons.Node.Node):
def __init__(self, name, directory, fs):
"""Initialize a generic Node.FS.Base object.
-
+
Call the superclass initialization, take care of setting up
our relative and absolute paths, identify our parent
directory, and indicate that this node should use
@@ -842,7 +842,7 @@ class Entry(Base):
def get_contents(self):
"""Fetch the contents of the entry.
-
+
Since this should return the real contents from the file
system, we check to see into what sort of subclass we should
morph this Entry."""
@@ -898,7 +898,7 @@ class LocalFS:
if SCons.Memoize.use_memoizer:
__metaclass__ = SCons.Memoize.Memoized_Metaclass
-
+
# This class implements an abstraction layer for operations involving
# a local file system. Essentially, this wraps any function in
# the os, os.path or shutil modules that we use to actually go do
@@ -1009,7 +1009,7 @@ class FS(LocalFS):
self.Top.path = '.'
self.Top.tpath = '.'
self._cwd = self.Top
-
+
def set_SConstruct_dir(self, dir):
self.SConstruct_dir = dir
@@ -1091,7 +1091,7 @@ class FS(LocalFS):
last_orig = path_orig.pop() # strip last element
last_norm = path_norm.pop() # strip last element
-
+
# Lookup the directory
for orig, norm in map(None, path_orig, path_norm):
try:
@@ -1133,7 +1133,7 @@ class FS(LocalFS):
# disk where we just created a File node, and vice versa.
result.diskcheck_match()
- directory.entries[last_norm] = result
+ directory.entries[last_norm] = result
directory.add_wkid(result)
else:
e.must_be_same(fsclass)
@@ -1211,7 +1211,7 @@ class FS(LocalFS):
directory = self.Dir(directory)
name, directory = self._transformPath(name, directory)
return self._doLookup(klass, name, directory, create)
-
+
def File(self, name, directory = None, create = 1):
"""Lookup or create a File node with the specified name. If
the name is a relative path (begins with ./, ../, or a file name),
@@ -1223,7 +1223,7 @@ class FS(LocalFS):
specified path.
"""
return self.Entry(name, directory, create, File)
-
+
def Dir(self, name, directory = None, create = 1):
"""Lookup or create a Dir node with the specified name. If
the name is a relative path (begins with ./, ../, or a file name),
@@ -1235,11 +1235,11 @@ class FS(LocalFS):
specified path.
"""
return self.Entry(name, directory, create, Dir)
-
+
def BuildDir(self, build_dir, src_dir, duplicate=1):
"""Link the supplied build directory to the source directory
for purposes of building files."""
-
+
if not isinstance(src_dir, SCons.Node.Node):
src_dir = self.Dir(src_dir)
if not isinstance(build_dir, SCons.Node.Node):
@@ -1377,7 +1377,7 @@ class Dir(Base):
pass
if duplicate != None:
node.duplicate=duplicate
-
+
def __resetDuplicate(self, node):
if node != self:
node.duplicate = node.get_dir().duplicate
@@ -1388,7 +1388,8 @@ class Dir(Base):
def Dir(self, name):
"""Create a directory node named 'name' relative to this directory."""
- return self.fs.Dir(name, self)
+ dir = self.fs.Dir(name, self)
+ return dir
def File(self, name):
"""Create a file node named 'name' relative to this directory."""
@@ -1484,7 +1485,7 @@ class Dir(Base):
path_elems = ['..'] * (len(self.path_elements) - i) \
+ map(lambda n: n.name, other.path_elements[i:])
-
+
result = string.join(path_elems, os.sep)
memo_dict[other] = result
@@ -2154,7 +2155,7 @@ class File(Base):
def _rmv_existing(self):
self.clear_memoized_values()
Unlink(self, [], None)
-
+
def prepare(self):
"""Prepare for this file to be created."""
SCons.Node.Node.prepare(self)
diff --git a/src/engine/SCons/Options/OptionsTests.py b/src/engine/SCons/Options/OptionsTests.py
index 1d9b851..95bd1cd 100644
--- a/src/engine/SCons/Options/OptionsTests.py
+++ b/src/engine/SCons/Options/OptionsTests.py
@@ -472,8 +472,8 @@ B: b - alpha test
"""Test generating custom format help text"""
opts = SCons.Options.Options()
- def my_format(env, opt, help, default, actual):
- return '%s %s %s %s\n' % (opt, default, actual, help)
+ def my_format(env, opt, help, default, actual, aliases):
+ return '%s %s %s %s %s\n' % (opt, default, actual, help, aliases)
opts.FormatOptionHelpText = my_format
@@ -499,18 +499,18 @@ B: b - alpha test
opts.Update(env, {})
expect = """\
-ANSWER 42 54 THE answer to THE question
-B 42 54 b - alpha test
-A 42 54 a - alpha test
+ANSWER 42 54 THE answer to THE question ['ANSWER']
+B 42 54 b - alpha test ['B']
+A 42 54 a - alpha test ['A']
"""
text = opts.GenerateHelpText(env)
assert text == expect, text
expectAlpha = """\
-A 42 54 a - alpha test
-ANSWER 42 54 THE answer to THE question
-B 42 54 b - alpha test
+A 42 54 a - alpha test ['A']
+ANSWER 42 54 THE answer to THE question ['ANSWER']
+B 42 54 b - alpha test ['B']
"""
text = opts.GenerateHelpText(env, sort=cmp)
assert text == expectAlpha, text
diff --git a/src/engine/SCons/Options/PathOption.py b/src/engine/SCons/Options/PathOption.py
index 683ede0..995eeb9 100644
--- a/src/engine/SCons/Options/PathOption.py
+++ b/src/engine/SCons/Options/PathOption.py
@@ -128,7 +128,12 @@ class _PathOptionClass:
"""
if validator is None:
validator = self.PathExists
- return (key, '%s ( /path/to/%s )' % (help, key), default,
- validator, None)
+
+ if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key):
+ return (key, '%s ( /path/to/%s )' % (help, key[0]), default,
+ validator, None)
+ else:
+ return (key, '%s ( /path/to/%s )' % (help, key), default,
+ validator, None)
PathOption = _PathOptionClass()
diff --git a/src/engine/SCons/Options/__init__.py b/src/engine/SCons/Options/__init__.py
index 66c2143..ebd6052 100644
--- a/src/engine/SCons/Options/__init__.py
+++ b/src/engine/SCons/Options/__init__.py
@@ -44,31 +44,47 @@ from PathOption import PathOption # okay
class Options:
+ instance=None
+
"""
Holds all the options, updates the environment with the variables,
and renders the help text.
"""
- def __init__(self, files=None, args={}):
+ def __init__(self, files=None, args={}, is_global=1):
"""
files - [optional] List of option configuration files to load
- (backward compatibility) If a single string is passed it is
+ (backward compatibility) If a single string is passed it is
automatically placed in a file list
"""
-
self.options = []
self.args = args
self.files = None
if SCons.Util.is_String(files):
- self.files = [ files ]
+ self.files = [ files ]
elif files:
- self.files = files
+ self.files = files
+
+ # create the singleton instance
+ if is_global:
+ self=Options.instance
+
+ if not Options.instance:
+ Options.instance=self
def _do_add(self, key, help="", default=None, validator=None, converter=None):
class Option:
pass
option = Option()
- option.key = key
+
+ # if we get a list or a tuple, we take the first element as the
+ # option key and store the remaining in aliases.
+ if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key):
+ option.key = key[0]
+ option.aliases = key[1:]
+ else:
+ option.key = key
+ option.aliases = [ key ]
option.help = help
option.default = default
option.validator = validator
@@ -111,7 +127,7 @@ class Options:
Each list element is a tuple/list of arguments to be passed on
to the underlying method for adding options.
-
+
Example:
opt.AddOptions(
('debug', '', 0),
@@ -147,11 +163,14 @@ class Options:
# finally set the values specified on the command line
if args is None:
args = self.args
- values.update(args)
+
+ for arg, value in args.items():
+ for option in self.options:
+ if arg in option.aliases + [ option.key ]:
+ values[option.key]=value
# put the variables in the environment:
- # (don't copy over variables that are not declared
- # as options)
+ # (don't copy over variables that are not declared as options)
for option in self.options:
try:
env[option.key] = values[option.key]
@@ -204,7 +223,7 @@ class Options:
# Convert stuff that has a repr() that
# cannot be evaluated into a string
value = SCons.Util.to_String(value)
-
+
defaultVal = env.subst(SCons.Util.to_String(option.default))
if option.converter:
defaultVal = option.converter(defaultVal)
@@ -238,12 +257,19 @@ class Options:
actual = env.subst('${%s}' % opt.key)
else:
actual = None
- return self.FormatOptionHelpText(env, opt.key, opt.help, opt.default, actual)
+ return self.FormatOptionHelpText(env, opt.key, opt.help, opt.default, actual, opt.aliases)
lines = filter(None, map(format, options))
return string.join(lines, '')
- format = '\n%s: %s\n default: %s\n actual: %s\n'
+ format = '\n%s: %s\n default: %s\n actual: %s\n'
+ format_ = '\n%s: %s\n default: %s\n actual: %s\n aliases: %s\n'
+
+ def FormatOptionHelpText(self, env, key, help, default, actual, aliases=[]):
+ # Don't display the key name itself as an alias.
+ aliases = filter(lambda a, k=key: a != k, aliases)
+ if len(aliases)==0:
+ return self.format % (key, help, default, actual)
+ else:
+ return self.format_ % (key, help, default, actual, aliases)
- def FormatOptionHelpText(self, env, key, help, default, actual):
- return self.format % (key, help, default, actual)
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index 9288b85..8204c65 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -313,6 +313,9 @@ GlobalDefaultEnvironmentFunctions = [
'Split',
'TargetSignatures',
'Value',
+ 'Tag',
+ 'FindInstalledFiles',
+ 'FindSourceFiles',
]
GlobalDefaultBuilders = [
@@ -340,6 +343,7 @@ GlobalDefaultBuilders = [
'Tar',
'TypeLibrary',
'Zip',
+ 'Package',
]
for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders:
diff --git a/src/engine/SCons/Tool/ToolTests.py b/src/engine/SCons/Tool/ToolTests.py
index 52c032f..b9230f1 100644
--- a/src/engine/SCons/Tool/ToolTests.py
+++ b/src/engine/SCons/Tool/ToolTests.py
@@ -67,7 +67,7 @@ class ToolTestCase(unittest.TestCase):
try:
p = SCons.Tool.Tool('_does_not_exist_')
- except SCons.Errors.UserError:
+ except SCons.Errors.EnvironmentError:
pass
else:
raise
diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py
index dea77fd..bf3df56 100644
--- a/src/engine/SCons/Tool/__init__.py
+++ b/src/engine/SCons/Tool/__init__.py
@@ -15,7 +15,7 @@ tool definition.
#
# __COPYRIGHT__
-#
+#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
@@ -88,8 +88,11 @@ class Tool:
module = self._tool_module()
self.generate = module.generate
self.exists = module.exists
+ if hasattr(module, 'options'):
+ self.options = module.options
def _tool_module(self):
+ # TODO: Interchange zipimport with normal initilization for better error reporting
oldpythonpath = sys.path
sys.path = self.toolpath + sys.path
@@ -102,6 +105,8 @@ class Tool:
if file:
file.close()
except ImportError, e:
+ if str(e)!="No module named %s"%self.name:
+ raise SCons.Errors.EnvironmentError, e
try:
import zipimport
except ImportError:
@@ -130,6 +135,8 @@ class Tool:
file.close()
return module
except ImportError, e:
+ if e!="No module named %s"%self.name:
+ raise SCons.Errors.EnvironmentError, e
try:
import zipimport
importer = zipimport.zipimporter( sys.modules['SCons.Tool'].__path__[0] )
@@ -138,10 +145,10 @@ class Tool:
return module
except ImportError, e:
m = "No tool named '%s': %s" % (self.name, e)
- raise SCons.Errors.UserError, m
+ raise SCons.Errors.EnvironmentError, m
except ImportError, e:
m = "No tool named '%s': %s" % (self.name, e)
- raise SCons.Errors.UserError, m
+ raise SCons.Errors.EnvironmentError, m
def __call__(self, env, *args, **kw):
if self.init_kw is not None:
@@ -154,6 +161,16 @@ class Tool:
else:
kw = self.init_kw
env.Append(TOOLS = [ self.name ])
+ if hasattr(self, 'options'):
+ from SCons.Options import Options
+ if not env.has_key('options'):
+ from SCons.Script import ARGUMENTS
+ env['options']=Options(args=ARGUMENTS)
+ opts=env['options']
+
+ self.options(opts)
+ opts.Update(env)
+
apply(self.generate, ( env, ) + args, kw)
def __str__(self):
@@ -341,7 +358,7 @@ def FindAllTools(tools, env):
def ToolExists(tool, env=env):
return Tool(tool).exists(env)
return filter (ToolExists, tools)
-
+
def tool_list(platform, env):
# XXX this logic about what tool to prefer on which platform
@@ -415,11 +432,11 @@ def tool_list(platform, env):
ars = ['ar', 'mslib']
c_compiler = FindTool(c_compilers, env) or c_compilers[0]
-
+
# XXX this logic about what tool provides what should somehow be
# moved into the tool files themselves.
if c_compiler and c_compiler == 'mingw':
- # MinGW contains a linker, C compiler, C++ compiler,
+ # MinGW contains a linker, C compiler, C++ compiler,
# Fortran compiler, archiver and assembler:
cxx_compiler = None
linker = None
@@ -439,6 +456,7 @@ def tool_list(platform, env):
other_tools = FindAllTools(['BitKeeper', 'CVS',
'dmd',
+ 'install', 'filesystem',
'dvipdf', 'dvips', 'gs',
'jar', 'javac', 'javah',
'latex', 'lex',
@@ -449,11 +467,11 @@ def tool_list(platform, env):
# 'Subversion',
'swig',
'tar', 'tex',
- 'yacc', 'zip'],
+ 'yacc', 'zip', 'rpm', 'wix'],
env)
tools = ([linker, c_compiler, cxx_compiler,
fortran_compiler, assembler, ar]
+ other_tools)
-
+
return filter(lambda x: x, tools)
diff --git a/src/engine/SCons/Tool/bcc32.py b/src/engine/SCons/Tool/bcc32.py
index 86ca076..517ea29 100644
--- a/src/engine/SCons/Tool/bcc32.py
+++ b/src/engine/SCons/Tool/bcc32.py
@@ -42,12 +42,7 @@ def findIt(program, env):
borwin = env.WhereIs(program) or SCons.Util.WhereIs(program)
if borwin:
dir = os.path.dirname(borwin)
- path = env['ENV'].get('PATH', [])
- if not path:
- path = []
- if SCons.Util.is_String(path):
- path = string.split(path, os.pathsep)
- env['ENV']['PATH'] = string.join([dir]+path, os.pathsep)
+ env.PrependENVPath('PATH', dir)
return borwin
def generate(env):
diff --git a/src/engine/SCons/Tool/cc.py b/src/engine/SCons/Tool/cc.py
index d1a287a..bccd821 100644
--- a/src/engine/SCons/Tool/cc.py
+++ b/src/engine/SCons/Tool/cc.py
@@ -73,6 +73,18 @@ def generate(env):
shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
+#<<<<<<< .working
+#
+# env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS'
+# # It's a hack to test for darwin here, but the alternative of creating
+# # an applecc.py to contain this seems overkill. Maybe someday the Apple
+# # platform will require more setup and this logic will be moved.
+# env['FRAMEWORKS'] = SCons.Util.CLVar('')
+# env['FRAMEWORKPATH'] = SCons.Util.CLVar('')
+# if env['PLATFORM'] == 'darwin':
+# env['_CCCOMCOM'] = env['_CCCOMCOM'] + ' $_FRAMEWORKPATH'
+#=======
+#>>>>>>> .merge-right.r1907
add_common_cc_variables(env)
diff --git a/src/engine/SCons/Tool/filesystem.py b/src/engine/SCons/Tool/filesystem.py
new file mode 100644
index 0000000..5e631fd
--- /dev/null
+++ b/src/engine/SCons/Tool/filesystem.py
@@ -0,0 +1,89 @@
+"""SCons.Tool.filesystem
+
+Tool-specific initialization for the filesystem tools.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+import SCons
+from SCons.Tool.install import copyFunc
+
+copyToBuilder, copyAsBuilder = None, None
+
+def copyto_emitter(target, source, env):
+ """ changes the path of the source to be under the target (which
+ are assumed to be directories.
+ """
+ n_target = []
+
+ for t in target:
+ n_target = n_target + map( lambda s, t=t: t.File( str( s ) ), source )
+
+ return (n_target, source)
+
+def copy_action_func(target, source, env):
+ assert( len(target) == len(source) ), "\ntarget: %s\nsource: %s" %(map(str, target),map(str, source))
+
+ for t, s in zip(target, source):
+ if copyFunc(t.get_path(), s.get_path(), env):
+ return 1
+
+ return 0
+
+def copy_action_str(target, source, env):
+ return env.subst_target_source(env['COPYSTR'], 0, target, source)
+
+copy_action = SCons.Action.Action( copy_action_func, copy_action_str )
+
+def generate(env):
+ try:
+ env['BUILDERS']['CopyTo']
+ env['BUILDERS']['CopyAs']
+ except KeyError, e:
+ global copyToBuilder
+ if copyToBuilder is None:
+ copyToBuilder = SCons.Builder.Builder(
+ action = copy_action,
+ target_factory = env.fs.Dir,
+ source_factory = env.fs.Entry,
+ multi = 1,
+ emitter = [ copyto_emitter, ] )
+
+ global copyAsBuilder
+ if copyAsBuilder is None:
+ copyAsBuilder = SCons.Builder.Builder(
+ action = copy_action,
+ target_factory = env.fs.Entry,
+ source_factory = env.fs.Entry )
+
+ env['BUILDERS']['CopyTo'] = copyToBuilder
+ env['BUILDERS']['CopyAs'] = copyAsBuilder
+
+ env['COPYSTR'] = 'Copy file(s): "$SOURCES" to "$TARGETS"'
+
+def exists(env):
+ return 1
diff --git a/src/engine/SCons/Tool/install.py b/src/engine/SCons/Tool/install.py
new file mode 100644
index 0000000..5083c9f
--- /dev/null
+++ b/src/engine/SCons/Tool/install.py
@@ -0,0 +1,221 @@
+"""SCons.Tool.install
+
+Tool-specific initialization for the install tool.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+import SCons.Action
+import shutil, os, stat
+from SCons.Util import make_path_relative
+
+#
+# We keep track of *all* installed files.
+_INSTALLED_FILES = []
+
+#
+# Functions doing the actual work of the Install Builder.
+#
+def copyFunc(dest, source, env):
+ """Install a source file or directory into a destination by copying,
+ (including copying permission/mode bits)."""
+
+ if os.path.isdir(source):
+ if os.path.exists(dest):
+ if not os.path.isdir(dest):
+ raise SCons.Errors.UserError, "cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source))
+ else:
+ parent = os.path.split(dest)[0]
+ if not os.path.exists(parent):
+ os.makedirs(parent)
+ shutil.copytree(source, dest)
+ else:
+ shutil.copy2(source, dest)
+ st = os.stat(source)
+ os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+
+ return 0
+
+def installFunc(target, source, env):
+ """Install a source file into a target using the function specified
+ as the INSTALL construction variable."""
+ try:
+ install = env['INSTALL']
+ except KeyError:
+ raise SCons.Errors.UserError('Missing INSTALL construction variable.')
+
+ assert( len(target)==len(source) )
+ for t,s in zip(target,source):
+ if install(t.get_path(),s.get_path(),env):
+ return 1
+
+ return 0
+
+def stringFunc(target, source, env):
+ installstr = env.get('INSTALLSTR')
+ if installstr:
+ return env.subst_target_source(installstr, 0, target, source)
+ target = str(target[0])
+ source = str(source[0])
+ if os.path.isdir(source):
+ type = 'directory'
+ else:
+ type = 'file'
+ return 'Install %s: "%s" as "%s"' % (type, source, target)
+
+#
+# Emitter functions
+#
+def add_targets_to_INSTALLED_FILES(target, source, env):
+ """ an emitter that adds all target files to the list stored in the
+ _INSTALLED_FILES global variable. This way all installed files of one
+ scons call will be collected.
+ """
+ global _INSTALLED_FILES
+ files = _INSTALLED_FILES
+ #files.extend( [ x for x in target if not x in files ] )
+ for x in target:
+ if not x in files:
+ files.append(x)
+ return (target, source)
+
+class DESTDIR_factory:
+ """ a node factory, where all files will be relative to the dir supplied
+ in the constructor.
+ """
+ def __init__(self, env, dir):
+ self.env = env
+ self.dir = env.arg2nodes( dir, env.fs.Dir )[0]
+
+ def Entry(self, name):
+ name = make_path_relative(name)
+ return self.dir.Entry(name)
+
+ def Dir(self, name):
+ name = make_path_relative(name)
+ return self.dir.Dir(name)
+
+#
+# The Builder Definition
+#
+install_action = SCons.Action.Action(installFunc, stringFunc)
+installas_action = SCons.Action.Action(installFunc, stringFunc)
+
+InstallBuilder, InstallAsBuilder = None, None
+BaseInstallBuilder = None
+
+added = None
+
+def generate(env):
+
+ from SCons.Script import AddOption, GetOption
+ global added
+ if not added:
+ added = 1
+ AddOption('--install-sandbox',
+ dest='install_sandbox',
+ type="string",
+ action="store",
+ help='A directory under which all installed files will be placed.')
+
+ try:
+ env['BUILDERS']['Install']
+ env['BUILDERS']['InstallAs']
+
+ except KeyError, e:
+ install_sandbox = GetOption('install_sandbox')
+ if install_sandbox:
+ target_factory = DESTDIR_factory(env, install_sandbox)
+ else:
+ target_factory = env.fs
+
+ global BaseInstallBuilder
+ if BaseInstallBuilder is None:
+ BaseInstallBuilder = SCons.Builder.Builder(
+ action = install_action,
+ target_factory = target_factory.Entry,
+ source_factory = env.fs.Entry,
+ multi = 1,
+ emitter = [ add_targets_to_INSTALLED_FILES, ],
+ name = 'InstallBuilder')
+
+ global InstallBuilder
+ if InstallBuilder is None:
+ def InstallBuilderWrapper(env, target, source, dir=None, target_factory=target_factory):
+ if target and dir:
+ raise SCons.Errors.UserError, "Both target and dir defined for Install(), only one may be defined."
+ if not dir:
+ dir=target
+ try:
+ dnodes = env.arg2nodes(dir, target_factory.Dir)
+ except TypeError:
+ raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir)
+ sources = env.arg2nodes(source, env.fs.Entry)
+ tgt = []
+ for dnode in dnodes:
+ for src in sources:
+ # Prepend './' so the lookup doesn't interpret an initial
+ # '#' on the file name portion as meaning the Node should
+ # be relative to the top-level SConstruct directory.
+ target = env.fs.Entry('.'+os.sep+src.name, dnode)
+ tgt.extend(BaseInstallBuilder(env, target, src))
+ return tgt
+
+ InstallBuilder = InstallBuilderWrapper
+
+ global InstallAsBuilder
+ if InstallAsBuilder is None:
+ def InstallAsBuilderWrapper(env, target, source):
+ result = []
+ for src, tgt in map(lambda x, y: (x, y), source, target):
+ result.extend(BaseInstallBuilder(env, tgt, src))
+ return result
+
+ InstallAsBuilder = InstallAsBuilderWrapper
+
+ env['BUILDERS']['Install'] = InstallBuilder
+ env['BUILDERS']['InstallAs'] = InstallAsBuilder
+
+ # We'd like to initialize this doing something like the following,
+ # but there isn't yet support for a ${SOURCE.type} expansion that
+ # will print "file" or "directory" depending on what's being
+ # installed. For now we punt by not initializing it, and letting
+ # the stringFunc() that we put in the action fall back to the
+ # hand-crafted default string if it's not set.
+ #
+ #try:
+ # env['INSTALLSTR']
+ #except KeyError:
+ # env['INSTALLSTR'] = 'Install ${SOURCE.type}: "$SOURCES" as "$TARGETS"'
+
+ try:
+ env['INSTALL']
+ except KeyError:
+ env['INSTALL'] = copyFunc
+
+def exists(env):
+ return 1
diff --git a/src/engine/SCons/Tool/ipkg.py b/src/engine/SCons/Tool/ipkg.py
new file mode 100644
index 0000000..3f6ca23
--- /dev/null
+++ b/src/engine/SCons/Tool/ipkg.py
@@ -0,0 +1,61 @@
+"""SCons.Tool.ipkg
+
+Tool-specific initialization for ipkg.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+
+The ipkg tool calls the ipkg-build. Its only argument should be the
+packages fake_root.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Builder
+from os import popen
+from os.path import dirname
+
+def generate(env):
+ """Add Builders and construction variables for ipkg to an Environment."""
+ try:
+ bld = env['BUILDERS']['Ipkg']
+ except KeyError:
+ bld = SCons.Builder.Builder( action = '$IPKGCOM',
+ suffix = '$IPKGSUFFIX',
+ source_scanner = None,
+ target_scanner = None)
+ env['BUILDERS']['Ipkg'] = bld
+
+ env['IPKG'] = 'ipkg-build'
+ env['IPKGCOM'] = '$IPKG $IPKGFLAGS ${SOURCE}'
+ env['IPKGUSER'] = popen('id -un').read().strip()
+ env['IPKGGROUP'] = popen('id -gn').read().strip()
+ env['IPKGFLAGS'] = SCons.Util.CLVar('-o $IPKGUSER -g $IPKGGROUP')
+ env['IPKGSUFFIX'] = '.ipk'
+
+def exists(env):
+ return env.Detect('ipkg-build')
diff --git a/src/engine/SCons/Tool/link.py b/src/engine/SCons/Tool/link.py
index b9fb71d..be1a81a 100644
--- a/src/engine/SCons/Tool/link.py
+++ b/src/engine/SCons/Tool/link.py
@@ -48,7 +48,7 @@ def generate(env):
"""Add Builders and construction variables for gnulink to an Environment."""
SCons.Tool.createSharedLibBuilder(env)
SCons.Tool.createProgBuilder(env)
-
+
env['SHLINK'] = '$LINK'
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')
env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
diff --git a/src/engine/SCons/Tool/linkloc.py b/src/engine/SCons/Tool/linkloc.py
index f7c2c5a..f5256c9 100644
--- a/src/engine/SCons/Tool/linkloc.py
+++ b/src/engine/SCons/Tool/linkloc.py
@@ -96,8 +96,8 @@ def generate(env):
msvs_version = env.get('MSVS_VERSION')
include_path, lib_path, exe_path = get_msvc_paths(env, version = msvs_version)
- env['ENV']['LIB'] = lib_path
- env['ENV']['PATH'] = exe_path
+ env['ENV']['LIB'] = lib_path
+ env.PrependENVPath('PATH', exe_path)
addPharLapPaths(env)
diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py
index cc7f584..359c40f 100644
--- a/src/engine/SCons/Tool/mingw.py
+++ b/src/engine/SCons/Tool/mingw.py
@@ -107,15 +107,7 @@ def generate(env):
mingw = find(env)
if mingw:
dir = os.path.dirname(mingw)
-
- # The mingw bin directory must be added to the path:
- path = env['ENV'].get('PATH', [])
- if not path:
- path = []
- if SCons.Util.is_String(path):
- path = string.split(path, os.pathsep)
-
- env['ENV']['PATH'] = string.join([dir] + path, os.pathsep)
+ env.PrependENVPath('PATH', dir )
# Most of mingw is the same as gcc and friends...
diff --git a/src/engine/SCons/Tool/packaging.xml b/src/engine/SCons/Tool/packaging.xml
new file mode 100644
index 0000000..e2d0fb4
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging.xml
@@ -0,0 +1,73 @@
+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="packaging">
+<summary>
+A framework for building binary and source packages.
+</summary>
+</tool>
+
+<builder name="Package">
+<summary>
+Builds a Binary Package of the given source files.
+
+<example>
+env.Package(source = FindInstalledFiles())
+</example>
+</summary>
+</builder>
+
+<cvar name="JAR">
+<summary>
+The Java archive tool.
+</summary>
+</cvar>
+
+<cvar name="JARCHDIR">
+<summary>
+The directory to which the Java archive tool should change
+(using the
+<option>-C</option>
+option).
+</summary>
+</cvar>
+
+<cvar name="JARCOM">
+<summary>
+The command line used to call the Java archive tool.
+</summary>
+</cvar>
+
+<cvar name="JARCOMSTR">
+<summary>
+The string displayed when the Java archive tool
+is called
+If this is not set, then &cv-JARCOM; (the command line) is displayed.
+
+<example>
+env = Environment(JARCOMSTR = "JARchiving $SOURCES into $TARGET")
+</example>
+</summary>
+</cvar>
+
+<cvar name="JARFLAGS">
+<summary>
+General options passed to the Java archive tool.
+By default this is set to
+<option>cf</option>
+to create the necessary
+<command>jar</command>
+file.
+</summary>
+</cvar>
+
+<cvar name="JARSUFFIX">
+<summary>
+The suffix for Java archives:
+<filename>.jar</filename>
+by default.
+</summary>
+</cvar>
diff --git a/src/engine/SCons/Tool/packaging/__init__.py b/src/engine/SCons/Tool/packaging/__init__.py
new file mode 100644
index 0000000..c5f40b7
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/__init__.py
@@ -0,0 +1,332 @@
+"""SCons.Tool.Packaging
+
+SCons Packaging Tool.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Environment
+from SCons.Options import *
+from SCons.Errors import *
+from SCons.Util import is_List, make_path_relative
+
+import os, imp
+import SCons.Defaults
+
+__all__ = [ 'src_targz', 'src_tarbz2', 'src_zip', 'tarbz2', 'targz', 'zip', 'rpm', 'msi', 'ipk' ]
+
+#
+# Utility and Builder function
+#
+def Tag(env, target, source, *more_tags, **kw_tags):
+ """ Tag a file with the given arguments, just sets the accordingly named
+ attribute on the file object.
+
+ TODO: FIXME
+ """
+ if not target:
+ target=source
+ first_tag=None
+ else:
+ first_tag=source
+
+ if first_tag:
+ kw_tags[first_tag[0]] = ''
+
+ if len(kw_tags) == 0 and len(more_tags) == 0:
+ raise UserError, "No tags given."
+
+ # XXX: sanity checks
+ for x in more_tags:
+ kw_tags[x] = ''
+
+ if not SCons.Util.is_List(target):
+ target=[target]
+ else:
+ # hmm, sometimes the target list, is a list of a list
+ # make sure it is flattened prior to processing.
+ # TODO: perhaps some bug ?!?
+ target=env.Flatten(target)
+
+ for t in target:
+ for (k,v) in kw_tags.items():
+ # all file tags have to start with PACKAGING_, so we can later
+ # differentiate between "normal" object attributes and the
+ # packaging attributes. As the user should not be bothered with
+ # that, the prefix will be added here if missing.
+ #if not k.startswith('PACKAGING_'):
+ if k[:10] != 'PACKAGING_':
+ k='PACKAGING_'+k
+ setattr(t, k, v)
+
+def Package(env, target=None, source=None, **kw):
+ """ Entry point for the package tool.
+ """
+ # first check some arguments
+ if not source:
+ source = env.FindInstalledFiles()
+
+ if len(source)==0:
+ raise UserError, "No source for Package() given"
+
+ # has the option for this Tool been set?
+ try: kw['PACKAGETYPE']=env['PACKAGETYPE']
+ except KeyError: pass
+
+ if not kw.has_key('PACKAGETYPE') or kw['PACKAGETYPE']==None:
+ if env['BUILDERS'].has_key('Tar'):
+ kw['PACKAGETYPE']='targz'
+ elif env['BUILDERS'].has_key('Zip'):
+ kw['PACKAGETYPE']='zip'
+ else:
+ raise UserError, "No type for Package() given"
+ PACKAGETYPE=kw['PACKAGETYPE']
+ if not is_List(PACKAGETYPE):
+ #PACKAGETYPE=PACKAGETYPE.split(',')
+ PACKAGETYPE=string.split(PACKAGETYPE, ',')
+
+ # now load the needed packagers.
+ def load_packager(type):
+ try:
+ file,path,desc=imp.find_module(type, __path__)
+ return imp.load_module(type, file, path, desc)
+ except ImportError, e:
+ raise EnvironmentError("packager %s not available: %s"%(type,str(e)))
+
+ packagers=map(load_packager, PACKAGETYPE)
+
+ # now try to setup the default_target and the default PACKAGEROOT
+ # arguments.
+ try:
+ # fill up the target list with a default target name until the PACKAGETYPE
+ # list is of the same size as the target list.
+ if target==None or target==[]:
+ target=["%(NAME)s-%(VERSION)s"%kw]
+
+ size_diff=len(PACKAGETYPE)-len(target)
+ if size_diff>0:
+ target.extend([target]*size_diff)
+
+ if not kw.has_key('PACKAGEROOT'):
+ kw['PACKAGEROOT']="%(NAME)s-%(VERSION)s"%kw
+
+ except KeyError, e:
+ raise SCons.Errors.UserError( "Missing PackageTag '%s'"%e.args[0] )
+
+ # setup the source files
+ source=env.arg2nodes(source, env.fs.Entry)
+
+ # call the packager to setup the dependencies.
+ targets=[]
+ try:
+ for packager in packagers:
+ t=apply(packager.package, [env,target,source], kw)
+ targets.extend(t)
+
+ except KeyError, e:
+ raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\
+ % (e.args[0],packager.__name__) )
+ except TypeError, e:
+ # this exception means that a needed argument for the packager is
+ # missing. As our packagers get their "tags" as named function
+ # arguments we need to find out which one is missing.
+ from inspect import getargspec
+ args,varargs,varkw,defaults=getargspec(packager.package)
+ if defaults!=None:
+ args=args[:-len(defaults)] # throw away arguments with default values
+ map(args.remove, 'env target source'.split())
+ # now remove any args for which we have a value in kw.
+ #args=[x for x in args if not kw.has_key(x)]
+ args=filter(lambda x, kw=kw: not kw.has_key(x), args)
+
+ if len(args)==0:
+ raise # must be a different error, so reraise
+ elif len(args)==1:
+ raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\
+ % (args[0],packager.__name__) )
+ else:
+ raise SCons.Errors.UserError( "Missing PackageTags '%s' for %s packager"\
+ % (", ".join(args),packager.__name__) )
+
+ target=env.arg2nodes(target, env.fs.Entry)
+ targets.extend(env.Alias( 'package', targets ))
+ return targets
+
+def FindSourceFiles(env, target=None, source=None ):
+ """ returns a list of all children of the target nodes, which have no
+ children. This selects all leaves of the DAG that gets build by SCons for
+ handling dependencies.
+ """
+ if target==None: target = '.'
+
+ nodes = env.arg2nodes(target, env.fs.Entry)
+
+ sources = []
+ def build_source(ss):
+ for s in ss:
+ if s.__class__==SCons.Node.FS.Dir:
+ build_source(s.all_children())
+ elif not s.has_builder() and s.__class__==SCons.Node.FS.File:
+ sources.append(s)
+ else:
+ build_source(s.sources)
+
+ for node in nodes:
+ build_source(node.all_children())
+
+ # now strip the build_node from the sources by calling the srcnode
+ # function
+ def get_final_srcnode(file):
+ srcnode = file.srcnode()
+ while srcnode != file.srcnode():
+ srcnode = file.srcnode()
+ return srcnode
+
+ # get the final srcnode for all nodes, this means stripping any
+ # attached build node.
+ map( get_final_srcnode, sources )
+
+ # remove duplicates
+ return list(set(sources))
+
+def FindInstalledFiles(env, source=[], target=[]):
+ """ returns the list of all targets of the Install and InstallAs Builder.
+ """
+ from SCons.Tool import install
+ return install._INSTALLED_FILES
+
+#
+# SCons tool initialization functions
+#
+def generate(env):
+ try:
+ env['BUILDERS']['Package']
+ env['BUILDERS']['Tag']
+ env['BUILDERS']['FindSourceFiles']
+ env['BUILDERS']['FindInstalledFiles']
+ except KeyError:
+ env['BUILDERS']['Package'] = Package
+ env['BUILDERS']['Tag'] = Tag
+ env['BUILDERS']['FindSourceFiles'] = FindSourceFiles
+ env['BUILDERS']['FindInstalledFiles'] = FindInstalledFiles
+
+def exists(env):
+ return 1
+
+def options(opts):
+ opts.AddOptions(
+ EnumOption( [ 'PACKAGETYPE', '--package-type' ],
+ 'the type of package to create.',
+ None, allowed_values=map( str, __all__ ),
+ ignorecase=2
+ )
+ )
+
+def copy_attr(f1, f2):
+ """ copies the special packaging file attributes from f1 to f2.
+ """
+ #pattrs = [x for x in dir(f1) if not hasattr(f2, x) and\
+ # x.startswith('PACKAGING_')]
+ copyit = lambda x, f2=f2: not hasattr(f2, x) and x[:10] == 'PACKAGING_'
+ pattrs = filter(copyit, dir(f1))
+ for attr in pattrs:
+ setattr(f2, attr, getattr(f1, attr))
+#
+# Emitter functions which are reused by the various packagers
+#
+def packageroot_emitter(pkg_root, honor_install_location=1):
+ """ creates the packageroot emitter.
+
+ The package root emitter uses the CopyAs builder to copy all source files
+ to the directory given in pkg_root.
+
+ If honor_install_location is set and the copied source file has an
+ PACKAGING_INSTALL_LOCATION attribute, the PACKAGING_INSTALL_LOCATION is
+ used as the new name of the source file under pkg_root.
+
+ The source file will not be copied if it is already under the the pkg_root
+ directory.
+
+ All attributes of the source file will be copied to the new file.
+ """
+ def package_root_emitter(target, source, env, pkg_root=pkg_root, honor_install_location=honor_install_location):
+ pkgroot = pkg_root
+ # make sure the packageroot is a Dir object.
+ if SCons.Util.is_String(pkgroot): pkgroot=env.Dir(pkgroot)
+
+ def copy_file_to_pkg_root(file, env=env, pkgroot=pkgroot, honor_install_location=honor_install_location):
+ if file.is_under(pkgroot):
+ return file
+ else:
+ if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\
+ honor_install_location:
+ new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION)
+ else:
+ new_name=make_path_relative(file.get_path())
+
+ new_file=pkgroot.File(new_name)
+ new_file=env.CopyAs(new_file, file)[0]
+
+ copy_attr(file, new_file)
+
+ return new_file
+ return (target, map(copy_file_to_pkg_root, source))
+ return package_root_emitter
+
+from SCons.Warnings import warn, Warning
+
+def stripinstall_emitter():
+ """ create the a emitter which:
+ * strips of the Install Builder of the source target, and stores the
+ install location as the "PACKAGING_INSTALL_LOCATION" of the given source
+ File object. This effectively avoids having to execute the Install
+ Action while storing the needed install location.
+ * warns about files that are mangled by this emitter which have no
+ Install Builder.
+ """
+ def strip_install_emitter(target, source, env):
+ def has_no_install_location(file):
+ return not (file.has_builder() and\
+ hasattr(file.builder, 'name') and\
+ (file.builder.name=="InstallBuilder" or\
+ file.builder.name=="InstallAsBuilder"))
+
+ if len(filter(has_no_install_location, source)):
+ warn(Warning, "there are file to package which have no\
+ InstallBuilder attached, this might lead to irreproducible packages")
+
+ n_source=[]
+ for s in source:
+ if has_no_install_location(s):
+ n_source.append(s)
+ else:
+ for ss in s.sources:
+ n_source.append(ss)
+ copy_attr(s, ss)
+ setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path())
+
+ return (target, n_source)
+ return strip_install_emitter
diff --git a/src/engine/SCons/Tool/packaging/__init__.xml b/src/engine/SCons/Tool/packaging/__init__.xml
new file mode 100644
index 0000000..8e7f652
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/__init__.xml
@@ -0,0 +1,522 @@
+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="Packaging">
+<summary>
+TODO
+</summary>
+</tool>
+
+<builder name="Package">
+Builds software distribution packages. Packages consist of files to install
+and packaging information. The former may be specified with the source
+parameter and may be left out, in which case the &-bFindInstalledFiles;
+function will collect all files that have an &-bInstall; or
+&-bInstallAs; Builder attached. The target, if not specified will be deduced
+from additional information given to this Builder.
+
+The packaging information is specified with the help of construction Variables
+documented below. This information is called a tag to stress that some of them
+can also be attached to files with the &-bTag; Builder.The mandatory ones will
+complain if they were not specified. They vary depending on chosen target
+packager.
+
+The target packager may be selected with the "PACKAGETYPE" command line option
+or with the &-tPACKAGETYPE; construction variable. Currently there are six
+packagers available:
+
+ * msi - Microsoft Installer
+ * rpm - Redhat Package Manger
+ * ipkg - Itsy Package Management System
+ * tarbz, tarbz and zip
+
+An updated list is always available under the "package_type" option when
+running "scons --help" on a project that has packaging activated.
+
+<example>
+env = Environment(tools=['default', 'packaging'])
+env.Install('/bin/', 'my_program')
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGEVERSION = 0,
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'balalalalal',
+ DESCRIPTION = 'this should be really really long',
+ X_RPM_GROUP = 'Application/fu',
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+</example>
+
+<cvar name="DESCRIPTION">
+<summary>
+A long description of what the project is about.
+</summary>
+</cvar>
+
+<cvar name="DESCRIPTION_<lang>">
+<summary>
+TODO
+</summary>
+</cvar>
+
+
+<cvar name="SUMMARY">
+<summary>
+A short summary of what the project is about.
+</summary>
+</cvar>
+
+<cvar name="LICENSE">
+<summary>
+The shorthand of the license this project is under (gpl, lpgl, bsd etc.).
+</summary>
+</cvar>
+
+<cvar name="NAME">
+<summary>
+Specfies the name of the project to package.
+</summary>
+</cvar>
+
+<cvar name="VERSION">
+<summary>
+The version of the project, given as a string.
+</summary>
+</cvar>
+
+<cvar name="PACKAGEVERSION">
+<summary>
+The version of the package, if only changes in the package were done. Currently
+only used by the rpm packager.
+</summary>
+</cvar>
+
+<cvar name="PACKAGETYPE">
+<summary>
+Selects the package type to build. Currently those are available:
+
+ * msi - Microsoft Installer
+ * rpm - Redhat Package Manger
+ * ipkg - Itsy Package Management System
+ * tarbz2, targz and zip - tarball and zip packager
+ * src_tarbz2, src_targz and src_zip - source tarbarll and zip packager
+
+This may be overridden with the "package_type" command line option.
+</summary>
+</cvar>
+
+<cvar name="VENDOR">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="SOURCE_URL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="ARCHITECURE">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="PACKAGEROOT">
+<summary>
+defines the directory where all files in resulting archive will be placed if
+applicable. The default value is "$NAME-$VERSION".
+</summary>
+</cvar>
+
+<cvar name="LICENSE">
+<summary>
+Short name of the license your package is under. Example: gpl, lgpl, bsd ...
+See http://www.opensource.org/licenses/alphabetical
+</summary>
+</cvar>
+
+<cvar name="CHANGE_SPECFILE">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="CHANGELOG">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_PREINSTALL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_DEFATTR">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_POSTINSTALL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_PREUNINSTALL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_POSTUNINSTALL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_VERIFY">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_PREP">
+<summary>
+internal, but overridable
+</summary>
+</cvar>
+
+<cvar name="X_RPM_BUILD">
+<summary>
+internal, but overridable
+</summary>
+</cvar>
+
+<cvar name="X_RPM_INSTALL">
+<summary>
+internal, but overridable
+</summary>
+</cvar>
+
+<cvar name="X_RPM_CLEAN">
+<summary>
+internal, but overridable
+</summary>
+</cvar>
+
+<cvar name="X_RPM_URL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_GROUP">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_GROUP_<lang>">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_DISTRIBUTION">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_ICON">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_PACKAGER">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_REQUIRES">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_PROVIDES">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_CONFLICTS">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_BUILDREQUIRES">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_SERIAL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_EPOCH">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_AUTOREQPROV">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_EXCLUDEARCH">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_EXLUSIVEARCH">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_PREFIX">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_CONFLICTS">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_BUILDROOT">
+<summary>
+internal, but overridable
+</summary>
+</cvar>
+
+<cvar name="X_RPM_GROUP_<lang>">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_LICENSE_TEXT">
+<summary>
+The text of the software license in rtf format. Carriage return chars will be
+replaced with the rtf equivalent \\par.
+</summary>
+</cvar>
+
+<cvar name="X_MSI_LANGUAGE">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_UPGRADE_CODE">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_PRIORITY">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_SECTION">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_MAINTAINER">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_DEPENDS">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_DESCRIPTION">
+<summary>
+default is "$SUMMARY\n$DESCRIPTION"
+</summary>
+</cvar>
+
+<cvar name="X_IPK_POSTRM">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_PRERM">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_POSTINST">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_IPK_PREINST">
+<summary>
+TODO
+</summary>
+</cvar>
+
+</builder>
+
+<builder name="Tag">
+Leaves hints for the Package() Builder on how specific should be packaged.
+All those tags are optional.
+<example>
+Tag( Library( 'lib.c' ), unix-attr="0644" ) # makes sure the built library will
+ # be installed with 0644 file
+ # access mode
+Tag( 'file2.txt', doc ) # marks file2.txt to be a documentation file
+</example>
+
+<cvar name="INSTALL_LOCATION">
+<summary>
+internal, but overridable, TODO
+</summary>
+</cvar>
+
+<cvar name="CONFIG">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="CONFIG_NOREPLACE">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="DOC">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="UNIX_ATTR=">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="LANG_<lang>">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_VERIFY">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_DIR">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_DOCDIR">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_RPM_GHOST">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_FEATURE=">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_VITAL">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_FILEID">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_LONGNAME">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_SHORTNAME">
+<summary>
+TODO
+</summary>
+</cvar>
+
+<cvar name="X_MSI_SHORTNAME">
+<summary>
+TODO
+</summary>
+</cvar>
+
+</builder>
+
+<builder name="FindSourceFiles">
+A convenience function which returns all leafs of the build tree.
+</builder>
+
+<builder name="FindInstalledFiles">
+Returns all files "build" by the install builder.
+</builder>
+
+</tool>
+
diff --git a/src/engine/SCons/Tool/packaging/ipk.py b/src/engine/SCons/Tool/packaging/ipk.py
new file mode 100644
index 0000000..267fe3c
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/ipk.py
@@ -0,0 +1,179 @@
+"""SCons.Tool.Packaging.ipk
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Builder
+import SCons.Node.FS
+import os
+
+from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+
+def package(env, target, source, PACKAGEROOT, NAME, VERSION, DESCRIPTION,
+ SUMMARY, X_IPK_PRIORITY, X_IPK_SECTION, SOURCE_URL,
+ X_IPK_MAINTAINER, X_IPK_DEPENDS, **kw):
+ """ this function prepares the packageroot directory for packaging with the
+ ipkg builder.
+ """
+ SCons.Tool.Tool('ipkg').generate(env)
+
+ # setup the Ipkg builder
+ bld = env['BUILDERS']['Ipkg']
+ bld.push_emitter(packageroot_emitter(PACKAGEROOT))
+ bld.push_emitter(stripinstall_emitter())
+
+ # This should be overridable from the construction environment,
+ # which it is by using ARCHITECTURE=.
+ # Guessing based on what os.uname() returns at least allows it
+ # to work for both i386 and x86_64 Linux systems.
+ archmap = {
+ 'i686' : 'i386',
+ 'i586' : 'i386',
+ 'i486' : 'i386',
+ }
+
+ buildarchitecture = os.uname()[4]
+ buildarchitecture = archmap.get(buildarchitecture, buildarchitecture)
+
+ if kw.has_key('ARCHITECTURE'):
+ buildarchitecture = kw['ARCHITECTURE']
+
+ # setup the kw to contain the mandatory arguments to this fucntion.
+ # do this before calling any builder or setup function
+ loc=locals()
+ del loc['kw']
+ kw.update(loc)
+ del kw['source'], kw['target'], kw['env']
+
+ # generate the specfile
+ specfile = gen_ipk_dir(PACKAGEROOT, source, env, kw)
+
+ # override the default target.
+ if str(target[0])=="%s-%s"%(NAME, VERSION):
+ target=[ "%s_%s_%s.ipk"%(NAME, VERSION, buildarchitecture) ]
+
+ # now apply the Ipkg builder
+ return apply(bld, [env, target, specfile], kw)
+
+def gen_ipk_dir(proot, source, env, kw):
+ # make sure the packageroot is a Dir object.
+ if SCons.Util.is_String(proot): proot=env.Dir(proot)
+
+ # create the specfile builder
+ s_bld=SCons.Builder.Builder(
+ action = build_specfiles,
+ emitter = [stripinstall_emitter(), packageroot_emitter(proot)])
+
+ # create the specfile targets
+ spec_target=[]
+ control=proot.Dir('CONTROL')
+ spec_target.append(control.File('control'))
+ spec_target.append(control.File('conffiles'))
+ spec_target.append(control.File('postrm'))
+ spec_target.append(control.File('prerm'))
+ spec_target.append(control.File('postinst'))
+ spec_target.append(control.File('preinst'))
+
+ # apply the builder to the specfile targets
+ apply(s_bld, [env, spec_target, source], kw)
+
+ # the packageroot directory does now contain the specfiles.
+ return proot
+
+def build_specfiles(source, target, env):
+ """ filter the targets for the needed files and use the variables in env
+ to create the specfile.
+ """
+ #
+ # At first we care for the CONTROL/control file, which is the main file for ipk.
+ #
+ # For this we need to open multiple files in random order, so we store into
+ # a dict so they can be easily accessed.
+ #
+ #
+ opened_files={}
+ def open_file(needle, haystack):
+ try:
+ return opened_files[needle]
+ except KeyError:
+ file=filter(lambda x: x.get_path().rfind(needle)!=-1, haystack)[0]
+ opened_files[needle]=open(file.abspath, 'w')
+ return opened_files[needle]
+
+ control_file=open_file('control', target)
+
+ if not env.has_key('X_IPK_DESCRIPTION'):
+ env['X_IPK_DESCRIPTION']="%s\n %s"%(env['SUMMARY'],
+ env['DESCRIPTION'].replace('\n', '\n '))
+
+
+ content = """
+Package: $NAME
+Version: $VERSION
+Priority: $X_IPK_PRIORITY
+Section: $X_IPK_SECTION
+Source: $SOURCE_URL
+Architecture: $ARCHITECTURE
+Maintainer: $X_IPK_MAINTAINER
+Depends: $X_IPK_DEPENDS
+Description: $X_IPK_DESCRIPTION
+"""
+
+ control_file.write(env.subst(content))
+
+ #
+ # now handle the various other files, which purpose it is to set post-,
+ # pre-scripts and mark files as config files.
+ #
+ # We do so by filtering the source files for files which are marked with
+ # the "config" tag and afterwards we do the same for x_ipk_postrm,
+ # x_ipk_prerm, x_ipk_postinst and x_ipk_preinst tags.
+ #
+ # The first one will write the name of the file into the file
+ # CONTROL/configfiles, the latter add the content of the x_ipk_* variable
+ # into the same named file.
+ #
+ for f in [x for x in source if 'PACKAGING_CONFIG' in dir(x)]:
+ config=open_file('conffiles')
+ config.write(f.PACKAGING_INSTALL_LOCATION)
+ config.write('\n')
+
+ for str in 'POSTRM PRERM POSTINST PREINST'.split():
+ name="PACKAGING_X_IPK_%s"%str
+ for f in [x for x in source if name in dir(x)]:
+ file=open_file(name)
+ file.write(env[str])
+
+ #
+ # close all opened files
+ for f in opened_files.values():
+ f.close()
+
+ # call a user specified function
+ if env.has_key('CHANGE_SPECFILE'):
+ content += env['CHANGE_SPECFILE'](target)
+
+ return 0
diff --git a/src/engine/SCons/Tool/packaging/msi.py b/src/engine/SCons/Tool/packaging/msi.py
new file mode 100644
index 0000000..7fc7892
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/msi.py
@@ -0,0 +1,519 @@
+"""SCons.Tool.packaging.msi
+
+The msi packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import SCons
+from SCons.Action import Action
+from SCons.Builder import Builder
+
+from xml.dom.minidom import *
+from xml.sax.saxutils import escape
+
+from SCons.Tool.packaging import stripinstall_emitter
+
+#
+# Utility functions
+#
+def convert_to_id(s, id_set):
+ """ Some parts of .wxs need an Id attribute (for example: The File and
+ Directory directives. The charset is limited to A-Z, a-z, digits,
+ underscores, periods. Each Id must begin with a letter or with a
+ underscore. Google for "CNDL0015" for information about this.
+
+ Requirements:
+ * the string created must only contain chars from the target charset.
+ * the string created must have a minimal editing distance from the
+ original string.
+ * the string created must be unique for the whole .wxs file.
+
+ Observation:
+ * There are 62 chars in the charset.
+
+ Idea:
+ * filter out forbidden characters. Check for a collision with the help
+ of the id_set. Add the number of the number of the collision at the
+ end of the created string. Furthermore care for a correct start of
+ the string.
+ """
+ charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz0123456789_.'
+ if s[0] in '0123456789.':
+ s += '_'+s
+ id = filter( lambda c : c in charset, s )
+
+ # did we already generate an id for this file?
+ try:
+ return id_set[id][s]
+ except KeyError:
+ # no we did not so initialize with the id
+ if not id_set.has_key(id): id_set[id] = { s : id }
+ # there is a collision, generate an id which is unique by appending
+ # the collision number
+ else: id_set[id][s] = id + str(len(id_set[id]))
+
+ return id_set[id][s]
+
+def is_dos_short_file_name(file):
+ """ examine if the given file is in the 8.3 form.
+ """
+ fname, ext = os.path.splitext(file)
+ proper_ext = len(ext) == 0 or (2 <= len(ext) <= 4) # the ext contains the dot
+ proper_fname = file.isupper() and len(fname) <= 8
+
+ return proper_ext and proper_fname
+
+def gen_dos_short_file_name(file, filename_set):
+ """ see http://support.microsoft.com/default.aspx?scid=kb;en-us;Q142982
+
+ These are no complete 8.3 dos short names. The ~ char is missing and
+ replaced with one character from the filename. WiX warns about such
+ filenames, since a collision might occur. Google for "CNDL1014" for
+ more information.
+ """
+ # guard this to not confuse the generation
+ if is_dos_short_file_name(file):
+ return file
+
+ fname, ext = os.path.splitext(file) # ext contains the dot
+
+ # first try if it suffices to convert to upper
+ file = file.upper()
+ if is_dos_short_file_name(file):
+ return file
+
+ # strip forbidden characters.
+ forbidden = '."/[]:;=, '
+ fname = filter( lambda c : c not in forbidden, fname )
+
+ # check if we already generated a filename with the same number:
+ # thisis1.txt, thisis2.txt etc.
+ duplicate, num = not None, 1
+ while duplicate:
+ shortname = "%s%s" % (fname[:8-len(str(num))].upper(),\
+ str(num))
+ if len(ext) >= 2:
+ shortname = "%s%s" % (shortname, ext[:4].upper())
+
+ duplicate, num = shortname in filename_set, num+1
+
+ assert( is_dos_short_file_name(shortname) ), 'shortname is %s, longname is %s' % (shortname, file)
+ filename_set.append(shortname)
+ return shortname
+
+def create_feature_dict(files):
+ """ X_MSI_FEATURE and doc FileTag's can be used to collect files in a
+ hierarchy. This function collects the files into this hierarchy.
+ """
+ dict = {}
+
+ def add_to_dict( feature, file ):
+ if not SCons.Util.is_List( feature ):
+ feature = [ feature ]
+
+ for f in feature:
+ if not dict.has_key( f ):
+ dict[ f ] = [ file ]
+ else:
+ dict[ f ].append( file )
+
+ for file in files:
+ if hasattr( file, 'PACKAGING_X_MSI_FEATURE' ):
+ add_to_dict(file.PACKAGING_X_MSI_FEATURE, file)
+ elif hasattr( file, 'PACKAGING_DOC' ):
+ add_to_dict( 'PACKAGING_DOC', file )
+ else:
+ add_to_dict( 'default', file )
+
+ return dict
+
+def generate_guids(root):
+ """ generates globally unique identifiers for parts of the xml which need
+ them.
+
+ Component tags have a special requirement. Their UUID is only allowed to
+ change if the list of their contained resources has changed. This allows
+ for clean removal and proper updates.
+
+ To handle this requirement, the uuid is generated with an md5 hashing the
+ whole subtree of a xml node.
+ """
+ from md5 import md5
+
+ # specify which tags need a guid and in which attribute this should be stored.
+ needs_id = { 'Product' : 'Id',
+ 'Package' : 'Id',
+ 'Component' : 'Guid',
+ }
+
+ # find all XMl nodes matching the key, retrieve their attribute, hash their
+ # subtree, convert hash to string and add as a attribute to the xml node.
+ for (key,value) in needs_id.items():
+ node_list = root.getElementsByTagName(key)
+ attribute = value
+ for node in node_list:
+ hash = md5(node.toxml()).hexdigest()
+ hash_str = '%s-%s-%s-%s-%s' % ( hash[:8], hash[8:12], hash[12:16], hash[16:20], hash[20:] )
+ node.attributes[attribute] = hash_str
+
+
+
+def string_wxsfile(target, source, env):
+ return "building WiX file %s"%( target[0].path )
+
+def build_wxsfile(target, source, env):
+ """ compiles a .wxs file from the keywords given in env['msi_spec'] and
+ by analyzing the tree of source nodes and their tags.
+ """
+ file = open(target[0].abspath, 'w')
+
+ try:
+ # Create a document with the Wix root tag
+ doc = Document()
+ root = doc.createElement( 'Wix' )
+ root.attributes['xmlns']='http://schemas.microsoft.com/wix/2003/01/wi'
+ doc.appendChild( root )
+
+ filename_set = [] # this is to circumvent duplicates in the shortnames
+ id_set = {} # this is to circumvent duplicates in the ids
+
+ # Create the content
+ build_wxsfile_header_section(root, env)
+ build_wxsfile_file_section(root, source, env['NAME'], env['VERSION'], env['VENDOR'], filename_set, id_set)
+ generate_guids(root)
+ build_wxsfile_features_section(root, source, env['NAME'], env['VERSION'], env['SUMMARY'], id_set)
+ build_wxsfile_default_gui(root)
+ build_license_file(target[0].get_dir(), env)
+
+ # write the xml to a file
+ file.write( doc.toprettyxml() )
+
+ # call a user specified function
+ if env.has_key('CHANGE_SPECFILE'):
+ env['CHANGE_SPECFILE'](target, source)
+
+ except KeyError, e:
+ raise SCons.Errors.UserError( '"%s" package field for MSI is missing.' % e.args[0] )
+
+#
+# setup function
+#
+def create_default_directory_layout(root, NAME, VERSION, vendor, filename_set):
+ """ Create the wix default target directory layout and return the innermost
+ directory.
+
+ We assume that the XML tree delivered in the root argument already contains
+ the Product tag.
+
+ Everything is put under the PFiles directory property defined by WiX.
+ After that a directory with the 'vendor' tag is placed and then a
+ directory with the name of the project and its VERSION. This leads to the
+ following TARGET Directory Layout:
+ C:\<PFiles>\<Vendor>\<Projectname-Version>\
+ Example: C:\Programme\Company\Product-1.2\
+ """
+ doc = Document()
+ d1 = doc.createElement( 'Directory' )
+ d1.attributes['Id'] = 'TARGETDIR'
+ d1.attributes['Name'] = 'SourceDir'
+
+ d2 = doc.createElement( 'Directory' )
+ d2.attributes['Id'] = 'ProgramFilesFolder'
+ d2.attributes['Name'] = 'PFiles'
+
+ d3 = doc.createElement( 'Directory' )
+ d3.attributes['Id'] = 'vendor_folder'
+ d3.attributes['Name'] = escape( gen_dos_short_file_name( vendor, filename_set ) )
+ d3.attributes['LongName'] = escape( vendor )
+
+ d4 = doc.createElement( 'Directory' )
+ project_folder = "%s-%s" % ( NAME, VERSION )
+ d4.attributes['Id'] = 'MY_DEFAULT_FOLDER'
+ d4.attributes['Name'] = escape( gen_dos_short_file_name( project_folder, filename_set ) )
+ d4.attributes['LongName'] = escape( project_folder )
+
+ d1.childNodes.append( d2 )
+ d2.childNodes.append( d3 )
+ d3.childNodes.append( d4 )
+
+ root.getElementsByTagName('Product')[0].childNodes.append( d1 )
+
+ return d4
+
+#
+# mandatory and optional file tags
+#
+def build_wxsfile_file_section(root, files, NAME, VERSION, vendor, filename_set, id_set):
+ """ builds the Component sections of the wxs file with their included files.
+
+ Files need to be specified in 8.3 format and in the long name format, long
+ filenames will be converted automatically.
+
+ Features are specficied with the 'X_MSI_FEATURE' or 'DOC' FileTag.
+ """
+ root = create_default_directory_layout( root, NAME, VERSION, vendor, filename_set )
+ components = create_feature_dict( files )
+ factory = Document()
+
+ def get_directory( node, dir ):
+ """ returns the node under the given node representing the directory.
+
+ Returns the component node if dir is None or empty.
+ """
+ if dir == '' or not dir:
+ return node
+
+ Directory = node
+ dir_parts = dir.split(os.path.sep)
+
+ # to make sure that our directory ids are unique, the parent folders are
+ # consecutively added to upper_dir
+ upper_dir = ''
+
+ # walk down the xml tree finding parts of the directory
+ dir_parts = filter( lambda d: d != '', dir_parts )
+ for d in dir_parts[:]:
+ already_created = filter( lambda c: c.nodeName == 'Directory' and c.attributes['LongName'].value == escape(d), Directory.childNodes )
+
+ if already_created != []:
+ Directory = already_created[0]
+ dir_parts.remove(d)
+ upper_dir += d
+ else:
+ break
+
+ for d in dir_parts:
+ nDirectory = factory.createElement( 'Directory' )
+ nDirectory.attributes['LongName'] = escape( d )
+ nDirectory.attributes['Name'] = escape( gen_dos_short_file_name( d, filename_set ) )
+ upper_dir += d
+ nDirectory.attributes['Id'] = convert_to_id( upper_dir, id_set )
+
+ Directory.childNodes.append( nDirectory )
+ Directory = nDirectory
+
+ return Directory
+
+ for file in files:
+ drive, path = os.path.splitdrive( file.PACKAGING_INSTALL_LOCATION )
+ filename = os.path.basename( path )
+ dirname = os.path.dirname( path )
+
+ h = {
+ # tagname : default value
+ 'PACKAGING_X_MSI_VITAL' : 'yes',
+ 'PACKAGING_X_MSI_FILEID' : convert_to_id(filename, id_set),
+ 'PACKAGING_X_MSI_LONGNAME' : filename,
+ 'PACKAGING_X_MSI_SHORTNAME' : gen_dos_short_file_name(filename, filename_set),
+ 'PACKAGING_X_MSI_SOURCE' : file.get_path(),
+ }
+
+ # fill in the default tags given above.
+ for k,v in [ (k, v) for (k,v) in h.items() if not hasattr(file, k) ]:
+ setattr( file, k, v )
+
+ File = factory.createElement( 'File' )
+ File.attributes['LongName'] = escape( file.PACKAGING_X_MSI_LONGNAME )
+ File.attributes['Name'] = escape( file.PACKAGING_X_MSI_SHORTNAME )
+ File.attributes['Source'] = escape( file.PACKAGING_X_MSI_SOURCE )
+ File.attributes['Id'] = escape( file.PACKAGING_X_MSI_FILEID )
+ File.attributes['Vital'] = escape( file.PACKAGING_X_MSI_VITAL )
+
+ # create the <Component> Tag under which this file should appear
+ Component = factory.createElement('Component')
+ Component.attributes['DiskId'] = '1'
+ Component.attributes['Id'] = convert_to_id( filename, id_set )
+
+ # hang the component node under the root node and the file node
+ # under the component node.
+ Directory = get_directory( root, dirname )
+ Directory.childNodes.append( Component )
+ Component.childNodes.append( File )
+
+#
+# additional functions
+#
+def build_wxsfile_features_section(root, files, NAME, VERSION, SUMMARY, id_set):
+ """ This function creates the <features> tag based on the supplied xml tree.
+
+ This is achieved by finding all <component>s and adding them to a default target.
+
+ It should be called after the tree has been built completly. We assume
+ that a MY_DEFAULT_FOLDER Property is defined in the wxs file tree.
+
+ Furthermore a top-level with the name and VERSION of the software will be created.
+
+ An PACKAGING_X_MSI_FEATURE can either be a string, where the feature
+ DESCRIPTION will be the same as its title or a Tuple, where the first
+ part will be its title and the second its DESCRIPTION.
+ """
+ factory = Document()
+ Feature = factory.createElement('Feature')
+ Feature.attributes['Id'] = 'complete'
+ Feature.attributes['ConfigurableDirectory'] = 'MY_DEFAULT_FOLDER'
+ Feature.attributes['Level'] = '1'
+ Feature.attributes['Title'] = escape( '%s %s' % (NAME, VERSION) )
+ Feature.attributes['Description'] = escape( SUMMARY )
+ Feature.attributes['Display'] = 'expand'
+
+ for (feature, files) in create_feature_dict(files).items():
+ SubFeature = factory.createElement('Feature')
+ SubFeature.attributes['Level'] = '1'
+
+ if SCons.Util.is_Tuple(feature):
+ SubFeature.attributes['Id'] = convert_to_id( feature[0], id_set )
+ SubFeature.attributes['Title'] = escape(feature[0])
+ SubFeature.attributes['Description'] = escape(feature[1])
+ else:
+ SubFeature.attributes['Id'] = convert_to_id( feature, id_set )
+ if feature=='default':
+ SubFeature.attributes['Description'] = 'Main Part'
+ SubFeature.attributes['Title'] = 'Main Part'
+ elif feature=='PACKAGING_DOC':
+ SubFeature.attributes['Description'] = 'Documentation'
+ SubFeature.attributes['Title'] = 'Documentation'
+ else:
+ SubFeature.attributes['Description'] = escape(feature)
+ SubFeature.attributes['Title'] = escape(feature)
+
+ # build the componentrefs. As one of the design decision is that every
+ # file is also a component we walk the list of files and create a
+ # reference.
+ for f in files:
+ ComponentRef = factory.createElement('ComponentRef')
+ ComponentRef.attributes['Id'] = convert_to_id( os.path.basename(f.get_path()), id_set )
+ SubFeature.childNodes.append(ComponentRef)
+
+ Feature.childNodes.append(SubFeature)
+
+ root.getElementsByTagName('Product')[0].childNodes.append(Feature)
+
+def build_wxsfile_default_gui(root):
+ """ this function adds a default GUI to the wxs file
+ """
+ factory = Document()
+ Product = root.getElementsByTagName('Product')[0]
+
+ UIRef = factory.createElement('UIRef')
+ UIRef.attributes['Id'] = 'WixUI_Mondo'
+ Product.childNodes.append(UIRef)
+
+ UIRef = factory.createElement('UIRef')
+ UIRef.attributes['Id'] = 'WixUI_ErrorProgressText'
+ Product.childNodes.append(UIRef)
+
+def build_license_file(directory, spec):
+ """ creates a License.rtf file with the content of "X_MSI_LICENSE_TEXT"
+ in the given directory
+ """
+ name, text = '', ''
+
+ try:
+ name = spec['LICENSE']
+ text = spec['X_MSI_LICENSE_TEXT']
+ except KeyError:
+ pass # ignore this as X_MSI_LICENSE_TEXT is optional
+
+ if name!='' or text!='':
+ file = open( os.path.join(directory.get_path(), 'License.rtf'), 'w' )
+ file.write('{\\rtf')
+ if text!='':
+ file.write(text.replace('\n', '\\par '))
+ else:
+ file.write(name+'\\par\\par')
+ file.write('}')
+ file.close()
+
+#
+# mandatory and optional package tags
+#
+def build_wxsfile_header_section(root, spec):
+ """ Adds the xml file node which define the package meta-data.
+ """
+ # Create the needed DOM nodes and add them at the correct position in the tree.
+ factory = Document()
+ Product = factory.createElement( 'Product' )
+ Package = factory.createElement( 'Package' )
+
+ root.childNodes.append( Product )
+ Product.childNodes.append( Package )
+
+ # set "mandatory" default values
+ if not spec.has_key('X_MSI_LANGUAGE'):
+ spec['X_MSI_LANGUAGE'] = '1033' # select english
+
+ # mandatory sections, will throw a KeyError if the tag is not available
+ Product.attributes['Name'] = escape( spec['NAME'] )
+ Product.attributes['Version'] = escape( spec['VERSION'] )
+ Product.attributes['Manufacturer'] = escape( spec['vendor'] )
+ Product.attributes['Language'] = escape( spec['X_MSI_LANGUAGE'] )
+ Package.attributes['Description'] = escape( spec['SUMMARY'] )
+
+ # now the optional tags, for which we avoid the KeyErrror exception
+ if spec.has_key( 'DESCRIPTION' ):
+ Package.attributes['Comments'] = escape( spec['DESCRIPTION'] )
+
+ if spec.has_key( 'X_MSI_UPGRADE_CODE' ):
+ Package.attributes['X_MSI_UPGRADE_CODE'] = escape( spec['X_MSI_UPGRADE_CODE'] )
+
+ # We hardcode the media tag as our current model cannot handle it.
+ Media = factory.createElement('Media')
+ Media.attributes['Id'] = '1'
+ Media.attributes['Cabinet'] = 'default.cab'
+ Media.attributes['EmbedCab'] = 'yes'
+ root.getElementsByTagName('Product')[0].childNodes.append(Media)
+
+# this builder is the entry-point for .wxs file compiler.
+wxs_builder = Builder(
+ action = Action( build_wxsfile, string_wxsfile ),
+ emitter = stripinstall_emitter(),
+ suffix = '.wxs' )
+
+def package(env, target, source, PACKAGEROOT, NAME, VERSION,
+ DESCRIPTION, SUMMARY, **kw):
+ # make sure that the Wix Builder is in the environment
+ SCons.Tool.Tool('wix').generate(env)
+
+ # get put the keywords for the specfile compiler. These are the arguments
+ # given to the package function and all optional ones stored in kw, minus
+ # the the source, target and env one.
+ loc = locals()
+ del loc['kw']
+ kw.update(loc)
+ del kw['source'], kw['target'], kw['env']
+
+ # put the arguments into the env and call the specfile builder.
+ env['msi_spec'] = kw
+ specfile = apply( wxs_builder, [env, target, source], kw )
+
+ # now call the WiX Tool with the built specfile added as a source.
+ msifile = env.WiX(target, specfile)
+
+ # return the target and source tuple.
+ return (msifile, source+[specfile])
+
diff --git a/src/engine/SCons/Tool/packaging/packager.py b/src/engine/SCons/Tool/packaging/packager.py
new file mode 100644
index 0000000..7aca2b6
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/packager.py
@@ -0,0 +1,218 @@
+"""SCons.Tool.Packaging.packager
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import SCons.Defaults
+from SCons.Util import strip_abs_path
+
+class Packager:
+ """ abstract superclass of all packagers.
+
+ Defines the minimal set of function which need to be implemented in order
+ to create a new packager.
+ """
+ def __init__(self):
+ self.specfile_suffix = '.spec'
+
+ def create_builder(self, env, kw):
+ raise Exception( "%s does not implement create_builder()" % (self.__class__.__name_) )
+
+ def add_targets(self, kw):
+ """ In the absence of a target this method creates a default one of
+ the spec given in the kw argument.
+ """
+ if not kw.has_key('target'):
+ NAME, VERSION = kw['projectname'], kw['version']
+ kw['target'] = [ "%s-%s"%(NAME,VERSION) ]
+
+ return kw
+
+ def strip_install_emitter(self, target, source, env):
+ """ this emitter assert that all files in source have an InstallBuilder
+ attached. We take their sources and copy over the file tags, so the
+ the builder this emitter is attached to is independent of the *installed*
+ files.
+ """
+ tag_factories = [ LocationTagFactory() ]
+
+ def has_no_install_location(file):
+ return not ( file.has_builder() and (file.builder.name == 'InstallBuilder' or file.builder.name == 'InstallAsBuilder') )
+
+ # check if all source file belong into this package.
+ files = filter( has_no_install_location, source )
+ if len( files ) != 0:
+ raise SCons.Errors.UserError( "There are files which have no Install() Builder attached and are therefore not packageable\n%s" % map( lambda x: x.get_path(), files ) )
+
+ # All source files have an InstallBuilder attached and we don't want our
+ # package to be dependent on the *installed* files but only on the
+ # files that will be installed. Therefore we only care for sources of
+ # the files in the source list.
+ n_source = []
+
+ for s in source:
+ n_s = s.sources[0]
+ n_s.set_tags( s.get_tags(tag_factories) )
+ n_source.append( n_s )
+
+ return ( target, n_source )
+
+
+class BinaryPackager(Packager):
+ """ abstract superclass for all packagers creating a binary package.
+
+ Binary packagers are seperated from source packager by their requirement to
+ create a specfile or manifest file. This file contains the contents of the
+ binary packager together with some information about specific files.
+
+ This superclass provides two needed facilities:
+ * its specfile_emitter function sets up the correct list of source file
+ and warns about files with no InstallBuilder attached.
+ """
+ def create_specfile_targets(self, kw):
+ """ returns the specfile target name(s).
+
+ This function is called by specfile_emitter to find out the specfiles
+ target name.
+ """
+ p, v = kw['NAME'], kw['VERSION']
+ return '%s-%s' % ( p, v )
+
+ def specfile_emitter(self, target, source, env):
+ """ adds the to build specfile to the source list.
+ """
+ # create a specfile action that is executed for building the specfile
+ specfile_action = SCons.Action.Action( self.build_specfile,
+ self.string_specfile,
+ varlist=[ 'SPEC' ] )
+
+ # create a specfile Builder with the right sources attached.
+ specfile_builder = SCons.Builder.Builder( action = self.build_specfile,
+ suffix = self.specfile_suffix )
+
+ specfile = apply( specfile_builder, [ env ], {
+ 'target' : self.create_specfile_targets(env),
+ 'source' : source } )
+
+ specfile.extend( source )
+ return ( target, specfile )
+
+ def string_specfile(self, target, source, env):
+ return "building specfile %s"%(target[0].abspath)
+
+ def build_specfile(self, target, source, env):
+ """ this function is called to build the specfile of name "target"
+ from the source list and the settings in "env"
+ """
+ raise Exception( 'class does not implement build_specfile()' )
+
+class SourcePackager(Packager):
+ """ abstract superclass for all packagers which generate a source package.
+
+ They are seperated from other packager by the their package_root attribute.
+ Since before a source package is created with the help of a Tar or Zip
+ builder their content needs to be moved to a package_root. For example the
+ project foo with VERSION 1.2.3, will get its files placed in foo-1.2.3/.
+ """
+ def create_package_root(self,kw):
+ """ creates the package_r oot for a given specification dict.
+ """
+ try:
+ return kw['package_root']
+ except KeyError:
+ NAME, VERSION = kw['projectname'], kw['version']
+ return "%s-%s"%(NAME,VERSION)
+
+ def package_root_emitter(self, pkg_root, honor_install_location=1):
+ def package_root_emitter(target, source, env):
+ """ This emitter copies the sources to the src_package_root directory:
+ * if a source has an install_location, not its original name is
+ used but the one specified in the 'install_location' tag.
+ * else its original name is used.
+ * if the source file is already in the src_package_root directory,
+ nothing will be done.
+ """
+ new_source = []
+ for s in source:
+ if os.path.dirname(s.get_path()).rfind(pkg_root) != -1:
+ new_source.append(s)
+ else:
+ tags = s.get_tags()
+ new_s = None
+
+ if tags.has_key( 'install_location' ) and honor_install_location:
+ my_target = strip_abs_path(tags['install_location'])
+ else:
+ my_target = strip_abs_path(s.get_path())
+
+ new_s = env.CopyAs( os.path.join( pkg_root, my_target ), s )[0]
+
+ # store the tags of our original file in the new file.
+ new_s.set_tags( s.get_tags() )
+ new_source.append( new_s )
+
+ return (target, new_source)
+
+ return package_root_emitter
+
+class TagFactory:
+ """An instance of this class has the responsibility to generate additional
+ tags for a SCons.Node.FS.File instance.
+
+ Subclasses have to be callable. This class definition is informally
+ describing the interface.
+ """
+
+ def __call__(self, file, current_tag_dict):
+ """ This call has to return additional tags in the form of a dict.
+ """
+ pass
+
+ def attach_additional_info(self, info=None):
+ pass
+
+class LocationTagFactory(TagFactory):
+ """ This class creates the "location" tag, which describes the install
+ location of a given file.
+
+ This is done by analyzing the builder of a given file for a InstallBuilder,
+ from this builder the install location is deduced.
+ """
+
+ def __call__(self, file, current_tag_dict):
+ if current_tag_dict.has_key('install_location'):
+ return {}
+
+ if file.has_builder() and\
+ (file.builder.name == "InstallBuilder" or\
+ file.builder.name == "InstallAsBuilder") and\
+ file.has_explicit_builder():
+ return { 'install_location' : file.get_path() }
+ else:
+ return {}
+
+
diff --git a/src/engine/SCons/Tool/packaging/rpm.py b/src/engine/SCons/Tool/packaging/rpm.py
new file mode 100644
index 0000000..49b8ff8
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/rpm.py
@@ -0,0 +1,354 @@
+"""SCons.Tool.Packaging.rpm
+
+The rpm packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+
+import SCons.Builder
+
+from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter, src_targz
+
+def package(env, target, source, PACKAGEROOT, NAME, VERSION,
+ PACKAGEVERSION, DESCRIPTION, SUMMARY, X_RPM_GROUP, LICENSE,
+ **kw):
+ # initialize the rpm tool
+ SCons.Tool.Tool('rpm').generate(env)
+
+ # create the neccesary builder
+ bld = env['BUILDERS']['Rpm']
+ env['RPMFLAGS'] = SCons.Util.CLVar('-ta')
+
+ bld.push_emitter(targz_emitter)
+ bld.push_emitter(specfile_emitter)
+ bld.push_emitter(stripinstall_emitter())
+
+ # override the default target, with the rpm specific ones.
+ if str(target[0])=="%s-%s"%(NAME, VERSION):
+ # This should be overridable from the construction environment,
+ # which it is by using ARCHITECTURE=.
+ # Guessing based on what os.uname() returns at least allows it
+ # to work for both i386 and x86_64 Linux systems.
+ archmap = {
+ 'i686' : 'i386',
+ 'i586' : 'i386',
+ 'i486' : 'i386',
+ }
+
+ buildarchitecture = os.uname()[4]
+ buildarchitecture = archmap.get(buildarchitecture, buildarchitecture)
+
+ if kw.has_key('ARCHITECTURE'):
+ buildarchitecture = kw['ARCHITECTURE']
+
+ srcrpm = '%s-%s-%s.src.rpm' % (NAME, VERSION, PACKAGEVERSION)
+ binrpm = string.replace(srcrpm, 'src', buildarchitecture)
+
+ target = [ srcrpm, binrpm ]
+
+ # get the correct arguments into the kw hash
+ loc=locals()
+ del loc['kw']
+ kw.update(loc)
+ del kw['source'], kw['target'], kw['env']
+
+ # if no "SOURCE_URL" tag is given add a default one.
+ if not kw.has_key('SOURCE_URL'):
+ kw['SOURCE_URL']=(str(target[0])+".tar.gz").replace('.rpm', '')
+
+ # now call the rpm builder to actually build the packet.
+ return apply(bld, [env, target, source], kw)
+
+
+def targz_emitter(target, source, env):
+ """ Puts all source files into a tar.gz file. """
+ # the rpm tool depends on a source package, until this is chagned
+ # this hack needs to be here that tries to pack all sources in.
+ sources = env.FindSourceFiles()
+
+ # filter out the target we are building the source list for.
+ #sources = [s for s in sources if not (s in target)]
+ sources = filter(lambda s, t=target: not (s in t), sources)
+
+ # find the .spec file for rpm and add it since it is not necessarily found
+ # by the FindSourceFiles function.
+ #sources.extend( [s for s in source if str(s).rfind('.spec')!=-1] )
+ spec_file = lambda s: string.rfind(str(s), '.spec') != -1
+ sources.extend( filter(spec_file, source) )
+
+ # as the source contains the url of the source package this rpm package
+ # is built from, we extract the target name
+ #tarball = (str(target[0])+".tar.gz").replace('.rpm', '')
+ tarball = string.replace(str(target[0])+".tar.gz", '.rpm', '')
+ try:
+ #tarball = env['SOURCE_URL'].split('/')[-1]
+ tarball = string.split(env['SOURCE_URL'], '/')[-1]
+ except KeyError, e:
+ raise SCons.Errors.UserError( "Missing PackageTag '%s' for RPM packager" % e.args[0] )
+
+ tarball = src_targz.package(env, source=sources, target=tarball,
+ PACKAGEROOT=env['PACKAGEROOT'], )
+
+ return (target, tarball)
+
+def specfile_emitter(target, source, env):
+ specfile = "%s-%s" % (env['NAME'], env['VERSION'])
+
+ bld = SCons.Builder.Builder(action = build_specfile,
+ suffix = '.spec',
+ target_factory = SCons.Node.FS.File)
+
+ source.extend(bld(env, specfile, source))
+
+ return (target,source)
+
+def build_specfile(target, source, env):
+ """ Builds a RPM specfile from a dictionary with string metadata and
+ by analyzing a tree of nodes.
+ """
+ file = open(target[0].abspath, 'w')
+ str = ""
+
+ try:
+ file.write( build_specfile_header(env) )
+ file.write( build_specfile_sections(env) )
+ file.write( build_specfile_filesection(env, source) )
+ file.close()
+
+ # call a user specified function
+ if env.has_key('CHANGE_SPECFILE'):
+ env['CHANGE_SPECFILE'](target, source)
+
+ except KeyError, e:
+ raise SCons.Errors.UserError( '"%s" package field for RPM is missing.' % e.args[0] )
+
+
+#
+# mandatory and optional package tag section
+#
+def build_specfile_sections(spec):
+ """ Builds the sections of a rpm specfile.
+ """
+ str = ""
+
+ mandatory_sections = {
+ 'DESCRIPTION' : '\n%%description\n%s\n\n', }
+
+ str = str + SimpleTagCompiler(mandatory_sections).compile( spec )
+
+ optional_sections = {
+ 'DESCRIPTION_' : '%%description -l %s\n%s\n\n',
+ 'CHANGELOG' : '%%changelog\n%s\n\n',
+ 'X_RPM_PREINSTALL' : '%%pre\n%s\n\n',
+ 'X_RPM_POSTINSTALL' : '%%post\n%s\n\n',
+ 'X_RPM_PREUNINSTALL' : '%%preun\n%s\n\n',
+ 'X_RPM_POSTUNINSTALL' : '%%postun\n%s\n\n',
+ 'X_RPM_VERIFY' : '%%verify\n%s\n\n',
+
+ # These are for internal use but could possibly be overriden
+ 'X_RPM_PREP' : '%%prep\n%s\n\n',
+ 'X_RPM_BUILD' : '%%build\n%s\n\n',
+ 'X_RPM_INSTALL' : '%%install\n%s\n\n',
+ 'X_RPM_CLEAN' : '%%clean\n%s\n\n',
+ }
+
+ # Default prep, build, install and clean rules
+ # TODO: optimize those build steps, to not compile the project a second time
+ if not spec.has_key('X_RPM_PREP'):
+ spec['X_RPM_PREP'] = 'rm -rf "$RPM_BUILD_ROOT"' + '\n%setup -q'
+
+ if not spec.has_key('X_RPM_BUILD'):
+ spec['X_RPM_BUILD'] = 'mkdir "$RPM_BUILD_ROOT"'
+
+ if not spec.has_key('X_RPM_INSTALL'):
+ spec['X_RPM_INSTALL'] = 'scons --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"'
+
+ if not spec.has_key('X_RPM_CLEAN'):
+ spec['X_RPM_CLEAN'] = 'rm -rf "$RPM_BUILD_ROOT"'
+
+ str = str + SimpleTagCompiler(optional_sections, mandatory=0).compile( spec )
+
+ return str
+
+def build_specfile_header(spec):
+ """ Builds all section but the %file of a rpm specfile
+ """
+ str = ""
+
+ # first the mandatory sections
+ mandatory_header_fields = {
+ 'NAME' : '%%define name %s\nName: %%{name}\n',
+ 'VERSION' : '%%define version %s\nVersion: %%{version}\n',
+ 'PACKAGEVERSION' : '%%define release %s\nRelease: %%{release}\n',
+ 'X_RPM_GROUP' : 'Group: %s\n',
+ 'SUMMARY' : 'Summary: %s\n',
+ 'LICENSE' : 'License: %s\n', }
+
+ str = str + SimpleTagCompiler(mandatory_header_fields).compile( spec )
+
+ # now the optional tags
+ optional_header_fields = {
+ 'VENDOR' : 'Vendor: %s\n',
+ 'X_RPM_URL' : 'Url: %s\n',
+ 'SOURCE_URL' : 'Source: %s\n',
+ 'SUMMARY_' : 'Summary(%s): %s\n',
+ 'X_RPM_DISTRIBUTION' : 'Distribution: %s\n',
+ 'X_RPM_ICON' : 'Icon: %s\n',
+ 'X_RPM_PACKAGER' : 'Packager: %s\n',
+ 'X_RPM_GROUP_' : 'Group(%s): %s\n',
+
+ 'X_RPM_REQUIRES' : 'Requires: %s\n',
+ 'X_RPM_PROVIDES' : 'Provides: %s\n',
+ 'X_RPM_CONFLICTS' : 'Conflicts: %s\n',
+ 'X_RPM_BUILDREQUIRES' : 'BuildRequires: %s\n',
+
+ 'X_RPM_SERIAL' : 'Serial: %s\n',
+ 'X_RPM_EPOCH' : 'Epoch: %s\n',
+ 'X_RPM_AUTOREQPROV' : 'AutoReqProv: %s\n',
+ 'X_RPM_EXCLUDEARCH' : 'ExcludeArch: %s\n',
+ 'X_RPM_EXCLUSIVEARCH' : 'ExclusiveArch: %s\n',
+ 'X_RPM_PREFIX' : 'Prefix: %s\n',
+ 'X_RPM_CONFLICTS' : 'Conflicts: %s\n',
+
+ # internal use
+ 'X_RPM_BUILDROOT' : 'BuildRoot: %s\n', }
+
+ # fill in default values:
+ # Adding a BuildRequires renders the .rpm unbuildable under System, which
+ # are not managed by rpm, since the database to resolve this dependency is
+ # missing (take Gentoo as an example)
+# if not s.has_key('x_rpm_BuildRequires'):
+# s['x_rpm_BuildRequires'] = 'scons'
+
+ if not spec.has_key('X_RPM_BUILDROOT'):
+ spec['X_RPM_BUILDROOT'] = '%{_tmppath}/%{name}-%{version}-%{release}'
+
+ str = str + SimpleTagCompiler(optional_header_fields, mandatory=0).compile( spec )
+ return str
+
+#
+# mandatory and optional file tags
+#
+def build_specfile_filesection(spec, files):
+ """ builds the %file section of the specfile
+ """
+ str = '%files\n'
+
+ if not spec.has_key('X_RPM_DEFATTR'):
+ spec['X_RPM_DEFATTR'] = '(-,root,root)'
+
+ str = str + '%%defattr %s\n' % spec['X_RPM_DEFATTR']
+
+ supported_tags = {
+ 'PACKAGING_CONFIG' : '%%config %s',
+ 'PACKAGING_CONFIG_NOREPLACE' : '%%config(noreplace) %s',
+ 'PACKAGING_DOC' : '%%doc %s',
+ 'PACKAGING_UNIX_ATTR' : '%%attr %s',
+ 'PACKAGING_LANG_' : '%%lang(%s) %s',
+ 'PACKAGING_X_RPM_VERIFY' : '%%verify %s',
+ 'PACKAGING_X_RPM_DIR' : '%%dir %s',
+ 'PACKAGING_X_RPM_DOCDIR' : '%%docdir %s',
+ 'PACKAGING_X_RPM_GHOST' : '%%ghost %s', }
+
+ for file in files:
+ # build the tagset
+ tags = {}
+ for k in supported_tags.keys():
+ try:
+ tags[k]=getattr(file, k)
+ except AttributeError:
+ pass
+
+ # compile the tagset
+ str = str + SimpleTagCompiler(supported_tags, mandatory=0).compile( tags )
+
+ str = str + ' '
+ str = str + file.PACKAGING_INSTALL_LOCATION
+ str = str + '\n\n'
+
+ return str
+
+class SimpleTagCompiler:
+ """ This class is a simple string substition utility:
+ the replacement specfication is stored in the tagset dictionary, something
+ like:
+ { "abc" : "cdef %s ",
+ "abc_" : "cdef %s %s" }
+
+ the compile function gets a value dictionary, which may look like:
+ { "abc" : "ghij",
+ "abc_gh" : "ij" }
+
+ The resulting string will be:
+ "cdef ghij cdef gh ij"
+ """
+ def __init__(self, tagset, mandatory=1):
+ self.tagset = tagset
+ self.mandatory = mandatory
+
+ def compile(self, values):
+ """ compiles the tagset and returns a str containing the result
+ """
+ def is_international(tag):
+ #return tag.endswith('_')
+ return tag[-1:] == '_'
+
+ def get_country_code(tag):
+ return tag[-2:]
+
+ def strip_country_code(tag):
+ return tag[:-2]
+
+ replacements = self.tagset.items()
+
+ str = ""
+ #domestic = [ (k,v) for k,v in replacements if not is_international(k) ]
+ domestic = filter(lambda t, i=is_international: not i(t[0]), replacements)
+ for key, replacement in domestic:
+ try:
+ str = str + replacement % values[key]
+ except KeyError, e:
+ if self.mandatory:
+ raise e
+
+ #international = [ (k,v) for k,v in replacements if is_international(k) ]
+ international = filter(lambda t, i=is_international: i(t[0]), replacements)
+ for key, replacement in international:
+ try:
+ #int_values_for_key = [ (get_country_code(k),v) for k,v in values.items() if strip_country_code(k) == key ]
+ x = filter(lambda t,key=key,s=strip_country_code: s(t[0]) == key, values.items())
+ int_values_for_key = map(lambda t,g=get_country_code: (g(t[0]),t[1]), x)
+ for v in int_values_for_key:
+ str = str + replacement % v
+ except KeyError, e:
+ if self.mandatory:
+ raise e
+
+ return str
+
diff --git a/src/engine/SCons/Tool/packaging/src_tarbz2.py b/src/engine/SCons/Tool/packaging/src_tarbz2.py
new file mode 100644
index 0000000..7d876e4
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/src_tarbz2.py
@@ -0,0 +1,37 @@
+"""SCons.Tool.Packaging.tarbz2
+
+The tarbz2 SRC packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+from SCons.Tool.packaging import packageroot_emitter
+
+def package(env, target, source, PACKAGEROOT, **kw):
+ bld = env['BUILDERS']['Tar']
+ bld.set_suffix('.tar.bz2')
+ bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+ return bld(env, target, source, TARFLAGS='-jc')
diff --git a/src/engine/SCons/Tool/packaging/src_targz.py b/src/engine/SCons/Tool/packaging/src_targz.py
new file mode 100644
index 0000000..d84976e
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/src_targz.py
@@ -0,0 +1,37 @@
+"""SCons.Tool.Packaging.targz
+
+The targz SRC packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+from SCons.Tool.packaging import packageroot_emitter
+
+def package(env, target, source, PACKAGEROOT, **kw):
+ bld = env['BUILDERS']['Tar']
+ bld.set_suffix('.tar.gz')
+ bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+ return bld(env, target, source, TARFLAGS='-zc')
diff --git a/src/engine/SCons/Tool/packaging/src_zip.py b/src/engine/SCons/Tool/packaging/src_zip.py
new file mode 100644
index 0000000..d60fe85
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/src_zip.py
@@ -0,0 +1,37 @@
+"""SCons.Tool.Packaging.zip
+
+The zip SRC packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+from SCons.Tool.packaging import packageroot_emitter
+
+def package(env, target, source, PACKAGEROOT, **kw):
+ bld = env['BUILDERS']['Zip']
+ bld.set_suffix('.zip')
+ bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+ return bld(env, target, source)
diff --git a/src/engine/SCons/Tool/packaging/tarbz2.py b/src/engine/SCons/Tool/packaging/tarbz2.py
new file mode 100644
index 0000000..7127896
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/tarbz2.py
@@ -0,0 +1,38 @@
+"""SCons.Tool.Packaging.tarbz2
+
+The tarbz2 SRC packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+
+def package(env, target, source, PACKAGEROOT, **kw):
+ bld = env['BUILDERS']['Tar']
+ bld.set_suffix('.tar.gz')
+ bld.push_emitter(packageroot_emitter(PACKAGEROOT))
+ bld.push_emitter(stripinstall_emitter())
+ return bld(env, target, source, TARFLAGS='-jc')
diff --git a/src/engine/SCons/Tool/packaging/targz.py b/src/engine/SCons/Tool/packaging/targz.py
new file mode 100644
index 0000000..798e570
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/targz.py
@@ -0,0 +1,38 @@
+"""SCons.Tool.Packaging.targz
+
+The targz SRC packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+
+def package(env, target, source, PACKAGEROOT, **kw):
+ bld = env['BUILDERS']['Tar']
+ bld.set_suffix('.tar.gz')
+ bld.push_emitter(packageroot_emitter(PACKAGEROOT))
+ bld.push_emitter(stripinstall_emitter())
+ return bld(env, target, source, TARFLAGS='-zc')
diff --git a/src/engine/SCons/Tool/packaging/zip.py b/src/engine/SCons/Tool/packaging/zip.py
new file mode 100644
index 0000000..7663a4a
--- /dev/null
+++ b/src/engine/SCons/Tool/packaging/zip.py
@@ -0,0 +1,38 @@
+"""SCons.Tool.Packaging.zip
+
+The zip SRC packager.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+
+def package(env, target, source, PACKAGEROOT, **kw):
+ bld = env['BUILDERS']['Zip']
+ bld.set_suffix('.zip')
+ bld.push_emitter(packageroot_emitter(PACKAGEROOT))
+ bld.push_emitter(stripinstall_emitter())
+ return bld(env, target, source)
diff --git a/src/engine/SCons/Tool/rpm.py b/src/engine/SCons/Tool/rpm.py
new file mode 100644
index 0000000..2fc4b58
--- /dev/null
+++ b/src/engine/SCons/Tool/rpm.py
@@ -0,0 +1,126 @@
+"""SCons.Tool.rpm
+
+Tool-specific initialization for rpm.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+
+The rpm tool calls the rpmbuild command. The first and only argument should a
+tar.gz consisting of the source file and a specfile.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import re
+import shutil
+import popen2
+
+import SCons.Builder
+import SCons.Node.FS
+import SCons.Util
+import SCons.Action
+import SCons.Defaults
+
+def get_cmd(source, env):
+ tar_file_with_included_specfile = source
+ if SCons.Util.is_List(source):
+ tar_file_with_included_specfile = source[0]
+ return "%s %s %s"%(env['RPM'], env['RPMFLAGS'],
+ tar_file_with_included_specfile.abspath )
+
+def build_rpm(target, source, env):
+ # create a temporary rpm build root.
+ tmpdir = os.path.join( os.path.dirname( target[0].abspath ), 'rpmtemp' )
+ if os.path.exists(tmpdir):
+ shutil.rmtree(tmpdir)
+
+ # now create the mandatory rpm directory structure.
+ for d in ['RPMS', 'SRPMS', 'SPECS', 'BUILD']:
+ os.makedirs( os.path.join( tmpdir, d ) )
+
+ # set the topdir as an rpmflag.
+ env.Prepend( RPMFLAGS = '--define \'_topdir %s\'' % tmpdir )
+
+ # now call rpmbuild to create the rpm package.
+ handle = popen2.Popen3( get_cmd(source, env), capturestderr=1 )
+ output = handle.fromchild.read()
+ #output += handle.childerr.read()
+ output = output + handle.childerr.read()
+ status = handle.wait()
+
+ if status:
+ raise SCons.Errors.BuildError( node=target[0],
+ errstr=output,
+ filename=str(target[0]) )
+ else:
+ # XXX: assume that LC_ALL=c is set while running rpmbuild
+ output_files = re.compile( 'Wrote: (.*)' ).findall( output )
+
+ for output, input in zip( output_files, target ):
+ rpm_output = os.path.basename(output)
+ expected = os.path.basename(input.get_path())
+
+ assert expected == rpm_output, "got %s but expected %s" % (rpm_output, expected)
+ shutil.copy( output, input.abspath )
+
+
+ # cleanup before leaving.
+ shutil.rmtree(tmpdir)
+
+ return status
+
+def string_rpm(target, source, env):
+ try:
+ return env['RPMCOMSTR']
+ except KeyError:
+ return get_cmd(source, env)
+
+rpmAction = SCons.Action.Action(build_rpm, string_rpm)
+
+RpmBuilder = SCons.Builder.Builder(action = SCons.Action.Action('$RPMCOM', '$RPMCOMSTR'),
+ source_factory = SCons.Node.FS.Entry,
+ source_scanner = SCons.Defaults.DirScanner,
+ suffix = '$RPMSUFFIX')
+
+
+
+def generate(env):
+ """Add Builders and construction variables for rpm to an Environment."""
+ try:
+ bld = env['BUILDERS']['Rpm']
+ except KeyError:
+ bld = RpmBuilder
+ env['BUILDERS']['Rpm'] = bld
+
+ env.SetDefault(RPM = 'LC_ALL=c rpmbuild')
+ env.SetDefault(RPMFLAGS = SCons.Util.CLVar('-ta'))
+ env.SetDefault(RPMCOM = rpmAction)
+ env.SetDefault(RPMSUFFIX = '.rpm')
+
+def exists(env):
+ return env.Detect('rpmbuild')
diff --git a/src/engine/SCons/Tool/wix.py b/src/engine/SCons/Tool/wix.py
new file mode 100644
index 0000000..b133947
--- /dev/null
+++ b/src/engine/SCons/Tool/wix.py
@@ -0,0 +1,91 @@
+"""SCons.Tool.wix
+
+Tool-specific initialization for wix, the Windows Installer XML Tool.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Builder
+import SCons.Action
+import os
+import string
+
+def generate(env):
+ """Add Builders and construction variables for WiX to an Environment."""
+ if not exists(env):
+ return
+
+ env['WIXCANDLEFLAGS'] = ['-nologo']
+ env['WIXCANDLEINCLUDE'] = []
+ env['WIXCANDLECOM'] = '$WIXCANDLE $WIXCANDLEFLAGS -I $WIXCANDLEINCLUDE -o ${TARGET} ${SOURCE}'
+
+ env['WIXLIGHTFLAGS'].append( '-nologo' )
+ env['WIXLIGHTCOM'] = "$WIXLIGHT $WIXLIGHTFLAGS -out ${TARGET} ${SOURCES}"
+
+ object_builder = SCons.Builder.Builder(
+ action = '$WIXCANDLECOM',
+ suffix = '.wxiobj',
+ src_suffix = '.wxs')
+
+ linker_builder = SCons.Builder.Builder(
+ action = '$WIXLIGHTCOM',
+ src_suffix = '.wxiobj',
+ src_builder = object_builder)
+
+ env['BUILDERS']['WiX'] = linker_builder
+
+def exists(env):
+ env['WIXCANDLE'] = 'candle.exe'
+ env['WIXLIGHT'] = 'light.exe'
+
+ # try to find the candle.exe and light.exe tools and
+ # add the install directory to light libpath.
+ #for path in os.environ['PATH'].split(os.pathsep):
+ for path in string.split(os.environ['PATH'], os.pathsep):
+ # workaround for some weird python win32 bug.
+ if path[0] == '"' and path[-1:]=='"':
+ path = path[1:-1]
+
+ # normalize the path
+ path = os.path.normpath(path)
+
+ # search for the tools in the PATH environment variable
+ try:
+ if env['WIXCANDLE'] in os.listdir(path) and\
+ env['WIXLIGHT'] in os.listdir(path):
+ env.PrependENVPath('PATH', path)
+ env['WIXLIGHTFLAGS'] = [ os.path.join( path, 'wixui.wixlib' ),
+ '-loc',
+ os.path.join( path, 'WixUI_en-us.wxl' ) ]
+ return 1
+ except OSError:
+ pass # ignore this, could be a stale PATH entry.
+
+ return None
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index a5ea859..22aca08 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -951,6 +951,23 @@ class Unbuffered:
def __getattr__(self, attr):
return getattr(self.file, attr)
+def make_path_relative(path):
+ """ makes an absolute path name to a relative pathname.
+ """
+ if os.path.isabs(path):
+ drive_s,path = os.path.splitdrive(path)
+
+ import re
+ if not drive_s:
+ path=re.compile("/*(.*)").findall(path)[0]
+ else:
+ path=path[1:]
+
+ assert( not os.path.isabs( path ) ), path
+ return path
+
+
+
# The original idea for AddMethod() and RenameFunction() come from the
# following post to the ActiveState Python Cookbook:
#
@@ -1025,4 +1042,6 @@ def RenameFunction(function, name):
name,
func_defaults)
+
+
del __revision__
diff --git a/test/Copy.py b/test/Copy.py
index b318e58..4da725e 100644
--- a/test/Copy.py
+++ b/test/Copy.py
@@ -54,6 +54,10 @@ env.Command('bar.out', 'bar.in', [Cat,
env = Environment(OUTPUT = 'f7.out', INPUT = 'f7.in')
env.Command('f8.out', 'f8.in', [Copy('$OUTPUT', '$INPUT'), Cat])
env.Command('f9.out', 'f9.in', [Cat, Copy('${TARGET}-Copy', '$SOURCE')])
+
+env.CopyTo( 'd4', 'f10.in' )
+env.CopyAs( 'd4/f11.out', 'f11.in')
+env.CopyAs( 'd4/f12.out', 'd5/f12.in')
""")
test.write('f1.in', "f1.in\n")
@@ -70,6 +74,10 @@ test.subdir('d6.out')
test.write('f7.in', "f7.in\n")
test.write('f8.in', "f8.in\n")
test.write('f9.in', "f9.in\n")
+test.write('f10.in', "f10.in\n")
+test.write('f11.in', "f11.in\n")
+test.subdir('d5')
+test.write(['d5', 'f12.in'], "f12.in\n")
expect = test.wrap_stdout(read_str = """\
Copy("f1.out", "f1.in")
@@ -81,6 +89,9 @@ cat(["bar.out"], ["bar.in"])
Copy("f4.out", "f4.in")
Copy("d5.out", "d5.in")
Copy("d6.out", "f6.in")
+Copy file(s): "f10.in" to "d4/f10.in"
+Copy file(s): "f11.in" to "d4/f11.out"
+Copy file(s): "d5/f12.in" to "d4/f12.out"
Copy("f7.out", "f7.in")
cat(["f8.out"], ["f8.in"])
cat(["f9.out"], ["f9.in"])
@@ -98,6 +109,9 @@ test.must_not_exist('f7.out')
test.must_not_exist('f8.out')
test.must_not_exist('f9.out')
test.must_not_exist('f9.out-Copy')
+test.must_not_exist('d4/f10.in')
+test.must_not_exist('d4/f11.out')
+test.must_not_exist('d4/f12.out')
test.run()
@@ -111,5 +125,8 @@ test.must_match('f7.out', "f7.in\n")
test.must_match('f8.out', "f8.in\n")
test.must_match('f9.out', "f9.in\n")
test.must_match('f9.out-Copy', "f9.in\n")
+test.must_match('d4/f10.in', 'f10.in\n')
+test.must_match('d4/f11.out', 'f11.in\n')
+test.must_match('d4/f12.out', 'f12.in\n')
test.pass_test()
diff --git a/test/Install/Install.py b/test/Install/Install.py
index 3c1dc49..f7f8e26 100644
--- a/test/Install/Install.py
+++ b/test/Install/Install.py
@@ -91,7 +91,7 @@ test.write(['work', 'sub', 'f4.in'], "sub/f4.in\n")
test.write(f5_txt, "f5.txt\n")
test.write(f6_txt, "f6.txt\n")
-test.run(chdir = 'work', arguments = '.')
+test.run(chdir = 'work', arguments = '--debug=stacktrace .')
test.fail_test(test.read(f1_out) != "f1.in\n")
test.fail_test(test.read(f2_out) != "f2.in\n")
diff --git a/test/Install/directories.py b/test/Install/directories.py
index 300ed4d..3005859 100644
--- a/test/Install/directories.py
+++ b/test/Install/directories.py
@@ -74,14 +74,14 @@ arguments = [
test.workpath('outside', 'd4'),
]
-expect = test.wrap_stdout("""
+expect = test.wrap_stdout("""\
Install directory: "dir1" as "%s"
Install directory: "dir2" as "%s"
Install directory: "dir3" as "%s"
Install directory: "dir4" as "%s"
""" % tuple(arguments))
-test.run(chdir = 'work', arguments = arguments)
+test.run(chdir = 'work', arguments = arguments, stdout = expect)
test.must_match(test.workpath('outside', 'dir1', 'f2'), "work/dir1/f2\n")
test.must_match(test.workpath('outside', 'dir1', 'sub', 'f3'), "work/dir1/sub/f3\n")
diff --git a/test/Install/option--install-sandbox.py b/test/Install/option--install-sandbox.py
new file mode 100644
index 0000000..4cf9310
--- /dev/null
+++ b/test/Install/option--install-sandbox.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the --install-sandbox commandline option for Install() and InstallAs().
+"""
+
+import os.path
+import sys
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('install', 'subdir')
+target = 'destination'
+destdir = test.workpath( target )
+_SUBDIR_file3_out = os.path.join('$SUBDIR', 'file3.out')
+_SUBDIR_file3_in = os.path.join('$SUBDIR', 'file3.in')
+
+target_file2_out = os.path.join(target, 'file2.out')
+subdir_file3_in = os.path.join('subdir', 'file3.in')
+target_subdir_file3_out = os.path.join(target, 'subdir', 'file3.out')
+file1_out = target+os.path.join( target, destdir, 'file1.out' )
+
+#
+test.write('SConstruct', r"""
+env = Environment(SUBDIR='subdir')
+env.Install(r'%(destdir)s', 'file1.out')
+env.InstallAs(['file2.out', r'%(_SUBDIR_file3_out)s'],
+ ['file2.in', r'%(_SUBDIR_file3_in)s'])
+""" % locals())
+
+test.write('file1.out', "file1.out\n")
+test.write('file2.in', "file2.in\n")
+test.write(['subdir', 'file3.in'], "subdir/file3.in\n")
+
+expect = test.wrap_stdout("""\
+Install file: "file2.in" as "%(target_file2_out)s"
+Install file: "%(subdir_file3_in)s" as "%(target_subdir_file3_out)s"
+Install file: "file1.out" as "%(file1_out)s"
+""" % locals())
+
+test.run(arguments = '--install-sandbox=%s' % destdir, stdout=expect)
+
+test.must_match(file1_out, "file1.out\n")
+test.must_match('destination/file2.out', "file2.in\n")
+test.must_match('destination/subdir/file3.out', "subdir/file3.in\n")
+
+#
+test.pass_test()
diff --git a/test/Java/JAVAH.py b/test/Java/JAVAH.py
index 15adf34..ecd3737 100644
--- a/test/Java/JAVAH.py
+++ b/test/Java/JAVAH.py
@@ -122,7 +122,7 @@ os.system(string.join(sys.argv[1:], " "))
""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
test.write('SConstruct', """
-foo = Environment(tools = ['javac', 'javah'],
+foo = Environment(tools = ['javac', 'javah', 'install'],
JAVAC = r'%(where_javac)s',
JAVAH = r'%(where_javah)s')
javah = foo.Dictionary('JAVAH')
diff --git a/test/option-j.py b/test/option-j.py
index 8f4c190..bc36f08 100644
--- a/test/option-j.py
+++ b/test/option-j.py
@@ -66,6 +66,7 @@ foo you
test.write('SConstruct', """
MyBuild = Builder(action = r'%(_python_)s build.py $TARGETS')
env = Environment(BUILDERS = { 'MyBuild' : MyBuild })
+env.Tool('install')
env.MyBuild(target = 'f1', source = 'f1.in')
env.MyBuild(target = 'f2', source = 'f2.in')
@@ -162,6 +163,7 @@ os.environ['PYTHONPATH'] = save_pythonpath
test.write('SConstruct', """
MyBuild = Builder(action = r'%(_python_)s build.py $TARGETS')
env = Environment(BUILDERS = { 'MyBuild' : MyBuild })
+env.Tool('install')
env.MyBuild(target = 'f1', source = 'f1.in')
env.MyBuild(target = 'f2', source = 'f2.in')
@@ -190,6 +192,7 @@ test.fail_test(not (start2 < finish1))
test.write('SConstruct', """
MyBuild = Builder(action = r'%(_python_)s build.py $TARGETS')
env = Environment(BUILDERS = { 'MyBuild' : MyBuild })
+env.Tool('install')
env.MyBuild(target = 'f1', source = 'f1.in')
env.MyBuild(target = 'f2', source = 'f2.in')
diff --git a/test/option-n.py b/test/option-n.py
index 2e7694b..d1f87f0 100644
--- a/test/option-n.py
+++ b/test/option-n.py
@@ -63,6 +63,7 @@ file.close()
test.write('SConstruct', """
MyBuild = Builder(action = r'%(_python_)s build.py $TARGETS')
env = Environment(BUILDERS = { 'MyBuild' : MyBuild })
+env.Tool('install')
env.MyBuild(target = 'f1.out', source = 'f1.in')
env.MyBuild(target = 'f2.out', source = 'f2.in')
env.Install('install', 'f3.in')
diff --git a/test/option/profile.py b/test/option/profile.py
index 7adf933..d0c0ffc 100644
--- a/test/option/profile.py
+++ b/test/option/profile.py
@@ -47,7 +47,6 @@ scons_prof = test.workpath('scons.prof')
test.run(arguments = "--profile=%s -h" % scons_prof)
test.fail_test(string.find(test.stdout(), 'usage: scons [OPTION]') == -1)
-test.fail_test(string.find(test.stdout(), 'usage: scons [OPTION]') == -1)
try:
save_stdout = sys.stdout
diff --git a/test/packaging/guess-package-name.py b/test/packaging/guess-package-name.py
new file mode 100644
index 0000000..5b4cd67
--- /dev/null
+++ b/test/packaging/guess-package-name.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This tests the feature of guessing the package name from the given metadata
+projectname and version.
+
+Also overriding this default package name is tested
+
+Furthermore that targz is the default packager is tested.
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+test = TestSCons.TestSCons()
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+ test.skip_test('tar not found; skipping test\n')
+
+#
+# TEST: default package name creation.
+#
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+env=Environment(tools=['default', 'packaging'])
+env.Program( 'src/main.c' )
+env.Package( NAME = 'libfoo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'zip',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(options="--debug=stacktrace", stderr = None)
+
+test.must_exist( 'libfoo-1.2.3.zip' )
+
+#
+# TEST: overriding default package name.
+#
+
+test.write('SConstruct', """
+env=Environment(tools=['default', 'packaging'])
+env.Program( 'src/main.c' )
+env.Package( NAME = 'libfoo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'src_targz',
+ target = 'src.tar.gz',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(stderr = None)
+
+test.must_exist( 'src.tar.gz' )
+
+#
+# TEST: default package name creation with overriden packager.
+#
+
+test.write('SConstruct', """
+env=Environment(tools=['default', 'packaging'])
+env.Program( 'src/main.c' )
+env.Package( NAME = 'libfoo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'src_tarbz2',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(stderr = None)
+
+test.must_exist( 'libfoo-1.2.3.tar.bz2' )
+
+test.pass_test()
diff --git a/test/packaging/ipkg.py b/test/packaging/ipkg.py
new file mode 100644
index 0000000..62ce628
--- /dev/null
+++ b/test/packaging/ipkg.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to call the ipkg tool trough SCons.
+
+TODO: make a test to assert that the clean action removes ALL intermediate files
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+test = TestSCons.TestSCons()
+ipkg = test.Environment().WhereIs('ipkg-build')
+
+if not ipkg:
+ test.skip_test("ipkg-build not found, skipping test\n")
+
+test.write( 'main.c', r"""
+int main(int argc, char *argv[])
+{
+ return 0;
+}
+""")
+
+test.write( 'foo.conf', '' )
+
+test.write( 'SConstruct', r"""
+env=Environment(tools=['default', 'packaging'])
+prog = env.Install( 'bin/', Program( 'main.c') )
+conf = env.Install( 'etc/', File( 'foo.conf' ) )
+env.Tag( conf, 'CONF', 'MEHR', 'UND MEHR' )
+env.Package( PACKAGETYPE = 'ipk',
+ source = env.FindInstalledFiles(),
+ NAME = 'foo',
+ VERSION = '0.0',
+ SUMMARY = 'foo is the ever-present example program -- it does everything',
+ DESCRIPTION = '''foo is not a real package. This is simply an example that you
+may modify if you wish.
+.
+When you modify this example, be sure to change the Package, Version,
+Maintainer, Depends, and Description fields.''',
+
+ SOURCE_URL = 'http://gnu.org/foo-0.0.tar.gz',
+ X_IPK_SECTION = 'extras',
+ X_IPK_PRIORITY = 'optional',
+ ARCHITECTURE = 'arm',
+ X_IPK_MAINTAINER = 'Familiar User <user@somehost.net>',
+ X_IPK_DEPENDS = 'libc6, grep', )
+""")
+
+expected="""scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+gcc -o main.o -c main.c
+gcc -o main main.o
+Copy file(s): "main" to "foo-0.0/bin/main"
+Copy file(s): "foo.conf" to "foo-0.0/etc/foo.conf"
+build_specfiles(["foo-0.0/CONTROL/control", "foo-0.0/CONTROL/conffiles", "foo-0.0/CONTROL/postrm", "foo-0.0/CONTROL/prerm", "foo-0.0/CONTROL/postinst", "foo-0.0/CONTROL/preinst"], ["foo-0.0/bin/main", "foo-0.0/etc/foo.conf"])
+ipkg-build -o %s -g %s foo-0.0
+Packaged contents of foo-0.0 into %s/foo_0.0_arm.ipk
+scons: done building targets.
+"""%(os.popen('id -un').read().strip(), os.popen('id -gn').read().strip(), test.workpath())
+
+test.run(arguments="--debug=stacktrace foo_0.0_arm.ipk", stdout=expected)
+test.must_exist( 'foo-0.0/CONTROL/control' )
+test.must_exist( 'foo_0.0_arm.ipk' )
+
+test.subdir( 'foo-0.0' )
+test.subdir( [ 'foo-0.0', 'CONTROL' ] )
+
+test.write( [ 'foo-0.0', 'CONTROL', 'control' ], r"""
+Package: foo
+Priority: optional
+Section: extras
+Source: http://gnu.org/foo-0.0.tar.gz
+Version: 0.0
+Architecture: arm
+Maintainer: Familiar User <user@somehost.net>
+Depends: libc6, grep
+Description: foo is the ever-present example program -- it does everything
+ foo is not a real package. This is simply an example that you
+ may modify if you wish.
+ .
+ When you modify this example, be sure to change the Package, Version,
+ Maintainer, Depends, and Description fields.
+""")
+
+test.write( 'main.c', r"""
+int main(int argc, char *argv[])
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+env = Environment( tools = [ 'default', 'ipkg' ] )
+prog = env.Install( 'foo-0.0/bin/' , env.Program( 'main.c') )
+env.Ipkg( [ env.Dir( 'foo-0.0' ), prog ] )
+""")
+
+test.run(arguments='', stderr = None)
+test.must_exist( 'foo_0.0_arm.ipk' )
+
+test.pass_test()
diff --git a/test/packaging/msi/file-placement.py b/test/packaging/msi/file-placement.py
new file mode 100644
index 0000000..08b0ba6
--- /dev/null
+++ b/test/packaging/msi/file-placement.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the msi packagers ability to put files into distinct directories.
+"""
+
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+try:
+ from xml.dom.minidom import *
+except ImportError:
+ test.skip_test('Canoot import xml.dom.minidom skipping test\n')
+
+wix = test.Environment().WhereIs('candle')
+
+if wix:
+ #
+ # Test the default directory layout
+ #
+ test.write( 'file1.exe', "file1" )
+
+ test.write('SConstruct', """
+env = Environment(tools=['default', 'packaging'])
+f1 = env.Install( '/bin/' , 'file1.exe' )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2',
+ PACKAGETYPE = 'msi',
+ SUMMARY = 'balalalalal',
+ DESCRIPTION = 'this should be reallly really long',
+ VENDOR = 'Nanosoft_2000',
+ source = [ f1 ],
+ )
+""")
+
+ test.run(arguments='', stderr = None)
+
+ dom = parse( test.workpath( 'foo-1.2.wxs' ) )
+ dirs = dom.getElementsByTagName( 'Directory' )
+
+ test.fail_test( not dirs[0].attributes['Name'].value == 'SourceDir' )
+ test.fail_test( not dirs[1].attributes['Name'].value == 'PFiles' )
+ test.fail_test( not dirs[2].attributes['Name'].value == 'NANOSOF1' )
+ test.fail_test( not dirs[3].attributes['Name'].value == 'FOO-1.2' )
+
+ #
+ # Try to put 7 files into 5 distinct directories of varying depth and overlapping count
+ #
+ test.write( 'file1.exe', "file1" )
+ test.write( 'file2.exe', "file2" )
+ test.write( 'file3.dll', "file3" )
+ test.write( 'file4.dll', "file4" )
+ test.write( 'file5.class', "file5" )
+ test.write( 'file6.class', "file6" )
+ test.write( 'file7.class', "file7" )
+
+ test.write('SConstruct', """
+env = Environment(tools=['default', 'packaging'])
+f1 = env.Install( '/bin/' , 'file1.exe' )
+f2 = env.Install( '/bin/' , 'file2.exe' )
+f3 = env.Install( '/lib/' , 'file3.dll' )
+f4 = env.Install( '/lib/' , 'file4.dll' )
+f5 = env.Install( '/java/edu/teco/' , 'file5.class' )
+f6 = env.Install( '/java/teco/' , 'file6.class' )
+f7 = env.Install( '/java/tec/' , 'file7.class' )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2',
+ PACKAGETYPE = 'msi',
+ SUMMARY = 'balalalalal',
+ DESCRIPTION = 'this should be reallly really long',
+ VENDOR = 'Nanosoft_2000',
+ LICENSE = 'afl',
+ source = [ f1, f2, f3, f4, f5, f6, f7 ],
+ )
+""")
+
+ test.run(arguments='', stderr = None)
+
+ dom = parse( test.workpath( 'foo-1.2.wxs' ) )
+ files = dom.getElementsByTagName( 'File' )
+
+ test.fail_test( not files[0].parentNode.parentNode.attributes['LongName'].value == 'bin' )
+ test.fail_test( not files[1].parentNode.parentNode.attributes['LongName'].value == 'bin' )
+ test.fail_test( not files[2].parentNode.parentNode.attributes['LongName'].value == 'lib' )
+ test.fail_test( not files[3].parentNode.parentNode.attributes['LongName'].value == 'lib' )
+
+ test.fail_test( not files[4].parentNode.parentNode.attributes['LongName'].value == 'teco' )
+ test.fail_test( not files[4].parentNode.parentNode.parentNode.attributes['LongName'].value == 'edu' )
+ test.fail_test( not files[4].parentNode.parentNode.parentNode.parentNode.attributes['LongName'].value == 'java' )
+
+ test.fail_test( not files[5].parentNode.parentNode.attributes['LongName'].value == 'teco' )
+ test.fail_test( not files[5].parentNode.parentNode.parentNode.attributes['LongName'].value == 'java' )
+
+ test.fail_test( not files[6].parentNode.parentNode.attributes['LongName'].value == 'tec' )
+ test.fail_test( not files[6].parentNode.parentNode.parentNode.attributes['LongName'].value == 'java' )
+
+ #
+ # Test distinct directories put into distinct features
+ #
+ test.write( 'file1.exe', "file1" )
+ test.write( 'file2.exe', "file2" )
+ test.write( 'file3.dll', "file3" )
+ test.write( 'file3-.dll', "file3" )
+
+ test.write('SConstruct', """
+env = Environment(tools=['default', 'packaging'])
+f1 = env.Install( '/bin/' , 'file1.exe' )
+f2 = env.Install( '/bin/' , 'file2.exe' )
+f3 = env.Install( '/lib/' , 'file3.dll' )
+f4 = env.Install( '/lib/' , 'file3-.dll' ) # generate a collision in the ids
+
+env.Tag( [f1, f2, f4], X_MSI_FEATURE = 'Core Part' )
+env.Tag( f3, X_MSI_FEATURE = 'Java Part' )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2',
+ PACKAGETYPE = 'msi',
+ SUMMARY = 'balalalalal',
+ DESCRIPTION = 'this should be reallly really long',
+ VENDOR = 'Nanosoft_2000',
+ LICENSE = 'afl',
+ source = [ f1, f2, f3, f4 ],
+ )
+""")
+
+ test.run(arguments='', stderr = None)
+
+ dom = parse( test.workpath( 'foo-1.2.wxs' ) )
+ features = dom.getElementsByTagName( 'Feature' )
+
+ test.fail_test( not features[1].attributes['Title'].value == 'Core Part' )
+ componentrefs = features[1].getElementsByTagName( 'ComponentRef' )
+ test.fail_test( not componentrefs[0].attributes['Id'].value == 'file1.exe' )
+ test.fail_test( not componentrefs[1].attributes['Id'].value == 'file2.exe' )
+ test.fail_test( not componentrefs[2].attributes['Id'].value == 'file3.dll1' )
+
+ test.fail_test( not features[2].attributes['Title'].value == 'Java Part' )
+ componentrefs = features[2].getElementsByTagName( 'ComponentRef' )
+ test.fail_test( not componentrefs[0].attributes['Id'].value == 'file3.dll' )
+
+else:
+ test.no_result()
+
diff --git a/test/packaging/msi/package.py b/test/packaging/msi/package.py
new file mode 100644
index 0000000..24bd26d
--- /dev/null
+++ b/test/packaging/msi/package.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to create a simple msi package.
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+try:
+ from xml.dom.minidom import *
+except ImportError:
+ test.skip_test('Canoot import xml.dom.minidom skipping test\n')
+
+wix = test.Environment().WhereIs('candle')
+
+if wix:
+ #
+ # build with minimal tag set and test for the given package meta-data
+ #
+ test.write( 'file1.exe', "file1" )
+ test.write( 'file2.exe', "file2" )
+
+ test.write('SConstruct', """
+import os
+
+env = Environment(tools=['default', 'packaging'])
+
+f1 = env.Install( '/usr/' , 'file1.exe' )
+f2 = env.Install( '/usr/' , 'file2.exe' )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2',
+ PACKAGETYPE = 'msi',
+ SUMMARY = 'balalalalal',
+ DESCRIPTION = 'this should be reallly really long',
+ VENDOR = 'Nanosoft_2000',
+ source = [ f1, f2 ],
+ )
+
+env.Alias( 'install', [ f1, f2 ] )
+""")
+
+ test.run(arguments='', stderr = None)
+
+ test.must_exist( 'foo-1.2.wxs' )
+ test.must_exist( 'foo-1.2.msi' )
+
+ dom = parse( test.workpath( 'foo-1.2.wxs' ) )
+ Product = dom.getElementsByTagName( 'Product' )[0]
+ Package = dom.getElementsByTagName( 'Package' )[0]
+
+ test.fail_test( not Product.attributes['Manufacturer'].value == 'Nanosoft_2000' )
+ test.fail_test( not Product.attributes['Version'].value == '1.2' )
+ test.fail_test( not Product.attributes['Name'].value == 'foo' )
+
+ test.fail_test( not Package.attributes['Description'].value == 'balalalalal' )
+ test.fail_test( not Package.attributes['Comments'].value == 'this should be reallly really long' )
+
+ #
+ # build with file tags resulting in multiple components in the msi installer
+ #
+ test.write( 'file1.exe', "file1" )
+ test.write( 'file2.exe', "file2" )
+ test.write( 'file3.html', "file3" )
+ test.write( 'file4.dll', "file4" )
+ test.write( 'file5.dll', "file5" )
+
+ test.write('SConstruct', """
+import os
+env = Environment(tools=['default', 'packaging'])
+f1 = env.Install( '/usr/' , 'file1.exe' )
+f2 = env.Install( '/usr/' , 'file2.exe' )
+f3 = env.Install( '/usr/' , 'file3.html' )
+f4 = env.Install( '/usr/' , 'file4.dll' )
+f5 = env.Install( '/usr/' , 'file5.dll' )
+
+env.Tag( f1, X_MSI_FEATURE = 'Java Part' )
+env.Tag( f2, X_MSI_FEATURE = 'Java Part' )
+env.Tag( f3, 'DOC' )
+env.Tag( f4, X_MSI_FEATURE = 'default' )
+env.Tag( f5, X_MSI_FEATURE = ('Another Feature', 'with a long description') )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2',
+ PACKAGETYPE = 'msi',
+ SUMMARY = 'balalalalal',
+ DESCRIPTION = 'this should be reallly really long',
+ VENDOR = 'Nanosoft_tx2000',
+ source = [ f1, f2, f3, f4, f5 ],
+ )
+
+env.Alias( 'install', [ f1, f2, f3, f4, f5 ] )
+""")
+
+ test.run(arguments='', stderr = None)
+
+ test.must_exist( 'foo-1.2.wxs' )
+ test.must_exist( 'foo-1.2.msi' )
+
+ dom = parse( test.workpath( 'foo-1.2.wxs' ) )
+ elements = dom.getElementsByTagName( 'Feature' )
+ test.fail_test( not elements[1].attributes['Title'].value == 'Main Part' )
+ test.fail_test( not elements[2].attributes['Title'].value == 'Documentation' )
+ test.fail_test( not elements[3].attributes['Title'].value == 'Another Feature' )
+ test.fail_test( not elements[3].attributes['Description'].value == 'with a long description' )
+ test.fail_test( not elements[4].attributes['Title'].value == 'Java Part' )
+
+else:
+ test.no_result()
diff --git a/test/packaging/option--package-type.py b/test/packaging/option--package-type.py
new file mode 100644
index 0000000..68a075c
--- /dev/null
+++ b/test/packaging/option--package-type.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the --package-type option.
+"""
+
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+ test.skip_test('rpm not found, skipping test\n')
+
+test.subdir('src')
+
+test.write( 'main', '' )
+
+test.write('SConstruct', """
+# -*- coding: iso-8859-15 -*-
+env=Environment(tools=['default', 'packaging'])
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+prog=env.Install( '/bin', 'main' )
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ LICENSE = 'gpl',
+ SUMMARY = 'hello',
+ PACKAGEVERSION = 0,
+ X_RPM_GROUP = 'Application/office',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really long',
+ source = [ prog ],
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+""" % locals())
+
+test.run(arguments='package PACKAGETYPE=rpm', stderr = None)
+
+src_rpm = 'foo-1.2.3-0.src.rpm'
+machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+
+test.must_exist( src_rpm )
+test.must_exist( machine_rpm )
+
+test.must_not_exist( 'bin/main.c' )
+test.must_not_exist( '/bin/main.c' )
+
+test.pass_test()
diff --git a/test/packaging/place-files-in-subdirectory.py b/test/packaging/place-files-in-subdirectory.py
new file mode 100644
index 0000000..d9758a1
--- /dev/null
+++ b/test/packaging/place-files-in-subdirectory.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the requirement to place files in a given subdirectory before archiving.
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+ test.skipt_test('tar not found, skipping test\n')
+
+#
+# TEST: subdir creation and file copying
+#
+test.subdir('src')
+
+test.write('src/main.c', '')
+
+test.write('SConstruct', """
+env = Environment(tools=['default', 'packaging'])
+env.Package( NAME = 'libfoo',
+ PACKAGEROOT = 'libfoo',
+ PACKAGETYPE = 'src_zip',
+ VERSION = '1.2.3',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(arguments='libfoo-1.2.3.zip', stderr = None)
+
+test.must_exist( 'libfoo' )
+test.must_exist( 'libfoo/SConstruct' )
+test.must_exist( 'libfoo/src/main.c' )
+
+#
+# TEST: subdir guessing and file copying.
+#
+test.subdir('src')
+
+test.write('src/main.c', '')
+
+test.write('SConstruct', """
+env = Environment(tools=['default', 'packaging'])
+env.Package( NAME = 'libfoo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'src_zip',
+ TARGET = 'src.zip',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(stderr = None)
+
+test.must_exist( 'libfoo-1.2.3' )
+test.must_exist( 'libfoo-1.2.3/SConstruct' )
+test.must_exist( 'libfoo-1.2.3/src/main.c' )
+
+#
+# TEST: unpacking without the buildir.
+#
+test.subdir('src')
+test.subdir('temp')
+
+test.write('src/main.c', '')
+
+test.write('SConstruct', """
+env = Environment(tools=['default', 'packaging'])
+env.Package( NAME = 'libfoo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'src_targz',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(stderr = None)
+
+str = os.popen( 'tar -tzf %s'%test.workpath('libfoo-1.2.3.tar.gz') ).read()
+test.fail_test( str != "libfoo-1.2.3/src/main.c\nlibfoo-1.2.3/SConstruct\n" )
+
+test.pass_test()
diff --git a/test/packaging/rpm/cleanup.py b/test/packaging/rpm/cleanup.py
new file mode 100644
index 0000000..26bf79b
--- /dev/null
+++ b/test/packaging/rpm/cleanup.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Assert that files created by the RPM packager will be removed by 'scons -c'.
+"""
+
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+# TODO: skip this test, since only the intermediate directory needs to be
+# removed.
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+ test.skip_test('rpm not found, skipping test\n')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+env=Environment(tools=['default', 'packaging'])
+
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+
+prog = env.Install( '/bin/' , Program( 'src/main.c') )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGEVERSION = 0,
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'balalalalal',
+ X_RPM_GROUP = 'Application/fu',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really really long',
+ source = [ prog ],
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+
+env.Alias( 'install', prog )
+""" % locals())
+
+# first run: build the package
+# second run: test if the intermediate files have been cleaned
+test.run( arguments='' )
+test.run( arguments='-c' )
+
+src_rpm = 'foo-1.2.3-0.src.rpm'
+machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+
+test.must_not_exist( machine_rpm )
+test.must_not_exist( src_rpm )
+test.must_not_exist( 'foo-1.2.3.tar.gz' )
+test.must_not_exist( 'foo-1.2.3.spec' )
+test.must_not_exist( 'foo-1.2.3/foo-1.2.3.spec' )
+test.must_not_exist( 'foo-1.2.3/SConstruct' )
+test.must_not_exist( 'foo-1.2.3/src/main.c' )
+# We don't remove the directories themselves. Yet.
+#test.must_not_exist( 'foo-1.2.3' )
+#test.must_not_exist( 'foo-1.2.3/src' )
+test.must_not_exist( 'bin/main' )
+
+test.pass_test()
diff --git a/test/packaging/rpm/internationalization.py b/test/packaging/rpm/internationalization.py
new file mode 100644
index 0000000..66c2291
--- /dev/null
+++ b/test/packaging/rpm/internationalization.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to handle internationalized package and file meta-data.
+
+These are x-rpm-Group, description, summary and the lang_xx file tag.
+"""
+
+import os
+
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+ test.skip_test('rpm not found, skipping test\n')
+
+#
+# test INTERNATIONAL PACKAGE META-DATA
+#
+test.write( [ 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+# -*- coding: utf-8 -*-
+import os
+
+env = Environment(tools=['default', 'packaging'])
+
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+
+prog = env.Install( '/bin', Program( 'main.c' ) )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'hello',
+ SUMMARY_de = 'hallo',
+ SUMMARY_fr = 'bonjour',
+ PACKAGEVERSION = 0,
+ X_RPM_GROUP = 'Application/office',
+ X_RPM_GROUP_de = 'Applikation/büro',
+ X_RPM_GROUP_fr = 'Application/bureau',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really long',
+ DESCRIPTION_de = 'das sollte wirklich lang sein',
+ DESCRIPTION_fr = 'ceci devrait être vraiment long',
+ source = [ prog ],
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+
+env.Alias ( 'install', prog )
+""" % locals())
+
+test.run(arguments='', stderr = None)
+
+src_rpm = 'foo-1.2.3-0.src.rpm'
+machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+
+test.must_exist( src_rpm )
+test.must_exist( machine_rpm )
+
+test.must_not_exist( 'bin/main' )
+
+cmd = 'rpm -qp --queryformat \'%%{GROUP}-%%{SUMMARY}-%%{DESCRIPTION}\' %s'
+
+os.environ['LC_ALL'] = 'de_DE.utf8'
+os.environ['LANGUAGE'] = 'de'
+out = os.popen( cmd % test.workpath(machine_rpm) ).read()
+test.fail_test( out != 'Applikation/büro-hallo-das sollte wirklich lang sein' )
+
+os.environ['LC_ALL'] = 'fr_FR.utf8'
+os.environ['LANGUAGE'] = 'fr'
+out = os.popen( cmd % test.workpath(machine_rpm) ).read()
+test.fail_test( out != 'Application/bureau-bonjour-ceci devrait être vraiment long' )
+
+os.environ['LANGUAGE'] = 'en'
+out = os.popen( cmd % test.workpath(machine_rpm) ).read()
+test.fail_test( out != 'Application/office-hello-this should be really long' )
+
+os.environ['LC_ALL'] = 'ae'
+out = os.popen( cmd % test.workpath(machine_rpm) ).read()
+test.fail_test( out != 'Application/office-hello-this should be really long' )
+
+#
+# test INTERNATIONAL PACKAGE TAGS
+#
+
+test.write( [ 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write( ['man.de'], '' )
+test.write( ['man.en'], '' )
+test.write( ['man.fr'], '' )
+
+test.write('SConstruct', """
+# -*- coding: utf-8 -*-
+import os
+
+env = Environment(tools=['default', 'packaging'])
+prog = env.Install( '/bin', Program( 'main.c' ) )
+
+man_pages = Flatten( [
+ env.Install( '/usr/share/man/de', 'man.de' ),
+ env.Install( '/usr/share/man/en', 'man.en' ),
+ env.Install( '/usr/share/man/fr', 'man.fr' )
+] )
+
+env.Tag( man_pages, 'LANG_DE', 'DOC')
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'hello',
+ SUMMARY_de = 'hallo',
+ SUMMARY_fr = 'bonjour',
+ PACKAGEVERSION = 0,
+ X_RPM_GROUP = 'Application/office',
+ X_RPM_GROUP_de = 'Applikation/büro',
+ X_RPM_GROUP_fr = 'Application/bureau',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really long',
+ DESCRIPTION_de = 'das sollte wirklich lang sein',
+ DESCRIPTION_fr = 'ceci devrait être vraiment long',
+ source = [ prog, man_pages ],
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz',
+ )
+
+env.Alias ( 'install', [ prog, man_pages ] )
+""" % locals())
+
+
+test.run(arguments='--install-sandbox=blubb install', stderr = None)
+
+test.must_exist( src_rpm )
+test.must_exist( machine_rpm )
+
+test.pass_test()
diff --git a/test/packaging/rpm/package.py b/test/packaging/rpm/package.py
new file mode 100644
index 0000000..a5f9f0f
--- /dev/null
+++ b/test/packaging/rpm/package.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to create a really simple rpm package.
+"""
+
+import os
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+ test.skip_test('rpm not found, skipping test\n')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os
+
+env=Environment(tools=['default', 'packaging'])
+
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+
+prog = env.Install( '/bin/' , Program( 'src/main.c') )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGEVERSION = 0,
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'balalalalal',
+ X_RPM_GROUP = 'Application/fu',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --debug=tree --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really really long',
+ source = [ prog ],
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+
+env.Alias( 'install', prog )
+""" % locals())
+
+test.run(arguments='', stderr = None)
+
+src_rpm = 'foo-1.2.3-0.src.rpm'
+machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+
+test.must_exist( machine_rpm )
+test.must_exist( src_rpm )
+test.must_not_exist( 'bin/main' )
+test.fail_test( not os.popen('rpm -qpl %s' % machine_rpm).read()=='/bin/main\n')
+test.fail_test( not os.popen('rpm -qpl %s' % src_rpm).read()=='foo-1.2.3.spec\nfoo-1.2.3.tar.gz\n')
+
+test.pass_test()
diff --git a/test/packaging/rpm/tagging.py b/test/packaging/rpm/tagging.py
new file mode 100644
index 0000000..198799a
--- /dev/null
+++ b/test/packaging/rpm/tagging.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to add file tags
+"""
+
+import os
+import string
+
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+ test.skip_test('rpm not found, skipping test\n')
+
+#
+# Test adding an attr tag to the built program.
+#
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os
+
+env = Environment(tools=['default', 'packaging'])
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+install_dir= os.path.join( ARGUMENTS.get('prefix', '/'), 'bin/' )
+prog_install = env.Install( install_dir , Program( 'src/main.c' ) )
+env.Tag( prog_install, UNIX_ATTR = '(0755, root, users)' )
+env.Alias( 'install', prog_install )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'balalalalal',
+ PACKAGEVERSION = 0,
+ X_RPM_GROUP = 'Applicatio/fu',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really really long',
+ source = [ prog_install ],
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+""" % locals())
+
+test.run(arguments='', stderr = None)
+
+src_rpm = 'foo-1.2.3-0.src.rpm'
+machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+
+test.must_exist( machine_rpm )
+test.must_exist( src_rpm )
+test.fail_test( not os.popen('rpm -qpl %s' % machine_rpm).read()=='/bin/main\n')
+test.fail_test( not os.popen('rpm -qpl %s' % src_rpm).read()=='foo-1.2.3.spec\nfoo-1.2.3.tar.gz\n')
+
+expect = '(0755, root, users) /bin/main'
+test.fail_test(string.find(test.read('foo-1.2.3.spec'), expect) == -1)
+
+test.pass_test()
diff --git a/test/packaging/sandbox-test.py b/test/packaging/sandbox-test.py
new file mode 100644
index 0000000..f82940b
--- /dev/null
+++ b/test/packaging/sandbox-test.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test a simple project
+"""
+
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+ test.skip_test('tar not found, skipping test\n')
+
+test.subdir('src')
+
+test.write([ 'src', 'foobar.h' ], '')
+test.write([ 'src', 'foobar.c' ], '')
+
+test.write('SConstruct', """
+from glob import glob
+
+src_files = glob( 'src/*.c' )
+include_files = glob( 'src/*.h' )
+
+SharedLibrary( 'foobar', src_files )
+
+env = Environment(tools=['default', 'packaging'])
+
+env.Package( NAME = 'libfoobar',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'targz',
+ source = src_files + include_files )
+
+env.Package( NAME = 'libfoobar',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'zip',
+ source = src_files + include_files )
+""")
+
+test.run(stderr=None)
+
+test.must_exist( 'libfoobar-1.2.3.tar.gz' )
+test.must_exist( 'libfoobar-1.2.3.zip' )
+
+test.pass_test()
diff --git a/test/packaging/strip-install-dir.py b/test/packaging/strip-install-dir.py
new file mode 100644
index 0000000..65b6a61
--- /dev/null
+++ b/test/packaging/strip-install-dir.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test stripping the InstallBuilder of the Package source file.
+"""
+
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+test.write( 'main.c', '' )
+test.write('SConstruct', """
+prog = Install( '/bin', 'main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ source = [ prog ],
+ )
+""")
+
+expected = """scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+Copy file(s): "main.c" to "foo-1.2.3/bin/main.c"
+tar -zc -f foo-1.2.3.tar.gz foo-1.2.3/bin/main.c
+scons: done building targets.
+"""
+
+test.run(arguments='', stderr = None, stdout=expected)
+
+test.must_not_exist( 'bin/main.c' )
+test.must_not_exist( '/bin/main.c' )
+
+test.pass_test()
diff --git a/test/packaging/tar/bz2.py b/test/packaging/tar/bz2.py
new file mode 100644
index 0000000..938ac11
--- /dev/null
+++ b/test/packaging/tar/bz2.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This tests the SRC bz2 packager, which does the following:
+ - create a tar package from the specified files
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+tar = test.detect('TAR', 'tar')
+
+if tar:
+ test.subdir('src')
+
+ test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+ """)
+
+ test.write('SConstruct', """
+Program( 'src/main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( PACKAGETYPE = 'src_tarbz2',
+ target = 'src.tar.bz2',
+ PACKAGEROOT = 'test',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+ test.run(arguments='', stderr = None)
+
+ test.must_exist( 'src.tar.bz2' )
+
+test.pass_test()
diff --git a/test/packaging/tar/gz.py b/test/packaging/tar/gz.py
new file mode 100644
index 0000000..26ce60d
--- /dev/null
+++ b/test/packaging/tar/gz.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This tests the SRC 'targz' packager, which does the following:
+ - create a targz package containing the specified files.
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+tar = test.detect('TAR', 'tar')
+
+if tar:
+ test.subdir('src')
+
+ test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+ """)
+
+ test.write('SConstruct', """
+Program( 'src/main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( PACKAGETYPE = 'src_targz',
+ target = 'src.tar.gz',
+ PACKAGEROOT = 'test',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+ test.run(arguments='', stderr = None)
+
+ test.must_exist( 'src.tar.gz' )
+
+test.pass_test()
diff --git a/test/packaging/use-builddir.py b/test/packaging/use-builddir.py
new file mode 100644
index 0000000..50a569a
--- /dev/null
+++ b/test/packaging/use-builddir.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test the ability to use the archiver in combination with builddir.
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+ test.skip_test('tar not found, skipping test\n')
+
+#
+# TEST: builddir usage.
+#
+test.subdir('src')
+test.subdir('build')
+
+test.write('src/main.c', '')
+
+test.write('SConstruct', """
+BuildDir('build', 'src')
+env=Environment(tools=['default', 'packaging'])
+env.Package( NAME = 'libfoo',
+ PACKAGEROOT = 'build/libfoo',
+ VERSION = '1.2.3',
+ PACKAGETYPE = 'src_zip',
+ target = 'build/libfoo-1.2.3.zip',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(stderr = None)
+
+test.must_exist( 'build/libfoo-1.2.3.zip' )
+
+# TEST: builddir not placed in archive
+# XXX: BuildDir should be stripped.
+#
+test.subdir('src')
+test.subdir('build')
+test.subdir('temp')
+
+test.write('src/main.c', '')
+
+test.write('SConstruct', """
+BuildDir('build', 'src')
+env=Environment(tools=['default', 'packaging'])
+env.Package( NAME = 'libfoo',
+ VERSION = '1.2.3',
+ PAKCAGETYPE = 'src_targz',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(stderr = None)
+
+test.must_exist( 'libfoo-1.2.3.tar.gz' )
+
+os.popen( 'tar -C temp -xzf %s'%test.workpath('libfoo-1.2.3.tar.gz') )
+
+test.must_exist( 'temp/libfoo-1.2.3/src/main.c' )
+test.must_exist( 'temp/libfoo-1.2.3/SConstruct' )
+
+test.pass_test()
diff --git a/test/packaging/zip.py b/test/packaging/zip.py
new file mode 100644
index 0000000..a2406e6
--- /dev/null
+++ b/test/packaging/zip.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This tests the SRC zip packager, which does the following:
+ - create a zip package from the specified files
+"""
+
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+zip = test.detect('ZIP', 'zip')
+
+if not zip:
+ test.skip_test('zip not found, skipping test\n')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+Program( 'src/main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( PACKAGETYPE = 'src_zip',
+ target = 'src.zip',
+ PACKAGEROOT = 'test',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'src.zip' )
+
+test.pass_test()