Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
db7a694
Add support for Nix package generation.
lopsided98 Apr 1, 2019
74d60c3
Simple performance optimizations based on profiling.
lopsided98 May 5, 2019
a21fc6b
nix: add tests and fix code style
lopsided98 May 5, 2019
67caba6
nix: update to account for upstream changes
lopsided98 Dec 8, 2019
fc4cc7a
nix: add ROS 2 support
lopsided98 Nov 7, 2019
ef493a8
Add ROS_PYTHON_VERSION to distro condition context.
lopsided98 Nov 8, 2019
43a9232
nix: update based on upstream changes
lopsided98 Dec 8, 2019
f34a42f
nix: fix submitting pull requests.
lopsided98 Dec 9, 2019
4bba1ab
nix: make dependency ordering deterministic
lopsided98 Dec 10, 2019
c5f4114
nix: put generated files in a subdirectory
lopsided98 Jan 18, 2020
b2c39c2
nix: fix downloading archives from GitLab
lopsided98 May 2, 2020
278cabf
nix: fix integration test ArgumentParser description
lopsided98 Aug 24, 2020
5746789
utils: ignore error removing downloaded file on error
lopsided98 Aug 24, 2020
0518f23
nix: sort license dictionary
lopsided98 Mar 29, 2022
dd4010a
nix: add more license mappings
lopsided98 Mar 29, 2022
0699c89
nix: provide condition context to package metadata parser
lopsided98 Mar 29, 2022
0eef782
nix: add buildtool_depends to both buildInputs and nativeBuildInputs
lopsided98 Sep 20, 2022
6af0803
nix: fix formatting to satisfy pycodestyle and flake8
lopsided98 Dec 9, 2022
b29f56d
nix: rename NixDerivation to NixExpression
lopsided98 Dec 9, 2022
9469f1b
nix: fix license test and public domain license
lopsided98 Dec 9, 2022
385eebb
nix: add rolling to --all mode
lopsided98 Dec 9, 2022
c989235
nix: fix: escape special characters in license
locnide Feb 19, 2024
f3b2ace
nix: escape description string
lopsided98 Mar 23, 2024
9c7897e
Fix trailing whitespace
lopsided98 Mar 23, 2024
32123e3
Set correct pull request message for --dry-run case
robwoolley Dec 12, 2024
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Supported Platforms:
--------------------
* Gentoo
* OpenEmbedded
* Nix

