forked from pool/micropython-lib
Accepting request 1253088 from home:dheidler:branches:devel:languages:python
- Update to 1.24.1 - Align build process to upstream * Upstream removed the old Makefile and restructured the repo * Build package index locally * Install all packages from local index using mip * Backport access_index_from_local_fs.patch to avoid using http for serving the index OBS-URL: https://build.opensuse.org/request/show/1253088 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/micropython-lib?expand=0&rev=4
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
<multibuild>
|
||||
<package>testsuite</package>
|
||||
</multibuild>
|
||||
60
access_index_from_local_fs.patch
Normal file
60
access_index_from_local_fs.patch
Normal file
@@ -0,0 +1,60 @@
|
||||
From 65adc276a3e76e51b8c9c6770a2611679e607f3f Mon Sep 17 00:00:00 2001
|
||||
From: Dominik Heidler <dheidler@suse.de>
|
||||
Date: Fri, 14 Mar 2025 14:01:39 +0100
|
||||
Subject: [PATCH] micropython/mip: Allow accessing index from local filesystem.
|
||||
|
||||
Just set the index to `file://relative/path` or `file:///absolute/path`.
|
||||
|
||||
Signed-off-by: Dominik Heidler <dheidler@suse.de>
|
||||
---
|
||||
micropython/mip/mip/__init__.py | 29 +++++++++++++++++++++--------
|
||||
1 file changed, 21 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/micropython/mip/mip/__init__.py b/micropython/mip/mip/__init__.py
|
||||
index 8920ad8f4..4b4a8c79f 100644
|
||||
--- a/micropython/mip/mip/__init__.py
|
||||
+++ b/micropython/mip/mip/__init__.py
|
||||
@@ -91,6 +91,14 @@ def _rewrite_url(url, branch=None):
|
||||
|
||||
|
||||
def _download_file(url, dest):
|
||||
+ if url.startswith("file://"):
|
||||
+ print("Copying:", dest)
|
||||
+ src = url[7:]
|
||||
+ _ensure_path_exists(dest)
|
||||
+ with open(src, "b") as sf:
|
||||
+ with open(dest, "wb") as f:
|
||||
+ _chunk(sf, f.write)
|
||||
+ return True
|
||||
response = requests.get(url)
|
||||
try:
|
||||
if response.status_code != 200:
|
||||
@@ -108,15 +116,20 @@ def _download_file(url, dest):
|
||||
|
||||
|
||||
def _install_json(package_json_url, index, target, version, mpy):
|
||||
- response = requests.get(_rewrite_url(package_json_url, version))
|
||||
- try:
|
||||
- if response.status_code != 200:
|
||||
- print("Package not found:", package_json_url)
|
||||
- return False
|
||||
+ if package_json_url.startswith("file://"):
|
||||
+ import json
|
||||
|
||||
- package_json = response.json()
|
||||
- finally:
|
||||
- response.close()
|
||||
+ package_json = json.load(open(package_json_url[7:]))
|
||||
+ else:
|
||||
+ response = requests.get(_rewrite_url(package_json_url, version))
|
||||
+ try:
|
||||
+ if response.status_code != 200:
|
||||
+ print("Package not found:", package_json_url)
|
||||
+ return False
|
||||
+
|
||||
+ package_json = response.json()
|
||||
+ finally:
|
||||
+ response.close()
|
||||
for target_path, short_hash in package_json.get("hashes", ()):
|
||||
fs_target_path = target + "/" + target_path
|
||||
if _check_exists(fs_target_path, short_hash):
|
||||
655
manifestfile.py
Normal file
655
manifestfile.py
Normal file
@@ -0,0 +1,655 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# This file is part of the MicroPython project, http://micropython.org/
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2022 Jim Mussared
|
||||
# Copyright (c) 2019 Damien P. George
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
from __future__ import print_function
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import tempfile
|
||||
from collections import namedtuple
|
||||
|
||||
__all__ = ["ManifestFileError", "ManifestFile"]
|
||||
|
||||
# Allow freeze*() etc.
|
||||
MODE_FREEZE = 1
|
||||
# Only allow include/require/module/package.
|
||||
MODE_COMPILE = 2
|
||||
# Same as compile, but handles require(..., pypi="name") as a requirements.txt entry.
|
||||
MODE_PYPROJECT = 3
|
||||
|
||||
# In compile mode, .py -> KIND_COMPILE_AS_MPY
|
||||
# In freeze mode, .py -> KIND_FREEZE_AS_MPY, .mpy->KIND_FREEZE_MPY
|
||||
KIND_AUTO = 1
|
||||
# Freeze-mode only, .py -> KIND_FREEZE_AS_MPY, .mpy->KIND_FREEZE_MPY
|
||||
KIND_FREEZE_AUTO = 2
|
||||
|
||||
# Freeze-mode only, The .py file will be frozen as text.
|
||||
KIND_FREEZE_AS_STR = 3
|
||||
# Freeze-mode only, The .py file will be compiled and frozen as bytecode.
|
||||
KIND_FREEZE_AS_MPY = 4
|
||||
# Freeze-mode only, The .mpy file will be frozen directly.
|
||||
KIND_FREEZE_MPY = 5
|
||||
# Compile mode only, the .py file should be compiled to .mpy.
|
||||
KIND_COMPILE_AS_MPY = 6
|
||||
|
||||
# File on the local filesystem.
|
||||
FILE_TYPE_LOCAL = 1
|
||||
# URL to file. (TODO)
|
||||
FILE_TYPE_HTTP = 2
|
||||
|
||||
# Default list of libraries in micropython-lib to search for library packages.
|
||||
BASE_LIBRARY_NAMES = ("micropython", "python-stdlib", "python-ecosys")
|
||||
|
||||
|
||||
class ManifestFileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ManifestIgnoreException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ManifestUsePyPIException(Exception):
|
||||
def __init__(self, pypi_name):
|
||||
self.pypi_name = pypi_name
|
||||
|
||||
|
||||
# The set of files that this manifest references.
|
||||
ManifestOutput = namedtuple(
|
||||
"ManifestOutput",
|
||||
[
|
||||
"file_type", # FILE_TYPE_*.
|
||||
"full_path", # The input file full path.
|
||||
"target_path", # The target path on the device.
|
||||
"timestamp", # Last modified date of the input file.
|
||||
"kind", # KIND_*.
|
||||
"metadata", # Metadata for the containing package.
|
||||
"opt", # Optimisation level (or None).
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# Represents the metadata for a package.
|
||||
class ManifestPackageMetadata:
|
||||
def __init__(self, is_require=False):
|
||||
self._is_require = is_require
|
||||
self._initialised = False
|
||||
|
||||
self.version = None
|
||||
self.description = None
|
||||
self.license = None
|
||||
self.author = None
|
||||
|
||||
# Annotate a package as being from the python standard library.
|
||||
self.stdlib = False
|
||||
|
||||
# Allows a python-ecosys package to be annotated with the
|
||||
# corresponding name in PyPI. e.g. micropython-lib/requests is based
|
||||
# on pypi/requests.
|
||||
self.pypi = None
|
||||
# For a micropython package, this is the name that we will publish it
|
||||
# to PyPI as. e.g. micropython-lib/senml publishes as
|
||||
# pypi/micropython-senml.
|
||||
self.pypi_publish = None
|
||||
|
||||
def update(
|
||||
self,
|
||||
mode,
|
||||
description=None,
|
||||
version=None,
|
||||
license=None,
|
||||
author=None,
|
||||
stdlib=False,
|
||||
pypi=None,
|
||||
pypi_publish=None,
|
||||
):
|
||||
if self._initialised:
|
||||
raise ManifestFileError("Duplicate call to metadata().")
|
||||
|
||||
# In MODE_PYPROJECT, if this manifest is being evaluated as a result
|
||||
# of a require(), then figure out if it should be replaced by a PyPI
|
||||
# dependency instead.
|
||||
if mode == MODE_PYPROJECT and self._is_require:
|
||||
if stdlib:
|
||||
# No dependency required at all for CPython.
|
||||
raise ManifestIgnoreException
|
||||
if pypi_publish or pypi:
|
||||
# In the case where a package is both based on a PyPI package and
|
||||
# provides one, preference depending on the published one.
|
||||
# (This should be pretty rare).
|
||||
raise ManifestUsePyPIException(pypi_publish or pypi)
|
||||
|
||||
self.description = description
|
||||
self.version = version
|
||||
self.license = license
|
||||
self.author = author
|
||||
self.pypi = pypi
|
||||
self.pypi_publish = pypi_publish
|
||||
self._initialised = True
|
||||
|
||||
def check_initialised(self, mode):
|
||||
# Ensure that metadata() is the first thing a manifest.py does.
|
||||
# This is to ensure that we early-exit if it should be replaced by a pypi dependency.
|
||||
if mode in (MODE_COMPILE, MODE_PYPROJECT):
|
||||
if not self._initialised:
|
||||
raise ManifestFileError("metadata() must be the first command in a manifest file.")
|
||||
|
||||
def __str__(self):
|
||||
return "version={} description={} license={} author={} pypi={} pypi_publish={}".format(
|
||||
self.version, self.description, self.license, self.author, self.pypi, self.pypi_publish
|
||||
)
|
||||
|
||||
|
||||
# Turns a dict of options into a object with attributes used to turn the
|
||||
# kwargs passed to include() and require into the "options" global in the
|
||||
# included manifest.
|
||||
# options = IncludeOptions(foo="bar", blah="stuff")
|
||||
# options.foo # "bar"
|
||||
# options.blah # "stuff"
|
||||
class IncludeOptions:
|
||||
def __init__(self, **kwargs):
|
||||
self._kwargs = kwargs
|
||||
self._defaults = {}
|
||||
|
||||
def defaults(self, **kwargs):
|
||||
self._defaults = kwargs
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._kwargs.get(name, self._defaults.get(name, None))
|
||||
|
||||
|
||||
class ManifestFile:
|
||||
def __init__(self, mode, path_vars=None):
|
||||
# See MODE_* constants above.
|
||||
self._mode = mode
|
||||
# Path substitution variables.
|
||||
self._path_vars = path_vars or {}
|
||||
# List of files (as ManifestFileResult) references by this manifest.
|
||||
self._manifest_files = []
|
||||
# List of PyPI dependencies (when mode=MODE_PYPROJECT).
|
||||
self._pypi_dependencies = []
|
||||
# Don't allow including the same file twice.
|
||||
self._visited = set()
|
||||
# Stack of metadata for each level.
|
||||
self._metadata = [ManifestPackageMetadata()]
|
||||
# Registered external libraries.
|
||||
self._libraries = {}
|
||||
# List of directories to search for packages.
|
||||
self._library_dirs = []
|
||||
# Add default micropython-lib libraries if $(MPY_LIB_DIR) has been specified.
|
||||
if self._path_vars["MPY_LIB_DIR"]:
|
||||
for lib in BASE_LIBRARY_NAMES:
|
||||
self.add_library(lib, os.path.join("$(MPY_LIB_DIR)", lib))
|
||||
|
||||
def _resolve_path(self, path):
|
||||
# Convert path to an absolute path, applying variable substitutions.
|
||||
for name, value in self._path_vars.items():
|
||||
if value is not None:
|
||||
path = path.replace("$({})".format(name), value)
|
||||
return os.path.abspath(path)
|
||||
|
||||
def _manifest_globals(self, kwargs):
|
||||
# This is the "API" available to a manifest file.
|
||||
g = {
|
||||
"metadata": self.metadata,
|
||||
"include": self.include,
|
||||
"require": self.require,
|
||||
"add_library": self.add_library,
|
||||
"package": self.package,
|
||||
"module": self.module,
|
||||
"options": IncludeOptions(**kwargs),
|
||||
}
|
||||
|
||||
# Extra legacy functions only for freeze mode.
|
||||
if self._mode == MODE_FREEZE:
|
||||
g.update(
|
||||
{
|
||||
"freeze": self.freeze,
|
||||
"freeze_as_str": self.freeze_as_str,
|
||||
"freeze_as_mpy": self.freeze_as_mpy,
|
||||
"freeze_mpy": self.freeze_mpy,
|
||||
}
|
||||
)
|
||||
|
||||
return g
|
||||
|
||||
def files(self):
|
||||
return self._manifest_files
|
||||
|
||||
def pypi_dependencies(self):
|
||||
# In MODE_PYPROJECT, this will return a list suitable for requirements.txt.
|
||||
return self._pypi_dependencies
|
||||
|
||||
def execute(self, manifest_file):
|
||||
if manifest_file.endswith(".py"):
|
||||
# Execute file from filesystem.
|
||||
self.include(manifest_file)
|
||||
else:
|
||||
# Execute manifest code snippet.
|
||||
try:
|
||||
exec(manifest_file, self._manifest_globals({}))
|
||||
except Exception as er:
|
||||
raise ManifestFileError("Error in manifest: {}".format(er))
|
||||
|
||||
def _add_file(self, full_path, target_path, kind=KIND_AUTO, opt=None):
|
||||
# Check file exists and get timestamp.
|
||||
try:
|
||||
stat = os.stat(full_path)
|
||||
timestamp = stat.st_mtime
|
||||
except OSError:
|
||||
raise ManifestFileError("Cannot stat {}".format(full_path))
|
||||
|
||||
# Map the AUTO kinds to their actual kind based on mode and extension.
|
||||
_, ext = os.path.splitext(full_path)
|
||||
if self._mode == MODE_FREEZE:
|
||||
if kind in (
|
||||
KIND_AUTO,
|
||||
KIND_FREEZE_AUTO,
|
||||
):
|
||||
if ext.lower() == ".py":
|
||||
kind = KIND_FREEZE_AS_MPY
|
||||
elif ext.lower() == ".mpy":
|
||||
kind = KIND_FREEZE_MPY
|
||||
else:
|
||||
if kind != KIND_AUTO:
|
||||
raise ManifestFileError("Not in freeze mode")
|
||||
if ext.lower() != ".py":
|
||||
raise ManifestFileError("Expected .py file")
|
||||
kind = KIND_COMPILE_AS_MPY
|
||||
|
||||
self._manifest_files.append(
|
||||
ManifestOutput(
|
||||
FILE_TYPE_LOCAL, full_path, target_path, timestamp, kind, self._metadata[-1], opt
|
||||
)
|
||||
)
|
||||
|
||||
def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=False):
|
||||
base_path = self._resolve_path(base_path)
|
||||
|
||||
if files is not None:
|
||||
# Use explicit list of files (relative to package_path).
|
||||
for file in files:
|
||||
if package_path:
|
||||
file = os.path.join(package_path, file)
|
||||
self._add_file(os.path.join(base_path, file), file, kind=kind, opt=opt)
|
||||
else:
|
||||
if base_path:
|
||||
prev_cwd = os.getcwd()
|
||||
os.chdir(self._resolve_path(base_path))
|
||||
|
||||
# Find all candidate files.
|
||||
for dirpath, _, filenames in os.walk(package_path or ".", followlinks=True):
|
||||
for file in filenames:
|
||||
file = os.path.relpath(os.path.join(dirpath, file), ".")
|
||||
_, ext = os.path.splitext(file)
|
||||
if ext.lower() in exts:
|
||||
self._add_file(
|
||||
os.path.join(base_path, file),
|
||||
file,
|
||||
kind=kind,
|
||||
opt=opt,
|
||||
)
|
||||
elif strict:
|
||||
raise ManifestFileError("Unexpected file type")
|
||||
|
||||
if base_path:
|
||||
os.chdir(prev_cwd)
|
||||
|
||||
def metadata(self, **kwargs):
|
||||
"""
|
||||
From within a manifest file, use this to set the metadata for the
|
||||
package described by current manifest.
|
||||
|
||||
After executing a manifest file (via execute()), call this
|
||||
to obtain the metadata for the top-level manifest file.
|
||||
|
||||
See ManifestPackageMetadata.update() for valid kwargs.
|
||||
"""
|
||||
if kwargs:
|
||||
self._metadata[-1].update(self._mode, **kwargs)
|
||||
return self._metadata[-1]
|
||||
|
||||
def include(self, manifest_path, is_require=False, **kwargs):
|
||||
"""
|
||||
Include another manifest.
|
||||
|
||||
The manifest argument can be a string (filename) or an iterable of
|
||||
strings.
|
||||
|
||||
Relative paths are resolved with respect to the current manifest file.
|
||||
|
||||
If the path is to a directory, then it implicitly includes the
|
||||
manifest.py file inside that directory.
|
||||
|
||||
Optional kwargs can be provided which will be available to the
|
||||
included script via the `options` variable.
|
||||
|
||||
e.g. include("path.py", extra_features=True)
|
||||
|
||||
in path.py:
|
||||
options.defaults(standard_features=True)
|
||||
|
||||
# freeze minimal modules.
|
||||
if options.standard_features:
|
||||
# freeze standard modules.
|
||||
if options.extra_features:
|
||||
# freeze extra modules.
|
||||
"""
|
||||
if is_require:
|
||||
self._metadata[-1].check_initialised(self._mode)
|
||||
|
||||
if not isinstance(manifest_path, str):
|
||||
for m in manifest_path:
|
||||
self.include(m, **kwargs)
|
||||
else:
|
||||
manifest_path = self._resolve_path(manifest_path)
|
||||
# Including a directory grabs the manifest.py inside it.
|
||||
if os.path.isdir(manifest_path):
|
||||
manifest_path = os.path.join(manifest_path, "manifest.py")
|
||||
if manifest_path in self._visited:
|
||||
return
|
||||
self._visited.add(manifest_path)
|
||||
if is_require:
|
||||
# This include is the result of require("name"), so push a new
|
||||
# package metadata onto the stack.
|
||||
self._metadata.append(ManifestPackageMetadata(is_require=True))
|
||||
try:
|
||||
with open(manifest_path) as f:
|
||||
# Make paths relative to this manifest file while processing it.
|
||||
# Applies to includes and input files.
|
||||
prev_cwd = os.getcwd()
|
||||
os.chdir(os.path.dirname(manifest_path))
|
||||
try:
|
||||
exec(f.read(), self._manifest_globals(kwargs))
|
||||
finally:
|
||||
os.chdir(prev_cwd)
|
||||
except ManifestIgnoreException:
|
||||
# e.g. MODE_PYPROJECT and this was a stdlib dependency. No-op.
|
||||
pass
|
||||
except ManifestUsePyPIException as e:
|
||||
# e.g. MODE_PYPROJECT and this was a package from
|
||||
# python-ecosys. Add PyPI dependency instead.
|
||||
self._pypi_dependencies.append(e.pypi_name)
|
||||
except Exception as e:
|
||||
raise ManifestFileError("Error in manifest file: {}: {}".format(manifest_path, e))
|
||||
if is_require:
|
||||
self._metadata.pop()
|
||||
|
||||
def _require_from_path(self, library_path, name, version, extra_kwargs):
|
||||
for root, dirnames, filenames in os.walk(library_path):
|
||||
if os.path.basename(root) == name and "manifest.py" in filenames:
|
||||
self.include(root, is_require=True, **extra_kwargs)
|
||||
return True
|
||||
return False
|
||||
|
||||
def require(self, name, version=None, pypi=None, library=None, **kwargs):
|
||||
"""
|
||||
Require a package by name from micropython-lib.
|
||||
|
||||
Optionally specify pipy="package-name" to indicate that this should
|
||||
use the named package from PyPI when building for CPython.
|
||||
|
||||
Optionally specify library="name" to reference a package from a
|
||||
library that has been previously registered with add_library(). Otherwise
|
||||
the list of library paths will be used.
|
||||
"""
|
||||
self._metadata[-1].check_initialised(self._mode)
|
||||
|
||||
if self._mode == MODE_PYPROJECT and pypi:
|
||||
# In PYPROJECT mode, allow overriding the PyPI dependency name
|
||||
# explicitly. Otherwise if the dependent package has metadata
|
||||
# (pypi_publish) or metadata(pypi) we will use that.
|
||||
self._pypi_dependencies.append(pypi)
|
||||
return
|
||||
|
||||
if library is not None:
|
||||
# Find package in external library.
|
||||
if library not in self._libraries:
|
||||
raise ValueError("Unknown library '{}' for require('{}').".format(library, name))
|
||||
library_path = self._libraries[library]
|
||||
# Search for {library_path}/**/{name}/manifest.py.
|
||||
if self._require_from_path(library_path, name, version, kwargs):
|
||||
return
|
||||
raise ValueError(
|
||||
"Package '{}' not found in external library '{}' ({}).".format(
|
||||
name, library, library_path
|
||||
)
|
||||
)
|
||||
|
||||
for lib_dir in self._library_dirs:
|
||||
# Search for {lib_dir}/**/{name}/manifest.py.
|
||||
if self._require_from_path(lib_dir, name, version, kwargs):
|
||||
return
|
||||
|
||||
raise ValueError("Package '{}' not found in any known library.".format(name))
|
||||
|
||||
def add_library(self, library, library_path, prepend=False):
|
||||
"""
|
||||
Register the path to an external named library.
|
||||
|
||||
The path will be automatically searched when using require(). By default the
|
||||
added library is added to the end of the list of libraries to search. Pass
|
||||
`prepend=True` to add it to the start of the list.
|
||||
|
||||
Additionally, the added library can be explicitly requested by using
|
||||
`require("name", library="library")`.
|
||||
"""
|
||||
library_path = self._resolve_path(library_path)
|
||||
self._libraries[library] = library_path
|
||||
self._library_dirs.insert(0 if prepend else len(self._library_dirs), library_path)
|
||||
|
||||
def package(self, package_path, files=None, base_path=".", opt=None):
|
||||
"""
|
||||
Define a package, optionally restricting to a set of files.
|
||||
|
||||
Simple case, a package in the current directory:
|
||||
package("foo")
|
||||
will include all .py files in foo, and will be stored as foo/bar/baz.py.
|
||||
|
||||
If the package isn't in the current directory, use base_path:
|
||||
package("foo", base_path="src")
|
||||
|
||||
To restrict to certain files in the package use files (note: paths should be relative to the package):
|
||||
package("foo", files=["bar/baz.py"])
|
||||
"""
|
||||
self._metadata[-1].check_initialised(self._mode)
|
||||
|
||||
# Include "base_path/package_path/**/*.py" --> "package_path/**/*.py"
|
||||
self._search(base_path, package_path, files, exts=(".py",), kind=KIND_AUTO, opt=opt)
|
||||
|
||||
def module(self, module_path, base_path=".", opt=None):
|
||||
"""
|
||||
Include a single Python file as a module.
|
||||
|
||||
If the file is in the current directory:
|
||||
module("foo.py")
|
||||
|
||||
Otherwise use base_path to locate the file:
|
||||
module("foo.py", "src/drivers")
|
||||
"""
|
||||
self._metadata[-1].check_initialised(self._mode)
|
||||
|
||||
# Include "base_path/module_path" --> "module_path"
|
||||
base_path = self._resolve_path(base_path)
|
||||
_, ext = os.path.splitext(module_path)
|
||||
if ext.lower() != ".py":
|
||||
raise ManifestFileError("module must be .py file")
|
||||
# TODO: version None
|
||||
self._add_file(os.path.join(base_path, module_path), module_path, opt=opt)
|
||||
|
||||
def _freeze_internal(self, path, script, exts, kind, opt):
|
||||
if script is None:
|
||||
self._search(path, None, None, exts=exts, kind=kind, opt=opt)
|
||||
elif isinstance(script, str) and os.path.isdir(os.path.join(path, script)):
|
||||
self._search(path, script, None, exts=exts, kind=kind, opt=opt)
|
||||
elif not isinstance(script, str):
|
||||
self._search(path, None, script, exts=exts, kind=kind, opt=opt)
|
||||
else:
|
||||
self._search(path, None, (script,), exts=exts, kind=kind, opt=opt)
|
||||
|
||||
def freeze(self, path, script=None, opt=None):
|
||||
"""
|
||||
Freeze the input, automatically determining its type. A .py script
|
||||
will be compiled to a .mpy first then frozen, and a .mpy file will be
|
||||
frozen directly.
|
||||
|
||||
`path` must be a directory, which is the base directory to _search for
|
||||
files from. When importing the resulting frozen modules, the name of
|
||||
the module will start after `path`, ie `path` is excluded from the
|
||||
module name.
|
||||
|
||||
If `path` is relative, it is resolved to the current manifest.py.
|
||||
Use $(MPY_DIR), $(MPY_LIB_DIR), $(PORT_DIR), $(BOARD_DIR) if you need
|
||||
to access specific paths.
|
||||
|
||||
If `script` is None all files in `path` will be frozen.
|
||||
|
||||
If `script` is an iterable then freeze() is called on all items of the
|
||||
iterable (with the same `path` and `opt` passed through).
|
||||
|
||||
If `script` is a string then it specifies the file or directory to
|
||||
freeze, and can include extra directories before the file or last
|
||||
directory. The file or directory will be _searched for in `path`. If
|
||||
`script` is a directory then all files in that directory will be frozen.
|
||||
|
||||
`opt` is the optimisation level to pass to mpy-cross when compiling .py
|
||||
to .mpy.
|
||||
"""
|
||||
self._freeze_internal(
|
||||
path,
|
||||
script,
|
||||
exts=(
|
||||
".py",
|
||||
".mpy",
|
||||
),
|
||||
kind=KIND_FREEZE_AUTO,
|
||||
opt=opt,
|
||||
)
|
||||
|
||||
def freeze_as_str(self, path):
|
||||
"""
|
||||
Freeze the given `path` and all .py scripts within it as a string,
|
||||
which will be compiled upon import.
|
||||
"""
|
||||
self._search(path, None, None, exts=(".py",), kind=KIND_FREEZE_AS_STR)
|
||||
|
||||
def freeze_as_mpy(self, path, script=None, opt=None):
|
||||
"""
|
||||
Freeze the input (see above) by first compiling the .py scripts to
|
||||
.mpy files, then freezing the resulting .mpy files.
|
||||
"""
|
||||
self._freeze_internal(path, script, exts=(".py",), kind=KIND_FREEZE_AS_MPY, opt=opt)
|
||||
|
||||
def freeze_mpy(self, path, script=None, opt=None):
|
||||
"""
|
||||
Freeze the input (see above), which must be .mpy files that are
|
||||
frozen directly.
|
||||
"""
|
||||
self._freeze_internal(path, script, exts=(".mpy",), kind=KIND_FREEZE_MPY, opt=opt)
|
||||
|
||||
|
||||
# Generate a temporary file with a line appended to the end that adds __version__.
|
||||
@contextlib.contextmanager
|
||||
def tagged_py_file(path, metadata):
|
||||
dest_fd, dest_path = tempfile.mkstemp(suffix=".py", text=True)
|
||||
try:
|
||||
with os.fdopen(dest_fd, "w") as dest:
|
||||
with open(path, "r") as src:
|
||||
contents = src.read()
|
||||
dest.write(contents)
|
||||
|
||||
# Don't overwrite a version definition if the file already has one in it.
|
||||
if metadata.version and "__version__ =" not in contents:
|
||||
dest.write("\n\n__version__ = {}\n".format(repr(metadata.version)))
|
||||
yield dest_path
|
||||
finally:
|
||||
os.unlink(dest_path)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
cmd_parser = argparse.ArgumentParser(description="List the files referenced by a manifest.")
|
||||
cmd_parser.add_argument("--freeze", action="store_true", help="freeze mode")
|
||||
cmd_parser.add_argument("--compile", action="store_true", help="compile mode")
|
||||
cmd_parser.add_argument("--pyproject", action="store_true", help="pyproject mode")
|
||||
cmd_parser.add_argument(
|
||||
"--lib",
|
||||
default=os.path.join(os.path.dirname(__file__), "../lib/micropython-lib"),
|
||||
help="path to micropython-lib repo",
|
||||
)
|
||||
cmd_parser.add_argument(
|
||||
"--unix-ffi", action="store_true", help="prepend unix-ffi to the library path"
|
||||
)
|
||||
cmd_parser.add_argument("--port", default=None, help="path to port dir")
|
||||
cmd_parser.add_argument("--board", default=None, help="path to board dir")
|
||||
cmd_parser.add_argument(
|
||||
"--top",
|
||||
default=os.path.join(os.path.dirname(__file__), ".."),
|
||||
help="path to micropython repo",
|
||||
)
|
||||
cmd_parser.add_argument("files", nargs="+", help="input manifest.py")
|
||||
args = cmd_parser.parse_args()
|
||||
|
||||
path_vars = {
|
||||
"MPY_DIR": os.path.abspath(args.top) if args.top else None,
|
||||
"BOARD_DIR": os.path.abspath(args.board) if args.board else None,
|
||||
"PORT_DIR": os.path.abspath(args.port) if args.port else None,
|
||||
"MPY_LIB_DIR": os.path.abspath(args.lib) if args.lib else None,
|
||||
}
|
||||
|
||||
mode = None
|
||||
if args.freeze:
|
||||
mode = MODE_FREEZE
|
||||
elif args.compile:
|
||||
mode = MODE_COMPILE
|
||||
elif args.pyproject:
|
||||
mode = MODE_PYPROJECT
|
||||
else:
|
||||
print("Error: No mode specified.", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
m = ManifestFile(mode, path_vars)
|
||||
if args.unix_ffi:
|
||||
m.add_library("unix-ffi", os.path.join("$(MPY_LIB_DIR)", "unix-ffi"), prepend=True)
|
||||
for manifest_file in args.files:
|
||||
try:
|
||||
m.execute(manifest_file)
|
||||
except ManifestFileError as er:
|
||||
print(er, file=sys.stderr)
|
||||
exit(1)
|
||||
print(m.metadata())
|
||||
for f in m.files():
|
||||
print(f)
|
||||
if mode == MODE_PYPROJECT:
|
||||
for r in m.pypi_dependencies():
|
||||
print("pypi-require:", r)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
3
micropython-lib-1.24.1.tar.gz
Normal file
3
micropython-lib-1.24.1.tar.gz
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4f779460853436a7547f2d994a2a4131792de98e5e882b271ecf6a73c649dbf9
|
||||
size 519815
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:66e15380eb109613263beb6825b8eecb9191088270c1a59e8c7d922dd57183c7
|
||||
size 289386
|
||||
@@ -1,3 +1,13 @@
|
||||
-------------------------------------------------------------------
|
||||
Fri Feb 7 11:48:45 UTC 2025 - Dominik Heidler <dheidler@suse.de>
|
||||
|
||||
- Update to 1.24.1
|
||||
- Align build process to upstream
|
||||
* Upstream removed the old Makefile and restructured the repo
|
||||
* Build package index locally
|
||||
* Install all packages from local index using mip
|
||||
* Backport access_index_from_local_fs.patch to avoid using http for serving the index
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Jan 11 00:39:13 UTC 2022 - Matej Cepl <mcepl@suse.com>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# spec file
|
||||
# spec file for package micropython-lib
|
||||
#
|
||||
# Copyright (c) 2022 SUSE LLC
|
||||
# Copyright (c) 2025 SUSE LLC
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
@@ -16,26 +16,22 @@
|
||||
#
|
||||
|
||||
|
||||
%define flavor @BUILD_FLAVOR@%{nil}
|
||||
%if "%{flavor}" != "testsuite"
|
||||
%define name_suffix %{nil}
|
||||
%else
|
||||
%define name_suffix -%{flavor}
|
||||
%endif
|
||||
Name: micropython-lib%{name_suffix}
|
||||
Version: 1.9.3
|
||||
Name: micropython-lib
|
||||
Version: 1.24.1
|
||||
Release: 0
|
||||
Summary: Core Python libraries ported to MicroPython
|
||||
License: MIT AND Python-2.0
|
||||
Group: Development/Libraries/Python
|
||||
URL: https://github.com/micropython/micropython-lib
|
||||
Source: https://github.com/micropython/micropython-lib/archive/v%{version}.tar.gz#/micropython-lib-%{version}.tar.gz
|
||||
Source1: https://raw.githubusercontent.com/micropython/micropython/refs/tags/v%{version}/tools/manifestfile.py
|
||||
Source2: https://raw.githubusercontent.com/micropython/micropython/refs/tags/v%{version}/mpy-cross/mpy_cross/__init__.py#/mpy_cross.py
|
||||
Patch1: access_index_from_local_fs.patch
|
||||
BuildRequires: fdupes
|
||||
BuildArch: noarch
|
||||
%if "%{flavor}" == "testsuite"
|
||||
BuildRequires: micropython
|
||||
BuildRequires: micropython-lib
|
||||
%endif
|
||||
BuildRequires: mpy-tools
|
||||
BuildRequires: python3
|
||||
|
||||
%description
|
||||
micropython-lib is a project to develop a non-monolothic standard library for MicroPython.
|
||||
@@ -43,34 +39,44 @@ Each module or package is available as a separate distribution package from PyPI
|
||||
Each module is either written from scratch or ported from CPython.
|
||||
|
||||
%prep
|
||||
%setup -q -n micropython-lib-%{version}
|
||||
%autosetup -p1 -n micropython-lib-%{version}
|
||||
|
||||
# prepare build.py dependencies
|
||||
cp %SOURCE1 ./tools/manifestfile.py
|
||||
mkdir tools/mpy_cross
|
||||
cp %SOURCE2 ./tools/mpy_cross/__init__.py
|
||||
|
||||
# bootstrap mip
|
||||
mkdir mip
|
||||
cp -r micropython/mip/mip/* mip/
|
||||
cp -r micropython/mip-cmdline/mip/* mip/
|
||||
|
||||
%build
|
||||
%if "%{flavor}" != "testsuite"
|
||||
%make_build
|
||||
%endif
|
||||
# build mip index
|
||||
python3 ./tools/build.py --mpy-cross %{_bindir}/mpy-cross --output ./mip_index
|
||||
|
||||
%install
|
||||
%if "%{flavor}" != "testsuite"
|
||||
%make_install PREFIX=%{buildroot}%{_prefix}/lib/micropython
|
||||
# install all pkgs from index
|
||||
for pkg in $(python3 -c 'import json;[print(p["name"]) for p in json.load(open("mip_index/index.json"))["packages"]]') ; do
|
||||
micropython -m mip install --no-mpy -t %{buildroot}%{_prefix}/lib/micropython -i file://mip_index "$pkg"
|
||||
done
|
||||
|
||||
# Fix rpm runtime dependency rpmlint error replace the shebang in all the scripts with %%{_bindir}/python3
|
||||
find %{buildroot}%{_prefix}/lib/micropython -name "*.py" -exec sed -i 's|#! %{_bindir}/env python3|#!%{_bindir}/python3|' {} ";"
|
||||
find %{buildroot}%{_prefix}/lib/micropython -name "*.py" -exec sed -i 's|#! %{_prefix}/local/bin/python|#!%{_bindir}/python3|' {} ";"
|
||||
# Fix permissions
|
||||
chmod 0755 %{buildroot}%{_prefix}/lib/micropython/{base64,cgi,keyword,quopri,timeit,uu}.py
|
||||
chmod 0755 %{buildroot}%{_prefix}/lib/micropython/{base64,keyword,quopri,uu}.py
|
||||
# Run fdupes
|
||||
%fdupes %{buildroot}%{_prefix}/lib/micropython
|
||||
%endif
|
||||
|
||||
%if "%{flavor}" == "testsuite"
|
||||
%check
|
||||
micropython -v ./test/test_pep380.py
|
||||
%endif
|
||||
# check that we can import a module that we just installed and that it actually works
|
||||
cd %{buildroot}%{_prefix}/lib/micropython
|
||||
micropython -c 'import hashlib; s=hashlib.sha384("foo").hexdigest(); assert s.startswith("98c11ffdf")'
|
||||
|
||||
%if "%{flavor}" != "testsuite"
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc README.md
|
||||
%{_prefix}/lib/micropython
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
|
||||
152
mpy_cross.py
Normal file
152
mpy_cross.py
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# This file is part of the MicroPython project, http://micropython.org/
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2022 Andrew Leech
|
||||
# Copyright (c) 2022 Jim Mussared
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
NATIVE_ARCHS = {
|
||||
"NATIVE_ARCH_NONE": "",
|
||||
"NATIVE_ARCH_X86": "x86",
|
||||
"NATIVE_ARCH_X64": "x64",
|
||||
"NATIVE_ARCH_ARMV6": "armv6",
|
||||
"NATIVE_ARCH_ARMV6M": "armv6m",
|
||||
"NATIVE_ARCH_ARMV7M": "armv7m",
|
||||
"NATIVE_ARCH_ARMV7EM": "armv7em",
|
||||
"NATIVE_ARCH_ARMV7EMSP": "armv7emsp",
|
||||
"NATIVE_ARCH_ARMV7EMDP": "armv7emdp",
|
||||
"NATIVE_ARCH_XTENSA": "xtensa",
|
||||
"NATIVE_ARCH_XTENSAWIN": "xtensawin",
|
||||
"NATIVE_ARCH_RV32IMC": "rv32imc",
|
||||
}
|
||||
|
||||
globals().update(NATIVE_ARCHS)
|
||||
|
||||
__all__ = ["version", "compile", "run", "CrossCompileError"] + list(NATIVE_ARCHS.keys())
|
||||
|
||||
|
||||
class CrossCompileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
_VERSION_RE = re.compile("mpy-cross emitting mpy v([0-9]+)(?:.([0-9]+))?")
|
||||
|
||||
|
||||
def _find_mpy_cross_binary(mpy_cross):
|
||||
if mpy_cross:
|
||||
return mpy_cross
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), "../build/mpy-cross"))
|
||||
|
||||
|
||||
def mpy_version(mpy_cross=None):
|
||||
"""
|
||||
Get the version and sub-version of the .mpy file format generated by this version of mpy-cross.
|
||||
|
||||
Returns: A tuple of `(mpy_version, mpy_sub_version)`
|
||||
Optional keyword arguments:
|
||||
- mpy_cross: Specific mpy-cross binary to use
|
||||
"""
|
||||
version_info = run(["--version"], mpy_cross=mpy_cross)
|
||||
match = re.search(_VERSION_RE, version_info)
|
||||
mpy_version, mpy_sub_version = int(match.group(1)), int(match.group(2) or "0")
|
||||
return (
|
||||
mpy_version,
|
||||
mpy_sub_version,
|
||||
)
|
||||
|
||||
|
||||
def compile(src, dest=None, src_path=None, opt=None, march=None, mpy_cross=None, extra_args=None):
|
||||
"""
|
||||
Compile the specified .py file with mpy-cross.
|
||||
|
||||
Returns: Standard output from mpy-cross as a string.
|
||||
|
||||
Required arguments:
|
||||
- src: The path to the .py file
|
||||
|
||||
Optional keyword arguments:
|
||||
- dest: The output .mpy file. Defaults to `src` (with .mpy extension)
|
||||
- src_path: The path to embed in the .mpy file (defaults to `src`)
|
||||
- opt: Optimisation level (0-3, default 0)
|
||||
- march: One of the `NATIVE_ARCH_*` constants (defaults to NATIVE_ARCH_NONE)
|
||||
- mpy_cross: Specific mpy-cross binary to use
|
||||
- extra_args: Additional arguments to pass to mpy-cross (e.g. `["-X", "emit=native"]`)
|
||||
"""
|
||||
if not src:
|
||||
raise ValueError("src is required")
|
||||
if not os.path.exists(src):
|
||||
raise CrossCompileError("Input .py file not found: {}.".format(src))
|
||||
|
||||
args = []
|
||||
|
||||
if src_path:
|
||||
args += ["-s", src_path]
|
||||
|
||||
if dest:
|
||||
args += ["-o", dest]
|
||||
|
||||
if march:
|
||||
args += ["-march=" + march]
|
||||
|
||||
if opt is not None:
|
||||
args += ["-O{}".format(opt)]
|
||||
|
||||
if extra_args:
|
||||
args += extra_args
|
||||
|
||||
args += [src]
|
||||
|
||||
run(args, mpy_cross)
|
||||
|
||||
|
||||
def run(args, mpy_cross=None):
|
||||
"""
|
||||
Run mpy-cross with the specified command line arguments.
|
||||
Prefer to use `compile()` instead.
|
||||
|
||||
Returns: Standard output from mpy-cross as a string.
|
||||
|
||||
Optional keyword arguments:
|
||||
- mpy_cross: Specific mpy-cross binary to use
|
||||
"""
|
||||
mpy_cross = _find_mpy_cross_binary(mpy_cross)
|
||||
|
||||
if not os.path.exists(mpy_cross):
|
||||
raise CrossCompileError("mpy-cross binary not found at {}.".format(mpy_cross))
|
||||
|
||||
try:
|
||||
st = os.stat(mpy_cross)
|
||||
os.chmod(mpy_cross, st.st_mode | stat.S_IEXEC)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return subprocess.check_output([mpy_cross] + args, stderr=subprocess.STDOUT).decode()
|
||||
except subprocess.CalledProcessError as er:
|
||||
raise CrossCompileError(er.output.decode())
|
||||
Reference in New Issue
Block a user