From 9f96c5c74869dc0a2892ae40098838269a403808 Mon Sep 17 00:00:00 2001 From: Martin Holmer Date: Mon, 15 Apr 2019 15:45:27 -0400 Subject: [PATCH] Simplify --local option and generalize its version logic --- README.md | 27 +++++++------ RELEASES.md | 16 ++++++++ pkgbld/cli.py | 34 ++++++++--------- pkgbld/release.py | 96 ++++++++++++++++++++++++----------------------- 4 files changed, 95 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index f066837..044102f 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ and getting something like this screen output: ``` $ pbrelease --help usage: pbrelease REPOSITORY_NAME PACKAGE_NAME MODEL_VERSION - [--help] [--local LOCAL] [--dryrun] [--version] + [--help] [--local] [--dryrun] [--version] Creates conda packages named PACKAGE_NAME for the PSL model in REPOSITORY_NAME that has a GitHub release named MODEL_VERSION. The packages are built locally @@ -76,12 +76,10 @@ positional arguments: optional arguments: -h, --help show this help message and exit - --local LOCAL optional flag where LOCAL is name of top-level directory - containing the model conda.recipe directory used to build - the package that is installed on local computer; no --local - option implies model source code is cloned from GitHub - REPOSITORY_NAME for MODEL_RELEASE and packages are uploaded - to Anaconda Cloud PSLmodels channel for public distribution + --local optional flag that causes package to be built from current + source code and installed on local computer without + packages being uploaded to Anaconda Cloud PSLmodels + channel. --dryrun optional flag that writes execution plan to stdout and quits without executing plan --version optional flag that writes Package-Builder release version @@ -142,15 +140,17 @@ $ pbrelease Behavioral-Responses behresp 0.5.0 To use `pbrelease` to make a **local package** from the current (even uncommitted) code on your current branch, make the top-level directory of the repo's source-code tree the current working directory. Then -use the `--local .` option as shown here: +use the `--local` option as shown here: ``` $ cd Tax-Calculator -$ pbrelease Tax-Calculator taxcalc 0.0.0 --local . +$ pbrelease Tax-Calculator taxcalc 0.0.0 --local ``` -This will produce a local package on your computer for testing or validation -work. You can uninstall this local package at any time using this command: +This will produce a local package on your computer for testing or +validation work. The above example specifies version 0.0.0, but you +can specify any version you want. You can uninstall this local +package at any time using this command: ``` $ conda uninstall taxcalc --yes @@ -190,7 +190,6 @@ work correctly. Here are the model code criteria: The best place to look at code that meets all these criteria is the PSLmodels Behavioral-Responses repository. -Version 0.0.0 indicates a local package; the `pbrelease` tool will +Version 0.0.0 indicates the earliest version; the `pbrelease` tool will automatically replace (in a temporary copy of your model code) the -0.0.0 with the X.Y.Z version specified when you execute `pbrelease` -without using the `--local` option. +0.0.0 with the X.Y.Z version specified when you execute `pbrelease`. diff --git a/RELEASES.md b/RELEASES.md index 334d454..2a1729f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,22 @@ Go [here](https://github.com/PSLmodels~/Package-Builder/pulls?q=is%3Apr+is%3Aclo for a complete commit history. +2019-04-15 Release 0.22.0 +------------------------- +(last merged pull request is +[#164](https://github.com/PSLmodels/Package-Builder/pull/164)) + +**API Changes** +- None + +**New Features** +- Simplify `--local` so that it is a simple boolean option and allow the locally installed package to be specified with any version + [[#164](https://github.com/PSLmodels/Package-Builder/pull/164) + by Martin Holmer] + +**Bug Fixes** +- None + 2019-03-13 Release 0.21.0 ------------------------- (last merged pull request is diff --git a/pkgbld/cli.py b/pkgbld/cli.py index 76e6bd8..8ef9929 100644 --- a/pkgbld/cli.py +++ b/pkgbld/cli.py @@ -18,7 +18,7 @@ def main(): """ # parse command-line arguments: usage_str = ('pbrelease REPOSITORY_NAME PACKAGE_NAME MODEL_VERSION\n' - ' [--help] [--local LOCAL] ' + ' [--help] [--local] ' '[--dryrun] [--version]') parser = argparse.ArgumentParser( prog='', @@ -45,16 +45,12 @@ def main(): 'Example: 1.0.1'), default=None) parser.add_argument('--local', - help=('optional flag where LOCAL is name of top-level ' - 'directory containing the model conda.recipe ' - 'directory used to build the package that is ' - 'installed on local computer; ' - 'no --local option implies model source code ' - 'is cloned from GitHub REPOSITORY_NAME for ' - 'MODEL_RELEASE and packages are uploaded to ' - 'Anaconda Cloud PSLmodels channel for public ' - 'distribution'), - default=None) + help=('optional flag that causes package to be ' + 'built from current source code and installed ' + 'on local computer without packages being ' + 'uploaded to Anaconda Cloud PSLmodels channel.'), + default=False, + action="store_true") parser.add_argument('--dryrun', help=('optional flag that writes execution plan ' 'to stdout and quits without executing plan'), @@ -90,17 +86,19 @@ def main(): emsg += ('ERROR: Anaconda token file {} ' 'does not exist\n'.format(pkgbld.ANACONDA_TOKEN_FILE)) if args.local: - if not os.path.isdir(args.local): - emsg += ('ERROR: LOCAL directory {} ' - 'does not exist\n'.format(args.local)) - if version != '0.0.0': - emsg += ('ERROR: MODEL_VERSION {} is not 0.0.0 ' - 'when using --local option\n'.format(version)) + cwd = os.getcwd() + if not cwd.endswith(repo_name): + emsg += ('ERROR: cwd={} does not correspond to ' + 'REPOSITORY_NAME={}\n'.format(cwd, repo_name)) + local_pkgname = os.path.join('.', pkg_name) + if not os.path.isdir(local_pkgname): + emsg += ('ERROR: cwd={} does not contain ' + 'subdirectory {} '.format(cwd, pkg_name)) if emsg: print(emsg) print('USAGE:', usage_str) return 1 # call pkgbld release function with specified parameters pkgbld.release(repo_name, pkg_name, version, - localdir=args.local, dryrun=args.dryrun) + local=args.local, dryrun=args.dryrun) return 0 diff --git a/pkgbld/release.py b/pkgbld/release.py index f8f8721..1868930 100644 --- a/pkgbld/release.py +++ b/pkgbld/release.py @@ -31,16 +31,16 @@ BUILDS_DIR = 'pkgbld_output' -def release(repo_name, pkg_name, version, localdir=None, dryrun=False): +def release(repo_name, pkg_name, version, local=False, dryrun=False): """ - If localdir==None, conduct build using cloned source code and - upload to Anaconda Cloud of conda packages for each operating-system + If local==False, conduct build using cloned source code and + upload to Anaconda Cloud conda packages for each operating-system platform and Python version for the specified Policy Simulation Library (PSL) model and GitHub release version. - If localdir==string, build from source code in localdir and skip the - convert and upload steps instead installing the built package on the - local computer. + If local==True, build from source code in current working directory and + skip the convert and upload steps instead installing the built package on + the local computer. Parameters ---------- @@ -54,9 +54,8 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): model version string having X.Y.Z semantic-versioning pattern; must be a release tag in the model repository - localdir: None or string - localdir containing model source code; None implies cloning - source code from GitHub repository + local: boolean + whether or not to build/install local package dryrun: boolean whether or not just the package build/upload plan is shown @@ -84,13 +83,19 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): raise ValueError('pkg_name is not a string object') if not isinstance(version, str): raise ValueError('version is not a string object') - if not (localdir is None or isinstance(localdir, str)): - raise ValueError('localdir is not None or a string object') - if isinstance(localdir, str): - if not os.path.isdir(localdir): - raise ValueError('localdir is not a directory') - if version != '0.0.0': - raise ValueError('version is not 0.0.0 when using --local option') + if not isinstance(local, bool): + raise ValueError('local is not a boolean object') + if local: + cwd = os.getcwd() + if not cwd.endswith(repo_name): + msg = ('ERROR: cwd={} does not correspond to ' + 'REPOSITORY_NAME={}\n'.format(cwd, repo_name)) + raise ValueError(msg) + local_pkgname = os.path.join('.', pkg_name) + if not os.path.isdir(local_pkgname): + msg = ('ERROR: cwd={} does not contain ' + 'subdirectory {} '.format(cwd, pkg_name)) + raise ValueError(msg) if not isinstance(dryrun, bool): raise ValueError('dryrun is not a boolean object') pattern = r'^[0-9]+\.[0-9]+\.[0-9]+$' @@ -98,11 +103,11 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): msg = 'version={} does not have X.Y.Z semantic-versioning pattern' raise ValueError(msg.format(version)) - # specify Python versions list, which depends on localdir + # specify Python versions list, which depends on local assert sys.version_info[0] == 3 local_python_version = '3.{}'.format(sys.version_info[1]) python_versions = [local_python_version] # always first in the list - if not localdir: + if not local: for ver in ALL_PYTHON_VERSIONS: if ver not in python_versions: python_versions.append(ver) @@ -113,7 +118,7 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): print(': package_name = {}'.format(pkg_name)) print(': model_version = {}'.format(version)) print(': python_versions = {}'.format(python_versions)) - if localdir: + if local: print(': Package-Builder will install package on local computer') else: print(': Package-Builder will upload model packages to:') @@ -131,12 +136,12 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): shutil.rmtree(WORKING_DIR) # copy model source code to working directory - if localdir: + if local: # copy source tree on local computer print(': Package-Builder is copying local source code') destination = os.path.join(WORKING_DIR, repo_name) ignorepattern = shutil.ignore_patterns('*.pyc', '*.html', 'test_*') - shutil.copytree(localdir, destination, ignore=ignorepattern) + shutil.copytree(cwd, destination, ignore=ignorepattern) os.chdir(WORKING_DIR) else: # clone code for model_version from model repository @@ -151,26 +156,25 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): os.chdir(repo_name) # specify version in several repository files - if not localdir: - print(': Package-Builder is setting version') - # ... specify version in meta.yaml file - u.file_revision( - filename=os.path.join('conda.recipe', 'meta.yaml'), - pattern=r'version: .*', - replacement='version: {}'.format(version) - ) - # ... specify version in setup.py file - u.file_revision( - filename='setup.py', - pattern=r'version = .*', - replacement='version = "{}"'.format(version) - ) - # ... specify version in package_name/__init__.py file - u.file_revision( - filename=os.path.join(pkg_name, '__init__.py'), - pattern=r'__version__ = .*', - replacement='__version__ = "{}"'.format(version) - ) + print(': Package-Builder is setting version') + # ... specify version in meta.yaml file + u.file_revision( + filename=os.path.join('conda.recipe', 'meta.yaml'), + pattern=r'version: .*', + replacement='version: {}'.format(version) + ) + # ... specify version in setup.py file + u.file_revision( + filename='setup.py', + pattern=r'version = .*', + replacement='version = "{}"'.format(version) + ) + # ... specify version in package_name/__init__.py file + u.file_revision( + filename=os.path.join(pkg_name, '__init__.py'), + pattern=r'__version__ = .*', + replacement='__version__ = "{}"'.format(version) + ) # build and upload model package for each Python version and OS platform local_platform = u.conda_platform_name() @@ -183,8 +187,8 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): '--no-anaconda-upload --output-folder {} ' 'conda.recipe').format(pyver, ANACONDA_CHANNEL, BUILDS_DIR) u.os_call(cmd) - # ... if localdir is specified, skip convert and upload logic - if localdir: + # ... if local is True, skip convert and upload logic + if local: break # out of for pyver loop # ... convert local build to other OS_PLATFORMS print((': Package-Builder is converting package ' @@ -209,7 +213,7 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): ANACONDA_TOKEN_FILE, ANACONDA_USER, pkgpath ) u.os_call(cmd) - if localdir: + if local: # do uninstall and install on local computer print(': Package-Builder is uninstalling any existing package') cmd = 'conda uninstall {} --yes'.format(pkg_name) @@ -217,8 +221,8 @@ def release(repo_name, pkg_name, version, localdir=None, dryrun=False): print(': Package-Builder is installing package on local computer') pkg_dir = os.path.join('file://', WORKING_DIR, repo_name, 'pkgbld_output') - cmd = 'conda install --channel {} {}=0.0.0 --yes' - u.os_call(cmd.format(pkg_dir, pkg_name)) + cmd = 'conda install --channel {} {}={} --yes' + u.os_call(cmd.format(pkg_dir, pkg_name, version)) print(': Package-Builder is cleaning-up')