Pathlib with_name does not rename file - python-3.x

File is not renamed by with_name(). A test file is created at Path p using touch() and updated using with_name().
1.) Is there an issue caused by usage of Path vs PurePath? (no)
2.) Is it necessary to call replace() using the updated path object to change the file name on disk? (yes)
from pathlib import Path
p = Path('./test.txt')
p.touch()
print(f'p before: {p}')
# ==> p before: test.txt
# p is not updated
p.with_name('test_new.txt')
print(f'p after: {p}')
# ==> p after: test.txt

Just changing the path using with_name() is insufficient to rename the corresponding file on the filesystem. I was able to rename the file by explicitly calling .replace() with the updated path object (see below).
p.replace(p.with_name('test_new.txt'))
Per MisterMiyagi's comment above:
From the docs: "PurePath.with_name(name) Return a new path with the
name changed. [...]". You have to assign the result of p.with_name(),
or it is lost.
### WORKS ###
from pathlib import Path
# sets original path
p = Path('./test.txt')
print(p)
# ==> test.txt
# create file on disk
p.touch()
# prints updated path, but does not update p (due to immutability?)
print(p.with_name('test_new.txt'))
# ==> test_new.txt
# p not updated
print(p)
# ==> test.txt
# assignment to q stores updated path
q = p.with_name('test_new.txt')
print(q)
# ==> test_new.txt
# file on disk is updated with new file name
p.replace(p.with_name('test_new.txt'))
# p.replace(q) # also works
print(p)
# ==> test_new.txt

path.with_name() will generate a new Path object.
path.rename() will generate a new Path object and call the new path object's touch method.
So the path.rename() will be the proper way.

Related

Move file from /tmp folder to Google Cloud Storage bucket

I originally posted this question when I was having trouble getting my python cloud function to create and write to a new file. Since then I've managed to create a csv in the /tmp directory but am struggling to find a way to move that file into my bucket's folder where the original csv was uploaded.
Is it possible to do this? I've looked through the Google Cloud Storage docs and tried using the blob.download_to_filename() and bucket.copy_blob() methods but am currently getting the error: FileNotFoundError: [Errno 2] No such file or directory: 'my-project.appspot.com/my-folder/my-converted-file.csv'
Appreciate any help or advice!
to move that file into my bucket
Here is an example. Bear in mind:
Don't copy and paste without thinking.
The code snippet is only to show the idea - it won't work as is. Modifications are required to fit into your context and requirements.
The _crc32sum function was not developed by me.
I did not test the code. It is just from my head with copying some elements from different public sources.
Here is the code:
import base64
import crc32c
import os
from google.cloud import exceptions
from google.cloud import storage
# =====> ==============================
# a function to calculate crc32c hash
def _crc32sum(filename: str, blocksize: int = 65536) -> int:
"""Calculate the crc32c hash for a file with the provided name
:param filename: the name of the file
:param blocksize: the size of the block for the file reading
:return: the calculated crc32c hash for the given file
"""
checksum = 0
with open(filename, "rb") as f_ref:
for block in iter(lambda: f_ref.read(blocksize), b""):
checksum = crc32c.crc32(block, checksum)
return checksum & 0xffffffff
# =====> ==============================
# use the default project in the client initialisation
CS = storage.Client()
lcl_file_name = "/tmp/my-local-file.csv"
tgt_bucket_name = "my-bucket-name"
tgt_object_name = "prefix/another-prefix/my-target-file.csv"
# =====> ==============================
# =====> ==============================
# =====> the process strats here
# https://googleapis.dev/python/storage/latest/_modules/google/cloud/storage/client.html#Client.lookup_bucket
gcs_tgt_bucket_ref = CS.lookup_bucket(tgt_bucket_name)
# check if the target bucket does exist
if gcs_tgt_bucket_ref is None:
# handle incorrect bucket name or its absence
# most likely we are to finish the execution here rather than 'pass'
pass
# calculate the hash for the local file
lcl_crc32c = _crc32sum(lcl_file_name)
base64_crc32c = base64.b64encode(lcl_crc32c.to_bytes(
length=4, byteorder='big')).decode('utf-8')
# check if the file/object in the bucket already exists
# https://googleapis.dev/python/storage/latest/_modules/google/cloud/storage/bucket.html#Bucket.blob
gcs_file_ref = gcs_tgt_bucket_ref.blob(tgt_object_name)
# https://googleapis.dev/python/storage/latest/_modules/google/cloud/storage/blob.html#Blob.exists
if gcs_file_ref.exists():
gcs_file_ref.reload()
# compare crc32c hashes - between the local file and the gcs file/object
if base64_crc32c != gcs_file_ref.crc32c:
# the blob file/object in the GCS has a different hash
# the blob file/object should be deleted and a new one to be uploaded
# https://googleapis.dev/python/storage/latest/_modules/google/cloud/storage/blob.html#Blob.delete
gcs_file_ref.delete()
else:
# the file/object is already in the bucket
# most likely we are to finish the execution here rather than 'pass'
pass
# upload file to the target bucket
# reinit the reference in case the target file/object was deleted
gcs_file_ref = gcs_tgt_bucket_ref.blob(tgt_file_name)
gcs_file_ref.crc32c = base64_crc32c
with open(lcl_file_name, 'rb') as file_obj:
try:
gcs_file_ref.metadata = {
"custom-metadata-key": "custom-metadata-value"
}
# https://googleapis.dev/python/storage/latest/_modules/google/cloud/storage/blob.html#Blob.upload_from_file
gcs_file_ref.upload_from_file(
file_obj=file_obj, content_type="text/csv", checksum="crc32c")
except exceptions.GoogleCloudError as gc_err:
# handle the exception here
# don't forget to delete the local file if it is not required anymore
# most likely we are to finish the execution here rather than 'pass'
pass
# clean behind
if lcl_file_name and os.path.exists(lcl_file_name):
os.remove(lcl_file_name)
# =====> the process ends here
# =====> ==============================
Let me know if there are significant mistakes, and I modify the example.

