release.py 8.08 KB
Newer Older
Sylvain Gugger's avatar
Sylvain Gugger committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# coding=utf-8
# Copyright 2021 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Sylvain Gugger's avatar
Sylvain Gugger committed
15
16
17
18
"""
Utility that prepares the repository for releases (or patches) by updating all versions in the relevant places. It
also performs some post-release cleanup, by updating the links in the main README to respective model doc pages (from
main to stable).
Sylvain Gugger's avatar
Sylvain Gugger committed
19

Sylvain Gugger's avatar
Sylvain Gugger committed
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
To prepare for a release, use from the root of the repo on the release branch with:

```bash
python release.py
```

or use `make pre-release`.

To prepare for a patch release, use from the root of the repo on the release branch with:

```bash
python release.py --patch
```

or use `make pre-patch`.

To do the post-release cleanup, use from the root of the repo on the main branch with:

```bash
python release.py --post_release
```

or use `make post-release`.
"""
Sylvain Gugger's avatar
Sylvain Gugger committed
44
45
46
47
48
49
50
import argparse
import os
import re

import packaging.version


Sylvain Gugger's avatar
Sylvain Gugger committed
51
# All paths are defined with the intent that this script should be run from the root of the repo.
Sylvain Gugger's avatar
Sylvain Gugger committed
52
PATH_TO_EXAMPLES = "examples/"
Sylvain Gugger's avatar
Sylvain Gugger committed
53
54
# This maps a type of file to the pattern to look for when searching where the version is defined, as well as the
# template to follow when replacing it with the new version.
Sylvain Gugger's avatar
Sylvain Gugger committed
55
56
57
58
59
REPLACE_PATTERNS = {
    "examples": (re.compile(r'^check_min_version\("[^"]+"\)\s*$', re.MULTILINE), 'check_min_version("VERSION")\n'),
    "init": (re.compile(r'^__version__\s+=\s+"([^"]+)"\s*$', re.MULTILINE), '__version__ = "VERSION"\n'),
    "setup": (re.compile(r'^(\s*)version\s*=\s*"[^"]+",', re.MULTILINE), r'\1version="VERSION",'),
}
Sylvain Gugger's avatar
Sylvain Gugger committed
60
# This maps a type of file to its path in Transformers
Sylvain Gugger's avatar
Sylvain Gugger committed
61
62
63
64
65
66
67
REPLACE_FILES = {
    "init": "src/transformers/__init__.py",
    "setup": "setup.py",
}
README_FILE = "README.md"


Sylvain Gugger's avatar
Sylvain Gugger committed
68
69
70
71
72
73
74
75
76
def update_version_in_file(fname: str, version: str, file_type: str):
    """
    Update the version of Transformers in one file.

    Args:
        fname (`str`): The path to the file where we want to update the version.
        version (`str`): The new version to set in the file.
        file_type (`str`): The type of the file (should be a key in `REPLACE_PATTERNS`).
    """
Sylvain Gugger's avatar
Sylvain Gugger committed
77
78
    with open(fname, "r", encoding="utf-8", newline="\n") as f:
        code = f.read()
Sylvain Gugger's avatar
Sylvain Gugger committed
79
    re_pattern, replace = REPLACE_PATTERNS[file_type]
Sylvain Gugger's avatar
Sylvain Gugger committed
80
81
82
83
84
85
    replace = replace.replace("VERSION", version)
    code = re_pattern.sub(replace, code)
    with open(fname, "w", encoding="utf-8", newline="\n") as f:
        f.write(code)


Sylvain Gugger's avatar
Sylvain Gugger committed
86
87
88
89
90
91
92
def update_version_in_examples(version: str):
    """
    Update the version in all examples files.

    Args:
        version (`str`): The new version to set in the examples.
    """
Sylvain Gugger's avatar
Sylvain Gugger committed
93
94
95
96
97
98
99
100
    for folder, directories, fnames in os.walk(PATH_TO_EXAMPLES):
        # Removing some of the folders with non-actively maintained examples from the walk
        if "research_projects" in directories:
            directories.remove("research_projects")
        if "legacy" in directories:
            directories.remove("legacy")
        for fname in fnames:
            if fname.endswith(".py"):
Sylvain Gugger's avatar
Sylvain Gugger committed
101
102
                update_version_in_file(os.path.join(folder, fname), version, file_type="examples")

Sylvain Gugger's avatar
Sylvain Gugger committed
103

Sylvain Gugger's avatar
Sylvain Gugger committed
104
105
106
def global_version_update(version: str, patch: bool = False):
    """
    Update the version in all needed files.
Sylvain Gugger's avatar
Sylvain Gugger committed
107

Sylvain Gugger's avatar
Sylvain Gugger committed
108
109
110
111
    Args:
        version (`str`): The new version to set everywhere.
        patch (`bool`, *optional*, defaults to `False`): Whether or not this is a patch release.
    """
Sylvain Gugger's avatar
Sylvain Gugger committed
112
113
114
    for pattern, fname in REPLACE_FILES.items():
        update_version_in_file(fname, version, pattern)
    if not patch:
