ecs/flint.py

280 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import platform
import errno
import configparser
import urllib.request
import shutil
import subprocess
from subprocess import PIPE
import stat
import sys
import argparse
flintlock = ".flintlock"
def install_plugin(url, plugins_folder, config, local = False):
metainfo = configparser.ConfigParser()
if not local:
req = urllib.request.Request(url + "/metainfo")
with urllib.request.urlopen(req) as response:
metainfo.read_string(response.read().decode("ascii"))
else:
with open(os.path.join(url, "metainfo.local"), "r") as f:
metainfo.read_file(f)
plugin_name = metainfo["meta"]["name"] + "@" + metainfo["meta"]["author"]
# Make sure we can't have local and remote version of the same plugin at the same time
# @todo This could be cleaner
if config.has_option("flint.py-plugins", plugin_name if local else plugin_name + ".local"):
config.remove_option("flint.py-plugins", plugin_name if local else plugin_name + ".local")
print("Installing '" + plugin_name + "'")
plugin_folder = os.path.join(plugins_folder, plugin_name)
if os.path.exists(plugin_folder):
shutil.rmtree(plugin_folder)
os.makedirs(plugin_folder)
# Register the plugin so that we can later update
config["flint.py-plugins"][plugin_name if not local else plugin_name + ".local"] = url
with open(flintlock, "w") as f:
config.write(f)
# @todo Use this same system for flint self
# @todo Add multiplatform support
if platform.system() == "Linux" and metainfo.has_section("linux"):
for filename in metainfo["linux"]:
if not local:
req = urllib.request.Request(url + "/" + filename)
with urllib.request.urlopen(req) as response, open(os.path.join(plugin_folder, filename), "wb") as f:
shutil.copyfileobj(response, f)
else:
os.symlink(os.path.abspath(os.path.join(url, metainfo["linux"][filename])), os.path.join(plugin_folder, filename))
elif platform.system() == "Windows" and metainfo.has_section("windows"):
for filename in metainfo["windows"]:
if not local:
req = urllib.request.Request(url + "/" + filename)
with urllib.request.urlopen(req) as response, open(os.path.join(plugin_folder, filename), "wb") as f:
shutil.copyfileobj(response, f)
else:
# @todo This requires admin mode on windows
os.symlink(os.path.abspath(os.path.join(url, metainfo["windows"][filename])), os.path.join(plugin_folder, filename))
else:
print("Plugin not available for current platform")
def remove_plugin(plugin_name, plugins_folder, config):
plugin_folder = os.path.join(plugins_folder, plugin_name)
if config.has_option("flint.py-plugins", plugin_name):
config.remove_option("flint.py-plugins", plugin_name)
elif config.has_option("flint.py-plugins", plugin_name + ".local"):
config.remove_option("flint.py-plugins", plugin_name + ".local")
else:
print("Plugin '" + plugin_name + "' does not exist")
return
if os.path.exists(plugin_folder):
shutil.rmtree(plugin_folder)
def main():
# @todo Add update, gets latest version and stores the specific version
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--config", action='store_const', const=True, default=False)
parser.add_argument("--local", action='store_const', const=True, default=False)
parser.add_argument("--generate")
parser.add_argument("--version")
parser.add_argument("--install", nargs='?', const="__update__")
parser.add_argument("--remove")
parser.add_argument("--debug-flint", action='store_const', const=True, default=False)
args, unknownargs = parser.parse_known_args()
flintdir_base = ".flint"
flintdir = os.path.join(flintdir_base, "versions")
flintplug = os.path.join(flintdir_base, "plugins")
flintbin = "flint"
if os.name == "nt":
flintbin += ".exe"
# Make sure the flintlock file exists
if not os.path.isfile(flintlock):
# Create a new flintlock file with default values
config = configparser.ConfigParser()
with open(flintlock, "w") as f:
config.write(f)
# Open the flintlock file
config = configparser.ConfigParser()
with open(flintlock, "r") as f:
config.read_file(f)
# Make sure that all required keys exist
if not config.has_section("flint.py"):
config["flint.py"] = {}
if not config.has_option("flint.py", "version"):
config["flint.py"]["version"] = "nightly"
if not config.has_option("flint.py", "base_url"):
config["flint.py"]["base_url"] = "https://downloads.mtgames.nl/release/flint"
if not config.has_section("flint.py-plugins"):
config["flint.py-plugins"] = {}
with open(flintlock, "w") as f:
config.write(f)
# Check if the user has enable config mode
if args.config:
# @todo Improve config mode
if args.version:
config["flint.py"]["version"] = args.version
print("Flint will now use version: " + args.version)
else:
print("Did nothing...")
with open(flintlock, "w") as f:
config.write(f)
elif args.generate:
if not os.path.exists(args.generate + "/src"):
os.makedirs(args.generate + "/src")
if not os.path.exists(args.generate + "/include"):
os.makedirs(args.generate + "/include")
# @todo Also generate a simlpe example cpp file
with open("./flint.lua", "w") as f:
f.write("executable \"" + args.generate + "\"\n")
f.write("\tpath \"" + args.generate + "\"\n")
f.write("\n")
f.write("run_target \"" + args.generate + "\"\n")
elif args.install:
# @todo We need to auto update all plugins
# @todo We need to be able to remove plugins
metainfo = configparser.ConfigParser()
if args.install == "__update__":
for plugin in config["flint.py-plugins"]:
# @todo Make the download functions actual functions so we can call them here
install_plugin(config["flint.py-plugins"][plugin], flintplug, config, plugin.endswith(".local"))
else:
install_plugin(args.install, flintplug, config, args.local)
elif args.remove:
remove_plugin(args.remove, flintplug, config)
with open(flintlock, "w") as f:
config.write(f)
else:
# Check desired version
version = config["flint.py"]["version"]
base_url = config["flint.py"]["base_url"]
if args.version:
version = args.version
print("Flint: " + version)
flintdir = os.path.join(flintdir, version)
if os.path.isfile(os.path.join(version, flintbin)):
# The version contains a path to a flint executable
flintdir = version
else:
# Make sure the flint directory exists
if not os.path.exists(flintdir):
try:
os.makedirs(flintdir)
except OSError as exc:
if exc != errno.EEXIST:
raise
# Check if we need to update
needs_update = False
# Download actual checksum
checksums = configparser.ConfigParser()
try:
req = urllib.request.Request(base_url + "/" + version + "/checksums")
with urllib.request.urlopen(req) as response:
checksums.read_string(response.read().decode("ascii"))
checksums_current = configparser.ConfigParser()
if (not os.path.isfile(os.path.join(flintdir, flintbin))) or (not os.path.isfile(os.path.join(flintdir, "checksums"))):
# We need to download instead of update
needs_update = True
checksums_current["flint"] = {}
print("Downloading flint...")
else:
# Load the current checksum
with open(os.path.join(flintdir, "checksums"), "r") as f:
checksums_current.read_file(f)
# Compare the checksum to determine if updates are needed
# @note This does not detect if the executable is corrupted
if not checksums_current["flint"][flintbin] == checksums["flint"][flintbin]:
needs_update = True
print("Updating flint...")
# Store the new checksum
checksums_current["flint"][flintbin] = checksums["flint"][flintbin]
with open(os.path.join(flintdir, "checksums"), "w") as f:
checksums_current.write(f)
except urllib.error.URLError as error:
print("Unable to update flint!")
needs_update = False
# @todo Show progress bar
if needs_update:
try:
req = urllib.request.Request(base_url + "/" + version + "/" + flintbin)
with urllib.request.urlopen(req) as response, open(flintdir + "/" + flintbin, "wb") as f:
shutil.copyfileobj(response, f)
# Make sure we can execute flint
mode = os.stat(os.path.join(flintdir, flintbin)).st_mode
os.chmod(os.path.join(flintdir, flintbin), mode | stat.S_IEXEC)
with open(flintlock, "w") as f:
config.write(f)
except urllib.error.URLError as error:
print("Flint update failed!")
needs_update = False
command = []
env = os.environ
if os.name == "nt":
env["PATH"] = flintdir + ";.flint/plugins;" + env["PATH"]
else:
env["LD_LIBRARY_PATH"] = flintdir + ":.flint/plugins"
if os.name == "nt":
command.extend([r"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat", "x64", "&&"])
env["VSCMD_START_DIR"] = os.getcwd()
if args.debug_flint:
if platform.system() == "Linux":
command.extend(["gdb", "--args"])
elif platform.system() == "Windows":
command.extend(["devenv", "/debugexe"])
else:
print("Debugging not available for current platform")
command.append(os.path.join(flintdir, flintbin))
command.extend(unknownargs)
# Python pre 3.5 does not support run
return_code = 0
if sys.version_info[1] < 5:
subprocess.call(command, env=env)
else:
return_code = subprocess.run(command, env=env).returncode
exit(return_code)
if __name__ == "__main__":
main()