diff --git a/build_scripts/file_utilities.py b/build_scripts/file_utilities.py index ba1a66ff9..4b882435d 100644 --- a/build_scripts/file_utilities.py +++ b/build_scripts/file_utilities.py @@ -1,25 +1,31 @@ -#Copyright(c) 2016-2024 Panos Karabelas +# Copyright(c) 2016-2025 Panos Karabelas # -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -#copies of the Software, and to permit persons to whom the Software is furnished -#to do so, subject to the following conditions : +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is furnished +# to do so, subject to the following conditions: # -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import sys -import subprocess +import hashlib import importlib +import os +import requests +import shutil +import stat +import subprocess +import sys +from pathlib import Path def install_and_import(package): try: @@ -33,11 +39,6 @@ def install_and_import(package): install_and_import('tqdm') install_and_import('requests') -import os -import hashlib -from tqdm import tqdm -import requests - def calculate_file_hash(file_path): hash_func = hashlib.new("sha256") with open(file_path, "rb") as f: @@ -56,6 +57,7 @@ def download_file(url, destination, expected_hash): response = requests.get(url, stream=True) total_size = int(response.headers.get('content-length', 0)) block_size = 1024 + from tqdm import tqdm t = tqdm(total=total_size, unit='iB', unit_scale=True) with open(destination, 'wb') as f: @@ -73,27 +75,53 @@ def download_file(url, destination, expected_hash): return def extract_archive(archive_path, destination_path, is_windows, use_working_dir=False): - # Determine the path to 7z based on the use_working_dir flag - if use_working_dir: - seven_zip_exe = '7z.exe' if is_windows else '7za' - else: - exe_dir = os.path.join(os.getcwd(), 'build_scripts') - seven_zip_exe = os.path.join(exe_dir, '7z.exe' if is_windows else '7za') + # determine the path to 7z based on the use_working_dir flag + seven_zip_exe = Path("build_scripts") / ("7z.exe" if is_windows else "7za") + seven_zip_exe = seven_zip_exe.resolve() if use_working_dir else seven_zip_exe + + # convert paths to string for subprocess, ensuring they are quoted if they contain spaces + archive_path_str = f'"{Path(archive_path).resolve()}"' + destination_path_str = f'"{Path(destination_path).resolve()}"' - # Construct the command - cmd = f"{seven_zip_exe} x {archive_path} -o{destination_path} -aoa" + # construct the command as a string with quoted paths + cmd = f'{str(seven_zip_exe)} x {archive_path_str} -o{destination_path_str} -aoa' print(f"Extracting {archive_path} to {destination_path} using: {seven_zip_exe}") try: - # Execute the command - result = subprocess.run(cmd, check=True, shell=True, text=True, capture_output=True) + # execute the command with shell=True to handle paths with spaces + result = subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True) print(result.stdout) except subprocess.CalledProcessError as e: print(f"An error occurred while extracting: {e}") print(f"Error output: {e.stderr}") - raise # Re-raise the exception for higher-level error handling if needed - + raise # re-raise the exception for higher-level error handling if needed except FileNotFoundError: print(f"The 7z executable was not found at {seven_zip_exe}. Please check the path or installation.") - raise \ No newline at end of file + raise + +def copy(source, destination): + def on_rm_error(func, path, exc_info): + os.chmod(path, stat.S_IWRITE) + func(path) + + source_path = Path(source).resolve() + dest_path = Path(destination).resolve() + + # check if source is a directory or file + if source_path.is_dir(): + # if source is a directory, ensure destination is a directory too + dest_path.mkdir(parents=True, exist_ok=True) # Create the destination directory if it doesn't exist + print(f"Copying directory \"{source_path}\" to directory \"{dest_path}\"...") + shutil.rmtree(str(dest_path), onerror=on_rm_error) + shutil.copytree(str(source_path), str(dest_path), dirs_exist_ok=True) + elif source_path.is_file(): + # if source is a file, ensure the parent directory of the destination exists + dest_path.parent.mkdir(parents=True, exist_ok=True) # Create parent directory if it doesn't exist + target = dest_path if dest_path.is_file() else dest_path / source_path.name + print(f"Copying file \"{source_path}\" to \"{target}\"...") + shutil.copy2(str(source_path), str(target)) + else: + print(f"Error: Source '{source_path}' is neither a file nor a directory.") + return False + return True \ No newline at end of file diff --git a/build_scripts/generate_project_files.py b/build_scripts/generate_project_files.py index 7ba7ea664..9dae6f4f4 100644 --- a/build_scripts/generate_project_files.py +++ b/build_scripts/generate_project_files.py @@ -1,116 +1,111 @@ -#Copyright(c) 2016-2025 Panos Karabelas +# Copyright(c) 2016-2025 Panos Karabelas # -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -#copies of the Software, and to permit persons to whom the Software is furnished -#to do so, subject to the following conditions : +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is furnished +# to do so, subject to the following conditions: # -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. # -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR -#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import os import shutil -import sys import stat import subprocess +import sys from pathlib import Path + import file_utilities paths = { "binaries": { - "data": Path("binaries/data"), - "models": Path("binaries/project/models"), - "music": Path("binaries/project/music"), - "terrain": Path("binaries/project/terrain"), - "materials": Path("binaries/project/materials"), + "data": Path("binaries") / "data", + "models": Path("binaries") / "project" / "models", + "music": Path("binaries") / "project" / "music", + "terrain": Path("binaries") / "project" / "terrain", + "materials": Path("binaries") / "project" / "materials", }, "third_party_libs": { - "dx": Path("third_party/libraries/dxcompiler.dll"), - "fmod": Path("third_party/libraries/fmod.dll"), - "fmod_debug": Path("third_party/libraries/fmodL.dll"), + "dx": Path("third_party") / "libraries" / "dxcompiler.dll", + "fmod": Path("third_party") / "libraries" / "fmod.dll", + "fmod_debug": Path("third_party") / "libraries" / "fmodL.dll", }, "assets": { - "models": Path("assets/models"), - "music": Path("assets/music"), - "terrain": Path("assets/terrain"), - "materials": Path("assets/materials"), + "models": Path("assets") / "models", + "music": Path("assets") / "music", + "terrain": Path("assets") / "terrain", + "materials": Path("assets") / "materials", }, } -def is_directory(path): - if not os.path.exists(path): - return os.path.splitext(path)[1] == "" - return os.path.isdir(path) +def generate_project_files(): + # determine if we're using Windows or another platform + is_windows = sys.argv[1].startswith("vs") # Assuming 'vs' prefix for Visual Studio -def copy(source, destination): - def on_rm_error(func, path, exc_info): - # make the file writable if it's read-only - os.chmod(path, stat.S_IWRITE) - func(path) + # construct the command, stripping any surrounding quotes from arguments + premake_exe = Path.cwd() / "build_scripts" / ("premake5.exe" if is_windows else "premake5") + premake_lua = Path("build_scripts") / "premake.lua" - if os.path.isfile(source): - if is_directory(destination): - dest_file = os.path.join(destination, os.path.basename(source)) - else: - dest_file = destination - print(f"Copying file \"{source}\" to \"{dest_file}\"...") - if os.path.exists(dest_file): - os.chmod(dest_file, stat.S_IWRITE) # make the file writable if it exists - shutil.copy2(source, dest_file) - elif is_directory(source) and is_directory(destination): - print(f"Copying directory \"{source}\" to directory \"{destination}\"...") - if os.path.exists(destination): - shutil.rmtree(destination, onerror=on_rm_error) - shutil.copytree(source, destination, dirs_exist_ok=True) - else: - print(f"Error: {source} and {destination} are not compatible.") - return False - return True - -def generate_project_files(): - cmd = ( - f'build_scripts\\premake5.exe --file="build_scripts\\premake.lua" "{sys.argv[1]}" "{sys.argv[2]}"' - if sys.argv[1] == "vs2022" - else f'premake5 --file="build_scripts/premake.lua" "{sys.argv[1]}" "{sys.argv[2]}"' - ) - subprocess.Popen(cmd, shell=True).communicate() + # remove quotes if they exist around sys.argv[1] and sys.argv[2] + action = sys.argv[1].strip('"') + platform = sys.argv[2].strip('"') + + # construct the command as a string with quoted paths + cmd = f'"{str(premake_exe)}" --file="{str(premake_lua)}" "{action}" "{platform}"' + + print("Running command:", cmd) - if sys.argv[1] == "vs2022" and not os.path.exists("spartan.sln"): - print("Error: spartan.sln not generated.") + try: + result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True) + print(result.stdout) + except subprocess.CalledProcessError as e: + print(f"Error occurred while generating project files: {e}") + print(f"Error output: {e.stderr}") sys.exit(1) - elif sys.argv[1] != "vs2022" and not os.path.exists("Makefile") and not os.path.exists("editor/Makefile") and not os.path.exists("runtime/Makefile"): - print("Error: makefiles not generated") + except Exception as e: + print(f"An unexpected error occurred: {e}") sys.exit(1) + # Check for generated files based on the action + if action == "vs2022": + if not Path("spartan.sln").exists(): + print("Error: spartan.sln not generated.") + sys.exit(1) + else: + makefiles = [Path("Makefile"), Path("editor") / "Makefile", Path("runtime") / "Makefile"] + if not any(m.exists() for m in makefiles): + print("Error: makefiles not generated") + sys.exit(1) + def main(): is_ci = "ci" in sys.argv print("\n1. Create binaries folder with the required data files...\n") - copy("data", paths["binaries"]["data"]) - copy("build_scripts/download_assets.py", "binaries/") - copy("build_scripts/file_utilities.py", "binaries/") - copy("build_scripts/7z.exe", "binaries/") - copy("build_scripts/7z.dll", "binaries/") - + file_utilities.copy("data", paths["binaries"]["data"]) + file_utilities.copy(Path("build_scripts") / "download_assets.py", "binaries") + file_utilities.copy(Path("build_scripts") / "file_utilities.py", "binaries") + file_utilities.copy(Path("build_scripts") / "7z.exe", "binaries") + file_utilities.copy(Path("build_scripts") / "7z.dll", "binaries") + print("\n2. Download and extract libraries...") library_url = 'https://www.dropbox.com/scl/fi/zq64yfpbly1goahmanm4r/libraries.7z?rlkey=m90lngvaosc9i3w8k16f1e1r6&st=5jm4fmqv&dl=1' - library_destination = 'third_party/libraries/libraries.7z' + library_destination = Path("third_party") / "libraries" / "libraries.7z" library_expected_hash = '8a20305ee9658dfdfba2aea88f26e6ee3d1330d7e6d26f42bc07bb76150ff1c5' - file_utilities.download_file(library_url, library_destination, library_expected_hash) - file_utilities.extract_archive("third_party/libraries/libraries.7z", "third_party/libraries/", sys.argv[1] == "vs2022", False) + file_utilities.download_file(library_url, str(library_destination), library_expected_hash) + file_utilities.extract_archive(str(library_destination), str(Path("third_party") / "libraries"), sys.argv[1] == "vs2022", False) print("3. Copying required DLLs to the binary directory...") for lib in paths["third_party_libs"].values(): - copy(lib, Path("binaries")) + file_utilities.copy(lib, Path("binaries")) print("\n4. Generate project files...\n") generate_project_files() diff --git a/generate_vs2022_vulkan.py b/generate_vs2022_vulkan.py index 2328ae821..ce9a8ee8e 100644 --- a/generate_vs2022_vulkan.py +++ b/generate_vs2022_vulkan.py @@ -1,15 +1,40 @@ +# Copyright(c) 2016-2025 Panos Karabelas +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is furnished +# to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + import os import subprocess import sys from pathlib import Path def main(): + # get the directory of the current script script_dir = Path(__file__).parent os.chdir(script_dir) + # define the path to the script to run script = script_dir / "build_scripts" / "generate_project_files.py" + + # run the script with specific arguments subprocess.Popen([sys.executable, str(script), "vs2022", "vulkan_windows"]).communicate() + + # exit the script sys.exit(0) if __name__ == "__main__": - main() + main() \ No newline at end of file