Sylvain Gugger's avatar
Sylvain Gugger committed
115
        # We don't update the version in the examples for patch releases.
Sylvain Gugger's avatar
Sylvain Gugger committed
116
117
118
        update_version_in_examples(version)


119
def clean_main_ref_in_model_list():
Sylvain Gugger's avatar
Sylvain Gugger committed
120
121
122
    """
    Replace the links from main doc to stable doc in the model list of the README.
    """
Sylvain Gugger's avatar
Sylvain Gugger committed
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
    # If the introduction or the conclusion of the list change, the prompts may need to be updated.
    _start_prompt = "馃 Transformers currently provides the following architectures"
    _end_prompt = "1. Want to contribute a new model?"
    with open(README_FILE, "r", encoding="utf-8", newline="\n") as f:
        lines = f.readlines()

    # Find the start of the list.
    start_index = 0
    while not lines[start_index].startswith(_start_prompt):
        start_index += 1
    start_index += 1

    index = start_index
    # Update the lines in the model list.
    while not lines[index].startswith(_end_prompt):
        if lines[index].startswith("1."):
            lines[index] = lines[index].replace(
140
                "https://huggingface.co/docs/transformers/main/model_doc",
141
                "https://huggingface.co/docs/transformers/model_doc",
Sylvain Gugger's avatar
Sylvain Gugger committed
142
143
144
145
146
147
148
            )
        index += 1

    with open(README_FILE, "w", encoding="utf-8", newline="\n") as f:
        f.writelines(lines)


Sylvain Gugger's avatar
Sylvain Gugger committed
149
150
151
152
def get_version() -> packaging.version.Version:
    """
    Reads the current version in the main __init__.
    """
Sylvain Gugger's avatar
Sylvain Gugger committed
153
154
155
156
157
158
    with open(REPLACE_FILES["init"], "r") as f:
        code = f.read()
    default_version = REPLACE_PATTERNS["init"][0].search(code).groups()[0]
    return packaging.version.parse(default_version)


Sylvain Gugger's avatar
Sylvain Gugger committed
159
160
161
162
163
164
165
166
167
168
def pre_release_work(patch: bool = False):
    """
    Do all the necessary pre-release steps:
    - figure out the next minor release version and ask confirmation
    - update the version eveywhere
    - clean-up the model list in the main README

    Args:
        patch (`bool`, *optional*, defaults to `False`): Whether or not this is a patch release.
    """
Sylvain Gugger's avatar
Sylvain Gugger committed
169
170
171
172
173
174
175
176
177
178
179
    # First let's get the default version: base version if we are in dev, bump minor otherwise.
    default_version = get_version()
    if patch and default_version.is_devrelease:
        raise ValueError("Can't create a patch version from the dev branch, checkout a released version!")
    if default_version.is_devrelease:
        default_version = default_version.base_version
    elif patch:
        default_version = f"{default_version.major}.{default_version.minor}.{default_version.micro + 1}"
    else:
        default_version = f"{default_version.major}.{default_version.minor + 1}.0"

Sylvain Gugger's avatar
Sylvain Gugger committed
180
    # Now let's ask nicely if we have found the right version.
Sylvain Gugger's avatar
Sylvain Gugger committed
181
182
183
184
185
186
187
    version = input(f"Which version are you releasing? [{default_version}]")
    if len(version) == 0:
        version = default_version

    print(f"Updating version to {version}.")
    global_version_update(version, patch=patch)
    if not patch:
188
        print("Cleaning main README, don't forget to run `make fix-copies`.")
189
        clean_main_ref_in_model_list()
Sylvain Gugger's avatar
Sylvain Gugger committed
190
191
192


def post_release_work():
Sylvain Gugger's avatar
Sylvain Gugger committed
193
194
195
196
197
198
    """
    Do all the necesarry post-release steps:
    - figure out the next dev version and ask confirmation
    - update the version eveywhere
    - clean-up the model list in the main README
    """
Sylvain Gugger's avatar
Sylvain Gugger committed
199
200
201
202
203
204
205
206
207
208
209
210
    # First let's get the current version
    current_version = get_version()
    dev_version = f"{current_version.major}.{current_version.minor + 1}.0.dev0"
    current_version = current_version.base_version

    # Check with the user we got that right.
    version = input(f"Which version are we developing now? [{dev_version}]")
    if len(version) == 0:
        version = dev_version

    print(f"Updating version to {version}.")
    global_version_update(version)
211
212
    print("Cleaning main README, don't forget to run `make fix-copies`.")
    clean_main_ref_in_model_list()
Sylvain Gugger's avatar
Sylvain Gugger committed
213
214
215
216
217
218
219
220
221
222


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--post_release", action="store_true", help="Whether this is pre or post release.")
    parser.add_argument("--patch", action="store_true", help="Whether or not this is a patch release.")
    args = parser.parse_args()
    if not args.post_release:
        pre_release_work(patch=args.patch)
    elif args.patch:
223
        print("Nothing to do after a patch :-)")
Sylvain Gugger's avatar
Sylvain Gugger committed
224
225
    else:
        post_release_work()