Browse Source

Merge branch 'master' into sync-cond-futex

gingerBill 3 years ago
parent
commit
beb698f31d

+ 8 - 3
.github/workflows/ci.yml

@@ -39,7 +39,9 @@ jobs:
           make
           make
         timeout-minutes: 10
         timeout-minutes: 10
       - name: Odin issues tests
       - name: Odin issues tests
-        run: tests/issues/run.sh
+        run: |
+          cd tests/issues
+          ./run.sh
         timeout-minutes: 10
         timeout-minutes: 10
       - name: Odin check examples/all for Linux i386
       - name: Odin check examples/all for Linux i386
         run: ./odin check examples/all -vet -strict-style -target:linux_i386
         run: ./odin check examples/all -vet -strict-style -target:linux_i386
@@ -91,7 +93,9 @@ jobs:
           make
           make
         timeout-minutes: 10
         timeout-minutes: 10
       - name: Odin issues tests
       - name: Odin issues tests
-        run: tests/issues/run.sh
+        run: |
+          cd tests/issues
+          ./run.sh
         timeout-minutes: 10
         timeout-minutes: 10
       - name: Odin check examples/all for Darwin arm64
       - name: Odin check examples/all for Darwin arm64
         run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
         run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
@@ -163,7 +167,8 @@ jobs:
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          call tests\issues\run.bat
+          cd tests\issues
+          call run.bat
         timeout-minutes: 10
         timeout-minutes: 10
       - name: Odin check examples/all for Windows 32bits
       - name: Odin check examples/all for Windows 32bits
         shell: cmd
         shell: cmd

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (c) 2016-2021 Ginger Bill. All rights reserved.
+Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
 
 
 Redistribution and use in source and binary forms, with or without
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 modification, are permitted provided that the following conditions are met:

+ 2 - 2
Makefile

@@ -1,7 +1,7 @@
-all: debug demo
+all: debug
 
 
 demo:
 demo:
-	./odin run examples/demo/demo.odin
+	./odin run examples/demo/demo.odin -file
 
 
 report:
 report:
 	./odin report
 	./odin report

+ 173 - 0
core/container/intrusive/list/intrusive_list.odin

@@ -0,0 +1,173 @@
+package container_intrusive_list
+
+import "core:intrinsics"
+
+// An intrusive doubly-linked list
+//
+// As this is an intrusive container, a `Node` must be embedded in your own
+// structure which is conventionally called a "link". The use of `push_front`
+// and `push_back` take the address of this node. Retrieving the data
+// associated with the node requires finding the relative offset of the node
+// of the parent structure. The parent type and field name are given to
+// `iterator_*` procedures, or to the built-in `container_of` procedure.
+//
+// This data structure is two-pointers in size:
+// 	8 bytes on 32-bit platforms and 16 bytes on 64-bit platforms
+List :: struct {
+	head: ^Node,
+	tail: ^Node,
+}
+
+
+Node :: struct {
+	next, prev: ^Node,
+}
+
+push_front :: proc(list: ^List, node: ^Node) {
+	if list.head != nil {
+		list.head.prev = node
+		node.prev, node.next = nil, list.head
+		list.head = node
+	} else {
+		list.head, list.tail = node, node
+		node.prev, node.next = nil, nil
+	}
+}
+
+push_back :: proc(list: ^List, node: ^Node) {
+	if list.tail != nil {
+		list.tail.next = node
+		node.prev, node.next = list.tail, nil
+		list.tail = node
+	} else {
+		list.head, list.tail = node, node
+		node.prev, node.next = nil, nil
+	}
+}
+
+remove :: proc(list: ^List, node: ^Node) {
+	if node != nil {
+		if node.next != nil {
+			node.next.prev = node.prev
+		}
+		if node.prev != nil {
+			node.prev.next = node.next
+		}
+		if list.head == node {
+			list.head = node.next
+		}
+		if list.tail == node {
+			list.tail = node.prev
+		}
+	}
+}
+
+remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
+	for node := list.head; node != nil; {
+		next := node.next
+		if to_erase(node) {
+			if node.next != nil {
+				node.next.prev = node.prev
+			}
+			if node.prev != nil {
+				node.prev.next = node.next
+			}
+			if list.head == node {
+				list.head = node.next
+			}
+			if list.tail == node {
+				list.tail = node.prev
+			}
+		}
+		node = next
+	}
+}
+
+
+is_empty :: proc(list: ^List) -> bool {
+	return list.head == nil
+}
+
+pop_front :: proc(list: ^List) -> ^Node {
+	link := list.head
+	if link == nil {
+		return nil
+	}
+	if link.next != nil {
+		link.next.prev = link.prev
+	}
+	if link.prev != nil {
+		link.prev.next = link.next
+	}
+	if link == list.head {
+		list.head = link.next
+	}
+	if link == list.tail {
+		list.tail = link.prev
+	}
+	return link
+
+}
+pop_back :: proc(list: ^List) -> ^Node {
+	link := list.tail
+	if link == nil {
+		return nil
+	}
+	if link.next != nil {
+		link.next.prev = link.prev
+	}
+	if link.prev != nil {
+		link.prev.next = link.next
+	}
+	if link == list.head {
+		list.head = link.next
+	}
+	if link == list.tail {
+		list.tail = link.prev
+	}
+	return link
+}
+
+
+Iterator :: struct($T: typeid) {
+	curr:   ^Node,
+	offset: uintptr,
+}
+
+iterator_head :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Node {
+	return {list.head, offset_of_by_string(T, field_name)}
+}
+
+iterator_tail :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Node {
+	return {list.tail, offset_of_by_string(T, field_name)}
+}
+
+iterator_from_node :: proc(node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Node {
+	return {node, offset_of_by_string(T, field_name)}
+}
+
+iterate_next :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
+	node := it.curr
+	if node == nil {
+		return nil, false
+	}
+	it.curr = node.next
+
+	return (^T)(uintptr(node) - it.offset), true
+}
+
+iterate_prev :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
+	node := it.curr
+	if node == nil {
+		return nil, false
+	}
+	it.curr = node.prev
+
+	return (^T)(uintptr(node) - it.offset), true
+}

+ 42 - 13
core/intrinsics/intrinsics.odin

@@ -41,6 +41,10 @@ mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) ---
 mem_zero                 :: proc(ptr: rawptr, len: int) ---
 mem_zero                 :: proc(ptr: rawptr, len: int) ---
 mem_zero_volatile        :: proc(ptr: rawptr, len: int) ---
 mem_zero_volatile        :: proc(ptr: rawptr, len: int) ---
 
 
+// prefer [^]T operations if possible
+ptr_offset :: proc(ptr: ^$T, offset: int) -> ^T ---
+ptr_sub    :: proc(a, b: ^$T) -> int ---
+
 unaligned_load           :: proc(src: ^$T) -> T ---
 unaligned_load           :: proc(src: ^$T) -> T ---
 unaligned_store          :: proc(dst: ^$T, val: T) -> T ---
 unaligned_store          :: proc(dst: ^$T, val: T) -> T ---
 
 
@@ -82,6 +86,7 @@ atomic_store_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) ---
 atomic_load           :: proc(dst: ^$T) -> T ---
 atomic_load           :: proc(dst: ^$T) -> T ---
 atomic_load_explicit  :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T ---
 atomic_load_explicit  :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T ---
 
 
+// fetch then operator
 atomic_add               :: proc(dst; ^$T, val: T) -> T ---
 atomic_add               :: proc(dst; ^$T, val: T) -> T ---
 atomic_add_explicit      :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T ---
 atomic_add_explicit      :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T ---
 atomic_sub               :: proc(dst; ^$T, val: T) -> T ---
 atomic_sub               :: proc(dst; ^$T, val: T) -> T ---
@@ -119,19 +124,20 @@ type_is_string     :: proc($T: typeid) -> bool ---
 type_is_typeid     :: proc($T: typeid) -> bool ---
 type_is_typeid     :: proc($T: typeid) -> bool ---
 type_is_any        :: proc($T: typeid) -> bool ---
 type_is_any        :: proc($T: typeid) -> bool ---
 
 
-type_is_endian_platform :: proc($T: typeid) -> bool ---
-type_is_endian_little   :: proc($T: typeid) -> bool ---
-type_is_endian_big      :: proc($T: typeid) -> bool ---
-type_is_unsigned        :: proc($T: typeid) -> bool ---
-type_is_numeric         :: proc($T: typeid) -> bool ---
-type_is_ordered         :: proc($T: typeid) -> bool ---
-type_is_ordered_numeric :: proc($T: typeid) -> bool ---
-type_is_indexable       :: proc($T: typeid) -> bool ---
-type_is_sliceable       :: proc($T: typeid) -> bool ---
-type_is_comparable      :: proc($T: typeid) -> bool ---
-type_is_simple_compare  :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
-type_is_dereferenceable :: proc($T: typeid) -> bool ---
-type_is_valid_map_key   :: proc($T: typeid) -> bool ---
+type_is_endian_platform       :: proc($T: typeid) -> bool ---
+type_is_endian_little         :: proc($T: typeid) -> bool ---
+type_is_endian_big            :: proc($T: typeid) -> bool ---
+type_is_unsigned              :: proc($T: typeid) -> bool ---
+type_is_numeric               :: proc($T: typeid) -> bool ---
+type_is_ordered               :: proc($T: typeid) -> bool ---
+type_is_ordered_numeric       :: proc($T: typeid) -> bool ---
+type_is_indexable             :: proc($T: typeid) -> bool ---
+type_is_sliceable             :: proc($T: typeid) -> bool ---
+type_is_comparable            :: proc($T: typeid) -> bool ---
+type_is_simple_compare        :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
+type_is_dereferenceable       :: proc($T: typeid) -> bool ---
+type_is_valid_map_key         :: proc($T: typeid) -> bool ---
+type_is_valid_matrix_elements :: proc($T: typeid) -> bool ---
 
 
 type_is_named            :: proc($T: typeid) -> bool ---
 type_is_named            :: proc($T: typeid) -> bool ---
 type_is_pointer          :: proc($T: typeid) -> bool ---
 type_is_pointer          :: proc($T: typeid) -> bool ---
@@ -146,6 +152,7 @@ type_is_enum             :: proc($T: typeid) -> bool ---
 type_is_proc             :: proc($T: typeid) -> bool ---
 type_is_proc             :: proc($T: typeid) -> bool ---
 type_is_bit_set          :: proc($T: typeid) -> bool ---
 type_is_bit_set          :: proc($T: typeid) -> bool ---
 type_is_simd_vector      :: proc($T: typeid) -> bool ---
 type_is_simd_vector      :: proc($T: typeid) -> bool ---
+type_is_matrix           :: proc($T: typeid) -> bool ---
 
 
 type_has_nil :: proc($T: typeid) -> bool ---
 type_has_nil :: proc($T: typeid) -> bool ---
 
 
@@ -153,6 +160,7 @@ type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
 type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
 type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
 
 
 type_has_field :: proc($T: typeid, $name: string) -> bool ---
 type_has_field :: proc($T: typeid, $name: string) -> bool ---
+type_field_type :: proc($T: typeid, $name: string) -> typeid ---
 
 
 type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_return_count    :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_return_count    :: proc($T: typeid) -> int where type_is_proc(T) ---
