การรวมไฟล์ที่ไม่ใช่ไพ ธ อนด้วย setup.py


200

ฉันจะทำอย่างไร setup.pyรวมไฟล์ที่ไม่ได้เป็นส่วนหนึ่งของรหัสได้อย่างไร (โดยเฉพาะมันเป็นไฟล์ลิขสิทธิ์ แต่อาจเป็นอย่างอื่นก็ได้)

ฉันต้องการที่จะควบคุมตำแหน่งของไฟล์ ในโฟลเดอร์ต้นฉบับต้นฉบับไฟล์อยู่ในรูทของแพ็คเกจ (เช่นในระดับเดียวกับระดับสูงสุด__init__.py) ฉันต้องการให้มันอยู่ตรงนั้นเมื่อติดตั้งแพ็คเกจโดยไม่คำนึงถึงระบบปฏิบัติการ ฉันจะทำอย่างไร


คุณจะทำอย่างนั้นได้อย่างไรในเวลานี้? คำถามก่อนหน้าของคุณระบุว่าคุณคุ้นเคยกับวิธีเพิ่มไฟล์ลิขสิทธิ์ดังนั้นรหัสของคุณที่ "ไม่ทำงาน" คืออะไร
SilentGhost

2
data_files = [('', ['lgpl2.1_license.txt',]),]วางไว้ในโฟลเดอร์ Python26
Ram Rachum

หลังจากข้อเสนอแนะเชิงลบฉันอ่านคำถามของคุณอีกครั้งและตระหนักว่าสิ่งที่ฉันหายไป ฉันได้อัปเดตคำตอบของฉันเพื่อให้โซลูชันที่ไม่แฮ็คกับคำถามของคุณที่ไม่ต้องการโมดูลเพิ่มเติมใด ๆ (เช่น setuptools หรือแจกจ่าย)
Evan Plaice

ขอบคุณอีวาน อย่างไรก็ตามฉันก็โอเคอย่างสมบูรณ์เมื่อใช้ setuptools เพราะมันแพร่หลายมาก
Ram Rachum

คำตอบ:


224

อาจเป็นวิธีที่ดีที่สุดในการทำเช่นนี้คือการใช้setuptools package_dataคำสั่ง นี่หมายถึงการใช้setuptools(หรือdistribute) แทนdistutilsแต่นี่เป็น "การอัพเกรด" ที่ราบรื่นมาก

นี่คือตัวอย่างเต็มรูปแบบ (แต่ยังไม่ทดลอง):

from setuptools import setup, find_packages

setup(
    name='your_project_name',
    version='0.1',
    description='A description.',
    packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']),
    package_data={'': ['license.txt']},
    include_package_data=True,
    install_requires=[],
)

สังเกตบรรทัดเฉพาะที่สำคัญตรงนี้:

package_data={'': ['license.txt']},
include_package_data=True,

package_dataเป็นdictชื่อแพ็คเกจ (ว่าง = แพ็คเกจทั้งหมด) ไปยังรายการรูปแบบ (สามารถรวม globs) ตัวอย่างเช่นหากคุณต้องการระบุเฉพาะไฟล์ภายในแพ็คเกจของคุณคุณสามารถทำได้เช่นกัน:

package_data={'yourpackage': ['*.txt', 'path/to/resources/*.txt']}

วิธีแก้ปัญหาที่นี่คือไม่เปลี่ยนชื่อpyไฟล์ที่ไม่ใช่ของคุณด้วย.pyนามสกุล

ดูการนำเสนอของ Ian Bickingสำหรับข้อมูลเพิ่มเติม

อัปเดต: วิธีการ [ดีกว่า] อีกวิธีหนึ่ง

วิธีการอื่นที่ใช้งานได้ดีหากคุณต้องการควบคุมเนื้อหาของการแจกจ่ายซอร์ส ( sdist) และมีไฟล์นอกแพ็คเกจ (เช่นไดเรกทอรีระดับบนสุด) คือการเพิ่มMANIFEST.inไฟล์ ดูเอกสาร Pythonสำหรับรูปแบบของไฟล์นี้