Generate sphinx autodoc for files that contain encoded byte strings

I have several files that contain endcoded byte strings for example:
variable_name = (b'encoded string')
however when I run make html to generate the auto documentation through Sphinx i receive the following error.
Encoding error:
'utf-8' codec can't decode byte 0xc0 in position 3: invalid start byte
I still need sphinx to generate documentation for these files and for the rest of the files within my project instead of failing, or worst case scenario still generate documentation for every other file that does not contain these byte strings and just skip over the ones that do. Is there a configuration variable I can add to my conf.py file to get it to recognize the byte string or just overlook it all together, whether that means just not documenting that variable or skipping that entire file then continue on to document the remaining files?
Steps to reproduce:
create sphinx project with autodoc
add "vari = (b'\n$\x00\xc0\x91 \xa0l')" to any .py file
generate documentation with "make html" - it fails with the above error
Any help is greatly appreciated! Thank you in advance!
# Sphinx version: 2.1.2
# Python version: 3.5.2 (CPython)
# Docutils version: 0.14
# Jinja2 version: 2.10.1
# Last messages:
# Loaded extensions:
Traceback (most recent call last):
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/sphinx/cmd/build.py", line 283, in build_main
args.tags, args.verbosity, args.jobs, args.keep_going)
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/sphinx/application.py", line 268, in __init__
self._init_builder()
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/sphinx/application.py", line 329, in _init_builder
self.events.emit('builder-inited')
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/sphinx/events.py", line 103, in emit
results.append(callback(self.app, *args))
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/extension.py", line 97, in run_autoapi
patterns=file_patterns, dirs=normalized_dirs, ignore=ignore_patterns
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/mapper.py", line 213, in load
data = self.read_file(path=path)
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/mapper.py", line 224, in read_file
parsed_data = Parser().parse_file(path)
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/parser.py", line 46, in parse_file
return self.parse(node)
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/parser.py", line 240, in parse
data = parse_func(node)
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/parser.py", line 227, in parse_module
child_data = self.parse(child)
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/parser.py", line 240, in parse
data = parse_func(node)
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/parser.py", line 72, in parse_assign
value = self._encode(assign_value[1])
File "/home/joshsullivan/.py_virtualenvs/verb_py3/lib/python3.5/site-packages/autoapi/mappers/python/parser.py", line 25, in _encode
return _TEXT_TYPE(to_encode, self._encoding)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc0 in position 3: invalid start byte
my_file.py
'''This is my docstring'''
vari = (b'\n$\x00\xc0\x91 \xa0l')
my Conf.py file:
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'vstars'
copyright = '2019, S.P.A.D.E'
author = 'S.P.A.D.E'
# The short X.Y version
version = '1.0'
# The full version, including alpha/beta/rc tags
release = '1.0.1'
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '2.0.1'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
#html_theme = 'alabaster'
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'vstarsdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'vstars.tex', 'vstars\\_sphinx\\_documentation Documentation',
'Joshua Sullivan', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'vstars', 'vstars Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'vstars', 'vstars Documentation',
author, 'vstars', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Extension configuration -------------------------------------------------
extensions = ['autoapi.extension']
# Document Python Code
autoapi_type = 'python'
autoapi_dirs = ['../']
my Index.rst file:
.. vstars documentation master file, created by
sphinx-quickstart on Wed Jul 3 10:03:20 2019.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to vstars's documentation!
==================================
.. toctree::
:maxdepth: 2
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
my Makefile:
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Generate __init__.py files for all subdirectories of vstars
init:
find ../ -type d -not -path "../doc*" -exec touch {}/__init__.py \;
# Put it first so that "make" without argument is like "make help".
help:
#$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
#$(SPHINXBUILD) -M $# "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
folder structure:
vstars
my_file.py
doc
Conf.py
Index.rst
Makefile
Cause
The UnicodeDecodeError stems from the Sphinx AutoAPI extension (https://sphinx-autoapi.readthedocs.io). The last item in the stack trace refers to line 25 in autoapi/mappers/python/parser.py.
_TEXT_TYPE is an alias for str, so _TEXT_TYPE(to_encode, self._encoding) means str(b'\n$\x00\xc0\x91 \xa0l', "UTF-8") at runtime, equivalent to b'\n$\x00\xc0\x91 \xa0l'.decode("UTF-8").
It looks like a bug. The problem does not occur with "vanilla" Sphinx.
Workaround
Add the following monkey patch to conf.py:
from autoapi.mappers.python.parser import Parser
def patched_encode(self, to_encode):
if self._encoding:
try:
if not(isinstance(bytes, to_encode)): # <- The patch
return _TEXT_TYPE(to_encode, self._encoding)
except TypeError:
# The string was already in the correct format
pass
return to_encode
Parser._encode = patched_encode

sorting error :"already exist"

This code was suppose tu sort my desktop , path =/Users/nicolas/Desktop/prova/
and it works properly if the destination folder doesnt exist and the program create it ,
else if there is already a folder with the same name it give an error when he tries to move the files and it says
complete output:
.DS_Store
nltks
png
Scherm.png
Schermata 2018-03-28 alle 11.07.13.png
DS_Store
nltks
png
png
Traceback (most recent call last):
File "/Users/nicolas/Desktop/tts/work in progress/sorting machine.py", line 16, in
shutil.move(path+names[x],path+currentexp)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 292, in move
raise Error, "Destination path '%s' already exists" % real_dst
shutil.Error: Destination path '/Users/nicolas/Desktop/prova/png/Scherm.png' already exists
iMac:w
but i ve not any file in it with the ds_store estension and the "nltk" is just a folder that shouldnt move .
program
import os
import shutil
path = "/Users/nicolas/Desktop/prova/"
names = os.listdir(path)
for x in range (0,len(names)):
print names[x]
for x in range (0,len(names)):
exp = names[x].split(".")
currentexp = exp[-1]
print (currentexp)
if os.path.exists(path+currentexp):
shutil.move(os.path.join(path, names[x]), os.path.join(path,currentexp))
else:
os.makedirs(path+currentexp)
shutil.move(os.path.join(path, names[x]), os.path.join(path,currentexp))
# if names[x] not in os.path.exists(path+currentexp):
# shutil.move(path+names[x],path+curentexp)
thanks for the help
from the documentation:
If the destination is an existing directory, then src is moved inside that directory. If the destination already exists but is not a directory, it may be overwritten depending on os.rename() semantics
To sum it up, if the image exists in the target dir, and both source & target dirs are on the same filesystem, shutil.move can use os.rename, and os.rename fails because you cannot rename an object with the name of an already existing one. You have to delete the target first.
Here's how I'd rewrite it:
target_dir = os.path.join(path,currentexp)
if not os.path.exists(target_dir):
os.makedirs(target_dir)
try:
os.remove(os.path.join(target_dir, names[x]))
except OSError:
pass # cannot remove or doesn't exist, ignore
shutil.move(os.path.join(path, names[x]), target_dir)
this simpler code tries to delete the target file before performing shutil.move. Of course os.remove can fail, failure is trapped, then shutil.move fails because of another error, but that's beyond our scope

How to get the default application mapped to a file extention in windows using Python

I would like to query Windows using a file extension as a parameter (e.g. ".jpg") and be returned the path of whatever app windows has configured as the default application for this file type.
Ideally the solution would look something like this:
from stackoverflow import get_default_windows_app
default_app = get_default_windows_app(".jpg")
print(default_app)
"c:\path\to\default\application\application.exe"
I have been investigating the winreg builtin library which holds the registry infomation for windows but I'm having trouble understanding its structure and the documentation is quite complex.
I'm running Windows 10 and Python 3.6.
Does anyone have any ideas to help?
The registry isn't a simple well-structured database. The Windows
shell executor has some pretty complex logic to it. But for the simple cases, this should do the trick:
import shlex
import winreg
def get_default_windows_app(suffix):
class_root = winreg.QueryValue(winreg.HKEY_CLASSES_ROOT, suffix)
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r'{}\shell\open\command'.format(class_root)) as key:
command = winreg.QueryValueEx(key, '')[0]
return shlex.split(command)[0]
>>> get_default_windows_app('.pptx')
'C:\\Program Files\\Microsoft Office 15\\Root\\Office15\\POWERPNT.EXE'
Though some error handling should definitely be added too.
Added some improvements to the nice code by Hetzroni, in order to handle more cases:
import os
import shlex
import winreg
def get_default_windows_app(ext):
try: # UserChoice\ProgId lookup initial
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{}\UserChoice'.format(ext)) as key:
progid = winreg.QueryValueEx(key, 'ProgId')[0]
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'SOFTWARE\Classes\{}\shell\open\command'.format(progid)) as key:
path = winreg.QueryValueEx(key, '')[0]
except: # UserChoice\ProgId not found
try:
class_root = winreg.QueryValue(winreg.HKEY_CLASSES_ROOT, ext)
if not class_root: # No reference from ext
class_root = ext # Try direct lookup from ext
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r'{}\shell\open\command'.format(class_root)) as key:
path = winreg.QueryValueEx(key, '')[0]
except: # Ext not found
path = None
# Path clean up, if any
if path: # Path found
path = os.path.expandvars(path) # Expand env vars, e.g. %SystemRoot% for ext .txt
path = shlex.split(path, posix=False)[0] # posix False for Windows operation
path = path.strip('"') # Strip quotes
# Return
return path

