From 87bc5ac5e85fd2f3f28f002f8a398cd130fca413 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 08 2026 16:46:38 +0000 Subject: [PATCH 1/21] new specfile template --- diff --git a/r2spec/specfile.tpl b/r2spec/specfile.tpl index 8c3b76e..cbfb7e6 100644 --- a/r2spec/specfile.tpl +++ b/r2spec/specfile.tpl @@ -1,87 +1,38 @@ -%global packname {{ packname }} -%global packver {{ packver }} -{% if arch %} -%global rlibdir %{_libdir}/R/library -{% else %} -%global rlibdir %{_datadir}/R/library -{% endif %} - -Name: R-%{packname} -Version: {{version}} -Release: 1%{?dist} -Summary: {{summary}} - -License: {{license}} -URL: {{URL}} -Source0: {{source0}} +Name: R-{{name}} +Version: %R_rpm_version {{version}} +Release: %autorelease +Summary: {{summary}} -# Here's the R view of the dependencies world: -# Depends: {{ depends | join(', ') }} -# Imports: {{ imports | join(', ') }} -# Suggests: {{ suggests | join(', ') }} -# LinkingTo: -# Enhances: +License: {{license}} +URL: {{url}} +Source: {{source}} {% if not arch %} -BuildArch: noarch -{% if with_deps %} -Requires: R-core -{% endif %} -{% endif %} -{% if with_deps %} -{% for dep in depends + imports %} -Requires: {{dep.rpm_version}} -{% endfor %} -{% for dep in suggests %} -Suggests: {{dep.rpm_version}} -{% endfor %} +BuildArch: noarch {% endif %} -BuildRequires: R-devel -BuildRequires: tex(latex) -{% for dep in depends + imports %} -BuildRequires: {{dep.rpm_version}} -{% endfor %} -{% for dep in suggests %} -BuildRequires: {{dep.rpm_version}} -{% endfor %} +BuildRequires: R-devel %description {{description}} - %prep -%setup -q -c -n %{packname} +%autosetup -c +%generate_buildrequires +%R_buildrequires %build - %install -mkdir -p %{buildroot}%{rlibdir} -%{_bindir}/R CMD INSTALL -l %{buildroot}%{rlibdir} %{packname} -test -d %{packname}/src && (cd %{packname}/src; rm -f *.o *.so) -rm -f %{buildroot}%{rlibdir}/R.css - +%R_install +%R_save_files {% if not no_check %} %check -%{_bindir}/R CMD check %{packname} - - +%R_check {% endif %} -%files -%dir %{rlibdir}/%{packname} -%doc %{rlibdir}/%{packname}/doc -%doc %{rlibdir}/%{packname}/html -%{rlibdir}/%{packname}/DESCRIPTION -%doc %{rlibdir}/%{packname}/NEWS -%{rlibdir}/%{packname}/INDEX -%{rlibdir}/%{packname}/NAMESPACE -%{rlibdir}/%{packname}/Meta -%{rlibdir}/%{packname}/R -%{rlibdir}/%{packname}/help +%files -f %{R_files} %changelog -* {{date}} {{name}} <{{email}}> - {{version}}-1 -- initial package for Fedora +%autochangelog From 76855249614032aff79bccbfab51e2c6cd1ec399 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 08 2026 16:58:21 +0000 Subject: [PATCH 2/21] bump major version --- diff --git a/r2spec/__init__.py b/r2spec/__init__.py index 91abb56..e1a045a 100644 --- a/r2spec/__init__.py +++ b/r2spec/__init__.py @@ -28,7 +28,7 @@ from subprocess import Popen, PIPE logging.basicConfig() LOG = logging.getLogger('R2spec') -VERSION = '5.0.0' +VERSION = '6.0.0' def get_logger(): From 36415e04f62e8e371157f6c652f32ba7c9d1f57e Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 08 2026 17:02:16 +0000 Subject: [PATCH 3/21] simplify spec class --- diff --git a/R2rpm.1 b/R2rpm.1 index b0fc4d6..ea2ccca 100644 --- a/R2rpm.1 +++ b/R2rpm.1 @@ -78,11 +78,6 @@ as defined by rpm. They are then extracted and the spec file generated. Enforce the generation of the spec even if it already exists on the system. .SH FILES -.I $HOME/.conf/r2spec/settings -.RS -User configuration file for R2spec, allows to specify user name and email to be used in the spec file. -.RE - .I /etc/R2spec/repos.cfg .RS The configuration file for the R repositories. diff --git a/R2spec.1 b/R2spec.1 index 7d741a5..987e947 100644 --- a/R2spec.1 +++ b/R2spec.1 @@ -75,11 +75,6 @@ as defined by rpm. They are then extracted and the spec file generated. Provides much more information on what's going on, including errors when they occur. .SH FILES -.I $HOME/.conf/r2spec/settings -.RS -User configuration file for R2spec, allows to specify user name and email to be used in the spec file. -.RE - .I /etc/R2spec/repos.cfg .RS The configuration file for the R repositories. diff --git a/r2spec/r2spec_obj.py b/r2spec/r2spec_obj.py index 9f0f076..82920d9 100644 --- a/r2spec/r2spec_obj.py +++ b/r2spec/r2spec_obj.py @@ -66,8 +66,6 @@ def setup_parser(prog): help='Path to local sources of a R package.') parser.add_argument('--no-check', action='store_true', help='Do not include the %%check section in the generated spec file.') - parser.add_argument('--with-deps', action='store_true', - help='Include the dependencies marked by the source as dependencies of the RPM.') parser.add_argument('--force-dl', action='store_true', help='Enforce the download of the source, even if they are already on the system.') parser.add_argument('--keep-sources', action='store_true', @@ -77,10 +75,6 @@ def setup_parser(prog): help='Keep the log file generated while building the rpm') parser.add_argument('--repo', default=None, help='Name of the repository as described in the configuration file.') - parser.add_argument('--name', default=None, - help='Name of the packager to use in the spec (overrides local configuration file).') - parser.add_argument('--email', default=None, - help='Email of the packager to use in the spec (overrides local configuration file).') parser.add_argument('--verbose', action='store_true', help='Give more info about what is going on.') parser.add_argument('--debug', action='store_true', @@ -105,116 +99,6 @@ def setup_r2rpm_parser(parser): return parser -def create_conf(configfile): - """Check if the provided configuration file exists, generate the - folder if it does not and return True or False according to the - initial check. - - :arg configfile, name of the configuration file looked for. - """ - if not os.path.exists(configfile): - dirn = os.path.dirname(configfile) - if not os.path.exists(dirn): - os.makedirs(dirn) - return True - return False - - -def save_config(configfile, parser): - """"Save the configuration into the specified file. - - :arg configfile, name of the file in which to write the configuration - :arg parser, ConfigParser object containing the configuration to - write down. - """ - conf = open(configfile, 'w') - parser.write(conf) - conf.close() - - -class Settings(object): - """ R2spec user config Setting""" - # Editor to use in the spec - try: - packager = os.getlogin() - except OSError: - # os.getlogin() raises an exception when the session is not - # in /var/run/utmp (non-login sessions) - if 'LOGNAME' in os.environ: - packager = os.environ['LOGNAME'] - else: - packager = pwd.getpwuid(os.getuid())[0] - - # Editor email to use in the spec - email = '' - - def __init__(self): - """Constructor of the Settings object. - This instantiates the Settings object and loads into the _dict - attributes the default configuration which each available option. - """ - self._dict = { - 'packager': self.packager, - 'email': self.email, - } - self.load_config('.config/r2spec/settings', 'main') - - def load_config(self, configfile, sec): - """Load the configuration in memory. - - :arg configfile, name of the configuration file loaded. - :arg sec, section of the configuration retrieved. - """ - parser = configparser.RawConfigParser() - configfile = os.environ['HOME'] + "/" + configfile - is_new = create_conf(configfile) - parser.read(configfile) - if not parser.has_section(sec): - parser.add_section(sec) - self.populate(parser, sec) - if is_new: - save_config(configfile, parser) - - def set(self, key, value): - """ Set the value to the given key in the settings. - - :arg key, name of the parameter to set from the settings. - :arg value, value of the parameter to set from the settings. - """ - if key not in self._dict: - raise KeyError(key) - self._dict[key] = value - - def get(self, key): - """ Return the associated with the given key in the settings. - - :arg key, name of the parameter to retrieve from the settings. - """ - if key not in self._dict: - raise KeyError(key) - return self._dict[key] - - def populate(self, parser, section): - """"Set option values from a INI file section. - - :arg parser: ConfigParser instance (or subclass) - :arg section: INI file section to read use. - """ - if parser.has_section(section): - opts = set(parser.options(section)) - else: - opts = set() - - for name in self._dict: - value = None - if name in opts: - value = parser.get(section, name) - parser.set(section, name, value) - self._dict[name] = value - else: - parser.set(section, name, self._dict[name]) - - class R2spec(object): """ R2spec object. """ @@ -229,13 +113,6 @@ class R2spec(object): This is the method which actually runs R2spec and generate the spec file according to the argument given. """ - settings = Settings() - - if args.name: - settings.set('packager', args.name) - if args.email: - settings.set('email', args.email) - if args.package: pack = RPackage(re.sub(r'^R-', '', args.package)) pack.search_package_in_repo() @@ -284,8 +161,7 @@ class R2spec(object): pack.get_description() pack.determine_arch() - spec = Spec(settings, pack, no_check=args.no_check, - with_deps=args.with_deps) + spec = Spec(pack, no_check=args.no_check) spec.fill_spec_info() spec.get_template() spec.write_spec(True) @@ -302,9 +178,8 @@ class R2rpm(R2spec): """ super(R2rpm, self).__init__() self.pack = RPackage() - self.settings = Settings() self.build = Build() - self.spec = Spec(self.settings, self.pack) + self.spec = Spec(self.pack) def __check_build_output(self): """ Handle the output from the build, if it succeeded we stop @@ -373,10 +248,9 @@ class R2rpm(R2spec): if args.package or args.url: self.pack.download(args.force_dl) - self.spec = Spec(self.settings, self.pack) + self.spec = Spec(self.pack) specfile = self.spec.get_specfile() self.spec.read_specfile() - self.spec.clean_files_section() self.spec.write_spec() print('Building... %s' % self.pack.name) @@ -388,7 +262,7 @@ class R2rpm(R2spec): if files == 1: return 1 if len(files) != 0: - self.spec.add_files(files) + # self.spec.add_files(files) # Write down the spec file self.spec.write_spec() diff --git a/r2spec/spec.py b/r2spec/spec.py index 859e742..84fd8d0 100644 --- a/r2spec/spec.py +++ b/r2spec/spec.py @@ -15,6 +15,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ Spec class, handles the read/write of the spec file @@ -83,80 +84,25 @@ class Spec: Write the spec file. """ - def __init__(self, settings, package=None, no_check=False, - with_deps=False): + def __init__(self, package=None, no_check=False): """ Constructor. """ self.package = package - self.settings = settings - self.__dict = { - 'with_deps': with_deps - } + self.__dict = {} self.log = get_logger() self.spec = None self.no_check = no_check - def add_files(self, files): - """ Add to a spec file the given files list. - - :arg files, list of the files missing from the spec for the rpm - to build. This list is generated by the parse_error method from - the Build object. - """ - self.log.info('Add files to the %file section') - - files.reverse() - files = self.generate_files(files) - - cnt = 0 - add = False - self.spec = self.spec.split("\n") - while cnt < len(self.spec): - row = self.spec[cnt] - if row.startswith('%changelog'): - self.spec.insert(cnt, "") - cnt = cnt + 1 - add = False - if row.startswith('%defattr'): - add = True - if add: - for filename in files: - cnt = cnt + 1 - self.spec.insert(cnt, filename) - cnt = cnt + 1 - - self.spec = "\n".join(self.spec) - - def clean_files_section(self): - """ Remove all the content of the %files section. """ - self.log.info('Clean the spec %files section') - cnt = 0 - remove = False - self.spec = self.spec.split('\n') - while cnt < len(self.spec): - row = self.spec[cnt] - if row.startswith('%changelog'): - remove = False - if remove: - del self.spec[cnt] - cnt = cnt - 1 - if row.startswith('%defattr'): - remove = True - cnt = cnt + 1 - - self.spec = "\n".join(self.spec) - def fill_spec_info(self): """ Fills the different variable required for the spec file. """ self.log.info('Filling spec variable from info collected') - self.__dict['packname'] = self.package.name - self.__dict['packver'] = self.package.up_version + self.__dict['name'] = self.package.name + self.__dict['version'] = self.package.up_version self.__dict['arch'] = self.package.arch - self.__dict['version'] = self.package.down_version self.__dict['summary'] = self.package.get('Title') self.__dict['license'] = self.package.get('License') - self.__dict['URL'] = self.package.url - self.__dict['source0'] = self.package.source0 + self.__dict['url'] = self.package.url + self.__dict['source'] = self.package.source0 self.__dict['depends'] = format_dependencies( self.package.get('Depends')) self.__dict['imports'] = format_dependencies( @@ -166,54 +112,8 @@ class Spec: self.__dict['description'] = textwrap.fill( self.package.get('Description'), width=75) - self.__dict['date'] = datetime.datetime.now( - ).strftime("%a %b %d %Y") - self.__dict['name'] = self.settings.get('packager') - self.__dict['email'] = self.settings.get('email') self.__dict['no_check'] = self.no_check - def generate_files(self, files): - """ Generate the list of files to add to the spec. - - :arg files, list of the files missing from the spec for the rpm - to build. This list is generated by the parse_error method from - the Build object. - """ - - # TODO: deal with case where there no conventionnal file in - # the sources: cf gdata - macros = { - '/usr/share/': '%{_datadir}/', - '/usr/lib/': '%{_libdir}/', - '/usr/lib64/': '%{_libdir}/', - } - - doc = ['doc', 'html', 'news', 'copying', 'licence', 'citation'] - - packname = self.package.name - - linedocs = [] - linefiles = [] - for filename in files: - #print filename - if 'R/library/' in filename: - folder, name = filename.split('R/library/%s/' % packname) - name = name.strip().split('/')[0] - if name.lower() in doc: - linedocs.append("%doc " + "%%{rlibdir}/%s/" % packname + name) - else: - linefiles.append("%%{rlibdir}/%s/" % packname + name) - else: - linefiles.append(filename.strip()) - - fileout = [] - for doc in set(linedocs): - fileout.append(doc) - for files in set(linefiles): - fileout.append(files) - - return fileout - def get_template(self): """ Read the empty template and fills it with the information retrieved. From db1add4ace59d1abc7b0e428baa1a5e715d6f0db Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 10:51:39 +0000 Subject: [PATCH 4/21] add no-rpmautospec switch --- diff --git a/r2spec/r2spec_obj.py b/r2spec/r2spec_obj.py index 82920d9..eaa96fe 100644 --- a/r2spec/r2spec_obj.py +++ b/r2spec/r2spec_obj.py @@ -15,6 +15,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ Main functions for R2spec @@ -64,6 +65,8 @@ def setup_parser(prog): help='URL to the sources of a R package.') parser.add_argument('-s', '--sources', help='Path to local sources of a R package.') + parser.add_argument('--no-rpmautospec, action='store_true', + help='Do not use rpmautospec.') parser.add_argument('--no-check', action='store_true', help='Do not include the %%check section in the generated spec file.') parser.add_argument('--force-dl', action='store_true', @@ -161,7 +164,9 @@ class R2spec(object): pack.get_description() pack.determine_arch() - spec = Spec(pack, no_check=args.no_check) + spec = Spec(pack, + rpmautospec=not args.no_rpmautospec, + check=not args.no_check) spec.fill_spec_info() spec.get_template() spec.write_spec(True) diff --git a/r2spec/spec.py b/r2spec/spec.py index 84fd8d0..e0e9a21 100644 --- a/r2spec/spec.py +++ b/r2spec/spec.py @@ -78,20 +78,36 @@ def format_dependencies(dependencies): return dep_list +# borrowed from rust2rpm +def detect_packager(): + rpmdev_packager = shutil.which("rpmdev-packager") + if rpmdev_packager is not None: + return subprocess.check_output(rpmdev_packager, universal_newlines=True).strip() + + git = shutil.which("git") + if git is not None: + name = subprocess.check_output([git, "config", "user.name"], universal_newlines=True).strip() + email = subprocess.check_output([git, "config", "user.email"], universal_newlines=True).strip() + return "{} <{}>".format(name, email) + + return None + + class Spec: """ Spec Class Write the spec file. """ - def __init__(self, package=None, no_check=False): + def __init__(self, package=None, rpmautospec=True, check=True): """ Constructor. """ self.package = package self.__dict = {} self.log = get_logger() self.spec = None - self.no_check = no_check + self.rpmautospec = rpmautospec + self.check = check def fill_spec_info(self): """ Fills the different variable required for the spec file. """ @@ -112,7 +128,10 @@ class Spec: self.__dict['description'] = textwrap.fill( self.package.get('Description'), width=75) - self.__dict['no_check'] = self.no_check + self.__dict['date'] = datetime.datetime.now().strftime("%a %b %d %Y") + self.__dict['packager'] = detect_packager() + self.__dict['rpmautospec'] = self.rpmautospec + self.__dict['check'] = self.check def get_template(self): """ Read the empty template and fills it with the information diff --git a/r2spec/specfile.tpl b/r2spec/specfile.tpl index cbfb7e6..033f4f3 100644 --- a/r2spec/specfile.tpl +++ b/r2spec/specfile.tpl @@ -1,6 +1,10 @@ Name: R-{{name}} Version: %R_rpm_version {{version}} +{% if rpmautospec %} Release: %autorelease +{% else %} +Release: 1%{?dist} +{% endif %} Summary: {{summary}} License: {{license}} @@ -27,7 +31,7 @@ BuildRequires: R-devel %R_install %R_save_files -{% if not no_check %} +{% if check %} %check %R_check {% endif %} @@ -35,4 +39,9 @@ BuildRequires: R-devel %files -f %{R_files} %changelog +{% if rpmautospec %} %autochangelog +{% else %} +* {{date}} {{packager}} - {{version}}-1 +- Initial package for Fedora +{% endif %} From 8176a38970f260f15ebad956ee831f537a9c85a6 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 11:42:13 +0000 Subject: [PATCH 5/21] further simplify Spec class --- diff --git a/r2spec/spec.py b/r2spec/spec.py index e0e9a21..1b8fac2 100644 --- a/r2spec/spec.py +++ b/r2spec/spec.py @@ -18,7 +18,7 @@ # (C) 2026 - Iñaki Úcar """ -Spec class, handles the read/write of the spec file +Spec class, handles the write of the spec file """ from __future__ import absolute_import, division, print_function @@ -30,10 +30,10 @@ import textwrap from collections import namedtuple from jinja2 import Environment, FileSystemLoader -from r2spec import get_logger, get_rpm_tag, R2specError +from r2spec import get_logger, R2specError -class Package(namedtuple('Package', 'name version')): +class Dependency(namedtuple('Package', 'name version')): def __str__(self): if self.version: return self.name + ' ' + self.version @@ -73,7 +73,7 @@ def format_dependencies(dependencies): version = '' if name not in ignorelist: name = 'R-%s' % name.strip() - dep_list.append(Package(name, version.strip())) + dep_list.append(Dependency(name, version.strip())) return dep_list @@ -102,15 +102,20 @@ class Spec: def __init__(self, package=None, rpmautospec=True, check=True): """ Constructor. """ + self.log = get_logger() self.package = package + self.__dict = {} - self.log = get_logger() - self.spec = None self.rpmautospec = rpmautospec self.check = check + self._fill_info() + + self.spec = None + self._get_template() - def fill_spec_info(self): - """ Fills the different variable required for the spec file. """ + def _fill_info(self): + """ Fills the different variable required for the spec file. + """ self.log.info('Filling spec variable from info collected') self.__dict['name'] = self.package.name self.__dict['version'] = self.package.up_version @@ -133,9 +138,8 @@ class Spec: self.__dict['rpmautospec'] = self.rpmautospec self.__dict['check'] = self.check - def get_template(self): - """ Read the empty template and fills it with the information - retrieved. + def _get_template(self): + """ Read the empty template and fills it with the information retrieved. """ self.log.info('Filling spec template') loader = FileSystemLoader(os.path.dirname(__file__)) @@ -147,31 +151,17 @@ class Spec: self.log.debug('ERROR: %s', err) raise R2specError('Cannot read the file %s' % (err, )) - def get_specfile(self): + def _get_specfile(self): """ Return the path to the spec file. """ - specdir = get_rpm_tag('_specdir') + specdir = '.' specname = 'R-%s.spec' % self.package.name return '%s/%s' % (specdir, specname) - def read_specfile(self): - """ Read the specfile present in the spec directory. - """ - specfile = self.get_specfile() - if os.path.exists(specfile) and os.path.isfile(specfile): - self.log.info('Reading file %s', specfile) - try: - stream = open(specfile, 'r') - self.spec = stream.read() - stream.close() - except IOError as err: - self.log.info('Cannot read the file %s', specfile) - self.log.debug('ERROR: %s', err) - - def write_spec(self, verbose=False): - """ Write down the spec to the spec directory as returned by rpm. + def write(self, verbose=False): + """ Write down the spec. """ - specfile = self.get_specfile() + specfile = self._get_specfile() self.log.info('Writing file %s', specfile) try: stream = open(specfile, 'w') From 8ffe89d2d4b3df606f88f1a83b1696a14556ffcf Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 11:43:38 +0000 Subject: [PATCH 6/21] add missing imports --- diff --git a/r2spec/spec.py b/r2spec/spec.py index 1b8fac2..1444f2d 100644 --- a/r2spec/spec.py +++ b/r2spec/spec.py @@ -25,8 +25,9 @@ from __future__ import absolute_import, division, print_function import datetime import os -import sys import textwrap +import subprocess +import shutil from collections import namedtuple from jinja2 import Environment, FileSystemLoader @@ -109,7 +110,7 @@ class Spec: self.rpmautospec = rpmautospec self.check = check self._fill_info() - + self.spec = None self._get_template() From 8452f15e054f9fcb0cbd7c5327e77f8830b55aec Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 13:50:20 +0000 Subject: [PATCH 7/21] move repos into library folder --- diff --git a/r2spec/repos.cfg b/r2spec/repos.cfg new file mode 100644 index 0000000..4f93089 --- /dev/null +++ b/r2spec/repos.cfg @@ -0,0 +1,24 @@ +[repo:cran] +url = %cran_url +source = %cran_source +db = https://cran.r-project.org/src/contrib/PACKAGES + +[repo:bioc] +url = %bioc_url +source = %bioc_source +db = https://bioconductor.org/packages/release/bioc/src/contrib/PACKAGES + +[repo:bioc-annotation] +url = %bioc_url data/annotation +source = %bioc_source data/annotation +db = https://bioconductor.org/packages/release/data/annotation/src/contrib/PACKAGES + +[repo:bioc-experiment] +url = %bioc_url data/experiment +source = %bioc_source data/experiment +db = https://bioconductor.org/packages/release/data/experiment/src/contrib/PACKAGES + +[repo:bioc-workflow] +url = %bioc_url workflows +source = %bioc_source workflows +db = https://bioconductor.org/packages/release/workflows/src/contrib/PACKAGES diff --git a/repos.cfg b/repos.cfg deleted file mode 100644 index 863f374..0000000 --- a/repos.cfg +++ /dev/null @@ -1,25 +0,0 @@ -[repo:bioconductor] -url = https://bioconductor.org/packages/release/bioc/html/%{packname}.html -source = https://bioconductor.org/packages/release/bioc/src/contrib/%{packname}_%{packver}.tar.gz -package = https://bioconductor.org/packages/release/bioc/src/contrib/PACKAGES - -[repo:bioconductor-annotation] -url = https://bioconductor.org/packages/release/data/annotation/html/%{packname}.html -source = https://bioconductor.org/packages/release/data/annotation/src/contrib/%{packname}_%{packver}.tar.gz -package = https://bioconductor.org/packages/release/data/annotation/src/contrib/PACKAGES - -[repo:bioconductor-experiment] -url = https://bioconductor.org/packages/release/data/experiment/html/%{packname}.html -source = https://bioconductor.org/packages/release/data/experiment/src/contrib/%{packname}_%{packver}.tar.gz -package = https://bioconductor.org/packages/release/data/experiment/src/contrib/PACKAGES - -[repo:cran] -url = https://CRAN.R-project.org/package=%{packname} -source = https://cran.r-project.org/src/contrib/%{packname}_%{packver}.tar.gz -package = https://cran.r-project.org/src/contrib/PACKAGES - -[repo:r-forge] -url = https://r-forge.r-project.org/projects/%{packname}/index.html -source = https://r-forge.r-project.org/src/contrib/%{packname}_%{packver}.tar.gz -package = https://r-forge.r-project.org/src/contrib/PACKAGES - From 6d72ac46bb2e5492d8239bc9c74969e125e38966 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 13:52:40 +0000 Subject: [PATCH 8/21] simplify rpackage class --- diff --git a/r2spec/__init__.py b/r2spec/__init__.py index e1a045a..1785bfc 100644 --- a/r2spec/__init__.py +++ b/r2spec/__init__.py @@ -15,7 +15,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon - +# (C) 2026 - Iñaki Úcar """ Miscellaneous functions used in R2spec. """ @@ -36,15 +36,13 @@ def get_logger(): return LOG -def get_rpm_tag(tag): +def get_rpm_tag(tag, args=[]): """" Reads the .rpmmacros and set the values accordingly Code from José Matos. :arg tag, the rpm tag to find the value of """ - dirname = Popen(["rpm", "-E", '%' + tag], - stdout=PIPE, universal_newlines=True).stdout.read()[:-1] - return dirname - + return Popen(['rpm'] + args + ['-E', tag], + stdout=PIPE, universal_newlines=True).stdout.read()[:-1] def get_mock_root(): """" Calls mock to retrieve the root path and return it minus the last diff --git a/r2spec/r2spec_obj.py b/r2spec/r2spec_obj.py index eaa96fe..f485ab9 100644 --- a/r2spec/r2spec_obj.py +++ b/r2spec/r2spec_obj.py @@ -65,19 +65,12 @@ def setup_parser(prog): help='URL to the sources of a R package.') parser.add_argument('-s', '--sources', help='Path to local sources of a R package.') - parser.add_argument('--no-rpmautospec, action='store_true', + parser.add_argument('--no-rpmautospec', action='store_true', help='Do not use rpmautospec.') parser.add_argument('--no-check', action='store_true', help='Do not include the %%check section in the generated spec file.') - parser.add_argument('--force-dl', action='store_true', - help='Enforce the download of the source, even if they are already on the system.') - parser.add_argument('--keep-sources', action='store_true', - help='The source will be extracted in the current ' - 'directory.') parser.add_argument('--keep-logs', action='store_true', help='Keep the log file generated while building the rpm') - parser.add_argument('--repo', default=None, - help='Name of the repository as described in the configuration file.') parser.add_argument('--verbose', action='store_true', help='Give more info about what is going on.') parser.add_argument('--debug', action='store_true', @@ -118,7 +111,7 @@ class R2spec(object): """ if args.package: pack = RPackage(re.sub(r'^R-', '', args.package)) - pack.search_package_in_repo() + #pack.search_package_in_repo() elif args.url: pack = RPackage(source0=args.url) source = args.url.rsplit('/', 1)[1] @@ -130,8 +123,6 @@ class R2spec(object): version = version.replace('-', '_') pack.down_version = version - if args.repo: - pack.set_repo(args.repo) elif args.sources: pack = RPackage() fullpath = os.path.expanduser(args.sources) @@ -151,25 +142,16 @@ class R2spec(object): version = version.replace('-', '_') pack.down_version = version - if args.repo: - pack.set_repo(args.repo) else: raise R2specError('Not enough argument given, see -h/--help') - if args.package or args.url: - pack.download(args.force_dl) - - if args.keep_sources: - pack.extract_sources() - pack.get_description() - pack.determine_arch() + #pack.get_description() + #pack.determine_arch() spec = Spec(pack, rpmautospec=not args.no_rpmautospec, check=not args.no_check) - spec.fill_spec_info() - spec.get_template() - spec.write_spec(True) + spec.write(True) return pack @@ -191,7 +173,7 @@ class R2rpm(R2spec): otherwise the parse the error and act accordingly. Return False if the build was successful, return True otherwise. """ - specfile = self.spec.get_specfile() + #specfile = self.spec.get_specfile() if self.build.outcode == 0: self.log.info("RPM %s done", specfile) # Get the list of rpm generated @@ -213,8 +195,8 @@ class R2rpm(R2spec): if args.package: self.pack.name = re.sub(r'^R-', '', args.package) self.spec.package = self.pack - specfile = self.spec.get_specfile() - self.pack.search_package_in_repo() + #specfile = self.spec.get_specfile() + #self.pack.search_package_in_repo() elif args.url: source = args.url.rsplit('/', 1)[1] self.pack.source = source @@ -225,8 +207,6 @@ class R2rpm(R2spec): version = version.replace('-', '_') self.pack.down_version = version - if args.repo: - self.pack.set_repo(args.repo) elif args.sources: fullpath = os.path.expanduser(args.sources) if '/' in fullpath: @@ -236,7 +216,7 @@ class R2rpm(R2spec): name = sources.split('_')[0] self.pack.name = name self.spec.package = self.pack - specfile = self.spec.get_specfile() + #specfile = self.spec.get_specfile() else: raise R2specError('Not enough argument given, see -h/--help') @@ -250,13 +230,10 @@ class R2rpm(R2spec): 'Spec file "%s" is already there, no need to regenerate it', specfile) - if args.package or args.url: - self.pack.download(args.force_dl) - self.spec = Spec(self.pack) - specfile = self.spec.get_specfile() - self.spec.read_specfile() - self.spec.write_spec() + #specfile = self.spec.get_specfile() + #self.spec.read_specfile() + self.spec.write() print('Building... %s' % self.pack.name) self.build.build(specfile, mock_config=args.mock_config, @@ -269,7 +246,7 @@ class R2rpm(R2spec): if len(files) != 0: # self.spec.add_files(files) # Write down the spec file - self.spec.write_spec() + self.spec.write() # Rebuild the package when build has failed the first time if self.build.outcode: # == 1 when build failed and 0 when build passed diff --git a/r2spec/rpackage.py b/r2spec/rpackage.py index 98f9ecb..acf4150 100644 --- a/r2spec/rpackage.py +++ b/r2spec/rpackage.py @@ -15,6 +15,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ R packages class. @@ -24,7 +25,6 @@ from __future__ import absolute_import, division, print_function import os import re -import sys import tarfile try: @@ -39,138 +39,47 @@ except ImportError: from r2spec import get_logger, get_rpm_tag, R2specError -def package_in_repo(url, name): - """ Browse the given url of an R repo for the package name provided. - Returns the version found upstream if the package is found, None - otherwise. - - :arg url, the url to the PACKAGES file of a R repo. - :arg name, the name of the R package. - """ - log = get_logger() - try: - stream = urllib_request.urlopen(url) - content = stream.read().decode() - stream.close() - sourcemotif = re.compile(r"Package:\s+%s\n" % name) - result = sourcemotif.search(content) - if result is not None: - log.info("Package found in : %s", url) - versionmotif = re.compile(r"Package:\s+%s\nVersion:(.*)" % name) - version = versionmotif.search(content).group(1).strip() - return (version) - else: - log.info("Not Found: %s in %s", name, url) - except IOError as ex: - print('Could not contact the repository at url: %s' % url) - log.debug('Error: %s', ex) - return None - - class RPackage(object): """ This class represent information available for R packages. """ - def __init__(self, name=None, url=None, source0=None): + XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")) + CACHEDIR = os.path.join(XDG_CACHE_HOME, "R2rpm") + + def __init__(self, name): """ Constructor. """ - self.name = name - parser = configparser.RawConfigParser() - parser.read('/etc/R2spec/repos.cfg') - self.config = parser self.log = get_logger() - self.description = {} + self.name = name + self.config = configparser.RawConfigParser() + self.config.read('%s/repos.cfg' % os.path.dirname(__file__)) self.up_version = None self.down_version = None + self.url = None self.source = None - self.arch = None - self.url = url - self.source0 = source0 + self._resolve() + self.tarball = self._download() + self.description = self._get_description() + self.arch = self.description['NeedsCompilation'] == 'yes' - def determine_arch(self): - """ Determine if the package is arch or noarch by looking at the - sources. - Set arch to True if the package is arch dependent. - Set arch to False if the package is noarch. - Let arch to None if could not determine. + def _download(self): + """ Download the source of the package into the cache directory. """ - self.log.info('Determining if the package is arch dependent or not') - try: - self.arch = self.description['NeedsCompilation'].lower() != 'no' - except KeyError: - pass - else: - self.log.info('Package is %sarch dependent', - '' if self.arch else 'not ') - return - extensions = ['c', 'C', 'cp', 'cpp', 'h', 'H',] - if os.path.exists(self.name): - for root, dirs, files in os.walk(self.name): - for entry in files: - if '.' in entry: - extension = entry.rsplit('.', 1)[1] - if extension in extensions \ - or 'f' in extension \ - or 'F' in extension: - self.arch = True - self.log.info('Package is arch dependent') - return - self.arch = False - self.log.info('Package is not arch dependent') - return - else: - self.log.info( - 'Could not find the extracted source to search the arch') - - def download(self, force=False): - """ Download the source of the package into the source directory - which we retrieve from rpm directly. - - arg force, boolean whether to force the download of the sources - even if they are on the system already. - """ - sourcedir = get_rpm_tag('_sourcedir') - - if not os.path.exists(sourcedir): - raise R2specError('Folder "%s" does not exist but is required for ' - 'R2spec/R2rpm to work' % sourcedir) + if not os.path.isdir(self.CACHEDIR): + os.mkdir(self.CACHEDIR) - sources = '%s/%s' % (sourcedir, self.source) + sources = '%s/%s_%s.tar.gz' % (self.CACHEDIR, self.name, self.up_version) + if os.path.exists(sources) and os.path.isfile(sources): + self.log.info("Sources are already present, no need to re-download") + return sources - if not force and os.path.exists(sources) and os.path.isfile(sources): - self.log.info( - "Sources are already present, no need to re-download") - return - - url = self.source0.rsplit('/', 1)[0] - url = '%s/%s' % (url, self.source) + defines = ['-D', 'name %s' % self.name, + '-D', 'version %s' % self.up_version] + url = get_rpm_tag(self.source, defines) self.log.info('Downloading %s', url) - remotefile = urllib_request.urlopen(url) with open(sources, 'wb') as localfile: localfile.write(remotefile.read()) - - def open_sources(self): - """ Open the source tarball. """ - sourcedir = get_rpm_tag('_sourcedir') - tarball = "%s/%s" % (sourcedir, self.source) - self.log.info("Opening: %s", tarball) - try: - return tarfile.open(tarball) - except tarfile.TarError as err: - self.log.debug("Error while extracting the tarball") - self.log.debug("ERROR: %s", err) - - def extract_sources(self): - """ Extract the sources into the current directory. """ - tar = self.open_sources() - if tar is None: - return - try: - with tar: - tar.extractall() - except tarfile.TarError as err: - self.log.debug("Error while extracting the tarball") - self.log.debug("ERROR: %s", err) + return sources def get(self, key): """ Retrieve the given key from the description information known @@ -185,38 +94,23 @@ class RPackage(object): else: return '' - def get_description(self): + def _get_description(self): """ This function extracts the source and reads the description from in them. """ - description = '%s/DESCRIPTION' % self.name + file = '%s/DESCRIPTION' % self.name content = None - if os.path.exists(self.name) and os.path.isfile(description): - self.log.info('Loading "%s" from extracted sources', description) - try: - with open(description, 'rb') as stream: + description = {} + + self.log.info('Loading "%s" from tarball', file) + try: + with tarfile.open(self.tarball) as tar: + with tar.extractfile(file) as stream: content = stream.read() - except IOError as err: - self.log.info( - 'An error occurred while reading the DESCRIPTION file: %s', - description) - self.log.debug('ERROR: %s', err) - else: - self.log.info('Loading "%s" from tarball', description) - tar = self.open_sources() - if tar is not None: - try: - with tar: - with tar.extractfile(description) as stream: - content = stream.read() - except tarfile.TarError as err: - self.log.debug("Error while extracting the DESCRIPTION " - "file from the tarball") - self.log.debug("ERROR: %s", err) - else: - self.log.info('Could not find a DESCRIPTION file "%s" to read', - description) - return + except tarfile.TarError as err: + self.log.debug("Error while extracting the DESCRIPTION " + "file from the tarball") + self.log.debug("ERROR: %s", err) encoding = re.search(b'^Encoding: (.+)$', content, re.MULTILINE) if encoding is not None: @@ -229,53 +123,56 @@ class RPackage(object): pattern = re.compile(r"\w:*") if pattern.match(row): key, value = row.split(':', 1) - self.description[key.strip()] = value.strip() + description[key.strip()] = value.strip() else: - self.description[key] = self.description[key] + ' ' + \ + description[key] = description[key] + ' ' + \ row.strip() + return description - def read_config(self): - """ Read the general configuration containing the repo information - """ - parser = configparser.RawConfigParser() - configfile = '/etc/R2spec/config' - parser.read(configfile) - self.config = parser - - def search_package_in_repo(self): + def _resolve(self): """ Search a package in all R repositories listed in the general configuration file. """ repo = None version = None - for section in self.config.sections(): - if section.startswith('repo:'): - version = package_in_repo(self.config.get(section, 'package'), - self.name) - if version: - repo = section - break + for repo in self.config.sections(): + version = self._resolve_version(repo) + if version: + break if version is None: self.log.info( 'Could not find package "%s" in any of the configured repos', self.name) raise R2specError( - 'Could not find package "%s" in any of the configured repos' \ - % self.name) + 'Could not find package "%s" in any of the configured repos' \ + % self.name) self.up_version = version self.down_version = version.replace('-', '.') self.url = self.config.get(repo, 'url') - self.source0 = self.config.get(repo, 'source') - self.source = '%s_%s.tar.gz' % (self.name, self.up_version) + self.source = self.config.get(repo, 'source') - def set_repo(self, reponame): - """ This function find the URL and Source0 tag for the spec file - from the configuration file of the repositories. - - :arg reponame, the name of the repository in which this package is + def _resolve_version(self, repo): + """ Browse the given url of an R repo for the package name provided. + Returns the version found upstream if the package is found, None + otherwise. + :arg repo, the repository """ - for section in self.config.sections(): - if section == 'repo:%s' % reponame: - self.url = self.config.get(section, 'url') - self.source0 = self.config.get(section, 'source') - break + url = self.config.get(repo, 'db') + try: + stream = urllib_request.urlopen(url) + content = stream.read().decode() + stream.close() + sourcemotif = re.compile(r"Package:\s+%s\n" % self.name) + result = sourcemotif.search(content) + if result is not None: + self.log.info("Package found in : %s", url) + versionmotif = re.compile( + r"Package:\s+%s\nVersion:(.*)" % self.name) + version = versionmotif.search(content).group(1).strip() + return (version) + else: + self.log.info("Not Found: %s in %s", self.name, url) + except IOError as ex: + print('Could not contact the repository at url: %s' % url) + self.log.debug('Error: %s', ex) + return None diff --git a/r2spec/spec.py b/r2spec/spec.py index 1444f2d..1607951 100644 --- a/r2spec/spec.py +++ b/r2spec/spec.py @@ -124,7 +124,7 @@ class Spec: self.__dict['summary'] = self.package.get('Title') self.__dict['license'] = self.package.get('License') self.__dict['url'] = self.package.url - self.__dict['source'] = self.package.source0 + self.__dict['source'] = self.package.source self.__dict['depends'] = format_dependencies( self.package.get('Depends')) self.__dict['imports'] = format_dependencies( @@ -152,17 +152,12 @@ class Spec: self.log.debug('ERROR: %s', err) raise R2specError('Cannot read the file %s' % (err, )) - def _get_specfile(self): - """ Return the path to the spec file. - """ - specdir = '.' - specname = 'R-%s.spec' % self.package.name - return '%s/%s' % (specdir, specname) - def write(self, verbose=False): """ Write down the spec. """ - specfile = self._get_specfile() + specdir = '.' + specname = 'R-%s.spec' % self.package.name + specfile = '%s/%s' % (specdir, specname) self.log.info('Writing file %s', specfile) try: stream = open(specfile, 'w') From e7e1600fc90da75a44858a627867bcb715708099 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 13:59:59 +0000 Subject: [PATCH 9/21] remove old 2.7 bits --- diff --git a/r2spec/__init__.py b/r2spec/__init__.py index 1785bfc..e6f26de 100644 --- a/r2spec/__init__.py +++ b/r2spec/__init__.py @@ -19,8 +19,6 @@ """ Miscellaneous functions used in R2spec. """ -from __future__ import absolute_import, division, print_function - import logging import shutil from subprocess import Popen, PIPE diff --git a/r2spec/build.py b/r2spec/build.py index 3292fb6..579fef2 100644 --- a/r2spec/build.py +++ b/r2spec/build.py @@ -19,12 +19,8 @@ """ Build related class and methods. """ -from __future__ import absolute_import, division, print_function - import os import subprocess -import shutil -import sys from r2spec import get_logger, BuildDepencenciesError, BuildError, get_mock_root diff --git a/r2spec/r2spec_obj.py b/r2spec/r2spec_obj.py index f485ab9..31394a0 100644 --- a/r2spec/r2spec_obj.py +++ b/r2spec/r2spec_obj.py @@ -21,26 +21,17 @@ Main functions for R2spec """ -from __future__ import absolute_import, division, print_function - import argparse import logging import os import re import sys -import pwd - -try: - import configparser -except ImportError: - import ConfigParser as configparser from r2spec.build import Build from r2spec.rpackage import RPackage from r2spec.spec import Spec from r2spec import * - # Initial simple logging stuff LOG = get_logger() if '--debug' in sys.argv: diff --git a/r2spec/rpackage.py b/r2spec/rpackage.py index acf4150..9444a3a 100644 --- a/r2spec/rpackage.py +++ b/r2spec/rpackage.py @@ -21,20 +21,11 @@ R packages class. """ -from __future__ import absolute_import, division, print_function - import os import re import tarfile - -try: - import configparser -except ImportError: - import ConfigParser as configparser -try: - import urllib.request as urllib_request -except ImportError: - import urllib2 as urllib_request +import configparser +import urllib.request as urllib_request from r2spec import get_logger, get_rpm_tag, R2specError diff --git a/r2spec/spec.py b/r2spec/spec.py index 1607951..def004e 100644 --- a/r2spec/spec.py +++ b/r2spec/spec.py @@ -21,8 +21,6 @@ Spec class, handles the write of the spec file """ -from __future__ import absolute_import, division, print_function - import datetime import os import textwrap diff --git a/test/test_R2rpm.py b/test/test_R2rpm.py index 36f4484..9655c8a 100644 --- a/test/test_R2rpm.py +++ b/test/test_R2rpm.py @@ -19,8 +19,6 @@ """ R2rpm tests script """ -from __future__ import absolute_import, division, print_function - # Cases: # -p @@ -42,11 +40,7 @@ import os import shutil import sys import unittest - -try: - import urllib.request as urllib_request -except ImportError: - import urllib2 as urllib_request +import urllib.request as urllib_request sys.path.insert(0, os.path.abspath('../')) from r2spec.r2spec_obj import R2rpm, setup_parser diff --git a/test/test_R2spec.py b/test/test_R2spec.py index c45d190..87cd2ee 100644 --- a/test/test_R2spec.py +++ b/test/test_R2spec.py @@ -19,8 +19,6 @@ """ R2spec tests script """ -from __future__ import absolute_import, division, print_function - # Cases: # -p @@ -43,11 +41,7 @@ import os import shutil import sys import unittest - -try: - import urllib.request as urllib_request -except ImportError: - import urllib2 as urllib_request +import urllib.request as urllib_request sys.path.insert(0, os.path.abspath('../')) from r2spec.r2spec_obj import R2spec, setup_parser From 1e87f0cf363fdd3f28e247dcbdbab271b9fd1320 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 15:40:48 +0000 Subject: [PATCH 10/21] simplify main classes --- diff --git a/R2rpm b/R2rpm index 03ed167..66aea48 100755 --- a/R2rpm +++ b/R2rpm @@ -16,21 +16,18 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ R2rpm launcher script """ -from __future__ import absolute_import, division, print_function - import sys -from r2spec.r2spec_obj import R2rpm, setup_parser +from r2spec.r2spec_obj import R2rpm from r2spec import R2specError if __name__ == '__main__': - PARSER = setup_parser('R2rpm') - ARG = PARSER.parse_args() try: - ret = R2rpm().main(ARG) + ret = R2rpm().main() except R2specError as err: print(err) ret = 1 diff --git a/R2spec b/R2spec index e280be9..ebdcf31 100755 --- a/R2spec +++ b/R2spec @@ -16,22 +16,18 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ R2spec launcher script """ -from __future__ import absolute_import, division, print_function - import sys -from r2spec.r2spec_obj import R2spec, setup_parser +from r2spec.r2spec_obj import R2spec from r2spec import R2specError if __name__ == '__main__': - PARSER = setup_parser('R2spec') - ARG = PARSER.parse_args() try: - ret = R2spec().main(ARG) - ret = 0 if ret is not None else 1 + ret = R2spec().main() except R2specError as err: print(err) ret = 1 diff --git a/r2spec/r2spec_obj.py b/r2spec/r2spec_obj.py index 31394a0..c11bab4 100644 --- a/r2spec/r2spec_obj.py +++ b/r2spec/r2spec_obj.py @@ -40,111 +40,40 @@ elif '--verbose' in sys.argv: LOG.setLevel(logging.INFO) -def setup_parser(prog): - """ - Set the main arguments. - :arg prog, the name of the program - """ - parser = argparse.ArgumentParser(usage='%(prog)s [options]', - prog=prog) - parser.add_argument('--version', action='version', - version='%(prog)s ' +VERSION) - # General connection options - parser.add_argument('-p', '--package', - help='Name of the R package to package.') - parser.add_argument('-u', '--url', - help='URL to the sources of a R package.') - parser.add_argument('-s', '--sources', - help='Path to local sources of a R package.') - parser.add_argument('--no-rpmautospec', action='store_true', - help='Do not use rpmautospec.') - parser.add_argument('--no-check', action='store_true', - help='Do not include the %%check section in the generated spec file.') - parser.add_argument('--keep-logs', action='store_true', - help='Keep the log file generated while building the rpm') - parser.add_argument('--verbose', action='store_true', - help='Give more info about what is going on.') - parser.add_argument('--debug', action='store_true', - help='Output bunches of debugging info.') - if prog == 'R2rpm': - setup_r2rpm_parser(parser) - return parser - - -def setup_r2rpm_parser(parser): - """ This function setup the argument specific to R2rpm. - - :arg parser, a argparse.ArgumentParers object to which new argument - will be added. - """ - parser.add_argument('--force-spec', action='store_true', - help="Force the generation of a new spec file.") - parser.add_argument('--mock-config', - help="Specify the mock configuration to use.") - parser.add_argument('--mock-resultdir', - help="Specify the mock result directory.") - return parser - - class R2spec(object): """ R2spec object. """ def __init__(self): """ Constructor. - Set the logger. """ self.log = get_logger() - def main(self, args): + self.args = argparse.ArgumentParser() + self.args.add_argument('--version', action='version', + version='%(prog)s ' + VERSION) + # General connection options + self.args.add_argument('package', + help='Name of the R package in any of the supported repositories.') + self.args.add_argument('--no-rpmautospec', action='store_true', + help='Do not use rpmautospec.') + self.args.add_argument('--no-check', action='store_true', + help='Do not include the %%check section in the generated spec file.') + self.args.add_argument('--verbose', action='store_true', + help='Give more info about what is going on.') + self.args.add_argument('--debug', action='store_true', + help='Output bunches of debugging info.') + + def main(self, write=True): """ Main function for R2spec. This is the method which actually runs R2spec and generate the spec file according to the argument given. """ - if args.package: - pack = RPackage(re.sub(r'^R-', '', args.package)) - #pack.search_package_in_repo() - elif args.url: - pack = RPackage(source0=args.url) - source = args.url.rsplit('/', 1)[1] - pack.source = source - name = source.split('_')[0] - pack.name = name - version = source.split('_')[1].split('.tar')[0] - pack.up_version = version - version = version.replace('-', '_') - pack.down_version = version - - elif args.sources: - pack = RPackage() - fullpath = os.path.expanduser(args.sources) - sourcedir = get_rpm_tag('_sourcedir') - if '/' in fullpath: - sources = fullpath.rsplit('/', 1)[1] - else: - sources = fullpath - pack.source = sources - pack.source0 = sources - if sources not in os.listdir(sourcedir): - move_sources(fullpath, sources) - name, version = sources.split('_', 1) - pack.name = name - version = version.split('.tar')[0] - pack.up_version = version - version = version.replace('-', '_') - pack.down_version = version - - else: - raise R2specError('Not enough argument given, see -h/--help') - - #pack.get_description() - #pack.determine_arch() - - spec = Spec(pack, - rpmautospec=not args.no_rpmautospec, - check=not args.no_check) - spec.write(True) - - return pack + self.args = self.args.parse_args() + self.pack = RPackage(self.args.package) + self.spec = Spec(self.pack, self.args) + if write: + self.spec.write() + return 0 class R2rpm(R2spec): @@ -152,19 +81,23 @@ class R2rpm(R2spec): def __init__(self): """ Constructor. - Set the logger. """ super(R2rpm, self).__init__() - self.pack = RPackage() + self.args.add_argument('--force-spec', action='store_true', + help="Force the generation of a new spec file.") + self.args.add_argument('--mock-config', + help="Specify the mock configuration to use.") + self.args.add_argument('--mock-resultdir', + help="Specify the mock result directory.") + self.build = Build() - self.spec = Spec(self.pack) def __check_build_output(self): """ Handle the output from the build, if it succeeded we stop otherwise the parse the error and act accordingly. Return False if the build was successful, return True otherwise. """ - #specfile = self.spec.get_specfile() + specfile = self.spec.get_spec() if self.build.outcode == 0: self.log.info("RPM %s done", specfile) # Get the list of rpm generated @@ -175,60 +108,25 @@ class R2rpm(R2spec): else: return True - def main(self, args): + def main(self): """ Main function for R2rpm. This is the method which actually runs R2rpm, it generates the spec files, cleans its %files section, build it and fill again the %files section. """ + super(R2rpm, self).main(False) - specfile = '' - if args.package: - self.pack.name = re.sub(r'^R-', '', args.package) - self.spec.package = self.pack - #specfile = self.spec.get_specfile() - #self.pack.search_package_in_repo() - elif args.url: - source = args.url.rsplit('/', 1)[1] - self.pack.source = source - name = source.split('_')[0] - self.pack.name = name - version = source.split('_')[1].split('.tar')[0] - self.pack.up_version = version - version = version.replace('-', '_') - self.pack.down_version = version - - elif args.sources: - fullpath = os.path.expanduser(args.sources) - if '/' in fullpath: - sources = fullpath.rsplit('/', 1)[1] - else: - sources = fullpath - name = sources.split('_')[0] - self.pack.name = name - self.spec.package = self.pack - #specfile = self.spec.get_specfile() - else: - raise R2specError('Not enough argument given, see -h/--help') - - if args.force_spec \ - or (not os.path.exists(specfile) \ - and not os.path.isfile(specfile)): - # Generate the spec file - self.pack = super(R2rpm, self).main(args) + specfile = self.spec.get_spec() + if self.args.force_spec or not os.path.isfile(specfile): + self.spec.write() else: self.log.info( 'Spec file "%s" is already there, no need to regenerate it', specfile) - self.spec = Spec(self.pack) - #specfile = self.spec.get_specfile() - #self.spec.read_specfile() - self.spec.write() - print('Building... %s' % self.pack.name) - self.build.build(specfile, mock_config=args.mock_config, - mock_resultdir=args.mock_resultdir) + self.build.build(specfile, mock_config=self.args.mock_config, + mock_resultdir=self.args.mock_resultdir) if self.__check_build_output(): try: files = self.build.parse_error(specfile) @@ -240,10 +138,10 @@ class R2rpm(R2spec): self.spec.write() # Rebuild the package when build has failed the first time - if self.build.outcode: # == 1 when build failed and 0 when build passed + if self.build.outcode: # == 1 when build failed print('Re-building... %s' % self.pack.name) - self.build.build(specfile, mock_config=args.mock_config, - mock_resultdir=args.mock_resultdir) + self.build.build(specfile, mock_config=self.args.mock_config, + mock_resultdir=self.args.mock_resultdir) self.__check_build_output() except BuildDepencenciesError as err: print('Missing dependencies to build %s' % self.pack.name) @@ -253,6 +151,4 @@ class R2rpm(R2spec): print('An error occurred during the build of %s' % self.pack.name) self.log.debug('Build ERROR: %s', err) return 1 - if not args.keep_logs: - self.build.remove_logs() return 0 diff --git a/r2spec/spec.py b/r2spec/spec.py index def004e..22aefa8 100644 --- a/r2spec/spec.py +++ b/r2spec/spec.py @@ -98,15 +98,14 @@ class Spec: Write the spec file. """ - def __init__(self, package=None, rpmautospec=True, check=True): + def __init__(self, package, opts): """ Constructor. """ self.log = get_logger() self.package = package self.__dict = {} - self.rpmautospec = rpmautospec - self.check = check + self.opts = opts self._fill_info() self.spec = None @@ -134,8 +133,8 @@ class Spec: width=75) self.__dict['date'] = datetime.datetime.now().strftime("%a %b %d %Y") self.__dict['packager'] = detect_packager() - self.__dict['rpmautospec'] = self.rpmautospec - self.__dict['check'] = self.check + self.__dict['rpmautospec'] = not self.opts.no_rpmautospec + self.__dict['check'] = not self.opts.no_check def _get_template(self): """ Read the empty template and fills it with the information retrieved. @@ -150,12 +149,17 @@ class Spec: self.log.debug('ERROR: %s', err) raise R2specError('Cannot read the file %s' % (err, )) - def write(self, verbose=False): - """ Write down the spec. + def get_spec(self): + """ Return the spec file. """ specdir = '.' specname = 'R-%s.spec' % self.package.name - specfile = '%s/%s' % (specdir, specname) + return '%s/%s' % (specdir, specname) + + def write(self, verbose=True): + """ Write down the spec. + """ + specfile = self.get_spec() self.log.info('Writing file %s', specfile) try: stream = open(specfile, 'w') From 57e3ebd7285af95f3a994916e2be9c6ce0adb3e4 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 18:21:56 +0000 Subject: [PATCH 11/21] simplify build class --- diff --git a/r2spec/__init__.py b/r2spec/__init__.py index e6f26de..7ca90ca 100644 --- a/r2spec/__init__.py +++ b/r2spec/__init__.py @@ -20,8 +20,6 @@ """ Miscellaneous functions used in R2spec. """ import logging -import shutil -from subprocess import Popen, PIPE logging.basicConfig() @@ -34,35 +32,6 @@ def get_logger(): return LOG -def get_rpm_tag(tag, args=[]): - """" Reads the .rpmmacros and set the values accordingly - Code from José Matos. - :arg tag, the rpm tag to find the value of - """ - return Popen(['rpm'] + args + ['-E', tag], - stdout=PIPE, universal_newlines=True).stdout.read()[:-1] - -def get_mock_root(): - """" Calls mock to retrieve the root path and return it minus the last - two levels. - """ - dirname = Popen(["mock", "--print-root-path"], - stdout=PIPE, universal_newlines=True).stdout.read()[:-1] - return dirname.rsplit('/', 3)[0] + '/' - - -def move_sources(fullpath, sources): - """ Copy the tarball from its current location to the sourcedir as - defined by rpm. - - :arg fullpath, the fullpath to the sources in their current location. - :arg sources, the name of the file in which the origin will be copied. - """ - sourcedir = get_rpm_tag('_sourcedir') - dest = '%s/%s' % (sourcedir, sources) - shutil.copyfile(fullpath, dest) - - class R2specError(Exception): """ R2specError class Template for all the error of the project diff --git a/r2spec/build.py b/r2spec/build.py index 579fef2..a80c3d5 100644 --- a/r2spec/build.py +++ b/r2spec/build.py @@ -15,14 +15,16 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon - +# (C) 2026 - Iñaki Úcar """ Build related class and methods. """ import os -import subprocess +import glob +import shutil +from subprocess import call, run, PIPE -from r2spec import get_logger, BuildDepencenciesError, BuildError, get_mock_root +from r2spec import * class Build: @@ -30,99 +32,62 @@ class Build: Builds the specfile and handles the errors """ - def __init__(self): + def __init__(self, specfile, sources, mock_args=[]): """ Constructor. """ - self.specname = '' + self.log = get_logger() + self.specfile = specfile + self.sources = sources + self.mock_args = mock_args self.outcode = None - self.rpm = None self.buildlog = None - self.log = get_logger() - self.files = [] # stores the files for the %file section + self.rpm = [] self.deps = [] # stores missing dependencies - def build(self, specname, rpmarg='ba', mock_config=False, - mock_resultdir=None): - """ Builds the given specfile and returns the errors if any. - - :arg specname, the full path to the spec file to build. - :arg rpmarg, the rpmbuild argument used to construct the rpm - :arg mock, if not None the package will be built using mock and - using the given configuration. - """ - self.log.info('Building the rpm (this may take some time)') - self.buildlog = '%s.build.log' % specname - if mock_config is None or mock_config is False: - cmd = 'LANG=C rpmbuild -%s %s > %s 2>&1' % (rpmarg, - specname, self.buildlog) - self.log.debug('Running %s', cmd) - self.outcode = subprocess.call(cmd, shell=True) + if '--resultdir' in self.mock_args: + self.resultdir = self.mock_args[ + self.mock_args.index('--resultdir') + 1] else: - cmd = 'LANG=C rpmbuild -bs %s > %s 2>&1' % ( - specname, self.buildlog) - self.log.debug('Running %s', cmd) - self.outcode = subprocess.call(cmd, shell=True) - self.get_rpm() - - name = specname.rsplit('R-', 1)[1].split('.spec')[0] - mockcommand = 'mock -r %s --uniqueext=%s --rebuild %s' % ( - mock_config, name, self.rpm[0]) - if mock_resultdir: - mockcommand = '%s --cleanup-after --resultdir=%s/%s' % (mockcommand, - mock_resultdir, name) - cmd = 'LANG=C %s >> %s.build.log 2>&1' % ( - mockcommand, specname) - self.log.debug('Running %s', cmd) - self.outcode = subprocess.call(cmd, shell=True) - if self.outcode == 30: - # When mock stop while building the cache, then check - # the root.log - self.log.debug('mock failed while building the cache') - filename = 'root.log' - else: - filename = 'build.log' - if mock_resultdir: - self.buildlog = '%s/%s/%s' % (mock_resultdir, - name, filename) - else: - directory = get_mock_root() - self.buildlog = '/%s/%s-%s/result/%s' % ( - directory, mock_config, name, filename) - self.log.debug('buildlog %s', self.buildlog) - - def get_rpm(self): - """ Parses the build.log to extract the rpm generated. - """ - try: - stream = open(self.buildlog, 'r') - log = stream.read() - stream.close() - logs = log.split("\n") - for line in logs: - if 'Wrote: ' in line and '.rpm' in line: - rpm = line.split('Wrote:')[1].strip() - if self.rpm is None: - self.rpm = [] - self.rpm.append(rpm) - else: - self.rpm.append(rpm) - except IOError as err: - self.log.info('Could not read the file "%s"', self.buildlog) - self.log.debug('ERROR: %s', err) - - def remove_logs(self): - """ Remove the log file generated while building the RPM. """ - if os.path.exists(self.buildlog): - os.remove(self.buildlog) + self.resultdir = run('mock -p %s' % ' '.join(self.mock_args), + shell=True, check=True, stdout=PIPE, text=True) + self.resultdir = self.resultdir.stdout[:-1].replace('root', 'result') + + def _run_cmd(self, cmd): + self.log.debug('Running %s', cmd) + + self.outcode = call(cmd, shell=True) + if self.outcode == 30: + # When mock stop while building the cache, then check + # the root.log + self.log.debug('mock failed while building the cache') + self.buildlog = self.resultdir + 'root.log' else: - self.log.info('No log file to remove') + self.buildlog = self.resultdir + 'build.log' + self.log.debug('buildlog %s', self.buildlog) + self.rpm = glob.glob(self.resultdir + '*.rpm') + self.log.debug('rpms %s', self.rpm) - def parse_error(self, specname): + def run(self): + """ Builds the given specfile and returns the errors if any. + """ + self.log.info('Building the srpm...') + self._run_cmd('mock -q --buildsrpm --spec %s --sources %s %s' % ( + self.specfile, self.sources, ' '.join(self.mock_args))) + + src = self.rpm.pop() + srpm = os.path.basename(src) + shutil.copyfile(src, srpm) + for file_path in glob.glob(self.resultdir + '*.log'): + os.remove(file_path) + + self.log.info('Building the rpm...') + self._run_cmd('mock -q --rebuild %s %s' % ( + srpm, ' '.join(self.mock_args))) + os.remove(srpm) + + def parse_error(self): """Parses the build.log to extract the error - Generates a file list (file installed but not found) Throw an error Package not found Throw an error Build Time error - - :arg specname, the path to the spec file. """ try: stream = open(self.buildlog, 'r') @@ -136,23 +101,12 @@ class Build: logs = log.split('\n') - flagfile = False flagdep = False for row in logs: - if row == '': - flagfile = False - if 'Child returncode was: 1' in row \ - or 'Installed' in row \ - or 'Failed' in row: - flagfile = False - if flagfile: - self.files.append(row) if flagdep: entry = row.split('is needed')[0].replace('\t', '').strip() if entry not in self.deps: self.deps.append(entry) - if 'Installed (but unpackaged) file(s) found:' in row: - flagfile = True if 'Failed build dependencies:' in row: flagdep = True if 'Error: No Package' in row: @@ -162,9 +116,7 @@ class Build: dep = " ".join(self.deps) raise BuildDepencenciesError('Dependencies missing: %s' % dep) - if len(self.deps) == 0 and len(self.files) == 0 and self.outcode == 1: - log = '%s.build.log' % specname + if len(self.deps) == 0 and self.outcode == 1: + log = '%s.build.log' % self.specfile raise BuildError( 'An error occurred at build time, see the log in %s' % log) - - return self.files diff --git a/r2spec/r2spec_obj.py b/r2spec/r2spec_obj.py index c11bab4..bd8ce10 100644 --- a/r2spec/r2spec_obj.py +++ b/r2spec/r2spec_obj.py @@ -85,28 +85,8 @@ class R2rpm(R2spec): super(R2rpm, self).__init__() self.args.add_argument('--force-spec', action='store_true', help="Force the generation of a new spec file.") - self.args.add_argument('--mock-config', - help="Specify the mock configuration to use.") - self.args.add_argument('--mock-resultdir', - help="Specify the mock result directory.") - - self.build = Build() - - def __check_build_output(self): - """ Handle the output from the build, if it succeeded we stop - otherwise the parse the error and act accordingly. - Return False if the build was successful, return True otherwise. - """ - specfile = self.spec.get_spec() - if self.build.outcode == 0: - self.log.info("RPM %s done", specfile) - # Get the list of rpm generated - self.build.get_rpm() - print("RPM generated:") - print("\n".join(self.build.rpm)) - return False - else: - return True + self.args.add_argument('...', nargs='...', + help="Other arguments passed to mock.") def main(self): """ Main function for R2rpm. @@ -117,6 +97,7 @@ class R2rpm(R2spec): super(R2rpm, self).main(False) specfile = self.spec.get_spec() + tarball = self.pack.tarball if self.args.force_spec or not os.path.isfile(specfile): self.spec.write() else: @@ -124,25 +105,17 @@ class R2rpm(R2spec): 'Spec file "%s" is already there, no need to regenerate it', specfile) - print('Building... %s' % self.pack.name) - self.build.build(specfile, mock_config=self.args.mock_config, - mock_resultdir=self.args.mock_resultdir) - if self.__check_build_output(): + print('Building... %s' % specfile) + self.build = Build(specfile, tarball, getattr(self.args, '...')) + self.build.run() + if self.build.outcode == 0: + self.log.info("RPM %s done", specfile) + print("RPM generated:") + print("\n".join(self.build.rpm)) + else: try: - files = self.build.parse_error(specfile) - if files == 1: - return 1 - if len(files) != 0: - # self.spec.add_files(files) - # Write down the spec file - self.spec.write() - - # Rebuild the package when build has failed the first time - if self.build.outcode: # == 1 when build failed - print('Re-building... %s' % self.pack.name) - self.build.build(specfile, mock_config=self.args.mock_config, - mock_resultdir=self.args.mock_resultdir) - self.__check_build_output() + self.build.parse_error() + return 1 except BuildDepencenciesError as err: print('Missing dependencies to build %s' % self.pack.name) self.log.info('%s', err) diff --git a/r2spec/rpackage.py b/r2spec/rpackage.py index 9444a3a..36d6b92 100644 --- a/r2spec/rpackage.py +++ b/r2spec/rpackage.py @@ -26,8 +26,21 @@ import re import tarfile import configparser import urllib.request as urllib_request +from subprocess import Popen, PIPE -from r2spec import get_logger, get_rpm_tag, R2specError +from r2spec import * + + +def get_rpm_tag(tag, **defines): + """" Reads the .rpmmacros and set the values accordingly + Code from José Matos. + :arg tag, the rpm tag to find the value of + """ + args = [] + for key, value in defines.items(): + args = args + ['-D', '%s %s' % (key, value)] + return Popen(['rpm'] + args + ['-E', tag], + stdout=PIPE, universal_newlines=True).stdout.read()[:-1] class RPackage(object): @@ -63,9 +76,7 @@ class RPackage(object): self.log.info("Sources are already present, no need to re-download") return sources - defines = ['-D', 'name %s' % self.name, - '-D', 'version %s' % self.up_version] - url = get_rpm_tag(self.source, defines) + url = get_rpm_tag(self.source, name=self.name, version=self.up_version) self.log.info('Downloading %s', url) remotefile = urllib_request.urlopen(url) with open(sources, 'wb') as localfile: From 47ce5d5e0c5d6fa9746150613e825f1eb5196260 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 18:24:34 +0000 Subject: [PATCH 12/21] some cleanup --- diff --git a/r2spec/r2spec_obj.py b/r2spec/r2spec_obj.py index bd8ce10..3e72ae7 100644 --- a/r2spec/r2spec_obj.py +++ b/r2spec/r2spec_obj.py @@ -24,7 +24,6 @@ Main functions for R2spec import argparse import logging import os -import re import sys from r2spec.build import Build @@ -51,7 +50,6 @@ class R2spec(object): self.args = argparse.ArgumentParser() self.args.add_argument('--version', action='version', version='%(prog)s ' + VERSION) - # General connection options self.args.add_argument('package', help='Name of the R package in any of the supported repositories.') self.args.add_argument('--no-rpmautospec', action='store_true', @@ -90,9 +88,7 @@ class R2rpm(R2spec): def main(self): """ Main function for R2rpm. - This is the method which actually runs R2rpm, it generates the - spec files, cleans its %files section, build it and fill again - the %files section. + This is the method which actually runs R2spec, then R2rpm using mock. """ super(R2rpm, self).main(False) From e1da60b239d3db124c1b99e972b26f0311c99ddb Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 18:26:17 +0000 Subject: [PATCH 13/21] update README --- diff --git a/README.md b/README.md index 40f25c0..b652938 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # R2spec -**Author:** Pierre-Yves Chibon +**Authors:** Pierre-Yves Chibon, Iñaki Úcar R2spec generates RPMs spec files for R libraries according to Fedora's [R Packaging Guidelines](https://docs.fedoraproject.org/en-US/packaging-guidelines/R/). -It creates the spec file from an input which could be either a source file (*.tar.gz) or a url (https://*.tar.gz). +It creates the spec file from a package name from the official respositories (CRAN, BioC). From f2e3fc8b0b3227da81dfb977b7f8d9d0e7a11556 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 18:48:50 +0000 Subject: [PATCH 14/21] update setup --- diff --git a/setup.py b/setup.py index 3ced326..e76538a 100644 --- a/setup.py +++ b/setup.py @@ -3,8 +3,6 @@ Setup script """ -from __future__ import absolute_import, division, print_function - from setuptools import setup from r2spec import VERSION @@ -12,18 +10,16 @@ setup( name = 'R2spec', description = 'R2spec is a small python tool that generates spec file and rpm for R libraries.', description_long = '', - data_files = [('/usr/share/man/man1/', [ 'R2spec.1', 'R2rpm.1' ] ), - ('/etc/R2spec/', [ 'repos.cfg' ] ) ], + data_files = [('/usr/share/man/man1/', [ 'R2spec.1', 'R2rpm.1' ] )], version = VERSION, - author = 'Pierre-Yves Chibon', - author_email = 'pingou@pingoured.fr', - maintainer = 'Pierre-Yves Chibon', - maintainer_email = 'pingou@pingoured.fr', - license = 'GPLv3+', - download_url='https://pagure.io/r2spec/releases', - url='https://pagure.io/r2spec', + author = 'Pierre-Yves Chibon, Iñaki Úcar', + maintainer = 'Iñaki Úcar', + maintainer_email = 'iucar@fedoraproject.org', + license = 'GPL-3.0-or-later', + download_url = 'https://pagure.io/r2spec/releases', + url = 'https://pagure.io/r2spec', package_dir = {'R2spec': 'r2spec'}, packages = ['r2spec'], - include_package_data=True, - scripts=['R2spec', 'R2rpm'], - ) + include_package_data = True, + scripts = ['R2spec', 'R2rpm'], +) From 908853e05ca2c4daf13d55f048e093ed9bb46f81 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 20:54:41 +0000 Subject: [PATCH 15/21] some fixes --- diff --git a/r2spec/build.py b/r2spec/build.py index a80c3d5..be09c93 100644 --- a/r2spec/build.py +++ b/r2spec/build.py @@ -56,14 +56,13 @@ class Build: self.outcode = call(cmd, shell=True) if self.outcode == 30: - # When mock stop while building the cache, then check - # the root.log + # When mock stop while building the cache, then check the root.log self.log.debug('mock failed while building the cache') - self.buildlog = self.resultdir + 'root.log' + self.buildlog = os.path.join(self.resultdir, 'root.log') else: - self.buildlog = self.resultdir + 'build.log' + self.buildlog = os.path.join(self.resultdir, 'build.log') self.log.debug('buildlog %s', self.buildlog) - self.rpm = glob.glob(self.resultdir + '*.rpm') + self.rpm = glob.glob(os.path.join(self.resultdir, '*.rpm')) self.log.debug('rpms %s', self.rpm) def run(self): @@ -76,7 +75,7 @@ class Build: src = self.rpm.pop() srpm = os.path.basename(src) shutil.copyfile(src, srpm) - for file_path in glob.glob(self.resultdir + '*.log'): + for file_path in glob.glob(os.path.join(self.resultdir, '*.rpm')): os.remove(file_path) self.log.info('Building the rpm...') From 9179bd4d8613161e2d25f8e254ad608d8a87507c Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 20:55:09 +0000 Subject: [PATCH 16/21] rewrite tests --- diff --git a/test/__init__.py b/test/__init__.py index 95b682f..19dfda2 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -15,6 +15,34 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon - +# (C) 2026 - Iñaki Úcar """ Makes of the test folder a module. """ +import sys +import os +import shutil +import glob +from urllib.request import urlopen + +sys.path.insert(0, os.path.abspath('../')) +from r2spec.rpackage import RPackage + +RESULTDIR = os.path.join(RPackage.CACHEDIR, 'result') +PKG_NAME = 'errors' +PKG_PATH = 'https://cran.r-project.org/web/packages/%s/DESCRIPTION' % PKG_NAME +try: + PKG_VERS = urlopen(PKG_PATH).\ + read().splitlines()[3].split()[1].decode('utf-8') + OFFLINE = False +except: + OFFLINE = True + +ARGV = sys.argv + +def cleanup(): + """ Clean up the environnment. """ + sys.argv = ARGV + if os.path.exists(RPackage.CACHEDIR): + shutil.rmtree(RPackage.CACHEDIR) + for spec in glob.glob('R-*.spec'): + os.remove(spec) diff --git a/test/test_R2rpm.py b/test/test_R2rpm.py index 9655c8a..e55883b 100644 --- a/test/test_R2rpm.py +++ b/test/test_R2rpm.py @@ -16,61 +16,18 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ R2rpm tests script """ -# Cases: -# -p - -# -u - -# --> download sources -# -s --force-dl - -# -u --force-dl - -# --> download sources -# -s - -# -u --force-dl --keep-sources - import os -import shutil import sys import unittest -import urllib.request as urllib_request -sys.path.insert(0, os.path.abspath('../')) -from r2spec.r2spec_obj import R2rpm, setup_parser +from test import * +from r2spec.r2spec_obj import R2rpm from r2spec import R2specError -BASE_URL = 'https://fedorahosted.org/releases/F/e/FedoraReview/' -R_SRC = 'Rdummypkg_1.0.tar.gz' -R_TEST_SRC = BASE_URL + R_SRC - - -def clean_directory(): - """ Remove the 'Rdummypkg' folder is present in the current - working directory. - """ - if os.path.exists('Rdummypkg') and os.path.isdir('Rdummypkg'): - shutil.rmtree('Rdummypkg') - if os.path.exists(R_SRC) and os.path.isfile(R_SRC): - os.remove(R_SRC) - - -def download_sources(url): - """ Download the given url in the current working directory. - :arg url, url to the object to download - """ - sources = url.rsplit('/', 1)[1] - remotefile = urllib_request.urlopen(url) - with open(sources, 'wb') as localfile: - localfile.write(remotefile.read()) - return sources - class R2rpmtests(unittest.TestCase): """ R2rpm tests. """ @@ -80,139 +37,32 @@ class R2rpmtests(unittest.TestCase): unittest.TestCase.__init__(self, methodName) def setUp(self): - """ Set up the environnment, ran before every tests. """ - clean_directory() + cleanup() def tearDown(self): - """ Clean up the environnment, ran after every tests. """ - clean_directory() - - def test_r2rpm_empty_arguments(self): - """ Test R2rpm with no arguments. """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - try: - R2rpm().main(arg) - except R2specError as err: - self.assertEqual('Not enough argument given, see -h/--help', - err.value) - - def test_r2rpm_url(self): - """ Test R2rpm using an url direct to the sources. """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.url = R_TEST_SRC - R2rpm().main(arg) - - def test_r2rpm_package(self): - """ Test R2rpm using a package name. """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.package = 'tkWidgets' - R2rpm().main(arg) - - def test_r2rpm_package_and_mock(self): - """ Test R2rpm using a package name using mock. """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.package = 'tkWidgets' - arg.mock_config = 'fedora-rawhide-x86_64' - R2rpm().main(arg) - - def test_r2rpm_sources(self): - """ Test R2rpm using a package name. """ - sources = download_sources(R_TEST_SRC) - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.sources = sources - R2rpm().main(arg) - - def test_r2rpm_sources_repo(self): - """ Test R2rpm using a package name and a repo. """ - sources = download_sources(R_TEST_SRC) - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.sources = sources - arg.repo = 'cran' - R2rpm().main(arg) - - def test_r2rpm_url_force_spec(self): - """ Test R2rpm using an url direct to the sources and forcing - spec generation. - """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.url = R_TEST_SRC - arg.force_spec = True - R2rpm().main(arg) - - def test_r2rpm_sources_force_spec_repo(self): - """ Test R2rpm using a package name, forcing the generation - of the spec file and setting the repo. - """ - sources = download_sources(R_TEST_SRC) - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.sources = sources - arg.repo = 'cran' - arg.force_spec = True - R2rpm().main(arg) - - def test_r2rpm_package_force_spec(self): - """ Test R2rpm using a package name and forcing - spec generation. - """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.package = 'tkWidgets' - arg.force_spec = True - R2rpm().main(arg) - - def test_r2rpm_package_force_spec_and_mock(self): - """ Test R2rpm using a package name using mock and forcing - spec generation. - """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.package = 'tkWidgets' - arg.mock_config = 'fedora-rawhide-x86_64' - arg.force_spec = True - R2rpm().main(arg) - - def test_r2rpm_sources_force_spec(self): - """ Test R2rpm using a package name and forcing - spec generation. - """ - sources = download_sources(R_TEST_SRC) - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.sources = sources - arg.force_spec = True - R2rpm().main(arg) - - def test_r2rpm_url_force_spec_force_dl(self): - """ Test R2rpm using an url direct to the sources and forcing - spec generation and the download. - """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.url = R_TEST_SRC - arg.force_spec = True - arg.force_dl = True - R2rpm().main(arg) - - def test_r2rpm_url_force_spec_force_dl_keep_sources(self): - """ Test R2rpm using an url direct to the sources, forcing - spec generation and the download and keep the sources. - """ - parser = setup_parser('R2rpm') - arg = parser.parse_args() - arg.url = R_TEST_SRC - arg.force_spec = True - arg.force_dl = True - arg.keep_sources = True - R2rpm().main(arg) - self.assertTrue(os.path.exists('Rdummypkg')) + cleanup() + + def test_r2rpm_empty_package(self): + with self.assertRaises(SystemExit): + R2rpm().main() + + def test_r2rpm_invalid_package(self): + sys.argv = ARGV + ['asdfasdfasdfasdf'] + with self.assertRaises(R2specError): + R2rpm().main() + + @unittest.skipIf(OFFLINE, 'offline') + def test_r2rpm_valid_package(self): + sys.argv = ARGV + [PKG_NAME, '--resultdir', RESULTDIR, + '-r', 'fedora-rawhide-x86_64'] + R2rpm().main() + tarball = '%s/%s_%s.tar.gz' % (RPackage.CACHEDIR, PKG_NAME, PKG_VERS) + self.assertTrue(os.path.isfile(tarball)) + self.assertTrue(os.path.isfile('R-%s.spec' % PKG_NAME)) + rpms = glob.glob('%s/R-%s-%s*.rpm' % (RESULTDIR, PKG_NAME, PKG_VERS)) + self.assertTrue(len(rpms) > 0) + for rpm in rpms: + self.assertTrue(os.path.isfile(rpm)) suite = unittest.TestLoader().loadTestsFromTestCase(R2rpmtests) unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/test_R2spec.py b/test/test_R2spec.py index 87cd2ee..d357f80 100644 --- a/test/test_R2spec.py +++ b/test/test_R2spec.py @@ -16,62 +16,18 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ R2spec tests script """ -# Cases: -# -p - -# -u - -# --> download sources -# -s --force-dl - -# -u --force-dl - -# --> download sources -# -s - -# -u --force-dl --keep-sources - -import argparse import os -import shutil import sys import unittest -import urllib.request as urllib_request -sys.path.insert(0, os.path.abspath('../')) -from r2spec.r2spec_obj import R2spec, setup_parser +from test import * +from r2spec.r2spec_obj import R2spec from r2spec import R2specError -BASE_URL = 'https://fedorahosted.org/releases/F/e/FedoraReview/' -R_SRC = 'Rdummypkg_1.0.tar.gz' -R_TEST_SRC = BASE_URL + R_SRC - - -def clean_directory(): - """ Remove the 'Rdummypkg' folder is present in the current - working directory. - """ - if os.path.exists('Rdummypkg') and os.path.isdir('Rdummypkg'): - shutil.rmtree('Rdummypkg') - if os.path.exists(R_SRC) and os.path.isfile(R_SRC): - os.remove(R_SRC) - - -def download_sources(url): - """ Download the given url in the current working directory. - :arg url, url to the object to download - """ - sources = url.rsplit('/', 1)[1] - remotefile = urllib_request.urlopen(url) - with open(sources, 'wb') as localfile: - localfile.write(remotefile.read()) - return sources - class R2spectests(unittest.TestCase): """ R2spec tests. """ @@ -81,85 +37,27 @@ class R2spectests(unittest.TestCase): unittest.TestCase.__init__(self, methodName) def setUp(self): - """ Set up the environnment, ran before every tests. """ - clean_directory() + cleanup() def tearDown(self): - """ Clean up the environnment, ran after every tests. """ - clean_directory() - - def test_r2spec_empty_arguments(self): - """ Test R2spec with no arguments """ - parser = setup_parser('R2spec') - arg = parser.parse_args() - try: - R2spec().main(arg) - except R2specError as err: - self.assertEqual('Not enough argument given, see -h/--help', - err.value) - - def test_r2spec_url(self): - """ Test R2spec using an url direct to the sources """ - parser = setup_parser('R2spec') - arg = parser.parse_args() - arg.url = R_TEST_SRC - R2spec().main(arg) - - def test_r2spec_package(self): - """ Test R2spec using a package name """ - parser = setup_parser('R2spec') - arg = parser.parse_args() - arg.package = 'tkWidgets' - R2spec().main(arg) - - def test_r2spec_sources(self): - """ Test R2spec using a package name """ - sources = download_sources(R_TEST_SRC) - parser = setup_parser('R2spec') - arg = parser.parse_args() - arg.sources = sources - R2spec().main(arg) - - def test_r2spec_sources_repo(self): - """ Test R2spec using a package name with a repo name """ - sources = download_sources(R_TEST_SRC) - parser = setup_parser('R2spec') - arg = parser.parse_args() - arg.sources = sources - arg.repo = 'cran' - R2spec().main(arg) - - def test_r2spec_url_force_dl(self): - """ Test R2spec using an url direct to the sources and force - download. - """ - parser = setup_parser('R2spec') - arg = parser.parse_args() - arg.url = R_TEST_SRC - arg.force_dl = True - R2spec().main(arg) - - def test_r2spec_package_force_dl(self): - """ Test R2spec using a package name and force download. """ - parser = setup_parser('R2spec') - arg = parser.parse_args() - arg.package = 'tkWidgets' - arg.force_dl = True - R2spec().main(arg) - - def test_r2spec_url_force_dl_keep_sources(self): - """ Test R2spec using an url direct to the sources, force - download and keep the sources. - """ - if os.path.exists('Rdummypkg') and os.path.isfolder('Rdummypkg'): - shutil.remove('Rdummypkg') - parser = setup_parser('R2spec') - arg = parser.parse_args() - arg.url = R_TEST_SRC - arg.force_dl = True - arg.keep_sources = True - R2spec().main(arg) - self.assertTrue(os.path.exists('Rdummypkg')) + cleanup() + + def test_r2spec_empty_package(self): + with self.assertRaises(SystemExit): + R2spec().main() + + def test_r2spec_invalid_package(self): + sys.argv = ARGV + ['asdfasdfasdfasdf'] + with self.assertRaises(R2specError): + R2spec().main() + + @unittest.skipIf(OFFLINE, 'offline') + def test_r2spec_valid_package(self): + sys.argv = ARGV + [PKG_NAME] + R2spec().main() + tarball = '%s/%s_%s.tar.gz' % (RPackage.CACHEDIR, PKG_NAME, PKG_VERS) + self.assertTrue(os.path.isfile(tarball)) + self.assertTrue(os.path.isfile('R-%s.spec' % PKG_NAME)) suite = unittest.TestLoader().loadTestsFromTestCase(R2spectests) unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests.py b/tests.py index b1920d6..91ea6c0 100755 --- a/tests.py +++ b/tests.py @@ -16,11 +16,10 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar """ R2spec launcher script """ -from __future__ import absolute_import, division, print_function - import unittest from test.test_R2spec import R2spectests from test.test_R2rpm import R2rpmtests From 39b9b80fc698c00c4b1343bc21b8cb20f9cbb1fb Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 21:08:42 +0000 Subject: [PATCH 17/21] update various files --- diff --git a/.gitignore b/.gitignore index 9ff5781..1eae578 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ build/ dist/ MANIFEST - +R2spec.egg-info diff --git a/CHANGELOG b/CHANGELOG index eb8a7c5..b97056a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,12 @@ #*********************************************** # R2spec # -# R2spec is made to help to the creation of R specfile -# It works from a source file (*.tar.gz) or a url (https://...*.tar.gz) +# R2spec is made to help to the creation of R specfile from a package name # +# Started the 13th February 2008by Pierre-Yves chibon # -# Made the 13th February 2008 -# by Pierre-Yves chibon +# Version 6.0.0 -- 9th January 2026 +# - Overhaul to follow https://fedoraproject.org/wiki/Changes/Adopt_new_R_Packaging_Guidelines # # Version 4.1.0 -- # - Features diff --git a/MANIFEST.in b/MANIFEST.in index dfe4f97..836b60f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include CHANGELOG INSTALL README LICENSE PKG-INFO R2spec.spec -include repos.cfg r2spec/specfile.tpl tests.py +include r2spec/repos.cfg r2spec/specfile.tpl tests.py include R2spec.1 R2rpm.1 include test tests.py diff --git a/make_release b/make_release index ff033a6..77bde08 100755 --- a/make_release +++ b/make_release @@ -1,6 +1,5 @@ #!/bin/bash -# reads version from setup.py, create a source tarball and optionally -# upload to fedorahosted +# reads version from setup.py, create a source tarball read -i 'y' -e -p "Did you update the version on PKG-INFO and setup.py? (y/n): " if [ "$REPLY" = "n" ];then @@ -15,10 +14,3 @@ fi python setup.py clean python setup.py build sdist - -read -i 'n' -e -p "Do you want to upload file $dfile to fedorahosted? (y/n): " - -if [ "$REPLY" = "y" ];then - scp "${dfile}" fedorahosted.org:r2spec -fi - From 510b00bbbd162fc5ee3529e72699e094035af708 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 09 2026 21:26:50 +0000 Subject: [PATCH 18/21] rewrite the man pages --- diff --git a/R2rpm.1 b/R2rpm.1 index ea2ccca..7915f49 100644 --- a/R2rpm.1 +++ b/R2rpm.1 @@ -1,72 +1,33 @@ .TH "R2rpm" 1 .SH NAME -R2rpm \- Automatically generates RPM for R libraries. +R2rpm \- enerate RPM binaries for R packages .SH SYNOPSIS .B R2rpm --p -.br -.B R2rpm --u -.br -.B R2rpm -- s +[OPTIONS] package ... .SH DESCRIPTION - -This tool generate RPM for R libraries according to the Fedora guidelines for R packaging. +This tool generates RPM binaries for R packages according to the Fedora guidelines for R packaging. These guidelines are available at: https://docs.fedoraproject.org/en-US/packaging-guidelines/R/ -.SH USAGE -This program generates RPM file for R libraries for Fedora. -.HP -R2rpm -p - -It will browse the configure R repositories for this specified package name. -If found, the package will be downloaded, extracted, the information -contained in the sources are read (especially those contained in the DESCRIPTION -file). The sources are then removed from the current working directory (unless -specified otherwise). -Finally the spec file is written to the filesystem using the information collected. - -.HP -R2rpm -u - -This alternative expects directly an URL for the sources of the R packages. -R2spec will try to fill the spec as best as possible from the information -collected. - -.HP -R2rpm -s - -This alternative uses local sources. The sources are copied to the sourcedir -as defined by rpm. They are then extracted and the spec file generated. +It runs R2spec to generate a spec file in the current working directory. +Then, buils the RPM using mock. +.SH POSITIONAL ARGUMENTS +.B package + The name of the package in the upstream repository. +.B ... + Further arguments passed to mock. .SH OPTIONS -.B -p / --package - The name of the package to search in the main repositories collected. +.B --version + Shows the program version. .sp -.B -u / --url - Direct URL to the source of the R package. +.B --no-rpmautospec + Do not use rpmautospec. .sp -.B -s / --sources - The sources of the R package present locally on the filesystem. -.sp -.B --force-dl - Enforce the download of the sources even if they are present on the system. -.sp -.B --keep-sources - Do not remove the sources once extracted. -.sp -.B --repo - Enable to specify a repository for a local package. -.sp -.B --name - Name of the packager to use in the spec file. -.sp -.B --email - Email of the packager to use in the spec file. +.B --no-check + Do not include the %check section in the generated spec file. .sp .B --verbose Provides a some detailed output of what's going on. @@ -75,21 +36,11 @@ as defined by rpm. They are then extracted and the spec file generated. Provides much more information on what's going on, including errors when they occur. .sp .B --force-spec - Enforce the generation of the spec even if it already exists on the system. - -.SH FILES -.I /etc/R2spec/repos.cfg -.RS -The configuration file for the R repositories. -.RE - -.I /usr/lib/python2.7/site-packages/r2spec/specfile.tpl -.RS -Template used to generate the spec file. -.RE + Force the generation of the spec even if it already exists. .SH AUTHORS Pierre-Yves Chibon + Iñaki Úcar .sp .sp diff --git a/R2spec.1 b/R2spec.1 index 987e947..80d8246 100644 --- a/R2spec.1 +++ b/R2spec.1 @@ -1,72 +1,33 @@ .TH "R2spec" 1 .SH NAME -R2spec \- Automatically generates RPM spec file for R libraries. +R2spec \- generate RPM spec files for R packages .SH SYNOPSIS .B R2spec --p -.br -.B R2spec --u -.br -.B R2spec -- s +[OPTIONS] package .SH DESCRIPTION - -This tool generate RPM spec file for R libraries according to the Fedora guidelines for R packaging. +This tool generates RPM spec files for R packages according to the Fedora guidelines for R packaging. These guidelines are available at: https://docs.fedoraproject.org/en-US/packaging-guidelines/R/ -.SH USAGE -This program generates spec file for R libraries for Fedora. -.HP -R2spec -p - -It will browse the configure R repositories for this specified package name. -If found, the package will be downloaded, extracted, the information -contained in the sources are read (especially those contained in the DESCRIPTION -file). The sources are then removed from the current working directory (unless -specified otherwise). -Finally the spec file is written to the filesystem using the information collected. - -.HP -R2spec -u - -This alternative expects directly an URL for the sources of the R packages. -R2spec will try to fill the spec as best as possible from the information -collected. - -.HP -R2spec -s - -This alternative uses local sources. The sources are copied to the sourcedir -as defined by rpm. They are then extracted and the spec file generated. +It searches for the package in the official repositories (CRAN and BioC). +If found, the package is downloaded to $XDG_CACHE_HOME/R2spec, +and the necessary metadata is extracted from the DESCRIPTION file. +Finally the spec file is written to the current working directory using the information collected. +.SH POSITIONAL ARGUMENTS +.B package + The name of the package in the upstream repository. .SH OPTIONS -.B -p / --package - The name of the package to search in the main repositories collected. +.B --version + Shows the program version. .sp -.B -u / --url - Direct URL to the source of the R package. +.B --no-rpmautospec + Do not use rpmautospec. .sp -.B -s / --sources - The sources of the R package present locally on the filesystem. -.sp -.B --force-dl - Enforce the download of the sources even if they are present on the system. -.sp -.B --keep-sources - Do not remove the sources once extracted. -.sp -.B --repo - Enable to specify a repository for a local package. -.sp -.B --name - Name of the packager to use in the spec file. -.sp -.B --email - Email of the packager to use in the spec file. +.B --no-check + Do not include the %check section in the generated spec file. .sp .B --verbose Provides a some detailed output of what's going on. @@ -74,19 +35,9 @@ as defined by rpm. They are then extracted and the spec file generated. .B --debug Provides much more information on what's going on, including errors when they occur. -.SH FILES -.I /etc/R2spec/repos.cfg -.RS -The configuration file for the R repositories. -.RE - -.I /usr/lib/python2.7/site-packages/r2spec/specfile.tpl -.RS -Template used to generate the spec file. -.RE - .SH AUTHORS Pierre-Yves Chibon + Iñaki Úcar .sp .sp diff --git a/r2spec/rpackage.py b/r2spec/rpackage.py index 36d6b92..bc34561 100644 --- a/r2spec/rpackage.py +++ b/r2spec/rpackage.py @@ -47,7 +47,7 @@ class RPackage(object): """ This class represent information available for R packages. """ XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")) - CACHEDIR = os.path.join(XDG_CACHE_HOME, "R2rpm") + CACHEDIR = os.path.join(XDG_CACHE_HOME, "R2spec") def __init__(self, name): """ Constructor. """ From 2bac84879cee749f62fb7ccf0f2a67edaf135194 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 10 2026 00:04:27 +0000 Subject: [PATCH 19/21] setup fixes --- diff --git a/MANIFEST.in b/MANIFEST.in index 836b60f..372190d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include CHANGELOG INSTALL README LICENSE PKG-INFO R2spec.spec +include CHANGELOG INSTALL README LICENSE PKG-INFO include r2spec/repos.cfg r2spec/specfile.tpl tests.py include R2spec.1 R2rpm.1 include test tests.py diff --git a/make_release b/make_release index 77bde08..40b11eb 100755 --- a/make_release +++ b/make_release @@ -1,16 +1,15 @@ #!/bin/bash # reads version from setup.py, create a source tarball -read -i 'y' -e -p "Did you update the version on PKG-INFO and setup.py? (y/n): " -if [ "$REPLY" = "n" ];then - exit -fi - -version=`grep VERSION r2spec/__init__.py | sed "s/.*'\(.*\)'.*/\1/"` -dfile="dist/R2spec-$version.tar.gz" -if [ -f ${dfile} ]; then - rm -v $dfile -fi - +rm -rf build dist R2spec.egg-info python setup.py clean python setup.py build sdist + +# setuptools now lowercases the name; hack time! +version=`grep VERSION r2spec/__init__.py | sed "s/.*'\(.*\)'.*/\1/"` +cd dist +tar xf r2spec-$version.tar.gz +mv r2spec-$version R2spec-$version +tar czf R2spec-$version.tar.gz R2spec-$version +rm -rf R2spec-$version r2spec-$version.tar.gz +cd - diff --git a/setup.py b/setup.py index e76538a..db02fd1 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,6 @@ from r2spec import VERSION setup( name = 'R2spec', description = 'R2spec is a small python tool that generates spec file and rpm for R libraries.', - description_long = '', data_files = [('/usr/share/man/man1/', [ 'R2spec.1', 'R2rpm.1' ] )], version = VERSION, author = 'Pierre-Yves Chibon, Iñaki Úcar', From 93a5d1e682c4e6b62793f609c96114979ff6ee48 Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 10 2026 00:33:51 +0000 Subject: [PATCH 20/21] more fixes --- diff --git a/MANIFEST.in b/MANIFEST.in index 372190d..8e95041 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include CHANGELOG INSTALL README LICENSE PKG-INFO +include CHANGELOG INSTALL README.md LICENSE PKG-INFO include r2spec/repos.cfg r2spec/specfile.tpl tests.py include R2spec.1 R2rpm.1 include test tests.py diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index 19dfda2..0000000 --- a/test/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -#-*- coding: utf-8 -*- - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# (C) 2011 - Pierre-Yves Chibon -# (C) 2026 - Iñaki Úcar - -""" Makes of the test folder a module. """ -import sys -import os -import shutil -import glob -from urllib.request import urlopen - -sys.path.insert(0, os.path.abspath('../')) -from r2spec.rpackage import RPackage - -RESULTDIR = os.path.join(RPackage.CACHEDIR, 'result') -PKG_NAME = 'errors' -PKG_PATH = 'https://cran.r-project.org/web/packages/%s/DESCRIPTION' % PKG_NAME -try: - PKG_VERS = urlopen(PKG_PATH).\ - read().splitlines()[3].split()[1].decode('utf-8') - OFFLINE = False -except: - OFFLINE = True - -ARGV = sys.argv - -def cleanup(): - """ Clean up the environnment. """ - sys.argv = ARGV - if os.path.exists(RPackage.CACHEDIR): - shutil.rmtree(RPackage.CACHEDIR) - for spec in glob.glob('R-*.spec'): - os.remove(spec) diff --git a/test/test_R2rpm.py b/test/test_R2rpm.py index e55883b..0fc6917 100644 --- a/test/test_R2rpm.py +++ b/test/test_R2rpm.py @@ -24,7 +24,7 @@ import os import sys import unittest -from test import * +from test.test_helpers import * from r2spec.r2spec_obj import R2rpm from r2spec import R2specError diff --git a/test/test_R2spec.py b/test/test_R2spec.py index d357f80..03f2699 100644 --- a/test/test_R2spec.py +++ b/test/test_R2spec.py @@ -24,7 +24,7 @@ import os import sys import unittest -from test import * +from test.test_helpers import * from r2spec.r2spec_obj import R2spec from r2spec import R2specError diff --git a/test/test_helpers.py b/test/test_helpers.py new file mode 100644 index 0000000..7ba1610 --- /dev/null +++ b/test/test_helpers.py @@ -0,0 +1,48 @@ +#-*- coding: utf-8 -*- + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# (C) 2011 - Pierre-Yves Chibon +# (C) 2026 - Iñaki Úcar + +""" Helpers for tests. """ +import sys +import os +import shutil +import glob +from urllib.request import urlopen + +sys.path.insert(0, os.path.abspath('../')) +from r2spec.rpackage import RPackage + +RESULTDIR = os.path.join(RPackage.CACHEDIR, 'result') +PKG_NAME = 'errors' +PKG_PATH = 'https://cran.r-project.org/web/packages/%s/DESCRIPTION' % PKG_NAME +try: + PKG_VERS = urlopen(PKG_PATH).\ + read().splitlines()[3].split()[1].decode('utf-8') + OFFLINE = False +except: + OFFLINE = True + +ARGV = sys.argv + +def cleanup(): + """ Clean up the environnment. """ + sys.argv = ARGV + if os.path.exists(RPackage.CACHEDIR): + shutil.rmtree(RPackage.CACHEDIR) + for spec in glob.glob('R-*.spec'): + os.remove(spec) From faca0ea7827374b66b66f89209b4fc2d88c1b9ba Mon Sep 17 00:00:00 2001 From: Iñaki Úcar Date: Jan 10 2026 00:34:33 +0000 Subject: [PATCH 21/21] update spec file --- diff --git a/R2spec.spec b/R2spec.spec index 9bb44f2..5d39780 100644 --- a/R2spec.spec +++ b/R2spec.spec @@ -1,235 +1,45 @@ -# sitelib for noarch packages, sitearch for others (remove the unneeded one) -%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} - Name: R2spec -Version: 5.0.0 -Release: 1%{?dist} -Summary: Python script to generate R spec file +Version: 6.0.0 +Release: %autorelease +Summary: Python script to generate R spec files and RPMs -Group: Development/Languages -License: GPLv3+ +License: GPL-3.0-or-later URL: https://pagure.io/r2spec -Source0: https://releases.pagure.org/r2spec/R2spec-%{version}.tar.gz -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Source0: https://releases.pagure.org/r2spec/%{name}-%{version}.tar.gz BuildArch: noarch -BuildRequires: python-devel -Requires: R python-jinja2 wget fedora-packager -# On EL5 we need a recent version of python -Requires: python >= python-2.6 python-argparse >= python-argparse-1.2.1 +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-jinja2 +Requires: python3-jinja2 +Requires: mock Provides: R2rpm >= 1.0.0 %description -R2spec is a small python tool that generates spec file for R libraries. -It can work from a URL or a tarball. -R2spec provides R2rpm which generates rpm for R libraries using the -R2spec API. +R2spec is a small python tool that generates spec files for R packages. +R2spec provides R2rpm which generates RPM binaries for R packages using +the R2spec API. %prep %setup -q %build -%{__python} setup.py build +%{__python3} setup.py build %install -rm -rf %{buildroot} -%{__python} setup.py install --root=%{buildroot} -install r2spec/specfile.tpl %{buildroot}/%{python_sitelib}/r2spec/ -chmod -x %{buildroot}/%{python_sitelib}/r2spec/specfile.tpl - -%clean -rm -rf %{buildroot} +%{__python3} setup.py install --root=%{buildroot} -## Only work localy, needs internet -#%check -#%{__python} tests.py +%check +%{__python3} tests.py %files -#-f installed_files2 -%defattr(-,root,root,-) -%doc README LICENSE CHANGELOG -%{python_sitelib}/* -%config(noreplace) %{_sysconfdir}/%{name}/repos.cfg +%doc README.md CHANGELOG +%license LICENSE +%{python3_sitelib}/* %{_bindir}/%{name} %{_bindir}/R2rpm -%{_mandir}/man1/%{name}.1.gz -%{_mandir}/man1/R2rpm.1.gz +%{_mandir}/man1/%{name}.1* +%{_mandir}/man1/R2rpm.1* %changelog -* Mon Sep 09 2019 Pierre-Yves Chibon - 5.0.0-1 -- Update to 5.0.0 -- Port to python3 -- Use textwrap to format package description -- Update the spec file for more recent standards -- Use https links -- Use rpm Suggests for R Suggests -- Disable explicit Requires/Suggests by default -- Use encoding from DESCRIPTION when reading it -- Update CRAN URL to use canonical form -- Check NeedsCompilation for arch dependence if available - -* Thu Jun 04 2015 Pierre-Yves Chibon - 4.2.1-1 -- Bump the version - -* Thu Jun 04 2015 Pierre-Yves Chibon - 4.2.0-1 -- Update to 4.2.0 -- Switch from '.format' invocations to '%'; backward compatible (Allen S. Rout) -- Fix deps on EL5 -- Document in the specfile the dependencies world according to R (Allen S. Rout) -- Accept package names with leading 'R-'.. (Allen S. Rout) -- Fail elegantly is the rpmbuild folder does not exist (RHBZ#901771) -- Do not mark DESCRIPTION R package file as doc (Castedo Ellerman) - -* Sat Feb 11 2012 Pierre-Yves Chibon - 4.1.0-1 -- Update to 4.1.0 - -* Thu Jan 12 2012 Fedora Release Engineering - 4.0.0-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild - -* Sat Nov 12 2011 Pierre-Yves Chibon - 4.0.0-1 -- Update to 4.0.0 which is an almost complete rewrite - -* Mon Feb 07 2011 Fedora Release Engineering - 3.0.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jul 28 2010 pingou 3.0.3-1 -- Release 3.0.3 - -* Tue Jul 27 2010 pingou 3.0.2-3 -- Change python to %%{__python} (enables to specify - the python version at build time) Request from Olivier Lahaye - -* Wed Jul 21 2010 David Malcolm - 3.0.2-2 -- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild - -* Tue Jun 29 2010 pingou 3.0.2-1 -- Release 3.0.2 - -* Mon Jun 28 2010 pingou 3.0.1-1 -- Release version 3.0.1 -- Update the description (R2spec is now more a tool than a script) - -* Wed May 05 2010 pingou 3.0.0-1 -- Release version 3.0.0 -- Update the description - -* Wed May 05 2010 pingou 3.0.0-0.9 -- Prerelease 0.9 -- Small bug fixes -- The argument to rpmbuild is configurable -- Fix typo in changelog -- Add fedora-packager as Requires - -* Sat Mar 27 2010 ingou 3.0.0-0.8 -- Prerelease 0.8 -- Enable to specify the command in the conf file - -* Sat Mar 27 2010 ingou 3.0.0-0.7 -- Prerelease 0.7 -- Fix the formatting of the description -- Option to use mock to build the RPMs - -* Fri Mar 26 2010 ingou 3.0.0-0.6 -- Prerelease 0.6 -- Fix R2rpm.py - -* Thu Mar 25 2010 Pingou 3.0.0-0.5 -- Prerelease 0.5 -- Do not print the todo while running R2rpm -- Fix summary if ends with a dot -- Refactor the function to read the rpm macro -- Change UTF-8 to utf-8 to make emacs happy - -* Wed Mar 24 2010 Pingou 3.0.0-0.4 -- Prerelease 0.4 -- Fix the description to fit in the length -- Add wget in the requires - -* Wed Mar 24 2010 Pingou 3.0.0-0.3 -- Prerelease 0.3 -- Add the -p option to build from a package name -- Output the rpm generated - -* Tue Mar 23 2010 Pingou 3.0.0-0.2 -- Prerelease 0.2 -- Add the -p option to build from a package name -- Add the man page for R2rpm -- Fix release -- Fix changelog - -* Sun Mar 07 2010 Pingou 3.0.0-0.1 -- Prerelease 0.1 -- Add the R2rpm script - -* Sun Aug 02 2009 Pingou 2.5.3-1 -- New upstream release - -* Fri Jul 24 2009 Fedora Release Engineering - 2.5.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Sun Mar 22 2009 Pingou 2.5.2-2 -- Correct the source0 - -* Sun Mar 22 2009 Pingou 2.5.2-1 -- New upstream release - -* Mon Feb 23 2009 Fedora Release Engineering - 2.5.1-7 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sun Dec 07 2008 Pingou 2.5.1-6 -- Correct the third sed - -* Sun Dec 07 2008 Pingou 2.5.1-5 -- Add the new sed to change the ~ - -* Sun Dec 07 2008 Pingou 2.5.1-4 -- Remove the Patch0 - -* Fri Dec 05 2008 Pingou 2.5.1-3 -- Apply patch for copy of the sources - -* Sat Nov 29 2008 Ignacio Vazquez-Abrams - 2.5.1-2 -- Rebuild for Python 2.6 - -* Tue Oct 07 2008 Pingou 2.5.1-1 -- New upstream release - -* Sun Aug 31 2008 Pingou 2.5.0-3 -- Bring __init__.py back :) - -* Sun Aug 31 2008 Pingou 2.5.0-2 -- Change Source0 to the correct one -- Correct a bug SOURCES != SOURCe -- Remove file __init__.py -- Change defattr(-,root,root) to defattr(-,root,root,-) - -* Sun Aug 31 2008 Pingou 2.5.0-1 -- New upstream release - -* Thu Aug 28 2008 Pingou 2.4.2-1 -- Update to version 2.4.2 - -* Wed Aug 20 2008 Pingou 2.4.1-1 -- Update to version 2.4.1 - -* Sun Aug 18 2008 Pingou 2.4.0-1 -- Update to version 2.4.0 -- Addition of the spec into the sources - -* Mon Aug 11 2008 Pingou 2.3-2 -- Change the source0 and url thanks to fedorahosted.org - -* Sun Aug 10 2008 Pingou 2.3-1 -- Update to version 2.3 - -* Wed Jul 30 2008 Pingou 2.2-1 -- Update to version 2.2 - -* Wed Jul 30 2008 Pingou 2.1-1 -- Update to version 2.1 - -* Wed Jul 30 2008 Pingou 2.0-1 -- Update to version 2.0 - -* Tue Jul 29 2008 Pingou 1.3-1 -- First RPM for Fedora +%autochangelog