ionelmc / cookiecutter-pylibrary

Enhanced cookiecutter template for Python libraries.
BSD 2-Clause "Simplified" License
1.25k stars 207 forks source link

CI Bootstrapping errors #226

Closed SterlingPeet closed 1 year ago

SterlingPeet commented 3 years ago

I have followed your pattern for CI bootstrapping with a template that I am developing for another project. I have noticed that I am running into issues with the yaml contents actually translating to the cookiecutter inputs, meaning that a number of the envs are actually just testing the default input. In particular this happens with yes/no questions.

When I came over here to look at some of the env files that your bootstrapper is generating, I saw this:

https://github.com/ionelmc/cookiecutter-pylibrary/blob/be88cad1498c6f1ac365ce692ace642b71678916/ci/envs/cext-docs-nolegacy.cookiecutterrc#L29

Are you running into the same problems with your bootstrapping script?

My bootstrap script (in development) is located here:

https://github.com/SterlingPeet/cookiecutter-fprime-deployment/blob/feature/reference_and_linux/ci/bootstrap.py

ionelmc commented 3 years ago

Oh my. I messed up the config. This would be the fix:

diff --git a/ci/setup.cfg b/ci/setup.cfg
index e543c68..17778ce 100644
--- a/ci/setup.cfg
+++ b/ci/setup.cfg
@@ -43,7 +43,7 @@ repo_hosting_domain =
     : github.com
     private: no &command_line_interface[plain] &test_runner[pytest] &test_matrix_configurator[no] &test_matrix_separate_coverage[no] &c_extension_support[no] &setup_py_uses_setuptools_scm[no] &linter[flake8] &legacy_python[yes]
 travis_osx =
-    : no yes &c_extension_test_pypi[no]
+    : no &c_extension_test_pypi[no]
     osx: yes &c_extension_test_pypi[yes]
 c_extension_test_pypi =
     : no
ionelmc commented 3 years ago

Please explain what is wrong with your template? What files and what content should your bootstrapper output? It seems fine to me.

SterlingPeet commented 3 years ago

OK, so when I run my bootstrap script, it creates about 140 envs, including this one: https://github.com/SterlingPeet/cookiecutter-fprime-deployment/blob/feature/reference_and_linux/ci/envs/dotpath-native-threads-noparams-noseq-noxfer-nohealth-norefdoc-passive.cookiecutterrc

when I run tox -e dotpath-native-threads-noparams-noseq-noxfer-nohealth-norefdoc-passive it does render the template, but with the wrong options.

contents of env file cookiecutterrc:

default_context:
  component_kind: passive
  deployment_baremetal_scheduler: 'no'
  deployment_command_sequence_support: 'no'
  deployment_file_xfer_support: 'no'
  deployment_health_support: 'no'
  deployment_parameter_support: 'no'
  deployment_path: .
  deployment_path_to_project_root: ..
  deployment_ref_doc_boilerplate: 'no'
  deployment_target_platform_support: Native

It isn't all of the context, but it should use the defaults for unlisted keys. The resulting rendered template contains a generated .cookiecutterrc file to support re-rendering in the same way as this repository's template. The contents of the rendered .cookiecutterrc file are as follows:

# This file exists so you can easily regenerate your project.
#
# `cookiepatcher` is a convenient shim around `cookiecutter`
# for regenerating projects (it will generate a .cookiecutterrc
# automatically for any template). To use it:
#
#    pip install cookiepatcher
#    cookiepatcher gh:sterlingpeet/cookiecutter-fprime-deployment MyExample
#
# See:
#    https://pypi.org/project/cookiepatcher
#
# Alternatively, you can run:
#
#    cookiecutter --overwrite-if-exists --config-file=MyExample/.cookiecutterrc gh:sterlingpeet/cookiecutter-fprime-deployment

default_context:

    _template:                 '..'
    arduino_log_stream:        'Serial'
    component_dir_name:        'LedBlinker'
    component_explicit_common: ''
    component_explicit_component_suffix: 'Component'
    component_impl_suffix:     ''
    component_instance_name:   'ledblinker'
    component_kind:            'passive'
    component_multiplatform_support: 'no'
    component_name:            'Led Blinker'
    component_namespace:       'MyExample'
    component_path_to_fprime_root: '../..'
    component_short_description: 'Example component to support My Example deployment.'
    component_slug:            'LedBlinker'
    dep_short_description:     'Example deployment for F Prime FSW framework.'
    deployment_baremetal_scheduler: 'no'
    deployment_command_sequence_support: 'yes'
    deployment_display_name:   'My Example'
    deployment_file_xfer_support: 'yes'
    deployment_health_support: 'yes'
    deployment_parameter_support: 'no'
    deployment_path:           '.'
    deployment_path_to_fprime_framework: '../fprime'
    deployment_path_to_project_root: '..'
    deployment_ref_doc_boilerplate: 'no'
    deployment_rg_sched_contexts_hpp: 'no'
    deployment_slug:           'MyExample'
    deployment_target_platform_support: 'Native'
    email:                     'noreply@nospam.com'
    full_name:                 'Sterling Peet'
    license:                   'None'
    startup_arduino_led_flash: 'yes'
    startup_delay_msec:        '2000'

