diff options
author | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2017-07-20 17:01:01 +0200 |
---|---|---|
committer | Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> | 2017-07-20 17:01:01 +0200 |
commit | 2aa28ebaf493e8c75ac5114a7f2a9280c04b3654 (patch) | |
tree | d715699f7868a424dc6a514c3808d28541690359 | |
parent | 9bd5ee0017cb7ddbcbd1dca50e292e68c6eca312 (diff) |
Continuing to correct the tools.
-rw-r--r-- | tools/Internals/exceptions.py | 148 | ||||
-rw-r--r-- | tools/Internals/locale.py | 14 | ||||
-rw-r--r-- | tools/Internals/locales/en_US.yml | 41 | ||||
-rwxr-xr-x | tools/Internals/module.py | 133 | ||||
-rwxr-xr-x | tools/Internals/suph.py | 23 | ||||
-rwxr-xr-x | tools/Internals/tools/__init__.py | 128 | ||||
-rwxr-xr-x | tools/Internals/tools/__util__.py | 23 | ||||
-rw-r--r-- | tools/Internals/tools/gnu_ar.py | 100 | ||||
-rw-r--r-- | tools/Internals/tools/gnu_as.py | 85 | ||||
-rwxr-xr-x | tools/Internals/tools/gnu_gcc.py | 159 | ||||
-rwxr-xr-x | tools/Internals/tools/renesas_asmsh.py | 24 | ||||
-rwxr-xr-x | tools/Internals/tools/renesas_optlnk.py | 29 | ||||
-rwxr-xr-x | tools/Internals/tools/renesas_shc.py | 46 | ||||
-rwxr-xr-x | tools/configure.py | 16 | ||||
-rwxr-xr-x | tools/make.py | 27 |
15 files changed, 744 insertions, 252 deletions
diff --git a/tools/Internals/exceptions.py b/tools/Internals/exceptions.py index 5b2d6d7..2a18a56 100644 --- a/tools/Internals/exceptions.py +++ b/tools/Internals/exceptions.py @@ -4,6 +4,7 @@ """ import sys +from .locale import * #*****************************************************************************# # Main definitions # @@ -16,10 +17,29 @@ import sys class PotatoException(Exception): ''' The base class for PotatoSDK exceptions. ''' + ret = 1 + ags = () + msg = None + + def __init__(self, *args): + if self.msg == None: + message = "Internal exception '%s'"%type(self).__name__ + else: + if len(args) != len(self.ags): + raise TypeError("Expected arguments: '%s'"%"', '".join(self.ags)) + + try: args = self.prepare(args) + except: pass + + message = self.msg % args + + super(PotatoException, self).__init__(message) + class PotatoWarning(Exception): ''' The base class for PotatoSDK warnings. ''' - pass + def __init__(self, *args): + super(PotatoWarning, self).__init__(self.message % args) # Function to raise one of these. @@ -28,7 +48,7 @@ def Raise(exc): try: raise exc except PotatoWarning as e: - print("WARNING: %s"%str(e)) + print(loc['prefix']['warning'], str(e), file=sys.stderr) # Main function wrapper. @@ -40,10 +60,17 @@ def do_main(main_func): except SilentException: exit(1) except PotatoException as e: - print("ERROR: %s"%str(e), file=sys.stderr) + print(loc['prefix']['error'], str(e), file=sys.stderr) exit(e.ret) #*****************************************************************************# -# Exceptions definitions # +# Internal exceptions # +#*****************************************************************************# +class ToolNotFoundException(PotatoException): + """ (internal) A tool was not found! """ + + ret = 2 +#*****************************************************************************# +# 'Public' exceptions # #*****************************************************************************# class SilentException(PotatoException): """ The silent exception, for exiting silently (usually because the @@ -56,95 +83,110 @@ class UnsupportedLanguageException(PotatoException): """ One of the given languages were unsupported. """ ret = 2 - def __init__(self, lang): - super(UnsupportedLanguageException, self).__init__("""\ -Language '%s' is unsupported."""%lang) + ags = ('language',) + msg = loc['exceptions']['UnsupportedLanguage'] class UnsupportedArchException(PotatoException): """ The given arch was unsupported. """ ret = 2 - def __init__(self, arch): - super(UnsupportedArchException, self).__init__("""\ -Architecture '%s' is unsupported."""%arch) - -class UnsupportedPlatformException(PotatoException): - """ The given platform was unsupported. """ - - ret = 2 - def __init__(self, platform): - super(UnsupportedPlatformException, self).__init__("""\ -Platform '%s' is unsupported."""%platform) + ags = ('arch',) + msg = loc['exceptions']['UnsupportedArch'] class UpdatedModuleException(PotatoException): """ The configuration of a module has been updated since the last user configuration. """ ret = 2 - def __init__(self, name): - super(UpdatedModuleException, self).__init__("""\ -Module '%s' config has been updated since last configuration. -Please re-configure."""%name) + ags = ('module',) + msg = loc['exceptions']['UpdatedModule'] class UpdatedPlatformException(PotatoException): """ The configuration of a platform has been updated since the last user configuration. """ ret = 2 - def __init__(self, name): - super(UpdatedPlatformException, self).__init__("""\ -Platform '%s' config has been updated since last configuration. -Please re-configure."""%name) + ags = ('platform',) + msg = loc['exceptions']['UpdatedPlatform'] class UpdatedGlobalException(PotatoException): """ The global configuration has been updated since the last user configuration. """ ret = 2 - def __init__(self, name): - super(UpdatedGlobalException, self).__init__("""\ -Global config has been updated since last configuration. -Please re-configure."""%name) + msg = loc['exceptions']['UpdatedGlobal'] class UnresolvedDependencyException(PotatoException): """ A module dependency could not be resolved. """ ret = 2 - def __init__(self, name, from_modules): - super(UnresolvedDependencyException, self).__init__("""\ -Dependency '%s' from '%s' does not exist or could not be loaded!""" \ - %(name, "', '".join(from_modules))) + ags = ('dependency', 'from_modules') + msg = loc['exceptions']['UnresolvedDependency'] + + def prepare(self, dependency, from_modules): + return (dependency, "', '".join(from_modules)) class InvalidCommandException(PotatoException): """ The given command was unknown. """ ret = 2 - def __init__(self, command): - super(InvalidCommandException, self).__init__("""\ -Unknown command '%s'."""%command) - -class ToolNotFoundException(PotatoException): - """ A tool was not found! """ - - ret = 2 - def __init__(self, name): - super(ToolNotFoundException, self).__init__("""\ -Tool '%s' was not found for this architecture, please add the utility -directory to your path, or use the `--tooldir` configuration option."""%name) + ags = ('command',) + msg = loc['exceptions']['InvalidCommand'] class UnsupportedArchForModuleException(PotatoWarning): """ A module doesn't support an architecture. """ - def __init__(self, name, arch): - super(UnsupportedArchForModuleException, self).__init__("""\ -'%s' additional module doesn't support '%s' arch!"""%(name, arch)) + ags = ('module', 'arch') + msg = loc['exceptions']['UnsupportedArchForModule'] class UnsupportedCompilerForModuleException(PotatoWarning): """ A module doesn't support a compiler. """ - def __init__(self, name, compiler): - super(UnsupportedCompilerForModuleException, self).__init__("""\ -'%s' additional module doesn't support '%s' compiler from '%s'!""" \ - %(name, compiler[1], compiler[0])) + ags = ('module', 'compiler', 'compiler_maker') + msg = loc['exceptions']['UnsupportedCompilerForModule'] + +class PlatformNotFoundException(PotatoException): + """ The given platform was unsupported. """ + + ret = 2 + ags = ('platform',) + msg = loc['exceptions']['PlatformNotFound'] + +class ModuleNotFoundException(PotatoException): + """ A module was not found! """ + + ret = 2 + ags = ('module',) + msg = loc['exceptions']['ModuleNotFound'] + +class NoCCompilerException(PotatoException): + """ No appropriate C compiler was found. """ + + ret = 2 + msg = loc['exceptions']['NoCCompiler'] + +class NoCppCompilerException(PotatoException): + """ No appropriate C++ compiler was found. """ + + ret = 2 + msg = loc['exceptions']['NoCppCompiler'] + +class NoCAssemblerException(PotatoException): + """ No appropriate ASM-C compiler was found. """ + + ret = 2 + msg = loc['exceptions']['NoCAssembler'] + +class NoAssemblerException(PotatoException): + """ No appropriate assembler was found. """ + + ret = 2 + msg = loc['exceptions']['NoAssembler'] + +class NoPackerException(PotatoException): + """ No appropriate packer was found. """ + + ret = 2 + msg = loc['exceptions']['NoPacker'] # End of file. diff --git a/tools/Internals/locale.py b/tools/Internals/locale.py new file mode 100644 index 0000000..34c1df0 --- /dev/null +++ b/tools/Internals/locale.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +""" Locale management. + Here, we load the appropriate locale among the ones in the `locales` + folder, and put it in the `loc` variable, which the modules will + then include. +""" + +import os +import yaml + +loc = yaml.load(open(os.path.join(os.path.dirname(__file__), + 'locales', 'en_US.yml')).read()) + +# End of file. diff --git a/tools/Internals/locales/en_US.yml b/tools/Internals/locales/en_US.yml new file mode 100644 index 0000000..825efb0 --- /dev/null +++ b/tools/Internals/locales/en_US.yml @@ -0,0 +1,41 @@ +%YAML 1.2 +--- +prefix: + warning: "Warning:" + error: "Error:" +exceptions: + UnsupportedLanguage: >- + Language '%s' is unsupported. + UnsupportedArch: >- + Architecture '%s' is unsupported. + UpdatedModule: >- + Module '%s' configuration has been updated since last configuration. + Please re-configure. + UpdatedPlatform: >- + Platform '%s' configuration has been updated since last configuration. + Please re-configure. + UpdatedGlobal: >- + Global configuration has been updated since last configuration. + Please re-configure. + UnresolvedDependency: >- + Dependency '%s' from '%s' does not exist or could not be loaded! + InvalidCommand: >- + Unknown command '%s'. + UnsupportedArchForModule: >- + '%s' additional module doesn't support '%s' arch! + UnsupportedCompilerForModule: >- + '%s' additional module doesn't support '%s' compiler from '%s'! + PlatformNotFound: >- + Platform '%s' was not found or invalid! + ModuleNotFound: >- + Module '%s' was not found or invalid! + NoCCompiler: >- + No appropriate C compiler was found. + NoCppCompiler: >- + No appropriate C++ compiler was found. + NoCAssembler: >- + No appropriate ASM-C compiler was found. + NoAssembler: >- + No appropriate assembler was found. + NoPacker: >- + No appropriate archiver was found. diff --git a/tools/Internals/module.py b/tools/Internals/module.py index 0ef9159..cb46592 100755 --- a/tools/Internals/module.py +++ b/tools/Internals/module.py @@ -1,70 +1,105 @@ #!/usr/bin/env python3 -''' Source module class and functions for libcarrot build. ''' +""" The main thing about libcarrot's organization, if you haven't understood + yet, is that it works using modules. This is the main management file + for modules: module object, finding modules out of user configuration + options, and gather modules out of a list. + + The used configuration formats are in `FORMATS.md`. +""" import os, sys import yaml +from time import time from .exceptions import * -# The module class. +#*****************************************************************************# +# Source module object # +#*****************************************************************************# class SourceModule: - ''' The source module class. - `root` is the modules root. - `name` is the module category and name, e.g. `casiowin/fxlib`. ''' - def __init__(self, root, name): - ''' Initialize the class, gather the information from the FS. ''' - nameparts = name.split('/') + ['info.yml'] - configpath = os.path.join(root, *nameparts) - config = yaml.load(open(configpath).read()) - - # Get the meta information. + """ The source module class. + Extracts data from the filesystem, and offer utilities to interact + with its elements easily. + """ + + def __init__(self, root, name, mtim): + """ Initialize the class, gather the information from the platform + then the module configuration, and deduce module properties + out of it. + + `root` is the modules root (generally `./arch`). + `name` is the module platform and name, e.g. `casiowin/fxlib`. + `mtim` is the reference modification time for the user config. + """ + + # Open the platform configuration, then the module configuration. + namepts = name.split('/') + pconfigpath = os.path.join(root, namepts[0], 'info.yml') + mconfigpath = os.path.join(root, namepts[0], namepts[1], 'info.yml') + try: + pconfig = yaml.load(open(pconfigpath)) + except FileNotFoundError: + Raise(PlatformNotFoundException(namepts[0])) + try: + mconfig = yaml.load(open(mconfigpath)) + except FileNotFoundError: + Raise(ModuleNotFoundException(name)) + + if os.path.getmtime(pconfigpath) > mtim: + Raise(UpdatedPlatformException(namepts[0])) + if os.path.getmtime(mconfigpath) > mtim: + Raise(UpdatedModuleException(name)) + + # Keep the arguments. self.root = root self.name = name - nameparts = name.split('/') - self.__root = os.path.join(self.root, *nameparts) - self.description = config['description'] - self.compiler = config['compiler'] if 'compiler' in config else [] - self.deps = config['deps'] if 'deps' in config else [] - self.conflicts = config['conflicts'] if 'conflicts' in config else [] - self.out = config['out'] if 'out' in config else 'libc' - self.mtim = os.path.getmtime(configpath) - if 'requires' in config: - req = config['requires'] - self.compiler = req['compiler'] if 'compiler' in req else None - self.arch = req['arch'] if 'arch' in req else [] - else: - self.compiler = None - self.arch = [] - - # Get non-initialized options from the parent configuration. - platform_config_path = \ - os.path.join(root, name.split('/')[0], 'info.yml') - try: cfile = open(platform_config_path) - except: True - else: - platform_config = yaml.load(cfile); del cfile - if not 'arch' in config and 'arch' in platform_config: - self.arch = platform_config['arch'] + self.__root = os.path.join(self.root, namepts[0], namepts[1]) + self.__objroot = os.path.normpath(os.path.join(self.root, '..', + 'obj', namepts[0], namepts[1])) + self.mtim = os.path.getmtime(mconfigpath) + + # Get the usual things. + self.description = mconfig['description'] + self.out = mconfig['out'] if 'out' in mconfig else 'libc' + self.deps = mconfig['deps'] if 'deps' in mconfig else [] + self.conflicts = mconfig['conflicts'] if 'conflicts' in mconfig else [] + + # Get the requirements. + self.compiler = [] + self.arch = [] + if 'requires' in mconfig: + r = mconfig['requires'] + if 'compiler' in r: + self.compiler = r['compiler'] + if 'arch' in r: + self.arch = r['arch'] + if not self.arch and 'arch' in pconfig: + self.arch = pconfig['arch'] def getpath(self, *elements): - ''' Get the complete path of a subdirectory. ''' - return os.path.join(self.__root, *elements) + """ Get the complete path within a directory. """ + + # Get the root, adjust the elements if necessary. + rt = self.__root + if elements and elements[0] == 'obj': + rt = self.__objroot + elements = elements[1:] + + # Join it all and send it back! + return os.path.join(rt, *elements) def getfiles(self, directory, extensions): ''' Get the files list. ''' - # Check if it is the object directory. - if directory == 'obj': - root = os.path.join(directory, self.name, '') - else: - root = os.path.join(self.root, self.name, directory, '') + # Get the root. + root = self.getpath(directory) # Get the source files. files = [] for r, _, filenames in os.walk(root): for f in filenames: pos = f.find('.') - if pos < 0 and None in extensions: + if pos < 0 and not None in extensions: continue elif pos >= 0 and not f[pos + 1:] in extensions: continue @@ -99,7 +134,7 @@ def list_modules(root, arch, compiler = ('GNU', 'GCC'), \ # First, get the additional modules, and check if one of them is of # an unsupported language. for name in set(additional_modules): - module = SourceModule(root, name) + module = SourceModule(root, name, time()) if arch and module.arch and not arch in module.arch: Raise(UnsupportedArchForModuleException(name, arch)) continue @@ -126,7 +161,7 @@ def list_modules(root, arch, compiler = ('GNU', 'GCC'), \ if not key in default: default[key] = [] default[key] += config['default'][key] except FileNotFoundError: - Raise(UnsupportedPlatformException(platform)) + Raise(PlatformNotFoundException(platform)) # Add C dependencies to C++ dependencies. if not 'c++' in default: @@ -141,7 +176,7 @@ def list_modules(root, arch, compiler = ('GNU', 'GCC'), \ for name in lmod: if name in modules: continue - module = SourceModule(root, name) + module = SourceModule(root, name, time()) if arch and module.arch and not arch in module.arch: continue if module.compiler and not compiler in module.compiler: @@ -156,7 +191,7 @@ def list_modules(root, arch, compiler = ('GNU', 'GCC'), \ for dep in newdeps: name = dep[0] try: - module = SourceModule(root, name, default) + module = SourceModule(root, name, time()) except Exception: Raise(UnresolvedDependencyException(name, dep[1])) blacklist.append(name) diff --git a/tools/Internals/suph.py b/tools/Internals/suph.py index 251401c..6324f4a 100755 --- a/tools/Internals/suph.py +++ b/tools/Internals/suph.py @@ -1,12 +1,25 @@ #!/usr/bin/env python3 -''' This tool makes an include file from libcasio files. ''' +''' This tool is the libcarrot super-preprocessor. + It was previously called 'makeinc' or 'makeinc.py', but has been merged + into the internals for more efficient calls. + + What it basically does is: + - add full (comment) headers out of metas, with licence content & stuff; + - add include guards; + - reindent preprocessor directives; + - add the `#include_bits` preprocessor directive, which embeds zero or + more "header bits" using the bits PATH (list of directories where to + find bits). + + Features to come are: + - macro profiling, in order to skip some conditions and optimize the + headers for the end user; + - better header metadata (different licences from LGPLv3, ...). +''' import os, yaml from sys import stderr -#*****************************************************************************# -# Useful functions # -#*****************************************************************************# def getheader(path_in): ''' Gets the header information from a file. ''' @@ -227,8 +240,6 @@ def make_header(base, name, out_path): ics[name] = {'base': base, 'bits': bdeps, 'mtim': os.path.getmtime(out_path)} - - def suph(incdir, incdirs, bitdirs): # Open the cache. try: diff --git a/tools/Internals/tools/__init__.py b/tools/Internals/tools/__init__.py index af725b6..4bd6f2a 100755 --- a/tools/Internals/tools/__init__.py +++ b/tools/Internals/tools/__init__.py @@ -1,4 +1,97 @@ #!/usr/bin/env python3 +""" This is the main build tool directory/interface. + + It is responsible for finding tools and their configuration + out of user configuration, such as the used languages, the output + formats (object format, library format), + and for applying these tools and configurations and make them available + through a platform-agnostic interface. + + Tools are identified by their manufacturer, e.g. 'GNU' or 'Borland', + and by their name, e.g. 'GCC' or 'Turbo C++'. + Each tool interface is a simple {"function": function} dictionary, where: + - "getparams": get the tool configuration for which the tool class will + be able to do the job (raises an exception if it doesn't manage); + this function takes different parameters depending on the role it + is supposed to occupy; + - "cc": make an object file out of a C source file (and headers); + - "cxx": make an object file out of a C++ source file (and headers); + - "asmc": make an object file out of an ASM-C source file (and headers); + - "asm": make an object file out of an ASM source file; + - "pack": make a static library out of object files. + + For a C compiler, the `getparams` function should have this prototype: + + def <getparams>(typ, arch, obj_format, std) + + Where `typ` is "cc", `arch` is the architecture for which we're building, + `std` is the C standards list the compiler shall support, and `obj_format` + is the object format the compiler should produce. + + The C compilation function itself should have this prototype: + + def <cc>(params, obj, src, incdirs, std) + + Where `params` is the parameters object generated by the `getparams` + function, `obj` is the path of the object to make, `src` is the path + of the C source file, `incdirs` is the list of header directories, + and `std` is the C standard to compile in. + + The C++ interface is more or less the same, except it is for C++, + so the `typ` parameter of the `getparams` function is "cxx", + and the "cxx" function should be set. + + For the ASM-C interface, the `getparams` function should have this + prototype: + + def <getparams>(typ, arch, obj_format) + + Where `typ` is "asmc", and the rest of the arguments are the same than + for the C compiler `getparams` function. + + The ASM-C compilation function itself should have this prototype: + + def <asmc>(params, obj, src, incdirs) + + Where `params` is the parameters object generated by the `getparams` + function, `obj` is the path of the object to make, `src` is the + path of the ASM-C source file, and `incdirs` is the list of header + directories. + + For the ASM interface, the `getparams` function should have this + prototype: + + def <getparams>(typ, arch, obj_format, syntax) + + Where `typ` is "asm", `arch` is the architecture for which to build, + `obj_format` is the object format to produce, and `syntax` is the + syntax name, depending on the architecture. + + The `asm` function itself should be defined as: + + def <asm>(params, obj, src) + + Where `params` is the parameters object generated by the `getparams` + function, `obj` is the path of the object to make, and `src` is the + path of the assembly source file. + + For the static library generator interface, the `getparams` function + should have this prototype: + + def <getparams>(typ, arch, lib_format, object_format) + + Where `typ` is "pack", `arch` is the architecture for which to build, + `lib_format` is the library format, and `obj_format` is the object + format the function takes. + + The `pack` function itself should be defined as: + + def <pack>(params, lib, objs) + + Where `params` is the parameters object generated by the `getparams` + function, `lib` is the path where to make the library, and `objs` is + the list of objects to pack. +""" import os, sys, functools as ft from subprocess import call @@ -70,11 +163,36 @@ def find(languages, arch): asmc_name = ('GNU', 'GCC') pack_name = ('GNU', 'ar') - cp = __c_compilers[cc_name]['getparams']('cc', arch) - cxxp = __cxx_compilers[cxx_name]['getparams']('cxx', arch) - asmcp = __c_assemblers[asmc_name]['getparams']('asmc', arch) - asp = __assemblers[asm_name]['getparams']('asm', arch) - arp = __archivers[pack_name]['getparams']('pack', arch) + # Setup the C compiler. + try: + cp = __c_compilers[cc_name]['getparams']('cc', arch, 'elf', ['c89']) + except ToolNotFoundException: + Raise(NoCCompilerException) + + # Setup the C++ compiler. + try: + cxxp = __cxx_compilers[cxx_name]['getparams']('cxx', arch, + 'elf', ['c++98']) + except ToolNotFoundException: + Raise(NoCppCompilerException) + + # Setup the C-ASM compiler. + try: + asmcp = __c_assemblers[asmc_name]['getparams']('asmc', arch, 'elf') + except ToolNotFoundException: + Raise(NoCAssemblerException) + + # Setup the assembler. + try: + asp = __assemblers[asm_name]['getparams']('asm', arch, 'elf', 'gnu') + except ToolNotFoundException: + Raise(NoAssemblerException) + + # Setup the packer. + try: + arp = __archivers[pack_name]['getparams']('pack', arch, 'coff', 'elf') + except ToolNotFoundException: + Raise(NoPackerException) return { 'cc': cc_name, 'cc_params': cp, diff --git a/tools/Internals/tools/__util__.py b/tools/Internals/tools/__util__.py index 8ec1e79..637a7a2 100755 --- a/tools/Internals/tools/__util__.py +++ b/tools/Internals/tools/__util__.py @@ -14,7 +14,7 @@ def add_tooldir(tooldir): __path.append(tooldir) # Tool management. -def getutil(name, tools): +def getutil(tools): # Check that the tools is a list. if type(tools) != list: tools = [tools] @@ -24,10 +24,7 @@ def getutil(name, tools): l = os.listdir(udir) for tool in tools: if tool in l: - return os.path.join(udir, tool) - - # There is no such utility :( - Raise(ToolNotFoundException(name)) + yield os.path.join(udir, tool) #*****************************************************************************# # GNU-specific utilities # #*****************************************************************************# @@ -46,9 +43,17 @@ def getgnu(arch, tool): for arches, prefixes in __gnu_prefixes: if not arch in arches: continue - return getutil(tool, - ['%s-elf-%s' %(pre, tool) for pre in prefixes] + - ['%s-elf-%s.exe'%(pre, tool) for pre in prefixes]) - Raise(ToolNotFoundException(tool)) + tools = \ + ['%s-elf-%s' %(pre, tool) for pre in prefixes] + \ + ['%s-elf-%s.exe'%(pre, tool) for pre in prefixes] + for path in getutil(tools): + yield path + +def check_bfd_arch(arch, bfd_arch): + for arches, prefixes in __gnu_prefixes: + if not arch in arches: + continue + return bfd_arch in prefixes + return False # End of file. diff --git a/tools/Internals/tools/gnu_ar.py b/tools/Internals/tools/gnu_ar.py index b4a2fb3..2dcdbe4 100644 --- a/tools/Internals/tools/gnu_ar.py +++ b/tools/Internals/tools/gnu_ar.py @@ -1,41 +1,86 @@ #!/usr/bin/env python3 -''' The GNU archiver. ''' +""" These functions bring the GNU archive manager, part of GNU binutils, + to the libcarrot build tools. + + When configured for a non-native target, as for all GNU Binutils elements, + GCC will install the binary as `<bfd target>-ar`, so we're looking for + something like `<arch>-<os or format>-ar`, e.g. `sh3eb-elf-ar` for + platform-independent assembler for the SuperH Core 3, big endian version, + generating ELF object files or executables. + + If there is no appropriate non-native target, the native assembler might + just be usable. We just have to check the BFD target in GNU ar's help + message (non-translated version). +""" import os import tempfile from subprocess import call, check_output from .__util__ import * -def __getparams(typ, arch): +#*****************************************************************************# +# Discovery, configuration # +#*****************************************************************************# +def __get_ar_bfd(path): + """ Get the supported BFD targets out of the ar binary path. """ + + # Make the help command with the default locale, get the output. + env = os.environ.copy() + env['LANG'] = 'en_US' + out = check_output([path, '--help'], env=env).decode() + + # Get the appropriate line. + lines = [line for line in out.splitlines() if len(line.split(':')) == 3 \ + and line.split(':')[1] == ' supported targets'] + if len(lines) != 1: + return [] + + # Get the BFD targets. + bfd = [] + for element in lines[0].split(':')[2].split(): + t = element.split('-') + if len(t) == 1: + continue + bfd.append((t[0], '_'.join(t[1:]))) + return bfd + +def __iter_ar(arch): + """ Iterate through all of the `ar` occurrences among the system. """ + + for elt in getgnu(arch, 'ar'): + yield elt + for elt in getutil(['ar', 'ar.exe']): + yield elt + +def __getparams(typ, arch, lib_format, obj_format): ''' Get the params. ''' + if obj_format[:3] != 'elf': + raise ToolNotFound + # Get the utility - try: gar = getgnu(arch, 'ar') - except ToolNotFoundException: - gar = getutil('ar', ['ar', 'ar.exe']) - env = os.environ.copy() - env['LANG'] = 'en_US' - out = check_output([gar, '--help'], env=env).decode() - lns = [line for line in out.splitlines() if len(line.split(':')) == 3 \ - and line.split(':')[1] == ' supported targets'] - if len(lns) != 1: - Raise(ToolNotFoundException('ar')) - targets = lns[0].split(':')[2].split() - target_found = False - for target in targets: - target = '_'.join(target.split('-')[1:]) - if target == arch: - target_found = True - break - if not target_found: - Raise(ToolNotFoundException('ar')) - - return {'path': gar} - -def __getformats(params): - ''' Get the supported archive formats. ''' - return ['coff'] + path = None + for gar in __iter_ar(arch): + bfd_list = __get_ar_bfd(gar) + + for bfd in bfd_list: + if not check_bfd_arch(arch, bfd[1]): + continue + if bfd[0] != lib_format: + continue + + path = gar + break + + if path: break + + if not path: + Raise(ToolNotFoundException) + return {'path': path} +#*****************************************************************************# +# Usage # +#*****************************************************************************# def __pack(params, lib, objs): # Set up the MRI script. tmp_fd, tmp_path = tempfile.mkstemp(suffix='.mri') @@ -57,7 +102,6 @@ def __pack(params, lib, objs): __all__ = ["GNU_AR"] GNU_AR = { 'getparams': __getparams, - 'getfmt_ar': __getformats, 'pack': __pack } diff --git a/tools/Internals/tools/gnu_as.py b/tools/Internals/tools/gnu_as.py index efe7e08..241bd26 100644 --- a/tools/Internals/tools/gnu_as.py +++ b/tools/Internals/tools/gnu_as.py @@ -1,23 +1,78 @@ #!/usr/bin/env python3 -''' The GNU assembler. ''' +""" These functions bring the GNU assembler, part of GNU binutils, + to the libcarrot build tools. + + When configured for a non-native target, as for all GNU Binutils elements, + GCC will install the binary as `<bfd target>-as`, so we're looking for + something like `<arch>-<os or format>-as`, e.g. `sh3eb-elf-as` for + platform-independent assembler for the SuperH Core 3, big endian version, + generating ELF object files or executables. + + If there is no appropriate non-native target, the native assembler might + just be usable. We just have to check the BFD target in GNU as' version + message (non-translated version). +""" import os from subprocess import call, check_output from .__util__ import * -def __getparams(typ, arch): - ''' Get the parameters. ''' +#*****************************************************************************# +# Discovery, configuration # +#*****************************************************************************# +def __get_as_bfd(path): + """ Get the BFD target out of the as binary path. """ + + # Make the version command with the default locale, get the output. + env = os.environ.copy() + env['LANG'] = 'en_US' + out = check_output([path, '--version'], env=env).decode() + + # Get the BFD target, which is on the last line, e.g. + # This assembler was configured for a target of `sh3eb-elf'. + bfd = out.splitlines()[-1].split('`')[1].split("'")[0] + + # Get the format using the platform. + bfd = bfd.split('-') + arch = bfd[0] + plt = '-'.join(bfd[1:]) + if plt == "pc-linux-gnu": + plt = "elf" + # TODO: others + + # Return. + return (arch, plt) + +def __iter_as(arch): + """ Iterate through all of the `as` occurrences among the system. """ + + for elt in getgnu(arch, 'as'): + yield elt + for elt in getutil(['as', 'as.exe']): + yield elt + +def __getparams(typ, arch, objfmt, syntax): + """ Get the assembler parameters. """ + + # Check the object format. + # TODO: some configurations of AS produce other formats such as PE...? + if objfmt != 'elf': + raise ToolNotFound # Get the utility. - try: gas = [getgnu(arch, 'as')] - except ToolNotFoundException: - gas = getutil('as', ['as', 'as.exe']) - env = os.environ.copy() - env['LANG'] = 'en_US' - out = check_output([gas, '--version'], env=env).decode() - bfd = out.splitlines()[-1].split('`')[1].split("'")[0] - if bfd.split('-')[0] != arch: - Raise(ToolNotFoundException('as')) + path = None + for gas in __iter_as(arch): + bfd = __get_as_bfd(gas) + + if not check_bfd_arch(arch, bfd[0]): + continue + if bfd[1] != objfmt: + continue + + path = gas + break + if not path: + Raise(ToolNotFoundException) # Get the flags. flags = [] @@ -26,8 +81,10 @@ def __getparams(typ, arch): arch = arch[:-3] flags += ['--isa=' + arch, '--big'] - return {'path': gas, 'flags': flags} - + return {'path': path, 'flags': flags} +#*****************************************************************************# +# Usage # +#*****************************************************************************# def __asm(params, obj, src): # Set up the command line. commandline = [params['path']] diff --git a/tools/Internals/tools/gnu_gcc.py b/tools/Internals/tools/gnu_gcc.py index d849a50..a6d2dee 100755 --- a/tools/Internals/tools/gnu_gcc.py +++ b/tools/Internals/tools/gnu_gcc.py @@ -1,71 +1,138 @@ #!/usr/bin/env python3 -''' The GNU C Compiler. ''' +""" These functions bring the GNU Compiler Collection, more precisely + the C/C++ compiler, to the libcarrot build tools. + + When configured for a non-native target, GCC will install its binary as + `<bfd target>-gcc` in the host binary directory, so we're looking for + something like `<arch>-<os or format>-gcc`, e.g. `sh3eb-elf-gcc` for + platform-independent gcc for the SuperH Core 3, big endian version, + generating ELF object files or executables. + + If there is no appropriate non-native target, the native compiler might + just be usable. We just have to check the BFD target using + GCC's `-dumpmachine` option. +""" import os from subprocess import call, check_output, DEVNULL from tempfile import mkstemp from .__util__ import * -def __teststd(path, standards): - ''' Test if GCC supports those standards using the `-std` option. ''' +#*****************************************************************************# +# Discovery, configuration # +#*****************************************************************************# +def __get_gcc_bfd(path): + """ Get the BFD target out of the gcc binary path. """ - sfd, sf = mkstemp(suffix='.c', text='int main(void) { return (0); }') + # Make the dump command with the default locale, get the output. + env = os.environ.copy() + env['LANG'] = 'en_US' + out = check_output([path, '-dumpmachine'], env=env).decode().strip() + + # Check the BFD target. + bfd = out.split('-') + arch = bfd[0] + plt = '-'.join(bfd[1:]) + if plt == "pc-linux-gnu": + plt = "elf" + # TODO: others + + # Return + return (arch, plt) + +def __iter_gcc(arch): + """ Iterate through all of the `gcc` occurrences among the system. """ + + for elt in getgnu(arch, 'gcc'): + yield elt + for elt in getutil(['gcc', 'gcc.exe']): + yield elt + +def __testcc(typ, path='/usr/bin/gcc', flags=[], + text='int main(void) { return (0); }'): + """ Try to compile, see if it works. """ + + if typ == "cc": + source_suffix = '.c' + gcc_lang = 'c' + elif typ == "cxx": + source_suffix = '.cpp' + gcc_lang = 'c++' + elif typ == "asmc": + source_suffix = '.sx' + gcc_lang = 'assembler-with-cpp' + + sfd, sf = mkstemp(suffix=source_suffix, text=text) os.close(sfd) - std = [] - for s in standards: - ofd, of = mkstemp(suffix='.o') - os.close(ofd) + ofd, of = mkstemp(suffix='.o') + os.close(ofd) - cl = [path, '-x', 'c', '-std=%s'%s, '-o', of, sf, '-nostdlib'] - if not call(cl, shell=False, stdout=DEVNULL, stderr=DEVNULL): - std.append(s) + cl = [path, '-x', gcc_lang, '-o', of, sf, '-nostdlib'] + flags + if call(cl, shell=False, stdout=DEVNULL, stderr=DEVNULL): + worked = False + else: + worked = True - try: os.remove(of) - except: pass + try: os.remove(of) + except: pass + try: os.remove(sf) + except: pass - os.remove(sf) - return std + return worked -def __getparams(typ, arch): - ''' Get the parameters. ''' +def __teststd(typ, path, standards): + ''' Test if GCC supports those standards using the `-std` option. ''' - # Get the compiler path. - try: gcc = getgnu(arch, 'gcc') - except ToolNotFoundException: - gcc = getutil('gcc', ['gcc', 'gcc.exe']) - bfd = check_output([gcc, '-dumpmachine']).decode().strip() - if bfd.split('-')[0] != arch: - Raise(ToolNotFoundException('gcc')) + for s in standards: + if not __testcc(typ, path, ['-std=%s'%s]): + return False + return True - # Get the compilation flags. - # TODO: use `-ffreestanding` (implies `-fno-builtin`?) - cflags = ['-Wall', '-Wextra', '-Wno-attributes', - '-O2', '-nostartfiles', '-fno-builtin-function'] +def __getparams(typ, arch, objfmt, std=[]): + """ Get the GNU Compiler Collection respecting some + pre-defined constraints. """ - # Get the CPU flags. + # Prepare the minimum compilation flags. + cflags = [] if arch[:2] == 'sh': cflags += ['-mhitachi', '-fomit-frame-pointer'] if arch[-3:] == 'dsp': cflags.append('-Wa,-dsp') arch = arch[:-3] - cflags += ['-m' + arch[2:], '-mb'] + cflags += ['-m%s'%arch[2:], '-mb'] else: - # FIXME: raise exception? - cflags.append('-march=' + arch) - - ret = {'path': gcc, 'flags': cflags} - if typ == 'cc': - ret['std'] = __teststd(gcc, ['c89', 'c95', 'c99', 'c11']) - elif typ == 'cxx': - ret['std'] = __teststd(gcc, ['c++98', 'c++11', 'c++14', 'c++17']) - return ret - -def __getstd(params): - ''' Get the supported C standards. ''' - - return params['std'] + # FIXME: is it like this for everything? raise exception here? + cflags.append('-march=%s'%arch) + + # Get the utility. + path = None + for gcc in __iter_gcc(arch): + bfd = __get_gcc_bfd(gcc) + + if not check_bfd_arch(arch, bfd[0]): + continue + if bfd[1] != objfmt: + continue + if not __testcc(typ, gcc, cflags): + continue + if typ != "asmc" and not __teststd(typ, gcc, std): + continue + + path = gcc + break + if not path: + Raise(ToolNotFoundException) + + # Get the rest of the compilation flags. + # TODO: use `-ffreestanding` (implies `-fno-builtin`?) + cflags += ['-Wall', '-Wextra', '-Wno-attributes', + '-O2', '-nostartfiles', '-fno-builtin-function'] + return {'path': path, 'flags': cflags} +#*****************************************************************************# +# Use the parameters # +#*****************************************************************************# def __makeenv(incdirs): incpath = os.pathsep.join(incdirs) env = os.environ.copy() @@ -102,11 +169,11 @@ def __asmc(params, obj, src, incdirs): # Make the call. return call(commandline, env=__makeenv(incdirs)) +# Main entry. + __all__ = ["GNU_GCC"] GNU_GCC = { 'getparams': __getparams, - 'getcstd': __getstd, - 'getcxxstd': __getstd, 'cc': __cc, 'cxx': __cxx, 'asmc': __asmc diff --git a/tools/Internals/tools/renesas_asmsh.py b/tools/Internals/tools/renesas_asmsh.py index 8fff98c..4d76c3f 100755 --- a/tools/Internals/tools/renesas_asmsh.py +++ b/tools/Internals/tools/renesas_asmsh.py @@ -1,23 +1,37 @@ #!/usr/bin/env python3 -''' The Renesas SuperH assembler. ''' +""" These functions bring the Renesas SuperH proprietary Assembler to + the libcarrot build tools. + + You can find more information about this assembler in the toolchain manual: + https://bible.planet-casio.com/renesas/rej10b0152_sh.pdf +""" import os, tempfile from subprocess import call from .__util__ import * -def __getparams(typ, arch): +#*****************************************************************************# +# Discovery, configuration # +#*****************************************************************************# +def __getparams(typ, arch, obj_format, std): ''' Get the parameters. ''' + if arch[:2] != 'sh': - raise Exception + raise UnsupportedArchException(arch) + + # Get the tool. + try: asmsh = next(getutil(['asmsh.exe'])) + except StopIteration: Raise(ToolNotFoundException) - asmsh = getutil('asmsh', ['asmsh.exe']) CPU = arch.upper() flags = ['-cpu=%s'%arch, '-endian=big'] if arch[-3:] == 'dsp': flags.append('-dspc') return {'path': asmsh, 'CPU': CPU, 'flags': flags} - +#*****************************************************************************# +# Usage # +#*****************************************************************************# def __asm(self, obj, src, incdirs): ''' Assemble a source assembly file. ''' diff --git a/tools/Internals/tools/renesas_optlnk.py b/tools/Internals/tools/renesas_optlnk.py index 50fe02a..d6aeb78 100755 --- a/tools/Internals/tools/renesas_optlnk.py +++ b/tools/Internals/tools/renesas_optlnk.py @@ -1,20 +1,34 @@ #!/usr/bin/env python3 -''' The Renesas Optimize Linker. ''' +""" These functions bring the Renesas SuperH proprietary Optimizing Linker + (and archiver) to the libcarrot build tools. + + You can find more information about this tool in the toolchain manual: + https://bible.planet-casio.com/renesas/rej10b0152_sh.pdf +""" import os, tempfile from subprocess import call from .__util__ import * -def __getparams(typ, arch): +#*****************************************************************************# +# Discovery, configuration # +#*****************************************************************************# +def __getparams(typ, arch, lib_format, obj_format): ''' Get the parameters. ''' - return {'path': getutil('optlnk', ['Optlnk.exe'])} - -def __getformats(params): - ''' Get the supported archive formats. ''' + # Check the format. + if lib_format != 'HLIB' or obj_format != 'ELF': + Raise(ToolNotFoundException) - return ['hlib'] + # Check the path. + try: optlnk = next(getutil(['optlnk.exe'])) + except: Raise(ToolNotFoundException) + # Return the parameters. + return {'path': optlnk} +#*****************************************************************************# +# Usage # +#*****************************************************************************# def __pack(params, lib, objs): ''' Pack the object files into an archive. ''' @@ -40,7 +54,6 @@ def __pack(params, lib, objs): __all__ = ["Renesas_OptLnk"] Renesas_OptLnk = { 'getparams': __getparams, - 'getfmt_ar': __getformats, 'pack': __pack } diff --git a/tools/Internals/tools/renesas_shc.py b/tools/Internals/tools/renesas_shc.py index 836f781..288d8e2 100755 --- a/tools/Internals/tools/renesas_shc.py +++ b/tools/Internals/tools/renesas_shc.py @@ -1,36 +1,42 @@ #!/usr/bin/env python3 -''' The Renesas C/C++ Compiler. ''' +""" These functions bring the Renesas SuperH C/C++ proprietary Compiler to + the libcarrot build tools. + + You can find more information about this compiler in the toolchain manual: + https://bible.planet-casio.com/renesas/rej10b0152_sh.pdf +""" import os from subprocess import call from .__util__ import * -def __getparams(typ, arch): - ''' Get the parameters. ''' +#*****************************************************************************# +# Discovery, configuration # +#*****************************************************************************# +def __getparams(typ, arch, objfmt, std): + """ Get the parameters. """ + + # Check the architecture, the output format, and the standards. + if arch[:2] != 'sh' or objfmt != 'elf' or \ + (typ == "cc" and std != ['c89']) or (typ == "cxx" and std != ['c++98']): + Raise(ToolNotFoundException) - if arch[:2] != 'sh': - raise UnsupportedArchException(arch) + # Get the tool path. + try: shc = next(getutil(['shc.exe'])) + except StopIteration: Raise(ToolNotFoundException) - shc = getutil('shc', ['shc.exe']) + # Get the flags. CPU = arch.upper() flags = ['-cpu=%s'%arch, '-endian=big'] if arch[-3:] == 'dsp': flags.append('-dspc') return {'path': shc, 'CPU': CPU, 'flags': flags} - -def __getstd_c(params): - ''' Get the supported C standards. ''' - - return ['c89'] - -def __getstd_cxx(params): - ''' Get the supported C++ standards. ''' - - return ['c++98'] - +#*****************************************************************************# +# Usage # +#*****************************************************************************# def __cc(params, obj, src, incdirs, std): - ''' Compile a C file. ''' + """ Compile a C source file into an object file. """ # Make the temporary thing with all of the subcommands. tmpinfo = tempfile.mkstemp() @@ -66,7 +72,7 @@ def __cxx(self, obj, src, incdirs=[]): print(src, *params['flags'], sep='\n', file=tmp) print('-object="%s"'%obj, '-lang=cpp', sep='\n', file=tmp) print('-size', '-gbr=auto', '-noinline', '-chgincpath', '-errorpath', - '-nologo', '-debug', sep='\n', file=tmp) + '-nologo', '-debug', sep='\n', file=tmp) tmp.close() # Edit the environment. @@ -88,8 +94,6 @@ def __cxx(self, obj, src, incdirs=[]): __all__ = ["Renesas_SHC"] Renesas_SHC = { 'getparams': __getparams, - 'getcstd': __getstd_c, - 'getcxxstd': __getstd_cxx, 'cc': __cc, 'cxx': __cxx } diff --git a/tools/configure.py b/tools/configure.py index cf9fdb8..c5ff51d 100755 --- a/tools/configure.py +++ b/tools/configure.py @@ -1,5 +1,16 @@ #!/usr/bin/env python3 -''' Configure how to build libcarrot. ''' +""" This tool serves for user configuration of the libcarrot. + Basically, it takes the user parameters (architecture, platform, + programming languages, additional modules, build tools), and makes + a configuration that the actual tool, 'make.py', can read to know + what he is supposed to do. + + It is responsible for finding the modules corresponding to what the + user asks, finding a configuration of the build tools that work and + produce the expected result, and warn if what the user asks for is + incorrect or if there are any mistakes in the involved + global, platform or module configurations. +""" import os, sys, platform, shlex, argparse from subprocess import call @@ -13,8 +24,7 @@ def __getplatform(): # Internal function to get the architecture. def __getarch(): - name = platform.machine() - return name + return platform.machine().lower() #*****************************************************************************# # Set up the arguments parser # #*****************************************************************************# diff --git a/tools/make.py b/tools/make.py index 922b7d5..3da0659 100755 --- a/tools/make.py +++ b/tools/make.py @@ -1,5 +1,13 @@ #!/usr/bin/env python3 -''' The PotatoSDK is an SDK using different toolchains to compile libcarrot. ''' +""" This tool is responsable for actually building the library. + It takes the configuration from the file generated by 'configure.py', + which contains the modules to build and the build tools configuration, + finds out what is left to do, what has been updated (to re-build), + and finishes the job. + + It is possible to make several times out of a single configuration, + and this is cool when you are just programming in one or more modules. +""" import os, traceback from argparse import ArgumentParser @@ -14,7 +22,7 @@ argparser = ArgumentParser(prog='make.py') # Set up the arguments. argparser.add_argument('-c', '--config', dest='config', - default='build.yml', help='the build configuration path') + default='.config.yml', help='the build configuration path') argparser.add_argument('-i', '--include', dest='incdir', default='include', help='the generated include directory') argparser.add_argument('--obj', dest='objdir', default='obj', @@ -23,8 +31,9 @@ argparser.add_argument('command', help='the command to execute') #*****************************************************************************# # Commands # #*****************************************************************************# -# `tool clean`: clean the project. def clean(args): + """ Clean the project (remove generated files in order to + force rebuild). """ # Remove the caches. try: os.remove('.makeinc-cache') except FileNotFoundError: pass @@ -37,16 +46,21 @@ def clean(args): try: rmtree('obj') except FileNotFoundError: pass -# `tool mrproper`: clean the project and the configuration. def mrproper(args): + """ Clean the project for distribution (remove generated files and + configurations). """ + + # Clean generated files. clean(args) # Remove the configuration. + try: os.remove('.config.yml') + except FileNotFoundError: pass try: os.remove('build.yml') except FileNotFoundError: pass -# `tool [build]`: make the project. def build(args): + """ Build the project with the current configuration. """ args.config = yaml.load(open(args.config).read()) # Open the main configuration @@ -209,6 +223,9 @@ def build(args): # Main interface. # #*****************************************************************************# def main(): + """ Main function, checks the command and runs the appropriate + function. """ + # Parse the arguments, get the configuration. args = argparser.parse_args() |