Installation:
=============
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def append_local_version_label(public_version):
'console_scripts': [
'superflore-gen-ebuilds = superflore.generators.ebuild:main',
'superflore-gen-oe-recipes = superflore.generators.bitbake:main',
'superflore-gen-nix = superflore.generators.nix:main',
'superflore-check-ebuilds = superflore.test_integration.gentoo:main',
]
}
Expand Down
10 changes: 6 additions & 4 deletions superflore/generators/bitbake/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,12 @@ def main():
delta = "Regenerated: '%s'\n" % args.only
overlay.add_generated_files(args.ros_distro)
commit_msg = '\n'.join([get_pr_text(
title + '\n' + pr_comment.replace(
'**superflore**', 'superflore'), markup=''), delta])
comment=title + '\n' + pr_comment.replace(
'**superflore**', 'superflore'),
markup=''), delta])
overlay.commit_changes(args.ros_distro, commit_msg)
if args.dry_run:
save_pr(overlay, args.only, '', pr_comment, title=title)
save_pr(overlay, delta, '', pr_comment, title=title)
sys.exit(0)
file_pr(overlay, delta, '', pr_comment, distro=args.ros_distro,
title=title)
Expand Down Expand Up @@ -216,7 +217,8 @@ def main():
args.ros_distro,
now)
commit_msg = '\n'.join([get_pr_text(
title + '\n' + pr_comment.replace('**superflore**', 'superflore'),
comment=title + '\n' +
pr_comment.replace('**superflore**', 'superflore'),
markup=''), delta])
overlay.commit_changes(args.ros_distro, commit_msg)
delta = gen_delta_msg(total_changes)
Expand Down
4 changes: 2 additions & 2 deletions superflore/generators/ebuild/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ def main():
regen_dict[args.ros_distro] = to_commit
overlay.regenerate_manifests(regen_dict)
overlay.commit_changes(args.ros_distro)
delta = "Regenerated: '%s'\n" % args.only
if args.dry_run:
save_pr(
overlay,
args.only,
delta,
missing_deps=gen_missing_deps_msg(missing_depends),
comment=pr_comment
)
sys.exit(0)
delta = "Regenerated: '%s'\n" % args.only
file_pr(
overlay,
delta,
Expand Down
3 changes: 3 additions & 0 deletions superflore/generators/nix/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from superflore.generators.nix.run import main
if __name__ == '__main__':
main()
110 changes: 110 additions & 0 deletions superflore/generators/nix/gen_packages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright 2019 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
from typing import Dict, Iterable

from rosdistro import DistributionFile
from rosinstall_generator.distro import get_package_names
from superflore.exceptions import UnresolvedDependency
from superflore.generators.nix.nix_package import NixPackage
from superflore.generators.nix.nix_package_set import NixPackageSet
from superflore.utils import err
from superflore.utils import make_dir
from superflore.utils import ok
from superflore.utils import warn

org = "Open Source Robotics Foundation"
org_license = "BSD"

_version_regex = re.compile(r"version\s*=\s*\"([^\"]*)\"")


def regenerate_pkg(overlay, pkg: str, distro: DistributionFile,
preserve_existing: bool, tar_dir: str,
sha256_cache: Dict[str, str]):
all_pkgs = set(get_package_names(distro)[0])

if pkg not in all_pkgs:
raise RuntimeError("Unknown package '{}'".format(pkg))

normalized_pkg = NixPackage.normalize_name(pkg)

package_dir = os.path.join(overlay.repo.repo_dir, 'distros', distro.name,
normalized_pkg)
package_file = os.path.join(package_dir, 'default.nix')
make_dir(package_dir)

# check for an existing package
existing = os.path.exists(package_file)
previous_version = None

if preserve_existing and existing:
ok("derivation for package '{}' up to date, skipping...".format(pkg))
return None, [], None

if existing:
with open(package_file, 'r') as f:
existing_derivation = f.read()
version_match = _version_regex.search(existing_derivation)
if version_match:
try:
previous_version = version_match.group(1)
except IndexError:
pass
if not previous_version:
warn("Failed to extract previous package version")

try:
current = NixPackage(pkg, distro, tar_dir, sha256_cache, all_pkgs)
except Exception as e:
err('Failed to generate derivation for package {}!'.format(pkg))
raise e

try:
derivation_text = current.derivation.get_text(org, org_license)
except UnresolvedDependency:
err("'Failed to resolve required dependencies for package {}!"
.format(pkg))
unresolved = current.unresolved_dependencies
for dep in unresolved:
err(" unresolved: \"{}\"".format(dep))
return None, unresolved, None
except Exception as e:
err('Failed to generate derivation for package {}!'.format(pkg))
raise e

ok("Successfully generated derivation for package '{}'.".format(pkg))
try:
with open('{0}'.format(package_file), "w") as recipe_file:
recipe_file.write(derivation_text)
except Exception as e:
err("Failed to write derivation to disk!")
raise e
return current, previous_version, normalized_pkg


def regenerate_pkg_set(overlay, distro_name: str, pkg_names: Iterable[str]):
distro_dir = os.path.join(overlay.repo.repo_dir, 'distros', distro_name)
overlay_file = os.path.join(distro_dir, 'generated.nix')
make_dir(distro_dir)

package_set = NixPackageSet(pkg_names)

try:
with open(overlay_file, "w") as recipe_file:
recipe_file.write(package_set.get_text(org, org_license))
except Exception as e:
err("Failed to write derivation to disk!")
raise e
202 changes: 202 additions & 0 deletions superflore/generators/nix/nix_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 David Bensoussan, Synapticon GmbH
# Copyright (c) 2019 Open Source Robotics Foundation, Inc.
#
#
# 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 operator import attrgetter
import os
from textwrap import dedent
from time import gmtime, strftime
from typing import Iterable, Set
import urllib.parse

from superflore.utils import get_license


def _escape_nix_string(string: str):
return '"{}"'.format(string.replace("\\", "\\\\")
.replace("${", r"\${")
.replace('"', r"\""))


class NixLicense:
"""
Converts a ROS license to the correct Nix license attribute.
"""

_LICENSE_MAP = {
'Apache-2.0': 'asl20',
'ASL 2.0': 'asl20',
'Boost-1.0': 'boost',
'BSD-2': 'bsd2',
'BSD-3-Clause': 'bsd3',
'BSD': 'bsdOriginal',
'CC-BY-NC-SA-4.0': 'cc-by-nc-sa-40',
'GPL-1': 'gpl1',
'GPL-2': 'gpl2',
'GPL-3.0-only': 'gpl3Only',
'GPL-3': 'gpl3',
'LGPL-2.1': 'lgpl21',
'LGPL-2': 'lgpl2',
'LGPL-3.0-only': 'lgpl3Only',
'LGPL-3': 'lgpl3',
'MIT': 'mit',
'MPL-1.0': 'mpl10',
'MPL-1.1': 'mpl11',
'MPL-2.0': 'mpl20',
'PD': 'publicDomain',
}

def __init__(self, name):
try:
name = get_license(name)
self.name = self._LICENSE_MAP[name]
self.custom = False
except KeyError:
self.name = name
self.custom = True

@property
def nix_code(self) -> str:
if self.custom:
return _escape_nix_string(self.name)
else:
return self.name


class NixExpression:
def __init__(self, name: str, version: str,
src_url: str, src_sha256: str,
description: str, licenses: Iterable[NixLicense],
distro_name: str,
build_type: str,
build_inputs: Set[str] = set(),
propagated_build_inputs: Set[str] = set(),
check_inputs: Set[str] = set(),
native_build_inputs: Set[str] = set(),
propagated_native_build_inputs: Set[str] = set()
) -> None:
self.name = name
self.version = version
self.src_url = src_url
self.src_sha256 = src_sha256
# fetchurl's naming logic cannot account for URL parameters
self.src_name = os.path.basename(
urllib.parse.urlparse(self.src_url).path)

self.description = description
self.licenses = licenses
self.distro_name = distro_name
self.build_type = build_type

self.build_inputs = build_inputs
self.propagated_build_inputs = propagated_build_inputs
self.check_inputs = check_inputs
self.native_build_inputs = native_build_inputs
self.propagated_native_build_inputs = \
propagated_native_build_inputs

@staticmethod
def _to_nix_list(it: Iterable[str]) -> str:
return '[ ' + ' '.join(it) + ' ]'

@staticmethod
def _to_nix_parameter(dep: str) -> str:
return dep.split('.')[0]

def get_text(self, distributor: str, license_name: str) -> str:
"""
Generate the Nix expression, given the distributor line
and the license text.
"""

ret = []
ret += dedent('''
# Copyright {} {}
# Distributed under the terms of the {} license

''').format(
strftime("%Y", gmtime()), distributor,
license_name)

ret += '{ lib, buildRosPackage, fetchurl, ' + \
', '.join(sorted(set(map(self._to_nix_parameter,
self.build_inputs |
self.propagated_build_inputs |
self.check_inputs |
self.native_build_inputs |
self.propagated_native_build_inputs)))
) + ' }:'

ret += dedent('''
buildRosPackage {{
pname = "ros-{distro_name}-{name}";
version = "{version}";

src = fetchurl {{
url = "{src_url}";
name = "{src_name}";
sha256 = "{src_sha256}";
}};

buildType = "{build_type}";
''').format(
distro_name=self.distro_name,
name=self.name,
version=self.version,
src_url=self.src_url,
src_name=self.src_name,
src_sha256=self.src_sha256,
build_type=self.build_type)

if self.build_inputs:
ret += " buildInputs = {};\n" \
.format(self._to_nix_list(sorted(self.build_inputs)))

if self.check_inputs:
ret += " checkInputs = {};\n" \
.format(self._to_nix_list(sorted(self.check_inputs)))

if self.propagated_build_inputs:
ret += " propagatedBuildInputs = {};\n" \
.format(self._to_nix_list(sorted(
self.propagated_build_inputs)))

if self.native_build_inputs:
ret += " nativeBuildInputs = {};\n" \
.format(self._to_nix_list(sorted(self.native_build_inputs)))

if self.propagated_native_build_inputs:
ret += " propagatedNativeBuildInputs = {};\n".format(
self._to_nix_list(sorted(self.propagated_native_build_inputs)))

ret += dedent('''
meta = {{
description = {};
license = with lib.licenses; {};
}};
}}
''').format(_escape_nix_string(self.description),
self._to_nix_list(map(attrgetter('nix_code'),
self.licenses)))

return ''.join(ret)
Loading