[Notes] [Git][BuildStream/buildstream][Qinusty/600-recursive-variables] 8 commits: element.py: Schedule assemble for key of workspaced elements



Title: GitLab

Qinusty pushed to branch Qinusty/600-recursive-variables at BuildStream / buildstream

Commits:

9 changed files:

Changes:

  • buildstream/_exceptions.py
    ... ... @@ -217,6 +217,9 @@ class LoadErrorReason(Enum):
    217 217
         # A recursive include has been encountered.
    
    218 218
         RECURSIVE_INCLUDE = 21
    
    219 219
     
    
    220
    +    # A recursive variable has been encountered
    
    221
    +    RECURSIVE_VARIABLE = 22
    
    222
    +
    
    220 223
     
    
    221 224
     # LoadError
    
    222 225
     #
    

  • buildstream/_variables.py
    ... ... @@ -132,6 +132,12 @@ class Variables():
    132 132
                     value = _yaml.node_get(variables, str, key)
    
    133 133
     
    
    134 134
                     resolved_var, item_unmatched = self._subst(value, variables)
    
    135
    +                if _wrap_variable(key) in resolved_var:
    
    136
    +                    raise LoadError(LoadErrorReason.RECURSIVE_VARIABLE,
    
    137
    +                                    "{}: ".format(_yaml.node_get_provenance(variables, key)) +
    
    138
    +                                    ("Variable '{}' references itself recursively through" +
    
    139
    +                                     " another variable declariation.").format(key))
    
    140
    +
    
    135 141
                     resolved[key] = resolved_var
    
    136 142
                     unmatched += item_unmatched
    
    137 143
     
    
    ... ... @@ -168,8 +174,12 @@ class Variables():
    168 174
         # Helper function to fetch information about the node referring to a variable
    
    169 175
         #
    
    170 176
         def _find_references(self, varname):
    
    171
    -        fullname = '%{' + varname + '}'
    
    177
    +        fullname = _wrap_variable(varname)
    
    172 178
             for key, value in _yaml.node_items(self.original):
    
    173 179
                 if fullname in value:
    
    174 180
                     provenance = _yaml.node_get_provenance(self.original, key)
    
    175 181
                     yield (key, provenance)
    
    182
    +
    
    183
    +
    
    184
    +def _wrap_variable(var):
    
    185
    +    return "%{" + var + "}"

  • buildstream/element.py
    ... ... @@ -1101,9 +1101,12 @@ class Element(Plugin):
    1101 1101
                 # until the full cache query below.
    
    1102 1102
                 if (not self.__assemble_scheduled and not self.__assemble_done and
    
    1103 1103
                         not self.__cached_success(keystrength=_KeyStrength.WEAK) and
    
    1104
    -                    not self._pull_pending() and self._is_required()):
    
    1105
    -                self._schedule_assemble()
    
    1106
    -                return
    
    1104
    +                    not self._pull_pending()):
    
    1105
    +                # For uncached workspaced elements, assemble is required
    
    1106
    +                # even if we only need the cache key
    
    1107
    +                if self._is_required() or self._get_workspace():
    
    1108
    +                    self._schedule_assemble()
    
    1109
    +                    return
    
    1107 1110
     
    
    1108 1111
             if self.__strict_cache_key is None:
    
    1109 1112
                 dependencies = [
    
    ... ... @@ -1126,13 +1129,17 @@ class Element(Plugin):
    1126 1129
                     self.__weak_cached = self.__artifacts.contains(self, self.__weak_cache_key)
    
    1127 1130
     
    
    1128 1131
             if (not self.__assemble_scheduled and not self.__assemble_done and
    
    1129
    -                not self._cached_success() and not self._pull_pending() and self._is_required()):
    
    1132
    +                not self._cached_success() and not self._pull_pending()):
    
    1130 1133
                 # Workspaced sources are considered unstable if a build is pending
    
    1131 1134
                 # as the build will modify the contents of the workspace.
    
    1132 1135
                 # Determine as early as possible if a build is pending to discard
    
    1133 1136
                 # unstable cache keys.
    
    1134
    -            self._schedule_assemble()
    
    1135
    -            return
    
    1137
    +
    
    1138
    +            # For uncached workspaced elements, assemble is required
    
    1139
    +            # even if we only need the cache key
    
    1140
    +            if self._is_required() or self._get_workspace():
    
    1141
    +                self._schedule_assemble()
    
    1142
    +                return
    
    1136 1143
     
    
    1137 1144
             if self.__cache_key is None:
    
    1138 1145
                 # Calculate strong cache key
    
    ... ... @@ -1430,7 +1437,6 @@ class Element(Plugin):
    1430 1437
         # in a subprocess.
    
    1431 1438
         #
    
    1432 1439
         def _schedule_assemble(self):
    
    1433
    -        assert self._is_required()
    
    1434 1440
             assert not self.__assemble_scheduled
    
    1435 1441
             self.__assemble_scheduled = True
    
    1436 1442
     
    
    ... ... @@ -1438,6 +1444,8 @@ class Element(Plugin):
    1438 1444
             for dep in self.dependencies(Scope.BUILD, recurse=False):
    
    1439 1445
                 dep._set_required()
    
    1440 1446
     
    
    1447
    +        self._set_required()
    
    1448
    +
    
    1441 1449
             # Invalidate workspace key as the build modifies the workspace directory
    
    1442 1450
             workspace = self._get_workspace()
    
    1443 1451
             if workspace:
    
    ... ... @@ -1661,6 +1669,10 @@ class Element(Plugin):
    1661 1669
         #   (bool): Whether a pull operation is pending
    
    1662 1670
         #
    
    1663 1671
         def _pull_pending(self):
    
    1672
    +        if self._get_workspace():
    
    1673
    +            # Workspace builds are never pushed to artifact servers
    
    1674
    +            return False
    
    1675
    +
    
    1664 1676
             if self.__strong_cached:
    
    1665 1677
                 # Artifact already in local cache
    
    1666 1678
                 return False
    

  • dev-requirements.txt
    ... ... @@ -8,3 +8,4 @@ pytest-env
    8 8
     pytest-pep8
    
    9 9
     pytest-pylint
    
    10 10
     pytest-xdist
    
    11
    +pytest-timeout

  • tests/format/variables.py
    1 1
     import os
    
    2 2
     import pytest
    
    3
    +import sys
    
    3 4
     from buildstream import _yaml
    
    4 5
     from buildstream._exceptions import ErrorDomain, LoadErrorReason
    
    5 6
     from tests.testutils.runcli import cli
    
    ... ... @@ -72,3 +73,20 @@ def test_missing_variable(cli, datafiles, tmpdir):
    72 73
         ])
    
    73 74
         result.assert_main_error(ErrorDomain.LOAD,
    
    74 75
                                  LoadErrorReason.UNRESOLVED_VARIABLE)
    
    76
    +
    
    77
    +@pytest.mark.timeout(3, method="signal")
    
    78
    +@pytest.mark.datafiles(os.path.join(DATA_DIR, 'cyclic_variables'))
    
    79
    +def test_cyclic_variables(cli, datafiles):
    
    80
    +    print_warning("Performing cyclic test, if this test times out it will " +
    
    81
    +                  "exit the test sequence")
    
    82
    +    project = os.path.join(datafiles.dirname, datafiles.basename)
    
    83
    +    result = cli.run(project=project, silent=True, args=[
    
    84
    +        "build", "cyclic.bst"
    
    85
    +    ])
    
    86
    +    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.RECURSIVE_VARIABLE)
    
    87
    +
    
    88
    +
    
    89
    +def print_warning(msg):
    
    90
    +    RED, END = "\033[91m", "\033[0m"
    
    91
    +    print(("\n{}{}{}").format(RED, msg, END), file=sys.stderr)
    
    92
    +

  • tests/format/variables/cyclic_variables/cyclic.bst
    1
    +kind: manual
    
    2
    +
    
    3
    +variables:
    
    4
    +  a: "%{prefix}/a"
    
    5
    +  prefix: "%{a}/some_prefix/"
    \ No newline at end of file

  • tests/format/variables/cyclic_variables/project.conf
    1
    +name: test

  • tests/frontend/workspace.py
    ... ... @@ -780,3 +780,73 @@ def test_inconsitent_pipeline_message(cli, tmpdir, datafiles, kind):
    780 780
             'build', element_name
    
    781 781
         ])
    
    782 782
         result.assert_main_error(ErrorDomain.PIPELINE, "inconsistent-pipeline-workspaced")
    
    783
    +
    
    784
    +
    
    785
    +@pytest.mark.datafiles(DATA_DIR)
    
    786
    +@pytest.mark.parametrize("strict", [("strict"), ("non-strict")])
    
    787
    +def test_cache_key_workspace_in_dependencies(cli, tmpdir, datafiles, strict):
    
    788
    +    checkout = os.path.join(str(tmpdir), 'checkout')
    
    789
    +    element_name, project, workspace = open_workspace(cli, os.path.join(str(tmpdir), 'repo-a'),
    
    790
    +                                                      datafiles, 'git', False)
    
    791
    +
    
    792
    +    element_path = os.path.join(project, 'elements')
    
    793
    +    back_dep_element_name = 'workspace-test-back-dep.bst'
    
    794
    +
    
    795
    +    # Write out our test target
    
    796
    +    element = {
    
    797
    +        'kind': 'compose',
    
    798
    +        'depends': [
    
    799
    +            {
    
    800
    +                'filename': element_name,
    
    801
    +                'type': 'build'
    
    802
    +            }
    
    803
    +        ]
    
    804
    +    }
    
    805
    +    _yaml.dump(element,
    
    806
    +               os.path.join(element_path,
    
    807
    +                            back_dep_element_name))
    
    808
    +
    
    809
    +    # Modify workspace
    
    810
    +    shutil.rmtree(os.path.join(workspace, 'usr', 'bin'))
    
    811
    +    os.makedirs(os.path.join(workspace, 'etc'))
    
    812
    +    with open(os.path.join(workspace, 'etc', 'pony.conf'), 'w') as f:
    
    813
    +        f.write("PONY='pink'")
    
    814
    +
    
    815
    +    # Configure strict mode
    
    816
    +    strict_mode = True
    
    817
    +    if strict != 'strict':
    
    818
    +        strict_mode = False
    
    819
    +    cli.configure({
    
    820
    +        'projects': {
    
    821
    +            'test': {
    
    822
    +                'strict': strict_mode
    
    823
    +            }
    
    824
    +        }
    
    825
    +    })
    
    826
    +
    
    827
    +    # Build artifact with dependency's modified workspace
    
    828
    +    assert cli.get_element_state(project, element_name) == 'buildable'
    
    829
    +    assert cli.get_element_key(project, element_name) == "{:?<64}".format('')
    
    830
    +    assert cli.get_element_state(project, back_dep_element_name) == 'waiting'
    
    831
    +    assert cli.get_element_key(project, back_dep_element_name) == "{:?<64}".format('')
    
    832
    +    result = cli.run(project=project, args=['build', back_dep_element_name])
    
    833
    +    result.assert_success()
    
    834
    +    assert cli.get_element_state(project, element_name) == 'cached'
    
    835
    +    assert cli.get_element_key(project, element_name) != "{:?<64}".format('')
    
    836
    +    assert cli.get_element_state(project, back_dep_element_name) == 'cached'
    
    837
    +    assert cli.get_element_key(project, back_dep_element_name) != "{:?<64}".format('')
    
    838
    +    result = cli.run(project=project, args=['build', back_dep_element_name])
    
    839
    +    result.assert_success()
    
    840
    +
    
    841
    +    # Checkout the result
    
    842
    +    result = cli.run(project=project, args=[
    
    843
    +        'checkout', back_dep_element_name, checkout
    
    844
    +    ])
    
    845
    +    result.assert_success()
    
    846
    +
    
    847
    +    # Check that the pony.conf from the modified workspace exists
    
    848
    +    filename = os.path.join(checkout, 'etc', 'pony.conf')
    
    849
    +    assert os.path.exists(filename)
    
    850
    +
    
    851
    +    # Check that the original /usr/bin/hello is not in the checkout
    
    852
    +    assert not os.path.exists(os.path.join(checkout, 'usr', 'bin', 'hello'))

  • tests/testutils/runcli.py
    ... ... @@ -94,14 +94,28 @@ class Result():
    94 94
         #    error_reason (any): The reason field of the error which occurred
    
    95 95
         #    fail_message (str): An optional message to override the automatic
    
    96 96
         #                        assertion error messages
    
    97
    +    #    debug (bool): If true, prints information regarding the exit state of the result()
    
    97 98
         # Raises:
    
    98 99
         #    (AssertionError): If any of the assertions fail
    
    99 100
         #
    
    100 101
         def assert_main_error(self,
    
    101 102
                               error_domain,
    
    102 103
                               error_reason,
    
    103
    -                          fail_message=''):
    
    104
    -
    
    104
    +                          fail_message='',
    
    105
    +                          *, debug=False):
    
    106
    +        if debug:
    
    107
    +            print(
    
    108
    +                """
    
    109
    +                Exit code: {}
    
    110
    +                Exception: {}
    
    111
    +                Domain:    {}
    
    112
    +                Reason:    {}
    
    113
    +                """.format(
    
    114
    +                    self.exit_code,
    
    115
    +                    self.exception,
    
    116
    +                    self.exception.domain,
    
    117
    +                    self.exception.reason
    
    118
    +                ))
    
    105 119
             assert self.exit_code == -1, fail_message
    
    106 120
             assert self.exc is not None, fail_message
    
    107 121
             assert self.exception is not None, fail_message
    



  • [Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]