2010年2月20日土曜日

distutils.buildを理解する。

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
大筋でどのようにできているかというと、setupは、distributionを表現するdistがあって、実際の生成プロセスを記述するcommandとsubcommandからなっている。次に関係ないところをすっ飛ばして引用してみた。
  • core.py
    def setup (**attrs):
                dist.run_commands()
    
  • dist.py
    def run_commands (self):
            for cmd in self.commands:
                self.run_command(cmd)
    
  • cmd.py
    def run_command (self, command):
            """Run some other command: uses the 'run_command()' method of
            Distribution, which creates and finalizes the command object if
            necessary and then invokes its 'run()' method.
            """
            self.distribution.run_command(command)
    
  • dist.py
    def run_command (self, command):
            """Do whatever it takes to run a command (including nothing at all,
            if the command has already been run).  Specifically: if we have
            already created and run the command named by 'command', return
            silently without doing anything.  If the command named by 'command'
            doesn't even have a command object yet, create one.  Then invoke
            'run()' on that command object (or an existing one).
            """
            # Already been here, done that? then return silently.
            if self.have_run.get(command):
                return
    
            log.info("running %s", command)
            cmd_obj = self.get_command_obj(command)
            cmd_obj.ensure_finalized()
            cmd_obj.run()
            self.have_run[command] = 1
    
  • cmd.py コメントにあるとおり、サブクラスではメソッドrunを実装している。
    # Subclasses must define:
        #   initialize_options()
        #     provide default values for all options; may be customized by
        #     setup script, by options from config file(s), or by command-line
        #     options
        #   finalize_options()
        #     decide on the final values for all options; this is called
        #     after all possible intervention from the outside world
        #     (command-line, option file, etc.) has been processed
        #   run()
        #     run the command: do whatever it is we're here to do,
        #     controlled by the command's various option values
    
  • install.py
    def run (self):
            # Obviously have to build before we can install
            if not self.skip_build:
                self.run_command('build')
    
            # Run all sub-commands (at least those that need to be run)
            for cmd_name in self.get_sub_commands():
                self.run_command(cmd_name)
    
  • cmd.py (get_sub_commands)
    def get_sub_commands (self):
            commands = []
            for (cmd_name, method) in self.sub_commands:
                if method is None or method(self):
                    commands.append(cmd_name)
            return commands
    
  • build.py sub_commandはclass変数として定義されている。
    sub_commands = [('build_py',      has_pure_modules),
                        ('build_clib',    has_c_libraries),
                        ('build_ext',     has_ext_modules),
                        ('build_scripts', has_scripts),
                       ]
    
  • build_ext.py subcommandの一例として、build_extを追ってみる。
    def run (self):
            ...skip...
            self.compiler = new_compiler(compiler=self.compiler,
                                         verbose=self.verbose,
                                         dry_run=self.dry_run,
                                         force=self.force)
            customize_compiler(self.compiler)
            ...skip...
            # Now actually compile and link everything.
            self.build_extensions()
    
        def build_extensions(self):
            # First, sanity-check the 'extensions' list
            self.check_extensions_list(self.extensions)
    
            for ext in self.extensions:
                self.build_extension(ext)
    
        def build_extension(self, ext):
        ...skip...
            objects = self.compiler.compile(sources,
            ...skip
            self.compiler.link_shared_object(
    
  • ccompiler.py はcコンパイラの抽象。_compileとlinkをsubclassで実装する。
    def compile(self, sources, output_dir=None, macros=None,
        def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
         --> empty
        def link (self,
         --> empty
    
        def spawn (self, cmd):
            spawn (cmd, dry_run=self.dry_run)
         --> from distutils.spawn import spawn
    
  • unixccompiler.py posix系はこのsubclassで実装されている。
    def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
            try:
                self.spawn(self.compiler_so + cc_args + [src, '-o', obj] +
                           extra_postargs)
            except DistutilsExecError, msg:
                raise CompileError, msg
    
        def link(self, target_desc, objects,
                    self.spawn(linker + ld_args)
    
  • spawn.py まあ当然ですが、forkに落ちます。
    def _spawn_posix (cmd,
        pid = os.fork()
    
  • build.pyでsub_commandのmappingを定義していたが、そのペアの右側はbuildのメソッドであり、distのmethodに転送される。。なので、このmethodがtrueを返すと、sub_commandが実行される。

    build.py
    def has_scripts (self):
            return self.distribution.has_scripts()
    
    dist.py
    def has_scripts (self):
            return self.scripts and len(self.scripts) > 0
    
  • ではこのscriptsはどこからきているかというとsetupの**attrからきている。
    core.py
    def setup (**attrs):
            _setup_distribution = dist = klass(attrs)
    
    
    dist.py
    class Distribution:
        def __init__ (self, attrs=None):
            if attrs:
                for (key,val) in attrs.items():
                    if hasattr(self.metadata, key):
                        setattr(self.metadata, key, val)
                    elif hasattr(self, key):
                        setattr(self, key, val)
                    else:
                        msg = "Unknown distribution option: %s" % repr(key)
                        if warnings is not None:
                            warnings.warn(msg)
                        else:
                            sys.stderr.write(msg + "\n")
    

0 件のコメント: