AbePralle-FGB/Build.rogue

376 lines
12 KiB
Plaintext

# To run this build file, install Rogue from github.com/AbePralle/Rogue and type "rogo" at the command line.
#$ LIBRARIES(macOS) = rgbds( exe:rgbasm )
# description()s are optional - Rogo uses introspection to determine which commands are available.
# 'rogo help default' displays the description for "default", etc.
description( "default", "The default action is performed when no other actions are specified. Use 'rogo default' to explicitly perform the default option." )
description( "help", "Displays a list of all actions that can be performed by Rogo." )
augment Build
PROPERTIES
# These properties can be overridden with a Local.mk setting ROM_NAME=OtherName etc.
ROM_NAME = "FGB.gb"
endAugment
routine rogo_default
rogo_build
endRoutine
routine rogo_build
local rgbasm = System.find_executable( "rgbasm" )
if (not rgbasm)
throw Error( "Please install the RGBDS Game Boy assembler from https://github.com/rednex/rgbds" )
endIf
File( "Build" ).create_folder
File( "ROM" ).create_folder
local build_output = "Build/" + Build.ROM_NAME
local rom_output = "ROM/" + Build.ROM_NAME
local newest_datafile_timestamp = 0.0
forEach (datafile in File("Data/**").listing(&files,&ignore_hidden))
newest_datafile_timestamp = newest_datafile_timestamp.or_larger( File(datafile).timestamp_ms )
endForEach
local obj_files = String[]
local any_new_obj_files = false
forEach (asm_file in File("Source/**/*.asm").listing)
local obj_file = "Build/$.obj" (File(asm_file).filename.leftmost(-4))
if (File(asm_file).is_newer_than(obj_file) or newest_datafile_timestamp > File(obj_file).timestamp_ms)
execute "rgbasm -p 0xff $ -o $" (asm_file,obj_file)
any_new_obj_files = true
elseIf (File(obj_file).is_newer_than(rom_output))
any_new_obj_files = true
endIf
obj_files.add( obj_file )
endForEach
if (any_new_obj_files)
execute "rgblink --map Build/FGB.map --sym Build/FGB.sym $ -o $" (obj_files.join(" "),build_output)
execute "rgbfix -p 0xff -v $" (build_output)
local file_size = File( build_output ).size
println "> Copy $ -> $ ($ bytes)" (build_output,rom_output,file_size.format(","))
File( build_output ).copy_to( rom_output )
else
println "No changes detected. Output: $ ($ bytes)" (rom_output,File(rom_output).size.format(","))
endIf
endRoutine
routine rogo_clean
verbose_delete( "Build" )
endRoutine
routine verbose_delete( filepath:String )
if (File(filepath).exists)
println "> Delete " + filepath
if (not File(filepath).delete)
println "*** Failed to delete - retrying with sudo"
local cmd = ''sudo rm -rf $'' (File(filepath).esc)
execute cmd
endIf
endIf
endRoutine
routine execute( commands:String, error_message=null:String, &suppress_error )->Logical
forEach (cmd in LineReader(commands))
print( "> " )
println( cmd )
if (0 != System.run(cmd))
if (suppress_error)
return false
else
if (not error_message) error_message = "Build failed."
throw Error( error_message )
endIf
endIf
endForEach
return true
endRoutine
#-------------------------------------------------------------------------------
# Introspection-based Launcher Framework
#-------------------------------------------------------------------------------
# Rogo is a "build your own build system" facilitator. At its core Rogo just
# recompiles build files if needed and then runs the build executable while
# forwarding any command line arguments. This file contains a default framework
# which uses introspection to turn command line arguments into parameterized
# routine calls.
# Example: to handle the command "rogo abc xyz 5", define
# "routine rogo_abc_xyz( n:Int32 )".
# "rogo_default" will run in the absence of any other command line argument.
# The following "comment directives" can be used in this file to control how
# RogueC compiles it and to manage automatic dependency installation and
# linking.
# Each of the following should be on a line beginning with the characters #$
# (preceding whitespace is fine). Sample args are given.
# ROGUEC = roguec # Path to roguec to compile this file with
# ROGUEC_ARGS = --whatever # Additional options to pass to RogueC
# CC = gcc -Wall -fno-strict-aliasing
# CC_ARGS = -a -b -c # Additional C args
# LINK = -lalpha -lbeta # Link this build file with these options
# LINK(macOS) = ... # Options applying only to
# # System.os=="macOS" (use with any OS and
# # any comment directive)
# LINK_LIBS = true # Links following LIBRARIES with this Build
# # file (otherwise just installs them)
# LINK_LIBS = false # Linking turned off for following
# # LIBRARIES - info can still be obtained
# # from $LIBRARY_FLAGS or $LIBRARIES(libname,...)
# LIBRARIES = libalpha
# LIBRARIES = libbeta(library-name)
# LIBRARIES = libfreetype6-dev(freetype2)
# DEPENDENCIES = Library/Rogue/**/*.rogue
#
# LIBRARIES = name(package)
# LIBRARIES = name(package:<package> install:<install-cmd>
# link:<link-flags> which:<which-name>)
#
# The following macro is replaced within this file (Build.rogue) - the libraries
# should normally also be declared in #$ LIBRARIES:
#
# $LIBRARY_FLAGS(lib1,lib2) # sample macro
# ->
# -Ipath/to/lib1/include -Lpath/to/lib1/library -I ... # sample replacement
routine syntax( command:String, text:String )
Build.rogo_syntax[ command ] = text
endRoutine
routine description( command:String, text:String )
Build.rogo_descriptions[ command ] = text
endRoutine
routine help( command:String, description_text=null:String, syntax_text=null:String )
if (description_text) description( command, description_text )
if (syntax_text) syntax( command, syntax_text )
endRoutine
try
Build.launch
catch (err:Error)
Build.rogo_error = err
Build.on_error
endTry
class Build [singleton]
PROPERTIES
rogo_syntax = [String:String]
rogo_descriptions = [String:String]
rogo_prefix = "rogo_"
rogo_command = "default"
rogo_args = @[]
rogo_error : Error
LOCAL_SETTINGS_FILE = "Local.settings"
METHODS
method launch
rogo_args.add( forEach in System.command_line_arguments )
read_defs
on_launch
parse_args
dispatch_command
method dispatch_command
local m = find_command( rogo_command )
if (not m) throw Error( "No such routine rogo_$()" (rogo_command) )
local args = @[]
forEach (arg in rogo_args)
which (arg)
case "true": args.add( true )
case "false": args.add( false )
case "null": args.add( null )
others: args.add( arg )
endWhich
endForEach
m( args )
method find_command( name:String )->MethodInfo
return <<Routine>>.find_global_method( rogo_prefix + name )
method on_error
local w = Console.width.or_smaller( 80 )
Console.error.println "=" * w
Console.error.println rogo_error
Console.error.println "=" * w
on_exit
System.exit 1
method on_command_found
noAction
method on_command_not_found
local w = Console.width.or_smaller( 80 )
println "=" * w
println "ERROR: No such command '$'." (rogo_args.first)
println "=" * w
println
rogo_command = "help"
rogo_args.clear
on_command_found
method on_launch
noAction
method on_exit
noAction
method parse_args
block
if (rogo_args.count)
local parts = String[]
parts.add( forEach in rogo_args )
rogo_args.clear
while (parts.count)
local cmd = parts.join("_")
if (find_command(cmd))
rogo_command = cmd
on_command_found
escapeBlock
endIf
rogo_args.insert( parts.remove_last )
endWhile
on_command_not_found
endIf
# Use default command
on_command_found
endBlock
method read_defs
read_defs( LOCAL_SETTINGS_FILE )
method read_defs( defs_filepath:String )
# Attempt to read defs from Local.settings
local overrides = String[]
if (File(defs_filepath).exists)
forEach (line in LineReader(File(defs_filepath)))
if (line.contains("="))
local name = line.before_first('=').trimmed
local value = line.after_first('=').trimmed
if (value.begins_with('"') or value.begins_with('\''))
value = value.leftmost(-1).rightmost(-1)
endIf
local p = <<Build>>.find_property( name )
if (p)
overrides.add( "$ = $" (name,value) )
p.set_value( this, value )
endIf
endIf
endForEach
endIf
method _join( value:Value )->String
local args = String[]
args.add( forEach in value )
return args.join( "_" )
endClass
routine rogo_help( command="":String )
# SYNTAX: rogo help [command]
# Displays help for a specified command or else all build commands.
command = Build._join( Build.rogo_args )
if (command.count)
local syntax = get_syntax( command )
local success = false
if (syntax)
println "SYNTAX"
println " " + syntax
println
success = true
endIf
local description = get_description( command )
if (description)
local max_w = Console.width - 2
println "DESCRIPTION"
forEach (line in LineReader(description.word_wrapped(max_w)))
print( " " )
println( line )
endForEach
println
success = true
endIf
if (success)
return
else
local w = Console.width.or_smaller( 80 )
println "=" * w
println "ERROR: No such command '$'." (command)
println "=" * w
println
endIf
endIf
println "USAGE"
local entries = CommandInfo[]
local max_len = 0
forEach (m in <<Routine>>.global_methods)
if (m.name.begins_with(Build.rogo_prefix))
local name = m.name.after_first( Build.rogo_prefix )
local entry = CommandInfo( name, get_syntax(name), get_description(name) )
max_len .= or_larger( entry.syntax.count )
entries.add entry
endIf
endForEach
entries.sort( $1.name < $2.name )
max_len += 2
local max_w = Console.width
forEach (entry in entries)
print " " + entry.syntax
if (entry.@description)
local description = entry.@description
loop (max_len - entry.syntax.count) print ' '
contingent
sufficient (max_len + description.count <= max_w)
if (description.contains(". "))
description = description.before_first( ". " ) + "."
sufficient (max_len + description.count <= max_w)
endIf
necessary (max_len + 10 <= max_w)
description = description.unright( (description.count - (max_w - max_len))+3 ) + "..."
satisfied
print description
endContingent
endIf
println
endForEach
println
endRoutine
routine get_syntax( m_name:String )->String
if (Build.rogo_syntax.contains(m_name))
return "rogo " + Build.rogo_syntax[ m_name ]
else
local m = <<Routine>>.find_global_method( Build.rogo_prefix + m_name )
if (not m) return null
local line = "rogo $" (m_name.replacing('_',' '))
line += " <$>" ((forEach in m.parameters).name)
return line
endIf
endRoutine
routine get_description( m_name:String )->String
if (Build.rogo_descriptions.contains(m_name))
return Build.rogo_descriptions[ m_name ]
else
return null
endIf
endRoutine
class CommandInfo( name:String, syntax:String, description:String );