How to create a symbolic link with SCons?

I'm using SCons for building a project and need to add a symbolic link to a file it is installing via env.Install. What command(s) will make a link that's the equivalent of running ln -s on the command line?
SCons doesn't have a dedicated symbolic link command, but you can use os.symlink(src, dst) from Python's os module:
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)
This may not work correctly on Windows, I've only tried it on Linux.
There seems to be little advancement in the SCons core code for symbolic link support and I wasn't satisfied any one solution I found on the web. Here is a potential builder which incorporates aspects of both Nick's and richq's answers. Additionally, it will catch name changes (due to the emitter method) and is as platform-agnostic as I could get it.
I prefer this builder because it will make links relative to the directory in which they are installed. One could add an option to force the link to be absolute I suppose, but I have not needed or wanted that yet.
Currently, if the OS doesn't support symlinks, I just pass and do nothing, but one could use os.copytree() for example however the dependency becomes messy if the source is a directory so the emitter would need to do something fancy. I'm up for any suggestions here.
One can put the following code into the file site_scons/site_tools/symlink.py (with blank _init_.py files in the appropriate places). Then do this in the SConstruct file:
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
This creates a builder to perform the job:
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)
Again, this solution is for Linux.
If you wanted to issue the command directly to the shell and know the OS, subprocess can be used as well.
E.g.: subprocess.call(['ln', '-s', '</src/path>', '</dest/path>'])
In addition to Nicks solution, you can add a directory symlink by using a file as a directory name carrier. It's not the cleanest solution and debugging path names is a pain, but this works well:
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)
This will make a link to deliverables/subdir named deliverables/last, provided that a file deliverables/subdir/filename exists.

Resources