001package 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
022import org.apache.maven.scm.ScmException;
023import org.apache.maven.scm.ScmFileStatus;
024import org.apache.maven.scm.log.DefaultLog;
025import org.apache.maven.scm.provider.bazaar.command.BazaarConstants;
026import org.apache.maven.scm.provider.bazaar.command.BazaarConsumer;
027import org.codehaus.plexus.util.cli.Commandline;
028
029import java.io.File;
030import java.util.regex.Matcher;
031import java.util.regex.Pattern;
032
033/**
034 * Check bazaar installation.
035 *
036 * @author <a href="mailto:torbjorn@smorgrav.org">Torbjorn Eikli Smorgrav</a>
037 *
038 */
039public 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}