2023-08-29 03:15:16 +00:00
import hashlib
import json
import os
import re
import shutil
from . log import *
from . utils import yeetdir
from . version import src_dir , orca_version
def attach_source_commands ( subparsers ) :
source_cmd = subparsers . add_parser ( " source " , help = " Commands for helping compile the Orca source code into your project. " )
2023-09-16 20:46:00 +00:00
source_sub = source_cmd . add_subparsers ( required = True , title = " commands " )
2023-08-29 03:15:16 +00:00
cflags_cmd = source_sub . add_parser ( " cflags " , help = " Get help setting up a C or C++ compiler to compile the Orca source. " )
cflags_cmd . add_argument ( " srcdir " , nargs = " ? " , default = src_dir ( ) , help = " the directory containing the Orca source code (defaults to system installation) " )
cflags_cmd . set_defaults ( func = shellish ( cflags ) )
vendor_cmd = source_sub . add_parser ( " vendor " , help = " Copy the Orca source code into your project. " )
vendor_cmd . add_argument ( " dir " , type = str , help = " the directory into which the Orca source code will be copied " )
vendor_cmd . set_defaults ( func = shellish ( vendor ) )
def vendor ( args ) :
# Verify that we are ok to vendor into the requested dir.
if os . path . exists ( args . dir ) :
try :
with open ( vendor_file_path ( args . dir ) , " r " ) as f :
vendor_info = json . load ( f )
version = vendor_info [ " version " ]
print ( f " Orca version { version } is currently installed in that directory. " )
if vendor_checksum ( args . dir ) != vendor_info [ " checksum " ] :
log_error ( f " The contents of your vendor directory have been modified. This command will exit to avoid overwriting any local changes. To proceed, manually delete { args . dir } and try again. " )
exit ( 1 )
except FileNotFoundError :
if len ( os . listdir ( args . dir ) ) > 0 :
log_error ( f " The requested directory already exists and does not appear to contain Orca source code. To avoid deleting anything important, please either provide the correct path or manually empty { args . dir } first. " )
exit ( 1 )
yeetdir ( args . dir )
shutil . copytree ( src_dir ( ) , args . dir )
with open ( vendor_file_path ( args . dir ) , " w " ) as f :
json . dump ( {
" version " : orca_version ( ) ,
" checksum " : vendor_checksum ( args . dir ) ,
} , f , indent = 2 )
print ( f " Version { orca_version ( ) } of the Orca source code has been copied to { args . dir } . " )
def vendor_file_path ( vendor_dir ) :
return os . path . join ( vendor_dir , " .orcavendor " )
def vendor_checksum ( dir ) :
return dirhash ( dir , excluded_extensions = [ " orcavendor " ] )
def cflags ( args ) :
if not os . path . exists ( os . path . join ( args . srcdir , " orca.h " ) ) :
log_error ( f " The provided path does not seem to contain the Orca source code: { args . srcdir } " )
exit ( 1 )
def path_contains ( a , b ) :
a_abs = os . path . abspath ( a )
b_abs = os . path . abspath ( b )
return os . path . commonpath ( [ a_abs , b_abs ] ) == a_abs
def nicepath ( path ) :
path_abs = os . path . abspath ( path )
if path_contains ( os . getcwd ( ) , path_abs ) :
return os . path . relpath ( path_abs )
else :
return path_abs
include = nicepath ( args . srcdir )
orcac = nicepath ( os . path . join ( args . srcdir , " orca.c " ) )
extinclude = nicepath ( os . path . join ( args . srcdir , " ext " ) )
sysinclude = nicepath ( os . path . join ( args . srcdir , " libc-shim/include " ) )
libcsource = nicepath ( os . path . join ( args . srcdir , " libc-shim/src/*.c " ) )
print ( " To compile Orca as part of your C or C++ project, you must: " )
print ( f " > Put the following directory on your SYSTEM include search path: " )
print ( f " { sysinclude } " )
print ( f " > Put the following directories on your include search path: " )
print ( f " { include } " )
print ( f " { extinclude } " )
print ( f " > Compile the following file as a single translation unit: " )
print ( f " { orcac } " )
print ( f " > Compile the following files as separate translation units: " )
print ( f " { libcsource } " )
print ( )
print ( " The following clang flags are also required: " )
print ( " > --target=wasm32 (to compile to wasm) " )
print ( " > --no-standard-libraries (to use only our libc shim) " )
print ( " > -mbulk-memory (to enable memset/memcpy intrinsics, which are required) " )
print ( " > -D__ORCA__ (to signal that the Orca source code is being compiled to run on Orca itself) " )
print ( " > -Wl,--no-entry (to prevent wasm-ld from looking for a _start symbol) " )
print ( " > -Wl,--export-dynamic (to expose your module ' s functions to Orca) " )
print ( )
print ( " And the following clang flags are recommended: " )
print ( " > -g -O2 (to compile with optimizations and debug info) " )
print ( )
print ( " Complete clang example: " )
print ( )
2023-09-16 20:14:19 +00:00
print ( f " clang --target=wasm32 --no-standard-libraries -mbulk-memory -g -O2 -D__ORCA__ -Wl,--no-entry -Wl,--export-dynamic -isystem \" { sysinclude } \" -I \" { include } \" -I \" { extinclude } \" \" { orcac } \" \" { libcsource } \" your-main.c " )
2023-08-29 03:15:16 +00:00
print ( )
if not path_contains ( os . getcwd ( ) , args . srcdir ) :
print ( " If these paths look crazy to you, consider vendoring the source code into your " )
print ( " project using `orca source vendor`. " )
print ( )
# -----------------------------------------------------------------------------
# Directory-hashing implementation pulled from the checksumdir package on pypi.
# Licensed under the MIT license.
# -----------------------------------------------------------------------------
def dirhash (
dirname ,
hash_func = hashlib . sha1 ,
excluded_files = None ,
ignore_hidden = False ,
followlinks = False ,
excluded_extensions = None ,
include_paths = False
) :
if not excluded_files :
excluded_files = [ ]
if not excluded_extensions :
excluded_extensions = [ ]
if not os . path . isdir ( dirname ) :
raise TypeError ( " {} is not a directory. " . format ( dirname ) )
hashvalues = [ ]
for root , dirs , files in os . walk ( dirname , topdown = True , followlinks = followlinks ) :
if ignore_hidden and re . search ( r " / \ . " , root ) :
continue
dirs . sort ( )
files . sort ( )
for fname in files :
if ignore_hidden and fname . startswith ( " . " ) :
continue
if fname . split ( " . " ) [ - 1 : ] [ 0 ] in excluded_extensions :
continue
if fname in excluded_files :
continue
hashvalues . append ( _filehash ( os . path . join ( root , fname ) , hash_func ) )
if include_paths :
hasher = hash_func ( )
# get the resulting relative path into array of elements
path_list = os . path . relpath ( os . path . join ( root , fname ) ) . split ( os . sep )
# compute the hash on joined list, removes all os specific separators
hasher . update ( ' ' . join ( path_list ) . encode ( ' utf-8 ' ) )
hashvalues . append ( hasher . hexdigest ( ) )
return _reduce_hash ( hashvalues , hash_func )
def _filehash ( filepath , hashfunc ) :
hasher = hashfunc ( )
blocksize = 64 * 1024
if not os . path . exists ( filepath ) :
return hasher . hexdigest ( )
with open ( filepath , " rb " ) as fp :
while True :
data = fp . read ( blocksize )
if not data :
break
hasher . update ( data )
return hasher . hexdigest ( )
def _reduce_hash ( hashlist , hashfunc ) :
hasher = hashfunc ( )
for hashvalue in sorted ( hashlist ) :
hasher . update ( hashvalue . encode ( " utf-8 " ) )
return hasher . hexdigest ( )