From: <Cra...@nt...> - 2005-06-29 17:33:57
|
Author: CrawfordCurrie Date: 2005-06-29 10:33:16 -0700 (Wed, 29 Jun 2005) New Revision: 4461 Added: twiki/branches/DEVELOP/data/TWiki/BuildContrib.txt twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/ twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/DEPENDENCIES twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/MANIFEST twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/TEMPLATE_installer.pl twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/build.pl Log: BuildContrib: ported the build contrib over from CVS and targeted to DEVELOP. I just can't face maintaining it in CVS any more, it's just way too slow. BIG ASSUMPTION: that Will will deliver a manifest-driven build system for building TWikiFors sometime before DakarRelease. Added: twiki/branches/DEVELOP/data/TWiki/BuildContrib.txt =================================================================== --- twiki/branches/DEVELOP/data/TWiki/BuildContrib.txt 2005-06-29 17:29:00 UTC (rev 4460) +++ twiki/branches/DEVELOP/data/TWiki/BuildContrib.txt 2005-06-29 17:33:16 UTC (rev 4461) @@ -0,0 +1,105 @@ +---+!! TWiki Developer Environment + +Automates build process, including installer generation, for Pugins, Add-ons and Contrib modules. + +%TOC% + +---+ Summary of Contents + +---++ Build & Install Modules +The Build.pm module can be used by developers to create a really simple build process for your plugin, addon or contrib module. The advantage of using this module is that it dictates a standard build procedure for plugins, so you won't get file names wrong or get the list of files in the plugin topic wrong either. + +The module also automatically generates an installer script that manages dependencies in the user installation. + +#DevelopmentModel +---+++ Development Model +The build module assumes: + * that you are *not* developing your plugin in a live TWiki installation (which is usually a really bad idea), but are instead doing the sensible thing and developing in a separate directory tree, usually - but not always - a CVS checkout area. + * that your module follows the standards for plugins and contribs i.e. that it + 1 has a topic in the =data/TWiki= directory + 1 has a perl module in the =lib/TWiki/Plugins= or =lib/TWiki/Contrib= directory + 1 has a sub-directory alongside the perl module that contains the extra =.pm= files specific to the module + +The idea is that your module is built using a perl script =build.pl= that is kept in the module-specific subdirectory (3). This build script is like a Makefile. It has several _targets_ that can be used to perform different stages of the development cycle. By default these targets are: +| =build= | check that everything is perl | +| =test= | run unit tests | +| =installer= | Build an install/uninstall script for use by end-users | +| =install= | install on local installation defined by $TWIKI_HOME | +| =uninstall= | uninstall from local installation defined by $TWIKI_HOME | +| =pod= | build POD documentation | +| =release= | build, pod and package a release zip | +| =upload= | build, pod, package and upload to twiki.org | +| =manifest= | print a best-guess _draft_ MANIFEST file | +The default target is =test=. + +The module is designed so that most common behaviour is catered for. It is also easy to _override_ any of the default targets in your =build.pl= and add extra behaviours. + +The result of the 'release' target is a ZIP file, that will contain the following: + 1 All the files listed in the MANIFEST + 1 A generated documentation topic + 1 An install/uninstall script, that is run after the module is unzipped +This zip file can be generated and subsequently uploaded to twiki.org, or you can simply use the =upload= target directly. + +The easiest way to start using the module is to copy the =build.pl= file from =lib/TWiki/Contrib/BuildContrib/build.pl= into your own plugin or contrib, in the corresponding place i.e. copy it to =lib/TWiki/Plugins/MyPlugin/build.pl= or =lib/TWiki/Contrib/MyContrib/build.pl=. Then edit the file, and change as appropriate (it should be fairly obvious what you have to do). Then type + * =perl build.pl manifest= +This should inspect the directory structure and write a best-guess MANIFEST file. + +__Notes__ + * This is a _build_ module for use by developers, not an _install_ module for end users. When a release is built, an installer/uninstaller script is automatically generated that is packaged with the release. This simple script is the only part of the build system visible to end users + * This topic and all the embedded POD documentation are generated using this build module. + * The build environment does _not_ assume the use of CVS, though it is highly recommended. + * The =test= target assumes you are using Test::Unit. =Build.pm= has only been tested on Linux, but should work with cygwin OK. +---+++ Install support +The installer script shipped with the package is very simple. By default all it does is to check the dependencies you list, and if necessary download and install any missing TWiki and CPAN modules. Other dependencies are simply checked. In Dakar and later releases, TWiki topics shipped with the module are automatically merged into any existing local copies, ensuring histories are preserved. + +If you want your installer to do anything else - for example, optionally install submodules shipped with the module - then you will need to write a POSTINSTALL script. A good example of a POSTINSTALL can be found in the CVS tree for TWiki:Plugins/WysiwygPlugin. + +---++ Test Fixtures +The are a number of test fixtures in the repository that can be used for testing plugins with =Test::Unit=. The fixtures replace the =TWiki::Func=, =TWiki::Store= and TWiki modules, allowing a stand-alone unit test suite to be built for the plugin. The easiest way to use the fixtures is to copy the tests from an existing plugin that uses them - for example, CommentPlugin, ActionTrackerPlugin, WebDAVPlugin or FormQueryPlugin - and modify the tests appropriately for your plugin. + +The fixtures do not replace _all_ of the TWiki core functionality, just those pieces used by the plugins. If you find a piece is missing, and you need it, please feel free to extend the fixtures on condition that you check your improvements back in. +---++ Support scripts +=conformance_analyser.pl= is a perl script that analyses the conformance of plugins to the API and packaging standards. It is driven from the zips downloadable from twiki.org. + +---+ Detailed Documentation +%$POD% + +---+ Settings + * Name of the perl package + * Set STUB = %$STUB% + * One line description: + * Set SHORTDESCRIPTION = Automate build process for Pugins, Add-ons and Contrib modules + +---+ Installation Instructions +You are strongly recommended to use this Contrib to help split your code development away from your live TWiki environment, as described [[#DevelopmentModel][above]. + * Download the ZIP file from the Plugin web (see below) + * Unzip ==%TOPIC%.zip== in your development ($TWIKI_DEV) directory. Content: + | *File:* | *Description:* | +%$MANIFEST% + * Optionally, run ==%TOPIC%_installer== to automatically check and install other TWiki modules that this module depends on. You can also do this step manually. + * Alternatively, manually make sure the dependencies listed in the table below are resolved. + %$DEPENDENCIES% + +---+ Contrib Info + +| Authors: | TWiki:Main.CrawfordCurrie | +| Copyright ©: | 2004, Crawford Currie http://www.c-dot.co.uk | +| License: | [[http://www.gnu.org/copyleft/gpl.html][GPL]] | +| Dependencies: | %$DEPENDENCIES% | +| 24/05/05 | 1.007 Crawford Currie improved POSINSTALL support and documentation | +| 24/03/05 | 1.006 Crawford Currie made it look in the same directory as build.pl for MANIFEST and DEPENDENCIES and the INSTALL files, first | +| 15/03/05 | 1.005 Crawford Currie made the installer script check in files in Dakar installs, and fixed bugs highlighted by Thomas Weigert | +| 04/12/04 | 1.004 Martin Cleaver removed twikicli - now in TWiki:Plugins.TWikiShellContrib | +| 12/10/04 | 1.003 Crawford Currie added ONLYIF control to DEPENDENCIES. Added optional PREINSTALL and POSTINSTALL scripts (used to e.g. remove dead code). | +| 1/10/04 | 1.002 Crawford Currie added topic updating reminder to installer | +| 30/08/04 | 1.001 Crawford Currie added installer scripts | +| 26/08/04 | Martin Cleaver added twikicli | +| 14/08/04 | 1.000 Crawford Currie made the initial implementation, separated out from the old SharedCode module | +| Home: | http://TWiki.org/cgi-bin/view/Plugins/%TOPIC% | +| Feedback: | http://TWiki.org/cgi-bin/view/Plugins/%TOPIC%Dev | +| Appraisal: | http://TWiki.org/cgi-bin/view/Plugins/BuildContribAppraisal | + +__Related Topics:__ %TWIKIWEB%.TWikiPreferences + +-- TWiki:Main/CrawfordCurrie - %$DATE% %BR% +-- TWiki:Main/MartinCleaver - %$DATE% Property changes on: twiki/branches/DEVELOP/data/TWiki/BuildContrib.txt ___________________________________________________________________ Name: svn:executable + * Added: twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/DEPENDENCIES =================================================================== --- twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/DEPENDENCIES 2005-06-29 17:29:00 UTC (rev 4460) +++ twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/DEPENDENCIES 2005-06-29 17:33:16 UTC (rev 4461) @@ -0,0 +1,6 @@ +Test::Unit,>=0.24,cpan,Optional, required for unit testing +Pod::Text,>=2.21,cpan, POD documentation extracter +File::Spec,>=0.86,cpan,Used to convert relative paths to absolute +File::Find,>=1.05,cpan,Used to find file components +File::Copy,>=2.06,cpan,Used to copy files +File::Path,>1000000,cpan,Used to build paths Property changes on: twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/DEPENDENCIES ___________________________________________________________________ Name: svn:executable + * Added: twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/MANIFEST =================================================================== --- twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/MANIFEST 2005-06-29 17:29:00 UTC (rev 4460) +++ twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/MANIFEST 2005-06-29 17:33:16 UTC (rev 4461) @@ -0,0 +1,6 @@ +data/TWiki/BuildContrib.txt Description +lib/TWiki/Contrib/Build.pm Main code module +lib/TWiki/Contrib/BuildContrib/build.pl Build script +lib/TWiki/Contrib/BuildContrib/TEMPLATE_installer.pl Installer template +test/bin/TestRunner.pl Script for running tests + Property changes on: twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/MANIFEST ___________________________________________________________________ Name: svn:executable + * Added: twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/TEMPLATE_installer.pl =================================================================== --- twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/TEMPLATE_installer.pl 2005-06-29 17:29:00 UTC (rev 4460) +++ twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/TEMPLATE_installer.pl 2005-06-29 17:33:16 UTC (rev 4461) @@ -0,0 +1,732 @@ +# Install script for %$MODULE% +# +# Copyright (C) 2004 Crawford Currie http://c-dot.co.uk +# +# NOTE TO THE DEVELOPER: THIS FILE IS GENERATED AUTOMATICALLY +# BY THE BUILD PROCESS DO NOT EDIT IT - IT WILL BE OVERWRITTEN +# +use strict; +use Socket; + +my $noconfirm = 0; +my $twiki; +my $NL = "\n"; +my %manifest = ( %$FILES% ); +my @deps = ( %$SATISFIES% ); +my $dakar; +my %available; +my $lwp; +my @archTypes = ( 'tar.gz', 'tgz', 'zip' ); + +require 5.006; + +BEGIN { + my $check_perl_module = sub { + my $module = shift; + + eval "use $module;"; + if( $@ ) { + print "Warning: $module is not available, some installer functions have been disabled\n"; + $available{$module} = 0; + } else { + $available{$module} = 1; + } + return $available{$module}; + }; + + unless ( -d 'lib' && + -d 'bin' && + -e 'bin/setlib.cfg' ) { + die 'This installer must be run from the root directory of a TWiki installation'; + } + my $here = `pwd`; # in case bin is a link + # read setlib.cfg + chdir('bin'); + require 'setlib.cfg'; + chomp($here); chdir($here); + # See if we can make a TWiki. If we can, then we can save topic + # and attachment histories. Key off TWiki::Merge because it was + # added in Dakar. + if( &$check_perl_module( 'TWiki::Merge' )) { + eval "use TWiki"; + $twiki = new TWiki(); + $dakar = 1; + } else { + # Not Dakar + no strict; + do 'lib/TWiki.cfg'; + if( -e 'lib/LocalSite.cfg') { + # Store plugin config in LocalSite.cfg + do 'lib/LocalSite.cfg'; + } + use strict; + $dakar = 0; + } + + if( &$check_perl_module( 'LWP' )) { + $lwp = new LWP::UserAgent(); + $lwp->agent("TWikiPluginsInstaller"); + } + &$check_perl_module( 'CPAN' ); +} + +sub check_dep { + my $dep = shift; + my( $ok, $msg ) = (1, ''); + + if( $dep->{type} =~ /^(perl|cpan)$/i ) { + # Try to 'use' the perl module + eval 'use '.$dep->{name}; + if( $@ ) { + $msg = $@; + $msg =~ s/ in .*$/\n/s; + $ok = 0; + } else { + # OK, it was loaded. See if a version constraint is specified + if( defined( $dep->{version} ) ) { + my $ver; + # check the $VERSION variable in the loaded module + eval '$ver = $'.$dep->{name}.'::VERSION;'; + if( $@ || !defined( $ver ) ) { + $msg .= 'The VERSION of the package could not be found: '. + $@; + $ok = 0; + } else { + # The version variable exists + eval '$ok = ( $ver '.$dep->{version}.' )'; + if( $@ || ! $ok ) { + # The version variable fails the constraint + $msg .= ' '.$ver.' is currently installed: '.$@; + $ok = 0; + } + } + } + } + } else { + # This module has no perl interface, and can't be checked + $ok = 0; + $msg = 'Module is type '.$dep->{type}. + ', and cannot be automatically checked.'.$NL. + 'Please check it manually and install if necessary.'.$NL; + } + return ( $ok, $msg ); +} + +# Satisfy dependencies on modules, by checking: +# 1. If the module is a perl module, then: +# 1. If the module is loadable in the current environment +# 2. If the dependency has specified a version constraint, then +# the module must have a top-level variable VERSION which satisfies +# the constraint. +# Note that all TWiki modules are perl modules - even non-perl +# distributions have a perl 'stub' module that carries the version info. +# 2. If the module is _not_ perl, then we can't check it. +sub satisfy { + my $dep = shift; + my $result = 1; + my $trig = eval $dep->{trigger}; + + return 1 unless ( $trig ); + + print <<DONE; +########################################################## +Checking dependency on $dep->{name}.... +DONE + my ( $ok, $msg ) = check_dep( $dep ); + + unless ( $ok ) { + print <<DONE; +*** %$MODULE% depends on $dep->{type} package $dep->{name} $dep->{version} +which is described as "$dep->{description}" +But when I tried to find it I got this error: + +$msg +DONE + $result = 0; + } + + unless( $ok ) { + if( $dep->{name} =~ m/^TWiki::(Contrib|Plugins)::(\w*)/ ) { + my $pack = $1; + my $packname = $2; + $packname .= $pack if( $pack eq 'Contrib' && $packname !~ /Contrib$/); + my $reply = ask('Would you like me to try to download and install the latest version of '.$packname.' from twiki.org?'); + if( $reply ) { + $result = getPlugin( $packname ); + } + } elsif ( $dep->{type} eq 'cpan' && $available{CPAN} ) { + print <<'DONE'; +This module is available from the CPAN archive (http://www.cpan.org). You +can download and install it from here. The module will be installed +to wherever you configured CPAN to install to. + +DONE + my $reply = ask('Would you like me to try to download and install the latest version of '.$dep->{name}.' from cpan.org?'); + if( $reply ) { + CPAN::install( $dep->{name} ); + ( $ok, $msg ) = check_dep( $dep ); + unless( $ok ) { + my $e = "it"; + if( $CPAN::Config->{makepl_arg} =~ /PREFIX=(\S+)/) { + $e = $1; + } + print STDERR <<DONE; +######################################################################### +# WARNING: I still can't find the module $dep->{name} +# +# If you installed the module in a non-standard directory, make sure you +# have followed the instructions in bin/setlib.cfg and added $e +# to your \@INC path. +######################################################################### + +DONE + } + } + } + } + + return $result; +} + +=pod + +---++ StaticMethod ask( $question ) -> $boolean +Ask a question. +Example: =if( ask( "Proceed?" )) + +=cut + +sub ask { + my $q = shift; + my $reply; + + return 1 if $noconfirm; + + $q .= '?' unless $q =~ /\?\s*$/; + + print $q.' [y/n] '; + while ( ( $reply = <STDIN> ) !~ /^[yn]/i ) { + print 'Please answer yes or no'.$NL; + } + return ( $reply =~ /^y/i ) ? 1 : 0; +} + +=pod + +---++ StaticMethod prompt( $question, $default ) -> $string +Prompt for a string, using a default if return is pressed. +Example: =$dir = prompt("Directory"); + +=cut + +sub prompt { + my( $q, $default) = @_; + my $reply = ''; + while( !$reply ) { + print $q; + print " ($default)" if defined $default; + print ': '; + $reply = <STDIN>; + chomp($reply); + $reply ||= $default; + } + return $reply; +} + +=pod + +---++ StaticMethod getConfig( $major, $minor, $cairo ) -> $string + * =$major= name of major key. + * =$minor= if undefined, there is no minor key + * =$cairo= expression that when evaled will get the cairo style config var +Get the value of a config var, trying first the Dakar option and +then if that fails, the Cairo name (if any). +Example: +=getConfig("Name")= +will get the value of =$TWiki::cfg{Name}=. +=getConfig("MyPlugin", "Name")= +will get the value of =$TWiki::cfg{Name}=. +=getConfig("HomeTopicName", undef, '$mainTopicname')= +will get the name of the WebHome topic on Dakar and Cairo. + +See setConfig for more information on major/minor keys. + +=cut + +sub getConfig { + my( $major, $minor, $cairo ) = @_; + + if( $minor && defined $TWiki::cfg{$major}{$minor} ) { + return $TWiki::cfg{$major}{$minor}; + } + if (!$minor && defined $TWiki::cfg{$major}) { + return $TWiki::cfg{$major}; + } + + if( defined $cairo ) { + return eval $cairo; + } + return undef; +} + +=pod + +---++ StaticMethod setConfig( $major, ... ) + * =$major= if defined, name of major key. If not given, there is no major key and the minorkeys are treated as major keys + * =...= list of minorkey=>value pairs +Set the given configuration variables. =$value= must be complete +with all syntactic sugar including quotes. +The valued are written to $TWiki::cfg{major key}{setting} if a major +key is given (recommended, use the plugin name) or $TWiki::cfg{setting} otherwise. Example: +<verbatim> +setConfig( 'CarsPlugin', Name=>"'Mercedes'" }; +setConfig( Tools => "qw(hammer saw screwdriver)" }; +</verbatim> +will write +<verbatim> +$TWiki::cfg{CarsPlugin}{Best} = 'Mercedes'; +$TWiki::cfg{Tools} = qw(hammer saw screwdriver); +</verbatim> + +=cut + +sub setConfig { + my @settings = @_; + my $txt; + my $key; + if (scalar(@settings) % 2) { + # pluck the first odd parameter off to be the key + $key = shift @settings; + } + my %keys = @settings; + if( -e "lib/LocalSite.cfg" ) { + open(F, "<lib/LocalSite.cfg") || + die "Failed to open lib/LocalSite.cfg for read"; + undef $/; + $txt = <F>; + close(F); + # kill the old settings if any are there + foreach my $setting ( keys %keys ) { + if( $key ) { + $txt =~ s/\$TWiki::cfg{$key}{$setting}\s*=.*;//s; + } else { + $txt =~ s/\$TWiki::cfg{$setting}\s*=.*;//s; + } + } + } + open(F, ">lib/LocalSite.cfg") || + die "Failed to open lib/LocalSite.cfg for write"; + print F $txt if $txt; + foreach my $setting ( keys %keys ) { + if( defined $key ) { + print F '$TWiki::cfg{'.$key.'}{'.$setting.'} = '; + } else { + print F '$TWiki::cfg{'.$setting.'} = '; + } + print F $keys{$setting}, ";\n"; + } + close(F); + + # is this Cairo or earlier? If it is, we need to include + # LocalSite.cfg from TWiki.cfg + unless( $dakar ) { + open(F, "<lib/TWiki.cfg"); + undef $/; + $txt = <F>; + close(F); + unless( $txt =~ /^do.*LocalSite\.cfg/m ) { + $txt =~ s/^\s*1\s*;\s*$//m; + open(F, ">lib/TWiki.cfg") || + die "Failed to open lib/TWiki.cfg for write"; + print F "$txt\ndo 'LocalSite.cfg';\n1;\n"; + close(F); + } + } +} + +# Try and download an archive from a URI +# Return undef if the download failed +sub getArchive { + my $pack = shift; + + foreach my $type ( @archTypes ) { + my $f = $pack.'.'.$type; + + if( -e $f ) { + my $ans = ask( 'An existing '.$f. + ' exists; would you like me to use it?' ); + return $f if $ans; + } + unless ( unlink( $f )) { + print STDERR 'Could not remove old '.$f.$NL; + } + } + + my $response; + foreach my $type ( @archTypes ) { + $response = $lwp->get( $pack.'.'.$type ); + + if( $response->is_success ) { + return $response->content(); + } + } + + print STDERR 'Failed to download ', $pack, + ' -- ', $response->status_line, $NL; + + return undef; +} + +# Download the zip file for the given plugin package from twiki.org. +sub getPlugin { + my $packname = shift; + + my $data = getArchive( 'http://www.twiki.org/p/pub/Plugins/$pack/$pack' ); + + return 0 unless unpackArchive( $data ); + + if( -e $packname.'_installer.pl' ) { + print `perl ${packname}_installer.pl install`; + if ( $? ) { + print STDERR 'Installation of ',$packname,' failed',$NL; + return 0; + } + } + + return 1; +} + +=pod + +---++ StaticMethod unpackArchive( $archive, $remapper ) +Unpack an archive. The unpacking method is determined from the file +extension e.g. .zip, .tgz. .tar, etc. + +The remapper is a callback function that is used to rename +target file paths, $remapper( $path ) -> $path. This supports +installations that have renamed their data and pub directories, +for example. + +=cut + +sub unpackArchive { + my( $name, $remapper ) = @_; + + if( $name =~ /\.zip/i ) { + return unzip( $name, $remapper ); + } elsif( $name =~ /(\.tar\.gz|\.tgz|\.tar)/ ) { + return untar( $name, $remapper ); + } else { + print STDERR 'Failed to unpack archive ',$name, + '; unrecognized file type\n'; + } +} + +=pod + +---++ StaticMethod unzip $archive ) +Unzip a zip using Archive::Zip if installed, falling back to +command-line unzip otherwise. + +=cut + +sub unzip { + my( $archive, $remapper ) = @_; + + eval 'use Archive::Zip'; + unless ( $@ ) { + my $zip = new Archive::Zip( $archive ); + unless ( $zip ) { + print STDERR 'Could not open zip file '.$archive.$NL; + return 0; + } + + my @members = $zip->members(); + foreach my $member ( @members ) { + my $file = $member->fileName(); + my $target = $remapper ? &$remapper( $file ) : $file ; + my $err = $zip->extractMember( $file, $target ); + if ( $err ) { + print STDERR 'Failed to extract ',$file,' from zip file ', + $zip,'. Archive may be corrupt.',$NL; + return 0; + } else { + print " $target\n"; + } + } + } else { + print STDERR 'Archive::Zip is not installed; trying unzip'.$NL; + print `unzip $archive`; + if ( $! ) { + print STDERR 'unzip failed: ',$!,$NL; + return 0; + } + } + + return 1; +} + +=pod + +---++ StaticMethod untar( $archive, $remapper ) +Unpack a tar using Archive::Tar if installed, falling back to +command-line tar otherwise. + +=cut + +sub untar { + my( $archive, $remapper ) = @_; + + my $compressed = ( $archive =~ /z$/i ) ? 'z' : ''; + + eval 'use Archive::Tar'; + unless ( $@ ) { + my $tar = new Archive::Tar( $archive, $compressed ); + unless ( $tar ) { + print STDERR 'Could not open tar file '.$archive.$NL; + return 0; + } + + my @members = $tar->list_files(); + foreach my $file ( @members ) { + my $target = $remapper ? &$remapper( $file ) : $file; + + my $err = $tar->extract_file( $file, $target ); + unless ( $err ) { + print STDERR 'Failed to extract ',$file,' from tar file ', + $tar,'. Archive may be corrupt.',$NL; + return 0; + } else { + print " $target\n"; + } + } + } else { + print STDERR 'Archive::Tar is not installed; trying tar'.$NL; + print `tar xvf$compressed $archive`; + if ( $! ) { + print STDERR 'tar failed: ',$!,$NL; + return 0; + } + } + + return 1; +} + +# Check in. On Cairo, do nothing because the apache user +# has everything checked out :-( +sub checkin { + my( $web, $topic, $file ) = @_; + + # If this is Dakar, we have a good chance of completing the + # install. + my $err = 1; + if( $twiki ) { + my $user = + $twiki->{users}->findUser($TWiki::cfg{DefaultUserLogin}); + if( $file ) { + $err = $twiki->{store}->saveAttachment + ( $web, $topic, $file, $user, + { comment => 'Saved by installer' } ); + } else { + # read the topic to recover meta-data + my( $meta, $text ) = + $twiki->{store}->readTopic( $user, $web, $topic ); + $err = $twiki->{store}->saveTopic + ( $user, $web, $topic, $text, $meta, + { comment => 'Saved by installer' } ); + } + } + return ( !$err ); +} + +sub uninstall { + my $file; + my @dead; + foreach $file ( keys %manifest ) { + if( -e $file ) { + push( @dead, $file ); + } + } + unless ( $#dead > 1 ) { + print STDERR 'No part of %$MODULE% is installed'; + return 0; + } + print 'To uninstall %$MODULE%, the following files will be deleted:'.$NL; + print join( ', ', @dead ); + my $reply = ask('Are you SURE you want to uninstall %$MODULE%?'); + if( $reply ) { + # >>>> PREUNINSTALL + %$PREUNINSTALL%; + # <<<< PREUNINSTALL + foreach $file ( keys %manifest ) { + if( -e $file ) { + unlink( $file ); + } + } + # >>>> POSTUNINSTALL + %$POSTUNINSTALL%; + # <<<< POSTUNINSTALL + } + print '### %$MODULE% uninstalled ###'.$NL; + return 1; +} + +sub install { + # >>>> PREINSTALL + %$PREINSTALL%; + # <<<< PREINSTALL + unless ( $noconfirm ) { + print 'Hit <Enter> to proceed with installation',$NL; + <STDIN>; + } + my $unsatisfied = 0; + foreach my $dep ( @deps ) { + unless ( satisfy( $dep ) ) { + $unsatisfied++; + } + } + + # For each file in the MANIFEST, set the permissions, and check + # to see if it is targeted at pub or data. If it is, then add a + # call to "checkin" for the file. + my @topic; + my @pub; + my @bads; + my $file; + foreach $file ( keys %manifest ) { + if( $file =~ /^data\/(\w+)\/(\w+).txt$/ ) { + push(@topic, $file); + } elsif( $file =~ /^pub\/(\w+)\/(\w+)\/([^\/]+)$/ ) { + push(@pub, $file); + } + chmod( $manifest{$file}, $file ) || + print STDERR "WARNING: cannot set permissions on $file\n"; + } + foreach $file ( @topic ) { + $file =~ /^data\/(\w+)\/(\w+).txt$/; + unless( checkin( $1, $2, undef )) { + push( @bads, $file ); + } + } + foreach $file ( @pub ) { + $file =~ /^pub\/(\w+)\/(\w+)\/([^\/]+)$/; + unless( checkin( $1, $2, $3 )) { + push( @bads, $file ); + } + } + + if( scalar( @bads )) { + print STDERR ' +WARNING: I cannot automatically update the local revision history for:',"\n\t"; + print STDERR join( "\n\t", @bads ); + print STDERR <<DONE; + +You can update the revision histories of these files by: + 1. Editing any topics and saving them without changing them, + 2. Uploading attachments to the same topics again. +Ignore this warning unless you have modified the files locally. +DONE + } + + print $NL.'### %$MODULE% installed'; + print ' with ',$unsatisfied.' unsatisfied dependencies' if ( $unsatisfied ); + print ' ###'.$NL; + # >>>> POSTINSTALL + %$POSTINSTALL%; + # <<<< POSTINSTALL + + print $NL,'### Installation finished ###',$NL; +} + +sub usage { + print STDERR <<'DONE'; +Usage:%$MODULE%_installer [-a] install + %$MODULE%_installer [-a] uninstall + %$MODULE%_installer [-a] upgrade + +Operates on the directory tree below where it is run from, +so should be run from the top level of your TWiki installation. + +install will check dependencies and perform any required +post-install steps. + +uninstall will remove all files that were installed for +%$MODULE% even if they have been locally modified. + +upgrade will download the latest zip from TWiki.org and install +it, overwriting your existing zip and installer script. + +-a means don't prompt for confirmation before resolving + dependencies + +DONE +} + +unshift( @INC, 'lib' ); + +print $NL,'### %$MODULE% Installer ###',$NL,$NL; +my $n = 0; +my $action = 'install'; +while ( $n < scalar( @ARGV ) ) { + if( $ARGV[$n] eq '-a' ) { + $noconfirm = 1; + } elsif( $ARGV[$n] =~ m/(install|uninstall|upgrade)/ ) { + $action = $1; + } else { + usage( ); + die 'Bad parameter '.$ARGV[$n]; + } + $n++; +} + +print <<DONE; +This installer must be run from the root directory of your TWiki +installation. +DONE +unless( $noconfirm ) { + print <<DONE + * The script will not do anything without asking you for + confirmation first. +DONE +} +print <<DONE; + * You can abort the script at any point and re-run it later + * If you answer 'no' to any questions you can always re-run + the script again later +DONE + +if( $action eq 'install' ) { + install(); +} + +if( $action eq 'uninstall' ) { + uninstall(); +} + +if( $action eq 'upgrade' ) { + my $zip = '%$MODULE%.zip'; + + return 0 unless getArchive( '%$MODULE%' ); + + print <<DONE; +I would like to uninstall %$MODULE% before upgrading, to +make sure that any files that have been removed from the +package are also removed from your installation. +DONE + my $reply = ask("Is it OK to uninstall the existing package?"); + if( $reply ) { + uninstall(); + } else { + print <<DONE; +Installation will overwrite any files previously installed for +%$MODULE%. +DONE + $reply = ask('Is this OK?'); + exit unless $reply; + } + + if( unzip( $zip )) { + # Recursively invoke the (new) installer + print `perl %$MODULE%_installer.pl install`; + } +} Added: twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/build.pl =================================================================== --- twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/build.pl 2005-06-29 17:29:00 UTC (rev 4460) +++ twiki/branches/DEVELOP/lib/TWiki/Contrib/BuildContrib/build.pl 2005-06-29 17:33:16 UTC (rev 4461) @@ -0,0 +1,54 @@ +#!/usr/bin/perl -w +# +# Example build class. Copy this file to the equivalent place in your +# plugin or contrib and edit. +# +# Read the comments at the top of lib/TWiki/Contrib/Build.pm for +# details of how the build process works, and what files you +# have to provide and where. +# +# Requires the environment variable TWIKI_LIBS (a colon-separated path +# list) to be set to point at the build system and any required dependencies. +# Usage: ./build.pl [-n] [-v] [target] +# where [target] is the optional build target (build, test, +# install, release, uninstall), test is the default.` +# Two command-line options are supported: +# -n Don't actually do anything, just print commands +# -v Be verbose +# + +# Standard preamble +BEGIN { + foreach my $pc (split(/:/, $ENV{TWIKI_LIBS})) { + unshift @INC, $pc; + } +} + +use TWiki::Contrib::Build; + +# Declare our build package +{ package BuildBuild; + + @BuildBuild::ISA = ( "TWiki::Contrib::Build" ); + + sub new { + my $class = shift; + return bless( $class->SUPER::new( "BuildContrib", "Build" ), $class ); + } + + # Example: Override the build target + sub target_build { + my $this = shift; + + $this->SUPER::target_build(); + + # Do other build stuff here + } +} + +# Create the build object +$build = new BuildBuild(); + +# Build the target on the command line, or the default target +$build->build($build->{target}); + |