@@ -160,20 +168,41 @@ type_proc_return_count    :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_parameter_type  :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 type_proc_parameter_type  :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 type_proc_return_type     :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 type_proc_return_type     :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 
 
+type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
+
 type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
 type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
 type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
 type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
 
 
+type_is_specialized_polymorphic_record   :: proc($T: typeid) -> bool ---
+type_is_unspecialized_polymorphic_record :: proc($T: typeid) -> bool ---
+
+type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
 
 
 type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
 type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
 
 
 type_equal_proc  :: proc($T: typeid) -> (equal:  proc "contextless" (rawptr, rawptr) -> bool)                 where type_is_comparable(T) ---
 type_equal_proc  :: proc($T: typeid) -> (equal:  proc "contextless" (rawptr, rawptr) -> bool)                 where type_is_comparable(T) ---
 type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
 type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
 
 
+constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
 
 
 // WASM targets only
 // WASM targets only
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
 wasm_memory_size :: proc(index: uintptr)        -> int ---
 wasm_memory_size :: proc(index: uintptr)        -> int ---
 
 
+
+// Darwin targets only
+objc_object   :: struct{}
+objc_selector :: struct{}
+objc_class    :: struct{}
+objc_id    :: ^objc_object
+objc_SEL   :: ^objc_selector
+objc_Class :: ^objc_class
+
+objc_find_selector     :: proc($name: string) -> objc_SEL   ---
+objc_register_selector :: proc($name: string) -> objc_SEL   ---
+objc_find_class        :: proc($name: string) -> objc_Class ---
+objc_register_class    :: proc($name: string) -> objc_Class ---
+
 // Internal compiler use only
 // Internal compiler use only
 
 
 __entry_point :: proc() ---
 __entry_point :: proc() ---

+ 10 - 0
core/runtime/core_builtin.odin

@@ -5,6 +5,16 @@ import "core:intrinsics"
 @builtin
 @builtin
 Maybe :: union($T: typeid) #maybe {T}
 Maybe :: union($T: typeid) #maybe {T}
 
 
+
+@builtin
+container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Field_Type {
+	offset :: offset_of_by_string(T, field_name)
+	return (^T)(uintptr(ptr) - offset) if ptr != nil else nil
+}
+
+
 @thread_local global_default_temp_allocator_data: Default_Temp_Allocator
 @thread_local global_default_temp_allocator_data: Default_Temp_Allocator
 
 
 @builtin
 @builtin

+ 4 - 4
core/sync/sema_internal.odin

@@ -6,19 +6,19 @@ import "core:time"
 
 
 when #config(ODIN_SYNC_SEMA_USE_FUTEX, true) {
 when #config(ODIN_SYNC_SEMA_USE_FUTEX, true) {
 	_Sema :: struct {
 	_Sema :: struct {
-		sema: Atomic_Sema,
+		atomic: Atomic_Sema,
 	}
 	}
 
 
 	_sema_post :: proc(s: ^Sema, count := 1) {
 	_sema_post :: proc(s: ^Sema, count := 1) {
-		atomic_sema_post(&s.impl.sema, count)
+		atomic_sema_post(&s.impl.atomic, count)
 	}
 	}
 
 
 	_sema_wait :: proc(s: ^Sema) {
 	_sema_wait :: proc(s: ^Sema) {
-		atomic_sema_wait(&s.impl.sema)
+		atomic_sema_wait(&s.impl.atomic)
 	}
 	}
 
 
 	_sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool {
 	_sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool {
-		return atomic_sema_wait_with_timeout(&s.impl.sema, duration)
+		return atomic_sema_wait_with_timeout(&s.impl.atomic, duration)
 	}
 	}
 } else {
 } else {
 	_Sema :: struct {
 	_Sema :: struct {

+ 4 - 5
core/thread/thread_pool.odin

@@ -44,10 +44,10 @@ Pool :: struct {
 }
 }
 
 
 // Once initialized, the pool's memory address is not allowed to change until
 // Once initialized, the pool's memory address is not allowed to change until
-// it is destroyed. If thread_count < 1, thread count 1 will be used.
+// it is destroyed. 
 //
 //
 // The thread pool requires an allocator which it either owns, or which is thread safe.
 // The thread pool requires an allocator which it either owns, or which is thread safe.
-pool_init :: proc(pool: ^Pool, thread_count: int, allocator: mem.Allocator) {
+pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) {
 	context.allocator = allocator
 	context.allocator = allocator
 	pool.allocator = allocator
 	pool.allocator = allocator
 	pool.tasks      = make([dynamic]Task)
 	pool.tasks      = make([dynamic]Task)
@@ -113,9 +113,8 @@ pool_join :: proc(pool: ^Pool) {
 // the thread pool. You can even add tasks from inside other tasks.
 // the thread pool. You can even add tasks from inside other tasks.
 //
 //
 // Each task also needs an allocator which it either owns, or which is thread
 // Each task also needs an allocator which it either owns, or which is thread
-// safe. By default, allocations in the task are disabled by use of the
-// nil_allocator.
-pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0, allocator := context.allocator) {
+// safe. 
+pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) {
 	sync.guard(&pool.mutex)
 	sync.guard(&pool.mutex)
 
 
 	append(&pool.tasks, Task{
 	append(&pool.tasks, Task{

+ 2 - 1
examples/demo/demo.odin

@@ -1166,7 +1166,8 @@ threading_example :: proc() {
 
 
 
 
 		for i in 0..<30 {
 		for i in 0..<30 {
-			thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i)
+			// be mindful of the allocator used for tasks. The allocator needs to be thread safe, or be owned by the task for exclusive use 
+			thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i, allocator=context.allocator)
 		}
 		}
 
 
 		thread.pool_start(&pool)
 		thread.pool_start(&pool)

+ 208 - 28
src/build_settings.cpp

@@ -3,7 +3,6 @@
 #include <sys/sysctl.h>
 #include <sys/sysctl.h>
 #endif
 #endif
 
 
-
 // #if defined(GB_SYSTEM_WINDOWS)
 // #if defined(GB_SYSTEM_WINDOWS)
 // #define DEFAULT_TO_THREADED_CHECKER
 // #define DEFAULT_TO_THREADED_CHECKER
 // #endif
 // #endif
@@ -198,6 +197,22 @@ enum RelocMode : u8 {
 	RelocMode_DynamicNoPIC,
 	RelocMode_DynamicNoPIC,
 };
 };
 
 
+enum BuildPath : u8 {
+	BuildPath_Main_Package,     // Input  Path to the package directory (or file) we're building.
+	BuildPath_RC,               // Input  Path for .rc  file, can be set with `-resource:`.
+	BuildPath_RES,              // Output Path for .res file, generated from previous.
+	BuildPath_Win_SDK_Root,     // windows_sdk_root
+	BuildPath_Win_SDK_UM_Lib,   // windows_sdk_um_library_path
+	BuildPath_Win_SDK_UCRT_Lib, // windows_sdk_ucrt_library_path
+	BuildPath_VS_EXE,           // vs_exe_path
+	BuildPath_VS_LIB,           // vs_library_path
+
+	BuildPath_Output,           // Output Path for .exe, .dll, .so, etc. Can be overridden with `-out:`.
+	BuildPath_PDB,              // Output Path for .pdb file, can be overridden with `-pdb-name:`.
+
+	BuildPathCOUNT,
+};
+
 // This stores the information for the specify architecture of this build
 // This stores the information for the specify architecture of this build
 struct BuildContext {
 struct BuildContext {
 	// Constants
 	// Constants
@@ -226,9 +241,13 @@ struct BuildContext {
 
 
 	bool show_help;
 	bool show_help;
 
 
+	Array<Path> build_paths;   // Contains `Path` objects to output filename, pdb, resource and intermediate files.
+	                           // BuildPath enum contains the indices of paths we know *before* the work starts.
+
 	String out_filepath;
 	String out_filepath;
 	String resource_filepath;
 	String resource_filepath;
 	String pdb_filepath;
 	String pdb_filepath;
+
 	bool   has_resource;
 	bool   has_resource;
 	String link_flags;
 	String link_flags;
 	String extra_linker_flags;
 	String extra_linker_flags;
@@ -300,8 +319,6 @@ struct BuildContext {
 
 
 };
 };
 
 
-
-
 gb_global BuildContext build_context = {0};
 gb_global BuildContext build_context = {0};
 
 
 bool global_warnings_as_errors(void) {
 bool global_warnings_as_errors(void) {
@@ -605,28 +622,6 @@ bool allow_check_foreign_filepath(void) {
 // is_abs_path
 // is_abs_path
 // has_subdir
 // has_subdir
 
 
-enum TargetFileValidity : u8 {
-	TargetFileValidity_Invalid,
-
-	TargetFileValidity_Writable_File,
-	TargetFileValidity_No_Write_Permission,
-	TargetFileValidity_Directory,
-
-	TargetTargetFileValidity_COUNT,
-};
-
-TargetFileValidity set_output_filename(void) {
-	// Assembles the output filename from build_context information.
-	// Returns `true`  if it doesn't exist or is a file.
-	// Returns `false` if a directory or write-protected file.
-
-
-
-
-	return TargetFileValidity_Writable_File;
-}
-
-
 String const WIN32_SEPARATOR_STRING = {cast(u8 *)"\\", 1};
 String const WIN32_SEPARATOR_STRING = {cast(u8 *)"\\", 1};
 String const NIX_SEPARATOR_STRING   = {cast(u8 *)"/",  1};
 String const NIX_SEPARATOR_STRING   = {cast(u8 *)"/",  1};
 
 
@@ -973,7 +968,6 @@ char *token_pos_to_string(TokenPos const &pos) {
 	return s;
 	return s;
 }
 }
 
 
-
 void init_build_context(TargetMetrics *cross_target) {
 void init_build_context(TargetMetrics *cross_target) {
 	BuildContext *bc = &build_context;
 	BuildContext *bc = &build_context;
 
 
@@ -1152,8 +1146,194 @@ void init_build_context(TargetMetrics *cross_target) {
 
 
 	bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
 	bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
 
 
-
-
 	#undef LINK_FLAG_X64
 	#undef LINK_FLAG_X64
 	#undef LINK_FLAG_386
 	#undef LINK_FLAG_386
 }
 }
+
+#if defined(GB_SYSTEM_WINDOWS)
+// NOTE(IC): In order to find Visual C++ paths without relying on environment variables.
+// NOTE(Jeroen): No longer needed in `main.cpp -> linker_stage`. We now resolve those paths in `init_build_paths`.
+#include "microsoft_craziness.h"
+#endif
+
+// NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate.
+// We've previously called `parse_build_flags`, so `out_filepath` should be set.
+bool init_build_paths(String init_filename) {
+	gbAllocator   ha = heap_allocator();
+	BuildContext *bc = &build_context;
+
+	// NOTE(Jeroen): We're pre-allocating BuildPathCOUNT slots so that certain paths are always at the same enumerated index.
+	array_init(&bc->build_paths, permanent_allocator(), BuildPathCOUNT);
+
+	// [BuildPathMainPackage] Turn given init path into a `Path`, which includes normalizing it into a full path.
+	bc->build_paths[BuildPath_Main_Package] = path_from_string(ha, init_filename);
+
+	bool produces_output_file = false;
+	if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) {
+		produces_output_file = true;
+	} else if (bc->command_kind & Command__does_build) {
+		produces_output_file = true;
+	}
+
+	if (!produces_output_file) {
+		// Command doesn't produce output files. We're done.
+		return true;
+	}
+
+	#if defined(GB_SYSTEM_WINDOWS)
+		if (bc->resource_filepath.len > 0) {
+			bc->build_paths[BuildPath_RC]      = path_from_string(ha, bc->resource_filepath);
+			bc->build_paths[BuildPath_RES]     = path_from_string(ha, bc->resource_filepath);
+			bc->build_paths[BuildPath_RC].ext  = copy_string(ha, STR_LIT("rc"));
+			bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res"));
+		}
+
+		if (bc->pdb_filepath.len > 0) {
+			bc->build_paths[BuildPath_PDB]          = path_from_string(ha, bc->pdb_filepath);
+		}
+
+		if ((bc->command_kind & Command__does_build) && (!bc->ignore_microsoft_magic)) {
+			// NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest.
+			Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8();
+
+			if (find_result.windows_sdk_version == 0) {
+				gb_printf_err("Windows SDK not found.\n");
+				return false;
+			}
+
+			GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0);
+			GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0);
+
+			if (find_result.windows_sdk_root.len > 0) {
+				bc->build_paths[BuildPath_Win_SDK_Root]     = path_from_string(ha, find_result.windows_sdk_root);
+			}
+
+			if (find_result.windows_sdk_um_library_path.len > 0) {
+				bc->build_paths[BuildPath_Win_SDK_UM_Lib]   = path_from_string(ha, find_result.windows_sdk_um_library_path);
+			}
+
+			if (find_result.windows_sdk_ucrt_library_path.len > 0) {
+				bc->build_paths[BuildPath_Win_SDK_UCRT_Lib] = path_from_string(ha, find_result.windows_sdk_ucrt_library_path);
+			}
+
+			if (find_result.vs_exe_path.len > 0) {
+				bc->build_paths[BuildPath_VS_EXE]           = path_from_string(ha, find_result.vs_exe_path);
+			}
+
+			if (find_result.vs_library_path.len > 0) {
+				bc->build_paths[BuildPath_VS_LIB]           = path_from_string(ha, find_result.vs_library_path);
+			}
+
+			gb_free(ha, find_result.windows_sdk_root.text);
+			gb_free(ha, find_result.windows_sdk_um_library_path.text);
+			gb_free(ha, find_result.windows_sdk_ucrt_library_path.text);
+			gb_free(ha, find_result.vs_exe_path.text);
+			gb_free(ha, find_result.vs_library_path.text);
+
+		}
+	#endif
+
+	// All the build targets and OSes.
+	String output_extension;
+
+	if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) {
+		output_extension = STR_LIT("odin-doc");
+	} else if (is_arch_wasm()) {
+		output_extension = STR_LIT("wasm");
+	} else if (build_context.build_mode == BuildMode_Executable) {
+		// By default use a .bin executable extension.
+		output_extension = STR_LIT("bin");
+
+		if (build_context.metrics.os == TargetOs_windows) {
+			output_extension = STR_LIT("exe");
+		} else if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) {
+			output_extension = make_string(nullptr, 0);
+		}
+	} else if (build_context.build_mode == BuildMode_DynamicLibrary) {
+		// By default use a .so shared library extension.
+		output_extension = STR_LIT("so");
+
+		if (build_context.metrics.os == TargetOs_windows) {
+			output_extension = STR_LIT("dll");
+		} else if (build_context.metrics.os == TargetOs_darwin) {
+			output_extension = STR_LIT("dylib");
+		}
+	} else if (build_context.build_mode == BuildMode_Object) {
+		// By default use a .o object extension.
+		output_extension = STR_LIT("o");
+
+		if (build_context.metrics.os == TargetOs_windows) {
+			output_extension = STR_LIT("obj");
+		}
+	} else if (build_context.build_mode == BuildMode_Assembly) {
+		// By default use a .S asm extension.
+		output_extension = STR_LIT("S");
+	} else if (build_context.build_mode == BuildMode_LLVM_IR) {
+		output_extension = STR_LIT("ll");
+	} else {
+		GB_PANIC("Unhandled build mode/target combination.\n");
+	}
+
+	if (bc->out_filepath.len > 0) {
+		bc->build_paths[BuildPath_Output] = path_from_string(ha, bc->out_filepath);
+		if (build_context.metrics.os == TargetOs_windows) {
+			String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]);
+			defer (gb_free(ha, output_file.text));
+			if (path_is_directory(bc->build_paths[BuildPath_Output])) {
+				gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file));
+				return false;
+			} else if (bc->build_paths[BuildPath_Output].ext.len == 0) {
+				gb_printf_err("Output path %.*s must have an appropriate extension.\n", LIT(output_file));
+				return false;				
+			}
+		}
+	} else {
+		Path output_path;
+
+		if (str_eq(init_filename, str_lit("."))) {
+			// We must name the output file after the current directory.
+			debugf("Output name will be created from current base name %.*s.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename));
+			String last_element  = last_path_element(bc->build_paths[BuildPath_Main_Package].basename);
+
+			if (last_element.len == 0) {
+				gb_printf_err("The output name is created from the last path element. `%.*s` has none. Use `-out:output_name.ext` to set it.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename));
+				return false;
+			}
+			output_path.basename = copy_string(ha, bc->build_paths[BuildPath_Main_Package].basename);
+			output_path.name     = copy_string(ha, last_element);
+
+		} else {
+			// Init filename was not 'current path'.
+			// Contruct the output name from the path elements as usual.
+			String output_name = remove_directory_from_path(init_filename);
+			output_name        = remove_extension_from_path(output_name);
+			output_name        = copy_string(ha, string_trim_whitespace(output_name));
+			output_path        = path_from_string(ha, output_name);
+
+			// Replace extension.
+			if (output_path.ext.len > 0) {
+				gb_free(ha, output_path.ext.text);
+			}
+		}
+		output_path.ext  = copy_string(ha, output_extension);
+
+		bc->build_paths[BuildPath_Output] = output_path;
+	}
+
+	// Do we have an extension? We might not if the output filename was supplied.
+	if (bc->build_paths[BuildPath_Output].ext.len == 0) {
+		if (build_context.metrics.os == TargetOs_windows || build_context.build_mode != BuildMode_Executable) {
+			bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension);
+		}
+	}
+
+	// Check if output path is a directory.
+	if (path_is_directory(bc->build_paths[BuildPath_Output])) {
+		String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]);
+		defer (gb_free(ha, output_file.text));
+		gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file));
+		return false;
+	}
+
+	return true;
+}

+ 33 - 0
src/check_builtin.cpp

@@ -29,6 +29,7 @@ BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_boolean_end -
 
 
 	is_type_named,
 	is_type_named,
 	is_type_pointer,
 	is_type_pointer,
+	is_type_multi_pointer,
 	is_type_array,
 	is_type_array,
 	is_type_enumerated_array,
 	is_type_enumerated_array,
 	is_type_slice,
 	is_type_slice,
@@ -3866,6 +3867,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 	case BuiltinProc_type_is_valid_matrix_elements:
 	case BuiltinProc_type_is_valid_matrix_elements:
 	case BuiltinProc_type_is_named:
 	case BuiltinProc_type_is_named:
 	case BuiltinProc_type_is_pointer:
 	case BuiltinProc_type_is_pointer:
+	case BuiltinProc_type_is_multi_pointer:
 	case BuiltinProc_type_is_array:
 	case BuiltinProc_type_is_array:
 	case BuiltinProc_type_is_enumerated_array:
 	case BuiltinProc_type_is_enumerated_array:
 	case BuiltinProc_type_is_slice:
 	case BuiltinProc_type_is_slice:
@@ -3926,6 +3928,37 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			break;
 			break;
 		}
 		}
 		break;
 		break;
+	case BuiltinProc_type_field_type:
+		{
+			Operand op = {};
+			Type *bt = check_type(c, ce->args[0]);
+			Type *type = base_type(bt);
+			if (type == nullptr || type == t_invalid) {
+				error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name));
+				return false;
+			}
+			Operand x = {};
+			check_expr(c, &x, ce->args[1]);
+
+			if (!is_type_string(x.type) || x.mode != Addressing_Constant || x.value.kind != ExactValue_String) {
+				error(ce->args[1], "Expected a const string for field argument");
+				return false;
+			}
+
+			String field_name = x.value.value_string;
+
+			Selection sel = lookup_field(type, field_name, false);
+			if (sel.index.count == 0) {
+				gbString t = type_to_string(type);
+				error(ce->args[1], "'%.*s' is not a field of type %s", LIT(field_name), t);
+				gb_string_free(t);
+				return false;
+			}
+			operand->mode = Addressing_Type;
+			operand->type = sel.entity->type;
+			break;
+		}
+		break;
 
 
 	case BuiltinProc_type_is_specialization_of:
 	case BuiltinProc_type_is_specialization_of:
 		{
 		{

+ 2 - 2
src/check_type.cpp

@@ -1338,14 +1338,14 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
 							}
 							}
 						}
 						}
 					}
 					}
-				} else if (allow_caller_location && (o.mode == Addressing_Context || are_types_identical(o.type, t_source_code_location))) {
+				} else if (allow_caller_location && o.mode == Addressing_Context) {
 					param_value.kind = ParameterValue_Value;
 					param_value.kind = ParameterValue_Value;
 					param_value.ast_value = expr;
 					param_value.ast_value = expr;
 				} else if (o.value.kind != ExactValue_Invalid) {
 				} else if (o.value.kind != ExactValue_Invalid) {
 					param_value.kind = ParameterValue_Constant;
 					param_value.kind = ParameterValue_Constant;
 					param_value.value = o.value;
 					param_value.value = o.value;
 				} else {
 				} else {
-					error(expr, "Default parameter must be a constant");
+					error(expr, "Default parameter must be a constant, %d", o.mode);
 				}
 				}
 			}
 			}
 		} else {
 		} else {

+ 4 - 0
src/checker_builtin_procs.hpp

@@ -158,6 +158,7 @@ BuiltinProc__type_simple_boolean_begin,
 
 
 	BuiltinProc_type_is_named,
 	BuiltinProc_type_is_named,
 	BuiltinProc_type_is_pointer,
 	BuiltinProc_type_is_pointer,
+	BuiltinProc_type_is_multi_pointer,
 	BuiltinProc_type_is_array,
 	BuiltinProc_type_is_array,
 	BuiltinProc_type_is_enumerated_array,
 	BuiltinProc_type_is_enumerated_array,
 	BuiltinProc_type_is_slice,
 	BuiltinProc_type_is_slice,
@@ -179,6 +180,7 @@ BuiltinProc__type_simple_boolean_begin,
 BuiltinProc__type_simple_boolean_end,
 BuiltinProc__type_simple_boolean_end,
 
 
 	BuiltinProc_type_has_field,
 	BuiltinProc_type_has_field,
+	BuiltinProc_type_field_type,
 
 
 	BuiltinProc_type_is_specialization_of,
 	BuiltinProc_type_is_specialization_of,
 
 
@@ -375,6 +377,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 
 
 	{STR_LIT("type_is_named"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_named"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_pointer"),           1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_pointer"),           1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("type_is_multi_pointer"),      1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_array"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_array"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_enumerated_array"),  1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_enumerated_array"),  1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_slice"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_slice"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -395,6 +398,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 	{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 
 
 	{STR_LIT("type_has_field"),            2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_has_field"),            2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("type_field_type"),           2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 
 	{STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 

+ 1 - 256
src/common.cpp

@@ -675,262 +675,7 @@ wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) {
 
 
 #endif
 #endif
 
 
-
-#if defined(GB_SYSTEM_WINDOWS)
-	bool path_is_directory(String path) {
-		gbAllocator a = heap_allocator();
-		String16 wstr = string_to_string16(a, path);
-		defer (gb_free(a, wstr.text));
-
-		i32 attribs = GetFileAttributesW(wstr.text);
-		if (attribs < 0) return false;
-
-		return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
-	}
-
-#else
-	bool path_is_directory(String path) {
-		gbAllocator a = heap_allocator();
-		char *copy = cast(char *)copy_string(a, path).text;
-		defer (gb_free(a, copy));
-
-		struct stat s;
-		if (stat(copy, &s) == 0) {
-			return (s.st_mode & S_IFDIR) != 0;
-		}
-		return false;
-	}
-#endif
-
-
-String path_to_full_path(gbAllocator a, String path) {
-	gbAllocator ha = heap_allocator();
-	char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
-	defer (gb_free(ha, path_c));
-
-	char *fullpath = gb_path_get_full_name(a, path_c);
-	String res = string_trim_whitespace(make_string_c(fullpath));
-#if defined(GB_SYSTEM_WINDOWS)
-	for (isize i = 0; i < res.len; i++) {
-		if (res.text[i] == '\\') {
-			res.text[i] = '/';
-		}
-	}
-#endif
-	return res;
-}
-
-
-
-struct FileInfo {
-	String name;
-	String fullpath;
-	i64    size;
-	bool   is_dir;
-};
-
-enum ReadDirectoryError {
-	ReadDirectory_None,
-
-	ReadDirectory_InvalidPath,
-	ReadDirectory_NotExists,
-	ReadDirectory_Permission,
-	ReadDirectory_NotDir,
-	ReadDirectory_Empty,
-	ReadDirectory_Unknown,
-
-	ReadDirectory_COUNT,
-};
-
-i64 get_file_size(String path) {
-	char *c_str = alloc_cstring(heap_allocator(), path);
-	defer (gb_free(heap_allocator(), c_str));
-
-	gbFile f = {};
-	gbFileError err = gb_file_open(&f, c_str);
-	defer (gb_file_close(&f));
-	if (err != gbFileError_None) {
-		return -1;
-	}
-	return gb_file_size(&f);
-}
-
-
-#if defined(GB_SYSTEM_WINDOWS)
-ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
-	GB_ASSERT(fi != nullptr);
-
-	gbAllocator a = heap_allocator();
-
-	while (path.len > 0) {
-		Rune end = path[path.len-1];
-		if (end == '/') {
-			path.len -= 1;
-		} else if (end == '\\') {
-			path.len -= 1;
-		} else {
-			break;
-		}
-	}
-
-	if (path.len == 0) {
-		return ReadDirectory_InvalidPath;
-	}
-	{
-		char *c_str = alloc_cstring(a, path);
-		defer (gb_free(a, c_str));
-
-		gbFile f = {};
-		gbFileError file_err = gb_file_open(&f, c_str);
-		defer (gb_file_close(&f));
-
-		switch (file_err) {
-		case gbFileError_Invalid:    return ReadDirectory_InvalidPath;
-		case gbFileError_NotExists:  return ReadDirectory_NotExists;
-		// case gbFileError_Permission: return ReadDirectory_Permission;
-		}
-	}
-
-	if (!path_is_directory(path)) {
-		return ReadDirectory_NotDir;
-	}
-
-
-	char *new_path = gb_alloc_array(a, char, path.len+3);
-	defer (gb_free(a, new_path));
-
-	gb_memmove(new_path, path.text, path.len);
-	gb_memmove(new_path+path.len, "/*", 2);
-	new_path[path.len+2] = 0;
-
-	String np = make_string(cast(u8 *)new_path, path.len+2);
-	String16 wstr = string_to_string16(a, np);
-	defer (gb_free(a, wstr.text));
-
-	WIN32_FIND_DATAW file_data = {};
-	HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
-	if (find_file == INVALID_HANDLE_VALUE) {
-		return ReadDirectory_Unknown;
-	}
-	defer (FindClose(find_file));
-
-	array_init(fi, a, 0, 100);
-
-	do {
-		wchar_t *filename_w = file_data.cFileName;
-		i64 size = cast(i64)file_data.nFileSizeLow;
-		size |= (cast(i64)file_data.nFileSizeHigh) << 32;
-		String name = string16_to_string(a, make_string16_c(filename_w));
-		if (name == "." || name == "..") {
-			gb_free(a, name.text);
-			continue;
-		}
-
-		String filepath = {};
-		filepath.len = path.len+1+name.len;
-		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
-		defer (gb_free(a, filepath.text));
-		gb_memmove(filepath.text, path.text, path.len);
-		gb_memmove(filepath.text+path.len, "/", 1);
-		gb_memmove(filepath.text+path.len+1, name.text, name.len);
-
-		FileInfo info = {};
-		info.name = name;
-		info.fullpath = path_to_full_path(a, filepath);
-		info.size = size;
-		info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
-		array_add(fi, info);
-	} while (FindNextFileW(find_file, &file_data));
-
-	if (fi->count == 0) {
-		return ReadDirectory_Empty;
-	}
-
-	return ReadDirectory_None;
-}
-#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
-
-#include <dirent.h>
-
-ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
-	GB_ASSERT(fi != nullptr);
-
-	gbAllocator a = heap_allocator();
-
-	char *c_path = alloc_cstring(a, path);
-	defer (gb_free(a, c_path));
-
-	DIR *dir = opendir(c_path);
-	if (!dir) {
-		switch (errno) {
-		case ENOENT:
-			return ReadDirectory_NotExists;
-		case EACCES:
-			return ReadDirectory_Permission;
-		case ENOTDIR:
-			return ReadDirectory_NotDir;
-		default:
-			// ENOMEM: out of memory
-			// EMFILE: per-process limit on open fds reached
-			// ENFILE: system-wide limit on total open files reached
-			return ReadDirectory_Unknown;
-		}
-		GB_PANIC("unreachable");
-	}
-
-	array_init(fi, a, 0, 100);
-
-	for (;;) {
-		struct dirent *entry = readdir(dir);
-		if (entry == nullptr) {
-			break;
-		}
-
-		String name = make_string_c(entry->d_name);
-		if (name == "." || name == "..") {
-			continue;
-		}
-
-		String filepath = {};
-		filepath.len = path.len+1+name.len;
-		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
-		defer (gb_free(a, filepath.text));
-		gb_memmove(filepath.text, path.text, path.len);
-		gb_memmove(filepath.text+path.len, "/", 1);
-		gb_memmove(filepath.text+path.len+1, name.text, name.len);
-		filepath.text[filepath.len] = 0;
-
-
-		struct stat dir_stat = {};
-
-		if (stat((char *)filepath.text, &dir_stat)) {
-			continue;
-		}
-
-		if (S_ISDIR(dir_stat.st_mode)) {
-			continue;
-		}
-
-		i64 size = dir_stat.st_size;
-
-		FileInfo info = {};
-		info.name = name;
-		info.fullpath = path_to_full_path(a, filepath);
-		info.size = size;
-		array_add(fi, info);
-	}
-
-	if (fi->count == 0) {
-		return ReadDirectory_Empty;
-	}
-
-	return ReadDirectory_None;
-}
-#else
-#error Implement read_directory
-#endif
-
-
+#include "path.cpp"
 
 
 struct LoadedFile {
 struct LoadedFile {
 	void *handle;
 	void *handle;

+ 35 - 11
src/gb/gb.h

@@ -6273,20 +6273,44 @@ char *gb_path_get_full_name(gbAllocator a, char const *path) {
 #else
 #else
 	char *p, *result, *fullpath = NULL;
 	char *p, *result, *fullpath = NULL;
 	isize len;
 	isize len;
-	p = realpath(path, NULL);
-	fullpath = p;
-	if (p == NULL) {
-		// NOTE(bill): File does not exist
-		fullpath = cast(char *)path;
-	}
+	fullpath = realpath(path, NULL);
+
+	if (fullpath == NULL) {
+		// NOTE(Jeroen): Path doesn't exist.
+		if (gb_strlen(path) > 0 && path[0] == '/') {
+			// But it is an absolute path, so return as-is.
+
+			fullpath = (char *)path;
+			len      = gb_strlen(fullpath) + 1;
+			result   = gb_alloc_array(a, char, len + 1);
+
+			gb_memmove(result, fullpath, len);
+			result[len] = 0;
+
+		} else {
+			// Appears to be a relative path, so construct an absolute one relative to <cwd>.
+			char cwd[4096];
+			getcwd(&cwd[0], 4096);
+
+			isize path_len = gb_strlen(path);
+			isize cwd_len  = gb_strlen(cwd);
+			len            = cwd_len + 1 + path_len + 1;
+			result         = gb_alloc_array(a, char, len);
 
 
-	len = gb_strlen(fullpath);
+			gb_memmove(result, (void *)cwd, cwd_len);
+			result[cwd_len] = '/';
 
 
-	result = gb_alloc_array(a, char, len + 1);
-	gb_memmove(result, fullpath, len);
-	result[len] = 0;
-	free(p);
+			gb_memmove(result + cwd_len + 1, (void *)path, gb_strlen(path));
+			result[len] = 0;
 
 
+		}
+	} else {
+		len      = gb_strlen(fullpath) + 1;
+		result   = gb_alloc_array(a, char, len + 1);
+		gb_memmove(result, fullpath, len);
+		result[len] = 0;
+		free(fullpath);
+	}
 	return result;
 	return result;
 #endif
 #endif
 }
 }

+ 12 - 2
src/llvm_backend.cpp

@@ -967,7 +967,12 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime)
 }
 }
 
 
 String lb_filepath_ll_for_module(lbModule *m) {
 String lb_filepath_ll_for_module(lbModule *m) {
-	String path = m->gen->output_base;
+	String path = concatenate3_strings(permanent_allocator(),
+		build_context.build_paths[BuildPath_Output].basename,
+		STR_LIT("/"),
+		build_context.build_paths[BuildPath_Output].name
+	);
+
 	if (m->pkg) {
 	if (m->pkg) {
 		path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name);
 		path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name);
 	} else if (USE_SEPARATE_MODULES) {
 	} else if (USE_SEPARATE_MODULES) {
@@ -978,7 +983,12 @@ String lb_filepath_ll_for_module(lbModule *m) {
 	return path;
 	return path;
 }
 }
 String lb_filepath_obj_for_module(lbModule *m) {
 String lb_filepath_obj_for_module(lbModule *m) {
-	String path = m->gen->output_base;
+	String path = concatenate3_strings(permanent_allocator(),
+		build_context.build_paths[BuildPath_Output].basename,
+		STR_LIT("/"),
+		build_context.build_paths[BuildPath_Output].name
+	);
+
 	if (m->pkg) {
 	if (m->pkg) {
 		path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name);
 		path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name);
 	}
 	}

+ 0 - 1
src/llvm_backend_general.cpp

@@ -87,7 +87,6 @@ bool lb_init_generator(lbGenerator *gen, Checker *c) {
 		return false;
 		return false;
 	}
 	}
 
 
-
 	String init_fullpath = c->parser->init_fullpath;
 	String init_fullpath = c->parser->init_fullpath;
 
 
 	if (build_context.out_filepath.len == 0) {
 	if (build_context.out_filepath.len == 0) {

+ 72 - 80
src/main.cpp

@@ -46,7 +46,6 @@ gb_global Timings global_timings = {0};
 #include "checker.cpp"
 #include "checker.cpp"
 #include "docs.cpp"
 #include "docs.cpp"
 
 
-
 #include "llvm_backend.cpp"
 #include "llvm_backend.cpp"
 
 
 #if defined(GB_SYSTEM_OSX)
 #if defined(GB_SYSTEM_OSX)
@@ -57,16 +56,8 @@ gb_global Timings global_timings = {0};
 #endif
 #endif
 
 
 #include "query_data.cpp"
 #include "query_data.cpp"
-
-
-#if defined(GB_SYSTEM_WINDOWS)
-// NOTE(IC): In order to find Visual C++ paths without relying on environment variables.
-#include "microsoft_craziness.h"
-#endif
-
 #include "bug_report.cpp"
 #include "bug_report.cpp"
 
 
-
 // NOTE(bill): 'name' is used in debugging and profiling modes
 // NOTE(bill): 'name' is used in debugging and profiling modes
 i32 system_exec_command_line_app(char const *name, char const *fmt, ...) {
 i32 system_exec_command_line_app(char const *name, char const *fmt, ...) {
 	isize const cmd_cap = 64<<20; // 64 MiB should be more than enough
 	isize const cmd_cap = 64<<20; // 64 MiB should be more than enough
@@ -130,34 +121,35 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) {
 }
 }
 
 
 
 
-
-
 i32 linker_stage(lbGenerator *gen) {
 i32 linker_stage(lbGenerator *gen) {
 	i32 result = 0;
 	i32 result = 0;
 	Timings *timings = &global_timings;
 	Timings *timings = &global_timings;
 
 
-	String output_base = gen->output_base;
+	String output_filename = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]);
+	debugf("Linking %.*s\n", LIT(output_filename));
+
+	// TOOD(Jeroen): Make a `build_paths[BuildPath_Object] to avoid `%.*s.o`.
 
 
 	if (is_arch_wasm()) {
 	if (is_arch_wasm()) {
 		timings_start_section(timings, str_lit("wasm-ld"));
 		timings_start_section(timings, str_lit("wasm-ld"));
 
 
 	#if defined(GB_SYSTEM_WINDOWS)
 	#if defined(GB_SYSTEM_WINDOWS)
 		result = system_exec_command_line_app("wasm-ld",
 		result = system_exec_command_line_app("wasm-ld",
-			"\"%.*s\\bin\\wasm-ld\" \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s",
+			"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
 			LIT(build_context.ODIN_ROOT),
 			LIT(build_context.ODIN_ROOT),
-			LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
 	#else
 	#else
 		result = system_exec_command_line_app("wasm-ld",
 		result = system_exec_command_line_app("wasm-ld",
-			"wasm-ld \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s",
-			LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+			"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
 	#endif
 	#endif
 		return result;
 		return result;
 	}
 	}
 
 
 	if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) {
 	if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) {
-#ifdef GB_SYSTEM_UNIX
+#if defined(GB_SYSTEM_UNIX) 
 		result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
 		result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
-			LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
 #else
 #else
 		gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n",
 		gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n",
 			LIT(target_os_names[build_context.metrics.os]),
 			LIT(target_os_names[build_context.metrics.os]),
@@ -181,28 +173,11 @@ i32 linker_stage(lbGenerator *gen) {
 		gbString lib_str = gb_string_make(heap_allocator(), "");
 		gbString lib_str = gb_string_make(heap_allocator(), "");
 		defer (gb_string_free(lib_str));
 		defer (gb_string_free(lib_str));
 
 
-		char const *output_ext = "exe";
 		gbString link_settings = gb_string_make_reserve(heap_allocator(), 256);
 		gbString link_settings = gb_string_make_reserve(heap_allocator(), 256);
 		defer (gb_string_free(link_settings));
 		defer (gb_string_free(link_settings));
 
 
-
-		// NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest.
-		Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8();
-
-		if (find_result.windows_sdk_version == 0) {
-			gb_printf_err("Windows SDK not found.\n");
-			exit(1);
-		}
-
-		if (build_context.ignore_microsoft_magic) {
-			find_result = {};
-		}
-
 		// Add library search paths.
 		// Add library search paths.
-		if (find_result.vs_library_path.len > 0) {
-			GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0);
-			GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0);
-
+		if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) {
 			String path = {};
 			String path = {};
 			auto add_path = [&](String path) {
 			auto add_path = [&](String path) {
 				if (path[path.len-1] == '\\') {
 				if (path[path.len-1] == '\\') {
@@ -210,9 +185,9 @@ i32 linker_stage(lbGenerator *gen) {
 				}
 				}
 				link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path));
 				link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path));
 			};
 			};
-			add_path(find_result.windows_sdk_um_library_path);
-			add_path(find_result.windows_sdk_ucrt_library_path);
-			add_path(find_result.vs_library_path);
+			add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename);
+			add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename);
+			add_path(build_context.build_paths[BuildPath_VS_LIB].basename);
 		}
 		}
 
 
 
 
@@ -252,14 +227,14 @@ i32 linker_stage(lbGenerator *gen) {
 
 
 
 
 		if (build_context.build_mode == BuildMode_DynamicLibrary) {
 		if (build_context.build_mode == BuildMode_DynamicLibrary) {
-			output_ext = "dll";
 			link_settings = gb_string_append_fmt(link_settings, " /DLL");
 			link_settings = gb_string_append_fmt(link_settings, " /DLL");
 		} else {
 		} else {
 			link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup");
 			link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup");
 		}
 		}
 
 
 		if (build_context.pdb_filepath != "") {
 		if (build_context.pdb_filepath != "") {
-			link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(build_context.pdb_filepath));
+			String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]);
+			link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path));
 		}
 		}
 
 
 		if (build_context.no_crt) {
 		if (build_context.no_crt) {
@@ -300,13 +275,21 @@ i32 linker_stage(lbGenerator *gen) {
 			object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
 			object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
 		}
 		}
 
 
+		String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]);
+		defer (gb_free(heap_allocator(), vs_exe_path.text));
+
 		char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE";
 		char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE";
 		if (!build_context.use_lld) { // msvc
 		if (!build_context.use_lld) { // msvc
 			if (build_context.has_resource) {
 			if (build_context.has_resource) {
+				String rc_path  = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]);
+				String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]);
+				defer (gb_free(heap_allocator(), rc_path.text));
+				defer (gb_free(heap_allocator(), res_path.text));
+
 				result = system_exec_command_line_app("msvc-link",
 				result = system_exec_command_line_app("msvc-link",
-					"\"rc.exe\" /nologo /fo \"%.*s.res\" \"%.*s.rc\"",
-					LIT(output_base),
-					LIT(build_context.resource_filepath)
+					"\"rc.exe\" /nologo /fo \"%.*s\" \"%.*s\"",
+					LIT(res_path),
+					LIT(rc_path)
 				);
 				);
 
 
 				if (result) {
 				if (result) {
@@ -314,13 +297,13 @@ i32 linker_stage(lbGenerator *gen) {
 				}
 				}
 
 
 				result = system_exec_command_line_app("msvc-link",
 				result = system_exec_command_line_app("msvc-link",
-					"\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
+					"\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s "
 					"/nologo /incremental:no /opt:ref /subsystem:%s "
 					"/nologo /incremental:no /opt:ref /subsystem:%s "
 					" %.*s "
 					" %.*s "
 					" %.*s "
 					" %.*s "
 					" %s "
 					" %s "
 					"",
 					"",
-					LIT(find_result.vs_exe_path), object_files, LIT(output_base), LIT(output_base), output_ext,
+					LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename),
 					link_settings,
 					link_settings,
 					subsystem_str,
 					subsystem_str,
 					LIT(build_context.link_flags),
 					LIT(build_context.link_flags),
@@ -329,13 +312,13 @@ i32 linker_stage(lbGenerator *gen) {
 				  );
 				  );
 			} else {
 			} else {
 				result = system_exec_command_line_app("msvc-link",
 				result = system_exec_command_line_app("msvc-link",
-					"\"%.*slink.exe\" %s -OUT:\"%.*s.%s\" %s "
+					"\"%.*slink.exe\" %s -OUT:\"%.*s\" %s "
 					"/nologo /incremental:no /opt:ref /subsystem:%s "
 					"/nologo /incremental:no /opt:ref /subsystem:%s "
 					" %.*s "
 					" %.*s "
 					" %.*s "
 					" %.*s "
 					" %s "
 					" %s "
 					"",
 					"",
-					LIT(find_result.vs_exe_path), object_files, LIT(output_base), output_ext,
+					LIT(vs_exe_path), object_files, LIT(output_filename),
 					link_settings,
 					link_settings,
 					subsystem_str,
 					subsystem_str,
 					LIT(build_context.link_flags),
 					LIT(build_context.link_flags),
@@ -350,13 +333,13 @@ i32 linker_stage(lbGenerator *gen) {
 
 
 		} else { // lld
 		} else { // lld
 			result = system_exec_command_line_app("msvc-lld-link",
 			result = system_exec_command_line_app("msvc-lld-link",
-				"\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s.%s\" %s "
+				"\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s "
 				"/nologo /incremental:no /opt:ref /subsystem:%s "
 				"/nologo /incremental:no /opt:ref /subsystem:%s "
 				" %.*s "
 				" %.*s "
 				" %.*s "
 				" %.*s "
 				" %s "
 				" %s "
 				"",
 				"",
-				LIT(build_context.ODIN_ROOT), object_files, LIT(output_base),output_ext,
+				LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename),
 				link_settings,
 				link_settings,
 				subsystem_str,
 				subsystem_str,
 				LIT(build_context.link_flags),
 				LIT(build_context.link_flags),
@@ -415,7 +398,7 @@ i32 linker_stage(lbGenerator *gen) {
 				} else if (string_ends_with(lib, str_lit(".so"))) {
 				} else if (string_ends_with(lib, str_lit(".so"))) {
 					// dynamic lib, relative path to executable
 					// dynamic lib, relative path to executable
 					// NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible
 					// NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible
-					//                at runtimeto the executable
+					//                at runtime to the executable
 					lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib));
 					lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib));
 				} else {
 				} else {
 					// dynamic or static system lib, just link regularly searching system library paths
 					// dynamic or static system lib, just link regularly searching system library paths
@@ -431,9 +414,6 @@ i32 linker_stage(lbGenerator *gen) {
 			object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
 			object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
 		}
 		}
 
 
-		// Unlike the Win32 linker code, the output_ext includes the dot, because
-		// typically executable files on *NIX systems don't have extensions.
-		String output_ext = {};
 		gbString link_settings = gb_string_make_reserve(heap_allocator(), 32);
 		gbString link_settings = gb_string_make_reserve(heap_allocator(), 32);
 
 
 		if (build_context.no_crt) {
 		if (build_context.no_crt) {
@@ -461,26 +441,12 @@ i32 linker_stage(lbGenerator *gen) {
 			// correctly this way since all the other dependencies provided implicitly
 			// correctly this way since all the other dependencies provided implicitly
 			// by the compiler frontend are still needed and most of the command
 			// by the compiler frontend are still needed and most of the command
 			// line arguments prepared previously are incompatible with ld.
 			// line arguments prepared previously are incompatible with ld.
-			//
-			// Shared libraries are .dylib on MacOS and .so on Linux.
-			if (build_context.metrics.os == TargetOs_darwin) {
-				output_ext = STR_LIT(".dylib");
-			} else {
-				output_ext = STR_LIT(".so");
-			}
 			link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' ");
 			link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' ");
 			link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' ");
 			link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' ");
 		} else if (build_context.metrics.os != TargetOs_openbsd) {
 		} else if (build_context.metrics.os != TargetOs_openbsd) {
 			// OpenBSD defaults to PIE executable. do not pass -no-pie for it.
 			// OpenBSD defaults to PIE executable. do not pass -no-pie for it.
 			link_settings = gb_string_appendc(link_settings, "-no-pie ");
 			link_settings = gb_string_appendc(link_settings, "-no-pie ");
 		}
 		}
-		if (build_context.out_filepath.len > 0) {
-			//NOTE(thebirk): We have a custom -out arguments, so we should use the extension from that
-			isize pos = string_extension_position(build_context.out_filepath);
-			if (pos > 0) {
-				output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len);
-			}
-		}
 
 
 		gbString platform_lib_str = gb_string_make(heap_allocator(), "");
 		gbString platform_lib_str = gb_string_make(heap_allocator(), "");
 		defer (gb_string_free(platform_lib_str));
 		defer (gb_string_free(platform_lib_str));
@@ -507,7 +473,7 @@ i32 linker_stage(lbGenerator *gen) {
 		defer (gb_string_free(link_command_line));
 		defer (gb_string_free(link_command_line));
 
 
 		link_command_line = gb_string_appendc(link_command_line, object_files);
 		link_command_line = gb_string_appendc(link_command_line, object_files);
-		link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s%.*s\" ", LIT(output_base), LIT(output_ext));
+		link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename));
 		link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str);
 		link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str);
 		link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str);
 		link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str);
 		link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags));
 		link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags));
