如何使用SCons创建符号链接?

13

我正在使用SCons构建项目,需要为通过env.Install安装的文件添加符号链接。哪些命令可以创建等同于在命令行中运行ln -s的链接?

5个回答

11

SCons没有专门的符号链接命令,但是你可以使用Python的os模块中的os.symlink(src, dst)命令:

import os
env = Environment()
def SymLink(target, source, env):
    os.symlink(os.path.abspath(str(source[0])), os.path.abspath(str(target[0])))
env.Command("file.out", "file.in", SymLink)

这可能在Windows上无法正常工作,我只在Linux上尝试过。


由于某种原因,在子目录中尝试创建符号链接时,例如“env.Command(flavour +'/Resources','src/Resources',SymLink)”,其中flavour为“debug”或“release”,无法正常工作。 - Septagram
它不应该在Windows上工作,因为这个操作系统不支持链接。 - AlexDarkVoid

8

在SCons核心代码中,符号链接支持似乎没有什么进展,我也不满意网上找到的任何一个解决方案。这里是一个潜在的构建器,结合了Nick和richq的回答。此外,它将捕获名称更改(由于emitter方法),并且尽可能与平台无关。

我喜欢这个构建器,因为它会使链接相对于它们所安装的目录。我想可以添加一个选项来强制链接是绝对路径,但我还没有需要或想要那样做。

目前,如果操作系统不支持符号链接,则只需跳过并不执行任何操作,但例如可以使用os.copytree(),但如果源是目录,则依赖关系会变得混乱,因此emitter需要做一些花哨的事情。我可以接受任何建议。

可以将以下代码放入文件site_scons/site_tools/symlink.py(在适当的位置使用空白的_init_.py文件)。然后在SConstruct文件中执行以下操作:

SConstruct:

env = Environment()
env.Tool('symlink')
env.SymLink('link_name.txt', 'real_file.txt')

symlink.py:

import os
from os import path

from SCons.Node import FS
from SCons.Script import Action, Builder

def generate(env):
    '''
    SymLink(link_name,source)
    env.SymLink(link_name,source)

    Makes a symbolic link named "link_name" that points to the
    real file or directory "source". The link produced is always
    relative.
    '''
    bldr = Builder(action = Action(symlink_builder,symlink_print),
        target_factory = FS.File,
        source_factory = FS.Entry,
        single_target = True,
        single_source = True,
        emitter = symlink_emitter)
    env.Append(BUILDERS = {'SymLink' : bldr})

def exists(env):
    '''
    we could test if the OS supports symlinks here, or we could
    use copytree as an alternative in the builder.
    '''
    return True

def symlink_print(target, source, env):
    lnk = path.basename(target[0].abspath)
    src = path.basename(source[0].abspath)
    return 'Link: '+lnk+' points to '+src

def symlink_emitter(target, source, env):
    '''
    This emitter removes the link if the source file name has changed
    since scons does not seem to catch this case.
    '''
    lnk = target[0].abspath
    src = source[0].abspath
    lnkdir,lnkname = path.split(lnk)
    srcrel = path.relpath(src,lnkdir)

    if int(env.get('verbose',0)) > 3:
        ldir = path.relpath(lnkdir,env.Dir('#').abspath)
        if rellnkdir[:2] == '..':
            ldir = path.abspath(ldir)
        print '  symbolic link in directory: %s' % ldir
        print '      %s -> %s' % (lnkname,srcrel)

    try:
        if path.exists(lnk):
            if os.readlink(lnk) != srcrel:
                os.remove(lnk)
    except AttributeError:
        # no symlink available, so we remove the whole tree? (or pass)
        #os.rmtree(lnk)
        print 'no os.symlink capability on this system?'

    return (target, source)

def symlink_builder(target, source, env):
    lnk = target[0].abspath
    src = source[0].abspath
    lnkdir,lnkname = path.split(lnk)
    srcrel = path.relpath(src,lnkdir)

    if int(env.get('verbose',0)) > 4:
        print 'target:', target
        print 'source:', source
        print 'lnk:', lnk
        print 'src:', src
        print 'lnkdir,lnkname:', lnkdir, lnkname
        print 'srcrel:', srcrel

    if int(env.get('verbose',0)) > 4:
        print 'in directory: %s' % path.relpath(lnkdir,env.Dir('#').abspath)
        print '    symlink: %s -> %s' % (lnkname,srcrel)

    try:
        os.symlink(srcrel,lnk)
    except AttributeError:
        # no symlink available, so we make a (deep) copy? (or pass)
        #os.copytree(srcrel,lnk)
        print 'no os.symlink capability on this system?'

    return None

你是否已经为硬链接编写了一个变体?我想要与内置的“Install”相同的接口,其中第一个参数是目录。 - Nordlöw

3
这将创建一个构建器来执行此工作:
mylib = env.SharedLibrary("foobar", SRCS)

builder = Builder(action = "ln -s ${SOURCE.file} ${TARGET.file}", chdir = True)

env.Append(BUILDERS = {"Symlink" : builder})

mylib_link = env.Symlink("_foobar.so", mylib)

env.Default(mylib)
env.Default(mylib_link)

再次强调,这个解决方案适用于Linux。


1
很不幸,它在目录上表现得很差(根本就不行):“TypeError: Directory /home/septi/Dropbox/Code/StreetCleaner/src/Resources found where file expected.:" - Septagram
@Septagram:如何解决目录问题? - Nordlöw
@Nordlöw,抱歉,已经有一段时间了,我不知道 :( 请尝试其他答案,并在找到答案时进行评论。 - Septagram
看起来SCons不支持目标是一个目录。然而,包装函数可以解决这个问题。 - Nordlöw
可能将target_factory = Dir作为您的构建器参数使用会起作用:builder = Builder(action = ...,target_factory = Dir)有关更多信息,请参阅target_factory下的man页面。 - GaryO
@Septagram 我找到了一个解决方案... 好吧,是一个变通方法,请看我的回答。 - Sander

0
如果您想直接向shell发出命令并了解操作系统,也可以使用subprocess
例如:subprocess.call(['ln', '-s', '</src/path>', '</dest/path>'])

0
除了Nicks的解决方案之外,您还可以通过使用文件作为目录名称载体来添加目录符号链接。这不是最干净的解决方案,调试路径名也很麻烦,但这个方法效果很好:
def symlink_last(target_source_env):
    src = os.path.basename(os.path.dirname(str(source[0])))
    link = "deliverables/last"
    print "Symlinking "+ src + "as" + link
    os.symlink(src, link)

BUILD_TARGETS.append('link')
install_dir = "deliverables/subdir"
carrier_file = "filename"
builder = Builder(action = symlink_last, chdir=False)
env.Append(BUILDERS={ "Symlink" : builder }) 
env.Alias(target="link", source=env.Symlink(dir="deliverables", source = install_dir + carrier_file)

这将创建一个链接到deliverables/subdir的名为deliverables/last的文件,前提是存在一个deliverables/subdir/filename文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接