Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG BYOND_VERSION=513.1542
ARG BYOND_VERSION
FROM beestation/byond:${BYOND_VERSION}

WORKDIR /app
Expand Down
116 changes: 73 additions & 43 deletions listener.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import platform
import random
import re
import os
from flask import Flask, jsonify, request, abort
import shutil
import string
import subprocess
from pathlib import Path

import docker
from flask import Flask, abort, jsonify, request

app = Flask(__name__)
client = docker.from_env()
Expand All @@ -28,7 +29,11 @@ def startCompile():
if request.method == "POST":
posted_data = request.get_json()
if "code_to_compile" in posted_data:
return jsonify(compileTest(posted_data["code_to_compile"], posted_data["byond_version"]))
return jsonify(
compileTest(
posted_data["code_to_compile"], posted_data["byond_version"]
)
)
else:
abort(400)

Expand Down Expand Up @@ -71,25 +76,54 @@ def buildVersion(version: str):
else:
try:
print(f"Attempting to build version: {version}")
client.images.build(
path=f"https://github.com/BeeStation/byond-docker.git",
platform="linux/386",
rm=True,
pull=True,
tag=f"beestation/byond:{version}",
buildargs={
"buildtime_BYOND_MAJOR": version.split(".")[0],
"buildtime_BYOND_MINOR": version.split(".")[1],
},
)
return client.images.build(
path=f"{Path.cwd()}",
dockerfile="Dockerfile",
rm=True,
pull=True,
tag=f"test:{version}",
buildargs={"BYOND_VERSION": version},
)
except docker.errors.BuildError:
raise


def normalizeCode(codeText: str):
indent_step = float("inf")
lines = codeText.split("\n")
for line in lines:
indent_level = len(line) - len(line.lstrip())
if indent_level > 0:
indent_step = min(indent_step, indent_level)
if indent_step == float("inf"):
return codeText
result_lines = []
for line in lines:
stripped = line.lstrip()
indent_level = len(line) - len(stripped)
result_lines.append("\t" * (indent_level // indent_step) + stripped)
return "\n".join(result_lines)


def compileTest(codeText: str, version: str):
try:
buildVersion(version=version)
except docker.errors.BuildError as e:
results = {"build_error": True, "exception": str(e)}
return results

codeText = normalizeCode(codeText)

randomDir = Path.cwd().joinpath(randomString())
randomDir.mkdir()
shutil.copyfile(TEST_DME, randomDir.joinpath("test.dme"))
Expand All @@ -98,53 +132,39 @@ def compileTest(codeText: str, version: str):
fc.write(loadTemplate(codeText))
else:
fc.write(loadTemplate(codeText, False))
docker_path = None
if HOST_OS == "Windows":
# To get cleaner outputs, we run docker as a subprocess rather than through the API
proc = subprocess.Popen(
[
"docker",
"run",
"--name",
f"{randomDir.name}",
"--rm",
"--network",
"none",
"-v",
f"{randomDir}:/app/code:ro",
f"test:{version}",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
docker_path = "docker"
else:
# Expects the linux user to be running docker locally, not as root
proc = subprocess.Popen(
[
f"{Path.home()}/bin/docker",
"run",
"--name",
f"{randomDir.name}",
"--rm",
"--network",
"none",
"-v",
f"{randomDir}:/app/code:ro",
f"test:{version}",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
docker_path = f"{Path.home()}/bin/docker"
if not os.path.isfile(docker_path):
docker_path = "/usr/bin/docker"
Comment thread
pali6 marked this conversation as resolved.
proc = subprocess.Popen(
[
docker_path,
"run",
"--name",
f"{randomDir.name}",
"--platform",
"linux/386",
"--rm",
"--network",
"none",
"-v",
f"{randomDir}:/app/code:ro",
f"test:{version}",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
try:
compile_log, run_log = proc.communicate(
timeout=30
) # A bit hacky, but provides exceptionally clean results. The main output will be captured as the compile_log while the "error" output is captured as run_log
test_killed = False
except subprocess.TimeoutExpired:
proc.kill()
if HOST_OS == "Windows":
subprocess.run(["docker", "stop", f"{randomDir.name}"], capture_output=True)
else:
subprocess.run([f"{Path.home()}/bin/docker", "stop", f"{randomDir.name}"], capture_output=True)
subprocess.run([docker_path, "stop", f"{randomDir.name}"], capture_output=True)
compile_log, run_log = proc.communicate()
test_killed = True

Expand All @@ -153,15 +173,25 @@ def compileTest(codeText: str, version: str):
run_log = re.sub(
r"The BYOND hub reports that port \d* is not reachable.", "", run_log
) # remove the network error message
compile_log = (compile_log[:1200] + "...") if len(compile_log) > 1200 else compile_log
run_log = re.sub(r"\n---------------\n", "\n", run_log) # remove the dashes
run_log = re.sub(r"World opened on network port \d*\.\n", "", run_log)
compile_log = re.sub(r"loading test.dme\n", "", compile_log)
compile_log = re.sub(r"saving test.dmb\n", "", compile_log)
compile_log = (
(compile_log[:1200] + "...") if len(compile_log) > 1200 else compile_log
)
run_log = (run_log[:1200] + "...") if len(run_log) > 1200 else run_log

shutil.rmtree(randomDir)

if f"Unable to find image 'test:{version}' locally" in run_log:
results = {"build_error": True, "exception": run_log}
else:
results = {"compile_log": compile_log, "run_log": run_log, "timeout": test_killed}
results = {
"compile_log": compile_log,
"run_log": run_log,
"timeout": test_killed,
}

return results

Expand Down