@@ -524,9 +490,7 @@ i32 linker_stage(lbGenerator *gen) {
 		if (build_context.ODIN_DEBUG) {
 		if (build_context.ODIN_DEBUG) {
 			// NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe
 			// NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe
 			// to the symbols in the object file
 			// to the symbols in the object file
-			result = system_exec_command_line_app("dsymutil",
-				"dsymutil %.*s%.*s", LIT(output_base), LIT(output_ext)
-			);
+			result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename));
 
 
 			if (result) {
 			if (result) {
 				return result;
 				return result;
@@ -1526,6 +1490,10 @@ bool parse_build_flags(Array<String> args) {
 									gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path));
 									gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path));
 									bad_flags = true;
 									bad_flags = true;
 									break;
 									break;
+								} else if (!gb_file_exists((const char *)path.text)) {
+									gb_printf_err("Invalid -resource path %.*s, file does not exist.\n", LIT(path));
+									bad_flags = true;
+									break;
 								}
 								}
 								build_context.resource_filepath = substring(path, 0, string_extension_position(path));
 								build_context.resource_filepath = substring(path, 0, string_extension_position(path));
 								build_context.has_resource = true;
 								build_context.has_resource = true;
@@ -1540,6 +1508,11 @@ bool parse_build_flags(Array<String> args) {
 							String path = value.value_string;
 							String path = value.value_string;
 							path = string_trim_whitespace(path);
 							path = string_trim_whitespace(path);
 							if (is_build_flag_path_valid(path)) {
 							if (is_build_flag_path_valid(path)) {
+								if (path_is_directory(path)) {
+									gb_printf_err("Invalid -pdb-name path. %.*s, is a directory.\n", LIT(path));
+									bad_flags = true;
+									break;									
+								}
 								// #if defined(GB_SYSTEM_WINDOWS)
 								// #if defined(GB_SYSTEM_WINDOWS)
 								// 	String ext = path_extension(path);
 								// 	String ext = path_extension(path);
 								// 	if (ext != ".pdb") {
 								// 	if (ext != ".pdb") {
@@ -2666,6 +2639,8 @@ int main(int arg_count, char const **arg_ptr) {
 		return 1;
 		return 1;
 	}
 	}
 
 
+	init_filename = copy_string(permanent_allocator(), init_filename);
+
 	if (init_filename == "-help" ||
 	if (init_filename == "-help" ||
 	    init_filename == "--help") {
 	    init_filename == "--help") {
 		build_context.show_help = true;
 		build_context.show_help = true;
@@ -2688,6 +2663,12 @@ int main(int arg_count, char const **arg_ptr) {
 				gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename));
 				gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename));
 				gb_printf_err("The `-file` flag tells it to treat a file as a self-contained package.\n");
 				gb_printf_err("The `-file` flag tells it to treat a file as a self-contained package.\n");
 				return 1;
 				return 1;
+			} else {
+				String const ext = str_lit(".odin");
+				if (!string_ends_with(init_filename, ext)) {
+					gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename));
+					return 1;
+				}
 			}
 			}
 		}
 		}
 	}
 	}
@@ -2709,13 +2690,24 @@ int main(int arg_count, char const **arg_ptr) {
 			get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared")));
 			get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared")));
 	}
 	}
 
 
-
 	init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr);
 	init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr);
 	// if (build_context.word_size == 4 && build_context.metrics.os != TargetOs_js) {
 	// if (build_context.word_size == 4 && build_context.metrics.os != TargetOs_js) {
 	// 	print_usage_line(0, "%.*s 32-bit is not yet supported for this platform", LIT(args[0]));
 	// 	print_usage_line(0, "%.*s 32-bit is not yet supported for this platform", LIT(args[0]));
 	// 	return 1;
 	// 	return 1;
 	// }
 	// }
 
 
+	// Set and check build paths...
+	if (!init_build_paths(init_filename)) {
+		return 1;
+	}
+
+	if (build_context.show_debug_messages) {
+		for_array(i, build_context.build_paths) {
+			String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]);
+			debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path));
+		}		
+	}
+
 	init_global_thread_pool();
 	init_global_thread_pool();
 	defer (thread_pool_destroy(&global_thread_pool));
 	defer (thread_pool_destroy(&global_thread_pool));
 
 
@@ -2732,6 +2724,8 @@ int main(int arg_count, char const **arg_ptr) {
 	}
 	}
 	defer (destroy_parser(parser));
 	defer (destroy_parser(parser));
 
 
+	// TODO(jeroen): Remove the `init_filename` param.
+	// Let's put that on `build_context.build_paths[0]` instead.
 	if (parse_packages(parser, init_filename) != ParseFile_None) {
 	if (parse_packages(parser, init_filename) != ParseFile_None) {
 		return 1;
 		return 1;
 	}
 	}
@@ -2810,16 +2804,14 @@ int main(int arg_count, char const **arg_ptr) {
 	}
 	}
 
 
 	if (run_output) {
 	if (run_output) {
+		String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]);
+		defer (gb_free(heap_allocator(), exe_name.text));
+
 	#if defined(GB_SYSTEM_WINDOWS)
 	#if defined(GB_SYSTEM_WINDOWS)
-		return system_exec_command_line_app("odin run", "%.*s.exe %.*s", LIT(gen->output_base), LIT(run_args_string));
+		return system_exec_command_line_app("odin run", "%.*s %.*s", LIT(exe_name), LIT(run_args_string));
 	#else
 	#else
-		//NOTE(thebirk): This whole thing is a little leaky
-		String output_ext = {};
-		String complete_path = concatenate_strings(permanent_allocator(), gen->output_base, output_ext);
-		complete_path = path_to_full_path(permanent_allocator(), complete_path);
-		return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(complete_path), LIT(run_args_string));
+		return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string));
 	#endif
 	#endif
 	}
 	}
-
 	return 0;
 	return 0;
 }
 }

+ 1 - 1
src/parser.cpp

@@ -5751,7 +5751,7 @@ ParseFileError parse_packages(Parser *p, String init_filename) {
 			}
 			}
 		}
 		}
 	}
 	}
-	
+
 
 
 	{ // Add these packages serially and then process them parallel
 	{ // Add these packages serially and then process them parallel
 		mutex_lock(&p->wait_mutex);
 		mutex_lock(&p->wait_mutex);

+ 394 - 0
src/path.cpp

@@ -0,0 +1,394 @@
+/*
+	Path handling utilities.
+*/
+String remove_extension_from_path(String const &s) {
+	for (isize i = s.len-1; i >= 0; i--) {
+		if (s[i] == '.') {
+			return substring(s, 0, i);
+		}
+	}
+	return s;
+}
+
+String remove_directory_from_path(String const &s) {
+	isize len = 0;
+	for (isize i = s.len-1; i >= 0; i--) {
+		if (s[i] == '/' ||
+		    s[i] == '\\') {
+			break;
+		}
+		len += 1;
+	}
+	return substring(s, s.len-len, s.len);
+}
+
+bool path_is_directory(String path);
+
+String directory_from_path(String const &s) {
+	if (path_is_directory(s)) {
+		return s;
+	}
+
+	isize i = s.len-1;
+	for (; i >= 0; i--) {
+		if (s[i] == '/' ||
+		    s[i] == '\\') {
+			break;
+		}
+	}
+	if (i >= 0) {
+		return substring(s, 0, i);	
+	}
+	return substring(s, 0, 0);
+}
+
+#if defined(GB_SYSTEM_WINDOWS)
+	bool path_is_directory(String path) {
+		gbAllocator a = heap_allocator();
+		String16 wstr = string_to_string16(a, path);
+		defer (gb_free(a, wstr.text));
+
+		i32 attribs = GetFileAttributesW(wstr.text);
+		if (attribs < 0) return false;
+
+		return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
+	}
+
+#else
+	bool path_is_directory(String path) {
+		gbAllocator a = heap_allocator();
+		char *copy = cast(char *)copy_string(a, path).text;
+		defer (gb_free(a, copy));
+
+		struct stat s;
+		if (stat(copy, &s) == 0) {
+			return (s.st_mode & S_IFDIR) != 0;
+		}
+		return false;
+	}
+#endif
+
+
+String path_to_full_path(gbAllocator a, String path) {
+	gbAllocator ha = heap_allocator();
+	char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
+	defer (gb_free(ha, path_c));
+
+	char *fullpath = gb_path_get_full_name(a, path_c);
+	String res = string_trim_whitespace(make_string_c(fullpath));
+#if defined(GB_SYSTEM_WINDOWS)
+	for (isize i = 0; i < res.len; i++) {
+		if (res.text[i] == '\\') {
+			res.text[i] = '/';
+		}
+	}
+#endif
+	return copy_string(a, res);
+}
+
+struct Path {
+	String basename;
+	String name;
+	String ext;
+};
+
+// NOTE(Jeroen): Naively turns a Path into a string.
+String path_to_string(gbAllocator a, Path path) {
+	if (path.basename.len + path.name.len + path.ext.len == 0) {
+		return make_string(nullptr, 0);
+	}
+
+	isize len = path.basename.len + 1 + path.name.len + 1;
+	if (path.ext.len > 0) {
+		 len += path.ext.len + 1;
+	}
+
+	u8 *str = gb_alloc_array(a, u8, len);
+
+	isize i = 0;
+	gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
+	gb_memmove(str+i, "/", 1);                                i += 1;
+	gb_memmove(str+i, path.name.text,     path.name.len);     i += path.name.len;
+	if (path.ext.len > 0) {
+		gb_memmove(str+i, ".", 1);                            i += 1;
+		gb_memmove(str+i, path.ext.text,  path.ext.len);      i += path.ext.len;
+	}
+	str[i] = 0;
+
+	String res = make_string(str, i);
+	res        = string_trim_whitespace(res);
+	return res;
+}
+
+// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
+String path_to_full_path(gbAllocator a, Path path) {
+	String temp = path_to_string(heap_allocator(), path);
+	defer (gb_free(heap_allocator(), temp.text));
+
+	return path_to_full_path(a, temp);
+}
+
+// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
+// and then breaks it into its components to make a Path.
+Path path_from_string(gbAllocator a, String const &path) {
+	Path res = {};
+
+	if (path.len == 0) return res;
+
+	String fullpath = path_to_full_path(a, path);
+	defer (gb_free(heap_allocator(), fullpath.text));
+
+	res.basename = directory_from_path(fullpath);	
+	res.basename = copy_string(a, res.basename);
+
+	if (path_is_directory(fullpath)) {
+		// It's a directory. We don't need to tinker with the name and extension.
+		// It could have a superfluous trailing `/`. Remove it if so.
+		if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
+			res.basename.len--;
+		}
+		return res;
+	}
+
+	isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
+	res.name         = substring(fullpath, name_start, fullpath.len);
+	res.name         = remove_extension_from_path(res.name);
+	res.name         = copy_string(a, res.name);
+
+	res.ext          = path_extension(fullpath, false); // false says not to include the dot.
+	res.ext          = copy_string(a, res.ext);
+	return res;
+}
+
+// NOTE(Jeroen): Takes a path String and returns the last path element.
+String last_path_element(String const &path) {
+	isize count = 0;
+	u8 * start = (u8 *)(&path.text[path.len - 1]);
+	for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
+		count++;
+		start--;
+	}
+	if (count > 0) {
+		start++; // Advance past the `/` and return the substring.
+		String res = make_string(start, count);
+		return res;
+	}
+	// Must be a root path like `/` or `C:/`, return empty String.
+	return STR_LIT("");
+}
+
+bool path_is_directory(Path path) {
+	String path_string = path_to_full_path(heap_allocator(), path);
+	defer (gb_free(heap_allocator(), path_string.text));
+
+	return path_is_directory(path_string);
+}
+
+struct FileInfo {
+	String name;
+	String fullpath;
+	i64    size;
+	bool   is_dir;
+};
+
+enum ReadDirectoryError {
+	ReadDirectory_None,
+
+	ReadDirectory_InvalidPath,
+	ReadDirectory_NotExists,
+	ReadDirectory_Permission,
+	ReadDirectory_NotDir,
+	ReadDirectory_Empty,
+	ReadDirectory_Unknown,
+
+	ReadDirectory_COUNT,
+};
+
+i64 get_file_size(String path) {
+	char *c_str = alloc_cstring(heap_allocator(), path);
+	defer (gb_free(heap_allocator(), c_str));
+
+	gbFile f = {};
+	gbFileError err = gb_file_open(&f, c_str);
+	defer (gb_file_close(&f));
+	if (err != gbFileError_None) {
+		return -1;
+	}
+	return gb_file_size(&f);
+}
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
+	GB_ASSERT(fi != nullptr);
+
+	gbAllocator a = heap_allocator();
+
+	while (path.len > 0) {
+		Rune end = path[path.len-1];
+		if (end == '/') {
+			path.len -= 1;
+		} else if (end == '\\') {
+			path.len -= 1;
+		} else {
+			break;
+		}
+	}
+
+	if (path.len == 0) {
+		return ReadDirectory_InvalidPath;
+	}
+	{
+		char *c_str = alloc_cstring(a, path);
+		defer (gb_free(a, c_str));
+
+		gbFile f = {};
+		gbFileError file_err = gb_file_open(&f, c_str);
+		defer (gb_file_close(&f));
+
+		switch (file_err) {
+		case gbFileError_Invalid:    return ReadDirectory_InvalidPath;
+		case gbFileError_NotExists:  return ReadDirectory_NotExists;
+		// case gbFileError_Permission: return ReadDirectory_Permission;
+		}
+	}
+
+	if (!path_is_directory(path)) {
+		return ReadDirectory_NotDir;
+	}
+
+
+	char *new_path = gb_alloc_array(a, char, path.len+3);
+	defer (gb_free(a, new_path));
+
+	gb_memmove(new_path, path.text, path.len);
+	gb_memmove(new_path+path.len, "/*", 2);
+	new_path[path.len+2] = 0;
+
+	String np = make_string(cast(u8 *)new_path, path.len+2);
+	String16 wstr = string_to_string16(a, np);
+	defer (gb_free(a, wstr.text));
+
+	WIN32_FIND_DATAW file_data = {};
+	HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
+	if (find_file == INVALID_HANDLE_VALUE) {
+		return ReadDirectory_Unknown;
+	}
+	defer (FindClose(find_file));
+
+	array_init(fi, a, 0, 100);
+
+	do {
+		wchar_t *filename_w = file_data.cFileName;
+		i64 size = cast(i64)file_data.nFileSizeLow;
+		size |= (cast(i64)file_data.nFileSizeHigh) << 32;
+		String name = string16_to_string(a, make_string16_c(filename_w));
+		if (name == "." || name == "..") {
+			gb_free(a, name.text);
+			continue;
+		}
+
+		String filepath = {};
+		filepath.len = path.len+1+name.len;
+		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
+		defer (gb_free(a, filepath.text));
+		gb_memmove(filepath.text, path.text, path.len);
+		gb_memmove(filepath.text+path.len, "/", 1);
+		gb_memmove(filepath.text+path.len+1, name.text, name.len);
+
+		FileInfo info = {};
+		info.name = name;
+		info.fullpath = path_to_full_path(a, filepath);
+		info.size = size;
+		info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+		array_add(fi, info);
+	} while (FindNextFileW(find_file, &file_data));
+
+	if (fi->count == 0) {
+		return ReadDirectory_Empty;
+	}
+
+	return ReadDirectory_None;
+}
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
+
+#include <dirent.h>
+
+ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
+	GB_ASSERT(fi != nullptr);
+
+	gbAllocator a = heap_allocator();
+
+	char *c_path = alloc_cstring(a, path);
+	defer (gb_free(a, c_path));
+
+	DIR *dir = opendir(c_path);
+	if (!dir) {
+		switch (errno) {
+		case ENOENT:
+			return ReadDirectory_NotExists;
+		case EACCES:
+			return ReadDirectory_Permission;
+		case ENOTDIR:
+			return ReadDirectory_NotDir;
+		default:
+			// ENOMEM: out of memory
+			// EMFILE: per-process limit on open fds reached
+			// ENFILE: system-wide limit on total open files reached
+			return ReadDirectory_Unknown;
+		}
+		GB_PANIC("unreachable");
+	}
+
+	array_init(fi, a, 0, 100);
+
+	for (;;) {
+		struct dirent *entry = readdir(dir);
+		if (entry == nullptr) {
+			break;
+		}
+
+		String name = make_string_c(entry->d_name);
+		if (name == "." || name == "..") {
+			continue;
+		}
+
+		String filepath = {};
+		filepath.len = path.len+1+name.len;
+		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
+		defer (gb_free(a, filepath.text));
+		gb_memmove(filepath.text, path.text, path.len);
+		gb_memmove(filepath.text+path.len, "/", 1);
+		gb_memmove(filepath.text+path.len+1, name.text, name.len);
+		filepath.text[filepath.len] = 0;
+
+
+		struct stat dir_stat = {};
+
+		if (stat((char *)filepath.text, &dir_stat)) {
+			continue;
+		}
+
+		if (S_ISDIR(dir_stat.st_mode)) {
+			continue;
+		}
+
+		i64 size = dir_stat.st_size;
+
+		FileInfo info = {};
+		info.name = name;
+		info.fullpath = path_to_full_path(a, filepath);
+		info.size = size;
+		array_add(fi, info);
+	}
+
+	if (fi->count == 0) {
+		return ReadDirectory_Empty;
+	}
+
+	return ReadDirectory_None;
+}
+#else
+#error Implement read_directory
+#endif
+

+ 2 - 35
src/string.cpp

@@ -245,15 +245,14 @@ gb_inline isize string_extension_position(String const &str) {
 	return dot_pos;
 	return dot_pos;
 }
 }
 
 
-String path_extension(String const &str) {
+String path_extension(String const &str, bool include_dot = true) {
 	isize pos = string_extension_position(str);
 	isize pos = string_extension_position(str);
 	if (pos < 0) {
 	if (pos < 0) {
 		return make_string(nullptr, 0);
 		return make_string(nullptr, 0);
 	}
 	}
-	return substring(str, pos, str.len);
+	return substring(str, include_dot ? pos : pos + 1, str.len);
 }
 }
 
 
