2016-12-15 6 views
16

प्रस्तावना: अजगर setuptools पैकेज वितरण के लिए उपयोग किया जाता है। मेरे पास एक पायथन पैकेज है (आइए इसे my_package पर कॉल करें), जिसमें कई extra_require पैकेज हैं। सबकुछ काम करता है (पैकेज का इंस्टॉलेशन और बिल्ड, साथ ही एक्स्ट्रा, अगर अनुरोध किया गया हो), क्योंकि सभी extra_require पाइथन पैकेज स्वयं थे और पीआईपी सही ढंग से सबकुछ हल कर चुके थे। एक साधारण pip install my_package एक आकर्षण की तरह काम किया।अजगर setuptools/distutils कस्टम Makefile साथ `extra` पैकेज के लिए निर्माण

सेटअप: अब, एक्स्ट्रा कलाकार से एक के लिए मैं एक गैर अजगर पुस्तकालय X के एक द्विआधारी कॉल करने की आवश्यकता (हमें यह extra1 कॉल)।

मॉड्यूल X स्वयं (स्रोत कोड) my_package कोडबेस में जोड़ा गया था और वितरण my_package में शामिल किया गया था। दुख की बात है कि, मेरे उपयोग के लिए, X को पहले लक्ष्य मशीन पर एक बाइनरी में संकलित करने की आवश्यकता है (सी ++ कार्यान्वयन; मुझे लगता है कि इस तरह का संकलन my_package स्थापना के निर्माण चरण पर होगा)। विभिन्न प्लेटफार्म संकलन के लिए अनुकूलित X लाइब्रेरी में Makefile है, इसलिए सभी की आवश्यकता है, लाइब्रेरी X लाइब्रेरी my_package में संबंधित प्रक्रिया में जब निर्माण प्रक्रिया चल रही है, तो संबंधित निर्देशिका में make चलाएं।

प्रश्न # 1: कैसे एक टर्मिनल कमांड को चलाने के लिए (जैसे कि, मेरे मामले में make) पैकेज की निर्माण प्रक्रिया, setuptools/distutils का उपयोग कर के दौरान?

प्रश्न # 2: कैसे सुनिश्चित करें कि ऐसा टर्मिनल कमांड केवल तभी निष्पादित किया जाता है जब संबंधित extra1 स्थापना प्रक्रिया के दौरान निर्दिष्ट किया गया हो?

उदाहरण:

  1. किसी pip install my_package चलाता है, पुस्तकालय X की ऐसी कोई अतिरिक्त संकलन हो जाएगा।
  2. यदि कोई pip install my_package [extra1] चलाता है, तो मॉड्यूल X संकलित करने की आवश्यकता है, इसलिए इसी बाइनरी को लक्ष्य मशीन पर बनाया और उपलब्ध कराया जाएगा।
+1

सकारात्मक डुप्लिकेट [मैं setup.py में मेकफ़ाइल कैसे चला सकता हूं] (http://stackoverflow.com/questions/1754966/how-can-i-run-a-makefile-in-setup-py)? – lucianopaz

+1

बिल्कुल नहीं। यह ए) किसी स्थिति के लिए उत्तर नहीं है, जब ऐसी स्थापना की आवश्यकता होती है, केवल तभी जब "अतिरिक्त 1" शामिल होता है। बी) यह वास्तव में जानकारीपूर्ण/विस्तृत नहीं है। मैं एक और विस्तृत उत्तर की सराहना करता हूं, और मेरा मानना ​​है कि समुदाय के लिए यह बहुत ही जानकारीपूर्ण होगा यदि विस्तृत विवरण प्रदान किया गया हो। –

+0

क्या 'X' में 'setup.py' है और इस प्रकार एक नियमित पायथन पैकेज है? – fpbhb

उत्तर

1

दुर्भाग्य से, डॉक्स setup.py और पिप के बीच बातचीत के आसपास अत्यंत दुर्लभ हैं, लेकिन आप इस तरह कुछ करने के लिए सक्षम होना चाहिए:

import subprocess 

