Как создать символическую ссылку со SCons?

Я использую SCons для создания проекта и вам нужно добавить символическую ссылку на файл, который он устанавливает через env.Install. Какая команда сделает ссылку, эквивалентную запуску ln -s в командной строке?

Ответ 1

SCons не имеет выделенной команды символьной ссылки, но вы можете использовать os.symlink(src, dst) из модуля Python os:

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.

Ответ 2

Похоже, что в коде ядра SCons нет ничего лучше для поддержки символических ссылок, и я не был удовлетворен каким-либо одним решением, которое нашел в сети. Вот потенциальный строитель, который включает в себя аспекты ответов Ника и Ричка. Кроме того, он будет перехватывать изменения имени (из-за метода emitter) и настолько независим от платформы, насколько я мог себе представить.

Я предпочитаю этот конструктор, потому что он будет создавать ссылки относительно каталога, в котором они установлены. Можно предположить, что можно добавить опцию, чтобы ссылка была абсолютной, но я пока не нуждался или не хотел этого.

В настоящее время, если ОС не поддерживает символические ссылки, я просто пропускаю и ничего не делаю, но можно, например, использовать os.copytree(), однако зависимость становится беспорядочной, если источником является каталог, поэтому эмитент должен будет сделать что-то необычное. Я здесь для любых предложений.

Следующий код можно поместить в файл 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

Ответ 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.

Ответ 4

Если вы хотите ввести команду непосредственно в оболочку и знать ОС, можно также использовать subprocess.

Например: subprocess.call(['ln', '-s', '</src/path>', '</dest/path>'])