001    package org.apache.maven.scm.provider.bazaar;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.maven.scm.ScmException;
023    import org.apache.maven.scm.ScmFileStatus;
024    import org.apache.maven.scm.log.DefaultLog;
025    import org.apache.maven.scm.provider.bazaar.command.BazaarConstants;
026    import org.apache.maven.scm.provider.bazaar.command.BazaarConsumer;
027    import org.codehaus.plexus.util.cli.Commandline;
028    
029    import java.io.File;
030    import java.util.regex.Matcher;
031    import java.util.regex.Pattern;
032    
033    /**
034     * Check bazaar installation.
035     *
036     * @author <a href="mailto:torbjorn@smorgrav.org">Torbj�rn Eikli Sm�rgrav</a>
037     * @version $Id: BazaarConfig.java 806087 2009-08-20 08:41:15Z struberg $
038     */
039    public class BazaarConfig
040    {
041        //Minimum version for the Bazaar SCM
042        private static final float BAZAAR_REQ = 0.7f;
043    
044        private static final float PYTHON_REQ = 2.4f;
045    
046        //Bazaar specific
047        private static final String BAZAAR_VERSION_TAG = "bzr (bazaar-ng) ";
048    
049        private static final String BAZAAR_INSTALL_URL = "'http://bazaar-vcs.org/Installation'";
050    
051        //Python specific
052        private static final String PYTHON_EXEC = "python";
053    
054        private static final String PYTHON_VERSION = "-V";
055    
056        private static final String PYTHON_VERSION_TAG = "Python ";
057    
058        //Python modules
059        private static final String PARAMIKO = "\"import paramiko\"";
060    
061        private static final String CCRYPT = "\"import Crypto\"";
062    
063        private static final String CELEMENTREE = "\"import cElementTree\"";
064    
065        //Configuration to check with default values (not installed)
066        private VersionConsumer bazaarVersion = new VersionConsumer( null );
067    
068        private VersionConsumer pythonVersion = new VersionConsumer( null );
069    
070        private boolean cElementTree = false;
071    
072        private boolean paramiko = false;
073    
074        private boolean cCrypt = false;
075    
076        BazaarConfig( File workingDir )
077        {
078            try
079            {
080                pythonVersion = getPythonVersion( workingDir );
081                paramiko = checkPyModules( workingDir, PARAMIKO ); //does not throw
082                cCrypt = checkPyModules( workingDir, CCRYPT ); //does not throw
083                cElementTree = checkPyModules( workingDir, CELEMENTREE ); //does not throw
084                bazaarVersion = getBazaarVersion( workingDir );
085            }
086            catch ( ScmException e )
087            {
088                //Ignore - Either python and/or bazaar is not installed.
089                //This is already recorded thus we do not generate more info.
090            }
091    
092        }
093    
094        private boolean checkPyModules( File workingDir, String cmd )
095        {
096            PythonConsumer consumer = new PythonConsumer();
097            int exitCode;
098            try
099            {
100                Commandline cmdLine = buildPythonCmd( workingDir, new String[]{"-c", cmd} );
101                exitCode = BazaarUtils.executeCmd( consumer, cmdLine );
102            }
103            catch ( ScmException e )
104            {
105                //Ignore - error here is likly to manifest itself when checking python anyway.
106                exitCode = -1;
107            }
108    
109            return exitCode == 0 && consumer.getConsumedAndClear().equals( "" );
110        }
111    
112        /**
113         * @return True if one can run basic bazaar commands
114         */
115        private boolean isInstalled()
116        {
117            return pythonVersion.isVersionOk( PYTHON_REQ ) && bazaarVersion.isVersionOk( BAZAAR_REQ );
118        }
119    
120        /**
121         * @return True if all modules for bazaar are installed.
122         */
123        private boolean isComplete()
124        {
125            return isInstalled() && cElementTree && paramiko && cCrypt;
126        }
127    
128        public static VersionConsumer getBazaarVersion( File workingDir )
129            throws ScmException
130        {
131            String[] versionCmd = new String[]{BazaarConstants.VERSION};
132            VersionConsumer consumer = new VersionConsumer( BAZAAR_VERSION_TAG );
133            Commandline cmd = BazaarUtils.buildCmd( workingDir, versionCmd );
134    
135            // Execute command
136            BazaarUtils.executeCmd( consumer, cmd );
137    
138            // Return result
139            return consumer;
140        }
141    
142        public static VersionConsumer getPythonVersion( File workingDir )
143            throws ScmException
144        {
145            String[] versionCmd = new String[]{PYTHON_VERSION};
146            VersionConsumer consumer = new VersionConsumer( PYTHON_VERSION_TAG );
147            Commandline cmd = buildPythonCmd( workingDir, versionCmd );
148    
149            // Execute command
150            BazaarUtils.executeCmd( consumer, cmd );
151    
152            // Return result
153            return consumer;
154        }
155    
156        private static Commandline buildPythonCmd( File workingDir, String[] cmdAndArgs )
157            throws ScmException
158        {
159            Commandline cmd = new Commandline();
160            cmd.setExecutable( PYTHON_EXEC );
161            cmd.setWorkingDirectory( workingDir.getAbsolutePath() );
162            cmd.addArguments( cmdAndArgs );
163    
164            if ( !workingDir.exists() )
165            {
166                boolean success = workingDir.mkdirs();
167                if ( !success )
168                {
169                    String msg = "Working directory did not exist" + " and it couldn't be created: " + workingDir;
170                    throw new ScmException( msg );
171                }
172            }
173            return cmd;
174        }
175    
176        /**
177         * Get version of the executable.
178         * Version is resolved to the last match of a defined regexp in the command output.
179         */
180        private static class VersionConsumer
181            extends BazaarConsumer
182        {
183    
184            private static final Pattern VERSION_PATTERN = Pattern.compile( "[\\d]+.?[\\d]*" );
185    
186            private final String versionTag;
187    
188            private String versionStr = "NA";
189    
190            private float version = -1;
191    
192            VersionConsumer( String aVersionTag )
193            {
194                super( new DefaultLog() );
195                this.versionTag = aVersionTag;
196            }
197    
198            public void doConsume( ScmFileStatus status, String line )
199            {
200                if ( line.startsWith( versionTag ) )
201                {
202                    versionStr = line.substring( versionTag.length() );
203                }
204            }
205    
206            String getVersion()
207            {
208                return versionStr;
209            }
210    
211            boolean isVersionOk( float min )
212            {
213    
214                Matcher matcher = VERSION_PATTERN.matcher( versionStr );
215                if ( matcher.find() )
216                {
217                    String subStr = versionStr.substring( matcher.start(), matcher.end() );
218                    try
219                    {
220                        version = Float.valueOf( subStr ).floatValue();
221                    }
222                    catch ( NumberFormatException e )
223                    {
224                        //Print diagnostics and continue (this is not a major error)
225                        if ( getLogger().isErrorEnabled() )
226                        {
227                            getLogger().error( "Regexp for version did not result in a number: " + subStr, e );
228                        }
229                    }
230                }
231    
232                return min <= version;
233            }
234        }
235    
236        private static class PythonConsumer
237            extends BazaarConsumer
238        {
239    
240            private String consumed = "";
241    
242            PythonConsumer()
243            {
244                super( new DefaultLog() );
245            }
246    
247            public void doConsume( ScmFileStatus status, String line )
248            {
249                consumed = line;
250            }
251    
252            String getConsumedAndClear()
253            {
254                String tmp = consumed;
255                consumed = "";
256                return tmp;
257            }
258        }
259    
260        private String getInstalledStr()
261        {
262            if ( isComplete() )
263            {
264                return "valid and complete.";
265            }
266            return ( isInstalled() ? "incomplete. " : "invalid. " ) + "Consult " + BAZAAR_INSTALL_URL;
267        }
268    
269        public String toString( File workingDir )
270        {
271            boolean bzrOk = bazaarVersion.isVersionOk( BAZAAR_REQ );
272            boolean pyOk = pythonVersion.isVersionOk( PYTHON_REQ );
273            return "\n  Your Bazaar installation seems to be " + getInstalledStr() + "\n    Python version: "
274                + pythonVersion.getVersion() + ( pyOk ? " (OK)" : " (May be INVALID)" ) + "\n    Bazaar version: "
275                + bazaarVersion.getVersion() + ( bzrOk ? " (OK)" : " (May be INVALID)" ) + "\n    Paramiko installed: "
276                + paramiko + " (For remote access eg. sftp) " + "\n    cCrypt installed: " + cCrypt
277                + " (For remote access eg. sftp) " + "\n    cElementTree installed: " + cElementTree + " (Not mandatory) "
278                + "\n";
279        }
280    }