KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > neu > ccs > jmk > Make


1 // $Id: Make.java,v 1.6 2002/02/11 12:09:24 ramsdell Exp $
2

3 // The make class
4

5 /*
6  * Copyright 1997 by John D. Ramsdell
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  */

22
23 package edu.neu.ccs.jmk;
24
25 import java.io.*;
26 import java.util.Hashtable JavaDoc;
27 import java.util.Vector JavaDoc;
28
29 /**
30  * The make class.
31  * After loading a makefile, instances of this class contain a set of
32  * rules and patterns that maintain the consistency of a set of files.
33  * The rules are invoked by supplying a list of targets to the make method.
34  * <p>
35  * The most important part of the make algorithm is coded in the make
36  * method of a rule.
37  * @see edu.neu.ccs.jmk.Rule#make()
38  * @version November 1997
39  * @author John D. Ramsdell
40  */

41 public class Make
42 implements Operator
43 {
44   private final static String JavaDoc defaultMakefileName = "makefile.jmk";
45
46   /**
47    * Returns the CVS value associated with the Name key in a printable format.
48    */

49   public static String JavaDoc getVersion() {
50     String JavaDoc cvsName = CvsInfo.getTag("$Name: JMK1_6 $");
51     if (cvsName == null || cvsName.length() == 0)
52       cvsName = "unknown";
53     else if (cvsName.startsWith("JMK"))
54       cvsName = cvsName.substring(3).trim();
55     return "Make for Java version " + cvsName;
56   }
57
58   /* * * * * *
59    * Make rules and patterns
60    * * * * * */

61
62   // The rule directory
63
// It maps a target as a string to a rule.
64
private Hashtable JavaDoc dir = new Hashtable JavaDoc();
65
66   Rule get(String JavaDoc name) {
67     Rule rule = (Rule)dir.get(name);
68     if (rule == null) {
69       rule = new Rule(this, name);
70       put(rule);
71     }
72     return rule;
73   }
74
75   private Rule put(Rule rule) {
76     return (Rule)dir.put(rule.getTarget(), rule);
77   }
78
79   // The pattern list
80
// The list is search by a rule which lacks commands.
81
private Vector JavaDoc patterns = new Vector JavaDoc();
82
83   /**
84    * This method checks the patterns in a reverse order to see if one
85    * is applicable to the given rule.
86    * @return true if a pattern matched
87    */

88   boolean tryPatterns(Rule rule) {
89     for (int i = patterns.size() - 1; i >= 0; i--) {
90       Pattern pattern = (Pattern)patterns.elementAt(i);
91       if (pattern.merge(rule))
92     return true;
93     }
94     return false;
95   }
96
97   /**
98    * Adds a pattern if either the target or a prerequisite contains
99    * the pattern wild card, the percent sign.
100    */

101   private boolean maybeAddPattern(String JavaDoc target, String JavaDoc[] prerequisites,
102                   Command[] commands, int lineNumber) {
103     if (isPattern(target, prerequisites)) {
104       patterns.addElement(new Pattern(target, prerequisites,
105                       commands, lineNumber));
106       return true;
107     }
108     else
109       return false;
110   }
111
112   /**
113    * Do rule parts suggest a pattern?
114    */

115   private static boolean isPattern(String JavaDoc target, String JavaDoc[] prerequisites) {
116     if (Matcher.isPattern(target))
117       return true;
118     for (int i = 0; i < prerequisites.length; i++)
119       if (Matcher.isPattern(prerequisites[i]))
120     return true;
121     return false;
122   }
123
124   /**
125    * Is the target a special target?
126    * The only special target currently supported is .PHONY.
127    */

128   private boolean maybeAddSpecialTarget(String JavaDoc target,
129                     String JavaDoc[] prerequisites) {
130     if (target.equals(".PHONY")) {
131       for (int i = 0; i < prerequisites.length; i++)
132     get(prerequisites[i]).setPhony(true);
133       return true;
134     }
135     else
136       return false;
137   }
138
139   // The Default Target for a make without arguments.
140
// It is set by addRule.
141
private String JavaDoc defaultTarget = "default";
142
143   private boolean hasSeenARule;
144
145   /**
146    * Add something that parses as a rule. The means one could be adding
147    * a pattern, a special target, or a makefile rule.
148    */

149   void addRule(String JavaDoc target, String JavaDoc[] prerequisites,
150            Command[] commands, int lineNumber) {
151     if (maybeAddPattern(target, prerequisites, commands, lineNumber))
152       return;
153     if (maybeAddSpecialTarget(target, prerequisites))
154       return;
155     if (!hasSeenARule) { // A real rule has been found
156
defaultTarget = target; // Set default target if not set
157
hasSeenARule = true;
158     }
159     Rule rule = get(target); // The get will create a rule if none exists
160
Rule[] dependencies = new Rule[prerequisites.length];
161     for (int i = 0; i < prerequisites.length; i++) // Replace names
162
dependencies[i] = get(prerequisites[i]); // by rules
163
rule.merge(dependencies, commands, lineNumber);
164   }
165
166   /* Various make system options are get and set here. */
167
168   private PrintWriter out = new PrintWriter(System.out, true);
169
170   /**
171    * Get the print writer used for the make system output.
172    * @return the make system print writer
173    */

174   public PrintWriter getOut() {
175     return out;
176   }
177
178   /**
179    * Set the print writer used for the make system output.
180    * @param out the new make system print writer
181    */

182   public void setOut(PrintWriter out) {
183     this.out = out;
184   }
185
186   private File file = new File(defaultMakefileName);
187
188   /**
189    * Get the file associated with this make object.
190    * The makefile loader uses this file when called with no arguments.
191    * @return the file
192    */

193   public File getFile() {
194     return file;
195   }
196
197   /**
198    * Store a file with the make object.
199    * @param file the file, if null, store the default file
200    */

201   public void setFile(File file) {
202     if (file == null)
203       this.file = new File(defaultMakefileName);
204     else
205       this.file = file;
206   }
207
208   private boolean verbose = false;
209
210   /**
211    * Is verbose mode enabled?
212    * In verbose mode, rules print more information when interpreted and
213    * assignments are displayed.
214    * @return true if verbose mode is enabled
215    */

216   public boolean isVerbose() {
217     return verbose;
218   }
219
220   /**
221    * Set verbose mode.
222    */

223   public void setVerbose(boolean verbose) {
224     this.verbose = verbose;
225   }
226
227   private boolean justPrinting = false;
228
229   /**
230    * Is just printing mode enabled?
231    * In just printing mode, commands are printed but not run.
232    * @return true if just printing mode is enabled
233    */

234   public boolean isJustPrinting() {
235     return justPrinting;
236   }
237
238   /**
239    * Set just printing mode.
240    */

241   public void setJustPrinting(boolean justPrinting) {
242     this.justPrinting = justPrinting;
243   }
244
245   /**
246    * Code for interrupting a make run.
247    * The variable is volatile so that multithreaded applications need not
248    * acquire a lock to use the shared variable. The variables which hold
249    * the state of the other modes need not be volatile as their changes
250    * can be delayed until the object's lock status changes.
251    *
252    * The variable is static for recursive makes.
253    */

254   private static volatile boolean interrupt = false;
255
256   /**
257    * Should a make in progress be interrupted?
258    * @return true if a make in progress should be interrupted
259    */

260   public boolean isInterruptEnabled() {
261     return interrupt;
262   }
263
264   /**
265    * Set the interrupt.
266    * When true, a make in progress will be interrupted as soon as
267    * it polls the interrupt status.
268    * The interrupt is disabled when a make run starts and ends.
269    */

270   public void setInterruptEnabled(boolean interrupt) {
271     this.interrupt = interrupt;
272   }
273
274   /**
275    * This is the main entry point for starting a make run.
276    * It invokes the make method of the appropriate rules, and then
277    * resets any rule used during the make run.
278    * @param targets a list of targets to be made
279    * @exception CommandFailedException if make failed
280    */

281   public synchronized boolean make(String JavaDoc[] targets)
282        throws CommandFailedException
283   {
284     interrupt = false;
285     if (targets.length == 0)
286       targets = new String JavaDoc[] { defaultTarget };
287     try {
288       boolean made = false;
289       for (int i = 0; i < targets.length; i++)
290     if (get(targets[i]).make())
291       made = true;
292       return made;
293     }
294     finally { // Make sure rules get reset even when
295
for (int i = 0; i < targets.length; i++) // there is a failure
296
get(targets[i]).reset();
297       interrupt = false;
298     }
299   }
300
301   /* * * * * *
302    * The makefile loader and its associated parsing methods
303    * are in the Loader class.
304    * * * * * */

305
306   /**
307    * This method loads a makefile. In addition to parsing the input,
308    * the value associated with each identifier through an assignment
309    * is substituted for the identifer, and all string functions are
310    * executed. The result is a make object that contains only rules
311    * and patterns.
312    *
313    * <p>The makefile loader expects input to conform to the grammar
314    * given below. Comments start with the sharp sign character
315    * (<tt>#</tt>) and continue to the end of the line. In the
316    * grammar, syntactic categories begin with an uppercase letter.
317    * The pattern Thing... indicates zero or more Things, and vertical
318    * bar indicates alternatives. In the grammar, The pattern
319    * Thing... indicates zero or more Things, Thing,,, indicates one or
320    * Things separated by commas, and vertical bar indicates
321    * alternatives.
322    *
323    * <blockquote>
324    * Makefile <tt>-&gt;</tt> Statement Statement...
325    * <br>
326    * Statement <tt>-&gt;</tt> Assignment | Rule | Inclusion | Conditional
327    * <br>
328    * Assignment <tt>-&gt;</tt> Identifier = List ;
329    * <br>
330    * List <tt>-&gt;</tt> Item...
331    * <br>
332    * Item <tt>-&gt;</tt> String | Identifier | (Item List,,,)
333    * | { Block }
334    * | <tt>if</tt> List <tt>then</tt> Block <tt>else</tt> Block <tt>end</tt>
335    * | <tt>function</tt> (Identifier,,,) Block <tt>end</tt>
336    * | <tt>do</tt> Identifier (Initializer,,,) Block <tt>end</tt>
337    * <br>
338    * Block <tt>-&gt;</tt> Assignment... List
339    * <br>
340    * Initializer <tt>-&gt;</tt> Identifier = List
341    * <br>
342    * Rule <tt>-&gt;</tt> Item List : List ; Commands
343    * <br>
344    * Commands <tt>-&gt;</tt> | { Command... }
345    * <br>
346    * Command <tt>-&gt;</tt> Ignore Operator List ;
347    * <br>
348    * Ignore <tt>-&gt;</tt> | -
349    * <br>
350    * Operator <tt>-&gt;</tt> <tt>exec</tt> | <tt>delete</tt> |
351    * <tt>delall</tt> | <tt>mkdir</tt> |
352    * <tt>mkdirs</tt> | <tt>copy</tt> | <tt>rename</tt>
353    * | <tt>create</tt> | <tt>note</tt> | <tt>forname</tt>
354    * <br>
355    * Inclusion <tt>-&gt;</tt> <tt>include</tt> List ;
356    * <br>
357    * Conditional <tt>-&gt;</tt> <tt>if</tt> List <tt>then</tt>
358    * Statement... Alternative <tt>end</tt>
359    * <br>
360    * Alternative <tt>-&gt;</tt> | <tt>else</tt> Statement...
361    * <br>
362    * String <tt>-&gt;</tt> <tt>"</tt> Characters... <tt>"</tt>
363    * <br>
364    * Identifier <tt>-&gt;</tt> JavaIdentifier | <tt>@</tt> | <tt>&lt;</tt> |
365    * <tt>?</tt> | <tt>%</tt>
366    * </blockquote>
367    *
368    * <p>The JavaIdentifiers that are reserved and not available as
369    * Identifiers are <tt>include</tt>, <tt>if</tt>, <tt>then</tt>,
370    * <tt>else</tt>, <tt>end</tt>, and <tt>function</tt>.
371    *
372    * @exception FileNotFoundException if an error occurs will opening the file
373    * @exception ParseError if an error occurs during loading */

374   public synchronized void load()
375        throws FileNotFoundException, ParseError
376   {
377     load(new FileReader(getFile()));
378   }
379
380   /*
381    * Loads a makefile from a reader.
382    * @param in the source of the makefile
383    * @exception ParseError if an error occurs during loading
384    */

385   public synchronized void load(Reader in)
386        throws ParseError
387   {
388     dir = new Hashtable JavaDoc();
389     patterns = new Vector JavaDoc();
390     hasSeenARule = false;
391     new Loader(this, in).load();
392   }
393
394   /* * * * * *
395    * Make as an operator
396    * * * * * */

397
398   public String JavaDoc getName() {
399     return "make";
400   }
401
402   /**
403    * Execute the operation by running make after parsing the arguments.
404    * @param args parameters to the operation
405    * @param out place to write messages
406    * @exception CommandFailedException if operation failed
407    */

408   public void exec(String JavaDoc[] args, PrintWriter out)
409        throws CommandFailedException
410   {
411     boolean useWindow = false;
412     boolean useSwing = false;
413     setOut(out);
414     // Process the switches
415
if (args.length > 0 && args[0].startsWith("-")) {
416       int i = 0;
417     switches: // Target for break when "--" found.
418
for (; i < args.length; i++) {
419     if (!args[i].startsWith("-")) // Not a switch
420
break; // leave for loop
421
if (args[i].length() != 2) {
422       out.println("Unrecognized switch: " + args[i]);
423       usage();
424       return;
425     }
426     switch (args[i].charAt(1)) {
427     case 'v':
428       out.println(getVersion());
429       return;
430     case 'f':
431       i++;
432       if (i < args.length)
433         setFile(new File(args[i]));
434       else {
435         out.println("No makefile for -f option");
436         usage();
437         return;
438       }
439       break;
440     case 'd':
441       setVerbose(true);
442       break;
443     case 'n':
444       setJustPrinting(true);
445       break;
446     case 'w':
447       useWindow = true;
448       break;
449     case 's':
450       useSwing = true;
451       break;
452     case '-':
453       i++;
454       break switches;
455     default:
456       out.println("Unrecognized switch: " + args[i]);
457       usage();
458       return;
459     }
460       }
461       Vector JavaDoc v = new Vector JavaDoc(); // Copy non-switch args into v
462
for (; i < args.length; i++)
463     v.addElement(args[i]);
464       args = new String JavaDoc[v.size()];
465       v.copyInto(args); // then back to args
466
}
467     readAndMake(args, useWindow, useSwing);
468   }
469
470   private void usage()
471        throws CommandFailedException
472   {
473     out.println("Usage: java " + Make.class.getName() +
474         " [-f filename] [-d] [-n] [-v] [-w] [-s] filename ...");
475     out.println(" -f use filename for makefile" +
476         " (default is " + defaultMakefileName + ")");
477     out.println(" -d print debugging information");
478     out.println(" -n print but don't run commands");
479     out.println(" -w use AWT window for make output");
480     out.println(" -s use Swing window for make output");
481     out.println(" -v print the version number");
482     fail();
483   }
484
485   private void fail()
486        throws CommandFailedException
487   {
488     throw new CommandFailedException("Make failed");
489   }
490
491   /**
492    * Invoke make after command line processing has completed.
493    * @param targets the list of targets for a make run
494    * @param useWindow request make window when true
495    * @param useSwing request make Swing window when true
496    */

497   public void readAndMake(String JavaDoc[] targets,
498               boolean useWindow,
499               boolean useSwing)
500        throws CommandFailedException
501   {
502     if (useWindow) {
503       edu.neu.ccs.jmk.awt.MakeWindow.createMakeWindow(this, targets);
504     }
505     else if (useSwing) {
506       try { // Try using swing if possible
507
edu.neu.ccs.jmk.swing.JMakeWindow.createMakeWindow(this, targets);
508       }
509       catch (LinkageError JavaDoc le) {
510     edu.neu.ccs.jmk.awt.MakeWindow.createMakeWindow(this, targets);
511       }
512     }
513     else {
514       File file = getFile();
515       Reader in; // Open input to makefile
516

517       if (file.getPath().equals("-"))
518     in = new InputStreamReader(System.in);
519       else {
520     try {
521       in = new FileReader(file);
522     }
523     catch (FileNotFoundException ex) {
524       out.println("Cannot find makefile " + file.getPath());
525       fail();
526       return;
527     }
528       }
529
530       try {
531     load(in); // Load makefile
532
}
533       catch (ParseError pe) {
534     out.println(pe.getFileName() + ":" + pe.getLineNumber()
535             + ": Parse error: " + pe.getMessage());
536     fail();
537     return;
538       }
539
540       try {
541     if (!make(targets)) // Run make
542
out.println("Nothing to make");
543       }
544       catch (CommandFailedException ex) {
545     out.println("Command failed: " + ex.getMessage());
546     fail();
547     return;
548       }
549     }
550   }
551
552   /* * * * * *
553    * The main class method
554    * * * * * */

555
556   /**
557    * The main entry point for running Make.
558    * Invoke Make with "-h" to see the program's usage message.
559    */

560   public static void main(String JavaDoc[] args) {
561     PrintWriter out = new PrintWriter(System.out, true);
562     Make make = new Make();
563     try {
564       make.exec(args, out);
565     }
566     catch (CommandFailedException ex) {
567       System.exit(1);
568     }
569     catch (Throwable JavaDoc t) {
570       System.err.println("Internal error: " + t.getMessage());
571       t.printStackTrace();
572       System.exit(1);
573     }
574   }
575 }
576
Popular Tags