Dynamic-Pokemon-Expansion/scripts/build.py
2019-06-11 02:18:54 -04:00

349 lines
10 KiB
Python

#!/usr/bin/env python3
from glob import glob
from pathlib import Path
import os
import itertools
import hashlib
import subprocess
import sys
from datetime import datetime
from string import StringFileConverter
PathVar = os.environ.get('Path')
Paths = PathVar.split(';')
PATH = ""
for candidatePath in Paths:
if "devkitARM" in candidatePath:
PATH = candidatePath
break
if PATH == "":
print('DevKit does not exist in your Path variable.\nChecking default location.')
PATH = 'C://devkitPro//devkitARM//bin'
if os.path.isdir(PATH) == False:
print("...\nDevkit not found.")
sys.exit(1)
else:
print("Devkit found.")
PREFIX = '/arm-none-eabi-'
AS = (PATH + PREFIX + 'as')
CC = (PATH + PREFIX + 'gcc')
LD = (PATH + PREFIX + 'ld')
GR = ("deps/grit.exe")
WAV2AGB = ("deps/wav2agb.exe")
ARP = ('armips')
OBJCOPY = (PATH + PREFIX + 'objcopy')
SRC = './src'
GRAPHICS = './graphics'
ASSEMBLY = './assembly'
STRINGS = './strings'
AUDIO = './audio'
BUILD = './build'
IMAGES = '\Images'
ASFLAGS = ['-mthumb', '-I', ASSEMBLY]
LDFLAGS = ['BPRE.ld', '-T', 'linker.ld']
CFLAGS = ['-mthumb', '-mno-thumb-interwork', '-mcpu=arm7tdmi', '-mtune=arm7tdmi',
'-mno-long-calls', '-march=armv4t', '-Wall', '-Wextra','-Os', '-fira-loop-pressure', '-fipa-pta']
PrintedCompilingAudio = False #Used to tell the script whether or not the strings "Compiling Cries" has been printed
def run_command(cmd):
try:
subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
print(e.output.decode(), file = sys.stderr)
sys.exit(1)
def make_output_file(filename):
'''Return hash of filename to use as object filename'''
m = hashlib.md5()
m.update(filename.encode())
newfilename = os.path.join(BUILD, m.hexdigest() + '.o')
if not os.path.isfile(filename):
return [newfilename, False]
fileExists = os.path.isfile(newfilename)
if fileExists and os.path.getmtime(newfilename) > os.path.getmtime(filename): #If the object file was created after the file was last modified
return [newfilename, False]
return [newfilename, True]
def make_output_img_file(filename):
'''Return "IMG" + hash of filename to use as object filename'''
if "frontspr" in filename:
newfilename = os.path.join(BUILD, 'IMG_Front' + filename.split("Sprite")[1].split(".s")[0] + '.o')
elif "backspr" in filename:
newfilename = os.path.join(BUILD, 'IMG_Back' + filename.split("Sprite")[1].split(".s")[0] + '.o')
else:
m = hashlib.md5()
m.update(filename.encode())
newfilename = os.path.join(BUILD, 'IMG_' + m.hexdigest() + '.o')
if not os.path.isfile(filename):
return [newfilename, False]
fileExists = os.path.isfile(newfilename)
if fileExists and os.path.getmtime(newfilename) > os.path.getmtime(filename): #If the object file was created after the file was last modified
return [newfilename, False]
return [newfilename, True]
def make_output_audio_file(filename):
'''Return "AUDIO" + hash of filename to use as object filename'''
newfilename = os.path.join(BUILD, 'SND_' + filename.split("gCry")[1].split(".s")[0] + '.o')
if not os.path.isfile(filename):
return [newfilename, False]
fileExists = os.path.isfile(newfilename)
if fileExists and os.path.getmtime(newfilename) > os.path.getmtime(filename): #If the object file was created after the file was last modified
return [newfilename, False]
return [newfilename, True]
def process_assembly(in_file):
'''Assemble'''
out_file_list = make_output_file(in_file)
out_file = out_file_list[0]
if out_file_list[1] is False:
return out_file #No point in recompiling file
try:
print ('Assembling %s' % in_file)
cmd = [AS] + ASFLAGS + ['-c', in_file, '-o', out_file]
run_command(cmd)
except FileNotFoundError:
print('Error! The assembler could not be located.\nAre you sure you set up your path to devkitPro/devkitARM/bin correctly?')
sys.exit()
return out_file
def process_c(in_file):
'''Compile C'''
out_file_list = make_output_file(in_file)
out_file = out_file_list[0]
if out_file_list[1] is False:
return out_file #No point in recompiling file
try:
print ('Compiling %s' % in_file)
cmd = [CC] + CFLAGS + ['-c', in_file, '-o', out_file]
run_command(cmd)
except FileNotFoundError:
print('Error! The C compiler could not be located.\nAre you sure you set up your path to devkitPro/devkitARM/bin correctly?')
sys.exit()
return out_file
def process_string(filename):
'''Build Strings'''
out_file = filename.split(".string")[0] + '.s'
object_file = make_output_file(out_file)[0]
fileExists = os.path.isfile(object_file)
if fileExists and os.path.getmtime(object_file) > os.path.getmtime(filename): #If the .o file was created after the image was last modified
return make_output_file(out_file)[0]
print ('Building Strings %s' % filename)
StringFileConverter(filename)
out_file_list = make_output_file(out_file)
new_out_file = out_file_list[0]
if out_file_list[1] == False:
os.remove(out_file)
return new_out_file #No point in recompiling file
cmd = [AS] + ASFLAGS + ['-c', out_file, '-o', new_out_file]
run_command(cmd)
os.remove(out_file)
return new_out_file
def ProcessSpriteGraphics():
with open(GRAPHICS + "/backspriteflags.grit", "r") as file:
for line in file:
backflags = line.split()
break
with open(GRAPHICS + "/frontspriteflags.grit", "r") as file:
for line in file:
frontflags = line.split()
break
with open(GRAPHICS + "/iconspriteflags.grit", "r") as file:
for line in file:
iconflags = line.split()
break
try:
os.makedirs(SRC + "/generated")
except FileExistsError:
pass
backsprites = [file for file in glob(GRAPHICS + "/backspr" + "**/*.png", recursive=True)]
frontsprites = [file for file in glob(GRAPHICS + "/frontspr" + "**/*.png", recursive=True)]
iconsprites = [file for file in glob(GRAPHICS + "/pokeicon" + "**/*.png", recursive=True)]
print("Processing Front Sprites")
combinedFile = open(os.path.join('SRC', 'generated', 'frontsprites.s'), 'w')
for sprite in frontsprites:
assembled = sprite.split('.png')[0] + '.s'
if (not os.path.isfile(assembled)
or os.path.getmtime(sprite) > os.path.getmtime(assembled)):
run_command([GR, sprite] + frontflags + ['-o', assembled])
with open(assembled, 'r') as tempFile:
combinedFile.write(tempFile.read())
combinedFile.close()
print("Processing Back Sprites")
combinedFile = open(os.path.join('SRC', 'generated', 'backsprites.s'), 'w')
for sprite in backsprites:
assembled = sprite.split('.png')[0] + '.s'
if (not os.path.isfile(assembled)
or os.path.getmtime(sprite) > os.path.getmtime(assembled)):
run_command([GR, sprite] + backflags + ['-o', assembled])
with open(assembled, 'r') as tempFile:
combinedFile.write(tempFile.read())
combinedFile.close()
print("Processing Icon Sprites")
combinedFile = open(os.path.join('SRC', 'generated', 'iconsprites.s'), 'w')
for sprite in iconsprites:
assembled = sprite.split('.png')[0] + '.s'
if (not os.path.isfile(assembled)
or os.path.getmtime(sprite) > os.path.getmtime(assembled)):
run_command([GR, sprite] + iconflags + ['-o', assembled])
with open(assembled, 'r') as tempFile:
combinedFile.write(tempFile.read())
combinedFile.close()
def process_audio(in_file):
'''Compile Audio'''
out_file = in_file.split('.wav')[0] + '.s'
cmd = [WAV2AGB, in_file] + [out_file, '-c']
out_file_list = make_output_audio_file(out_file)
new_out_file = out_file_list[0]
try:
if os.path.getmtime(new_out_file) > os.path.getmtime(in_file): #If the .o file was created after the image was last modified
return new_out_file
else:
run_command(cmd)
except FileNotFoundError:
run_command(cmd) #No .o file has been created
global PrintedCompilingAudio
if (PrintedCompilingAudio is False):
print ('Compiling Cries')
PrintedCompilingAudio = True
out_file_list = make_output_audio_file(out_file)
new_out_file = out_file_list[0]
if out_file_list[1] == False:
os.remove(out_file)
return new_out_file #No point in recompiling file
cmd = [AS] + ASFLAGS + ['-c', out_file, '-o', new_out_file]
run_command(cmd)
os.remove(out_file)
return new_out_file
def link(objects):
'''Link objects into one binary'''
linked = 'build/linked.o'
cmd = [LD] + LDFLAGS + ['-o', linked] + list(objects)
run_command(cmd)
return linked
def objcopy(binary):
cmd = [OBJCOPY, '-O', 'binary', binary, 'build/output.bin']
run_command(cmd)
def run_glob(globstr, fn):
'''Glob recursively and run the processor function on each file in result'''
if globstr == '**/*.png' or globstr == '**/*.bmp': #Search the graphics location
return run_glob_graphics(globstr, fn)
elif globstr == '**/*.wav':
return run_glob_audio(globstr, fn)
if sys.version_info > (3, 4):
files = glob(os.path.join(SRC, globstr), recursive = True)
return map(fn, files)
else:
files = Path(SRC).glob(globstr)
return map(fn, map(str, files))
def run_glob_graphics(globstr, fn):
'''Glob recursively and run the processor function on each file in result'''
if sys.version_info > (3, 4):
files = glob(os.path.join(GRAPHICS, globstr), recursive = True)
return map(fn, files)
else:
files = Path(GRAPHICS).glob(globstr)
return map(fn, map(str, files))
def run_glob_audio(globstr, fn):
'''Glob recursively and run the processor function on each file in result'''
if sys.version_info > (3, 4):
files = glob(os.path.join(AUDIO, globstr), recursive = True)
return map(fn, files)
else:
files = Path(AUDIO).glob(globstr)
return map(fn, map(str, files))
def main():
starttime = datetime.now()
globs = {
'**/*.s': process_assembly,
'**/*.c': process_c,
'**/*.string': process_string,
#'**/*.png': process_image,
#'**/*.bmp': process_image,
'**/*.wav': process_audio,
}
# Create output directory
try:
os.makedirs(BUILD)
except FileExistsError:
pass
ProcessSpriteGraphics()
# Gather source files and process them
objects = itertools.starmap(run_glob, globs.items())
# Link and extract raw binary
linked = link(itertools.chain.from_iterable(objects))
objcopy(linked)
#Build special_inserts.asm
if not os.path.isfile('build/special_inserts.bin') or os.path.getmtime('build/special_inserts.bin') < os.path.getmtime('special_inserts.asm'): #If the binary file was created after the file was last modified):
cmd = cmd = [AS] + ASFLAGS + ['-c', 'special_inserts.asm', '-o', 'build/special_inserts.o']
run_command(cmd)
cmd = [OBJCOPY, '-O', 'binary', 'build/special_inserts.o', 'build/special_inserts.bin']
run_command(cmd)
print ('Assembling special_inserts.asm')
print('Built in ' + str(datetime.now() - starttime) + '.')
if __name__ == '__main__':
main()