blob: ad3ea7c79f81cd5fe7c19d0dd56ad4275f6e8be4 [file] [log] [blame]
from collections import defaultdict
import argparse
import datetime
import glob
import os
import shutil
import subprocess
import sys
import time
import tarfile
import tempfile
import zipfile
def fprint(*args, **kwargs):
print(*args, **kwargs)
sys.stdout.flush()
def files_in_dir(root, base = None):
walk_dir = os.path.join(root, base) if base else root
ret = []
for dir, _, files in os.walk(walk_dir):
for file in files:
rel = os.path.relpath(dir, root)
file_path = os.path.join(rel, file)
ret.append(file_path)
return ret
def write_files(workspace, files, dest):
fprint("Creating " + dest)
if dest.endswith(".zip"):
with zipfile.ZipFile(dest, "w") as zip:
for rel in files:
zip.write(os.path.join(workspace, rel), rel)
else:
os.makedirs(dest, exist_ok=True)
for rel in files:
path = os.path.join(dest, rel)
os.makedirs(os.path.dirname(path), exist_ok=True)
shutil.copy2(os.path.join(workspace, rel), path)
def is_in_any(path, dirs):
return any(path.startswith(d) for d in dirs)
def jps_build(args, environment, cwd):
if args.reuse_workspace:
workspace = args.reuse_workspace
else:
workspace = "/tmp/.jps_build"
if os.path.exists(workspace):
fprint("%s exists, deleting..." % workspace)
shutil.rmtree(workspace)
os.makedirs(workspace)
fprint("%s: Setting up sources..." % (str(datetime.datetime.now())))
start = time.time()
for source in args.sources:
startFile = time.time()
if source.endswith(".tar"):
with tarfile.open(source, "r") as tar:
if not args.reuse_workspace:
tar.extractall(workspace)
elif source.endswith(".zip"):
with zipfile.ZipFile(source, "r") as zip:
if not args.reuse_workspace:
# TODO: don't use shell, but extract all does not preserve x attribute
# zip.extractall(workspace)
subprocess.run(["unzip", source, "-d", workspace], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
elif source.endswith(".lst"):
with open(source, "r") as f:
for rel, path in [l.strip().split("=") for l in f.readlines()]:
dest = os.path.join(workspace, rel)
os.makedirs(os.path.dirname(dest), exist_ok=True)
if args.verbose:
fprint("Copying listed file from %s to: %s %s" %(path, workspace, rel))
shutil.copy2(path, dest)
elif os.path.isdir(source):
shutil.copytree(source, workspace, dirs_exist_ok=True)
fprint(" %s: %ds" % (source, time.time() - startFile))
fprint("Setup duration: %ds" % (time.time() - start) )
sources = set(files_in_dir(workspace))
home = os.path.join(workspace, "home")
os.makedirs(home, exist_ok=True)
env = {k : v for k,v in environment.items() }
env["HOME"] = home
env["JPS_WORKSPACE"] = workspace
for k,v in args.envs:
env[k] = v
bin_cwd = os.path.join(workspace, args.working_directory)
bin_path = os.path.join(bin_cwd, args.command)
fprint("%s: Running..." % (str(datetime.datetime.now())))
cmd = [bin_path]
cmd.extend([s.replace("{jps_bin_cwd}", bin_cwd) for s in args.args])
if args.verbose:
fprint("Running " + " ".join(cmd))
retcode = subprocess.call(cmd, cwd=bin_cwd, env=env)
run_workspace = environment.get("BUILD_WORKSPACE_DIRECTORY")
fprint("%s: Done running " % str(datetime.datetime.now()))
if retcode == 0:
all_files = set(files_in_dir(workspace))
downloaded_files = set()
output_files = set()
for new_file in all_files - sources:
is_output = False
if is_in_any(new_file, args.output_dirs):
output_files.add(new_file)
elif is_in_any(new_file, args.ignore_dirs):
pass
else:
# If it's not a build output or ignored, it is a download
downloaded_files.add(new_file)
if args.download_cache and run_workspace:
write_files(workspace, downloaded_files, os.path.join(run_workspace, args.download_cache))
fprint("%s: Done writing cache" % str(datetime.datetime.now()))
if args.out_file:
write_files(workspace, output_files, args.out_file)
fprint("%s: Done writing output file" % str(datetime.datetime.now()))
if args.verbose:
fprint("Output Dirs:\n " + "\n".join(args.output_dirs))
fprint("Output Files:\n " + "\n".join(output_files))
fprint("Downloaded Files:\n " + "\n".join(downloaded_files))
if run_workspace and args.delete_workspace and not args.reuse_workspace:
shutil.rmtree(workspace)
fprint("%s: Done deleting" % str(datetime.datetime.now()))
else:
fprint("Leaving " + workspace + " behind.")
fprint("%s: Done copying" % str(datetime.datetime.now()))
return retcode
def endswith_zip(arg):
if not arg.endswith(".zip"):
fprint("Argument '%s' must end with .zip" % arg)
sys.exit(1)
return arg
def main(argv, environment, cwd):
parser = argparse.ArgumentParser()
parser.add_argument(
"--sources",
dest="sources",
action="append",
required=True,
help="Path to the zip,tar or directory containing workspace files.")
parser.add_argument(
"--command",
dest="command",
required=True,
help="Command to execute on the workspace.")
parser.add_argument(
"--working_directory",
dest="working_directory",
default="",
help="Working directory in the workspace to run the command.")
parser.add_argument(
"--out_file",
help="The path to a zip file to store the output.",
type=endswith_zip)
parser.add_argument(
"--reuse_workspace",
dest="reuse_workspace",
help="The already setup workspace to use, useful to test.")
parser.add_argument(
"--download_cache",
dest="download_cache",
help="If given, where to save the download cache.")
parser.add_argument(
"--delete_workspace",
action=argparse.BooleanOptionalAction,
dest="delete_workspace",
default=True,
help="Whether to delete the temporary workspace.")
parser.add_argument(
"--module",
dest="module",
help="The name of the module to build.")
parser.add_argument(
"--arg",
dest="args",
action="append",
default=[],
help="Arguments to pass to the command.")
parser.add_argument(
"--output_dir",
dest="output_dirs",
action="append",
default=[],
help="Directories to be considered as outputs of the running command.")
parser.add_argument(
"--ignore_dir",
dest="ignore_dirs",
action="append",
default=[],
help="Directories that are created that are not download nor output")
parser.add_argument(
"--verbose",
action=argparse.BooleanOptionalAction,
default=False,
help="Generate verbose output")
parser.add_argument(
"--env",
nargs=2,
dest="envs",
action="append",
default=[],
help="Environment variables to pass to the command script.")
args = parser.parse_args(argv)
return jps_build(args, environment, cwd)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:], os.environ, os.getcwd()))