]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
lilypond-book: Set include path for --output option (issue 2423).
[lilypond.git] / scripts / lilypond-book.py
1 #!@TARGET_PYTHON@
2 # -*- coding: utf-8 -*-
3
4 # This file is part of LilyPond, the GNU music typesetter.
5 #
6 # LilyPond is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # LilyPond is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18
19 '''
20 Example usage:
21
22 test:
23   lilypond-book --filter="tr '[a-z]' '[A-Z]'" BOOK
24
25 convert-ly on book:
26   lilypond-book --filter="convert-ly --no-version --from=1.6.11 -" BOOK
27
28 classic lilypond-book:
29   lilypond-book --process="lilypond" BOOK.tely
30
31 TODO:
32
33   *  ly-options: intertext?
34   *  --line-width?
35   *  eps in latex / eps by lilypond -b ps?
36   *  check latex parameters, twocolumn, multicolumn?
37   *  use --png --ps --pdf for making images?
38
39   *  Converting from lilypond-book source, substitute:
40    @mbinclude foo.itely -> @include foo.itely
41    \mbinput -> \input
42
43 '''
44
45
46 # TODO: Better solve the global_options copying to the snippets...
47
48 import glob
49 import os
50 import re
51 import stat
52 import sys
53 import tempfile
54 import imp
55 from optparse import OptionGroup
56
57
58 """
59 @relocate-preamble@
60 """
61
62 import lilylib as ly
63 import fontextract
64 import langdefs
65 global _;_=ly._
66
67 import book_base as BookBase
68 import book_snippets as BookSnippet
69 import book_html
70 import book_docbook
71 import book_texinfo
72 import book_latex
73
74 ly.require_python_version ()
75
76 original_dir = os.getcwd ()
77 backend = 'ps'
78
79 help_summary = (
80 _ ("Process LilyPond snippets in hybrid HTML, LaTeX, texinfo or DocBook document.")
81 + '\n\n'
82 + _ ("Examples:")
83 + '''
84  $ lilypond-book --filter="tr '[a-z]' '[A-Z]'" %(BOOK)s
85  $ lilypond-book -F "convert-ly --no-version --from=2.0.0 -" %(BOOK)s
86  $ lilypond-book --process='lilypond -I include' %(BOOK)s
87 ''' % {'BOOK': _ ("BOOK")})
88
89 authors = ('Jan Nieuwenhuizen <janneke@gnu.org>',
90            'Han-Wen Nienhuys <hanwen@xs4all.nl>')
91
92 ################################################################
93 def exit (i):
94     if ly.is_verbose ():
95         raise Exception (_ ('Exiting (%d)...') % i)
96     else:
97         sys.exit (i)
98
99 progress = ly.progress
100 warning = ly.warning
101 error = ly.error
102
103 def identify ():
104     progress('%s (GNU LilyPond) %s' % (ly.program_name, ly.program_version))
105
106 def warranty ():
107     identify ()
108     ly.encoded_write (sys.stdout, '''
109 %s
110
111   %s
112
113 %s
114 %s
115 ''' % ( _ ('Copyright (c) %s by') % '2001--2012',
116         '\n  '.join (authors),
117         _ ("Distributed under terms of the GNU General Public License."),
118         _ ("It comes with NO WARRANTY.")))
119
120
121 def get_option_parser ():
122     p = ly.get_option_parser (usage=_ ("%s [OPTION]... FILE") % 'lilypond-book',
123                               description=help_summary,
124                               conflict_handler="resolve",
125                               add_help_option=False)
126
127     p.add_option ('-F', '--filter', metavar=_ ("FILTER"),
128                   action="store",
129                   dest="filter_cmd",
130                   help=_ ("pipe snippets through FILTER [default: `convert-ly -n -']"),
131                   default=None)
132
133     p.add_option ('-f', '--format',
134                   help=_ ("use output format FORMAT (texi [default], texi-html, latex, html, docbook)"),
135                   metavar=_ ("FORMAT"),
136                   action='store')
137
138     p.add_option("-h", "--help",
139                  action="help",
140                  help=_ ("show this help and exit"))
141
142     p.add_option ("-I", '--include', help=_ ("add DIR to include path"),
143                   metavar=_ ("DIR"),
144                   action='append', dest='include_path',
145                   default=[])
146
147     p.add_option ('--info-images-dir',
148                   help=_ ("format Texinfo output so that Info will "
149                           "look for images of music in DIR"),
150                   metavar=_ ("DIR"),
151                   action='store', dest='info_images_dir',
152                   default='')
153
154     p.add_option ('--left-padding',
155                   metavar=_ ("PAD"),
156                   dest="padding_mm",
157                   help=_ ("pad left side of music to align music inspite of uneven bar numbers (in mm)"),
158                   type="float",
159                   default=3.0)
160
161     p.add_option ('--lily-loglevel',
162                   help=_ ("Print lilypond log messages according to LOGLEVEL"),
163                   metavar=_ ("LOGLEVEL"),
164                   action='store', dest='lily_loglevel',
165                   default=os.environ.get ("LILYPOND_LOGLEVEL", None))
166
167     p.add_option ('--lily-output-dir',
168                   help=_ ("write lily-XXX files to DIR, link into --output dir"),
169                   metavar=_ ("DIR"),
170                   action='store', dest='lily_output_dir',
171                   default=None)
172
173     p.add_option ('--load-custom-package', help=_ ("Load the additional python PACKAGE (containing e.g. a custom output format)"),
174                   metavar=_ ("PACKAGE"),
175                   action='append', dest='custom_packages',
176                   default=[])
177
178     p.add_option ("-l", "--loglevel",
179                   help=_ ("Print log messages according to LOGLEVEL "
180                           "(NONE, ERROR, WARNING, PROGRESS (default), DEBUG)"),
181                   metavar=_ ("LOGLEVEL"),
182                   action='callback',
183                   callback=ly.handle_loglevel_option,
184                   type='string')
185
186     p.add_option ("-o", '--output', help=_ ("write output to DIR"),
187                   metavar=_ ("DIR"),
188                   action='store', dest='output_dir',
189                   default='')
190
191     p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
192                   help = _ ("process ly_files using COMMAND FILE..."),
193                   action='store',
194                   dest='process_cmd', default='')
195
196     p.add_option ('--redirect-lilypond-output',
197                   help = _ ("Redirect the lilypond output"),
198                   action='store_true',
199                   dest='redirect_output', default=False)
200
201     p.add_option ('-s', '--safe', help=_ ("Compile snippets in safe mode"),
202                   action="store_true",
203                   default=False,
204                   dest="safe_mode")
205
206     p.add_option ('--skip-lily-check',
207                   help=_ ("do not fail if no lilypond output is found"),
208                   metavar=_ ("DIR"),
209                   action='store_true', dest='skip_lilypond_run',
210                   default=False)
211
212     p.add_option ('--skip-png-check',
213                   help=_ ("do not fail if no PNG images are found for EPS files"),
214                   metavar=_ ("DIR"),
215                   action='store_true', dest='skip_png_check',
216                   default=False)
217
218     p.add_option ('--use-source-file-names',
219                   help=_ ("write snippet output files with the same base name as their source file"),
220                   action='store_true', dest='use_source_file_names',
221                   default=False)
222
223     p.add_option ('-V', '--verbose', help=_ ("be verbose"),
224                   action="callback",
225                   callback=ly.handle_loglevel_option,
226                   callback_args=("DEBUG",))
227
228     p.version = "@TOPLEVEL_VERSION@"
229     p.add_option("--version",
230                  action="version",
231                  help=_ ("show version number and exit"))
232
233     p.add_option ('-w', '--warranty',
234                   help=_ ("show warranty and copyright"),
235                   action='store_true')
236
237     group = OptionGroup (p, "Options only for the latex and texinfo backends")
238     group.add_option ('--latex-program',
239               help=_ ("run executable PROG instead of latex, or in\n\
240 case --pdf option is set instead of pdflatex"),
241               metavar=_ ("PROG"),
242               action='store', dest='latex_program',
243               default='latex')
244     group.add_option ('--texinfo-program',
245               help=_ ("run executable PROG instead of texi2pdf"),
246               metavar=_ ("PROG"),
247               action='store', dest='texinfo_program',
248               default='texi2pdf')
249     group.add_option ('--pdf',
250               action="store_true",
251               dest="create_pdf",
252               help=_ ("create PDF files for use with PDFTeX"),
253               default=False)
254     p.add_option_group (group)
255
256     p.add_option_group ('',
257                         description=(
258         _ ("Report bugs via %s")
259         % ' http://post.gmane.org/post.php'
260         '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
261
262
263     for formatter in BookBase.all_formats:
264       formatter.add_options (p)
265
266     return p
267
268 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
269
270 # If we are called with full path, try to use lilypond binary
271 # installed in the same path; this is needed in GUB binaries, where
272 # @bindir is always different from the installed binary path.
273 if 'bindir' in globals () and bindir:
274     lilypond_binary = os.path.join (bindir, 'lilypond')
275
276 # Only use installed binary when we are installed too.
277 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
278     lilypond_binary = 'lilypond'
279
280 global_options = None
281
282
283
284
285 def find_linestarts (s):
286     nls = [0]
287     start = 0
288     end = len (s)
289     while 1:
290         i = s.find ('\n', start)
291         if i < 0:
292             break
293
294         i = i + 1
295         nls.append (i)
296         start = i
297
298     nls.append (len (s))
299     return nls
300
301 def find_toplevel_snippets (input_string, formatter):
302     res = {}
303     types = formatter.supported_snippet_types ()
304     for t in types:
305         res[t] = re.compile (formatter.snippet_regexp (t))
306
307     snippets = []
308     index = 0
309     found = dict ([(t, None) for t in types])
310
311     line_starts = find_linestarts (input_string)
312     line_start_idx = 0
313     # We want to search for multiple regexes, without searching
314     # the string multiple times for one regex.
315     # Hence, we use earlier results to limit the string portion
316     # where we search.
317     # Since every part of the string is traversed at most once for
318     # every type of snippet, this is linear.
319     while 1:
320         first = None
321         endex = 1 << 30
322         for type in types:
323             if not found[type] or found[type][0] < index:
324                 found[type] = None
325
326                 m = res[type].search (input_string[index:endex])
327                 if not m:
328                     continue
329
330                 klass = global_options.formatter.snippet_class (type)
331
332                 start = index + m.start ('match')
333                 line_number = line_start_idx
334                 while (line_starts[line_number] < start):
335                     line_number += 1
336
337                 line_number += 1
338                 snip = klass (type, m, formatter, line_number, global_options)
339
340                 found[type] = (start, snip)
341
342             if (found[type]
343                 and (not first
344                      or found[type][0] < found[first][0])):
345                 first = type
346
347                 # FIXME.
348
349                 # Limiting the search space is a cute
350                 # idea, but this *requires* to search
351                 # for possible containing blocks
352                 # first, at least as long as we do not
353                 # search for the start of blocks, but
354                 # always/directly for the entire
355                 # @block ... @end block.
356
357                 endex = found[first][0]
358
359         if not first:
360             snippets.append (BookSnippet.Substring (input_string, index, len (input_string), line_start_idx))
361             break
362
363         while (start > line_starts[line_start_idx+1]):
364             line_start_idx += 1
365
366         (start, snip) = found[first]
367         snippets.append (BookSnippet.Substring (input_string, index, start, line_start_idx + 1))
368         snippets.append (snip)
369         found[first] = None
370         index = start + len (snip.match.group ('match'))
371
372     return snippets
373
374 def system_in_directory (cmd, directory, logfile):
375     """Execute a command in a different directory.
376
377     Because of win32 compatibility, we can't simply use subprocess.
378     """
379
380     current = os.getcwd()
381     os.chdir (directory)
382     """NB - ignore_error is deliberately set to the same value
383     as redirect_output - this is not a typo."""
384     retval = ly.system(cmd,
385               be_verbose=ly.is_verbose (),
386               redirect_output=global_options.redirect_output,
387               log_file=logfile,
388               progress_p=1,
389               ignore_error=global_options.redirect_output)
390     if retval != 0:
391         print ("Error trapped by lilypond-book")
392         print ("\nPlease see " + logfile + ".log\n")
393         sys.exit(1)
394
395     os.chdir (current)
396
397
398 def process_snippets (cmd, snippets,
399                       formatter, lily_output_dir):
400     """Run cmd on all of the .ly files from snippets."""
401
402     if not snippets:
403         return
404
405     cmd = formatter.adjust_snippet_command (cmd)
406
407     checksum = snippet_list_checksum (snippets)
408     contents = '\n'.join (['snippet-map-%d.ly' % checksum]
409                           + list (set ([snip.basename() + '.ly' for snip in snippets])))
410     name = os.path.join (lily_output_dir,
411                          'snippet-names-%d.ly' % checksum)
412     logfile = name.replace('.ly', '')
413     file (name, 'wb').write (contents)
414
415     system_in_directory (' '.join ([cmd, ly.mkarg (name)]),
416                          lily_output_dir,
417                          logfile)
418
419 def snippet_list_checksum (snippets):
420     return hash (' '.join([l.basename() for l in snippets]))
421
422 def write_file_map (lys, name):
423     snippet_map = file (os.path.join (
424         global_options.lily_output_dir,
425         'snippet-map-%d.ly' % snippet_list_checksum (lys)), 'w')
426
427     snippet_map.write ("""
428 #(define version-seen #t)
429 #(define output-empty-score-list #f)
430 #(ly:add-file-name-alist '(%s
431     ))\n
432 """ % '\n'.join(['("%s.ly" . "%s")\n' % (ly.basename ().replace('\\','/'), name)
433                  for ly in lys]))
434
435 def split_output_files(directory):
436     """Returns directory entries in DIRECTORY/XX/ , where XX are hex digits.
437
438     Return value is a set of strings.
439     """
440     files = []
441     for subdir in glob.glob (os.path.join (directory, '[a-f0-9][a-f0-9]')):
442         base_subdir = os.path.split (subdir)[1]
443         sub_files = [os.path.join (base_subdir, name)
444                      for name in os.listdir (subdir)]
445         files += sub_files
446     return set (files)
447
448 def do_process_cmd (chunks, input_name, options):
449     snippets = [c for c in chunks if isinstance (c, BookSnippet.LilypondSnippet)]
450
451     output_files = split_output_files (options.lily_output_dir)
452     outdated = [c for c in snippets if c.is_outdated (options.lily_output_dir, output_files)]
453
454     write_file_map (outdated, input_name)
455     progress (_ ("Writing snippets..."))
456     for snippet in outdated:
457         snippet.write_ly()
458
459     if outdated:
460         progress (_ ("Processing..."))
461         process_snippets (options.process_cmd, outdated,
462                           options.formatter, options.lily_output_dir)
463
464     else:
465         progress (_ ("All snippets are up to date..."))
466
467     if options.lily_output_dir != options.output_dir:
468         output_files = split_output_files (options.lily_output_dir)
469         for snippet in snippets:
470             snippet.link_all_output_files (options.lily_output_dir,
471                                            output_files,
472                                            options.output_dir)
473
474
475 ###
476 # Format guessing data
477
478 def guess_format (input_filename):
479     format = None
480     e = os.path.splitext (input_filename)[1]
481     for formatter in BookBase.all_formats:
482       if formatter.can_handle_extension (e):
483         return formatter
484     error (_ ("cannot determine format for: %s" % input_filename))
485     exit (1)
486
487 def write_if_updated (file_name, lines):
488     try:
489         f = file (file_name)
490         oldstr = f.read ()
491         new_str = ''.join (lines)
492         if oldstr == new_str:
493             progress (_ ("%s is up to date.") % file_name)
494
495             # this prevents make from always rerunning lilypond-book:
496             # output file must be touched in order to be up to date
497             os.utime (file_name, None)
498             return
499     except:
500         pass
501
502     output_dir = os.path.dirname (file_name)
503     if not os.path.exists (output_dir):
504         os.makedirs (output_dir)
505
506     progress (_ ("Writing `%s'...") % file_name)
507     file (file_name, 'w').writelines (lines)
508
509
510 def note_input_file (name, inputs=[]):
511     ## hack: inputs is mutable!
512     inputs.append (name)
513     return inputs
514
515 def samefile (f1, f2):
516     try:
517         return os.path.samefile (f1, f2)
518     except AttributeError:                # Windoze
519         f1 = re.sub ("//*", "/", f1)
520         f2 = re.sub ("//*", "/", f2)
521         return f1 == f2
522
523 def do_file (input_filename, included=False):
524     # Ugh.
525     input_absname = input_filename
526     if not input_filename or input_filename == '-':
527         in_handle = sys.stdin
528         input_fullname = '<stdin>'
529     else:
530         if os.path.exists (input_filename):
531             input_fullname = input_filename
532         else:
533             input_fullname = global_options.formatter.input_fullname (input_filename)
534         # Normalize path to absolute path, since we will change cwd to the output dir!
535         # Otherwise, "lilypond-book -o out test.tex" will complain that it is
536         # overwriting the input file (which it is actually not), since the
537         # input filename is relative to the CWD...
538         input_absname = os.path.abspath (input_fullname)
539
540         note_input_file (input_fullname)
541         in_handle = file (input_fullname)
542
543     if input_filename == '-':
544         input_base = 'stdin'
545     elif included:
546         input_base = os.path.splitext (input_filename)[0]
547     else:
548         input_base = os.path.basename (
549             os.path.splitext (input_filename)[0])
550
551     # don't complain when global_options.output_dir is existing
552     if not global_options.output_dir:
553         global_options.output_dir = os.getcwd()
554     else:
555         global_options.output_dir = os.path.abspath(global_options.output_dir)
556
557         if not os.path.isdir (global_options.output_dir):
558             os.mkdir (global_options.output_dir, 0777)
559         os.chdir (global_options.output_dir)
560
561     output_filename = os.path.join(global_options.output_dir,
562                                    input_base + global_options.formatter.default_extension)
563     if (os.path.exists (input_filename)
564         and os.path.exists (output_filename)
565         and samefile (output_filename, input_absname)):
566      error (
567      _ ("Output would overwrite input file; use --output."))
568      exit (2)
569
570     try:
571         progress (_ ("Reading %s...") % input_fullname)
572         source = in_handle.read ()
573
574         if not included:
575             global_options.formatter.init_default_snippet_options (source)
576
577
578         progress (_ ("Dissecting..."))
579         chunks = find_toplevel_snippets (source, global_options.formatter)
580
581         # Let the formatter modify the chunks before further processing
582         chunks = global_options.formatter.process_chunks (chunks)
583
584         if global_options.filter_cmd:
585             write_if_updated (output_filename,
586                      [c.filter_text () for c in chunks])
587         elif global_options.process_cmd:
588             do_process_cmd (chunks, input_fullname, global_options)
589             progress (_ ("Compiling %s...") % output_filename)
590             write_if_updated (output_filename,
591                      [s.replacement_text ()
592                      for s in chunks])
593
594         def process_include (snippet):
595             os.chdir (original_dir)
596             name = snippet.substring ('filename')
597             progress (_ ("Processing include: %s") % name)
598             return do_file (name, included=True)
599
600         include_chunks = map (process_include,
601                               filter (lambda x: isinstance (x, BookSnippet.IncludeSnippet),
602                                       chunks))
603
604         return chunks + reduce (lambda x, y: x + y, include_chunks, [])
605
606     except BookSnippet.CompileError:
607         os.chdir (original_dir)
608         progress (_ ("Removing `%s'") % output_filename)
609         raise BookSnippet.CompileError
610
611 def inverse_relpath (path, relpath):
612     """Given two paths, the second relative to the first,
613     return the first path relative to the second."""
614     if os.path.isabs (relpath):
615         return os.path.abspath (path)
616     relparts = ['']
617     parts = os.path.normpath (path).split (os.path.sep)
618     for part in os.path.normpath (relpath).split (os.path.sep):
619         if part == '..':
620             relparts.append (parts[-1])
621             parts.pop ()
622         else:
623             relparts.append ('..')
624             parts.append (part)
625     return os.path.sep.join (relparts[::-1])
626
627 def do_options ():
628     global global_options
629
630     opt_parser = get_option_parser()
631     (global_options, args) = opt_parser.parse_args ()
632
633     global_options.information = {'program_version': ly.program_version, 'program_name': ly.program_name }
634     global_options.original_dir = original_dir
635
636     if global_options.output_dir:
637         global_options.output_dir = os.path.expanduser (global_options.output_dir)
638         global_options.include_path.insert (0, inverse_relpath (original_dir, global_options.output_dir))
639
640     # Load the python packages (containing e.g. custom formatter classes)
641     # passed on the command line
642     nr = 0
643     for i in global_options.custom_packages:
644         nr += 1
645         print imp.load_source ("book_custom_package%s" % nr, i)
646
647
648     if global_options.warranty:
649         warranty ()
650         exit (0)
651     if not args or len (args) > 1:
652         opt_parser.print_help ()
653         exit (2)
654
655     return args
656
657 def main ():
658     # FIXME: 85 lines of `main' macramee??
659     if (os.environ.has_key ("LILYPOND_BOOK_LOGLEVEL")):
660         ly.set_loglevel (os.environ["LILYPOND_BOOK_LOGLEVEL"])
661     files = do_options ()
662
663     basename = os.path.splitext (files[0])[0]
664     basename = os.path.split (basename)[1]
665
666     if global_options.format:
667       # Retrieve the formatter for the given format
668       for formatter in BookBase.all_formats:
669         if formatter.can_handle_format (global_options.format):
670           global_options.formatter = formatter
671     else:
672         global_options.formatter = guess_format (files[0])
673         global_options.format = global_options.formatter.format
674
675     # make the global options available to the formatters:
676     global_options.formatter.global_options = global_options
677     formats = global_options.formatter.image_formats
678
679     if global_options.process_cmd == '':
680         global_options.process_cmd = (lilypond_binary
681                                       + ' --formats=%s -dbackend=eps ' % formats)
682
683     if global_options.process_cmd:
684         includes = global_options.include_path
685         if global_options.lily_output_dir:
686             # This must be first, so lilypond prefers to read .ly
687             # files in the other lybookdb dir.
688             includes = [global_options.lily_output_dir] + includes
689         global_options.process_cmd += ' '.join ([' -I %s' % ly.mkarg (p)
690                                                  for p in includes])
691
692     global_options.formatter.process_options (global_options)
693
694     if global_options.lily_loglevel:
695         ly.debug_output (_ ("Setting LilyPond's loglevel to %s") % global_options.lily_loglevel, True)
696         global_options.process_cmd += " --loglevel=%s" % global_options.lily_loglevel
697     elif ly.is_verbose ():
698         if os.environ.get ("LILYPOND_LOGLEVEL", None):
699             ly.debug_output (_ ("Setting LilyPond's loglevel to %s (from environment variable LILYPOND_LOGLEVEL)") % os.environ.get ("LILYPOND_LOGLEVEL", None), True)
700             global_options.process_cmd += " --loglevel=%s" % os.environ.get ("LILYPOND_LOGLEVEL", None)
701         else:
702             ly.debug_output (_ ("Setting LilyPond's output to --verbose, implied by lilypond-book's setting"), True)
703             global_options.process_cmd += " --verbose"
704
705     if global_options.padding_mm:
706         global_options.process_cmd += " -deps-box-padding=%f " % global_options.padding_mm
707
708     global_options.process_cmd += " -dread-file-list -dno-strip-output-dir"
709
710     if global_options.lily_output_dir:
711         global_options.lily_output_dir = os.path.abspath(global_options.lily_output_dir)
712         if not os.path.isdir (global_options.lily_output_dir):
713             os.makedirs (global_options.lily_output_dir)
714     else:
715         global_options.lily_output_dir = os.path.abspath(global_options.output_dir)
716
717     relative_output_dir = global_options.output_dir
718
719     identify ()
720     try:
721         chunks = do_file (files[0])
722     except BookSnippet.CompileError:
723         exit (1)
724
725     inputs = note_input_file ('')
726     inputs.pop ()
727
728     base_file_name = os.path.splitext (os.path.basename (files[0]))[0]
729     dep_file = os.path.join (global_options.output_dir, base_file_name + '.dep')
730     final_output_file = os.path.join (relative_output_dir,
731                      base_file_name + global_options.formatter.default_extension)
732
733     os.chdir (original_dir)
734     file (dep_file, 'w').write ('%s: %s\n'
735                                 % (final_output_file, ' '.join (inputs)))
736
737 if __name__ == '__main__':
738     main ()