ตั้งแต่เขียนคำตอบนี้ฉันพบว่าการใช้โดยMANIFEST.inทั่วไปแล้วเป็นวิธีที่น่าผิดหวังน้อยกว่าเพื่อให้แน่ใจว่าการกระจายแหล่งที่มาของคุณ (tar.gz ) มีไฟล์ที่คุณต้องการ

ตัวอย่างเช่นหากคุณต้องการรวมrequirements.txtจากระดับบนสุดให้รวมไดเรกทอรี "data" ระดับบนสุดซ้ำ:

include requirements.txt
recursive-include data *

อย่างไรก็ตามในการที่จะคัดลอกไฟล์เหล่านี้ในเวลาติดตั้งไปยังโฟลเดอร์ของแพ็คเกจภายในไซต์แพ็คเกจคุณจะต้องส่งinclude_package_data=Trueไปยังsetup()ฟังก์ชัน ดูการเพิ่มไฟล์ที่ไม่ใช่รหัสสำหรับข้อมูลเพิ่มเติม


5
package_data มีให้สำหรับสคริปต์การติดตั้ง distutils ล้วนตั้งแต่ Python 2.3
Éric Araujo

15
คำตอบนี้ดูสมเหตุสมผล แต่ไม่ได้ผลสำหรับฉัน เนื่องจาก package_data ไม่น่าเชื่อถืออย่างมาก (ต้องมีการประสานงานของ MANIFEST.in และ setup.py เพื่อเพิ่มไฟล์ไปยัง sdist และติดตั้งพวกเขาเป็นขั้นตอนที่แยกต่างหาก) และผู้เขียนคำตอบนี้บันทึกว่า "ไม่ผ่านการทดสอบ" ทุกคนสามารถทำได้ อื่นยืนยันว่ามันใช้งานได้สำหรับพวกเขา? ไฟล์ LICENSE ของฉันรวมอยู่ใน sdist แต่ไม่ได้ติดตั้งเมื่อฉันเรียกใช้ "python setup.py install" หรือ "pip install Package"
Jonathan Hartley

11
การนำเสนอของ Ian Bicking แสดงเฉพาะวิธีการติดตั้งข้อมูลแพ็คเกจสำหรับไฟล์ที่อยู่ในแพ็คเกจ ไฟล์ใบอนุญาตของฉันอยู่ที่ระดับบนสุดของโปรเจคคือไม่อยู่ในแพ็คเกจใด ๆ ฉันยังสามารถใช้ package_data ได้หรือไม่ การใช้ data_files เป็น non-starter เนื่องจากมันทำให้ไฟล์อยู่ในตำแหน่งที่ทั้งระบบ ไม่เกี่ยวข้องกับโครงการของฉันและทำให้แย่ลงตำแหน่งเปลี่ยนไปขึ้นอยู่กับว่าฉันเรียกใช้ "setup.py install" หรือ "pip install" จาก sdist เดียวกัน
Jonathan Hartley

8
ฉันเดาว่าเหตุผลที่มันใช้งานไม่ได้สำหรับฉันก็คือไฟล์ไม่ได้อยู่ในแพ็คเกจใด ๆ - เป็นไฟล์ LICENSE ในระดับบนสุดของที่เก็บและดังนั้นจึงไม่สามารถติดตั้งได้โดยใช้ 'package_data'
Jonathan Hartley

7
คำตอบนี้ไม่ได้ผลสำหรับฉัน แฟ้มเพิ่มเติมจะไม่ได้รับการใส่ลงไปใน tarball และ ...
lpapp

44

ในการบรรลุสิ่งที่คุณอธิบายจะใช้สองขั้นตอน ...

  • ไฟล์จะต้องเพิ่มไปยังแหล่ง tarball
  • จำเป็นต้องได้รับการแก้ไข setup.py เพื่อติดตั้งไฟล์ข้อมูลไปยังเส้นทางต้นทาง

ขั้นตอนที่ 1: ในการเพิ่มไฟล์ลงใน tarball ต้นทางให้รวมไว้ใน MANIFEST

สร้างMANIFESTเทมเพลตในโฟลเดอร์ที่มี setup.py

รายการนั้นเป็นไฟล์ข้อความที่มีรายชื่อของไฟล์ทั้งหมดที่จะรวมอยู่ในแหล่ง tarball

