You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
15 KiB
317 lines
15 KiB
;; DataBundle.cake: Bundle data into the executable to avoid loose files
|
|
;; Important caveat: The bundled data is not null-terminated, so don't just go printing it as a
|
|
;; string or you'll get garbage at the end
|
|
;; Define 'ARM-arch64 for ARM 64 platforms.
|
|
(import "CHelpers.cake" "CppHelpers.cake" "ComptimeHelpers.cake" "BuildTools.cake")
|
|
|
|
(c-import &with-decls "<stdbool.h>")
|
|
|
|
;; Do not change these arguments without also changing usage of them in data-bundle-invocations
|
|
(defmacro bundle-file (start-var-name symbol end-var-name symbol var-base-type array
|
|
filename-token string)
|
|
(when (and (= TokenType_Symbol (field (at 1 var-base-type) type))
|
|
(std-str-equals (field (at 1 var-base-type) contents) "addr"))
|
|
(ErrorAtToken (at 1 var-base-type)
|
|
"bundled file variable type must not be a pointer. It will be automatically pointerized")
|
|
(return false))
|
|
|
|
(var filename (addr (const char)) (call-on c_str (path filename-token > contents)))
|
|
(get-or-create-comptime-var environment files-to-bundle (template (in std vector) (in std string)))
|
|
(when (= (call-on-ptr end files-to-bundle)
|
|
(FindInContainer (deref files-to-bundle) filename))
|
|
(call-on-ptr push_back files-to-bundle filename))
|
|
|
|
(comptime-cond
|
|
('Unix
|
|
(var data-var-name (array 1024 char) (array 0))
|
|
(var data-var-name-write (addr char) data-var-name)
|
|
(strncpy data-var-name "_binary_" (array-size data-var-name))
|
|
(set data-var-name-write
|
|
(+ data-var-name-write (strlen data-var-name)))
|
|
|
|
(each-char-in-string-const filename current-char
|
|
(if (isalnum (deref current-char))
|
|
(set (deref data-var-name-write) (deref current-char))
|
|
(set (deref data-var-name-write) '_'))
|
|
(incr data-var-name-write))
|
|
(set (deref data-var-name-write) 0)
|
|
|
|
(var data-var-name-start-token Token (deref start-var-name))
|
|
(token-contents-snprintf data-var-name-start-token "%s_start" data-var-name)
|
|
(var data-var-name-end-token Token (deref end-var-name))
|
|
(token-contents-snprintf data-var-name-end-token "%s_end" data-var-name)
|
|
|
|
(tokenize-push output
|
|
(declare-external (var (token-splice-addr data-var-name-start-token)
|
|
(token-splice var-base-type)))
|
|
(declare-external (var (token-splice-addr data-var-name-end-token)
|
|
(token-splice var-base-type)))
|
|
(var (token-splice start-var-name) (addr (token-splice var-base-type))
|
|
(addr (token-splice-addr data-var-name-start-token)))
|
|
(var (token-splice end-var-name) (addr (token-splice var-base-type))
|
|
(addr (token-splice-addr data-var-name-end-token))))
|
|
(return true))
|
|
('Windows
|
|
;; We need to find the variable address at runtime
|
|
(tokenize-push output
|
|
(var-global (token-splice start-var-name) (addr (token-splice var-base-type)) null)
|
|
(var-global (token-splice end-var-name) (addr (token-splice var-base-type)) null))
|
|
;; Bundle more information to use later
|
|
(get-or-create-comptime-var environment data-bundle-invocations (template (in std vector) (addr (const Token))))
|
|
(when (= (call-on-ptr end data-bundle-invocations)
|
|
(FindInContainer (deref data-bundle-invocations) start-var-name))
|
|
(call-on-ptr push_back data-bundle-invocations start-var-name))
|
|
(return true)))
|
|
(return false))
|
|
|
|
(defun-comptime data-bundle-create-safe-resource-name (resource-filename (addr (const char))
|
|
out-buffer (addr char) out-buffer-size size_t)
|
|
(var buffer-write (addr char) out-buffer)
|
|
(each-char-in-string-const resource-filename current-char
|
|
(when (isalnum (deref current-char))
|
|
(set (deref buffer-write) (deref current-char))
|
|
(incr buffer-write)
|
|
(when (>= (- buffer-write out-buffer) (- out-buffer-size 1))
|
|
(break))))
|
|
(set (deref buffer-write) 0))
|
|
|
|
(defun-comptime convert-all-bundle-files (manager (ref ModuleManager) module (addr Module) &return bool)
|
|
(var environment (ref EvaluatorEnvironment) (field manager environment))
|
|
|
|
(get-or-create-comptime-var environment data-bundle-load-all-called bool false)
|
|
(unless (deref data-bundle-load-all-called)
|
|
(Log "error: call data-bundle-load-all-resources somewhere in your program initialization in " \
|
|
"order to properly load bundled data on all platforms\n")
|
|
(return false))
|
|
|
|
(get-or-create-comptime-var environment files-to-bundle (template (in std vector) (in std string)))
|
|
(when (call-on-ptr empty files-to-bundle)
|
|
(return true))
|
|
|
|
(var results (template (in std vector) int))
|
|
(call-on resize results (call-on-ptr size files-to-bundle))
|
|
(var current-result (addr int) (addr (at 0 results)))
|
|
|
|
(each-in-range (call-on-ptr size files-to-bundle) i
|
|
(var filename (ref (const (in std string))) (at i (deref files-to-bundle)))
|
|
(var cache-filename (array 1024 char) (array 0))
|
|
(unless (outputFilenameFromSourceFilename
|
|
(call-on c_str (field manager buildOutputDir))
|
|
(call-on c_str filename)
|
|
;; Add to end of file for type. Add bundle in case it's a cpp that we're also building,
|
|
;; to prevent name collisions in the cache
|
|
(comptime-cond ('Unix "data.o") ('Windows "data.res"))
|
|
cache-filename (sizeof cache-filename))
|
|
(return false))
|
|
|
|
(call-on push_back (field manager environment additionalStaticLinkObjects) cache-filename)
|
|
|
|
;; Don't re-package the resource if unchanged
|
|
(unless (fileIsMoreRecentlyModified (call-on c_str filename) cache-filename)
|
|
(continue))
|
|
|
|
(Logf "Bundle filename to: %s => %s\n" (call-on c_str filename) cache-filename)
|
|
|
|
(var output-type (addr (const char)) "elf64-x86-64")
|
|
(var binary-architecture (addr (const char)) "i386")
|
|
(comptime-cond
|
|
('ARM-arch64
|
|
(set output-type "elf64-littleaarch64")
|
|
(set binary-architecture "aarch64")))
|
|
|
|
(comptime-cond
|
|
('Unix
|
|
(run-process-start-or current-result
|
|
("objcopy" "--input" "binary" "--output" output-type
|
|
"--binary-architecture" binary-architecture
|
|
(call-on c_str filename) cache-filename)
|
|
(return false)))
|
|
('Windows
|
|
;; I'm not actually sure how long these can be!
|
|
(var safe-resource-name (array 1024 char) (array 0))
|
|
(data-bundle-create-safe-resource-name
|
|
(call-on c_str filename) safe-resource-name (array-size safe-resource-name))
|
|
;; Note that while we could bundle all our resources into a single RC file, we'd rather build
|
|
;; them separately so they don't all have to be recompiled if one changes
|
|
(var resource-definition-file (array 1024 char) (array 0))
|
|
(unless (outputFilenameFromSourceFilename
|
|
(call-on c_str (field manager buildOutputDir))
|
|
(call-on c_str filename)
|
|
;; Add to end of file for type. Add bundle in case it's a cpp that we're also building,
|
|
;; to prevent name collisions in the cache
|
|
"rc"
|
|
resource-definition-file (sizeof resource-definition-file))
|
|
(return false))
|
|
(scope ;; Write the resource definition
|
|
(var resource-file-ptr (addr FILE) (fopen resource-definition-file "wb"))
|
|
(unless resource-file-ptr
|
|
(Logf "error: failed to open resource file %s for writing\n" resource-definition-file)
|
|
(return false))
|
|
;; Arbitrary: "CUSTOMDATA" userdata type
|
|
(fprintf resource-file-ptr "%s CUSTOMDATA \"%s\"" safe-resource-name (call-on c_str filename))
|
|
(fclose resource-file-ptr))
|
|
(comptime-cond
|
|
('Cross-Compiling-From-Linux ;; Cross compiling
|
|
(run-process-start-or current-result
|
|
("wrc" "-m64" "-o" cache-filename "-i" resource-definition-file)
|
|
(Log "error: failed to run Wine Resource Compiler, a.k.a. wrc. Please install Wine to " \
|
|
"cross-compile resource files.\n")
|
|
(return false)))
|
|
(true
|
|
(var rc-executable (array 1024 char) (array 0))
|
|
(unless (resolveExecutablePath "rc.exe" rc-executable (array-size rc-executable))
|
|
(Log "error: failed to find rc.exe\n")
|
|
(return false))
|
|
(run-process-start-or current-result
|
|
(rc-executable
|
|
"/nologo" "/fo" cache-filename resource-definition-file)
|
|
(Log "error: failed to run Resource Compiler\n")
|
|
(return false))))))
|
|
(incr current-result))
|
|
|
|
(waitForAllProcessesClosed null)
|
|
(var result-index int 0)
|
|
(for-in result bool results
|
|
(unless (= 0 result)
|
|
(Logf "error: failed to bundle file %s\n" (call-on c_str (at result-index (deref files-to-bundle))))
|
|
(return false))
|
|
(incr result-index))
|
|
|
|
(return true))
|
|
|
|
(add-compile-time-hook-module pre-build convert-all-bundle-files)
|
|
|
|
(comptime-cond
|
|
('Windows
|
|
(defun-comptime bundle-generate-runtime-load (environment (ref EvaluatorEnvironment) &return bool)
|
|
(scope
|
|
(get-or-create-comptime-var environment bundle-files-processed bool false)
|
|
(when (deref bundle-files-processed)
|
|
(return true))
|
|
(set (deref bundle-files-processed) true))
|
|
|
|
(get-or-create-comptime-var environment files-to-bundle (template (in std vector) (in std string)))
|
|
(get-or-create-comptime-var environment data-bundle-invocations (template (in std vector) (addr (const Token))))
|
|
(var load-resources-body (addr (template std::vector Token)) (new (template std::vector Token)))
|
|
(call-on push_back (field environment comptimeTokens) load-resources-body)
|
|
|
|
(each-in-range (call-on-ptr size data-bundle-invocations) i
|
|
;; Generate the runtime code for finding the resource
|
|
(var start-var-name-token (addr (const Token)) (at i (deref data-bundle-invocations)))
|
|
(var end-var-name-token (addr (const Token)) (+ start-var-name-token 1))
|
|
(var base-type-start-token (addr (const Token)) (+ start-var-name-token 2))
|
|
(var type-token (addr (const Token)) (+ start-var-name-token 3))
|
|
(var resource-name-token Token (deref start-var-name-token))
|
|
(set (field resource-name-token type) TokenType_String)
|
|
(var safe-resource-name (array 1024 char) (array 0))
|
|
(scope
|
|
(var filename (ref (const (in std string))) (at i (deref files-to-bundle)))
|
|
(data-bundle-create-safe-resource-name
|
|
(call-on c_str filename) safe-resource-name (array-size safe-resource-name)))
|
|
(set (field resource-name-token contents) safe-resource-name)
|
|
(tokenize-push (deref load-resources-body)
|
|
(scope
|
|
(var resource data-bundle-resource
|
|
(data-bundle-get-resource (token-splice-addr resource-name-token)))
|
|
(unless (field resource start-resource)
|
|
(return false))
|
|
(declare-external (var (token-splice start-var-name-token)
|
|
(addr (token-splice base-type-start-token))))
|
|
(declare-external (var (token-splice end-var-name-token)
|
|
(addr (token-splice base-type-start-token))))
|
|
(set (token-splice start-var-name-token)
|
|
(type-cast (field resource start-resource) (addr (token-splice base-type-start-token))))
|
|
(set (token-splice end-var-name-token)
|
|
(type-cast (field resource end-resource) (addr (token-splice base-type-start-token)))))))
|
|
|
|
(var load-all-resources-function (addr (template std::vector Token)) (new (template std::vector Token)))
|
|
(call-on push_back (field environment comptimeTokens) load-all-resources-function)
|
|
(tokenize-push (deref load-all-resources-function)
|
|
(defun data-bundle-load-all-resources-implementation (&return bool)
|
|
(token-splice-array (deref load-resources-body))
|
|
(return true)))
|
|
(unless (ReplaceAndEvaluateDefinition environment
|
|
"data-bundle-load-all-resources-implementation"
|
|
(deref load-all-resources-function))
|
|
(return false))
|
|
(return true))
|
|
(add-compile-time-hook post-references-resolved bundle-generate-runtime-load)))
|
|
|
|
(defstruct-local data-bundle-resource
|
|
start-resource (addr void)
|
|
end-resource (addr void))
|
|
|
|
(comptime-cond
|
|
('Windows
|
|
(import "WindowsHeader.cake")
|
|
(c-import "<stdio.h>")
|
|
|
|
(defun-local data-bundle-get-resource (resource-name (addr (const char))
|
|
&return data-bundle-resource)
|
|
;; TODO: This will break if we're loading from a DLL!
|
|
(var h-module HMODULE null)
|
|
(var final-resource data-bundle-resource (array 0))
|
|
(var resource-info HRSRC
|
|
(FindResourceA h-module resource-name "CUSTOMDATA"))
|
|
(unless resource-info
|
|
(fprintf stderr "Could not find resource %s\n" resource-name)
|
|
(return final-resource))
|
|
|
|
(var resource HGLOBAL (LoadResource h-module resource-info))
|
|
(unless resource
|
|
(fprintf stderr "Could not load resource %s\n" resource-name)
|
|
(return final-resource))
|
|
|
|
(var resource-address LPVOID (LockResource resource))
|
|
(unless resource-address
|
|
(fprintf stderr "Could not lock resource %s\n" resource-name)
|
|
(return final-resource))
|
|
(var resource-size DWORD (SizeofResource h-module resource-info))
|
|
|
|
(set (field final-resource start-resource) (type-cast resource-address (addr void)))
|
|
(set (field final-resource end-resource) (+ (type-cast resource-address (addr char))
|
|
resource-size))
|
|
(return final-resource))))
|
|
|
|
(comptime-cond
|
|
('Windows
|
|
(defun-local enumerate-resource (h-module HMODULE type LPCSTR name LPSTR userdata LONG_PTR)
|
|
(fprintf stderr "%s %s\n" name type))
|
|
(defun-local data-bundle-list-resources ()
|
|
(fprintf stderr "Available resources:\n")
|
|
;; TODO: This will break if we're loading from a DLL!
|
|
(var h-module HMODULE null)
|
|
(EnumResourceNamesA h-module "CUSTOMDATA" (type-cast enumerate-resource ENUMRESNAMEPROCA)
|
|
(type-cast null LONG_PTR)))))
|
|
|
|
;; Replaced at compile-time on Windows. Use data-bundle-load-all-resources to invoke this so the
|
|
;; system can easily know it is called
|
|
(defun data-bundle-load-all-resources-implementation (&return bool)
|
|
(return true))
|
|
|
|
(defmacro data-bundle-load-all-resources ()
|
|
(get-or-create-comptime-var environment data-bundle-load-all-called bool false)
|
|
(set (deref data-bundle-load-all-called) true)
|
|
(tokenize-push output (data-bundle-load-all-resources-implementation))
|
|
(return true))
|
|
|
|
(comptime-cond
|
|
('auto-test
|
|
(c-import "<stdio.h>")
|
|
|
|
;; Bundle our own source code
|
|
(bundle-file data-start data-end (const char)
|
|
"../src/DataBundle.cake")
|
|
(defun-nodecl test--bundle-file (&return int)
|
|
(unless (data-bundle-load-all-resources)
|
|
(fprintf stderr "error: Could not load bundled data\n")
|
|
(comptime-cond ;; List resources if possible
|
|
('Windows (data-bundle-list-resources)))
|
|
(return 1))
|
|
(unless (and data-start data-end)
|
|
(fprintf stderr "error: Bundled data pointers null\n")
|
|
(return 1))
|
|
(fprintf stderr "Bundled data:\n")
|
|
;; We can't simply fprintf it because it's not null-terminated
|
|
(fwrite data-start (- data-end data-start) 1 stderr)
|
|
(return 0))))
|
|
|