Skip to content

Make upip installable #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions sdist_upip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# This module is part of Pycopy https://github.com/pfalcon/pycopy
# and pycopy-lib https://github.com/pfalcon/pycopy-lib, projects to
# create a (very) lightweight full-stack Python distribution.
#
# Copyright (c) 2016-2019 Paul Sokolovsky
# Licence: MIT
#
# This module overrides distutils (also compatible with setuptools) "sdist"
# command to perform pre- and post-processing as required for Pycopy's
# upip package manager.
#
# Preprocessing steps:
# * Creation of Python resource module (R.py) from each top-level package's
# resources.
# Postprocessing steps:
# * Removing metadata files not used by upip (this includes setup.py)
# * Recompressing gzip archive with 4K dictionary size so it can be
# installed even on low-heap targets.
#
import sys
import os
import zlib
from subprocess import Popen, PIPE
import glob
import tarfile
import re
import io

from distutils.filelist import FileList
from setuptools.command.sdist import sdist as _sdist


def gzip_4k(inf, fname):
comp = zlib.compressobj(level=9, wbits=16 + 12)
with open(fname + ".out", "wb") as outf:
while 1:
data = inf.read(1024)
if not data:
break
outf.write(comp.compress(data))
outf.write(comp.flush())
os.rename(fname, fname + ".orig")
os.rename(fname + ".out", fname)


FILTERS = [
# include, exclude, repeat
(r".+\.egg-info/(PKG-INFO|requires\.txt)", r"setup.py$"),
(r".+\.py$", r"[^/]+$"),
(None, r".+\.egg-info/.+"),
]


outbuf = io.BytesIO()

def filter_tar(name):
fin = tarfile.open(name, "r:gz")
fout = tarfile.open(fileobj=outbuf, mode="w")
for info in fin:
# print(info)
if not "/" in info.name:
continue
fname = info.name.split("/", 1)[1]
include = None

for inc_re, exc_re in FILTERS:
if include is None and inc_re:
if re.match(inc_re, fname):
include = True

if include is None and exc_re:
if re.match(exc_re, fname):
include = False

if include is None:
include = True

if include:
print("including:", fname)
else:
print("excluding:", fname)
continue

farch = fin.extractfile(info)
fout.addfile(info, farch)
fout.close()
fin.close()


def make_resource_module(manifest_files):
resources = []
# Any non-python file included in manifest is resource
for fname in manifest_files:
ext = fname.rsplit(".", 1)
if len(ext) > 1:
ext = ext[1]
else:
ext = ""
if ext != "py":
resources.append(fname)

if resources:
print("creating resource module R.py")
resources.sort()
last_pkg = None
r_file = None
for fname in resources:
try:
pkg, res_name = fname.split("/", 1)
except ValueError:
print("not treating %s as a resource" % fname)
continue
if last_pkg != pkg:
last_pkg = pkg
if r_file:
r_file.write("}\n")
r_file.close()
r_file = open(pkg + "/R.py", "w")
r_file.write("R = {\n")

with open(fname, "rb") as f:
r_file.write("%r: %r,\n" % (res_name, f.read()))

if r_file:
r_file.write("}\n")
r_file.close()


class sdist(_sdist):

def run(self):
self.filelist = FileList()
self.get_file_list()
make_resource_module(self.filelist.files)

r = super().run()

assert len(self.archive_files) == 1
print("filtering files and recompressing with 4K dictionary")
filter_tar(self.archive_files[0])
outbuf.seek(0)
gzip_4k(outbuf, self.archive_files[0])

return r

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code quality here could be better.

unclear: is this the usual code used for this purpose / is there anything better?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also: do we need to bundle this or can we just pip install something to get this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the file as-is. Other than the comments at the topic referring to "pycopy-lib" the file is essentially the same as the file which was part of "micropython-lib". I would keep the file "as-is" (including to allow comparing it to the source in the future, if the source might have changed/improved).

About bundling: I searched hard to find an installable version of this. I didnt manage. I did find a number of micropython modules (e.g. micropython-umqtt.simple2) that do have a setup.py, which import sdist_upip, but that don't ship along that file. So there could be a way yet to install that file in a way I couldn't figure out. I did find one module that shipped the file along.

Interestingly micropython-umqtt.simple2 imports sdist_upip from the parent directory .. (see here), which doesn't feel very "installed". Instead it feels like the author cloned the "pycopy-lib" repo, which has sdist_upip in its root, and then created a subdirectory for his module (or copying the existing umqtt.simple dir), and following the same pattern as other modules in the "pycopy-lib" repo. All modules in that repo import sdist_upip from .. which is the root of that repo.

So to summarise: I could not find a place to pip install it from and others seem to also rely on the file from either micropython-lib or pycopy-lib, so I chose the bundling option.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I agree to use 3rd party code "as is" to simplify updates (if any).

If there is no alternative, this is as good as it gets.


# For testing only
if __name__ == "__main__":
filter_tar(sys.argv[1])
outbuf.seek(0)
gzip_4k(outbuf, sys.argv[1])
30 changes: 30 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pathlib import Path
from setuptools import setup
import sdist_upip


HERE = Path(__file__).parent
README = (HERE / 'README.rst').read_text()

VERSION = "1.0.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

over time, we'll need to improve how this is handled.

either by using a tool that updates versions everywhere (here, code, docs, ...) or by deriving version from git (for other "normal python" projects i use setuptools_scm).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying, it's ok to have it hard coded for now, and only improve later? (It might be good to not take on too much more before getting to v1.0.0)

But I like the approach of deriving the version from git. Especially when putting the build/publish into a Github Action, it would allow understanding where each build artifact actually came from. Also, given that this setup.py is run with python3 (the dependencies alone make it not run on MicroPython), I would expect setuptools_scm to work just fine (i.e. that there's nothing MicroPython specific here).

Let me know if you feel this should be addressed before launch of v1.0.0

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok for now.

not sure if setuptools_scm can be used, it is made for normal python code in a git repo.
we have half of that (== git) on the dev machine, but none of that on the target device.


setup(
name="micropython-py-esp32-ulp",
version=VERSION,
description="Assembler toolchain for the ESP32 ULP co-processor, written in MicroPython",
long_description=README,
long_description_content_type='text/x-rst',
url="https://github.com/ThomasWaldmann/py-esp32-ulp",
license="MIT",
author="py-esp32-ulp authors",
author_email="tw@waldmann-edv.de",
maintainer="py-esp32-ulp authors",
maintainer_email="tw@waldmann-edv.de",
classifiers=[
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: Implementation :: MicroPython',
],
platforms=["esp32", "linux", "darwin"],
cmdclass={"sdist": sdist_upip.sdist},
packages=["esp32_ulp"],
)