from setuptools import Command 
from setuptools import setup 


class CustomInstall(Command): 

    user_options = [] 

    def initialize_options(self): 
     pass 

    def finalize_options(self): 
     pass 

    def run(self): 
     subprocess.call(
      ['touch', 
      '/home/{{YOUR_USERNAME}}/' 
      'and_thats_why_you_should_never_run_pip_as_sudo'] 
     ) 

setup(
    name='hack', 
    version='0.1', 
    cmdclass={'customcommand': CustomInstall} 
) 

यह आपको मनमाना कोड चलाने में एक हुक देता है आदेशों के साथ, और विभिन्न प्रकार के कस्टम विकल्प पार्सिंग का भी समर्थन करता है (यहां प्रदर्शित नहीं किया गया)।

एक setup.py फ़ाइल में इस रखो और कोशिश यह:

pip install --install-option="customcommand" .

ध्यान दें कि यह आदेश के बाद निष्पादित किया जाता है मुख्य स्थापित अनुक्रम है, तो आप ऐसा करने के लिए कोशिश कर रहे हैं कि वास्तव में क्या पर निर्भर करता है यह, काम नहीं कर सकतावर्बोज़ पिप उत्पादन स्थापित देखें:

(.venv) ayoon:tmp$ pip install -vvv --install-option="customcommand" . 
/home/ayoon/tmp/.venv/lib/python3.6/site-packages/pip/commands/install.py:194: UserWarning: Disabling all use of wheels due to the use of --build-options/- 
-global-options/--install-options.                               
    cmdoptions.check_install_build_global(options) 
Processing /home/ayoon/tmp 
    Running setup.py (path:/tmp/pip-j57ovc7i-build/setup.py) egg_info for package from file:///home/ayoon/tmp 
    Running command python setup.py egg_info 
    running egg_info 
    creating pip-egg-info/hack.egg-info 
    writing pip-egg-info/hack.egg-info/PKG-INFO 
    writing dependency_links to pip-egg-info/hack.egg-info/dependency_links.txt 
    writing top-level names to pip-egg-info/hack.egg-info/top_level.txt 
    writing manifest file 'pip-egg-info/hack.egg-info/SOURCES.txt' 
    reading manifest file 'pip-egg-info/hack.egg-info/SOURCES.txt' 
    writing manifest file 'pip-egg-info/hack.egg-info/SOURCES.txt' 
    Source in /tmp/pip-j57ovc7i-build has version 0.1, which satisfies requirement hack==0.1 from file:///home/ayoon/tmp 
Could not parse version from link: file:///home/ayoon/tmp 
Installing collected packages: hack 
    Running setup.py install for hack ...  Running command /home/ayoon/tmp/.venv/bin/python3.6 -u -c "import setuptools, tokenize;__file__='/tmp/pip-j57ovc7 
i-build/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install -- 
record /tmp/pip-_8hbltc6-record/install-record.txt --single-version-externally-managed --compile --install-headers /home/ayoon/tmp/.venv/include/site/python3 
.6/hack customcommand                                  
    running install 
    running build 
    running install_egg_info 
    running egg_info 
    writing hack.egg-info/PKG-INFO 
    writing dependency_links to hack.egg-info/dependency_links.txt 
    writing top-level names to hack.egg-info/top_level.txt 
    reading manifest file 'hack.egg-info/SOURCES.txt' 
    writing manifest file 'hack.egg-info/SOURCES.txt' 
    Copying hack.egg-info to /home/ayoon/tmp/.venv/lib/python3.6/site-packages/hack-0.1-py3.6.egg-info 
    running install_scripts 
    writing list of installed files to '/tmp/pip-_8hbltc6-record/install-record.txt' 
    running customcommand 
done 
    Removing source in /tmp/pip-j57ovc7i-build 
Successfully installed hack-0.1 
0