นี่คือลักษณะที่ปรากฏสำหรับโครงการของฉัน:

  • CHANGELOG.txt
  • INSTALL.txt
  • LICENSE.txt
  • pypreprocessor.py
  • README.txt
  • setup.py
  • test.py
  • todo.txt

หมายเหตุ: แม้ว่าsdist จะเพิ่มไฟล์บางไฟล์โดยอัตโนมัติแต่ฉันต้องการระบุให้ชัดเจนเพื่อให้แน่ใจว่าจะไม่คาดเดาว่ามันทำอะไรและไม่ได้

ขั้นตอนที่ 2: ในการติดตั้งไฟล์ข้อมูลไปยังโฟลเดอร์แหล่งข้อมูลให้แก้ไข setup.py

เนื่องจากคุณต้องการเพิ่มไฟล์ข้อมูล (LICENSE.txt) ในโฟลเดอร์การติดตั้งซอร์สคุณต้องแก้ไขพา ธ การติดตั้งข้อมูลเพื่อให้ตรงกับพา ธ การติดตั้งต้นทาง สิ่งนี้จำเป็นเพราะโดยปกติแล้วไฟล์ข้อมูลจะถูกติดตั้งในตำแหน่งที่แตกต่างจากไฟล์ต้นฉบับ

หากต้องการแก้ไขข้อมูลการติดตั้ง dir ให้ตรงกับแหล่งข้อมูลการติดตั้ง ...

ดึงข้อมูลการติดตั้งจาก distutils ด้วย:

from distutils.command.install import INSTALL_SCHEMES

ปรับเปลี่ยน dir การติดตั้งข้อมูลเพื่อให้ตรงกับ dir ติดตั้งแหล่งที่มา:

for scheme in INSTALL_SCHEMES.values():
    scheme['data'] = scheme['purelib']

และเพิ่มไฟล์ข้อมูลและตำแหน่งเพื่อตั้งค่า ():

data_files=[('', ['LICENSE.txt'])]

หมายเหตุ: ขั้นตอนข้างต้นควรบรรลุสิ่งที่คุณอธิบายในลักษณะมาตรฐานโดยไม่ต้องใช้ไลบรารีส่วนขยายใด ๆ


10
รายการควบคุมเฉพาะไฟล์ที่รวมอยู่ในแหล่ง tarball (ผลิตโดย sdist) ไฟล์ที่อยู่ในรายการจะไม่ถูกติดตั้ง
David Cournapeau

@ David ฉันไม่ทราบว่าฉันอยู่ไกลแค่ไหนในแนวทางแรกของฉัน ฉันได้อัปเดตคำตอบให้ถูกต้องเพื่อให้บรรลุคำถามที่ถามโดยไม่ต้องมีห้องสมุดบุคคลที่สามเพิ่มเติม
Evan Plaice

3
@ Éricเหตุผลพิเศษทำไม และคุณมีตัวเลือกการติดตั้งที่ใช้งานได้ซึ่งไม่ต้องการแพ็คเกจของบุคคลที่สาม (เช่น setup_tools) ในการทำงาน ฉันเลือก distutils มากกว่า setuptools เพราะมันมาพร้อมกับการติดตั้ง vanilla ของ python และฉันก็สร้างโมดูลสำหรับ PYPI ควรมีวิธีที่ดีกว่าในการทำเช่นนี้โดยใช้ distutils2 แต่ฉันไม่ได้สัมผัสกับไพ ธ อนสักพักแล้วฉันก็ไม่รู้เหมือนกัน เนื่องจากคุณดูเหมือนจะมีความรู้เกี่ยวกับ distutils2 ฉันคิดว่ามันจะเป็นประโยชน์ต่อพวกเราที่เหลือในการมีทางเลือก distutils2 ที่เหมาะสม
Evan Plaice

6
ดังที่ได้กล่าวไว้ในเธรดอื่นpackage_dataไม่ทำงานหากไฟล์ไม่ได้อยู่ในแพ็คเกจ
Gringo Suave

2
@ ÉricAraujo: มันไม่ใช่ความคิดที่ดีที่จะใช้วิธีนี้เพราะไม่มีวิธีอื่น มันคือการออกแบบ distutils ที่ไม่ดี - จริง แต่มันเป็น API สาธารณะแบบไม่ใช้ความจริงซึ่งจะไม่มีวันเปลี่ยนแปลงเพราะมันจะแตกหลายอย่าง หวังว่า distutils2 จะให้วิธีที่แนะนำที่ดีกว่า
Anatoly techtonik


