diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..18d1102 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,57 @@ +name: Build + +# on: +# push: +# pull_request: +# release: +# types: [published] + +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 +jobs: + build: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository + runs-on: ubuntu-latest + container: debian:bookworm + + steps: + + # Create the release, I guess? + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: Auto generated release + draft: false + prerelease: false + + # Install git. + - name: Git + run: | + apt-get update + apt-get install -y git + + # check out git repository. + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + + # Do the build. + - name: Build + run: | + Build/run_this_inside_debian_container_to_build.bsh + + - name: Upload Assets to Release with a wildcard + uses: csexton/release-asset-action@v3 + with: + pattern: "Build/Builds/Dist/SnekStudio_*" + github-token: ${{ secrets.GITHUB_TOKEN }} + release-url: ${{ steps.create_release.outputs.upload_url }} diff --git a/.gitignore b/.gitignore index 1c1c7f6..705da17 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ addons/KiriPythonRPCWrapper/Wheels # Stuff we removed right before release StuffToRemove + +# Extra junk from the build. +Build/build_vars.sh +Build/Builds \ No newline at end of file diff --git a/Build/Builds/.gdignore b/Build/Builds/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/Build/DownloadPythonRequirements.gd b/Build/DownloadPythonRequirements.gd new file mode 100644 index 0000000..a346a75 --- /dev/null +++ b/Build/DownloadPythonRequirements.gd @@ -0,0 +1,60 @@ +# FIXME: This file is kinda rough. We should probably clean it up. But we need +# build automation NOW. + +extends SceneTree + +var updater : KiriPythonBuildUpdater = null +var frame_count = 0 + +func _initialize(): + pass + +func _process(_delta): + + if frame_count == 0: + + # Instantiate the updater. + updater = load("res://addons/KiriPythonRPCWrapper/UpdateUI/PythonBuildUpdateUI.tscn").instantiate() + root.add_child(updater) + + # Start Python downloads per-platform. + updater._start_github_download("Windows") + updater._start_github_download("Linux") + # FIXME: MacOS. + + for platform in updater._current_downloads.keys(): + var download_request : HTTPRequest = updater._current_downloads[platform] + download_request.request_completed.connect( + func(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray): + if result == HTTPRequest.RESULT_SUCCESS and response_code == 200: + print("Download finished (", response_code, ") for platform: ", platform) + else: + push_error("Download failed (", result, ") (", response_code, ") for platform: ", platform) + quit(1)) + + # FIXME: Add MacOS. + if updater._check_platform_file_ready("Windows") and \ + updater._check_platform_file_ready("Linux"): + + # Download pip requirements. + var succeeded = true + if not updater.download_platform_requirements("Windows", true): + succeeded = false + if not updater.download_platform_requirements("Linux", true): + succeeded = false + # FIXME: MacOS. + + if not succeeded: + push_error("Something went wrong!") + quit(1) + + # We're done! Wrap it up. + updater.queue_free() + return true + + # Keep going. + frame_count += 1 + return false + +func _finalize(): + pass diff --git a/Build/WriteBuildVars.gd b/Build/WriteBuildVars.gd new file mode 100644 index 0000000..0c09173 --- /dev/null +++ b/Build/WriteBuildVars.gd @@ -0,0 +1,7 @@ +extends SceneTree + +func _initialize(): + var f : FileAccess = FileAccess.open("res://Build/build_vars.sh", FileAccess.WRITE) + f.store_string("VERSION=" + ProjectSettings.get("application/config/version") + "\n") + f.close() + quit(0) diff --git a/Build/build.bsh b/Build/build.bsh new file mode 100755 index 0000000..5b0239e --- /dev/null +++ b/Build/build.bsh @@ -0,0 +1,93 @@ +#!/bin/bash + +GODOT_RELEASE_URL="https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_linux.x86_64.zip" +GODOT_EXPORT_TEMPLATE_URL="https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_export_templates.tpz" +GODOT_EXPORT_TEMPLATE_VERSION="4.3.stable" + +set -e + +cd "$(dirname "$0")/.." +pushd . + +# Download Godot. +cd Build +echo "Downloading Godot..." +if [ \! -e godot.zip ]; then + curl -sfL -o godot.zip "${GODOT_RELEASE_URL}" +fi + +# Download templates. +echo "Downloading export templates..." +if [ \! -e godot_templates.tpz ]; then + curl -sfL -o godot_templates.tpz "${GODOT_EXPORT_TEMPLATE_URL}" +fi + +# Extract Godot. +echo "Extracting Godot..." +if [ \! -e GodotBinary ]; then + mkdir GodotBinary +fi +cd GodotBinary +unzip -o ../godot.zip +cd .. + +# Extract export templates. +echo "Extracting export templates..." +mkdir -p "$HOME/.local/share/godot/export_templates/${GODOT_EXPORT_TEMPLATE_VERSION}" +unzip -j godot_templates.tpz -d "$HOME/.local/share/godot/export_templates/${GODOT_EXPORT_TEMPLATE_VERSION}" + + +GODOT="$(realpath "$(ls GodotBinary/*.x86_64)")" +echo "Godot binary path: ${GODOT}" + +popd + + + + +# Write out build variables and read them in. +"${GODOT}" \ + --disable-render-loop \ + --no-header \ + --headless --script \ + Build/WriteBuildVars.gd +source Build/build_vars.sh + + + +# We have to do this or it can't see a bunch of the classes. + +"${GODOT}" --headless --path . \ + --import + +# Fetch all Python dependencies. + +"${GODOT}" --headless --path . \ + --script Build/DownloadPythonRequirements.gd + + +# Actually do the builds. + +"${GODOT}" --headless --path . \ + --export-release "Linux/X11" Build/Builds/SnekStudio_Linux/snekstudio + +"${GODOT}" --headless --path . \ + --export-debug "Windows Desktop" Build/Builds/SnekStudio_Windows/snekstudio.exe + +# Package them. + +pushd . +cd Build/Builds +tar czvf "SnekStudio_Linux_${VERSION}.tar.gz" SnekStudio_Linux +zip -r "SnekStudio_Windows_${VERSION}.zip" SnekStudio_Windows + +# Make the output directory + +if [ \! -e Dist ]; then + mkdir Dist +fi + +# Move stuff into it. + +mv -f *.tar.gz *.zip Dist/ + diff --git a/Build/build_inside_docker.bsh b/Build/build_inside_docker.bsh new file mode 100755 index 0000000..833b297 --- /dev/null +++ b/Build/build_inside_docker.bsh @@ -0,0 +1,11 @@ +#!/bin/bash + +# This is NOT a script to be run inside a container. It is a script that runs something inside a container + +cd "$(dirname "$0")/.." + +#echo "docker run -v /build:. debian" +docker run --rm \ + -v "$(realpath .)":/build \ + debian \ + "/build/Build/run_this_inside_debian_container_to_build.bsh" diff --git a/Build/run_this_inside_debian_container_to_build.bsh b/Build/run_this_inside_debian_container_to_build.bsh new file mode 100755 index 0000000..abc03d7 --- /dev/null +++ b/Build/run_this_inside_debian_container_to_build.bsh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +cd "$(dirname "$0")" + +bash ./setup_debian_container.bsh + +bash ./build.bsh + diff --git a/Build/setup_debian_container.bsh b/Build/setup_debian_container.bsh new file mode 100755 index 0000000..1b6812a --- /dev/null +++ b/Build/setup_debian_container.bsh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +apt-get update + +apt-get install -y \ + curl unzip zip libfontconfig1 + diff --git a/addons/KiriPythonRPCWrapper/UpdateUI/PythonBuildUpdateUI.gd b/addons/KiriPythonRPCWrapper/UpdateUI/PythonBuildUpdateUI.gd index c503f86..a9da051 100644 --- a/addons/KiriPythonRPCWrapper/UpdateUI/PythonBuildUpdateUI.gd +++ b/addons/KiriPythonRPCWrapper/UpdateUI/PythonBuildUpdateUI.gd @@ -1,5 +1,6 @@ @tool extends Control +class_name KiriPythonBuildUpdater # These names must match the output from # KiriPythonBuildWrangler.get_host_os_name(). @@ -181,6 +182,11 @@ func _update_platform_dropdowns(): func _update_download_button_progress(): for platform_name in _current_downloads.keys(): + + var download_percent : float = 100.0 * \ + _current_downloads[platform_name].get_downloaded_bytes() / \ + _current_downloads[platform_name].get_body_size() + _platform_buttons[platform_name].disabled = true if _current_downloads[platform_name].get_body_size() == -1: @@ -188,9 +194,6 @@ func _update_download_button_progress(): _platform_buttons[platform_name].text = "Starting..." else: # Change the button text to show a download percentage. - var download_percent : float = 100.0 * \ - _current_downloads[platform_name].get_downloaded_bytes() / \ - _current_downloads[platform_name].get_body_size() _platform_buttons[platform_name].text = "Downloading: " + \ str(int(download_percent)) + "%" @@ -446,15 +449,23 @@ func _get_latest_version_releaseinfo_completed( %Button_UpdateReleaseAssets.disabled = false _cleanup_request() -func _on_button_download_requirements_pressed(platform_name) -> void: +func _on_button_download_requirements_pressed(platform_name : String) -> void: + download_platform_requirements(platform_name) + _update_platform_ui() + +func download_platform_requirements(platform_name : String, automated : bool = false) -> bool: + + _load_platform_status() var download_path : String = get_script().resource_path.get_base_dir().path_join("../Wheels") print("Unpacking Python...") var python_instance : KiriPythonWrapperInstance = KiriPythonWrapperInstance.new("") if python_instance.setup_python(true) == false: - OS.alert("You need to download a Python build for your host platform first!") + if not automated: + OS.alert("You need to download a Python build for your host platform first!") push_error("You need to download a Python build for your host platform first!") + return false print("Writing requirements file...") DirAccess.make_dir_recursive_absolute(download_path) @@ -500,8 +511,10 @@ func _on_button_download_requirements_pressed(platform_name) -> void: # Handle errors or write a success indicator. if pip_download_return != 0: - OS.alert("Pip failed (platform: " + platform_name + "): " + str(output)) + if not automated: + OS.alert("Pip failed (platform: " + platform_name + "): " + str(output)) push_error("Pip failed (platform: ", platform_name, "): ", output) + return false else: var last_requirements_file : FileAccess = \ FileAccess.open( @@ -509,4 +522,4 @@ func _on_button_download_requirements_pressed(platform_name) -> void: FileAccess.WRITE) last_requirements_file.store_string(_platform_status["requirements"]) - _update_platform_ui() + return true diff --git a/addons/SnekStudioExport/SnekStudioExport.gd b/addons/SnekStudioExport/SnekStudioExport.gd index 360adeb..01a3b45 100644 --- a/addons/SnekStudioExport/SnekStudioExport.gd +++ b/addons/SnekStudioExport/SnekStudioExport.gd @@ -70,44 +70,58 @@ class SnekStudioExporter extends EditorExportPlugin: _export_path = path.get_base_dir() + # Export all mods as .zip files. DirAccess.make_dir_recursive_absolute(_export_path.path_join("Mods")) - var mods_list : PackedStringArray = DirAccess.get_directories_at("res://Mods") for mod_to_export in mods_list: var zp : ZIPPacker = ZIPPacker.new() zp.open(_export_path.path_join("Mods").path_join(mod_to_export + ".zip")) _zip_directory(zp, "res://Mods".path_join(mod_to_export)) zp.close() + + # Export all sample models. + DirAccess.make_dir_recursive_absolute(_export_path.path_join("SampleModels")) + copy_recursive( + _export_path.path_join("SampleModels"), + "res://SampleModels", + "*.vrm") + + # Export all LICENSE files. + DirAccess.make_dir_recursive_absolute(_export_path.path_join("Licenses")) + copy_recursive(_export_path.path_join("Licenses"), "res://Licenses") + + # Copy our own LICENSE file. + DirAccess.copy_absolute("res://LICENSE", _export_path.path_join("LICENSE.txt")) + + func copy_recursive(dest : String, src : String, match_string : String = ""): + + # Make the directory to put the thing in. + DirAccess.make_dir_recursive_absolute(dest.get_base_dir()) + + if DirAccess.dir_exists_absolute(src): + + # Make the directory. + print("make_dir_recursive_absolute: ", dest) + DirAccess.make_dir_recursive_absolute(dest) + + # Copy contents over + var directory_contents = PackedStringArray() + directory_contents.append_array(DirAccess.get_directories_at(src)) + directory_contents.append_array(DirAccess.get_files_at(src)) + print("directory contents... ", directory_contents) + for thing_in_directory in directory_contents: + copy_recursive( + dest.path_join(thing_in_directory), + src.path_join(thing_in_directory), + match_string) + + else: + + # Copy the thing. + if dest.match(match_string) or match_string == "": + print("Copy file: ", dest, " - ", src) + DirAccess.copy_absolute(src, dest) - #copy_recursive(_export_path.path_join("Mods"), "res://Mods") - - #func copy_recursive(dest : String, src : String): - # - ## Make the directory to put the thing in. - #DirAccess.make_dir_recursive_absolute(dest.get_base_dir()) - # - #if DirAccess.dir_exists_absolute(src): - # - ## Make the directory. - #print("make_dir_recursive_absolute: ", dest) - #DirAccess.make_dir_recursive_absolute(dest) - # - ## Copy contents over - #var directory_contents = PackedStringArray() - #directory_contents.append_array(DirAccess.get_directories_at(src)) - #directory_contents.append_array(DirAccess.get_files_at(src)) - #print("directory contents... ", directory_contents) - #for thing_in_directory in directory_contents: - #copy_recursive( - #dest.path_join(thing_in_directory), - #src.path_join(thing_in_directory)) - # - #else: -# - ## Copy the thing. - #print("copy_absolute: ", dest, " - ", src) - #DirAccess.copy_absolute(src, dest) - # func _export_file(path : String, type : String, features : PackedStringArray): @@ -116,16 +130,9 @@ class SnekStudioExporter extends EditorExportPlugin: var mods_path = "res://Mods/" if path.begins_with(mods_path): skip() - - #var path_relative = path.substr(len(mods_path)) - #var dest_path = _export_path.path_join("Mods/".path_join(path_relative)) - # - #DirAccess.make_dir_recursive_absolute(dest_path.get_base_dir()) - # - #DirAccess.copy_absolute(path, dest_path) - # - #print("_export_file: ", path) - #print(" dest_path: ", dest_path) + + if path.begins_with("res://SampleModels/"): + skip() func _get_name(): return "SnekStudioExporter"