-- -- features.lua -- -- $Id$ -- -- Lua 5.2 module providing a mingw-get setup hook for configuration of -- the user's MinGW GCC compiler preferences. -- -- Written by Keith Marshall -- Copyright (C) 2019, 2022, MinGW.OSDN Project -- -- -- Permission is hereby granted, free of charge, to any person obtaining a -- copy of this software and associated documentation files (the "Software"), -- to deal in the Software without restriction, including without limitation -- the rights to use, copy, modify, merge, publish, distribute, sublicense, -- and/or sell copies of the Software, and to permit persons to whom the -- Software is furnished to do so, subject to the following conditions: -- -- The above copyright notice and this permission notice shall be included -- in all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -- DEALINGS IN THE SOFTWARE. -- -- -- We begin by initializing a container, for construction of a Lua module -- to encapsulate the content of this source file. -- local M = {} -- -- mingw-get passes the MinGW installation root directory path, in the -- $MINGW32_SYSROOT environment variable; from this, we deduce the path -- name for the working copy of the features.h file... -- local function syspath( varname ) -- -- ...using this local helper function to ensure that the path name -- string, returned from the environment, is free from insignificant -- trailing directory name separators, and that all internal sequences -- of directory name separators are normalized to a single '/'. -- local pathname = os.getenv( varname ) if pathname then pathname = string.gsub( pathname, "[/\\]+", "/" ) pathname = string.match( pathname, "(.*[^/])/*$" ) end return pathname end local sysroot = syspath( "MINGW32_SYSROOT" ) if not sysroot then error( "environment variable MINGW32_SYSROOT may not be set", 0 ) end local config_file_name = sysroot .. "/include/features.h" -- -- Define a template, whence the default features configuration may be -- deduced when writing the initial content of the file. -- local config_default = { '/*', ' * features.h', ' *', ' * Features configuration for MinGW.OSDN GCC implementation; users may', ' * customize this file, to establish their preferred default behaviour.', ' * Projects may provide an alternative, package-specific configuration,', ' * either by placing their own customized in the package', ' * -I path, ahead of the system default, or by assignment of their', ' * preferred alternative to the _MINGW_FEATURES_HEADER macro.', ' *', ' *', ' * $'..'Id$', ' *', ' * Template written by Keith Marshall ', ' * Copyright (C) 2019, 2022, MinGW.OSDN Project.', ' *', ' *', ' * Permission is hereby granted, free of charge, to any person obtaining a', ' * copy of this software and associated documentation files (the "Software"),', ' * to deal in the Software without restriction, including without limitation', ' * the rights to use, copy, modify, merge, publish, distribute, sublicense,', ' * and/or sell copies of the Software, and to permit persons to whom the', ' * Software is furnished to do so, subject to the following conditions:', ' *', ' * The above copyright notice, this permission notice, and the following', ' * disclaimer shall be included in all copies or substantial portions of', ' * the Software.', ' *', ' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS', ' * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,', ' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL', ' * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER', ' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING', ' * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER', ' * DEALINGS IN THE SOFTWARE.', ' *', ' */', '#ifndef __MINGW_FEATURES__', '#pragma GCC system_header', '', '/* Users are expected to customize this header, but it remains subject to', ' * automatic updates by system software. To ensure that any customisation', ' * is not ovewritten, during such updates, it MUST observe the following:', ' *', ' * This header MUST define __MINGW_FEATURES__; the definition MUST begin', ' * with "#define __MINGW_FEATURES__ (__MINGW_FEATURES_BEGIN__) \\"; it MUST', ' * extend over multiple lines, and terminate with "__MINGW_FEATURES_END__";', ' * intervening lines may enumerate any defined features, one per line, and', ' * each specified as an argument to either the __MINGW_FEATURE_ENABLE__(),', ' * or the __MINGW_FEATURE_IGNORE__() macro, (ensuring that at least one', ' * space separates either of these macro names from its parenthesized', ' * argument name).', ' *', ' * CAUTION:', ' * If customizing this features configuration, ALWAYS refer to features', ' * using their designated symbolic constant names; NEVER usurp the use of', ' * these symbolic constants for any other purpose, and NEVER assume that', ' * any such constant has a specific value ... their definitions may vary', ' * between distinct MinGW Runtime Library software releases!', ' */', '#define __MINGW_FEATURES__ (__MINGW_FEATURES_BEGIN__) \\', ' __MINGW_FEATURE_IGNORE__ (__MINGW_ANSI_STDIO__) \\', ' __MINGW_FEATURE_IGNORE__ (__MINGW_LC_MESSAGES__) \\', ' __MINGW_FEATURE_IGNORE__ (__MINGW_LC_ENVVARS__) \\', ' __MINGW_FEATURES_END__', '', '#endif /* !__MINGW_FEATURES__: $'..'RCSfile$: end of file */' } -- local function defines( name ) -- -- A local helper function, to generate a string.match -- pattern for identification of any C #define statement, -- which defines a specified symbol "name" -- return "^%s*#%s*define%s+" .. name end -- local function feature( value ) -- -- A local helper function, to generate a string.match -- pattern for identification of a specified parenthesized -- value field, within a C #define statement. -- return "%s+%(%s*" .. value .. "%s*%)%s*" end -- local function begins_definition( line, name, value ) -- -- A local helper function to check whether any input "line" -- represents the first of a multiline C #define for the "name" -- symbol, with its initial field matching the parenthesized -- "value" token (default: "__MINGW_FEATURES_BEGIN__"). -- if not value then value = "__MINGW_FEATURES_BEGIN__" end return string.match( line, defines( name ) .. feature( value ) .. "\\$" ) end -- -- In the event that a features configuration has already been -- specified for this installation, capture this into internal -- "as built" configuration tables... -- local current_config, current_features local config = io.open( config_file_name ) if config then -- ...always starting collection into table "current_config"... -- current_config = {} local active_list = current_config -- -- ...reading the existing configuration file, line by line... -- for line in config:lines() do if active_list == current_features and string.match( line, "^%s*__MINGW_FEATURES_END__%s*$" ) then -- ...noting that actual configuration options will have -- been diverted to the "current_features" table; when all -- such options have been captured, redirect any residual -- content to the "current_config" table. -- active_list = current_config end -- -- Capture the current configuration record, into whichever -- diversion is currently active. -- table.insert( active_list, line ) -- if begins_definition( line, "__MINGW_FEATURES__" ) then -- When we find the first line of a (possibly) well-formed -- features configuration, divert capture of its subsequent -- lines to a new "current_features" table. -- current_features = {} active_list = current_features end end -- -- When capture is complete, ensure that the input stream file -- is closed; (we may want to reopen it later, to rewrite it). -- io.close( config ) -- -- Before we go any further, check for well-formedness of the -- configuration which we have just captured; effectively... -- if not current_features or active_list == current_features then -- ...when no "current_features" diversion has been created, -- or such a diversion remains open for capture, then an error -- has occurred, and the configuration is not well-formed. -- local function config_error( reason ) error( config_file_name .." ".. reason, 0 ) end for ref, line in next, current_config do if string.match( line, defines( "__MINGW_FEATURES__" ) ) then -- In this case, a "__MINGW_FEATURES__" definition is -- present, but it is not well-formed; (note that, here, -- we rescan the "current_config" table, using a less -- rigorous criterion for detection of a definition of -- of "__MINGW_FEATURES__" than that which is required -- to open the "current_features" diversion)... -- config_error( "has malformed __MINGW_FEATURES__ definition" ) end end -- ...while in this case, no "__MINGW_FEATURES__" definition -- was found, (well-formed, or otherwise). -- config_error( "does not define __MINGW_FEATURES__" ) end end -- -- local function update_configuration( stream_file, template, current ) -- -- A function to write a features configuration to a designated output -- stream, based on a specified template, reproducing and encapsulating -- any existing configuration which may also have been specified... -- local function stream_file_writeln( line ) -- -- ...using this helper function to write each line. -- stream_file:write( line .. "\n" ) end -- if current then -- An existing configuration header is to be updated. An image of -- its original content has already been loaded into "current"; copy -- it back to the original file, line by line... -- for ref, line in next, current do if begins_definition( line, "__MINGW_FEATURES__" ) then -- ...until we reach the first active configuration record. -- We now wish to merge any new configuration options from the -- template, into the existing configuration; work through the -- template, line by line, ignoring all lines... -- local merge = false for ref, sub in next, template do if begins_definition( sub, "__MINGW_FEATURES__" ) then -- ...until we find the first line of a (possibly) -- well-formed features configuration record; when we -- find this, we decompose and reassemble it, so that -- we may preserve a tabulation format matching the -- longer of the template and actual configuration -- records. -- local def = string.match( sub, "^[^(]*" ) local usr = string.match( line, "^[^(]*" ) if string.len( usr ) > string.len( def ) then def = usr end usr = string.match( line, "%(.*" ) sub = string.match( sub, "%(.*" ) if string.len( usr ) > string.len( sub ) then sub = usr end -- -- Write out the reassembled features configuration -- start record, and activate the features merge. -- stream_file_writeln( def .. sub ) merge = true -- -- During the merge operation, when we find the record -- terminator within the template... -- elseif string.match( sub, "^%s*__MINGW_FEATURES_END__%s*$" ) then -- ...we immediately write out any additional option -- specifications, which remain in the user specified -- configuration... -- for ref, sub in next, current_features do stream_file_writeln( sub ) end -- -- ...and break out of the merge cycle. -- break -- elseif merge then -- While we remain within the merge cycle, we extract -- the identification of each configuration option from -- the template, comparing it with all entries in the -- "current_features" table... -- tag = string.match( sub, "%(.*%)" ) for ref, usr in next, current_features do if string.match( usr, tag ) then -- -- ...and, when we find a match, we favour the -- existing configuration record over that from -- the template... -- sub = usr -- -- ...remove the corresponding entry from the -- "current_features" table, and break out of -- the inner matching loop, before... -- table.remove( current_features, ref ) break end end -- -- ...in either event, writing out a copy of each -- selected configuration record. -- stream_file_writeln( sub ) end end else -- -- For every line in the existing configuration file, outside -- the scope of the "__MINGW_FEATURES__" definition itself, we -- simply write out a verbatim copy of each line. -- stream_file_writeln( line ) end end else -- -- There is no existing configuration header, so we simply reproduce -- the template, processing it line by line... -- for ref, line in next, template do -- ...and writing out each line individually. -- stream_file_writeln( line ) end end end -- -- function M.pathname( suffix ) -- -- An exported utility function, to facilitate identification of -- the full MS-Windows path name for the features.h configuration -- file, as appropriate to the current installation... -- if suffix then -- ...appending any suffix which may have been specified, (e.g. -- to specify a reference to the features.h.sample file)... -- return config_file_name .. suffix end -- -- ...otherwise, specifying a reference to features.h itself. -- return config_file_name end -- -- function M.initialize( stream_file ) -- -- Primary initialization function; overwrites any existing features -- configuration header, opened for writing as "stream_file"... -- if not stream_file then -- ...or falling back to "io.stdout", in the event that no output -- stream file has been opened... -- stream_file = io.stdout end -- -- ...replacing any existing configuration with an exact copy of the -- "config_default" content specified within this module. -- update_configuration( stream_file, config_default ) end -- -- function M.update( stream_file ) -- -- Primary API function, exported for use by mingw-get (or any other -- client); overwrites any existing configuration header file, which -- has been opened for writing as "stream_file"... -- if not stream_file then -- ...or falling back to "io.stdout", in the event that no output -- stream file has been opened... -- stream_file = io.stdout end -- -- ...preserving any existing configuration, with the addition of -- any configuration options which have been included in the default -- template, but which do not yet appear in the existing header. -- update_configuration( stream_file, config_default, current_config ) end -- -- Since this source file is intended to be loaded as a Lua module, we -- must ultimately return a reference handle for it. -- return M -- -- $RCSfile$: end of file */