""" InstallFromWorkspace.cobra This program installs Cobra onto your system out of the workspace. It's a great convenience for users who only grab the workspace to get the very latest version of Cobra (e.g., they are not developing on the compiler). But even for developers, it's convenient to "snapshot" the compiler for your system when desired. Normally the compiler and libraries to be installed are compiled with -turbo. However, you can pass an argument to this program such as -debug which will replace -turbo. This is useful for troubleshooting. Why not a .bat file? Won't run on Mac, Linux, etc. Why not a bash script? Won't run on Windows-sans-cygwin. Also, as a .NET program this installer has easy access to the GAC and any other .NET resources. Furthermore, as a Cobra programmer, any Cobra user could potentially hack on it. TO RUN On Windows Update, if desired: > cd \path\to\Workspace > svn up Get into the Source: > cd Source Use the batch file: > bin\install-from-workspace On Mac, Linux, Unix-family, etc. Update, if desired: $ cd /path/to/Workspace $ svn up Get into the Source: $ cd Source Use the script: $ sudo ./bin/install-from-workspace The installer prints "Success!" at the end, if successful. Otherwise you will see an error message. It's safe to run the installer more than once. The installer does not change any workspace files that are under source code control. To test the installation, try invoking Cobra from *outside* the workspace. The command "cobra -about" will give you version, platform and directory information. NOTES In some special cases, users have required that Cobra.Core.dll not be installed to the GAC. There is an option for this called -skip-std-lib. Any other command line options are passed to the Cobra snapshot compiler when invoked to compile the compiler source code. If no options are specified, -turbo is the default. ASSUMPTIONS * Your system meets the requirements for Novell Mono 2.10+ or .NET 4.0+ http://msdn.microsoft.com/en-us/library/ms229070.aspx * This program is run in the Workspace\Source directory. * This program is run by Snapshot\cobra.exe. *NOT* .\cobra.exe TODO [ ] Create an install log [ ] Various "TODO" items in the source itself """ use System.Diagnostics use System.Reflection use System.Security.Principal use System.Text.RegularExpressions # for GAC installation use System.EnterpriseServices use System.EnterpriseServices.Internal # for locating msbuild/xbuild use Microsoft.Build.Utilities from 'Microsoft.Build.Utilities.v4.0' class CommandLineTool """ External tool that is accessed via command invocation. To call it, the tool must be located (found) first. """ var _output as ProcessOutput? var _possibleLocations as List """ Paths to look for the tool exe """ cue init(path as String) """ Name or path of the tool """ require path.length ensure .path == path body base.init _path = path _possibleLocations = List() get path from var as String """ Path of the executable """ get exitCode from var as int? """ Code returned after the execution of the last command """ get environment from var = Dictionary() """ Environment variables of program """ get output as String """ Output of the last program executed """ if _output return _output.combinedOutput.trim else return '' get found from var as bool """ True if the tool is located in the disk, false otherwise. To find the tool use locate method. """ def addPossibleLocations(locations as vari String) """ Add a list of paths (directories) to look for the program """ for loc in locations, _possibleLocations.add(loc) def _locateInDirectories(paths as List) require not .found body for path in paths if File.exists(path) _found = true _path = path return def locateInPossibleLocations """ Check for the existence in a list of paths. Useful if we know the directories where the tool use to be installed. """ require not .found body _locateInDirectories(_possibleLocations) def locateInPath(args as vari String) """ Locate if tool is in the user's PATH executing the command and checking if there where no errors. Some arguments can be passed to the program (i.e. /version -silent) if needed. """ require not .found body try _found = true # for require .found in run .run(args) catch _found = false def locate(args as vari String) require not .found body if File.exists(.path) _found = true return .locateInPossibleLocations if .found, return .locateInPath(args) def toString as String is override return .path def run(args as vari String) require .found body process = Process() process.startInfo.fileName = .path process.startInfo.arguments = args.join(' ') for key, value in _environment process.startInfo.environmentVariables[key] = value _output = process.runAndCaptureOutput _exitCode = process.exitCode # Helper functions def _slash as char return Path.directorySeparatorChar class BuildTool is abstract inherits CommandLineTool """ Common ancestor of MsBuild and Xbuild """ cue init(path as String) base.init(path) def msBuildExtensionsPath as String? """ Returns the location where custom .targets files should be added to. The location is called MSBuildExtensionsPath on both xbuild and msbuild. """ require .found body props = '/property:Configuration="";Platform=""' target = '/target:DisplayMSBuildExtensionsPath' path = '.[_slash]Cobra.MSBuild[_slash]Targets[_slash]Cobra.targets' .run(props, target, path) output = .output.splitLines result = nil for lineNum in output.count if output[lineNum].endsWith('DisplayMSBuildExtensionsPath:') # the next line contains the path we are looking for result = output[lineNum + 1].trim break return result class Xbuild inherits BuildTool cue init base.init('xbuild') .addPossibleLocations('/usr/bin/xbuild') class MsBuild inherits BuildTool cue init base.init('msbuild.exe') .addPossibleLocations(ToolLocationHelper.getPathToDotNetFrameworkFile('msbuild.exe', TargetDotNetFrameworkVersion.Version40) to !) class MsBuild32 inherits BuildTool """ 32 bits version of MsBuild for x86 architectures """ cue init base.init('MSBuild.exe') # ToolLocationHelper.getPathToDotNetFrameworkFile is present but not implemented on Mono # and getPathToBuildToolsFile is only available in 'Microsoft.Build.Utilities.v12.0.dll' # hard-coded to use the 32-bit version of MSBuild from .NET 4.0 otherwise # final verification test of HelloWorld.cobraproj will not pass windowsDir = Environment.getFolderPath(Environment.SpecialFolder.Windows) buildTool = '[windowsDir]\\Microsoft.NET\\Framework\\v4.0.30319\\MSBuild.exe' .addPossibleLocations(buildTool) class GacUtil is abstract inherits CommandLineTool """ Tool to install versioned assemblies into the system Global Assembly Cache (GAC) """ def list(library as String) as List """ List the contents of the global assembly cache. Returns the output of gacutil splitted in lines. """ require .found library.length body .run('-l', library) return .output.splitLines def isInstalled(library as String, version as String?, arch as String?) as bool """ Find if a library is installed. A version can be specified and an architecture to narrow the search. """ require .found library.length body for line in .list(library) if arch and arch in line if version if version in line, return true else return true else if version and version in line return true else return true return false def publish(library as String) """ Install a library through Publish class """ require library.length .found ensure .isInstalled(library, nil, nil) body Publish().gacInstall(library) def install(library as String) """ Install a library in the GAC """ require .found library.length ensure .isInstalled(library, nil, nil) body .run('-i', library, '-f') class GacUtilUnix inherits GacUtil cue init base.init('gacutil') .addPossibleLocations('/usr/bin/gacutil') def locate(args as vari String) is override base.locate('-silent') class GacUtilWindows inherits GacUtil # The .NET team cannot decide on a location for gacutil.exe var _searchDirs = [ # Windows 8.1 SDK r'\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\x64', r'\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools', r'Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools', # VS 2012 r'\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools', # VS2012 r'Microsoft SDKs\Windows\v7.1\Bin', # VS 2010 r'Microsoft SDKs\Windows\v7.1\Bin\NETFX 4.0 Tools\x64', # VS2010 r'Microsoft SDKs\Windows\v7.1\Bin\NETFX 4.0 Tools', # VS2010 r'Microsoft SDKs\Windows\v6.0A\bin', # VS 2008 r'Microsoft.NET\SDK\v2.0\bin', # VS 2005 / SDK .NET 2.0 r'Microsoft Visual Studio 8\SDK\v2.0\Bin', ] cue init base.init('gacutil.exe') def locate(args as vari String) is override """ Try to locate gacutil by different methods """ base.locate(args) if .found, return _locateInDirectories(_windowsSDKDirectories) if .found, return _locateInDirectories(_programFilesDirectories) def _windowsSDKDirectories as List result = List() # to-do: check for x64 in path vs -x86 option # search %ProgramFiles% (which can differ internationally) programFilesDir = Environment.getFolderPath(Environment.SpecialFolder.ProgramFiles) for dir in _searchDirs if Path.isPathRooted(dir) programFilesDrive = programFilesDir[0:1] result.add('[programFilesDrive]:[dir][_slash][.path]') else result.add('[programFilesDir][_slash][dir][_slash][.path]') return result def _programFilesDirectories as List result = List() for dir in _searchDirs # search drives for X:\Program Files\... for drive in Directory.getLogicalDrives drive = drive[:1] # normalize to just one character if drive <= 'B', continue result.add('[drive]:[_slash]Program Files[_slash][dir][_slash][.path]') return result ############################################################################## class Platform is abstract """ Facade for methods related with the platform where running. """ def description as String return CobraCore.operatingSystemDescription def clrVersion as String? return Assembly.getAssembly(Object).imageRuntimeVersion def commandLine as String return Environment.commandLine def currentDirectory as String return Environment.currentDirectory def osVersion as OperatingSystem? return Environment.osVersion def systemDirectory as String? return Environment.systemDirectory def version as Version return Environment.version def x64 as bool return Environment.is64BitProcess def environmentVariable(name as String) as String? return Environment.getEnvironmentVariable(name) def path as IList """ List of directories included in path """ result = .environmentVariable('PATH') if result return result.split(Path.pathSeparator).toList return List() def virtualMachine as String if .isRunningOnMono return 'Mono' else return '.NET' def virtualMachineVersion as String? return nil # TODO: What is in .NET? def print _printPair('Platform', .description) _printPair('Virtual Machine', .virtualMachine) _printPair('CLR Version', .clrVersion) _printPair('Env Command Line', .commandLine) _printPair('Env Current Dir', .currentDirectory) _printPair('Env OS Version', .osVersion) _printPair('Env System Dir', .systemDirectory) _printPair('Env Version', .version) def _printPair(key as String, value) key += ':' print '[key.padRight(18)] [value]' def _printCommand(key as String, command as String, args as String) output as String? try output = Platform.run(command, args).combinedOutput catch pass _printPair(key, _tighten(output)) def _tighten(s as String?) as String? if not s, return s s = s.replace('\r', '').replace('\n', ' ; ').replace('\t', ' ') while s.contains(' '), s = s.replace(' ', ' ') while s.contains('; ;'), s = s.replace('; ;', '; ') while s.contains(' '), s = s.replace(' ', ' ') return s def _printCommand(command as String) _printCommand(command, List()) def _printCommand(command as String, grepList as IList) try output = Platform.run(command).combinedOutput catch output = '' if output == '', return if grepList.count > 0 for line in output.splitLines found = false for grep in grepList if line.startsWith(grep) found = true break if found, _printRawLine(line) else for line in output.splitLines _printRawLine(line) def _printRawLine(line as String) i = line.indexOf(': ') if i == -1, i = line.indexOf(':\t') if i <> -1 _printPair(line[:i].trim, line[i+1:].trim) else print line shared def isRunningOnMono as bool return CobraCore.isRunningOnMono def isRunningOnUnix as bool return CobraCore.isRunningOnUnix def isRunningOnWindows as bool return not .isRunningOnUnix def get as Platform if .isRunningOnMono return MonoPlatform() else return DotNetPlatform() def run(command as String, args as vari String) as ProcessOutput require command.length body process = Process() process.startInfo.fileName = command process.startInfo.arguments = args.join(' ') return process.runAndCaptureOutput class MonoPlatform inherits Platform cue init base.init def virtualMachineVersion as String? is override return CobraCore.monoVersionString def print is override base.print _printCommand('sw_vers') # TODO: Move to MacOSX platform? Mac OS X #_printCommand('lsb_release -a') # could try /etc/*-release if this fails _printCommand('Mono', 'mono', '--version') _printCommand('Uname', 'uname', '-a') print class DotNetPlatform inherits Platform cue init base.init def print is override base.print # systeminfo is commented out below. it can churn the hard drive to butter when it # lists hotfixes and there is option to skip them. additionally, the info is fairly # well covered by other platform commands. # .printPlatformCommand('systeminfo', ['OS Name:', 'OS Version:']) _printCommand('WMI Op Sys Ver', 'wmic', 'os get Caption,CSDVersion /value') _printCommand('Cmd Op Sys Ver', 'cmd.exe', '/c ver') _printCommand('.NET Dirs', 'cmd.exe', r'/c "dir %WINDIR%\Microsoft.Net\Framework\v* /O:-N /B"') print ############################################################################## class Installer is abstract const configFileName = 'install-directory.text' var libs = [ { 'name': 'Cobra.Core', 'flags': '-build-standard-library', 'files': 'Cobra.Core/AssemblyAttrs.cobra', 'requiresGacVerification': true, }, { 'name': 'Cobra.Compiler', 'flags': '-c -t:lib -embed-version -ert:yes -namespace:Cobra.Compiler -files:files-to-compile.text', # 'files': 'Cobra.Core/Compiler.Attributes.cobra', # to-do 'files': 'cobra.cobra', 'requiresGacVerification': true, }, { 'name': 'Cobra.MSBuild', 'flags': '-c -t:lib -embed-version', 'files': 'Cobra.MSBuild/CobraCompiler.cobra', 'requiresGacVerification': false, } ] var _installDirectory = '.' var _msbuildExtPath as String? var _baseVersion as String? # ex: '0.8.0-post', '0.8.0-post-2009-03-01' var _targetDir as String? # ex: 'C:\Cobra', '/usr/local/cobra' var _versionDir as String? # ex: 'C:\Cobra\Cobra-0.8.0-post', 'C:\Cobra\Cobra-0.8.0-post-2009-03-01', '/usr/local/cobra/Cobra-0.8.1-post' var _cobraCommandPath as String? # ex: 'C:\Cobra\bin\cobra.bat', '/usr/local/cobra/bin/cobra' var _args = List() var _skipStdLib = false var _platformArg as String? var _platform as Platform var _snapShotCobra as CommandLineTool var _sourceCobra as CommandLineTool cue init base.init _platform = Platform.get _snapShotCobra = CommandLineTool('Snapshot[.slash]cobra.exe') _sourceCobra = CommandLineTool('cobra.exe') def run .printPlatformDescription .verifyElevatedPermissions .verifyInstallDirectory .verifyInWorkspace .verifyVirtualMachineVersion .locateSnapshotCompiler .buildCobraCompiler .getBaseVersion .makeStandardLibrary .verifyNewlyBuiltCobra .copyFilesToInstallDirectory .verifyNewlyInstalledCobra .cleanUpWorkspace .installInPath .changeDirectoryInstruction def cobraCommandName as String is abstract """ Name of platform specific cobra launch script """ def createCobraCommand is abstract """ Creates a platform specific launcher for cobra compiler """ def buildTool as BuildTool is abstract def gacutil as GacUtil is abstract ## Self utils get compileFlags as String if _args.count > 0, return _args.join(' ') else, return '-turbo' get slash as char return Path.directorySeparatorChar def baseBinDir as String """ Directory where the cobra launcher is installed """ require _targetDir is not nil body return '[_targetDir][.slash]bin' def configure(commandLineArgs as List) for arg in commandLineArgs if arg == '-skip-std-lib' _skipStdLib = true else if arg in ['-net4', '-dotnet4'] pass # old option. this is implicit now else if arg.startsWith('-platform:') _platformArg = arg else if arg == '-x86' _platformArg = '-platform:x86' else _args.add(arg) def onErrorPlatformAdvice """ Print specific platform advices in case of error """ pass def exitWithError(msg) print '** ERROR:', msg print 'Need help with the above error?' .onErrorPlatformAdvice print ' * Review http://cobra-language.com/troubleshooting' print ' * Ask at http://cobra-language.com/discuss' print ' * Ask at http://cobra-language.com/irc' Environment.exit(1) print 'Exiting from error.' def warning(msg) print '** WARNING:', msg ## File system def copyContents(source as String, target as String) print 'copy from:', source print ' to:', target .copyContents(DirectoryInfo(source), DirectoryInfo(target)) def copyContents(source as DirectoryInfo, target as DirectoryInfo) if not target.exists, target.create for sourceFile in source.getFiles sourceFile.copyTo(Path.combine(target.fullName, sourceFile.name), true) for sourceSubDir in source.getDirectories targetSubDir = target.createSubdirectory(sourceSubDir.name) to ! .copyContents(sourceSubDir, targetSubDir) def deleteDir(dir as String) if Directory.exists(dir) print 'del dir :', dir spacer = ' ' _unReadOnly(dir) numAttempts = 3 for attempt in 1 : numAttempts + 1 try Directory.delete(dir, true) # true = recursive catch IOException # sometimes "The directory is not empty." occurs if attempt == numAttempts, throw print spacer, 'Having trouble deleting directory. Try again in [attempt] seconds.' System.Threading.Thread.sleep(attempt*1_000) if not Directory.exists(dir) # and sometimes it goes away! print spacer, 'Directory is gone.' if not Directory.exists(dir), break def moveFile(source as String, target as String) # File.move does not overwrite a file if it already exists File.copy(source, target, true) File.delete(source) def _unReadOnly(dirName as String) _unReadOnly(DirectoryInfo(dirName)) def _unReadOnly(dir as DirectoryInfo) # print 'checking -', dir for file in dir.getFiles if sharp'file.Attributes & System.IO.FileAttributes.ReadOnly' # print 'changing -', file file.attributes = sharp'file.Attributes & ~System.IO.FileAttributes.ReadOnly' # file.attributes = file.attributes & ~FileAttributes.ReadOnly for subDir in dir.getDirectories _unReadOnly(subDir) def findAndDeleteDir(baseDir as String, findDir as String) .findAndDeleteDir(DirectoryInfo(baseDir), findDir) def findAndDeleteDir(baseDir as DirectoryInfo, findDir as String) for sourceSubDir in baseDir.getDirectories if sourceSubDir.name == findDir .deleteDir(sourceSubDir.fullName) else .findAndDeleteDir(sourceSubDir, findDir) def requireDir(dir as String) if Directory.exists(dir) print 'found dir:', dir else print 'make dir :', dir try Directory.createDirectory(dir) catch ex as SystemException .exitWithError('Unable to create installation directory.\n[ex.message]\nRun as admin, or put a correct install path into a file called "[.configFileName]".') def startStage(description as String) print '====', description print # this installer relies on there being no directory changes assert Environment.currentDirectory.toLower.endsWith('source') ## Running external commands def _displayOutput(s as String?) if s or s == '', return for line in s.replace('\r', '').split(c'\n') print ' : [line]' print def runExternalCommand(command as String, args as vari String) as String? """ Runs the given external command with the given args. Sets the process to the instance of Process created for this purpose. Returns the output, which is also displayed if displayOutput is true. Does not check process.exitCode. """ print 'run: [command] [args.join(' ')]' output = Platform.run(command, args).combinedOutput _displayOutput(output) return output def runSnapshotCobra(args as vari String) as String? """ Runs the Cobra compiler. Prints the output and returns it. If Cobra gives an error, calls .exitWithError. """ require _snapShotCobra.found body print 'run: [_snapShotCobra] [args.join(' ')]\n' _snapShotCobra.run(args) _displayOutput(_snapShotCobra.output) if _snapShotCobra.exitCode .exitWithError('Exit code from running SnapshotCobra: [_snapShotCobra.exitCode] [_snapShotCobra.output]') return _snapShotCobra.output def runSourceCobra(args as vari String) as String? require _sourceCobra.found body print 'run: [_snapShotCobra] [args.join(' ')]\n' _sourceCobra.run(args) _displayOutput(_sourceCobra.output) if _sourceCobra.exitCode .exitWithError('Exit code from running Source Cobra: [_sourceCobra.exitCode]') return _sourceCobra.output def runBuildTool(args as vari String) """ Runs msbuild or xbuild. Sets the COBRA_COMMAND_PATH environment variable on the process to ensure the correct Cobra compiler is used by the Cobra.MSBuild.CobraCompiler task. """ require .buildTool.found print 'run: [.buildTool] [args.join(' ')]' try if _cobraCommandPath .buildTool.environment['COBRA_COMMAND_PATH'] = Path.getDirectoryName(_cobraCommandPath) to ! .buildTool.run(args) catch e as Exception .exitWithError('"Failed to run build tool: [.buildTool]. ([e.message])"') ## Stages def printPlatformDescription .startStage('Print Platform Description') _platform.print def verifyElevatedPermissions .startStage('Verify running as admin user') wi = WindowsIdentity.getCurrent wp = WindowsPrincipal(wi) isAdmin = wp.isInRole(WindowsBuiltInRole.Administrator) or wi.token == IntPtr.zero if not isAdmin .exitWithError('Please run this executable as an admin user.') def verifyInstallDirectory .startStage('Verify installation directory') default = _installDirectory if Environment.currentDirectory.startsWith(default) msg = 'The installation files have been placed within the target directory to install Cobra to: "[default]"\n' msg += 'Please place these downloaded installation files in some other (temporary) directory.' .exitWithError(msg) if File.exists(.configFileName) # get install directory from first line of the file useDir = File.openText(.configFileName).readLine if useDir and useDir.trim.length, default = useDir.trim # TODO: prompt the user for the location .requireDir(default) _targetDir = default print def verifyInWorkspace .startStage('Verify running in workspace') msg = 'The current directory does not appear to be a workspace. This program is for installing from the workspace.' if not File.exists('Compiler.cobra'), .exitWithError(msg) if not Directory.exists('Snapshot'), .exitWithError(msg) print 'Verified.' print def verifyVirtualMachineVersion if not Platform.isRunningOnMono return .startStage('Verify Novell Mono version') minMonoVersion = '2.10' vers = _platform.virtualMachineVersion if vers is nil or vers.trim == '' .exitWithError('Cannot determine Mono version. Please install Mono [minMonoVersion] or higher.') else print 'Mono Version', vers parts = vers.split(c'.') if parts and parts[0] >= '2' print 'Verified >= [minMonoVersion]' print else .exitWithError('Mono version must be [minMonoVersion] or higher.') def locateSnapshotCompiler .startStage('Locate and test Cobra Snapshot compiler') _snapShotCobra.locate if not _snapShotCobra.found .exitWithError('Cannot locate [_snapShotCobra].') output = .runSnapshotCobra('-about') assert 'The Cobra Programming Language' in output assert 'Copyright' in output assert 'Usage' in output def makeStandardLibrary if _skipStdLib, return .locateGacUtil .locateBuildTool for lib in .libs # keys: name, flags libName = lib['name'] assert not libName.endsWith('.dll') .installLibrary(libName, lib['flags'], lib['files'].replace('/', .slash.toString), lib['requiresGacVerification']) def locateGacUtil if .gacutil.found, return .startStage('Locate gacutil') .gacutil.locate if not .gacutil.found .exitWithError('Cannot locate a gacutil. Maybe you can try again using the "Visual Studio Command Prompt" or ".NET SDK Command Prompt".') print 'Found:', .gacutil print def locateBuildTool """ Finds the location of msbuild.exe when running on Windows or the location of xbuild otherwise """ .startStage('Locate [.buildTool]') if .buildTool.found, return .buildTool.locate if .buildTool.found print 'Found:', .buildTool print else .exitWithError('Cannot locate [.buildTool].') def buildCobraCompiler .startStage('Build new Cobra compiler') args = [ '-compile', .compileFlags, '-ert:yes', 'cobra.cobra', '-files:files-to-compile.text' ] if _platformArg, args.add(' -sharp-args:\"[_platformArg]\"') .runSnapshotCobra(args.join(' ')) # How List -> vari String ?? _sourceCobra.locate if not _sourceCobra.found .exitWithError('No cobra.exe file was produced.') def getBaseVersion .startStage('Retrieve Cobra base version number') # It's called "base version" because it doesn't include any text after the numbers # "0.8.0 post release" --> "0.8.0" output = .runSourceCobra('-version') reMatch = Regex.match(output, r'svn:(\d+)') if reMatch.success assert reMatch.value _baseVersion = 'svn-' + reMatch.groups[1].toString else reMatch = Regex.match(output, r'\d+\.\d+\.\d+') if reMatch.success assert reMatch.value _baseVersion = reMatch.value else .exitWithError('Could not extract base version number.') _baseVersion += '-post' # check for an informal release parent = Path.getFullPath('..') if File.exists('[parent][.slash]InformalRelease.text') output = File.readAllText('[parent][.slash]InformalRelease.text') re = Regex(r'\d+\-\d+\-\d+') reMatch = re.match(output) if reMatch.success assert reMatch.value _baseVersion += '-' + reMatch.value else .exitWithError('Could not extract date from InformalRelease.text') print 'base version: [_baseVersion]' print def installLibrary(name as String, flags as String, files as String, requiresGacVerification) .buildLibrary(name, flags, files) .installLibraryToGAC(name) .verifyGacInstallation(name, requiresGacVerification) def buildLibrary(name as String, flags as String, files as String) """ Builds the copy of the Cobra Standard Library for installation to the GAC. Must build the library using the Source cobra.exe, not the Snapshot Always build with -debug since this is the copy for GAC installation. The debug info (mdb file) will be installed to the GAC along with the dll. This is required for the -d compiler option. """ .startStage('Build [name] library') args = [ flags, ' -debug', .compileFlags, '-out:[name].dll', '-key-file:Cobra.Core[.slash]Cobra.Core.snk', ' [files]' ] if _platformArg, args.add('-sharp-args:\"[_platformArg]\"') .runSourceCobra(args.join(' ')) # How List -> vari String ?? def installLibraryToGAC(name as String) .startStage('Install [name] library to the GAC') dllName = name + '.dll' print 'Invoking Publish.GacInstall...' try .gacutil.publish(dllName) print 'Done.' print catch print 'Not implemented exception.' print 'Will attempt to use gacutil.exe.' try .gacutil.install(dllName) print catch e as Exception print '[e.getType] [e.message]' def verifyGacInstallation(libName as String, required as bool) .startStage('Verifying [libName].dll installed to the GAC') if _platformArg if .gacutil.found _verifyGacInstallationViaGacUtil(libName, required) else .warning('Cannot verify [libName] due to [_platformArg] and `gacutil` cannot be found.') else if .gacutil.found _verifyGacInstallationViaGacUtil(libName, required) else _verifyGacInstallationViaAssemblyLoad(libName, required) print def _verifyGacInstallationViaGacUtil(libName as String, required as bool) ver = _baseVersion if 'svn-' in ver, ver = '0.0.' + ver[4:] ver = '=' + ver arch as String? if _platformArg, arch = 'processorArchitecture=[_platformArg.split(":")[1]]' found = .gacutil.isInstalled(libName, ver, arch) if found print '[libName].dll has been successfully installed to the GAC.' else msg = 'After installing the library "[libName]", it cannot be loaded.' if required, .exitWithError(msg) else, .warning(msg) def _verifyGacInstallationViaAssemblyLoad(libName as String, required as bool) dllName = libName + '.dll' try # temporarily rename the standard library to ensure that assembly # loading is attempted from the GAC. tmpFileName = 'temp-[Process.getCurrentProcess.id]-[dllName]' File.move(dllName, tmpFileName) try # avoid warning: "System.Reflection.Assembly.LoadWithPartialName(string)" is obsolete # a = Assembly.loadWithPartialName(libName) # with dynamic binding: assemblyType = Assembly to dynamic a as Assembly? try a = assemblyType.loadWithPartialName(libName) catch exc as Exception bar = String(c'-', 80) print bar print exc print bar # error is reported below for `a` being nil if a is nil msg = 'After installing the library "[libName]", it cannot be loaded.' if required, .exitWithError(msg) else, .warning(msg) if a # `a` could be nil due to issuing a warning, rather than an error, above # to-do: verify that a.getName.version is the correct if a.globalAssemblyCache print '[dllName] has been successfully installed to the GAC.' else .warning('After installing the library "[libName]", it can be loaded, but does not report that it is in the GAC.') finally File.move(tmpFileName, dllName) catch NotImplementedException print 'Attempting to use gacutil.' if not .gacutil.found .locateGacUtil .gacutil.run('-l', 'Cobra.Core') if '[dllName], Version=' in .gacutil.output print '[dllName] has been successfully installed to the GAC.' else .exitWithError('Installing the library "[libName]" to the GAC was unsuccessful.') def verifyNewlyBuiltCobra .startStage('Verify newly built Cobra compiler') .runSourceCobra('-about') msg = 'Cannot run hello.cobra with new compiler.' if not _skipStdLib if File.exists('hello.exe'), File.delete('hello.exe') # fresh start File.copy('Misc[.slash]hello.cobra', 'hello.cobra', true) output = .runSourceCobra('-ert:no', 'hello.cobra') if not output.startsWith('Hello') .exitWithError(msg) output = .runSourceCobra('hello.cobra') if not output.startsWith('Hello') .exitWithError(msg) output = .runSourceCobra('-ert:yes', 'hello.cobra') if not output.startsWith('Hello') .exitWithError(msg) def copyFilesToInstallDirectory require _targetDir is not nil .startStage('Copy files to install directory') targetDir = _targetDir if targetDir, .requireDir(targetDir) if Directory.exists('[targetDir][.slash]Source') print print 'The directory "[targetDir]" appears to contain a workspace or snapshot of the' print 'Cobra source code, due to containing the subdirectory "Source".' print 'Installing to that location can lead to confusion and technical difficulties.' print 'Consider clearing out "[targetDir]" and creating a workspace *inside* it called,' print 'for example, "Workspace".' print .exitWithError('Cannot install to workspace or workspace snapshot. See message above.') parent = Path.getFullPath('..') versionDir = '[targetDir][.slash]Cobra-[_baseVersion]' # ex: /usr/local/cobra/Cobra-0.8.0-post if versionDir == parent print print 'The directory "[versionDir]" cannot install over itself.' print 'Rename the directory such as appending the text "-package" and try again.' print .exitWithError('Cannot install over package.') .deleteDir(versionDir) .requireDir(versionDir) _versionDir = versionDir # TODO: readme file? versionOutput = '' if File.exists('[parent][.slash]InformalRelease.text') versionOutput = File.readAllText('[parent][.slash]InformalRelease.text') else svnRevision = SubversionUtils.readSubversionRevision('.', /#checkParentDirs=#/true) if svnRevision, versionOutput = svnRevision.toString if versionOutput <> '' fileName = '[versionDir][.slash]Version.text' print 'writing :', fileName File.writeAllText(fileName, versionOutput) print for dir in ['HowTo', 'Reusables', 'Samples', 'Supplements'] .copyContents('[parent][.slash][dir]', '[versionDir][.slash][dir]') versionBinDir = '[versionDir][.slash]bin' .requireDir(versionBinDir) print 'copy bin: [versionBinDir]' binFiles = 'cobra.exe Cobra.Sharp.*.dll WebAssets styles-cobra-doc.css styles-cobra-help.css styles-cobra-shl.css styles-exception-report.css styles-output-html.css'.split.toList for lib in .libs binFiles.add(lib['name'] + '.dll') if File.exists(lib['name'] + '.dll.mdb'), binFiles.add(lib['name'] + '.dll.mdb') if File.exists(lib['name'] + '.dll.pdb'), binFiles.add(lib['name'] + '.dll.pdb') for fileName in binFiles if Directory.exists(fileName) .copyContents(fileName, '[versionBinDir][.slash][fileName]') else for fileName2 in Directory.getFiles('.', fileName) # glob File.copy(fileName2, '[versionBinDir][.slash][fileName2]') .copyContents('Cobra.Core', '[versionBinDir][.slash]Cobra.Core') .copyMSBuildExtensions(versionBinDir) # delete _svn or .svn from the installation directory .findAndDeleteDir(versionDir, '_svn') .findAndDeleteDir(versionDir, '.svn') # create cobra.bat / cobra for invoking the latest version .requireDir(.baseBinDir) _cobraCommandPath = '[.baseBinDir][.slash][.cobraCommandName]' .createCobraCommand print def copyMSBuildExtensions(versionBinDir as String) require .buildTool.found body _msbuildExtPath = .buildTool.msBuildExtensionsPath if _msbuildExtPath is nil .warning('Unable to locate MSBuildExtensionsPath. Targets file will not be installed.') else _msbuildExtPath = Path.combine(_msbuildExtPath, 'Cobra') .requireDir(_msbuildExtPath to !) .copyContents('Cobra.MSBuild[.slash]Targets', _msbuildExtPath to !) # move the assembly to be alongside the targets file name = 'Cobra.MSBuild.dll' source = '[versionBinDir][.slash][name]' target = '[_msbuildExtPath][.slash][name]' .moveFile(source, target) if File.exists('[source].mdb'), .moveFile('[source].mdb', '[target].mdb') if File.exists('[source].pdb'), .moveFile('[source].pdb', '[target].pdb') def verifyNewlyInstalledCobra .startStage('Verify newly installed Cobra compiler') compiler = CommandLineTool('[.baseBinDir][.slash][.cobraCommandName]') compiler.locate compiler.run('-about') if compiler.exitCode or _ not compiler.output.startsWith('The Cobra Programming Language') .exitWithError('Cannot run the installed Cobra with -about') compiler.run('-ert:yes', 'Misc[.slash]hello.cobra') if compiler.exitCode or _ not compiler.output.startsWith('Hello') print compiler.output .exitWithError('Cannot run the installed Cobra on "hello.cobra"') # only test msbuild/xbuild if a location for the targets file was found if _msbuildExtPath projDir = 'Cobra.MSBuild[.slash]TestProjects[.slash]ExampleSolution[.slash]HelloWorld' .runBuildTool('[projDir][.slash]HelloWorld.cobraproj','/target:rebuild') if not .buildTool.output.contains('Build succeeded.') .exitWithError('Cannot build Cobra project file using installed Cobra.MSBuild library') else output = .runExternalCommand('[projDir][.slash]bin[.slash]Debug[.slash]HelloWorld.exe') if not output.startsWith('Hello') .exitWithError('"HelloWorld.exe" generated from Cobra project failed to run') def cleanUpWorkspace .startStage('Clean up workspace') # By removing cobra.exe and friends from the workspace, # we minimize the chances that the user runs the local Cobra compiler # instead of the installed one. fileNames = ['cobra.exe'] for lib in .libs, fileNames.add(lib['name'] + '.dll') for fileName in fileNames for ext in ['', '.mdb', '.pdb'] try, File.delete(fileName+ext) catch Exception, pass def findCobraCommandInPath as String? """ Finds if .cobraCommandName is in paths. If found the path is returned, nil otherwise. """ for commandDir in _platform.path if commandDir == '.', continue # skip the 'cobra' in the /Source directory # print 'checking path:', commandDir if commandDir.startsWith('~') home = _platform.environmentVariable('HOME') if home, commandDir = home + commandDir[1:] commandPath = Path.getFullPath('[commandDir][.slash][.cobraCommandName]') if File.exists(commandPath) print 'found in PATH:', commandPath return commandPath return nil def isCommandPathEqualToBaseDirCobraCommand(path as String) as bool """ Compares a path with base dir where cobra command maybe not ignoring case """ return String.compare(path, '[.baseBinDir][.slash][.cobraCommandName]', false) == 0 def installCobraCommandIfInPath(commandPath as String) if .isCommandPathEqualToBaseDirCobraCommand(commandPath) # e.g., if the PATH already points to the install directory print 'Your PATH already contains [.baseBinDir]' print 'so you can invoke "cobra" from any directory.' else if .newlyCompiledFileIsSymlinkedFromOneFoundOnPath(_cobraCommandPath, commandPath) print 'The existing "cobra" in your path is the correct' print 'symbolic link and has not been changed.' else .copyCobraCommandTo(commandPath, true) def printInstallManuallyCobraCommand """ The user must include manually cobra in path """ print 'The Cobra compiler is not in your PATH. To remedy you can:' print '1. Add [Path.getDirectoryName(_cobraCommandPath)] to your PATH' print '2. Copy [_cobraCommandPath] to any directory in your PATH' print def installCobraCommandIfNotInPath """ What to do if Cobra command is not in path """ .printInstallManuallyCobraCommand def installInPath .startStage('Install "cobra" into a system path directory') commandPath = .findCobraCommandInPath if commandPath .installCobraCommandIfInPath(commandPath) else .installCobraCommandIfNotInPath def newlyCompiledFileIsSymlinkedFromOneFoundOnPath(cobraCommandPath, commandPath) as bool return false def copyCobraCommandTo(commandPath as String, alreadyExisted as bool) require commandPath.length print 'copy from:', _cobraCommandPath print ' to:', commandPath try, File.delete(commandPath) catch Exception, pass File.copy(_cobraCommandPath, commandPath, true) print if alreadyExisted print 'The existing "cobra" in your path at' print commandPath print 'has been replaced with the new one.' else print 'The "cobra" command has been installed in your PATH at' print commandPath def changeDirectoryInstruction is abstract ############################################################################## class WindowsInstaller inherits Installer """ WindowsInstaller overrides specific methods of base class Installer to install in Windows. """ var _buildTool as BuildTool var _gacutil as GacUtil cue init base.init _installDirectory = r'C:\Cobra' _gacutil = GacUtilWindows() if _platform.x64 and _platformArg and _platformArg.endsWith('x86') _buildTool = MsBuild32() else _buildTool = MsBuild() def buildTool as BuildTool is override return _buildTool def gacutil as GacUtil is override return _gacutil def cobraCommandName as String is override return 'cobra.bat' def createCobraCommand is override print 'writing :', _cobraCommandPath using f = File.createText(_cobraCommandPath) f.writeLine('@"[_versionDir][.slash]bin[.slash]cobra.exe" %*') print def changeDirectoryInstruction is override return # the .bat file takes care of this def onErrorPlatformAdvice is override print ' * Make sure you ran as a Windows Administrator.' print ' * Install .NET or a Visual Studio product if you have not before.' def isCommandPathEqualToBaseDirCobraCommand(path as String) as bool is override """ Compares a path with base dir where cobra command maybe ignoring case """ return String.compare(path, '[.baseBinDir][.slash][.cobraCommandName]', true) == 0 ############################################################################## class UnixInstaller inherits Installer """ UnixInstaller overrides specific methods of base class Installer to install in Unix systems (OSX or Linux). """ var _buildTool as BuildTool var _gacutil as GacUtil cue init base.init _installDirectory = '/usr/local/cobra' _gacutil = GacUtilUnix() _buildTool = Xbuild() def buildTool as BuildTool is override return _buildTool def gacutil as GacUtil is override return _gacutil def cobraCommandName as String is override return 'cobra' def createCobraCommand is override print 'writing :', _cobraCommandPath using f = File.createText(_cobraCommandPath) f.writeLine('#!/bin/sh') f.writeLine('exec mono "[_versionDir][.slash]bin[.slash]cobra.exe" "$@"') Platform.run('chmod', 'a+x', _cobraCommandPath to !) def changeDirectoryInstruction is override # but on unix-family systems, you cannot change the working dir of a parent process .startStage('Change directory instruction') print 'Now that Cobra has been installed, pleased change out of the' print 'current directory, such as:' print print 'cd ../..' print def installCobraCommandIfNotInPath is override # TODO: on unix, create a symlink in /usr/local/bin or /usr/bin if they exist and are in the path paths = _platform.path if '/usr/local/bin' in paths .copyCobraCommandTo('/usr/local/bin/[.cobraCommandName]', false) else if '/usr/bin' in paths .copyCobraCommandTo('/usr/bin/[.cobraCommandName]', false) else .printInstallManuallyCobraCommand def onErrorPlatformAdvice is override print ' * Make sure you ran with sudo or as root.' print ' * Mono 2.10.x or higher are recommended.' def newlyCompiledFileIsSymlinkedFromOneFoundOnPath(cobraCommandPath, commandPath) as bool is override monoAssembly = Assembly.load('Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756') unixFileSystemInfo = monoAssembly.getType('Mono.Unix.UnixFileSystemInfo') to dynamic if unixFileSystemInfo fileInfo = unixFileSystemInfo.getFileSystemEntry(commandPath) if fileInfo.isSymbolicLink and fileInfo.contentsPath == cobraCommandPath return true return false def copyMSBuildExtensions(versionBinDir as String) is override base.copyMSBuildExtensions(versionBinDir) if _msbuildExtPath is nil, return Platform.run('chmod', 'a+r','[_msbuildExtPath][.slash]Cobra.targets') Platform.run('chmod', 'a+r','[_msbuildExtPath][.slash]Cobra.MSBuild.dll') ############################################################################## class InstallFromWorkspace var _installer as Installer cue init base.init if Platform.isRunningOnUnix _installer = UnixInstaller() else _installer = WindowsInstaller() def run print print 'The Cobra Programming Language' print 'Install From Workspace' print print 'Working...' print _installer.configure(CobraCore.commandLineArgs[1:]) _installer.run print 'Visit http://cobra-language.com/ for discussion, wiki, samples, irc and more.' print print 'Success!' def main is shared InstallFromWorkspace().run