def merge_dir(self, src_dir, dst_dir): """Copy the downloaded files into the target path""" self.log.info("merging new files...") if not exists(dst_dir): sh.mkdir(dst_dir, log=self.log) sh.cp(join(src_dir, '*'), dstdir=dst_dir, recursive=True, log=self.log.info)
def test_trailing_slashes(self): from mklib.sh import cp # Linux: # $ ls -F # dira/ filea # $ cp -v filea/ dira # cp: cannot stat `filea/': Not a directory # # It seems to work fine on other Unices (Solaris 6, Solaris 8, # AIX 5.1, HP-UX 11). # # This case is a little confused on Windows, but it does fail. # Given the _file_ "foo": # C:\> copy foo\ bar # The system cannot find the file specified. # **but**: # >>> glob.glob("foo\\") # ['foo\\'] # >>> os.stat("foo\\") # (33206, 0L, 3, 1, 0, 0, 6L, 1097450002, 1097450002, 1097450002) _write("filea", "this is filea") if sys.platform in ("win32", "linux2"): self.assertRaisesEx(OSError, cp, "filea"+os.sep, "dira") # At one point, cp()'s use of os.path.basename() screws up when # the given source and dest have trailing slashes. We cannot # just remove the trailing slashes all the time -- I don't think # -- because of some obscure POSIX thing (follow the note for # --remove-trailing-slashes in `info cp`). os.mkdir("dira") os.mkdir("dirb") cp("dira"+os.sep, "dirb"+os.sep, recursive=True) self.assertDir(join("dirb", "dira"))
def test_basic(self): from mklib.sh import cp _write("filea", "blah blah") cp("filea", "fileb") self.assertFile("fileb", content="blah blah") os.mkdir("dira") dst = join("dira", "fileb") cp("filea", dst) self.assertFile(dst, content="blah blah") os.mkdir("dirb") cp("filea", "dirb") self.assertFile(join("dirb", "filea"), content="blah blah") _write("fileb", "fileb's content") _write("filec", "filec's content") self.assertRaises(OSError, cp, ["fileb", "filec"], "dirc") self.assertRaises(OSError, cp, ["fileb", "filec"], "filea") os.mkdir("dirc") cp(["fileb", "filec"], "dirc") self.assertFile(join("dirc", "fileb"), content="fileb's content") self.assertFile(join("dirc", "filec"), content="filec's content") os.mkdir("dird") cp(["fileb", "filec"], dstdir="dird") self.assertFile(join("dird", "fileb"), content="fileb's content") self.assertFile(join("dird", "filec"), content="filec's content")
def make(self): assert sys.platform != "win32", "'webdist' not implemented for win32" build_dir = join(self.dir, "build", "webdist") zip_dir = join(build_dir, "go") if exists(build_dir): sh.rm(build_dir) os.makedirs(zip_dir) # Copy the webdist bits to the build tree. manifest = [ "src/trentm.com/project-info.xml", "src/trentm.com/index.markdown", "LICENSE.txt", "lib/go.py", "src/trentm.com/logo.jpg", ] for src in manifest: sh.cp(src, dstdir=zip_dir, log=self.log.info) # Zip up the webdist contents. dist_dir = join(self.dir, "dist") bit = abspath(join(dist_dir, "go-%s.web" % _get_version())) if exists(bit): os.remove(bit) if not exists(dist_dir): os.makedirs(dist_dir) sh.run_in_dir("zip -r %s go" % bit, build_dir, self.log.info)
def test_trailing_slashes(self): from mklib.sh import cp # Linux: # $ ls -F # dira/ filea # $ cp -v filea/ dira # cp: cannot stat `filea/': Not a directory # # It seems to work fine on other Unices (Solaris 6, Solaris 8, # AIX 5.1, HP-UX 11). # # This case is a little confused on Windows, but it does fail. # Given the _file_ "foo": # C:\> copy foo\ bar # The system cannot find the file specified. # **but**: # >>> glob.glob("foo\\") # ['foo\\'] # >>> os.stat("foo\\") # (33206, 0L, 3, 1, 0, 0, 6L, 1097450002, 1097450002, 1097450002) _write("filea", "this is filea") if sys.platform in ("win32", "linux2"): self.assertRaisesEx(OSError, cp, "filea" + os.sep, "dira") # At one point, cp()'s use of os.path.basename() screws up when # the given source and dest have trailing slashes. We cannot # just remove the trailing slashes all the time -- I don't think # -- because of some obscure POSIX thing (follow the note for # --remove-trailing-slashes in `info cp`). os.mkdir("dira") os.mkdir("dirb") cp("dira" + os.sep, "dirb" + os.sep, recursive=True) self.assertDir(join("dirb", "dira"))
def test_binary_mode(self): # Test copying text files with EOLs to make sure copying is # being done binary. from mklib.sh import cp multiline_content = "line1\nline2\r\nline3\rline4" _write("textfile", multiline_content) cp("textfile", "textfile_copy") self.assertFile("textfile_copy", content=multiline_content)
def test_no_dstdir(self): from mklib.sh import cp _write("filea", "filea's content") # Using 'dst' does not guarantee a dir. cp("filea", "dirb") self.assertFile("dirb", content="filea's content") # Using 'dstdir' **does** guarantee a dir. self.assertRaises(OSError, cp, "filea", dstdir="dira")
def test_preserve_dir(self): from mklib.sh import cp a = "dira"; b = "dirb" os.mkdir(a) a_stat = os.stat(a) os.chmod(a, 0777) cp(a, b, recursive=True, preserve=True) self.assertDir(b, mode=0777, mtime=a_stat.st_mtime, atime=a_stat.st_atime)
def test_no_preserve(self): from mklib.sh import cp a = "filea"; b = "fileb" _write(a, "this is filea") a_stat = os.stat(a) time.sleep(1) cp(a, b) # preserve=False by default a_mtime = os.stat(a).st_mtime b_mtime = os.stat(b).st_mtime self.assertNotEqual(a_mtime, b_mtime, "mtime's for src (%s, %s) and dst (%s, %s) were preserved " "by default, they should not be" % (a, a_mtime, b, b_mtime))
def test_force(self): from mklib.sh import cp _write("sourcefile", "this is sourcefile") sourcefile_mode = stat.S_IMODE(os.stat("sourcefile").st_mode) umask = _getumask() _write("noaccess", "this is nowrite") os.chmod("noaccess", 0000) self.assertRaises(OSError, cp, "sourcefile", "noaccess") cp("sourcefile", "noaccess", force=True) self.assertFile("noaccess", content="this is sourcefile", mode=sourcefile_mode&~umask)
def test_preserve_dir(self): from mklib.sh import cp a = "dira" b = "dirb" os.mkdir(a) a_stat = os.stat(a) os.chmod(a, 0777) cp(a, b, recursive=True, preserve=True) self.assertDir(b, mode=0777, mtime=a_stat.st_mtime, atime=a_stat.st_atime)
def test_force(self): from mklib.sh import cp _write("sourcefile", "this is sourcefile") sourcefile_mode = stat.S_IMODE(os.stat("sourcefile").st_mode) umask = _getumask() _write("noaccess", "this is nowrite") os.chmod("noaccess", 0000) self.assertRaises(OSError, cp, "sourcefile", "noaccess") cp("sourcefile", "noaccess", force=True) self.assertFile("noaccess", content="this is sourcefile", mode=sourcefile_mode & ~umask)
def test_common_sub_sub_dirs(self): # The automatic promotion of the target to: # join(target, os.path.basename(source)) # when the target is a directory must not be applied recursively. # Consider this layout: # basedir1 # `- commondir # `- commonsubdir # `- file1.txt # basedir2 # `- commondir # `- commonsubdir # `- file1.txt # Now copy the contents of those two basedirs to a new target: # mkdir targetdir # cp -r basedir1/* targetdir # cp -r basedir2/* targetdir # This *should* result in: # targetdir # `- commondir # `- commonsubdir # |- file1.txt # `- file2.txt # but will result in this if the promotion is recursive: # targetdir # `- commondir # `- commonsubdir # |- file1.txt # `- commonsubdir # `- file2.txt from mklib.sh import cp os.makedirs(join("basedir1", "commondir", "commonsubdir")) _write(join("basedir1", "commondir", "commonsubdir", "file1.txt"), content="this is file1") os.makedirs(join("basedir2", "commondir", "commonsubdir")) _write(join("basedir2", "commondir", "commonsubdir", "file2.txt"), content="this is file2") os.mkdir("targetdir") cp(join("basedir1", "*"), "targetdir", recursive=True) cp(join("basedir2", "*"), "targetdir", recursive=True) self.assertDir("targetdir", files=["commondir"]) self.assertDir(join("targetdir", "commondir"), files=["commonsubdir"]) self.assertDir(join("targetdir", "commondir", "commonsubdir"), files=["file1.txt", "file2.txt"]) self.assertFile( join("targetdir", "commondir", "commonsubdir", "file1.txt"), content="this is file1") self.assertFile( join("targetdir", "commondir", "commonsubdir", "file2.txt"), content="this is file2")
def test_no_preserve(self): from mklib.sh import cp a = "filea" b = "fileb" _write(a, "this is filea") a_stat = os.stat(a) time.sleep(1) cp(a, b) # preserve=False by default a_mtime = os.stat(a).st_mtime b_mtime = os.stat(b).st_mtime self.assertNotEqual( a_mtime, b_mtime, "mtime's for src (%s, %s) and dst (%s, %s) were preserved " "by default, they should not be" % (a, a_mtime, b, b_mtime))
def test_common_sub_sub_dirs(self): # The automatic promotion of the target to: # join(target, os.path.basename(source)) # when the target is a directory must not be applied recursively. # Consider this layout: # basedir1 # `- commondir # `- commonsubdir # `- file1.txt # basedir2 # `- commondir # `- commonsubdir # `- file1.txt # Now copy the contents of those two basedirs to a new target: # mkdir targetdir # cp -r basedir1/* targetdir # cp -r basedir2/* targetdir # This *should* result in: # targetdir # `- commondir # `- commonsubdir # |- file1.txt # `- file2.txt # but will result in this if the promotion is recursive: # targetdir # `- commondir # `- commonsubdir # |- file1.txt # `- commonsubdir # `- file2.txt from mklib.sh import cp os.makedirs(join("basedir1", "commondir", "commonsubdir")) _write(join("basedir1", "commondir", "commonsubdir", "file1.txt"), content="this is file1") os.makedirs(join("basedir2", "commondir", "commonsubdir")) _write(join("basedir2", "commondir", "commonsubdir", "file2.txt"), content="this is file2") os.mkdir("targetdir") cp(join("basedir1", "*"), "targetdir", recursive=True) cp(join("basedir2", "*"), "targetdir", recursive=True) self.assertDir("targetdir", files=["commondir"]) self.assertDir(join("targetdir", "commondir"), files=["commonsubdir"]) self.assertDir(join("targetdir", "commondir", "commonsubdir"), files=["file1.txt", "file2.txt"]) self.assertFile(join("targetdir", "commondir", "commonsubdir", "file1.txt"), content="this is file1") self.assertFile(join("targetdir", "commondir", "commonsubdir", "file2.txt"), content="this is file2")
def make(self): sh.mkdir(self.cfg.ashelp_dir, log=self.log) sh.cp(join(self.htdocs_dir, "*"), dstdir=self.cfg.ashelp_dir, recursive=True, log=self.log.info) junk = [ join(self.cfg.ashelp_dir, "komodo-js-api.toc"), join(self.cfg.ashelp_dir, "manifest.ini"), join(self.cfg.ashelp_dir, "aux_search.rdf"), ] for path in junk: if exists(path): sh.rm(path, log=self.log)
def test_dir(self): from mklib.sh import cp umask = _getumask() os.mkdir("dira") mode_dira = stat.S_IMODE(os.stat("dira").st_mode) filea = join("dira", "filea") _write(filea, "filea's content") mode_filea = stat.S_IMODE(os.stat(filea).st_mode) self.assertRaises(OSError, cp, "dira", "dirb") # need recursive=True to copy dir cp("dira", "dirb", recursive=True) self.assertDir("dirb", files=["filea"], mode=mode_dira&~umask) self.assertFile(join("dirb", "filea"), content="filea's content", mode=mode_filea&~umask)
def test_preserve(self): from mklib.sh import cp a = "filea"; b = "fileb" _write(a, "this is filea") a_stat = os.stat(a) os.chmod(a, 0777) cp(a, b, preserve=True) self.assertFile(b, mtime=a_stat.st_mtime, atime=a_stat.st_atime) if sys.platform != "win32": self.assertFile(b, mode=0777) os.chmod(a, 0700) cp(a, b, preserve=True) self.assertFile(b, mtime=a_stat.st_mtime, atime=a_stat.st_atime) if sys.platform != "win32": self.assertFile(b, mode=0700)
def test_noglob(self): from mklib.sh import cp # Can't have '*' or '?' in filenames on Windows, so use '[...]' # globbing to test. _write("foop", "hello from foop") _write("foot", "hello from foot") _write("foo[tp]", "hello from foo[tp]") os.mkdir("dir_with_glob") os.mkdir("dir_without_glob") cp("foo[tp]", dstdir="dir_with_glob") cp("foo[tp]", dstdir="dir_without_glob", noglob=True) self.assertDir("dir_with_glob", files=["foop", "foot"]) self.assertDir("dir_without_glob", files=["foo[tp]"]) os.mkdir("dir_with_no_files") self.assertRaises(OSError, cp, "fo*", dstdir="dir_with_no_files", noglob=True)
def test_preserve(self): from mklib.sh import cp a = "filea" b = "fileb" _write(a, "this is filea") a_stat = os.stat(a) os.chmod(a, 0777) cp(a, b, preserve=True) self.assertFile(b, mtime=a_stat.st_mtime, atime=a_stat.st_atime) if sys.platform != "win32": self.assertFile(b, mode=0777) os.chmod(a, 0700) cp(a, b, preserve=True) self.assertFile(b, mtime=a_stat.st_mtime, atime=a_stat.st_atime) if sys.platform != "win32": self.assertFile(b, mode=0700)
def test_dir(self): from mklib.sh import cp umask = _getumask() os.mkdir("dira") mode_dira = stat.S_IMODE(os.stat("dira").st_mode) filea = join("dira", "filea") _write(filea, "filea's content") mode_filea = stat.S_IMODE(os.stat(filea).st_mode) self.assertRaises(OSError, cp, "dira", "dirb") # need recursive=True to copy dir cp("dira", "dirb", recursive=True) self.assertDir("dirb", files=["filea"], mode=mode_dira & ~umask) self.assertFile(join("dirb", "filea"), content="filea's content", mode=mode_filea & ~umask)
def make(self): # Determine which release notes document to use. filters = self.cfg.filters or set([]) if "edit" in filters and "ide" not in filters: # Use "releases/ide.html" for the Komodo Edit release # notes -- at least for now. filters.add("ide") candidates = [] for filter in filters: path = join(self.htdocs_dir, "releases", filter + ".html") if exists(path): candidates.append(path) if len(candidates) == 1: relnotes_src_path = candidates[0] else: raise MkError( "Ambiguity in which `releases/*.html' to use " "for `relnotes.html'. This target can only be used when " "filtering for a specific Komodo flavor (see --filter " "configure.py option).") # CSS css_dir = join(self.cfg.miniset_dir, ".css") sh.mkdir(css_dir, log=self.log) sh.cp(join(self.htdocs_dir, "css", "screen.css"), dstdir=css_dir, log=self.log.info) # License text. sh.cp(self.cfg.license_text_path, join(self.cfg.miniset_dir, "license.txt"), log=self.log.info) # Release notes and install notes. # These are more difficult, because we need to update some of # the links in these files. manifest = [ (relnotes_src_path, join(self.cfg.miniset_dir, "relnotes.html")), (join(self.htdocs_dir, "install.html"), join(self.cfg.miniset_dir, "install.html")), ] # - Bunch 'o imports. for src, dst in manifest: independentize_html_path(src, dst, css_dir=".css", log=self.log)
def make(self): # komododoc.manifest defines = {"LANG": self.cfg.lang} src = join("mozhelp", "chrome.manifest") dst = join(self.cfg.chrome_dir, "komododoc.manifest") sh.mkdir(dirname(dst), log=self.log) self.log.info("preprocess %s %s", src, dst) preprocess.preprocess(src, dst, defines, contentType="Text", substitute=True) # content content_dir = join(self.cfg.chrome_dir, "komododoc", "content") sh.mkdir(content_dir, log=self.log) sh.cp(join("mozhelp", "helpOverlay.xul"), dstdir=content_dir, log=self.log.info) # locale sh.mkdir(self.locale_dir, log=self.log) sh.cp(join("mozhelp", "help_help.xhtml"), dstdir=self.locale_dir, log=self.log.info) sh.cp(join("mozhelp", "komodohelp.rdf"), dstdir=self.locale_dir, log=self.log.info) sh.cp(join(self.htdocs_dir, "*"), dstdir=self.locale_dir, recursive=True, log=self.log.info) junk = [ join(self.locale_dir, "komodo-js-api.toc"), join(self.locale_dir, "manifest.ini"), join(self.locale_dir, "toc.xml") ] for path in junk: if exists(path): sh.rm(path, log=self.log) help_toc_rdf = join(self.locale_dir, "help-toc.rdf") try: sh.run( "python support/tocxml2helptocrdf.py %s > %s" % (join(self.htdocs_dir, "toc.xml"), help_toc_rdf), self.log.info) except: if exists(help_toc_rdf): sh.rm(help_toc_rdf) raise
def make(self): htdocs_dir = self.htdocs_dir self.log.info("build '%s' docs into '%s'", self.cfg.lang, htdocs_dir) defines = { 'LICENSE_TEXT_PATH': self.cfg.license_text_path, } for src, dst in self.manifest(): src = normpath(join(self.dir, self.cfg.lang, src)) dst = normpath(join(htdocs_dir, dst)) if not exists(dirname(dst)): self.log.info("mkdir `%s'", dirname(dst)) os.makedirs(dirname(dst)) ext = splitext(src)[1] if ext in (".html", ".txt"): self.log.info("preprocess %s %s", src, dst) preprocess.preprocess(src, dst, defines, substitute=True) if ext == ".html": app_filter_html_path_inplace(dst, self.cfg.filters, log=self.log.info) else: sh.cp(src, dst, log=self.log.info)
def test_permissions(self): # Test nitpicky permissions on a simple copy: # Rules: if fileb exists, keep permissions, if not then set # permissions to filea&~umask. from mklib.sh import cp a = "filea" b = "fileb" old_umask = os.umask(022) try: # - before: filea (-rw-r--r--), no fileb, umask=0022 # action: cp filea fileb # after: filea (-rw-r--r--), fileb (-rw-r--r--) _write(a, "filea's content") os.chmod(a, 0644) cp(a, b) self.assertFile(b, mode=0644) # - before: filea (-rwxrwxrwx), no fileb, umask=0022 # action: cp filea fileb # after: filea (-rwxrwxrwx), fileb (-rwxr-xr-x) os.remove(b) os.chmod(a, 0777) cp(a, b) self.assertFile(b, mode=0755) # - before: filea (-rw-r--r--), fileb (-rwxrwxrwx), umask=0022 # action: cp filea fileb # after: filea (-rw-r--r--), fileb (-rwxrwxrwx) os.chmod(a, 0644) os.chmod(b, 0777) cp(a, b) self.assertFile(b, mode=0777) # - before: filea (-rwxrwxrwx), fileb (--w-------), umask=0022 # action: cp filea fileb # after: filea (-rwxrwxrwx), fileb (--w-------) os.chmod(a, 0777) os.chmod(b, 0200) cp(a, b) self.assertFile(b, mode=0200) finally: os.umask(old_umask)
def make(self): dst = self.results[0].relpath sh.mkdir(dirname(dst), log=self.log) sh.cp(self.deps[0].relpath, dst, log=self.log.info)
def make(self): build_dir = join(self.dir, "build", "langpack") pkg_dir = join(self.dir, "packages") locale_dir = join(self.dir, "src", "chrome", "komodo", "locale") # Clean build dir. if exists(build_dir): sh.rm(build_dir, self.log) os.makedirs(build_dir) # Version ver_bits = [self.LANGPACK_VERSION, self._svnversion_from_dir(locale_dir)] version = '.'.join([v for v in ver_bits if v]) # Create the package contents. os.makedirs(join(build_dir, "chrome")) sh.cp(locale_dir, join(build_dir, "chrome", "locale"), recursive=True, log=self.log.info) for dirpath, dnames, fnames in os.walk(build_dir): if ".svn" in dnames: sh.rm(join(dirpath, ".svn"), self.log) dnames.remove(".svn") for fname in [".consign", "Conscript"]: if fname in fnames: sh.rm(join(dirpath, fname), self.log) self._writefile(join(build_dir, "chrome.manifest"), "locale komodo-langpack en-US chrome/locale/en-US/") self._writefile(join(build_dir, "install.rdf"), _dedent("""\ <?xml version="1.0"?> <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Description about="urn:mozilla:install-manifest"> <em:name>Komodo Langpack</em:name> <em:description>Interface Langpack for Komodo</em:description> <em:version>%s</em:version> <em:id>[email protected]</em:id> <em:creator>ActiveState</em:creator> <em:type>2</em:type> <!-- Komodo IDE --> <em:targetApplication> <Description> <em:id>{36E66FA0-F259-11D9-850E-000D935D3368}</em:id> <em:minVersion>4.0</em:minVersion> <em:maxVersion>5.*</em:maxVersion> </Description> </em:targetApplication> <!-- Komodo Edit --> <em:targetApplication> <Description> <em:id>{b1042fb5-9e9c-11db-b107-000d935d3368}</em:id> <em:minVersion>4.0</em:minVersion> <em:maxVersion>5.*</em:maxVersion> </Description> </em:targetApplication> </Description> </RDF> """ % version)) # Package it up. if not exists(pkg_dir): os.makedirs(pkg_dir) pkg_name = ["Komodo", "LangPack", version] pkg_name = '-'.join(pkg_name) + ".xpi" pkg_path = join(pkg_dir, pkg_name) sh.run_in_dir('zip -rq "%s" .' % abspath(pkg_path), build_dir, self.log.info) self.log.info("created `%s'", pkg_path)
def make(self): build_dir = join(self.dir, "build", "langpack") pkg_dir = join(self.dir, "packages") locale_dir = join(self.dir, "src", "chrome", "komodo", "locale") # Clean build dir. if exists(build_dir): sh.rm(build_dir, self.log) os.makedirs(build_dir) # Version ver_bits = [ self.LANGPACK_VERSION, self._svnversion_from_dir(locale_dir) ] version = '.'.join([v for v in ver_bits if v]) # Create the package contents. os.makedirs(join(build_dir, "chrome")) sh.cp(locale_dir, join(build_dir, "chrome", "locale"), recursive=True, log=self.log.info) for dirpath, dnames, fnames in os.walk(build_dir): if ".svn" in dnames: sh.rm(join(dirpath, ".svn"), self.log) dnames.remove(".svn") for fname in [".consign", "Conscript"]: if fname in fnames: sh.rm(join(dirpath, fname), self.log) self._writefile(join(build_dir, "chrome.manifest"), "locale komodo-langpack en-US chrome/locale/en-US/") self._writefile( join(build_dir, "install.rdf"), _dedent("""\ <?xml version="1.0"?> <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Description about="urn:mozilla:install-manifest"> <em:name>Komodo Langpack</em:name> <em:description>Interface Langpack for Komodo</em:description> <em:version>%s</em:version> <em:id>[email protected]</em:id> <em:creator>ActiveState</em:creator> <em:type>2</em:type> <!-- Komodo IDE --> <em:targetApplication> <Description> <em:id>{36E66FA0-F259-11D9-850E-000D935D3368}</em:id> <em:minVersion>4.0</em:minVersion> <em:maxVersion>5.*</em:maxVersion> </Description> </em:targetApplication> <!-- Komodo Edit --> <em:targetApplication> <Description> <em:id>{b1042fb5-9e9c-11db-b107-000d935d3368}</em:id> <em:minVersion>4.0</em:minVersion> <em:maxVersion>5.*</em:maxVersion> </Description> </em:targetApplication> </Description> </RDF> """ % version)) # Package it up. if not exists(pkg_dir): os.makedirs(pkg_dir) pkg_name = ["Komodo", "LangPack", version] pkg_name = '-'.join(pkg_name) + ".xpi" pkg_path = join(pkg_dir, pkg_name) sh.run_in_dir('zip -rq "%s" .' % abspath(pkg_path), build_dir, self.log.info) self.log.info("created `%s'", pkg_path)
def make_pair(self, src, dst): sh.cp(src, dst, self.log)