def _copy_or_move(ctx, src, dst, function, function_name): """ Helper function to simplify copies and moves. """ src = Path(src) dst = Path(dst).addroot(ctx.buildroot) if not dst.exists(): # if dst ends with the separator, treat it like a directory if dst.endswith(os.sep): dst.makedirs() dst = dst / src.name else: dst.parent.makedirs() elif dst.isdir(): # If the dst is a directory, we're just copying that file into that # directory. dst = dst / src.name ctx.logger.check(' * %s' % function_name, '%s -> %s' % (src, dst), color='yellow') function(src, dst) return dst
def __init__(self, options): # Convert the paths to Path objects. options.buildroot = Path(options.buildroot) if options.state_file is None: options.state_file = STATE_FILE_DEFAULTS[options.database_engine] options.state_file = options.buildroot / options.state_file options.log_file = options.buildroot / options.log_file self.logger = fbuild.console.Log(verbose=options.verbose, nocolor=options.nocolor or options.no_color, threadcount=options.threadcount, show_threads=options.show_threads) self.db = fbuild.db.database.Database(self, engine=options.database_engine, explain=options.explain_database) self.scheduler = fbuild.sched.Scheduler(options.threadcount, logger=self.logger) self.options = options self.install_prefix = Path('/usr/local') self.to_install = [] self.tmpdir = self.buildroot / '.tmp' fbuild.temp.set_default_tempdir(self.tmpdir)
def __call__(self, src: fbuild.db.SRC, *, break_on_error=True, flags=[], buildroot=None, **kwargs) -> fbuild.db.DSTS: buildroot = buildroot or self.ctx.buildroot src = Path(src) cmd = [ sys.executable, self.exe.relpath(buildroot), '--cache-prefix=lpsrc-cache', '--trace=sources', '--trace=changes', '--nocache', ] if break_on_error: cmd.append('--break-on-error') cmd.extend(flags) cmd.append(src.relpath(buildroot)) stdout, stderr = self.ctx.execute( cmd, 'iscr extracting', src, color='green', cwd=buildroot, env={'PYTHONPATH': Path('buildsystem').relpath(buildroot)}, **kwargs) srcs = [] dsts = [] ipk_regex = re.compile('^CREATING .* NAMED FILE SOURCE (.*) \[.*\]') file_regex = re.compile('^File (.*) is (NEW|CHANGED|unchanged)') for line in io.StringIO(stdout.decode()): m = ipk_regex.match(line) if m: path = Path(m.group(1)) if not path.exists(): # The path may be relative to the .pak file. path = src.parent / path srcs.append(path) else: m = file_regex.match(line) if m: dsts.append(Path(m.group(1))) #self.ctx.db.add_external_dependencies_to_call(srcs=srcs) return dsts
def compile(self, src: fbuild.db.SRC, dst=None, *, flags=[], quieter=0, stdout_quieter=0, **kwargs) -> fbuild.db.DST: """Compile a c file and cache the results.""" src = Path(src) # Generate the dependencies while we compile the file. try: obj, stdout, stderr = self.compiler( src, dst, flags=list(chain(('/showIncludes', ), flags)), quieter=quieter, stdout_quieter=1 if stdout_quieter == 0 else stdout_quieter, **kwargs) except fbuild.ExecutionError as e: if quieter == 0 and stdout_quieter == 0: # We errored out, but we've hidden the stdout output. # Display the output while filtering out the dependeny # info. for line in io.StringIO(e.stdout.decode()): if not self._dep_regex.match(line) and \ line != src.name.splitext()[0] + '\r\n': self.ctx.logger.write(line) raise e # Parse the output and return the module dependencies. deps = [] for line in io.StringIO(stdout.decode()): m = self._dep_regex.match(line) if m: # The path sometimes is absolute, so try to convert it into a # relative path. try: deps.append(Path(m.group(1)).relpath()) except ValueError: # We couldn't find a relative path, so it must be from # outside our project. Lets just ignore that dependency # for now. pass elif quieter == 0 and stdout_quieter == 0: if line != src.name + '\r\n': self.ctx.logger.write(line) self.ctx.db.add_external_dependencies_to_call(srcs=deps) return obj
def build_flx_pkgconfig(host_phase, target_phase, flx_builder): return flx_builder.build_flx_pkgconfig_exe( dst=Path('host')/'bin'/'flx_pkgconfig', src=Path('src')/'tools'/'flx_pkgconfig.flx', includes=[ target_phase.ctx.buildroot / 'host'/'lib', target_phase.ctx.buildroot / 'share'/'lib', ], cxx_includes=[Path('src')/'flx_pkgconfig', target_phase.ctx.buildroot / 'share'/'lib'/'rtl', target_phase.ctx.buildroot / 'host'/'lib'/'rtl'], cxx_libs=[call('buildsystem.flx_rtl.build_runtime', host_phase, target_phase).static], )
def post_options(options, args): options.prefix = Path(options.prefix) options.bindir = Path(options.prefix / 'bin' if options.bindir is None else options.bindir) options.libdir = Path(options.prefix / 'lib' if options.libdir is None else options.libdir) if options.debug: options.buildroot = Path(options.buildroot, 'debug') else: options.buildroot = Path(options.buildroot, 'release') return options, args
def check_fluid(linker): fluidsynth = Path(linker.prefix + 'fluidsynth' + linker.suffix) fluidsynth = fluidsynth.addroot(Path('fluidsynth') / 'fluidsynth' / 'src') message = textwrap.dedent(''' You need to build Fluidsynth separately first! Try runnung 'cd fluidsynth/fluidsynth; cmake'. (See http://sourceforge.net/p/fluidsynth/wiki/BuildingWithCMake/ for info.) '''.rstrip().lstrip('\n')).replace('\n', ' ', 1) if not fluidsynth.exists(): raise fbuild.ConfigFailed(message) return fluidsynth
def build_runtime(host_phase, target_phase): path = Path('src/faio') buildsystem.copy_hpps_to_rtl( target_phase.ctx, path / 'faio_job.hpp', path / 'faio_timer.hpp', path / 'faio_posixio.hpp', path / 'faio_winio.hpp', ) dst = 'host/lib/rtl/faio' srcs = [ path / 'faio_job.cpp', path / 'faio_timer.cpp', ] includes = [ target_phase.ctx.buildroot / 'host/lib/rtl', Path('src', 'flx_async'), Path('src', 'pthread'), Path('src', 'demux'), Path('src', 'rtl'), Path('src', 'exceptions'), Path('src', 'gc'), path, ] macros = ['BUILD_FAIO'] libs = [ call('buildsystem.flx_pthread.build_runtime', target_phase), call('buildsystem.flx_async.build_runtime', host_phase, target_phase), call('buildsystem.demux.build_runtime', target_phase), ] if 'win32' in target_phase.platform: srcs.append(path / 'faio_winio.cpp') includes.append(Path('src', 'demux', 'win')) if 'posix' in target_phase.platform: srcs.append(path / 'faio_posixio.cpp') includes.append(Path('src', 'demux', 'posix')) return Record(static=buildsystem.build_cxx_static_lib( target_phase, dst, srcs, includes=includes, macros=macros, libs=[lib.static for lib in libs]), shared=buildsystem.build_cxx_shared_lib( target_phase, dst, srcs, includes=includes, macros=macros, libs=[lib.shared for lib in libs]))
def _link(self, linker, src, dst=None, *, includes=[], macros=[], cflags=[], libs=[], lflags=[], objects=[], buildroot=None): buildroot = buildroot or self.ctx.buildroot #print("_link: C++ compile src = " + src) if dst is None: dst = src.replaceext('') dst = Path(dst).addroot(buildroot) obj = self.cxx.compile(src, includes=includes, macros=macros, buildroot=buildroot, flags=cflags) thunk_obj = self.cxx.compile(src[:-4]+"_static_link_thunk.cpp", includes=includes, macros=macros, buildroot=buildroot, flags=cflags) return linker(dst, list(chain(objects, [obj,thunk_obj])), libs=libs, flags=lflags, buildroot=buildroot)
def build_flx(phase): path = Path('src/lib') dsts = [] dsts.extend( buildsystem.copy_flxs_to_lib(phase.ctx, (path / '*.flx{,h}').glob())) dsts.extend( buildsystem.copy_flxs_to_lib(phase.ctx, (path / '*.fdoc').glob())) dsts.extend( buildsystem.copy_flxs_to_libstd(phase.ctx, (path / 'std/*.flx{,h}').glob())) dsts.extend( buildsystem.copy_flxs_to_libstd(phase.ctx, (path / 'std/*.fdoc').glob())) dsts.extend( buildsystem.copy_flxs_to_libstd_posix( phase.ctx, (path / 'std/posix/*.flx{,h}').glob())) dsts.extend( buildsystem.copy_flxs_to_libstd_posix( phase.ctx, (path / 'std/posix/*.fdoc').glob())) dsts.extend( buildsystem.copy_flxs_to_libstl(phase.ctx, (path / 'stl/*.flx{,h}').glob())) dsts.extend( buildsystem.copy_flxs_to_libstl(phase.ctx, (path / 'stl/*.fdoc').glob())) return dsts
def build_libtsm(ctx, c, xkbcommon): base = Path('deps/libtsm') src = base / 'src' shl = src / 'shared' tsm = src / 'tsm' sources = Path.glob( tsm / '*.c') + [shl / 'shl-htable.c', base / 'external' / 'wcwidth.c'] includes = [shl, tsm, base] if xkbcommon is not None: cflags = xkbcommon.cflags else: cflags = [] macros = ['_GNU_SOURCE=1'] if not ctx.options.release: macros.append('BUILD_ENABLE_DEBUG') return Record(includes=includes, lib=c.build_lib('tsm', sources, includes=includes, macros=macros, cflags=cflags))
def __call__(self, src, *args, includes=[], debug=None, static=False, stdout=None, flags=[], cwd=None, **kwargs): cmd = [self.exe] if debug is None: debug = self.debug if debug: cmd.append('--debug') if static: cmd.append('--static') if stdout: cmd.append('--stdout=' + stdout) cmd.extend('-I' + i for i in sorted(includes) if Path(i).exists()) cmd.extend(self.flags) cmd.extend(flags) cmd.append(src) #print("src=" + str (src)) #with open(src,"r") as f: # text = f.read() # print(text) print("Flx.__call__.ctx.execute cmd=" + str(cmd)) return self.ctx.execute(cmd, *args, **kwargs)
def gen_fluid_fpc(ctx, cxx): all_flags = '-I%s ' % ctx.buildroot.abspath() all_libs = '-L%s ' % ctx.buildroot.abspath() for pkg in 'glib-2.0', 'gthread-2.0': cflags, libs = get_info_for(ctx, cxx, pkg, {}) all_flags += ' '.join(cflags) + ' ' all_libs += libs + ' ' all_libs += make_lib_args(cxx, 'fluidsynth') fluidsynth_root = Path('fluidsynth') / 'fluidsynth' fluidsynth_includes = ['include', 'src/midi', 'src/utils'] for include in fluidsynth_includes: all_flags += ' -I' + str(fluidsynth_root / include) def write(directory): template = textwrap.dedent(''' Name: fluid Description: Midifi fluidsynth stuff! cflags: {flags} provides_dlib: {libs} provides_slib: {libs} '''.lstrip('\n').rstrip(' ')) fpc = template.format(flags=all_flags, libs=all_libs) with open(directory / 'fluid.fpc', 'w') as f: f.write(fpc) write_fpc(ctx, 'fluid.fpc', write)
def install(self, path, category, addroot=''): try: self.to_install[category].append((Path(path).abspath(), addroot)) except AttributeError: pass except KeyError: raise fbuild.Error('invalid install category: {}'.format(category))
def build_runtime(phase): path = Path('src/exceptions') buildsystem.copy_hpps_to_rtl( phase.ctx, path / 'flx_exceptions.hpp', path / 'flx_eh.hpp', ) srcs = [ copy(ctx=phase.ctx, src=f, dst=phase.ctx.buildroot / f) for f in [ path / 'flx_exceptions.cpp', path / 'flx_eh.cpp', ] ] includes = [ 'src/rtl', phase.ctx.buildroot / 'host/lib/rtl', phase.ctx.buildroot / 'share/lib/rtl' ] macros = ['BUILD_FLX_EXCEPTIONS'] dst = 'host/lib/rtl/flx_exceptions' return Record(static=buildsystem.build_cxx_static_lib(phase, dst, srcs, includes=includes, macros=macros), shared=buildsystem.build_cxx_shared_lib(phase, dst, srcs, includes=includes, macros=macros))
def f(lib): if lib in new_libs: return if isinstance(lib, fbuild.builders.c.Library): for libpath in lib.libpaths: if libpath not in libpaths: libpaths.append(libpath) for l in lib.external_libs: if l not in external_libs: external_libs.append(l) # In order to make linux happy, we'll recursively walk the # dependencies first, then add the library. for l in lib.libs: f(l) parent, lib = Path(lib).split() if parent not in libpaths: libpaths.append(parent) lib = lib.name[len('lib'):] lib = lib.rsplit('.', 1)[0] if lib not in new_libs: new_libs.append(lib)
def build_flx(phase, flx_builder): print('[fbuild] [flx] building flx') #dlfcn_h = config_call('fbuild.config.c.posix.dlfcn_h', # phase.platform, # phase.cxx.static, # phase.cxx.shared) #if dlfcn_h.dlopen: # external_libs = dlfcn_h.external_libs # print("HAVE dlfcn.h, library=" + str (external_libs)) #else: # print("NO dlfcn.h available") # external_libs = [] external_libs = [] #print("[fbuild:flx.py:build_flx] ********** BUILDING FLX ***********************************************") return flx_builder.build_exe( aasync=False, dst=Path('host') / 'bin' / 'bootflx', src=phase.ctx.buildroot / 'share' / 'src' / 'tools' / 'bootflx.flx', includes=[ phase.ctx.buildroot / 'host' / 'lib', phase.ctx.buildroot / 'share' / 'lib', ], cxx_includes=[ phase.ctx.buildroot / 'share' / 'lib' / 'rtl', phase.ctx.buildroot / 'host' / 'lib' / 'rtl' ], cxx_libs=[ call('buildsystem.flx_rtl.build_runtime', phase).static, call('buildsystem.re2.build_runtime', phase).static, ] + external_libs, )
def __call__(self, src, *args, includes=[], debug=None, static=False, stdout=None, flags=[], cwd=None, **kwargs): cmd = [self.exe] if debug is None: debug = self.debug if debug: cmd.append('--debug') if static: cmd.append('--static') if stdout: cmd.append('--stdout=' + stdout) cmd.extend('-I' + i for i in sorted(includes) if Path(i).exists()) cmd.extend(self.flags) cmd.extend(flags) cmd.append(src) return self.ctx.execute(cmd, *args, **kwargs)
def uncached_compile(self, src, dst=None, *args, buildroot=None, pre_flags=(), **kwargs): """Compile an ocaml implementation or interface file without caching the results. This is needed when compiling temporary files.""" if src.endswith('.mli'): obj_suffix = '.cmi' else: obj_suffix = self.obj_suffix # Copy the source into the buildroot as if we generate a .ml, the .mli # needs to also be in the buildroot for ocaml to use the .cmi file. buildroot = buildroot or self.ctx.buildroot src_buildroot = src.addroot(buildroot) if src != src_buildroot: src_buildroot.parent.makedirs() src.copy(src_buildroot) src = src_buildroot dst = Path(dst or src).replaceext(obj_suffix) pre_flags = list(pre_flags) pre_flags.append('-c') return self._run(dst, [src], pre_flags=pre_flags, color='compile', buildroot=buildroot, *args, **kwargs)
def build_flx_pkgconfig(phase, flx_builder): #print('[fbuild] [flx] building flx_pkgconfig') #dlfcn_h = config_call('fbuild.config.c.posix.dlfcn_h', # phase.platform, # phase.cxx.static, # phase.cxx.shared) #if dlfcn_h.dlopen: # external_libs = dlfcn_h.external_libs #else: # external_libs = [] external_libs = [] return flx_builder.build_flx_pkgconfig_exe( dst=Path('host') / 'bin' / 'flx_pkgconfig', src=phase.ctx.buildroot / 'share' / 'src' / 'tools' / 'flx_pkgconfig.flx', includes=[ phase.ctx.buildroot / 'host' / 'lib', phase.ctx.buildroot / 'share' / 'lib', ], cxx_includes=[ phase.ctx.buildroot / 'share' / 'lib' / 'rtl', phase.ctx.buildroot / 'host' / 'lib' / 'rtl' ], cxx_libs=[call('buildsystem.flx_rtl.build_runtime', phase).static] + external_libs, )
def copy_dir_to(ctx, dstdir, srcdir, *, pattern=None) -> fbuild.db.DSTS: #print("Copy dir to: from srcdir = " + # str(srcdir) + ", pattern=" + str(pattern) + # ", to " + str(dstdir)) srcdir = Path(srcdir) srcs = [] dsts = [] for src in srcdir.find(pattern=pattern, include_dirs=False): dst = src.removeroot(srcdir + os.sep).addroot(dstdir) dst.parent.makedirs() srcs.append(src) dsts.append(dst) #ctx.logger.check(' * copy', '%s -> %s' % (src, dst), color='yellow') try: src.copy(dst) except shutil.SameFileError: pass ctx.db.add_external_dependencies_to_call(srcs=srcs) return dsts
def modules(self, src:fbuild.db.SRC, *, preprocessor=None, flags=()): """Calculate the modules this ocaml file depends on.""" src = Path(src) cmd = [self.exe] cmd.extend(self.pre_flags) cmd.append('-modules') if preprocessor is not None: cmd.extend(('-pp', preprocessor)) cmd.extend(self.flags) cmd.extend(flags) cmd.append(src) # Now, run ocamldep stdout, stderr = self.ctx.execute(cmd, str(self), src, color='yellow', stdout_quieter=1) # Parse the output and return the module dependencies. m = re.match(b'\S+:(?: (.*))?$', stdout.strip()) if not m: raise fbuild.ExecutionError('unable to understand %r' % stdout) s = m.group(1) if s is None: return () else: return tuple(s.decode().split())
def prune(self, prune_get_all, prune_get_bad): """Delete all destination files that were not referenced during this build. This will leave any files outside the build directory. To override this function's behavior, use prune_get_all and prune_get_bad.""" all_targets = set(prune_get_all(self)) bad_targets = prune_get_bad( self, all_targets - self.db.active_files - {self.options.state_file, self.options.log_file}) def error_handler(func, path, exc): self.logger.log('error deleting file %s: %s' % (path, exc[1]), color='red') for file in bad_targets: file = Path(file) # XXX: There should be a color better than 'compile' for this. self.logger.check(' * prune', file, color='compile') if file.isdir(): file.rmtree(ignore_errors=True, on_error=error_handler) else: try: file.remove() except: error_handler(None, file, sys.exc_info())
def _run(self, builder, src, dst=None, *, flags=[], quieter=0, stderr_quieter=0, buildroot=None, **kwargs): """Compile a java file.""" src = Path(src) dst = Path(dst or src.parent).addroot(buildroot or self.ctx.buildroot) # Extract the generated files when we compile the file. try: dst, stdout, stderr = builder( dst, [src], flags=list(chain(('-verbose', ), flags)), quieter=quieter, stderr_quieter=1 if stderr_quieter == 0 else stderr_quieter, **kwargs) except fbuild.ExecutionError as e: if quieter == 0 and stderr_quieter == 0: # We errored out, but we've hidden the stderr output. for line in io.StringIO(e.stderr.decode()): if not line.startswith('['): self.ctx.logger.write(line) raise e # Parse the output and find what files we generated. dsts = [] for line in io.StringIO(stderr.decode()): m = self._dep_regex.match(line) if m: dsts.append(Path(m.group(1))) elif quieter == 0 and stderr_quieter == 0: if not line.startswith('['): self.ctx.logger.write(line) # Log all the files we found self.ctx.logger.check(str(builder), '%s -> %s' % (src, ' '.join(dsts)), color='compile') return dsts
def __call__(self, dst, srcs, *args, buildroot=None, **kwargs): """Run a scala script.""" dst = Path(dst).addroot(buildroot or self.ctx.buildroot) dst.makedirs() stdout, stderr = self._run(srcs, *args, dst=dst, **kwargs) return dst, stdout, stderr
def __init__(self, ctx, exe=None): super().__init__(ctx) if exe is None: exe = call('fbuildroot.src_dir', ctx) / 'buildsystem/interscript/bin/iscr.py' self.exe = Path(exe)
def build_flx(host_phase, target_phase, flx_builder): return flx_builder.build_exe( async=False, dst=Path('host')/'bin'/'bootflx', src=Path('src')/'tools'/'bootflx.flx', includes=[ target_phase.ctx.buildroot / 'host'/'lib', target_phase.ctx.buildroot / 'share'/'lib', ], cxx_includes=[Path('src')/'flx', target_phase.ctx.buildroot / 'share'/'lib'/'rtl', target_phase.ctx.buildroot / 'host'/'lib'/'rtl'], cxx_libs=[ call('buildsystem.flx_rtl.build_runtime', host_phase, target_phase).static, call('buildsystem.re2.build_runtime', host_phase).static, ], )
def __call__(self, srcs, dst=None, *, pre_flags=[], flags=[], includes=[], macros=[], warnings=[], debug=None, optimize=None, **kwargs): new_includes = [] for include in chain(self.includes, includes, (s.parent for s in srcs)): if include not in new_includes: new_includes.append(include) includes = new_includes new_flags = [] for flag in chain(self.flags, flags): if flag not in new_flags: new_flags.append(flag) flags = new_flags macros = set(macros) macros.update(self.macros) warnings = set(warnings) warnings.update(self.warnings) # ---------------------------------------------------------------------- cmd = [self.exe, '/nologo'] cmd.extend(self.pre_flags) cmd.extend(pre_flags) if (debug is None and self.debug) or debug: cmd.extend(self.debug_flags) if (optimize is None and self.optimize) or optimize: cmd.extend(self.optimize_flags) # make sure that the path is converted into the native path format cmd.extend('/I' + Path(i) for i in sorted(includes) if i) cmd.extend('/D' + d for d in sorted(macros)) cmd.extend('/W' + w for w in sorted(warnings)) if dst is not None: cmd.append('/Fo' + dst) msg2 = '%s -> %s' % (' '.join(srcs), dst) else: msg2 = ' '.join(srcs) cmd.extend(flags) cmd.extend(srcs) return self.ctx.execute(cmd, msg2=msg2, **kwargs)
def copy_regex(ctx, *, srcdir, dstdir, src_pattern, dst_pattern, exclude_pattern=None, include_dirs=True) -> fbuild.db.DSTS: """ Recursively copies the files from the srcdir to the dstdir using the src_pattern and dst_pattern to choose and rename the files. >>> ctx = fbuild.context.make_default_context() >>> copy_regex(ctx, 'src', 'dst', r'(.*\.c)', r'foo-\1') """ srcdir = Path(srcdir) dstdir = Path(dstdir).addroot(ctx.buildroot) srcs = [] dsts = [] for src in srcdir.find(include_dirs=include_dirs): # Filter out any files we're ignoring. if exclude_pattern is not None and re.search(exclude_pattern, src): continue dst, nsub = re.subn(src_pattern, dst_pattern, src[len(srcdir + os.sep):]) if nsub > 0: dst = dstdir / Path(dst) ctx.logger.check(' * copy', '%s -> %s' % (src, dst), color='yellow') dst.parent.makedirs() src.copy(dst) srcs.append(src) dsts.append(dst) if srcs or dsts: ctx.db.add_external_dependencies_to_call(srcs=srcs, dsts=dsts) return dsts
def __call__(self, dst, srcs: fbuild.db.SRCS, *, libs: fbuild.db.SRCS = (), ldlibs=(), external_libs=(), flags=(), ranlib_flags=(), prefix=None, suffix=None, buildroot=None, **kwargs) -> fbuild.db.DST: buildroot = buildroot or self.ctx.buildroot #libs = set(libs) #libs.update(self.libs) #libs = sorted(libs) #assert srcs or libs, 'no sources passed into ar' assert srcs, 'no sources passed into ar' prefix = prefix or self.prefix suffix = suffix or self.suffix dst = Path(dst).addroot(buildroot) dst = dst.parent / prefix + dst.name + suffix dst.parent.makedirs() srcs = list(Path.globall(srcs)) cmd = [self.exe] cmd.extend(self.flags) cmd.extend(flags) cmd.append(dst) cmd.extend(srcs) #cmd.extend(libs) #cmd.extend(self.external_libs) #cmd.extend(external_libs) self.ctx.execute(cmd, msg1=str(self), msg2='%s -> %s' % (' '.join(srcs), dst), color='link', **kwargs) if self.ranlib is not None: cmd = [self.ranlib] cmd.extend(self.ranlib_flags) cmd.extend(ranlib_flags) cmd.append(dst) self.ctx.execute(cmd, msg1=self.ranlib.name, msg2=dst, color='link', **kwargs) return dst