7

ฉันต้องการโพสต์ความคิดเห็นในคำถามใดคำถามหนึ่ง แต่ฉันไม่มีชื่อเสียงพอที่จะทำเช่นนั้น>.>

นี่คือสิ่งที่ใช้งานได้สำหรับฉัน (เกิดขึ้นหลังจากอ้างอิงเอกสาร):

package_data={
    'mypkg': ['../*.txt']
},

include_package_data: False

บรรทัดสุดท้ายนั้นแปลกมากและสำคัญสำหรับฉันเช่นกัน (คุณสามารถละเว้นอาร์กิวเมนต์คำหลักนี้ - มันใช้งานได้เหมือนกัน)

สิ่งนี้จะทำอย่างไรก็อปปี้ไฟล์ข้อความทั้งหมดในไดเรกทอรีระดับบนสุดหรือรูทไดเร็กทอรี (หนึ่งระดับขึ้นจากแพ็คเกจที่mypkgคุณต้องการแจกจ่าย)

หวังว่านี่จะช่วยได้!


ฉันกำลังมองหาวิธีที่จะไม่ต้องสร้างสิ่งMANIFEST.inนี้ได้ผลสำหรับฉัน บรรทัดสุดท้ายก็สำคัญสำหรับฉันเช่นกัน สายของฉันถูกinclude_package_data=False, package_data={ "": ["../CHANGELOG.md"] },
Mendhak

7

ขั้นตอนที่ 1:สร้างMANIFEST.inไฟล์ในโฟลเดอร์เดียวกันด้วย setup.py

ขั้นตอนที่ 2:รวมเส้นทางสัมพันธ์ไปยังไฟล์ที่คุณต้องการเพิ่มMANIFEST.in

