diff options
Diffstat (limited to 'src')
37 files changed, 3225 insertions, 247 deletions
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__ |