[Distutils] another build_scripts patch

Greg Ward gward@python.net
Wed, 24 May 2000 22:17:51 -0400


On 24 May 2000, Bastian Kleineidam said:
> Next try with the build_scripts patch. Again the changes:

OK, I've checked it in.  A few minor comments (I'm checking these
changes in too, so howl if you don't like 'em and "cvs up" if you do.)

> - install_misc class is commented out because its not needed any more

You forgot to include this in the patch, but that's OK because that's
not what I meant!  I just wanted a comment next to "install_misc" to
remind us that it's not (currently) used.  I'll do it myself.

> data_files = ['doc/info.txt', # copy this file in install_scripts dir
>   ('testdata', ['a.dat', 'b.dat']), # copy these files in
>                                     # install_scripts/testdata
>   ('/etc', ['packagerc']),    # copy this in /etc. When --root is
>                               # given, copy this in rootdir/etc
> ]

Perfect!  (Assuming it works.  ;-)

> --- distutils.orig/distutils/command/__init__.py	Sat May 13 03:48:15 2000
> +++ distutils.patched/distutils/command/__init__.py	Wed May 24 10:11:54 2000
[...]
> @@ -99,5 +104,8 @@
>          # into the build tree
>          if self.distribution.has_ext_modules():
>              self.run_peer ('build_ext')
> +
> +        if self.distribution.scripts:
> +            self.run_peer ('build_scripts')

This should use the 'has_scripts()' method of Distribution -- fixed.

> +# check if Python is called on the first line with this expression
> +first_line_re = re.compile(r"^#!.+python(\s-\w+)*")

I think we can get away with a looser regex: I've changed this to
r'^#!.*python(\s+.*)?'.  Reasons:
  * I seem to recall having seen "#!python" notation somewhere -- it's
    a reasonable way to say, "Hey, <installation tool of choice>!  Fix
    this up for me at install time!"
  * Why assume/impose a particular syntax on Python's command line?
    let's just preserve any old args on the #! line.

> +    def _copy_files(self):
> +        """Copy all the scripts to the build dir"""
[...]


> +    def _adjust_files(self):
> +        """If the first line begins with #! and ends with python
> +	   replace it with the current python interpreter"""
[...]

This strikes me as a rather wasteful implementation: in the worst case,
we copy every file twice.  I've merged them into the following method;
it works for me, please look it over and try it out (from CVS -- a few
other tweaks to build_scripts.py were needed to make this work):

    def copy_scripts (self):
        """Copy each script listed in 'self.scripts'; if it's marked as a
        Python script in the Unix way (first line matches 'first_line_re',
        ie. starts with "\#!" and contains "python"), then adjust the first
        line to refer to the current Python intepreter as we copy.
        """
        outfiles = []
        self.mkpath(self.build_dir)
        for script in self.scripts:
            adjust = 0
            outfile = os.path.join(self.build_dir, script)

            if not self.force and not newer(script, outfile):
                self.announce("not copying %s (output up-to-date)" % script)
                continue

            # Always open the file, but ignore failures in dry-run mode --
            # that way, we'll get accurate feedback if we can read the
            # script.
            try:
                f = open(script, "r")
            except IOError:
                if not self.dry_run:
                    raise
                f = None
            else:
                first_line = f.readline()
                if not first_line:
                    self.warn("%s is an empty file (skipping)" % script)
                    continue

                match = first_line_re.match(first_line)
                if match:
                    adjust = 1
                    post_interp = match.group(1)

            if adjust:
                self.announce("copying and adjusting %s -> %s" %
                              (script, self.build_dir))
                if not self.dry_run:
                    outf = open(outfile, "w")
                    outf.write("#!%s%s\n" % 
                               (os.path.normpath(sys.executable), post_interp))
                    outf.writelines(f.readlines())
                    outf.close()
                if f:
                    f.close()
            else:
                f.close()
                self.copy_file(script, outfile)

    # copy_scripts ()

> --- distutils.orig/distutils/command/install_data.py	Sat May 13 05:09:50 2000
> +++ distutils.patched/distutils/command/install_data.py	Wed May 24 10:41:51 2000
[...]
>      def run (self):
> -        self._copy_files(self.distribution.data_files)
> +        self.mkpath(self.install_dir)
> +        for f in self.data_files:
> +            if type(f) == StringType:
> +                # its a simple file, so copy it
> +                self.copy_file(f, self.install_dir)
> +            else:
> +                # its a tuple with path to install to and a list of files
> +                dir = f[0]
> +                if not os.path.isabs(dir):
> +                    dir = os.path.join(self.install_dir, dir)
> +                elif self.root:
> +                    dir = os.path.join(self.root, dir[1:])
> +                self.mkpath(dir)
> +                for data in f[1]:
> +                    self.copy_file(data, dir)

This looks unportable, in particular the 'dir[1:]' business.  Should use
the 'distutils.util.change_root()' function, which handles non-Unix
platforms by blowing up with "I don't know how to do this!" error.
(Which is really a function of me not having sat down and carefully
thought through what it means to "change the root" of an MS-DOS
filename: the presence of drive letters complicates matters.)

        Greg
-- 
Greg Ward - just another /P(erl|ython)/ hacker          gward@python.net
http://starship.python.net/~gward/
I'm a GENIUS!  I want to dispute sentence structure with SUSAN SONTAG!!