Wiki

root/cobra/trunk/Source/InstallFromWorkspace.cobra

Revision 2661, 32.5 KB (checked in by Charles.Esterbrook, 8 days ago)

Minor.

  • Property svn:eol-style set to native
Line 
1"""
2InstallFromWorkspace.cobra
3
4This program installs Cobra onto your system out of the workspace. It's a great
5convenience for users who only grab the workspace to get the very latest
6version of Cobra (e.g., they are not developing on the compiler). But even for
7developers, it's convenient to "snapshot" the compiler for your system when
8desired.
9
10Normally the compiler and libraries to be installed are compiled with -turbo.
11However, you can pass an argument to this program such as -debug which will
12replace -turbo. This is useful for troubleshooting.
13
14Why not a .bat file? Won't run on Mac, Linux, etc.
15
16Why not a bash script? Won't run on Windows-sans-cygwin.
17
18Also, as a .NET program this installer has easy access to the GAC and any other
19.NET resources. Furthermore, as a Cobra programmer, any Cobra user could
20potentially hack on it.
21
22
23TO RUN
24
25    On Windows
26
27        Update, if desired:
28        > cd \path\to\Workspace
29        > svn up
30
31        Get into the Source:
32        > cd Source
33
34        Use the batch file:
35        > install-from-workspace
36
37    On Mac, Linux, etc.
38
39        Update, if desired:
40        $ cd /path/to/Workspace
41        $ svn up
42
43        Get into the Source:
44        $ cd Source
45
46        Use the script:
47        $ sudo ./install-from-workspace
48
49
50    The installer prints "Success!" at the end, if successful.
51    Otherwise you will see an error message.
52
53    It's safe to run the installer more than once.
54
55    The installer does not change any workspace files that are under source code control.
56
57    To test the installation, try invoking Cobra from *outside* the workspace.
58
59
60NOTES
61
62    In some special cases, users have required that Cobra.Lang.dll not be
63    installed to the GAC. There is an option for this called -skip-std-lib.
64    Any other command line options are passed to the Cobra snapshot compiler
65    when invoked to compile the compiler source code. If no options are
66    specified, -turbo is the default.
67
68   
69ASSUMPTIONS
70
71    * Your system meets the requirements for Novell Mono 2.6 or .NET 2.0
72      http://msdn.microsoft.com/en-us/library/ms229070.aspx
73
74    * This program is run in the Workspace\Source directory.
75
76    * This program is run by Snapshot\cobra.exe. *NOT* .\cobra.exe
77
78    * The current Cobra compiler in Source\ will report a -version equal to the
79      last released version plus optional additional text. If the last release
80      was "0.8.0" then the new release should be "0.8.0 post".
81
82    * The current Cobra compiler in Source\ will have three components to its
83      base version *number*. Form: x.y.z  Example: 0.8.0
84
85
86TODO
87
88    [ ] Create an install log
89
90    [ ] Various "TODO" items in the source itself
91
92"""
93
94use System.Diagnostics
95use System.Reflection
96use System.Security.Principal
97use System.Text.RegularExpressions
98
99# for GAC installation
100use System.EnterpriseServices
101use System.EnterpriseServices.Internal
102
103
104class InstallFromWorkspace
105
106    var installDirectories = [r'C:\Cobra', '/usr/local/cobra']
107
108    const configFileName = 'install-directory.text'
109
110    var libs = [
111        {
112            'name': 'Cobra.Lang',
113            'flags': '-build-standard-library',
114            'files': 'Cobra.Lang/AssemblyAttrs.cobra',
115        },
116        {
117            'name': 'Cobra.Lang.Compiler',
118            'flags': '-c -t:lib -namespace:Cobra.Lang.Compiler -files:files-to-compile.text',
119            'files': 'cobra.cobra',
120        },
121    ]
122
123    def main is shared
124        InstallFromWorkspace().run
125
126    var _lastCommand as String?
127
128    var _snapshotCompilerPath as String?
129    var _gacutil as String?
130    var _baseVersion as String?       # ex: '0.8.0-post', '0.8.0-post-2009-03-01'
131    var _targetDir as String?         # ex: 'C:\Cobra', '/usr/local/cobra'
132    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'
133    var _cobraCommandPath as String?  # ex: 'C:\Cobra\bin\cobra.bat', '/usr/local/cobra/bin/cobra'
134    var _args = List<of String>()
135    var _skipStdLib = false
136    var _willForceDotNet4 = false
137
138    def run
139        print
140        print 'The Cobra Programming Language'
141        print 'Install From Workspace'
142        print
143        print 'Working...'
144        print
145        .printPlatform
146        _args = .parseCommandLineArgs
147        .verifyElevatedPermissions
148        .verifyInstallDirectory
149        .verifyInWorkspace
150        .askNet4
151        .verifyVirtualMachineVersion
152        .locateSnapshotCompiler
153        # .locateGacUtil
154        .buildCobraCompiler
155        .getBaseVersion
156        if not _skipStdLib
157            for lib in .libs
158                # keys: name, flags
159                libName = lib['name']
160                assert not libName.endsWith('.dll')
161                .installLibrary(libName, lib['flags'], lib['files'].replace('/', .slash.toString))
162        .verifyNewlyBuiltCobra
163        .copyFilesToInstallDirectory
164        .verifyNewlyInstalledCobra
165        .cleanUpWorkspace
166        .installInPath
167        print
168        print 'Visit http://cobra-language.com/ for discussion, wiki, samples, irc and more.'
169        print
170        print 'Success!'
171
172
173    ## Self utils
174
175    get compileFlags as String
176        if _args.count > 0, return _args.join(' ')
177        else, return '-turbo'
178
179    def isRunningOnCLR2 as bool
180        ver = Assembly.getAssembly(Object).imageRuntimeVersion
181        return ver.startsWith('v2.') or ver.startsWith('2.')
182
183    def isRunningOnWindows as bool
184        return not .isRunningOnUnix
185
186    def isRunningOnUnix as bool
187        """
188        Returns true if the current process is running on Unix/Posix/Linux/BSD/etc.
189        """
190        return CobraCore.isRunningOnUnix
191
192    get lastCommand from var
193
194    get slash as char
195        return Path.directorySeparatorChar
196
197    def parseCommandLineArgs as List<of String>
198        args = CobraCore.commandLineArgs
199        args = args[1:]
200
201        i = args.indexOf('-skip-std-lib')
202        if i > -1
203            args.removeAt(i)
204            _skipStdLib = true
205
206        i = args.indexOf('-net4')
207        if i == -1, i = args.indexOf('-dotnet4')
208        if i > -1
209            args.removeAt(i)
210            _willForceDotNet4 = true
211
212        return args
213
214    def setUpCLR4(args as List<of String>)
215        # locate the external C# .NET 4 compiler
216        if CobraCore.isRunningOnMono
217            .setUpCLR4InMono(args)
218        else if .isRunningOnUnix
219            # does not compute. hmmm maybe DotGNU?
220            .error('Running on UNIX, but not running Mono. Cannot support -net4.')
221        else
222            .setUpCLR4InDotNet4(args)
223        args.add('-turbo')
224
225    def setUpCLR4InMono(args as List<of String>)
226        # look for dmcs - the Mono C# .NET 4 compiler
227        found = false
228        for path in Environment.getEnvironmentVariable('PATH').split(c':')
229            dmcs = Path.combine(path, 'dmcs')
230            if File.exists(dmcs)
231                found = true
232                break
233        if not found
234            .error('Cannot find the Mono dmcs compiler required by -net4. Make sure you have Mono 2.10+ installed and that dmcs is on your path.')
235        args.add('-native-compiler:' + dmcs)
236
237    def setUpCLR4InDotNet4(args as List<of String>)
238        # should be on Windows and Microsoft.NET at this point
239        # look for the Microsoft .NET 4.0 compiler
240        # Unfortunately, it's mere existance can be a red herring--
241        # a Windows 7 Ult 32-bit user for example, will have this file, but will still need to
242        # install either .NET 4.0 or one of the Microsoft Visual 2010 products.
243       
244        # the 'Windows' enum is only in .NET 4:
245        # windowsPath = Environment.getFolderPath(Environment.SpecialFolder.Windows)  # typically C:\Windows
246
247        for envVarName in ['windir', 'WINDIR', 'systemroot', 'SYSTEMROOT']
248            windowsPath = Environment.getEnvironmentVariable(envVarName)
249            if windowsPath and windowsPath <> ''
250                break
251        if windowsPath is nil or windowsPath == ''
252            .error('Cannot locate the Windows system directory required by -net4.')
253        csc = Path.combine(windowsPath, r'Microsoft.NET\Framework\v4.0.30319\csc.exe')
254        if not File.exists(csc)
255            .error('Cannot find the Microsoft csc compiler required by -net4. Try installing MS .NET 4.0, or the 4.0+SDK or one of the Visual 2010 products (the Express ones are free).')
256        args.add('-native-compiler:' + csc)
257
258    def error(msg)
259        print '** ERROR:', msg
260        print 'Need help with the above error?'
261        if .isRunningOnUnix
262            print ' * Make sure you ran with sudo or as root.'
263            print ' * Mono 2.6.x or Mono 2.10.x or higher are recommended.'
264        else
265            print ' * Make sure you ran as a Windows Administrator.'
266            print ' * Install .NET or a Visual Studio product if you have not before.'
267        print ' * Review http://cobra-language.com/troubleshooting'
268        print ' * Ask at http://cobra-language.com/discuss'
269        print ' * Ask at http://cobra-language.com/irc'
270        Environment.exit(1)
271        print 'Exiting from error.'
272
273    def warning(msg)
274        print '** WARNING:', msg
275
276    def isAdmin as bool
277        wi = WindowsIdentity.getCurrent
278        wp = WindowsPrincipal(wi)
279        return wp.isInRole(WindowsBuiltInRole.Administrator) or wi.token == IntPtr.zero
280
281
282    ## File system
283
284    def copyContents(source as String, target as String)
285        print 'copy from:', source
286        print '       to:', target
287        .copyContents(DirectoryInfo(source), DirectoryInfo(target))
288
289    def copyContents(source as DirectoryInfo, target as DirectoryInfo)
290        if not target.exists, target.create
291        for sourceFile in source.getFiles
292            sourceFile.copyTo(Path.combine(target.fullName, sourceFile.name), true)
293        for sourceSubDir in source.getDirectories
294            targetSubDir = target.createSubdirectory(sourceSubDir.name)
295            .copyContents(sourceSubDir, targetSubDir)
296
297    def deleteDir(dir as String)
298        if Directory.exists(dir)
299            print 'del dir  :', dir
300            spacer = '          '
301            _unReadOnly(dir)
302            numAttempts = 3
303            for attempt in 1 : numAttempts + 1
304                try
305                    Directory.delete(dir, true# true = recursive
306                catch IOException
307                    # sometimes "The directory is not empty." occurs
308                    if attempt == numAttempts, throw
309                    print spacer, 'Having trouble deleting directory. Try again in [attempt] seconds.'
310                    System.Threading.Thread.sleep(attempt*1_000)
311                    if not Directory.exists(dir# and sometimes it goes away!
312                        print spacer, 'Directory is gone.'
313                if not Directory.exists(dir), break
314
315    def _unReadOnly(dirName as String)
316        _unReadOnly(DirectoryInfo(dirName))
317
318    def _unReadOnly(dir as DirectoryInfo)
319        # print 'checking -', dir
320        for file in dir.getFiles
321            if sharp'file.Attributes & System.IO.FileAttributes.ReadOnly'
322                # print 'changing -', file
323                file.attributes = sharp'file.Attributes & ~System.IO.FileAttributes.ReadOnly'
324                # file.attributes = file.attributes & ~FileAttributes.ReadOnly
325        for subDir in dir.getDirectories
326            _unReadOnly(subDir)
327
328    def findAndDeleteDir(baseDir as String, findDir as String)
329        .findAndDeleteDir(DirectoryInfo(baseDir), findDir)
330
331    def findAndDeleteDir(baseDir as DirectoryInfo, findDir as String)
332        for sourceSubDir in baseDir.getDirectories
333            if sourceSubDir.name == findDir
334                .deleteDir(sourceSubDir.fullName)
335            else
336                .findAndDeleteDir(sourceSubDir, findDir)
337
338    def requireDir(dir as String)
339        if Directory.exists(dir)
340            print 'found dir:', dir
341        else
342            print 'make dir :', dir
343            try
344                Directory.createDirectory(dir)
345            catch ex as SystemException
346                .error('Unable to create installation directory.\n[ex.message]\nRun as admin, or put a correct install path into a file called "[.configFileName]".')
347
348    def startStage(description as String)
349        print '====', description
350        print
351        # this installer relies on there being no directory changes
352        assert Environment.currentDirectory.endsWith('Source')
353
354
355    ## Running external commands
356
357    def runCommand(command as String, args as String) as String
358        return .runCommand(command, args, true)
359
360    def runCommand(command as String, args as String, displayOutput as bool) as String
361        process as Process?
362        output = .runCommand(command, args, out process, displayOutput)
363        if process.exitCode, .error('Exit code from above command: [process.exitCode]')
364        return output
365
366    def runCommand(command as String, args as String, process as out Process?) as String
367        return .runCommand(command, args, out process, true)
368
369    def runCommand(command as String, args as String, process as out Process?, displayOutput as bool) as String
370        """
371        Runs the given external command with the given args.
372        Sets the process to the instance of Process created for this purpose.
373        Returns the output, which is also displayed if displayOutput is true.
374        Does not check process.exitCode.
375        """
376        print 'run: [command] [args]'
377        _lastCommand = command + ' ' + args
378        p = Process()
379        p.startInfo.fileName = command
380        p.startInfo.arguments = args
381        output = CobraCore.runAndCaptureAllOutput(p).trim
382        process = p
383        if displayOutput and output <> ''
384            for line in output.replace('\r', '').split(c'\n')
385                print '   : [line]'
386        print
387        return output
388
389    def runSnapshotCobra(args as String) as String
390        """
391        Runs the Cobra compiler.
392        Prints the output and returns it.
393        If Cobra gives an error, calls .error.
394        """
395        process as Process?
396        output = .runSnapshotCobra(args, out process)
397        if process.exitCode, .error('Exit code from running SnapshotCobra: [process.exitCode]')
398        return output
399
400    def runSnapshotCobra(args as String, process as out Process?) as String
401        """
402        Runs the Cobra compiler.
403        Creates and "returns" the Process instance via the `process` argument.
404        Prints the output and returns it.
405        This does not exit on any errors. Check the process yourself.
406        """
407        return .runCommand(_snapshotCompilerPath to !, args, out process)
408
409    def runSourceCobra(args as String) as String
410        process as Process?
411        output = .runSourceCobra(args, out process)
412        if process.exitCode, .error('Exit code from running Source Cobra: [process.exitCode]')
413        return output
414
415    def runSourceCobra(args as String, process as out Process?) as String
416        return .runCommand('cobra.exe', args, out process)
417
418
419    ## Stages
420
421    def printPlatform
422        .startStage('Print Platform Description')
423        if .isRunningOnUnix
424            .printPlatformCommand('sw_vers')  # Mac OS X
425            .printPlatformCommand('lsb_release -a')  # could try /etc/*-release if this fails
426            .printPlatformPairCommand('Uname', 'uname -a')
427        else
428            .printPlatformPairCommand('WMI Op Sys Ver', 'wmic os get Caption,CSDVersion /value')
429            .printPlatformCommand('system info', ['OS Name:', 'OS Version:'])
430            .printPlatformPairCommand('Cmd Op Sys Ver', 'ver')
431        .printPlatformPair('Virtual Machine', if(CobraCore.isRunningOnMono, 'Mono', '.NET'))
432        .printPlatformPair('CLR Version', Assembly.getAssembly(Object).imageRuntimeVersion)
433        .printPlatformPair('Env Command Line', Environment.commandLine)
434        .printPlatformPair('Env Current Dir', Environment.currentDirectory)
435        .printPlatformPair('Env OS Version', Environment.osVersion)
436        .printPlatformPair('Env System Dir', Environment.systemDirectory)
437        .printPlatformPair('Env Version', Environment.version)
438        if .isRunningOnUnix
439            .printPlatformPairCommand('Mono', 'mono --version')
440        else
441            .printPlatformCommand(r'dir %WINDIR%\Microsoft.Net\Framework\v* /O:-N /B')
442        print
443
444    def printPlatformPair(key as String, value)
445        key += ':'
446        print '[key.padRight(18)] [value]'
447
448    def printPlatformPairCommand(key as String, command as String)
449        .printPlatformPair(key, _platformCommand(command))
450
451    def printPlatformCommand(command as String) 
452        .printPlatformCommand(command, List<of String>())
453
454    def printPlatformCommand(command as String, grepList as IList<of String>)
455        output = _platformCommand(command)
456        if output is nil, return
457        if grepList.count > 0
458            for line in output.splitLines
459                found = false
460                for grep in grepList
461                    if line.contains(grep)
462                        found = true
463                        break
464                if found, _printRawLine(line)
465        else
466            for line in output.splitLines
467                _printRawLine(line)
468
469    def _platformCommand(command as String) as String?
470        # fails silently as some platform commands may not be available
471        if ' ' in command
472            command, args = command.split(@[c' '], 2)
473        else
474            args = ''
475        p = Process()
476        p.startInfo.fileName = command
477        p.startInfo.arguments = args
478        try
479            return CobraCore.runAndCaptureAllOutput(p).trim
480        catch
481            return nil
482
483    def _printRawLine(line as String)
484        i = line.indexOf(': ')
485        if i == -1, i = line.indexOf(':\t')
486        if i <> -1
487            .printPlatformPair(line[:i].trim, line[i+1:].trim)
488        else
489            print line
490
491    def verifyElevatedPermissions
492        .startStage('Verify running as admin user')
493        if not .isAdmin
494            .error('Please run this executable as an admin user.')
495
496    def verifyInstallDirectory
497        .startStage('Verify installation directory')
498        default = if(.isRunningOnUnix, .installDirectories[1], .installDirectories[0])
499        if File.exists(.configFileName)
500            # get install directory from first line of the file
501            useDir = File.openText(.configFileName).readLine
502            if useDir and useDir.trim.length, default = useDir.trim
503        # TODO: prompt the user for the location
504        .requireDir(default)
505        _targetDir = default
506        print
507
508    def verifyInWorkspace
509        .startStage('Verify running in workspace')
510        msg = 'The current directory does not appear to be a workspace. This program is for installing from the workspace.'
511        if not File.exists('Compiler.cobra'), .error(msg)
512        if not Directory.exists('Snapshot'), .error(msg)
513        print 'Verified.'
514        print
515
516    def askNet4
517        .startStage('.NET 4.0 Prompt')
518        if _willForceDotNet4
519            print '-net4 was passed on the command line'
520            answer = 'y'
521        else
522            print 'Cobra can be installed for .NET 2.0/3.0/3.5 or .NET 4.0.'
523            default = 'n'
524            if CobraCore.isRunningOnMono
525                mono = CobraCore.monoVersionString
526                if mono and mono.length >= 'x.y'.length
527                    if mono.startsWith('3.')
528                        default = 'y'
529                    else if mono.startsWith('2.') and mono.split(c'.')[1] in ['10', '11', '12']
530                        default = 'y'
531            else if .isRunningOnWindows
532                # windowsPath = Environment.getFolderPath(Environment.SpecialFolder.Windows) # .NET 4 only
533                windowsPath = Environment.getEnvironmentVariable('WINDIR') ? Environment.getEnvironmentVariable('SYSTEMROOT')
534                if windowsPath
535                    net4Path = Path.combine(windowsPath, r'Microsoft.NET\Framework\v4.0.30319\csc.exe')
536                    if File.exists(net4Path), default = 'y'
537            while true
538                print 'Do you want to install for .NET 4.0? \[[default]] ' stop
539                answer = Console.readLine.trim
540                if answer == '', answer = default
541                answer = answer[0].toString.toLower
542                if answer in 'yn', break
543        print
544        if answer == 'y'
545            _willForceDotNet4 = true
546            .setUpCLR4(_args)
547
548    def verifyVirtualMachineVersion
549        if CobraCore.isRunningOnMono
550            .startStage('Verify Novell Mono version')
551            minMonoVersion = if(_willForceDotNet4, '2.10', '2.6')
552            vers = CobraCore.monoVersionString
553            if vers is nil or vers.trim == ''
554                .error('Cannot determine Mono version. Please install Mono [minMonoVersion] or higher.')
555            else
556                print 'Mono Version', vers
557                parts = vers.split(c'.')
558                if parts and parts[0] >= '2'
559                    print 'Verified >= [minMonoVersion]'
560                    print
561                else
562                    .error('Mono version must be [minMonoVersion] or higher.')
563
564    def locateSnapshotCompiler
565        .startStage('Locate and test Cobra Snapshot compiler')
566        _snapshotCompilerPath = 'Snapshot[.slash]cobra.exe'
567        if not File.exists(_snapshotCompilerPath)
568            .error('Cannot locate [_snapshotCompilerPath].')
569        # Not needed; clutters the output; tends to obscure when the Snapshot is being used;
570        # _snapshotCompilerPath = Path.getFullPath(_snapshotCompilerPath)
571        output = .runSnapshotCobra('-about')
572        assert 'The Cobra Programming Language' in output
573        assert 'Copyright' in output
574        assert 'Usage' in output
575
576    def locateGacUtil(startStage as bool)
577        if startStage, .startStage('Locate gacutil.exe')
578        errorMsg = 'Cannot locate a gacutil. Maybe you can try again using the "Visual Studio Command Prompt" or ".NET SDK Command Prompt".'
579        if _willForceDotNet4 and .isRunningOnCLR2 and .isRunningOnWindows
580            programFilesDir = Environment.getFolderPath(Environment.SpecialFolder.ProgramFiles)
581            if programFilesDir.length >= 2
582                drive = programFilesDir[0].toString
583            else
584                drive = 'C'
585            # the following is wrong as it will be gacutil 3.5:
586            # gacutil = drive + r':\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\GacUtil.exe'
587            # gacutil 4.0 lives here:
588            gacutil = drive + r':\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\GacUtil.exe'
589            if File.exists(gacutil)
590                _gacutil = gacutil
591                return
592            .error(errorMsg)
593        slash = .slash
594        gacutil = 'gacutil'
595        p = Process()
596        p.startInfo.fileName = gacutil
597        p.startInfo.arguments = '-silent'
598        try
599            CobraCore.runAndCaptureAllOutput(p)
600            found = true
601            print 'found in system PATH'
602        catch FileNotFoundException
603            pass
604        catch ComponentModel.Win32Exception
605            pass
606
607        if not found
608            # try to find the gacutil
609            print 'Searching for gacutil...'
610            gacutil = 'gacutil.exe'
611
612            dirs = [
613                r'Microsoft.NET\SDK\v2.0\bin',        # VS 2005 / SDK .NET 2.0
614                r'Microsoft SDKs\Windows\v6.0A\bin',  # VS 2008
615                r'Microsoft Visual Studio 8\SDK\v2.0\Bin',
616            ]
617
618            # search %ProgramFiles% (which can differ internationally)
619            programFilesDir = Environment.getFolderPath(Environment.SpecialFolder.ProgramFiles)
620            for dir in dirs
621                path = '[programFilesDir][slash][dir][slash][gacutil]'
622                print 'checking:', path
623                if File.exists(path)
624                    print 'found:', path
625                    gacutil = path
626                    found = true
627                    break
628                # else, print path
629
630            if not found
631                for dir in dirs
632                    # search drives for X:\Program Files\...
633                    for drive in Directory.getLogicalDrives
634                        drive = drive[:1]  # normalize to just one character
635                        if drive <= 'B', continue
636                        path = '[drive]:[slash]Program Files[slash][dir][slash][gacutil]'
637                        print 'checking:', path
638                        if File.exists(path)
639                            print 'found:', path
640                            gacutil = path
641                            found = true
642                            break
643                        # else, print path
644                    if found, break
645
646        if not found, .error(errorMsg)
647        _gacutil = gacutil
648        if startStage, print
649
650    def buildCobraCompiler
651        .startStage('Build new Cobra compiler')
652        .runSnapshotCobra('-compile [.compileFlags] -ert:yes cobra.cobra -files:files-to-compile.text')
653        if not File.exists('cobra.exe'), .error('No cobra.exe file was produced.')
654
655    def getBaseVersion
656        .startStage('Retrieve Cobra base version number')
657        # It's called "base version" because it doesn't include any text after the numbers
658        # "0.8.0 post release" --> "0.8.0"
659        output = .runSourceCobra('-version')
660        reMatch = Regex.match(output, r'svn:(\d+)')
661        if reMatch.success
662            assert reMatch.value
663            _baseVersion = 'svn-' + reMatch.groups[1].toString
664        else
665            reMatch = Regex.match(output, r'\d+\.\d+\.\d+')
666            if reMatch.success
667                assert reMatch.value
668                _baseVersion = reMatch.value
669            else
670                .error('Could not extract base version number.')
671            _baseVersion += '-post'
672        # check for an informal release
673        parent = Path.getFullPath('..')
674        if File.exists('[parent][.slash]InformalRelease.text')
675            output = File.readAllText('[parent][.slash]InformalRelease.text')
676            re = Regex(r'\d+\-\d+\-\d+')
677            reMatch = re.match(output)
678            if reMatch.success
679                assert reMatch.value
680                _baseVersion += '-' + reMatch.value
681            else
682                .error('Could not extract date from InformalRelease.text')
683        print 'base version: [_baseVersion]'
684        print
685
686    def installLibrary(name as String, flags as String, files as String)
687        .buildLibrary(name, flags, files as String)
688        .installLibraryToGAC(name)
689        .verifyGacInstallation(name)
690
691    def buildLibrary(name as String, flags as String, files as String)
692        """
693        Builds the copy of the Cobra Standard Library for installation to the GAC.
694        Must build the library using the Source cobra.exe, not the Snapshot
695        Always build with -debug since this is the copy for GAC installation. The debug info (mdb file)
696        will be installed to the GAC along with the dll. This is required for the -d compiler option.
697        """
698        .startStage('Build [name] library')
699        .runSourceCobra('[flags] -debug [.compileFlags] -out:[name].dll -key-file:Cobra.Lang[.slash]Cobra.Lang.snk [files]')
700
701    def installLibraryToGAC(name as String)
702        .startStage('Install [name] library to the GAC')
703        dllName = name + '.dll'
704        if _willForceDotNet4 and .isRunningOnCLR2 and .isRunningOnWindows
705            # have to run gacutil command line
706            .locateGacUtil(false)
707            .runCommand(_gacutil, '-i [dllName] -f')
708        else
709            try
710                print 'Invoking Publish.GacInstall...'
711                Publish().gacInstall(dllName)
712                print 'Done.'
713                print
714            catch NotImplementedException
715                print 'Not implemented exception.'
716                print 'Will attempt to use gacutil.exe.'
717                .locateGacUtil(false)
718                .runCommand(_gacutil, '-i [dllName] -f')
719            catch e as Exception
720                print '[e.getType] e.message'
721
722    def verifyGacInstallation(name as String)
723        # TODO: verify the version number: Cobra.Lang, Version=0.0.1.0
724        dllName = name + '.dll'
725        .startStage('Verifying [dllName] installed to the GAC')
726        if _willForceDotNet4 and .isRunningOnCLR2
727            print 'Skipping due to running on .NET 2, but installed for .NET 4.'
728            print
729            return
730        try 
731            # temporarily rename the standard library to ensure that assembly
732            # loading is attempted from the GAC.
733            tmpFileName = 'temp-[Process.getCurrentProcess.id]-[dllName]'
734            File.move(dllName, tmpFileName)
735            # avoid warning: "System.Reflection.Assembly.LoadWithPartialName(string)" is obsolete
736            # a = Assembly.loadWithPartialName(name)
737            # with dynamic binding:
738            assemblyType = Assembly to dynamic
739            a = assemblyType.loadWithPartialName(name)
740            if a is nil
741                .error('After installing the library "[name]", it cannot be loaded.')
742            if a.globalAssemblyCache
743                print '[dllName] has been successfully installed to the GAC.'
744            else
745                .warning('After installing the library "[name]", it can be loaded, but does not report that it is in the GAC.')
746            File.move(tmpFileName, dllName)
747        catch NotImplementedException
748            print 'Attempting to use gacutil.'
749            if not _gacutil, .locateGacUtil(false)
750            output = .runCommand(_gacutil, '-l Cobra.Lang')
751            if '[dllName], Version=' in output
752                print '[dllName] has been successfully installed to the GAC.'
753            else
754                .error('Installing the library "[name]" to the GAC was unsuccessful.')
755        print       
756
757    def verifyNewlyBuiltCobra
758        .startStage('Verify newly built Cobra compiler')
759        .runSourceCobra('-about')
760        msg = 'Cannot run hello.cobra with new compiler.'
761        if not _skipStdLib
762            if File.exists('hello.exe'), File.delete('hello.exe'# fresh start
763            File.copy('Misc[.slash]hello.cobra', 'hello.cobra', true)
764            hello = 'hello.cobra'
765            output = .runSourceCobra('-ert:no [hello]')
766            if not output.startsWith('Hello'), .error(msg)
767            output = .runSourceCobra('[hello]')
768            if not output.startsWith('Hello'), .error(msg)
769        output = .runSourceCobra('-ert:yes [hello]')
770        if not output.startsWith('Hello'), .error(msg)
771
772    def copyFilesToInstallDirectory
773        require _targetDir is not nil
774        .startStage('Copy files to install directory')
775        slash = .slash
776        targetDir = _targetDir
777
778        if targetDir, .requireDir(targetDir)
779        if Directory.exists(Path.combine(targetDir, 'Source'))
780            print 'The directory "[targetDir]" appears to contain a workspace or snapshot of the'
781            print 'Cobra source code, due to containing the subdirectory "Source".'
782            print 'Installing to that location can lead to confusion and technical difficulties.'
783            print 'Consider clearing out "[targetDir]" and creating a workspace *inside* it called,'
784            print 'for example, "Workspace".'
785            print
786            .error('Cannot install to workspace or workspace snapshot. See message above.')
787
788        versionDir = '[targetDir][slash]Cobra-[_baseVersion]'  # ex: /usr/local/cobra/Cobra-0.8.0-post
789        .deleteDir(versionDir)
790        .requireDir(versionDir)
791        _versionDir = versionDir
792
793        # TODO: readme file?
794        parent = Path.getFullPath('..')
795
796        versionOutput = ''
797        if File.exists('[parent][slash]InformalRelease.text')
798            versionOutput = File.readAllText('[parent][slash]InformalRelease.text')
799        else if File.exists('.svn') or File.exists('_svn')
800            # record "svn info" in the installation directory
801            svnFailed = 'Cannot find Subversion revision information. The svn program is not installed or it failed. This only means that the revision number of the workspace is not recorded in the installation directory for your convenience. The compiler and libraries do not rely on this.'
802            try
803                process as Process?
804                versionOutput = .runCommand('svn', 'info', out process, false)
805                if process.exitCode
806                    print '"svn info" failed. Check your svn installation.'
807                    print '[versionOutput]'
808                    print
809                    .warning(svnFailed)
810            catch ex as SystemException
811                # user could be on TortoiseSVN
812                print 'svn is not installed or it is not in the PATH. ', ex.message
813                .warning(svnFailed)
814        if versionOutput <> ''
815            fileName = '[versionDir][slash]Version.text'
816            print 'writing  :', fileName
817            File.writeAllText(fileName, versionOutput)
818        print
819
820        for dir in ['HowTo', 'Reusables', 'Samples', 'Supplements']
821            .copyContents('[parent][slash][dir]', '[versionDir][slash][dir]')
822
823        versionBinDir = '[versionDir][slash]bin'
824        .requireDir(versionBinDir)
825        print 'copy  bin: [versionBinDir]'
826        binFiles = 'cobra.exe Cobra.Lang.dll 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
827        for fileName in binFiles
828            if Directory.exists(fileName)
829                .copyContents(fileName, '[versionBinDir][slash][fileName]')
830            else
831                for fileName2 in Directory.getFiles('.', fileName# glob
832                    File.copy(fileName2, '[versionBinDir][slash][fileName2]')
833
834        .copyContents('Cobra.Lang', '[versionBinDir][slash]Cobra.Lang')
835
836        # delete _svn or .svn from the installation directory
837        .findAndDeleteDir(versionDir, '_svn')
838        .findAndDeleteDir(versionDir, '.svn')
839
840        # create cobra.bat / cobra for invoking the latest version
841        baseBinDir = '[targetDir][slash]bin'
842        .requireDir(baseBinDir)
843        if .isRunningOnUnix
844            _cobraCommandPath = '[baseBinDir][slash]cobra'
845            print 'writing  :', _cobraCommandPath
846            using f = File.createText(_cobraCommandPath)
847                f.writeLine('#!/bin/sh')
848                f.writeLine('exec mono "[versionDir][slash]bin[slash]cobra.exe" "$@"')
849            .runCommand('chmod', "a+x '[_cobraCommandPath]'")
850        else
851            _cobraCommandPath = '[baseBinDir][slash]cobra.bat'
852            print 'writing  :', _cobraCommandPath
853            using f = File.createText(_cobraCommandPath)
854                f.writeLine('@"[versionDir][slash]bin[slash]cobra.exe" %*')
855            print
856
857    def verifyNewlyInstalledCobra
858        .startStage('Verify newly installed Cobra compiler')
859        output = .runCommand(_cobraCommandPath, '-about', true)
860        if not output.startsWith('The Cobra Programming Language')
861            .error('Cannot run the installed Cobra with -about')
862        output = .runCommand(_cobraCommandPath, '-ert:yes Misc[.slash]hello.cobra', true)
863        if not output.startsWith('Hello')
864            .error('Cannot run the installed Cobra on "hello.cobra"')
865
866    def cleanUpWorkspace
867        .startStage('Clean up workspace')
868        # By removing cobra.exe and friends from the workspace,
869        # we minimize the chances that the user runs the local Cobra compiler
870        # instead of the installed one.
871        fileNames = ['cobra.exe']
872        for lib in .libs, fileNames.add(lib['name'] + '.dll')
873        for fileName in fileNames
874            for ext in ['', '.mdb', '.pdb']
875                try, File.delete(fileName+ext)
876                catch Exception, pass
877
878    def installInPath
879        .startStage('Install "cobra" into a system path directory')
880        commandName = if(.isRunningOnUnix, 'cobra', 'cobra.bat')
881        slash = .slash
882        paths = (Environment.getEnvironmentVariable('PATH') ? '').split(Path.pathSeparator).toList
883        for commandDir in paths
884            if commandDir == '.', continue  # skip the 'cobra' in the <workspace>/Source directory
885            # print 'checking path:', commandDir
886            if commandDir.startsWith('~')
887                home = Environment.getEnvironmentVariable('HOME')
888                if home, commandDir = home + commandDir[1:]
889            commandPath = Path.getFullPath('[commandDir][slash][commandName]')
890            if File.exists(commandPath)
891                print 'found in PATH:', commandPath
892                found = true
893                break
894#       print
895        baseBinDir = '[_targetDir][slash]bin'
896        if found
897            if String.compare(commandPath, '[baseBinDir][slash][commandName]', not .isRunningOnUnix) == 0
898                # e.g., if the PATH already points to the install directory
899                print 'Your PATH already contains [baseBinDir]'
900                print 'so you can invoke "cobra" from any directory.'
901            else
902                if .newlyCompiledFileIsSymlinkedFromOneFoundOnPath(_cobraCommandPath, commandPath)
903                    print 'The existing "cobra" in your path is the correct'
904                    print 'symbolic link and has not been changed.'
905                else
906                    .copyCobraCommandTo(commandPath, true)
907        else if '/usr/local/bin' in paths
908            .copyCobraCommandTo('/usr/local/bin/[commandName]', false)
909        else
910            # TODO: on unix, create a symlink in /usr/local/bin or /usr/bin if they exist and are in the path
911            print 'The Cobra compiler is not in your PATH. To remedy you can:'
912            print '1. Add [Path.getDirectoryName(_cobraCommandPath)] to your PATH'
913            print '2. Copy [_cobraCommandPath] to any directory in your PATH'
914
915    def newlyCompiledFileIsSymlinkedFromOneFoundOnPath(cobraCommandPath, commandPath) as bool
916        if .isRunningOnUnix
917            monoAssembly = Assembly.load('Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756')
918            unixFileSystemInfo = monoAssembly.getType('Mono.Unix.UnixFileSystemInfo') to dynamic
919            if unixFileSystemInfo
920                fileInfo = unixFileSystemInfo.getFileSystemEntry(commandPath)
921                if fileInfo.isSymbolicLink and fileInfo.contentsPath == cobraCommandPath
922                    return true
923        return false
924
925    def copyCobraCommandTo(commandPath as String, alreadyExisted as bool)
926        require commandPath.length
927        print 'copy from:', _cobraCommandPath
928        print '       to:', commandPath
929        try, File.delete(commandPath)
930        catch Exception, pass
931        File.copy(_cobraCommandPath, commandPath, true)
932        print
933        if alreadyExisted
934            print 'The existing "cobra" in your path at'
935            print commandPath
936            print 'has been replaced with the new one.'
937        else
938            print 'The "cobra" command has been installed in your PATH at'
939            print commandPath
940
Note: See TracBrowser for help on using the browser.