From 49fc64618a726ee727c6ce898b72157601b2992b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 23 Dec 2019 11:24:28 +0530 Subject: [PATCH 1/3] fix: bench build "Cannot link {assets} to {site assets}" --- frappe/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/build.py b/frappe/build.py index 265a8c3976..456f02e971 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -118,6 +118,7 @@ def make_asset_dirs(make_copy=False, restore=False): else: shutil.rmtree(target) try: + os.unlink(target) os.symlink(source, target) except OSError: print('Cannot link {} to {}'.format(source, target)) From 658fcac45433ff7ab32f79eee8e7d0703e4b93d0 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 23 Dec 2019 13:15:36 +0530 Subject: [PATCH 2/3] fix: avoid race condition to create symlinks --- frappe/build.py | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/frappe/build.py b/frappe/build.py index 456f02e971..cd96634ca9 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,48 @@ 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)) + os.replace(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,8 +160,7 @@ def make_asset_dirs(make_copy=False, restore=False): else: shutil.rmtree(target) try: - os.unlink(target) - os.symlink(source, target) + symlink(source, target, overwrite=True) except OSError: print('Cannot link {} to {}'.format(source, target)) else: From 04bc21696668db4486f89fb513abd5dd596bfda1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 26 Dec 2019 09:36:27 +0530 Subject: [PATCH 3/3] fix: python 2 compatibility for symlink creation --- frappe/build.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/build.py b/frappe/build.py index cd96634ca9..f7437acf8f 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -47,7 +47,10 @@ def symlink(target, link_name, overwrite=False): # 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)) - os.replace(temp_link_name, 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)