diff --git a/frappe/build.py b/frappe/build.py index 265a8c3976..f7437acf8f 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -2,7 +2,7 @@ # MIT License. See license.txt from __future__ import unicode_literals, print_function -import os, frappe, json, shutil, re, warnings +import os, frappe, json, shutil, re, warnings, tempfile from os.path import exists as path_exists, join as join_path, abspath, isdir from distutils.spawn import find_executable from six import iteritems, text_type @@ -12,6 +12,51 @@ from frappe.utils.minify import JavascriptMinify Build the `public` folders and setup languages """ + +def symlink(target, link_name, overwrite=False): + ''' + Create a symbolic link named link_name pointing to target. + If link_name exists then FileExistsError is raised, unless overwrite=True. + When trying to overwrite a directory, IsADirectoryError is raised. + + Source: https://stackoverflow.com/a/55742015/10309266 + ''' + + if not overwrite: + os.symlink(target, linkname) + return + + # os.replace() may fail if files are on different filesystems + link_dir = os.path.dirname(link_name) + + # Create link to target with temporary filename + while True: + temp_link_name = tempfile.mktemp(dir=link_dir) + + # os.* functions mimic as closely as possible system functions + # The POSIX symlink() returns EEXIST if link_name already exists + # https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html + try: + os.symlink(target, temp_link_name) + break + except FileExistsError: + pass + + # Replace link_name with temp_link_name + try: + # Pre-empt os.replace on a directory with a nicer message + if os.path.isdir(link_name): + raise IsADirectoryError("Cannot symlink over existing directory: '{}'".format(link_name)) + try: + os.replace(temp_link_name, link_name) + except AttributeError: + os.renames(temp_link_name, link_name) + except: + if os.path.islink(temp_link_name): + os.remove(temp_link_name) + raise + + app_paths = None def setup(): global app_paths @@ -118,7 +163,7 @@ def make_asset_dirs(make_copy=False, restore=False): else: shutil.rmtree(target) try: - os.symlink(source, target) + symlink(source, target, overwrite=True) except OSError: print('Cannot link {} to {}'.format(source, target)) else: