1#!@PYTHON@ 2# 3# createmodule.py - Takes the name of a environment init script and 4# produces a modulefile that duplicates the changes made by the init script 5# 6# Copyright (C) 2012 by Orion E. Poplawski <orion@cora.nwra.com> 7# 8# This program is free software: you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation, either version 2 of the License, or 11# (at your option) any later version. 12 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17 18# You should have received a copy of the GNU General Public License 19# along with this program. If not, see <http://www.gnu.org/licenses/>. 20from __future__ import print_function 21 22from optparse import OptionParser 23import os,sys,re 24from subprocess import * 25import platform 26 27# Handle options 28usage = "Usage: %prog [-p prefix] <initscript> [args]" 29parser = OptionParser() 30parser.set_usage(usage) 31parser.add_option('-p', '--prefix', action='store', type='string', dest='prefix', help='Specify path prefix') 32parser.add_option('--noprefix', action='store_true', dest='noprefix', default=False, help='Do not generate a prefix') 33(options, args) = parser.parse_args() 34 35# Need a script name 36if not args: 37 parser.print_usage() 38 exit(1) 39 40# Determine if running environment is based on cmd.exe or not 41def iscmdshell(): 42 return True if platform.system() == 'Windows' else False 43 44# Return environment after a command 45def getenv(cmd = ':'): 46 env = {} 47 if iscmdshell(): 48 # ':' command not supported by cmd.exe 49 cmd = (cmd if cmd != ':' else '@echo off') + " >nul & set" 50 else: 51 cmd = cmd + " >/dev/null;env" 52 p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True) 53 (stdout, stderr) = p.communicate() 54 if p.returncode != 0: 55 print("ERROR: Could not execute initscript:") 56 print("%s returned exit code %d" % (cmd, p.returncode)) 57 print(stderr) 58 exit(1) 59 if stderr != '': 60 print("WARNING: initscript sent the following to stderr:") 61 print(stderr) 62 # Parse the output key=value pairs 63 skip = False 64 for line in stdout.splitlines(): 65 if skip: 66 if line == '}': 67 skip = False 68 continue 69 elif iscmdshell() and line.find('=') == -1: 70 continue 71 try: 72 (var,value) = line.split('=',1) 73 except ValueError: 74 print("ERROR: Could not parse output line:") 75 print(line) 76 exit(1) 77 # Exported functions - not handled 78 if value.find('() {') == 0: 79 skip = True 80 else: 81 env[var] = value 82 return env 83 84#Record initial environment 85env1=getenv() 86 87#Record environment after sourcing the initscript 88if iscmdshell(): 89 if len(args)>1: 90 env2=getenv('"' + args[0] + '" ' + " ".join(args[1:])) 91 else: 92 env2=getenv('"' + args[0] + '"') 93else: 94 env2=getenv(". " + " ".join(args)) 95 96# Initialize our variables for storing modifications 97chdir = None 98appendpath = {} 99prependpath = {} 100unhandled = {} 101setenv = {} 102unsetenv = [] 103pathnames = [] 104 105# Function to nomalize all paths in a list of paths and remove duplicate items 106def normpaths(paths): 107 newpaths = [] 108 for path in paths: 109 normpath = os.path.normpath(path) 110 if normpath not in newpaths and normpath != '.': 111 newpaths.append(os.path.normpath(path)) 112 return newpaths 113 114# Start with existing keys and look for changes 115for key in env1.keys(): 116 # Test for delete 117 if key not in env2: 118 unsetenv.append(key) 119 continue 120 # No change 121 if env1[key] == env2[key]: 122 del env2[key] 123 continue 124 #Working directory change 125 if key == 'PWD': 126 chdir=os.path.normpath(env2[key]) 127 pathnames.append(chdir) 128 del env2[key] 129 continue 130 # Determine modifcations to beginning and end of the string 131 try: 132 (prepend,append) = env2[key].split(env1[key]) 133 except ValueError: 134 continue 135 if prepend: 136 presep = prepend[-1:] 137 prependpaths = prepend.strip(presep).split(presep) 138 # LICENSE variables often include paths outside install directory 139 if 'LICENSE' not in key: 140 pathnames += prependpaths 141 if presep not in prependpath: 142 prependpath[presep] = {} 143 newpath = presep.join(normpaths(prependpaths)) 144 if newpath: 145 prependpath[presep][key] = newpath 146 else: 147 unhandled[key] = env2[key] 148 if append: 149 appsep = append[0:1] 150 appendpaths = append.strip(appsep).split(appsep) 151 # LICENSE variables often include paths outside install directory 152 if 'LICENSE' not in key: 153 pathnames += appendpaths 154 if appsep not in appendpath: 155 appendpath[appsep] = {} 156 newpath = appsep.join(normpaths(appendpaths)) 157 if newpath: 158 appendpath[appsep][key] = newpath 159 else: 160 unhandled[key] = env2[key] 161 del env2[key] 162 163# We're left with new keys in env2 164for key in env2.keys(): 165 # Use prepend-path for new paths 166 if (re.search('(DIRS|FILES|PATH)$',key)) or (':' in env2[key]): 167 prependpaths = env2[key].strip(':').split(':') 168 # MANPATH can have system defaults added it it wasn't previously set 169 # LICENSE variables often include paths outside install directory 170 if key != 'MANPATH' and 'LICENSE' not in key: 171 pathnames += prependpaths 172 if ':' not in prependpath: 173 prependpath[':'] = {} 174 prependpath[':'][key] = ':'.join(normpaths(prependpaths)) 175 continue 176 # Set new variables 177 setenv[key] = os.path.normpath(env2[key]) 178 if 'LICENSE' not in key: 179 pathnames.append(setenv[key]) 180 181# Report unhandled keys 182for key in unhandled.keys(): 183 print("Unhandled change of", key, file=sys.stderr) 184 print("Before <%s>" % env1[key], file=sys.stderr) 185 print("After <%s>" % unhandled[key], file=sys.stderr) 186 for sepkey in appendpath.keys(): 187 appendpath[sepkey].pop(key, None) 188 for sepkey in prependpath.keys(): 189 prependpath[sepkey].pop(key, None) 190 191# Determine a prefix 192prefix=None 193if options.prefix: 194 prefix = options.prefix 195elif not options.noprefix: 196 prefix = os.path.commonprefix(pathnames).rstrip('/') 197 if prefix == '': 198 prefix = None 199 200# Print out the modulefile 201print("#%Module 1.0") 202 203# Prefix 204if prefix is not None: 205 print("\nset prefix " + prefix + "\n") 206 207# Chdir 208if chdir is not None: 209 print("chdir\t" + chdir) 210 211# Function to format output line with tabs and substituting prefix 212def formatline(item, key, value=None): 213 print(item, end=' ') 214 print("\t"*(2-(len(item)+1)//8), end=' ') 215 print(key, end=' ') 216 if value is not None: 217 print("\t"*(3-(len(key)+1)//8), end=' ') 218 if prefix is not None: 219 # Prefer usage of regular expression to perform a none 220 # case-sensitive substitution (cygwin vs cmd.exe) 221 if iscmdshell(): 222 print(re.sub('(?i)' + re.escape(prefix), '$prefix', value)) 223 else: 224 print(value.replace(prefix,'$prefix')) 225 else: 226 print(value) 227 228# Paths first, grouped by variable name 229for sepkey in prependpath.keys(): 230 pathkeys = list(prependpath[sepkey].keys()) 231 pathkeys.sort() 232 for key in pathkeys: 233 if sepkey == ":": 234 formatline("prepend-path",key,prependpath[sepkey][key]) 235 else: 236 formatline("prepend-path --delim %s" % sepkey,key,prependpath[sepkey][key]) 237 238for sepkey in appendpath.keys(): 239 pathkeys = list(appendpath[sepkey].keys()) 240 pathkeys.sort() 241 for key in pathkeys: 242 if sepkey == ":": 243 formatline("append-path",key,appendpath[sepkey][key]) 244 else: 245 formatline("append-path --delim %s" % sepkey,key,appendpath[sepkey][key]) 246 247# Setenv 248setenvkeys = list(setenv.keys()) 249setenvkeys.sort() 250if setenvkeys: 251 print() 252for key in setenvkeys: 253 formatline("setenv",key,setenv[key]) 254 255# Unsetenv 256unsetenv.sort() 257if unsetenv: 258 print() 259for key in unsetenv: 260 formatline("unsetenv",key) 261