You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#!/usr/bin/python3
import os
import sys
import argparse
import subprocess
from xml.dom.minidom import parse as XMLOpen
import tempfile
import time
from dataclasses import dataclass
@DataClass
class PackageReference:
name: str
version: str
destination: str
@DataClass
class LibraryReference:
name: str
hintPath: str
@DataClass
class PublicizeTarget:
inputName: str
outputName: str
def parseArgs(argv):
argParser = argparse.ArgumentParser(description="Automate compiling C# projects on Linux, using Mono")
argParser.add_argument("--reference", type=str, metavar="PATH", help="Location of Rimworld Reference libraries")
argParser.add_argument("--output", "-o", type=str, default="Assemblies/CombatExtended.dll", help="Output filename")
argParser.add_argument("--csproj", type=str, default="Source/CombatExtended/CombatExtended.csproj", help="C# project to build.")
argParser.add_argument("--all-libs", action="store_true", default=False, help="Reference all dlls in $reference, even those not specified by the .csproj file")
argParser.add_argument("--verbose", "-v", action="count", default=0, help="Increase verbosity, specify more times for more verbosity")
argParser.add_argument("--download-libs", action="store_true", default=False, help=f"Automatically download referenced packages to {tdir}/downloads and unpack them to $reference")
argParser.add_argument("--publicizer", type=str, metavar="PATH", help="Location of AssemblyPublicizer source code or parent directory of AssemblyPublicizer.exe")
argParser.add_argument("--debug", action="store_true", default=False, help="Define DEBUG when calling csc")
argParser.add_argument("--refout", metavar="PATH", default=None, help="Specify where to save a reference library")
options = argParser.parse_args(argv[1:])
if not options.download_libs and options.reference is None:
print("You must either set reference to where the rimworld reference assemblies are (including Harmony), or set the download-libs option")
exit(1)
if options.csproj is None or not os.path.exists(options.csproj):
print("You must specify the path to the .csproj file you wish to build")
exit(1)
if options.reference is None:
options.reference = f"{tdir}/rwreference"
return options
def run_command(command, verbose):
if verbose:
print(f"Running command: {command}")
result = subprocess.run(command, shell=True)
if result.returncode != 0:
raise Exception(f"Command failed: {command}")
def downloadLib(name, version, dest, verbose):
if verbose:
print(f"Downloading {version} of {name} from nuget")
quiet = "-q" if verbose < 5 else ""
run_command(f"wget {quiet} https://www.nuget.org/api/v2/package/{name}/{version} -O {dest}", verbose)
def parse_packages(csproj):
packages = []
for idx, package in enumerate(csproj.getElementsByTagName("PackageReference")):
name = package.attributes['Include'].value
version = package.attributes['Version'].value
dest = f"{tdir}/downloads/rwref-{idx}.zip"
packages.append(PackageReference(name, version, dest))
return packages
def parse_libraries(csproj):
libraries = []
removed_libraries = []
for reference in csproj.getElementsByTagName("Reference"):
hintPath = reference.getElementsByTagName("HintPath")
if 'Remove' in reference.attributes:
assemblyName = reference.attributes['Remove'].value.rsplit('\', 1)[1]
removed_libraries.append(assemblyName)
continue
if hintPath:
hintPath = hintPath[0].firstChild.data.strip().replace("\", "/")
libraries.append(LibraryReference(reference.attributes['Include'].value, hintPath))
return libraries, removed_libraries
def parse_sources(csproj, base_dir, verbose):
sources = []
for d, _, files in os.walk(base_dir):
sources.extend(os.path.join(d, f).split(base_dir + '/', 1)[1] for f in files if f.endswith('.cs'))
for source in csproj.getElementsByTagName("Compile"):
if 'Include' in source.attributes:
sources.append(source.attributes['Include'].value.replace("\\", "/"))
if 'Remove' in source.attributes:
v = source.attributes['Remove'].value.replace('\\', '/')
if v in sources:
sources.remove(v)
elif v.endswith('**'):
if verbose:
print("Removing wildcard:", v)
sources = [i for i in sources if not i.startswith(v[:-2])]
return [os.path.join(base_dir, i) for i in sources]
def parse_publicize(csproj):
publicized_libraries = []
removed_libraries = []
publicize_task = [i for i in csproj.getElementsByTagName("Target") if 'Name' in i.attributes and i.attributes['Name'].value.startswith('Publi')]
variables = {}
for pt in publicize_task:
for pg in pt.getElementsByTagName("PropertyGroup"):
for var in pg.childNodes:
if var.nodeName != '#text':
val = var.firstChild.data.strip()
if '\\' in val:
variables[var.nodeName] = val.rsplit('\\', 1)[1]
elif ')' in val:
variables[var.nodeName] = val.rsplit(')', 1)[1]
else:
variables[var.nodeName] = val
for pub in pt.getElementsByTagName("Publicise"):
target = pub.attributes['TargetAssemblyPath'].value
if '$' in target:
target = target.replace('$', '%').replace(')', ')s') % variables
output = target.rsplit('.', 1)[0] + '_publicized.dll'
publicized_libraries.append(PublicizeTarget(target, output))
if not publicized_libraries:
publicize_task = [i.attributes['Include'].value for i in csproj.getElementsByTagName("Publicize") if 'Include' in i.attributes]
for pub in publicize_task:
publicized_libraries.append(PublicizeTarget(pub + '.dll', pub + '_publicized.dll'))
removed_libraries.append(pub + '.dll')
return publicized_libraries, removed_libraries
os.makedirs(f"{tdir}/downloads/unpack", exist_ok=True)
os.makedirs(options.reference, exist_ok=True)
packages, libraries, removed_libraries, publicized_libraries, sources = parse_csproj(options.csproj, options.verbose)
if publicized_libraries and not options.publicizer:
print("This .csproj requires publicizing assemblies, but you have not provided a path to AssemblyPublicizer")
exit(1)
publicizer = makePublicizer(options.publicizer, verbose) if publicized_libraries else None
if options.download_libs:
for package in packages:
downloadLib(package.name, package.version, package.destination, verbose)
unpackLib(package.destination, f"{tdir}/downloads/unpack", verbose)
time.sleep(1)
run_command(f"chmod -R +r {tdir}/downloads/unpack", verbose)
run_command(f"cp -r {tdir}/downloads/unpack/ref/net472/* {options.reference}", verbose)
run_command(f"cp -r {tdir}/downloads/unpack/lib/net472/* {options.reference}", verbose)
libraries = [resolveLibrary(l, options.reference, options.csproj, verbose) for l in libraries if '$' not in l.name]
if options.all_libs:
libraries.extend(os.path.join(options.reference, ref) for ref in os.listdir(options.reference) if ref.endswith(".dll"))
for p in publicized_libraries:
for l in libraries:
if os.path.basename(l) == p.inputName:
libraries.append(publicize(l, publicizer, verbose))
break
libraries = [l for l in set(libraries) if os.path.basename(l) not in removed_libraries]
args.extend([f'-out:{options.output}', *sources, *[f'-r:{r}' for r in libraries]])
args.extend(rest)
if options.refout:
args.extend([f"-refout:{options.refout}"])
if options.debug:
args.append('-define:DEBUG')
if verbose > 2:
print(libraries)
if verbose > 6:
print(args)
os.execvp(args[0], args)
I'm not seeing a major advantage here... Both os.system and subprocess.run(... shell=True) are subject to the same shell interpretation issues. It would be better to move away from os.system, and away from using wget to do the actual file downloads, as well as potentially using python's builtin zipfile to extract the results without cluttering up a scratch space, but the only difference between subprocess.run with a shell and os.system is os.system ignores SIGINT and SIGQUIT, letting the child crash as a result and then handling the error. Maybe it would be better for SIGINT to immediately stop Make.py, but that seems like a fairly small gain.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
#!/usr/bin/python3
import os
import sys
import argparse
import subprocess
from xml.dom.minidom import parse as XMLOpen
import tempfile
import time
from dataclasses import dataclass
tdir = tempfile.gettempdir()
args = ["csc", "-unsafe", "-warnaserror", "-nostdlib", "-target:library"]
@DataClass
class PackageReference:
name: str
version: str
destination: str
@DataClass
class LibraryReference:
name: str
hintPath: str
@DataClass
class PublicizeTarget:
inputName: str
outputName: str
def parseArgs(argv):
argParser = argparse.ArgumentParser(description="Automate compiling C# projects on Linux, using Mono")
argParser.add_argument("--reference", type=str, metavar="PATH", help="Location of Rimworld Reference libraries")
argParser.add_argument("--output", "-o", type=str, default="Assemblies/CombatExtended.dll", help="Output filename")
argParser.add_argument("--csproj", type=str, default="Source/CombatExtended/CombatExtended.csproj", help="C# project to build.")
argParser.add_argument("--all-libs", action="store_true", default=False, help="Reference all dlls in $reference, even those not specified by the .csproj file")
argParser.add_argument("--verbose", "-v", action="count", default=0, help="Increase verbosity, specify more times for more verbosity")
argParser.add_argument("--download-libs", action="store_true", default=False, help=f"Automatically download referenced packages to {tdir}/downloads and unpack them to $reference")
argParser.add_argument("--publicizer", type=str, metavar="PATH", help="Location of AssemblyPublicizer source code or parent directory of AssemblyPublicizer.exe")
argParser.add_argument("--debug", action="store_true", default=False, help="Define
DEBUG
when calling csc")argParser.add_argument("--refout", metavar="PATH", default=None, help="Specify where to save a reference library")
def run_command(command, verbose):
if verbose:
print(f"Running command: {command}")
result = subprocess.run(command, shell=True)
if result.returncode != 0:
raise Exception(f"Command failed: {command}")
def downloadLib(name, version, dest, verbose):
if verbose:
print(f"Downloading {version} of {name} from nuget")
quiet = "-q" if verbose < 5 else ""
run_command(f"wget {quiet} https://www.nuget.org/api/v2/package/{name}/{version} -O {dest}", verbose)
def unpackLib(path, dest, verbose):
quiet = "-q" if verbose < 6 else ""
run_command(f"unzip {quiet} -o {path} -d {dest}", verbose)
def resolveLibrary(library, reference, csproj, verbose):
if library.hintPath:
if verbose > 1:
print(f"Adding library via hintPath: {library.hintPath}")
base_dir = os.path.split(csproj)[0]
t_path = os.path.join(base_dir, library.hintPath)
if os.path.isabs(library.hintPath):
return os.path.abspath(t_path)
else:
return os.path.relpath(t_path)
if verbose > 1:
print(f"Searching for library named {library.name}.dll")
return os.path.join(reference, library.name + '.dll')
def publicize(p, publicizer, verbose):
if verbose:
print(f"Publicizing Assembly: {p}")
cwd = os.getcwd()
os.chdir(os.path.dirname(p))
run_command(f"mono {publicizer} --exit -i {p} -o {p[:-4]}_publicized.dll", verbose)
os.chdir(cwd)
return p[:-4] + '_publicized.dll'
def makePublicizer(p, verbose):
ap_path = os.path.join(p, "AssemblyPublicizer.exe")
def parse_packages(csproj):
packages = []
for idx, package in enumerate(csproj.getElementsByTagName("PackageReference")):
name = package.attributes['Include'].value
version = package.attributes['Version'].value
dest = f"{tdir}/downloads/rwref-{idx}.zip"
packages.append(PackageReference(name, version, dest))
return packages
def parse_libraries(csproj):
libraries = []
removed_libraries = []
for reference in csproj.getElementsByTagName("Reference"):
hintPath = reference.getElementsByTagName("HintPath")
if 'Remove' in reference.attributes:
assemblyName = reference.attributes['Remove'].value.rsplit('\', 1)[1]
removed_libraries.append(assemblyName)
continue
if hintPath:
hintPath = hintPath[0].firstChild.data.strip().replace("\", "/")
libraries.append(LibraryReference(reference.attributes['Include'].value, hintPath))
return libraries, removed_libraries
def parse_sources(csproj, base_dir, verbose):
sources = []
for d, _, files in os.walk(base_dir):
sources.extend(os.path.join(d, f).split(base_dir + '/', 1)[1] for f in files if f.endswith('.cs'))
def parse_publicize(csproj):
publicized_libraries = []
removed_libraries = []
publicize_task = [i for i in csproj.getElementsByTagName("Target") if 'Name' in i.attributes and i.attributes['Name'].value.startswith('Publi')]
variables = {}
def parse_csproj(csproj_path, verbose):
base_dir = os.path.split(csproj_path)[0]
if verbose:
print(f"Parsing .csproj: {csproj_path}")
with XMLOpen(csproj_path) as csproj:
packages = parse_packages(csproj)
libraries, removed_libraries = parse_libraries(csproj)
sources = parse_sources(csproj, base_dir, verbose)
publicized_libraries, removed_libraries_2 = parse_publicize(csproj)
removed_libraries.extend(removed_libraries_2)
return packages, libraries, removed_libraries, publicized_libraries, sources
def main(argv=sys.argv):
rest = []
if '--' in argv:
_idx = argv.index('--')
argv, rest = argv[:_idx], argv[_idx + 1:]
options = parseArgs(argv)
verbose = options.verbose
if name == 'main':
main(sys.argv)
Beta Was this translation helpful? Give feedback.
All reactions