include README.rst
include docs/*.txt
include funniest/data.json

ขั้นตอนที่ 3:ตั้งค่าinclude_package_data=Trueในsetup()ฟังก์ชันเพื่อคัดลอกไฟล์เหล่านี้ไปยังไซต์แพ็คเกจ

การอ้างอิงอยู่ที่นี่


6

มันคือ 2019 และนี่คือสิ่งที่ทำงาน - แม้จะมีคำแนะนำที่นี่และที่นั่นสิ่งที่ฉันพบบนอินเทอร์เน็ตมีการใช้เอกสารครึ่งทางsetuptools_scmผ่านเป็นตัวเลือกsetuptools.setupจ่ายบอลสำเร็จเป็นตัวเลือกในการ ซึ่งจะรวมถึงไฟล์ข้อมูลใด ๆ ที่เป็นเวอร์ชันใน VCS ของคุณไม่ว่าจะเป็นคอมไพล์หรืออื่น ๆ ไปยังแพ็คเกจล้อและจะทำการ "ติดตั้ง pip" จากที่เก็บ git เพื่อนำไฟล์เหล่านั้นไปด้วย

ดังนั้นฉันเพิ่งเพิ่มสองบรรทัดนี้ในการเรียกใช้การตั้งค่าที่ "setup.py" ไม่จำเป็นต้องติดตั้งหรือนำเข้าเพิ่มเติม:

    setup_requires=['setuptools_scm'],
    include_package_data=True,

ไม่จำเป็นต้องแสดงรายการ package_data ด้วยตนเองหรือในไฟล์ MANIFEST.in - หากเป็นเวอร์ชั่นจะรวมอยู่ในแพ็คเกจ เอกสารใน "setuptools_scm" ให้ความสำคัญกับการสร้างหมายเลขเวอร์ชันจากตำแหน่งการส่งมอบและไม่สนใจส่วนที่สำคัญจริงๆของการเพิ่มไฟล์ข้อมูล (ฉันไม่สนหรอกถ้าไฟล์วงล้อกลางของฉันชื่อ "* 0.2.2.dev45 + g3495a1f" หรือจะใช้หมายเลขเวอร์ชัน hardcoded "0.3.0dev0" ที่ฉันได้พิมพ์ - แต่ปล่อยให้ไฟล์สำคัญสำหรับโปรแกรม การทำงานเบื้องหลังเป็นสิ่งสำคัญ)


5

ใน setup.py ภายใต้การตั้งค่า (:

setup(
   name = 'foo library'
   ...
  package_data={
   'foolibrary.folderA': ['*'],     # All files from folder A
   'foolibrary.folderB': ['*.txt']  #All text files from folder B
   },

1
สิ่งนี้ไม่ได้ทำอะไรเพื่อบรรลุเป้าหมายของ OP สิ่งที่คุณเขียนpackage_dataจะไม่มีผลกับสิ่งsetup.py installใดนอกจากคุณจะแก้ไขคำสั่งติดตั้งเอง ยกเว้นว่าไฟล์เหล่านั้นอยู่ภายใต้ไดเรกทอรีแพ็คเกจซึ่งมักเป็นสิ่งที่คุณต้องการหลีกเลี่ยง
wvxvw

3

นี่เป็นคำตอบที่ง่ายกว่าสำหรับฉัน

ก่อนอื่นตามความเห็นของ Python Dev ข้างต้นไม่จำเป็นต้องใช้ setuptools:

package_data is also available to pure distutils setup scripts 
since 2.3.  Éric Araujo

เยี่ยมมากเพราะการวางข้อกำหนด setuptools ลงบนแพ็คเกจของคุณหมายความว่าคุณจะต้องติดตั้งด้วย ในระยะสั้น:

from distutils.core import setup

setup(
    # ...snip...
    packages          = ['pkgname'],
    package_data      = {'pkgname': ['license.txt']},
)

1
มันจะบ่นไดเรกทอรีpkgameไม่มีอยู่
Anthony Kong

1

ฉันแค่อยากติดตามบางสิ่งที่ฉันพบว่าทำงานกับ Python 2.7 บน Centos 6 การเพิ่ม package_data หรือ data_files ตามที่กล่าวไว้ข้างต้นไม่ได้ผลสำหรับฉัน ฉันเพิ่ม MANIFEST.IN พร้อมไฟล์ที่ฉันต้องการซึ่งวางไฟล์ที่ไม่ใช่ไพ ธ อนลงใน tarball แต่ไม่ได้ติดตั้งไว้ในเครื่องเป้าหมายผ่าน RPM

ในที่สุดฉันก็สามารถนำไฟล์เข้าสู่โซลูชันของฉันโดยใช้ "ตัวเลือก" ในการตั้งค่า / setuptools ไฟล์ตัวเลือกให้คุณแก้ไขส่วนต่าง ๆ ของไฟล์ข้อมูลจำเพาะจาก setup.py ดังนี้

from setuptools import setup


setup(
    name='theProjectName',
    version='1',
    packages=['thePackage'],
    url='',
    license='',
    author='me',
    author_email='me@email.com',
    description='',
    options={'bdist_rpm': {'install_script': 'filewithinstallcommands'}},
)

ไฟล์ - MANIFEST.in:

include license.txt

ไฟล์ - คำสั่ง filewithinstall:

mkdir -p $RPM_BUILD_ROOT/pathtoinstall/
#this line installs your python files
python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES
#install license.txt into /pathtoinstall folder
install -m 700 license.txt $RPM_BUILD_ROOT/pathtoinstall/
echo /pathtoinstall/license.txt >> INSTALLED_FILES

-12

คิดวิธีแก้ปัญหา: ฉันเปลี่ยนชื่อฉันlgpl2.1_license.txtเป็นlgpl2.1_license.txt.pyและใส่เครื่องหมายคำพูดสามรอบข้อความ ตอนนี้ฉันไม่จำเป็นต้องใช้data_filesตัวเลือกหรือเพื่อระบุเส้นทางที่แน่นอน ทำให้เป็นโมดูล Python น่าเกลียดฉันรู้ แต่ฉันคิดว่ามันน่าเกลียดน้อยกว่าการระบุพา ธ สัมบูรณ์


7
ดูโพสต์ของฉัน มันไม่จำเป็นต้องน่าเกลียด มันยากที่จะหาตัวอย่างที่ดีในเน็ตเพราะเอกสารที่ดีสำหรับแพ็คเกจการติดตั้งหายาก
Evan Plaice
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.