install() now takes spec AND prefix
This commit is contained in:
parent
0cd5866eea
commit
a4cda94524
21 changed files with 122 additions and 62 deletions
|
@ -9,6 +9,12 @@
|
||||||
from spack.error import SpackError
|
from spack.error import SpackError
|
||||||
|
|
||||||
|
|
||||||
|
def _check_concrete(spec):
|
||||||
|
"""If the spec is not concrete, raise a ValueError"""
|
||||||
|
if not spec.concrete:
|
||||||
|
raise ValueError('Specs passed to a DirectoryLayout must be concrete!')
|
||||||
|
|
||||||
|
|
||||||
class DirectoryLayout(object):
|
class DirectoryLayout(object):
|
||||||
"""A directory layout is used to associate unique paths with specs.
|
"""A directory layout is used to associate unique paths with specs.
|
||||||
Different installations are going to want differnet layouts for their
|
Different installations are going to want differnet layouts for their
|
||||||
|
@ -39,7 +45,8 @@ def make_path_for_spec(self, spec):
|
||||||
|
|
||||||
def path_for_spec(self, spec):
|
def path_for_spec(self, spec):
|
||||||
"""Return an absolute path from the root to a directory for the spec."""
|
"""Return an absolute path from the root to a directory for the spec."""
|
||||||
assert(spec.concrete)
|
_check_concrete(spec)
|
||||||
|
|
||||||
path = self.relative_path_for_spec(spec)
|
path = self.relative_path_for_spec(spec)
|
||||||
assert(not path.startswith(self.root))
|
assert(not path.startswith(self.root))
|
||||||
return os.path.join(self.root, path)
|
return os.path.join(self.root, path)
|
||||||
|
@ -105,7 +112,7 @@ def __init__(self, root, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def relative_path_for_spec(self, spec):
|
def relative_path_for_spec(self, spec):
|
||||||
assert(spec.concrete)
|
_check_concrete(spec)
|
||||||
|
|
||||||
path = new_path(
|
path = new_path(
|
||||||
spec.architecture,
|
spec.architecture,
|
||||||
|
@ -134,7 +141,7 @@ def read_spec(self, path):
|
||||||
|
|
||||||
|
|
||||||
def make_path_for_spec(self, spec):
|
def make_path_for_spec(self, spec):
|
||||||
assert(spec.concrete)
|
_check_concrete(spec)
|
||||||
|
|
||||||
path = self.path_for_spec(spec)
|
path = self.path_for_spec(spec)
|
||||||
spec_file_path = new_path(path, self.spec_file)
|
spec_file_path = new_path(path, self.spec_file)
|
||||||
|
@ -200,5 +207,3 @@ class InstallDirectoryAlreadyExistsError(DirectoryLayoutError):
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
super(InstallDirectoryAlreadyExistsError, self).__init__(
|
super(InstallDirectoryAlreadyExistsError, self).__init__(
|
||||||
"Install path %s already exists!")
|
"Install path %s already exists!")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Cmake(Package):
|
||||||
url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz'
|
url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz'
|
||||||
md5 = '097278785da7182ec0aea8769d06860c'
|
md5 = '097278785da7182ec0aea8769d06860c'
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure('--prefix=%s' % prefix,
|
configure('--prefix=%s' % prefix,
|
||||||
'--parallel=%s' % make_jobs)
|
'--parallel=%s' % make_jobs)
|
||||||
make()
|
make()
|
||||||
|
@ -407,27 +407,9 @@ def add_commands_to_module(self):
|
||||||
m.rmtree = shutil.rmtree
|
m.rmtree = shutil.rmtree
|
||||||
m.move = shutil.move
|
m.move = shutil.move
|
||||||
|
|
||||||
# Useful directories within the prefix
|
# Useful directories within the prefix are encapsulated in
|
||||||
|
# a Prefix object.
|
||||||
m.prefix = self.prefix
|
m.prefix = self.prefix
|
||||||
m.bin = new_path(self.prefix, 'bin')
|
|
||||||
m.sbin = new_path(self.prefix, 'sbin')
|
|
||||||
m.etc = new_path(self.prefix, 'etc')
|
|
||||||
m.include = new_path(self.prefix, 'include')
|
|
||||||
m.lib = new_path(self.prefix, 'lib')
|
|
||||||
m.lib64 = new_path(self.prefix, 'lib64')
|
|
||||||
m.libexec = new_path(self.prefix, 'libexec')
|
|
||||||
m.share = new_path(self.prefix, 'share')
|
|
||||||
m.doc = new_path(m.share, 'doc')
|
|
||||||
m.info = new_path(m.share, 'info')
|
|
||||||
m.man = new_path(m.share, 'man')
|
|
||||||
m.man1 = new_path(m.man, 'man1')
|
|
||||||
m.man2 = new_path(m.man, 'man2')
|
|
||||||
m.man3 = new_path(m.man, 'man3')
|
|
||||||
m.man4 = new_path(m.man, 'man4')
|
|
||||||
m.man5 = new_path(m.man, 'man5')
|
|
||||||
m.man6 = new_path(m.man, 'man6')
|
|
||||||
m.man7 = new_path(m.man, 'man7')
|
|
||||||
m.man8 = new_path(m.man, 'man8')
|
|
||||||
|
|
||||||
|
|
||||||
def preorder_traversal(self, visited=None, **kwargs):
|
def preorder_traversal(self, visited=None, **kwargs):
|
||||||
|
@ -529,7 +511,7 @@ def installed_dependents(self):
|
||||||
@property
|
@property
|
||||||
def prefix(self):
|
def prefix(self):
|
||||||
"""Get the prefix into which this package should be installed."""
|
"""Get the prefix into which this package should be installed."""
|
||||||
return spack.install_layout.path_for_spec(self.spec)
|
return self.spec.prefix
|
||||||
|
|
||||||
|
|
||||||
def url_version(self, version):
|
def url_version(self, version):
|
||||||
|
@ -620,7 +602,7 @@ def do_install(self):
|
||||||
# case it needs to add extra files)
|
# case it needs to add extra files)
|
||||||
spack.install_layout.make_path_for_spec(self.spec)
|
spack.install_layout.make_path_for_spec(self.spec)
|
||||||
|
|
||||||
self.install(self.prefix)
|
self.install(self.spec, self.prefix)
|
||||||
if not os.path.isdir(self.prefix):
|
if not os.path.isdir(self.prefix):
|
||||||
tty.die("Install failed for %s. No install dir created." % self.name)
|
tty.die("Install failed for %s. No install dir created." % self.name)
|
||||||
|
|
||||||
|
@ -683,7 +665,7 @@ def module(self):
|
||||||
fromlist=[self.__class__.__name__])
|
fromlist=[self.__class__.__name__])
|
||||||
|
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
"""Package implementations override this with their own build configuration."""
|
"""Package implementations override this with their own build configuration."""
|
||||||
tty.die("Packages must provide an install method!")
|
tty.die("Packages must provide an install method!")
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Callpath(Package):
|
||||||
depends_on("dyninst")
|
depends_on("dyninst")
|
||||||
depends_on("mpich")
|
depends_on("mpich")
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -5,7 +5,7 @@ class Cmake(Package):
|
||||||
url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz'
|
url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz'
|
||||||
versions = { '2.8.10.2' : '097278785da7182ec0aea8769d06860c' }
|
versions = { '2.8.10.2' : '097278785da7182ec0aea8769d06860c' }
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure('--prefix=%s' % prefix,
|
configure('--prefix=%s' % prefix,
|
||||||
'--parallel=%s' % make_jobs)
|
'--parallel=%s' % make_jobs)
|
||||||
make()
|
make()
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Dyninst(Package):
|
||||||
depends_on("libelf")
|
depends_on("libelf")
|
||||||
depends_on("libdwarf")
|
depends_on("libdwarf")
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -23,21 +23,21 @@ def clean(self):
|
||||||
make('clean')
|
make('clean')
|
||||||
|
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
# dwarf build does not set arguments for ar properly
|
# dwarf build does not set arguments for ar properly
|
||||||
make.add_default_arg('ARFLAGS=rcs')
|
make.add_default_arg('ARFLAGS=rcs')
|
||||||
|
|
||||||
# Dwarf doesn't provide an install, so we have to do it.
|
# Dwarf doesn't provide an install, so we have to do it.
|
||||||
mkdirp(bin, include, lib, man1)
|
mkdirp(prefix.bin, prefix.include, prefix.lib, prefix.man1)
|
||||||
|
|
||||||
with working_dir('libdwarf'):
|
with working_dir('libdwarf'):
|
||||||
configure("--prefix=%s" % prefix, '--enable-shared')
|
configure("--prefix=%s" % prefix, '--enable-shared')
|
||||||
make()
|
make()
|
||||||
|
|
||||||
install('libdwarf.a', lib)
|
install('libdwarf.a', prefix.lib)
|
||||||
install('libdwarf.so', lib)
|
install('libdwarf.so', prefix.lib)
|
||||||
install('libdwarf.h', include)
|
install('libdwarf.h', prefix.include)
|
||||||
install('dwarf.h', include)
|
install('dwarf.h', prefix.include)
|
||||||
|
|
||||||
with working_dir('dwarfdump2'):
|
with working_dir('dwarfdump2'):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
|
@ -46,6 +46,6 @@ def install(self, prefix):
|
||||||
# cause a race in parallel
|
# cause a race in parallel
|
||||||
make(parallel=False)
|
make(parallel=False)
|
||||||
|
|
||||||
install('dwarfdump', bin)
|
install('dwarfdump', prefix.bin)
|
||||||
install('dwarfdump.conf', lib)
|
install('dwarfdump.conf', prefix.lib)
|
||||||
install('dwarfdump.1', man1)
|
install('dwarfdump.1', prefix.man1)
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Libelf(Package):
|
||||||
|
|
||||||
versions = { '0.8.13' : '4136d7b4c04df68b686570afa26988ac' }
|
versions = { '0.8.13' : '4136d7b4c04df68b686570afa26988ac' }
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix,
|
configure("--prefix=%s" % prefix,
|
||||||
"--enable-shared",
|
"--enable-shared",
|
||||||
"--disable-dependency-tracking",
|
"--disable-dependency-tracking",
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Libunwind(Package):
|
||||||
|
|
||||||
versions = { '1.1' : 'fb4ea2f6fbbe45bf032cd36e586883ce' }
|
versions = { '1.1' : 'fb4ea2f6fbbe45bf032cd36e586883ce' }
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -13,7 +13,7 @@ class Mpich(Package):
|
||||||
provides('mpi@:3', when='@3:')
|
provides('mpi@:3', when='@3:')
|
||||||
provides('mpi@:1', when='@1:')
|
provides('mpi@:1', when='@1:')
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Mpileaks(Package):
|
||||||
depends_on("mpich")
|
depends_on("mpich")
|
||||||
depends_on("callpath")
|
depends_on("callpath")
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
from spack.color import *
|
from spack.color import *
|
||||||
from spack.util.lang import *
|
from spack.util.lang import *
|
||||||
from spack.util.string import *
|
from spack.util.string import *
|
||||||
|
from spack.util.prefix import Prefix
|
||||||
|
|
||||||
|
|
||||||
# Convenient names for color formats so that other things can use them
|
# Convenient names for color formats so that other things can use them
|
||||||
|
@ -439,6 +440,11 @@ def preorder_traversal(self, visited=None, d=0, **kwargs):
|
||||||
yield elt
|
yield elt
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prefix(self):
|
||||||
|
return Prefix(spack.install_layout.path_for_spec(self))
|
||||||
|
|
||||||
|
|
||||||
def _concretize_helper(self, presets=None, visited=None):
|
def _concretize_helper(self, presets=None, visited=None):
|
||||||
"""Recursive helper function for concretize().
|
"""Recursive helper function for concretize().
|
||||||
This concretizes everything bottom-up. As things are
|
This concretizes everything bottom-up. As things are
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Callpath(Package):
|
||||||
depends_on("dyninst")
|
depends_on("dyninst")
|
||||||
depends_on("mpi")
|
depends_on("mpi")
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Dyninst(Package):
|
||||||
depends_on("libelf")
|
depends_on("libelf")
|
||||||
depends_on("libdwarf")
|
depends_on("libdwarf")
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -5,7 +5,7 @@ class Fake(Package):
|
||||||
url = "http://www.fake-spack-example.org/downloads/fake-1.0.tar.gz"
|
url = "http://www.fake-spack-example.org/downloads/fake-1.0.tar.gz"
|
||||||
versions = { '1.0' : 'foobarbaz' }
|
versions = { '1.0' : 'foobarbaz' }
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -23,21 +23,21 @@ def clean(self):
|
||||||
make('clean')
|
make('clean')
|
||||||
|
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
# dwarf build does not set arguments for ar properly
|
# dwarf build does not set arguments for ar properly
|
||||||
make.add_default_arg('ARFLAGS=rcs')
|
make.add_default_arg('ARFLAGS=rcs')
|
||||||
|
|
||||||
# Dwarf doesn't provide an install, so we have to do it.
|
# Dwarf doesn't provide an install, so we have to do it.
|
||||||
mkdirp(bin, include, lib, man1)
|
mkdirp(prefix.bin, prefix.include, prefix.lib, prefix.man1)
|
||||||
|
|
||||||
with working_dir('libdwarf'):
|
with working_dir('libdwarf'):
|
||||||
configure("--prefix=%s" % prefix, '--enable-shared')
|
configure("--prefix=%s" % prefix, '--enable-shared')
|
||||||
make()
|
make()
|
||||||
|
|
||||||
install('libdwarf.a', lib)
|
install('libdwarf.a', prefix.lib)
|
||||||
install('libdwarf.so', lib)
|
install('libdwarf.so', prefix.lib)
|
||||||
install('libdwarf.h', include)
|
install('libdwarf.h', prefix.include)
|
||||||
install('dwarf.h', include)
|
install('dwarf.h', prefix.include)
|
||||||
|
|
||||||
with working_dir('dwarfdump2'):
|
with working_dir('dwarfdump2'):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
|
@ -46,6 +46,6 @@ def install(self, prefix):
|
||||||
# cause a race in parallel
|
# cause a race in parallel
|
||||||
make(parallel=False)
|
make(parallel=False)
|
||||||
|
|
||||||
install('dwarfdump', bin)
|
install('dwarfdump', prefix.bin)
|
||||||
install('dwarfdump.conf', lib)
|
install('dwarfdump.conf', prefix.lib)
|
||||||
install('dwarfdump.1', man1)
|
install('dwarfdump.1', prefix.man1)
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Libelf(Package):
|
||||||
'0.8.12' : 'e21f8273d9f5f6d43a59878dc274fec7',
|
'0.8.12' : 'e21f8273d9f5f6d43a59878dc274fec7',
|
||||||
'0.8.10' : '9db4d36c283d9790d8fa7df1f4d7b4d9' }
|
'0.8.10' : '9db4d36c283d9790d8fa7df1f4d7b4d9' }
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix,
|
configure("--prefix=%s" % prefix,
|
||||||
"--enable-shared",
|
"--enable-shared",
|
||||||
"--disable-dependency-tracking",
|
"--disable-dependency-tracking",
|
||||||
|
|
|
@ -15,7 +15,7 @@ class Mpich(Package):
|
||||||
provides('mpi@:3', when='@3:')
|
provides('mpi@:3', when='@3:')
|
||||||
provides('mpi@:1', when='@1:')
|
provides('mpi@:1', when='@1:')
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Mpich2(Package):
|
||||||
provides('mpi@:2.1', when='@1.1:')
|
provides('mpi@:2.1', when='@1.1:')
|
||||||
provides('mpi@:2.2', when='@1.2:')
|
provides('mpi@:2.2', when='@1.2:')
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Mpileaks(Package):
|
||||||
depends_on("mpi")
|
depends_on("mpi")
|
||||||
depends_on("callpath")
|
depends_on("callpath")
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Zmpi(Package):
|
||||||
provides('mpi@:10.0')
|
provides('mpi@:10.0')
|
||||||
depends_on('fake')
|
depends_on('fake')
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, spec, prefix):
|
||||||
configure("--prefix=%s" % prefix)
|
configure("--prefix=%s" % prefix)
|
||||||
make()
|
make()
|
||||||
make("install")
|
make("install")
|
||||||
|
|
67
lib/spack/spack/util/prefix.py
Normal file
67
lib/spack/spack/util/prefix.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
"""
|
||||||
|
This file contains utilities to help with installing packages.
|
||||||
|
"""
|
||||||
|
from spack.util.filesystem import new_path
|
||||||
|
|
||||||
|
class Prefix(object):
|
||||||
|
"""This class represents an installation prefix, but provides useful
|
||||||
|
attributes for referring to directories inside the prefix.
|
||||||
|
|
||||||
|
For example, you can do something like this::
|
||||||
|
|
||||||
|
prefix = Prefix('/usr')
|
||||||
|
print prefix.lib
|
||||||
|
print prefix.lib64
|
||||||
|
print prefix.bin
|
||||||
|
print prefix.share
|
||||||
|
print prefix.man4
|
||||||
|
|
||||||
|
This program would print:
|
||||||
|
|
||||||
|
/usr/lib
|
||||||
|
/usr/lib64
|
||||||
|
/usr/bin
|
||||||
|
/usr/share
|
||||||
|
/usr/share/man/man4
|
||||||
|
|
||||||
|
In addition, Prefix objects can be added to strings, e.g.:
|
||||||
|
|
||||||
|
print "foobar " + prefix
|
||||||
|
|
||||||
|
This prints 'foobar /usr". All of this is meant to make custom
|
||||||
|
installs easy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, prefix):
|
||||||
|
self.prefix = prefix
|
||||||
|
self.bin = new_path(self.prefix, 'bin')
|
||||||
|
self.sbin = new_path(self.prefix, 'sbin')
|
||||||
|
self.etc = new_path(self.prefix, 'etc')
|
||||||
|
self.include = new_path(self.prefix, 'include')
|
||||||
|
self.lib = new_path(self.prefix, 'lib')
|
||||||
|
self.lib64 = new_path(self.prefix, 'lib64')
|
||||||
|
self.libexec = new_path(self.prefix, 'libexec')
|
||||||
|
self.share = new_path(self.prefix, 'share')
|
||||||
|
self.doc = new_path(self.share, 'doc')
|
||||||
|
self.info = new_path(self.share, 'info')
|
||||||
|
self.man = new_path(self.share, 'man')
|
||||||
|
self.man1 = new_path(self.man, 'man1')
|
||||||
|
self.man2 = new_path(self.man, 'man2')
|
||||||
|
self.man3 = new_path(self.man, 'man3')
|
||||||
|
self.man4 = new_path(self.man, 'man4')
|
||||||
|
self.man5 = new_path(self.man, 'man5')
|
||||||
|
self.man6 = new_path(self.man, 'man6')
|
||||||
|
self.man7 = new_path(self.man, 'man7')
|
||||||
|
self.man8 = new_path(self.man, 'man8')
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.prefix
|
||||||
|
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return str(self) + other
|
||||||
|
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return other + str(self)
|
Loading…
Reference in a new issue