यह सवाल लंबे मुझे तंग करने के लिए वापस आ गया के बाद मैं दो साल पहले इस पर टिप्पणी की! मेरे पास हाल ही में एक ही समस्या थी, और मुझे दस्तावेज बहुत दुर्लभ मिला, क्योंकि मुझे लगता है कि आप में से अधिकांश ने अनुभव किया होगा। इसलिए मैंने setuptools और distutils के स्रोत कोड का थोड़ा सा शोध करने की कोशिश की ताकि यह देखने के लिए कि क्या आपके द्वारा पूछे गए दोनों प्रश्नों के लिए मुझे कम या ज्यादा मानक दृष्टिकोण मिल सकता है या नहीं। पैकेज के निर्माण की प्रक्रिया के दौरान एक टर्मिनल कमांड (यानी, मेरे मामले में make) को चलाने के लिए कैसे, setuptools का उपयोग कर/distutils:


पहला सवाल आप

प्रश्न # 1 पूछा ?

कई दृष्टिकोण है और उन सभी को जब setup बुला एक cmdclass की स्थापना शामिल है। setup के पैरामीटर cmdclass आदेश ऐसे नाम हैं जो निर्माण के आधार पर निष्पादित या वितरण की जरूरतों को स्थापित हो जाएगा, और वर्गों है कि (distutils.cmd.Command आधार वर्ग से विरासत एक पक्ष नोट के रूप में के बीच एक मैपिंग, setuptools.command.Command वर्ग distutils से प्राप्त होता है 'Command वर्ग होना चाहिए ताकि आप जिन चीज़ों ayoon किया की तरह किसी भी कमांड नाम को परिभाषित करने की अनुमति देता है, और फिर इसे विशेष रूप से निष्पादित जब कमांड लाइन से python setup.py --install-option="customcommand" बुला setuptools कार्यान्वयन से सीधे प्राप्त कर सकते हैं।

cmdclass। इस के साथ समस्या यह है कि यह नहीं है मानक आदेश जो pip के माध्यम से पैकेज स्थापित करने का प्रयास करते समय निष्पादित किया जाएगा यापर कॉल करके। इस तक पहुंचने का मानक तरीका यह जांचना है कि setup सामान्य आदेश में निष्पादित करने का प्रयास करें और उसके बाद उस विशेष cmdclass को अधिभारित करें।

setuptools.setup और distutils.setup में देख से, setup आदेशों यह found in the command line, मान देता है जो सिर्फ एक सादे install है चलेंगे। setuptools.setup के मामले में, यह परीक्षणों की एक श्रृंखला को ट्रिगर करेगा जो देखेंगे कि distutils.install कमांड क्लास को एक साधारण कॉल का सहारा लेना है, और यदि ऐसा नहीं होता है, तो यह bdist_egg चलाने का प्रयास करेगा। बदले में, यह आदेश कई चीजें करता है लेकिन build_clib, build_py और/या build_ext आदेशों को कॉल करने के लिए महत्वपूर्ण रूप से निर्णय लेता है। distutils.install यदि आवश्यक हो तो build चलाता है जो build_clib, build_py और/या build_ext चलाता है। इसका मतलब यह है कि चाहे आप setuptools या distutils का उपयोग करते हैं, यदि स्रोत से निर्माण करना आवश्यक है, तो build_clib, build_py, और/या build_ext आदेश चलाए जाएंगे, इसलिए ये वे हैं जिन्हें हम cmdclass के साथ ओवरलोड करना चाहते हैं setup, सवाल तीन में से कौन सा बन जाता है।

  • build_py शुद्ध पायथन पैकेज "निर्माण" के लिए उपयोग किया जाता है, इसलिए हम इसे सुरक्षित रूप से अनदेखा कर सकते हैं।
  • build_ext का उपयोग setup फ़ंक्शन पर कॉल के ext_modules पैरामीटर के माध्यम से घोषित घोषित एक्सटेंशन मॉड्यूल बनाने के लिए किया जाता है।हम इस वर्ग को ओवरलोड करना चाहते हैं, मुख्य विधि है कि प्रत्येक एक्सटेंशन बनाता build_extension (या here distutils के लिए)
  • build_clib घोषित पुस्तकालयों कि setup कार्य करने के लिए कॉल की libraries पैरामीटर के माध्यम से पारित कर रहे हैं बनाने के लिए उपयोग किया जाता है। इस मामले में, हमारे व्युत्पन्न वर्ग के साथ हमें मुख्य विधि को अधिभारित करना चाहिए build_libraries विधि (heredistutils के लिए)।

मैं एक उदाहरण पैकेज कि setuptoolsbuild_ext कमांड का उपयोग करके एक Makefile के माध्यम से एक खिलौना ग स्थिर पुस्तकालय बनाता साझा करेंगे। दृष्टिकोण को build_clib कमांड का उपयोग करने के लिए अनुकूलित किया जा सकता है, लेकिन आपको build_clib.build_libraries का स्रोत कोड चेकआउट करना होगा।

setup.py

import os, subprocess 
import setuptools 
from setuptools.command.build_ext import build_ext 
from distutils.errors import DistutilsSetupError 
from distutils import log as distutils_logger 


extension1 = setuptools.extension.Extension('test_pack_opt.test_ext', 
        sources = ['test_pack_opt/src/test.c'], 
        libraries = [':libtestlib.a'], 
        library_dirs = ['test_pack_opt/lib/'], 
        ) 

class specialized_build_ext(build_ext, object): 
    """ 
    Specialized builder for testlib library 

    """ 
    special_extension = extension1.name 

    def build_extension(self, ext): 

     if ext.name!=self.special_extension: 
      # Handle unspecial extensions with the parent class' method 
      super(specialized_build_ext, self).build_extension(ext) 
     else: 
      # Handle special extension 
      sources = ext.sources 
      if sources is None or not isinstance(sources, (list, tuple)): 
       raise DistutilsSetupError(
         "in 'ext_modules' option (extension '%s'), " 
         "'sources' must be present and must be " 
         "a list of source filenames" % ext.name) 
      sources = list(sources) 

      if len(sources)>1: 
       sources_path = os.path.commonprefix(sources) 
      else: 
       sources_path = os.path.dirname(sources[0]) 
      sources_path = os.path.realpath(sources_path) 
      if not sources_path.endswith(os.path.sep): 
       sources_path+= os.path.sep 

      if not os.path.exists(sources_path) or not os.path.isdir(sources_path): 
       raise DistutilsSetupError(
         "in 'extensions' option (extension '%s'), " 
         "the supplied 'sources' base dir " 
         "must exist" % ext.name) 

      output_dir = os.path.realpath(os.path.join(sources_path,'..','lib')) 
      if not os.path.exists(output_dir): 
       os.makedirs(output_dir) 

      output_lib = 'libtestlib.a' 

      distutils_logger.info('Will execute the following command in with subprocess.Popen: \n{0}'.format(
        'make static && mv {0} {1}'.format(output_lib, os.path.join(output_dir, output_lib)))) 


      make_process = subprocess.Popen('make static && mv {0} {1}'.format(output_lib, os.path.join(output_dir, output_lib)), 
              cwd=sources_path, 
              stdout=subprocess.PIPE, 
              stderr=subprocess.PIPE, 
              shell=True) 
      stdout, stderr = make_process.communicate() 
      distutils_logger.debug(stdout) 
      if stderr: 
       raise DistutilsSetupError('An ERROR occured while running the ' 
              'Makefile for the {0} library. ' 
              'Error status: {1}'.format(output_lib, stderr)) 
      # After making the library build the c library's python interface with the parent build_extension method 
      super(specialized_build_ext, self).build_extension(ext) 


setuptools.setup(name = 'tester', 
     version = '1.0', 
     ext_modules = [extension1], 
     packages = ['test_pack', 'test_pack_opt'], 
     cmdclass = {'build_ext': specialized_build_ext}, 
     ) 

test_pack/__ init__.py

from __future__ import absolute_import, print_function 

def py_test_fun(): 
    print('Hello from python test_fun') 

try: 
    from test_pack_opt.test_ext import test_fun as c_test_fun 
    test_fun = c_test_fun 
except ImportError: 
    test_fun = py_test_fun 

test_pack_opt/__ init__.py

from __future__ import absolute_import, print_function 
import test_pack_opt.test_ext 

test_pack_opt/src/Makefile

LIBS = testlib.so testlib.a 
SRCS = testlib.c 
OBJS = testlib.o 
CFLAGS = -O3 -fPIC 
CC = gcc 
LD = gcc 
LDFLAGS = 

all: shared static 

shared: libtestlib.so 

static: libtestlib.a 

libtestlib.so: $(OBJS) 
    $(LD) -pthread -shared $(OBJS) $(LDFLAGS) -o [email protected] 

libtestlib.a: $(OBJS) 
    ar crs [email protected] $(OBJS) $(LDFLAGS) 

clean: cleantemp 
    rm -f $(LIBS) 

cleantemp: 
    rm -f $(OBJS) *.mod 

.SUFFIXES: $(SUFFIXES) .c 

%.o:%.c 
    $(CC) $(CFLAGS) -c $< 

test_pack_opt/src/test.c

#include <Python.h> 
#include "testlib.h" 

static PyObject* 
test_ext_mod_test_fun(PyObject* self, PyObject* args, PyObject* keywds){ 
    testlib_fun(); 
    return Py_None; 
} 

static PyMethodDef TestExtMethods[] = { 
    {"test_fun", (PyCFunction) test_ext_mod_test_fun, METH_VARARGS | METH_KEYWORDS, "Calls function in shared library"}, 
    {NULL, NULL, 0, NULL} 
}; 

#if PY_VERSION_HEX >= 0x03000000 
    static struct PyModuleDef moduledef = { 
     PyModuleDef_HEAD_INIT, 
     "test_ext", 
     NULL, 
     -1, 
     TestExtMethods, 
     NULL, 
     NULL, 
     NULL, 
     NULL 
    }; 

    PyMODINIT_FUNC 
    PyInit_test_ext(void) 
    { 
     PyObject *m = PyModule_Create(&moduledef); 
     if (!m) { 
      return NULL; 
     } 
     return m; 
    } 
#else 
    PyMODINIT_FUNC 
    inittest_ext(void) 
    { 
     PyObject *m = Py_InitModule("test_ext", TestExtMethods); 
     if (m == NULL) 
     { 
      return; 
     } 
    } 
#endif 

test_pack_opt/src/testlib.c

#include "testlib.h" 

void testlib_fun(void){ 
    printf("Hello from testlib_fun!\n"); 
} 

test_pack_opt/src/testlib.h

#ifndef TESTLIB_H 
#define TESTLIB_H 

#include <stdio.h> 

void testlib_fun(void); 

#endif 

इस उदाहरण में, ग पुस्तकालय है कि मैं कस्टम Makefile सिर्फ एक समारोह जो stdout में "Hello from testlib_fun!\n" प्रिंट है का उपयोग करते हुए निर्माण करना चाहते हैं। test.c स्क्रिप्ट पाइथन और इस लाइब्रेरी के एकल फ़ंक्शन के बीच एक साधारण इंटरफ़ेस है। विचार यह है कि मैं setup बताता हूं कि मैं test_pack_opt.test_ext नामक एक सी एक्सटेंशन बनाना चाहता हूं, जिसमें केवल एक स्रोत फ़ाइल है: test.c इंटरफ़ेस स्क्रिप्ट, और मैं यह भी विस्तार बताता हूं कि इसे स्थिर पुस्तकालय libtestlib.a के विरुद्ध लिंक करना होगा। मुख्य बात यह है कि मैं specialized_build_ext(build_ext, object) का उपयोग कर build_ext cmdclass को अधिभारित करता हूं। object से विरासत केवल तभी जरूरी है जब आप पेरेंट क्लास विधियों को प्रेषित करने के लिए super पर कॉल करने में सक्षम होना चाहते हैं। build_extension विधि Extension उदाहरण दूसरे Extension उदाहरणों के साथ अच्छा काम करने के लिए build_extension के डिफ़ॉल्ट व्यवहार की आवश्यकता के लिए Extension उदाहरण लेता है, मैं जांचता हूं कि इस एक्सटेंशन का विशेष नाम है या नहीं और यदि मैं इसे कॉल नहीं करता super की build_extension विधि।

विशेष पुस्तकालय के लिए, मैं मेकफ़ाइल को केवल subprocess.Popen('make static ...'). The rest of the command passed to the shell is just to move the static library to a certain default location in which the library should be found to be able to link it to the rest of the compiled extension (which is also just compiled using the सुपर 's build_extension` विधि के साथ कॉल करता हूं)।

जैसा कि आप कल्पना कर सकते हैं कि इस कोड को अलग-अलग व्यवस्थित करने के कई तरीके हैं, इसलिए उन्हें सभी को सूचीबद्ध करने का अर्थ नहीं है। मुझे उम्मीद है कि यह उदाहरण मेकफ़ाइल को कॉल करने के तरीके को दिखाता है, और cmdclass और Command व्युत्पन्न कक्षा को आपको मानक स्थापना में make पर कॉल करने के लिए ओवरलोड करना चाहिए।


अब, सवाल पर 2.

प्रश्न # 2: कैसे सुनिश्चित करने के लिए, केवल कि इस तरह के टर्मिनल कमांड निष्पादित किया जाता है, तो इसी extra1 स्थापना प्रक्रिया के दौरान निर्दिष्ट किया जाता है?

यह के features पैरामीटर के साथ संभव था। मानक तरीका है कि आवश्यकताओं को पूरा करने के आधार पर पैकेज स्थापित करने का प्रयास करें। install_requires अनिवार्य आवश्यकताओं को सूचीबद्ध करता है, extras_requires वैकल्पिक आवश्यकताओं को सूचीबद्ध करता है। pip install Project-A[PDF] फोन करके setuptools documentation

setup(
    name="Project-A", 
    ... 
    extras_require={ 
     'PDF': ["ReportLab>=1.2", "RXP"], 
     'reST': ["docutils>=0.3"], 
    } 
) 

आप वैकल्पिक आवश्यक संकुल की स्थापना के लिए मजबूर कर सकता है से उदाहरण के लिए, लेकिन अगर किसी कारण से 'PDF' अतिरिक्त नामित के लिए आवश्यकताओं को पहले से संतुष्ट थे, pip install Project-A उसी के साथ खत्म होगा "Project-A" कार्यक्षमता। इसका अर्थ यह है कि जिस तरह से "प्रोजेक्ट-ए" स्थापित किया गया है, कमांड लाइन पर निर्दिष्ट प्रत्येक अतिरिक्त के लिए अनुकूलित नहीं किया गया है, "प्रोजेक्ट-ए" हमेशा उसी तरह स्थापित करने का प्रयास करेगा और अनुपलब्ध होने के कारण कम कार्यक्षमता के साथ समाप्त हो सकता है वैकल्पिक आवश्यकताओं।

जो मैंने समझा, उससे इसका मतलब है कि आपके मॉड्यूल एक्स को संकलित और स्थापित करने के लिए केवल [अतिरिक्त 1] निर्दिष्ट किया गया है, तो आपको मॉड्यूल एक्स को एक अलग पैकेज के रूप में शिप करना चाहिए और extras_require के माध्यम से उस पर निर्भर होना चाहिए। कल्पना मॉड्यूल एक्स my_package_opt में भेज दिया जाएगा चलें, my_package के लिए अपने स्थापना दिखना चाहिए

तरह
setup(
    name="my_package", 
    ... 
    extras_require={ 
     'extra1': ["my_package_opt"], 
    } 
) 

ठीक है, मुझे खेद है कि मेरा उत्तर इतने लंबे समय से किया जा रहा समाप्त हो गया हूँ, लेकिन मुझे आशा है कि यह मदद करता है। किसी भी वैचारिक या नामकरण त्रुटि को इंगित करने में संकोच न करें, क्योंकि मैंने ज्यादातर इसे setuptools स्रोत कोड से निकालने का प्रयास किया था।

संबंधित मुद्दे