-
 String string_trim_whitespace(String str) {
 String string_trim_whitespace(String str) {
 	while (str.len > 0 && rune_is_whitespace(str[str.len-1])) {
 	while (str.len > 0 && rune_is_whitespace(str[str.len-1])) {
 		str.len--;
 		str.len--;
@@ -299,38 +298,6 @@ String filename_from_path(String s) {
 	return make_string(nullptr, 0);
 	return make_string(nullptr, 0);
 }
 }
 
 
-String remove_extension_from_path(String const &s) {
-	for (isize i = s.len-1; i >= 0; i--) {
-		if (s[i] == '.') {
-			return substring(s, 0, i);
-		}
-	}
-	return s;
-}
-
-String remove_directory_from_path(String const &s) {
-	isize len = 0;
-	for (isize i = s.len-1; i >= 0; i--) {
-		if (s[i] == '/' ||
-		    s[i] == '\\') {
-			break;
-		}
-		len += 1;
-	}
-	return substring(s, s.len-len, s.len);
-}
-
-String directory_from_path(String const &s) {
-	isize i = s.len-1;
-	for (; i >= 0; i--) {
-		if (s[i] == '/' ||
-		    s[i] == '\\') {
-			break;
-		}
-	}
-	return substring(s, 0, i);
-}
-
 String concatenate_strings(gbAllocator a, String const &x, String const &y) {
 String concatenate_strings(gbAllocator a, String const &x, String const &y) {
 	isize len = x.len+y.len;
 	isize len = x.len+y.len;
 	u8 *data = gb_alloc_array(a, u8, len+1);
 	u8 *data = gb_alloc_array(a, u8, len+1);

+ 14 - 14
tests/core/Makefile

@@ -8,39 +8,39 @@ download_test_assets:
 	$(PYTHON) download_assets.py
 	$(PYTHON) download_assets.py
 
 
 image_test:
 image_test:
-	$(ODIN) run image/test_core_image.odin -file
+	$(ODIN) run image/test_core_image.odin -file -out:test_core_image
 
 
 compress_test:
 compress_test:
-	$(ODIN) run compress/test_core_compress.odin -file
+	$(ODIN) run compress/test_core_compress.odin -file -out:test_core_compress
 
 
 strings_test:
 strings_test:
-	$(ODIN) run strings/test_core_strings.odin -file
+	$(ODIN) run strings/test_core_strings.odin -file -out:test_core_strings
 
 
 hash_test:
 hash_test:
-	$(ODIN) run hash -out=test_hash -o:speed -no-bounds-check
+	$(ODIN) run hash -o:speed -no-bounds-check -out:test_hash 
 
 
 crypto_test:
 crypto_test:
-	$(ODIN) run crypto -out=test_crypto_hash -o:speed -no-bounds-check
+	$(ODIN) run crypto -o:speed -no-bounds-check -out:test_crypto_hash 
 
 
 noise_test:
 noise_test:
-	$(ODIN) run math/noise -out=test_noise
+	$(ODIN) run math/noise -out:test_noise
 
 
 encoding_test:
 encoding_test:
-	$(ODIN) run encoding/hxa -out=test_hxa -collection:tests=..
-	$(ODIN) run encoding/json -out=test_json
-	$(ODIN) run encoding/varint -out=test_varint
+	$(ODIN) run encoding/hxa -collection:tests=.. -out:test_hxa
+	$(ODIN) run encoding/json -out:test_json
+	$(ODIN) run encoding/varint -out:test_varint
 
 
 math_test:
 math_test:
-	$(ODIN) run math/test_core_math.odin -out=test_core_math -file -collection:tests=..
+	$(ODIN) run math/test_core_math.odin -file -collection:tests=.. -out:test_core_math
 
 
 linalg_glsl_math_test:
 linalg_glsl_math_test:
-	$(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=test_linalg_glsl_math -collection:tests=..
+	$(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -collection:tests=.. -out:test_linalg_glsl_math
 
 
 filepath_test:
 filepath_test:
-	$(ODIN) run path/filepath/test_core_filepath.odin -file -out=test_core_filepath -collection:tests=..
+	$(ODIN) run path/filepath/test_core_filepath.odin -file -collection:tests=.. -out:test_core_filepath
 
 
 reflect_test:
 reflect_test:
-	$(ODIN) run reflect/test_core_reflect.odin -file -out=test_core_reflect -collection:tests=..
+	$(ODIN) run reflect/test_core_reflect.odin -file -collection:tests=.. -out:test_core_reflect
 
 
 os_exit_test:
 os_exit_test:
-	$(ODIN) run os/test_core_os_exit.odin -file -out=test_core_os_exit && exit 1 || exit 0
+	$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0

+ 14 - 14
tests/core/build.bat

@@ -5,61 +5,61 @@ python3 download_assets.py
 echo ---
 echo ---
 echo Running core:image tests
 echo Running core:image tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run image    %COMMON%
+%PATH_TO_ODIN% run image    %COMMON% -out:test_core_image.exe
 
 
 echo ---
 echo ---
 echo Running core:compress tests
 echo Running core:compress tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run compress %COMMON%
+%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe
 
 
 echo ---
 echo ---
 echo Running core:strings tests
 echo Running core:strings tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run strings %COMMON%
+%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe
 
 
 echo ---
 echo ---
 echo Running core:hash tests
 echo Running core:hash tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run hash %COMMON% -o:size
+%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe
 
 
 echo ---
 echo ---
 echo Running core:odin tests
 echo Running core:odin tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run odin %COMMON% -o:size
+%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe
 
 
 echo ---
 echo ---
 echo Running core:crypto hash tests
 echo Running core:crypto hash tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run crypto %COMMON%
+%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe
 
 
 echo ---
 echo ---
 echo Running core:encoding tests
 echo Running core:encoding tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run encoding/hxa %COMMON%
-%PATH_TO_ODIN% run encoding/json %COMMON%
-%PATH_TO_ODIN% run encoding/varint %COMMON%
+%PATH_TO_ODIN% run encoding/hxa %COMMON% -out:test_hxa.exe
+%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe
+%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe
 
 
 echo ---
 echo ---
 echo Running core:math/noise tests
 echo Running core:math/noise tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run math/noise %COMMON%
+%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe
 
 
 echo ---
 echo ---
 echo Running core:math tests
 echo Running core:math tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run math %COMMON%
+%PATH_TO_ODIN% run math %COMMON% -out:test_core_math.exe
 
 
 echo ---
 echo ---
 echo Running core:math/linalg/glsl tests
 echo Running core:math/linalg/glsl tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run math/linalg/glsl %COMMON%
+%PATH_TO_ODIN% run math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe
 
 
 echo ---
 echo ---
 echo Running core:path/filepath tests
 echo Running core:path/filepath tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run path/filepath %COMMON%
+%PATH_TO_ODIN% run path/filepath %COMMON% -out:test_core_filepath.exe
 
 
 echo ---
 echo ---
 echo Running core:reflect tests
 echo Running core:reflect tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run reflect %COMMON%
+%PATH_TO_ODIN% run reflect %COMMON% -out:test_core_reflect.exe

+ 18 - 15
tests/core/download_assets.py

@@ -5,8 +5,9 @@ import sys
 import os
 import os
 import zipfile
 import zipfile
 
 
+TEST_SUITES        = ['PNG', 'XML']
 DOWNLOAD_BASE_PATH = "assets/{}"
 DOWNLOAD_BASE_PATH = "assets/{}"
-ASSETS_BASE_URL    = "https://raw.githubusercontent.com/Kelimion/compress-odin/master/tests/assets/{}/{}"
+ASSETS_BASE_URL    = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
 PNG_IMAGES         = [
 PNG_IMAGES         = [
 	"basi0g01.png", "basi0g02.png", "basi0g04.png", "basi0g08.png", "basi0g16.png", "basi2c08.png",
 	"basi0g01.png", "basi0g02.png", "basi0g04.png", "basi0g08.png", "basi0g16.png", "basi2c08.png",
 	"basi2c16.png", "basi3p01.png", "basi3p02.png", "basi3p04.png", "basi3p08.png", "basi4a08.png",
 	"basi2c16.png", "basi3p01.png", "basi3p02.png", "basi3p04.png", "basi3p08.png", "basi4a08.png",
@@ -73,25 +74,27 @@ def try_download_and_unpack_zip(suite):
 		print("Could not extract ZIP file")
 		print("Could not extract ZIP file")
 		return 2
 		return 2
 
 
-
 def main():
 def main():
-	print("Downloading PNG assets")
+	for suite in TEST_SUITES:
+		print("Downloading {} assets".format(suite))
 
 
-	# Make PNG assets path
-	try:
-		path = DOWNLOAD_BASE_PATH.format("PNG")
-		os.makedirs(path)
-	except FileExistsError:
-		pass
+		# Make assets path
+		try:
+			path = DOWNLOAD_BASE_PATH.format(suite)
+			os.makedirs(path)
+		except FileExistsError:
+			pass
+
+		# Try downloading and unpacking the assets
+		r = try_download_and_unpack_zip(suite)
+		if r is not None:
+			return r
+
+		# We could fall back on downloading the PNG files individually, but it's slow
+		print("Done downloading {} assets.".format(suite))
 
 
-	# Try downloading and unpacking the PNG assets
-	r = try_download_and_unpack_zip("PNG")
-	if r is not None:
-		return r
 
 
-	# We could fall back on downloading the PNG files individually, but it's slow
 
 
-	print("Done downloading PNG assets")
 	return 0
 	return 0
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':

+ 1 - 1
tests/core/math/big/build.bat

@@ -4,7 +4,7 @@ set PATH_TO_ODIN==..\..\..\..\odin
 set TEST_ARGS=-fast-tests
 set TEST_ARGS=-fast-tests
 set TEST_ARGS=-no-random
 set TEST_ARGS=-no-random
 set TEST_ARGS=
 set TEST_ARGS=
-set OUT_NAME=math_big_test_library
+set OUT_NAME=math_big_test_library.dll
 set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style
 set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style
 echo ---
 echo ---
 echo Running core:math/big tests
 echo Running core:math/big tests

+ 7 - 7
tests/issues/run.bat

@@ -1,17 +1,17 @@
 @echo off
 @echo off
 
 
-if not exist "tests\issues\build\" mkdir tests\issues\build
+if not exist "build\" mkdir build
 
 
-set COMMON=-collection:tests=tests -out:tests\issues\build\test_issue
+set COMMON=-collection:tests=.. -out:build\test_issue.exe
 
 
 @echo on
 @echo on
 
 
-.\odin build tests\issues\test_issue_829.odin %COMMON% -file
-tests\issues\build\test_issue
+..\..\odin build test_issue_829.odin %COMMON% -file
+build\test_issue
 
 
-.\odin build tests\issues\test_issue_1592.odin %COMMON% -file
-tests\issues\build\test_issue
+..\..\odin build test_issue_1592.odin %COMMON% -file
+build\test_issue
 
 
 @echo off
 @echo off
 
 
-rmdir /S /Q tests\issues\build
+rmdir /S /Q build

+ 8 - 8
tests/issues/run.sh

@@ -1,18 +1,18 @@
 #!/bin/bash
 #!/bin/bash
 set -eu
 set -eu
 
 
-mkdir -p tests/issues/build
-
-COMMON="-collection:tests=tests -out:tests/issues/build/test_issue"
+mkdir -p build
+ODIN=../../odin
+COMMON="-collection:tests=.. -out:build/test_issue"
 
 
 set -x
 set -x
 
 
-./odin build tests/issues/test_issue_829.odin $COMMON -file
-tests/issues/build/test_issue
+$ODIN build test_issue_829.odin $COMMON -file
+./build/test_issue
 
 
-./odin build tests/issues/test_issue_1592.odin $COMMON -file
-tests/issues/build/test_issue
+$ODIN build test_issue_1592.odin $COMMON -file
+./build/test_issue
 
 
 set +x
 set +x
 
 
-rm -rf tests/issues/build
+rm -rf build

+ 1 - 1
tests/vendor/Makefile

@@ -10,4 +10,4 @@ endif
 all: botan_test
 all: botan_test
 
 
 botan_test:
 botan_test:
-	$(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check $(ODINFLAGS)
+	$(ODIN) run botan -o:speed -no-bounds-check $(ODINFLAGS) -out=vendor_botan

+ 2 - 2
tests/vendor/build.bat

@@ -5,9 +5,9 @@ set PATH_TO_ODIN==..\..\odin
 echo ---
 echo ---
 echo Running vendor:botan tests
 echo Running vendor:botan tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run botan %COMMON%
+%PATH_TO_ODIN% run botan %COMMON% -out:vendor_botan.exe
 
 
 echo ---
 echo ---
 echo Running vendor:glfw tests
 echo Running vendor:glfw tests
 echo ---
 echo ---
-%PATH_TO_ODIN% run glfw %COMMON%
+%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe