blob: f248abcbb53225dd0b4b73e696649b9047345ec8 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2019 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A portable interface for symlinking."""
import argparse
import logging
import os
import shutil
import sys
import _env # pylint: disable=relative-import,unused-import
from starboard.tools import util
def IsWindows():
return sys.platform in ['win32', 'cygwin']
def ToLongPath(path):
"""Converts to a path that supports long filenames."""
if IsWindows():
# pylint: disable=g-import-not-at-top
from starboard.tools import win_symlink
return win_symlink.ToDevicePath(path)
else:
return path
def IsSymLink(path):
"""Platform neutral version os os.path.islink()."""
if IsWindows():
# pylint: disable=g-import-not-at-top
from starboard.tools import win_symlink
return win_symlink.IsReparsePoint(path)
else:
return os.path.islink(path)
def MakeSymLink(target_path, link_path):
"""Makes a symlink.
Args:
target_path: target path to be linked to
link_path: path to place the link
Returns:
None
"""
if IsWindows():
# pylint: disable=g-import-not-at-top
from starboard.tools import win_symlink
win_symlink.CreateReparsePoint(target_path, link_path)
else:
util.MakeDirs(os.path.dirname(link_path))
os.symlink(target_path, link_path)
def ReadSymLink(link_path):
"""Returns the path (abs. or rel.) to the folder referred to by link_path."""
if IsWindows():
# pylint: disable=g-import-not-at-top
from starboard.tools import win_symlink
path = win_symlink.ReadReparsePoint(link_path)
else:
try:
path = os.readlink(link_path)
except OSError:
path = None
return path
def DelSymLink(link_path):
if IsWindows():
# pylint: disable=g-import-not-at-top
from starboard.tools import win_symlink
win_symlink.UnlinkReparsePoint(link_path)
else:
os.unlink(link_path)
def Rmtree(path):
"""See Rmtree() for documentation of this function."""
if IsWindows():
# pylint: disable=g-import-not-at-top
from starboard.tools import win_symlink
func = win_symlink.RmtreeShallow
elif not os.path.islink(path):
func = shutil.rmtree
else:
os.unlink(path)
return
if os.path.exists(path):
func(path)
def OsWalk(root_dir, topdown=True, onerror=None, followlinks=False):
if IsWindows():
# pylint: disable=g-import-not-at-top
from starboard.tools import win_symlink
return win_symlink.OsWalk(root_dir, topdown, onerror, followlinks)
else:
return os.walk(root_dir, topdown, onerror, followlinks)
def _CreateArgumentParser():
"""Creates an argument parser for port_symlink."""
class MyParser(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(2)
help_msg = ('Example 1:\n'
' python port_link.py --link "target_path" "link_path"\n\n'
'Example 2:\n'
' python port_link.py --link "../target_path" "link_path"\n\n')
# Enables new lines in the description and epilog.
formatter_class = argparse.RawDescriptionHelpFormatter
parser = MyParser(epilog=help_msg, formatter_class=formatter_class)
parser.add_argument(
'--link',
help='The target path and link path to be used when '
'creating the symbolic link.',
metavar='"path"',
nargs=2)
parser.add_argument(
'-f',
'--force',
action='store_true',
help='Force the symbolic link to be created, removing existing files and '
'directories if needed.')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'-a',
'--use_absolute_path',
action='store_true',
help='Generated symlink is stored as an absolute path.')
group.add_argument(
'-r',
'--use_relative_path',
action='store_true',
help='Generated symlink is stored as a relative path.')
return parser
def main():
util.SetupDefaultLoggingConfig()
parser = _CreateArgumentParser()
args = parser.parse_args()
target_path, link_path = args.link
if args.force:
Rmtree(link_path)
if args.use_absolute_path:
target_path = os.path.abspath(target_path)
elif args.use_relative_path:
# os.path.relpath() requires its second parameter be a directory. If our
# target is a file, i.e. the link we will be creating should be to a file,
# we need to calculate the relative path between our target and the
# directory that our link will reside in, not the full path to the link
# itself. If we do not, our relative path will ascend one level too far.
# This is visible in the following examples.
#
# os.path.relpath(
# '/target/file.txt', '/link/file.txt') = '../../target/file.txt' [bad]
#
# os.path.relpath(
# '/target/file.txt', '/link') = '../target/file.txt' [good]
if os.path.isfile(target_path):
relative_link_path = os.path.dirname(link_path)
else:
relative_link_path = link_path
target_path = os.path.relpath(target_path, relative_link_path)
MakeSymLink(target_path=target_path, link_path=link_path)
if __name__ == '__main__':
main()