What is boggling my mind is that several of the keys don't match, deployment_command_sequence_support for example. The input file contains the value no but the rendered template claims yes. Inspection of other files in the template corroborate that the template was indeed rendered with the contents corresponding to yes configuration.

The test script is invoking the cookiecutter template on this line:

cookiecutter --no-input --config-file=../ci/envs/$1.cookiecutterrc ..

That seems to be correct, and it does complain if the path to that file is adjusted. So it does seem to find the file when inspecting the output of the tox execution:

+ cookiecutter --no-input --overwrite-if-exists --config-file=../ci/envs/dotpath-native-threads-noparams-noseq-noxfer-nohealth-norefdoc-passive.cookiecutterrc ..

So, it is looking like cookiecutter is ingesting the file, but not actually using the values. I guess this is actually a cookiecutter problem, and not a bootstrap script problem?

The tox execution output seems to indicate that it installs cookiecutter==1.7.2 in the env, in case that matters. running the above command directly in the correct folder yields exactly the same results, so it doesn't seem to be and env problem.

Any ideas how to track this down? I wasn't prepared to debug cookiecutter internals.

SterlingPeet commented 3 years ago

I could write a test to check that the rendered keys match the input keys, but I already know that it is not working. My original question was in part attempting to ask if you also see this behavior where you are actually getting only the default rendered template.

SterlingPeet commented 3 years ago

OK, I think I figured out my actual problem, but I would like to know if you see the same problem. I have used a complicated rendering in my cookiecutter.json. It allows me to set different default values in choice dictionaries based on previous input. In my test repository https://github.com/SterlingPeet/cookiecutter-conceptual-testing I have specified the value for baz in the following way:

    "baz": ["{% if cookiecutter.foo == 'yes' %}yes{% else %}no{% endif %}", "{% if cookiecutter.foo != 'yes' %}yes{% else %}no{% endif %}"]

In this project, I have set up a test that checks the output .cookicutterrc file to see if the values in the env.cookiecutter input file are correctly rendered. When I run tox, the baz key only contains the default value as if there are no inputs aside from the default.

tox -e foo
foo installed: arrow==1.2.1,attrs==21.2.0,backports.entry-points-selectable==1.1.0,binaryornot==0.4.4,certifi==2021.10.8,chardet==4.0.0,charset-normalizer==2.0.7,Cheetah3==3.2.6,click==8.0.3,cookiecutter==1.7.2,distlib==0.3.3,filelock==3.3.1,fprime-tools==2.0.2,idna==3.3,iniconfig==1.1.1,Jinja2==2.11.3,jinja2-time==0.2.0,lxml==4.6.3,Markdown==3.3.4,MarkupSafe==1.1.1,packaging==21.0,pexpect==4.8.0,platformdirs==2.4.0,pluggy==0.13.1,poyo==0.5.0,ptyprocess==0.7.0,py==1.10.0,pyparsing==3.0.1,pytest==6.2.4,python-dateutil==2.8.2,python-slugify==5.0.2,PyYAML==6.0,requests==2.26.0,setuptools-scm==6.0.1,six==1.16.0,text-unidecode==1.3,toml==0.10.2,tox==3.24.4,urllib3==1.26.7,virtualenv==20.9.0
foo run-test-pre: PYTHONHASHSEED='2901272865'
foo run-test: commands[0] | /Users/speet3/src/cookiecutter-conceptual-testing/ci/test.sh foo
+ shopt -s xpg_echo
+ [[ -z foo ]]
+ echo '\033[1;36m================================\033[0m'
================================
+ echo '\033[1;36m================================ Testing: foo\033[0m'
================================ Testing: foo
+ echo '\033[1;36m================================\033[0m'
================================
+ set -x
+ pwd
/Users/speet3/src/cookiecutter-conceptual-testing
+ cat ci/envs/foo.cookiecutterrc
default_context:
  bar: 'no'
  baz: 'no'
  foo: 'yes'
  slug: TestProject
+ rm -rf TestProject
+ cookiecutter --no-input --config-file=ci/envs/foo.cookiecutterrc .
+ python3 ci/check_rendered_config.py foo
Reading env input conf file: ci/envs/foo.cookiecutterrc
Reading env output conf file: TestProject/.cookiecutterrc
** ERROR: key baz mismatch, input['no'] output['yes']

So it is starting to look like this is a cookiecutter bug, perhaps something to do with the mixing of default and config dictionaries? It seems weird that only the complex rendered values get clobbered, while simple rendered values appear to work.

ionelmc commented 3 years ago

Wow I had no idea cookiecutter has this bug. I suspect reading its code would clarify the problem.

SterlingPeet commented 1 year ago

So its been a year, and I have just worked around this bug. Since its not a bug in your repo, the issue can be closed.

SterlingPeet commented 1 year ago

Oh yea, my solution was to keep the fancy default-changing behavior, but I wrote a CI renderer script to help test the template. It strips the fancy template out of the cookiecutter.json, and then replaces the file with exactly the answers for that run as the only choices. Then it restores the file at the end of the CI run. Seems to work well enough for testing.