|
66 | 66 | ) # Must check for string `false` since GitHub Actions passes the bool as a string |
67 | 67 |
|
68 | 68 |
|
69 | | -class App: |
70 | | - @staticmethod |
71 | | - def run_github_action(): |
72 | | - """Runs the complete GitHub Action workflow. |
73 | | -
|
74 | | - 1. Setup logging |
75 | | - 2. Grab the details about the tap |
76 | | - 3. Download the archive(s) |
77 | | - 4. Generate checksum(s) |
78 | | - 5. Generate the new formula |
79 | | - 6. Update README table (optional) |
80 | | - 7. Add, commit, and push updated formula to GitHub |
81 | | - """ |
82 | | - App.setup_logger() |
83 | | - logger = woodchips.get(LOGGER_NAME) |
84 | | - |
85 | | - logger.info(f'Starting Homebrew Releaser v{__version__}...') |
86 | | - App.check_required_env_variables() |
87 | | - |
88 | | - logger.info('Setting up git environment...') |
89 | | - setup_git(COMMIT_OWNER, COMMIT_EMAIL, HOMEBREW_OWNER, HOMEBREW_TAP) |
90 | | - |
91 | | - logger.info(f'Collecting data about {GITHUB_REPO}...') |
92 | | - repository = make_github_get_request(url=f'{GITHUB_BASE_URL}/repos/{GITHUB_OWNER}/{GITHUB_REPO}').json() |
93 | | - latest_release = make_github_get_request( |
94 | | - url=f'https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/releases/latest' |
95 | | - ).json() |
96 | | - assets = latest_release['assets'] |
97 | | - version = VERSION or latest_release['tag_name'] |
98 | | - version_no_v = version.lstrip('v') |
99 | | - logger.info(f'Latest release ({version}) successfully identified!') |
100 | | - |
101 | | - logger.info('Generating tar archive checksum(s)...') |
102 | | - archive_urls = [] |
103 | | - archive_checksum_entries = '' |
104 | | - |
105 | | - # Auto-generated tar URL must come first for later use (order is important) |
106 | | - if repository["private"]: |
107 | | - logger.debug('Repository is private. Using auto-generated release tarball and zipball REST API endpoints.') |
108 | | - archive_base_url = f'{GITHUB_BASE_URL}/repos/{GITHUB_OWNER}/{GITHUB_REPO}' |
109 | | - auto_generated_release_tar = f'{archive_base_url}/tarball/{version}' |
110 | | - auto_generated_release_zip = f'{archive_base_url}/zipball/{version}' |
111 | | - else: |
112 | | - logger.debug('Repository is public. Using auto-generated release tarball and zipball public URLs.') |
113 | | - archive_base_url = f'https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}/archive/refs/tags/{version}' |
114 | | - auto_generated_release_tar = f'{archive_base_url}.tar.gz' |
115 | | - auto_generated_release_zip = f'{archive_base_url}.zip' |
116 | | - |
117 | | - archive_urls.append(auto_generated_release_tar) |
118 | | - archive_urls.append(auto_generated_release_zip) |
119 | | - |
120 | | - target_browser_download_base_url = ( |
121 | | - f'https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}/releases/download/{version}/{GITHUB_REPO}-{version_no_v}' |
122 | | - ) |
123 | | - if TARGET_DARWIN_AMD64: |
124 | | - archive_urls.append(f'{target_browser_download_base_url}-darwin-amd64.tar.gz') |
125 | | - if TARGET_DARWIN_ARM64: |
126 | | - archive_urls.append(f'{target_browser_download_base_url}-darwin-arm64.tar.gz') |
127 | | - if TARGET_LINUX_AMD64: |
128 | | - archive_urls.append(f'{target_browser_download_base_url}-linux-amd64.tar.gz') |
129 | | - if TARGET_LINUX_ARM64: |
130 | | - archive_urls.append(f'{target_browser_download_base_url}-linux-arm64.tar.gz') |
131 | | - |
132 | | - checksums = [] |
133 | | - for archive_url in archive_urls: |
134 | | - if not assets: |
135 | | - assets = [0] # Populate `assets` so that if we don't have any, we can use the auto generated checksums |
136 | | - for asset in assets: |
137 | | - # Download the asset url so private repos work but use the brower URL for name and path in formula |
138 | | - if archive_url == auto_generated_release_tar or archive_url == auto_generated_release_zip: |
139 | | - download_url = archive_url |
140 | | - else: |
141 | | - download_url = asset['url'] |
142 | | - |
143 | | - if ( |
144 | | - archive_url == auto_generated_release_tar |
145 | | - or archive_url == auto_generated_release_zip |
146 | | - or archive_url == asset['browser_download_url'] |
147 | | - ): |
148 | | - # For REST API requests, we should not stream archive file, but it is fine for browser URLs |
149 | | - stream = False if archive_url.find("api.github.com") != -1 else True |
150 | | - downloaded_filename = App.download_archive(download_url, stream) |
151 | | - checksum = calculate_checksum(downloaded_filename) |
152 | | - archive_filename = get_filename_from_path(archive_url) |
153 | | - archive_checksum_entries += f'{checksum} {archive_filename}\n' |
154 | | - checksums.append( |
155 | | - { |
156 | | - archive_filename: { |
157 | | - 'checksum': checksum, |
158 | | - 'url': archive_url, |
159 | | - } |
160 | | - }, |
161 | | - ) |
162 | | - # We break here so we don't include duplicate checksums for the auto generated URLs |
163 | | - break |
164 | | - |
165 | | - write_file(CHECKSUM_FILE, archive_checksum_entries) |
166 | | - |
167 | | - logger.info(f'Generating Homebrew formula for {GITHUB_REPO}...') |
168 | | - template = generate_formula_data( |
169 | | - GITHUB_OWNER, |
170 | | - GITHUB_REPO, |
171 | | - repository, |
172 | | - checksums, |
173 | | - INSTALL, |
174 | | - auto_generated_release_tar, |
175 | | - DEPENDS_ON, |
176 | | - TEST, |
177 | | - DOWNLOAD_STRATEGY, |
178 | | - CUSTOM_REQUIRE, |
179 | | - FORMULA_INCLUDES, |
180 | | - UPDATE_PYTHON_RESOURCES, |
181 | | - version_no_v if VERSION else None, |
182 | | - ) |
183 | | - |
184 | | - formula_filename = f'{repository["name"]}.rb' |
185 | | - formula_dir = get_working_dir(os.path.join(HOMEBREW_TAP, FORMULA_FOLDER)) |
186 | | - formula_filepath = os.path.join(formula_dir, formula_filename) |
187 | | - write_file(formula_filepath, template, 'w') |
188 | | - |
189 | | - if UPDATE_PYTHON_RESOURCES: |
190 | | - logger.info('Attempting to update Python resources in the formula...') |
191 | | - setup_homebrew_tap(HOMEBREW_OWNER, HOMEBREW_TAP, formula_dir) |
192 | | - update_python_resources(formula_dir, formula_filename) |
193 | | - if DEBUG: |
194 | | - with open(formula_filepath, 'r') as formula_file: |
195 | | - formula_content = formula_file.read() |
196 | | - logger.debug(formula_content) |
197 | | - else: |
198 | | - logger.debug('Skipping update to Python resources.') |
199 | | - |
200 | | - if UPDATE_README_TABLE: |
201 | | - logger.info('Attempting to update the README\'s project table...') |
202 | | - update_readme(HOMEBREW_TAP) |
203 | | - else: |
204 | | - logger.debug('Skipping update to project README.') |
205 | | - |
206 | | - # Although users can skip a commit, still commit (but don't push) to dry-run the commit for debugging purposes |
207 | | - add_git(HOMEBREW_TAP) |
208 | | - commit_git(HOMEBREW_TAP, GITHUB_REPO, version) |
209 | | - |
210 | | - if SKIP_COMMIT: |
211 | | - logger.info(f'Skipping upload of checksum.txt to {HOMEBREW_TAP}.') |
212 | | - logger.info(f'Skipping push to {HOMEBREW_TAP}.') |
213 | | - else: |
214 | | - logger.info(f'Attempting to upload checksum.txt to the latest release of {GITHUB_REPO}...') |
215 | | - upload_checksum_file(latest_release) |
216 | | - logger.info(f'Attempting to release {version} of {GITHUB_REPO} to {HOMEBREW_TAP}...') |
217 | | - push_git(HOMEBREW_TAP, HOMEBREW_OWNER) |
218 | | - logger.info(f'Successfully released {version} of {GITHUB_REPO} to {HOMEBREW_TAP}!') |
219 | | - |
220 | | - @staticmethod |
221 | | - def setup_logger(): |
222 | | - """Setup a `woodchips` logger instance.""" |
223 | | - logging_level = 'DEBUG' if DEBUG else 'INFO' |
224 | | - |
225 | | - logger = woodchips.Logger( |
226 | | - name=LOGGER_NAME, |
227 | | - level=logging_level, |
228 | | - ) |
229 | | - logger.log_to_console(formatter='%(asctime)s - %(levelname)s - %(message)s') |
230 | | - |
231 | | - @staticmethod |
232 | | - def check_required_env_variables(): |
233 | | - """Checks that all required env variables are set.""" |
234 | | - logger = woodchips.get(LOGGER_NAME) |
235 | | - |
236 | | - required_env_variables = [ |
237 | | - GITHUB_TOKEN, |
238 | | - HOMEBREW_OWNER, |
239 | | - HOMEBREW_TAP, |
240 | | - INSTALL, |
241 | | - ] |
242 | | - |
243 | | - for env_variable in required_env_variables: |
244 | | - if not env_variable: |
245 | | - raise SystemExit( |
246 | | - 'You must provide all necessary environment variables. Please reference the Homebrew Releaser documentation.' # noqa |
| 69 | +def run_github_action(): |
| 70 | + """Runs the complete GitHub Action workflow. |
| 71 | +
|
| 72 | + 1. Setup logging |
| 73 | + 2. Grab the details about the tap |
| 74 | + 3. Download the archive(s) |
| 75 | + 4. Generate checksum(s) |
| 76 | + 5. Generate the new formula |
| 77 | + 6. Update README table (optional) |
| 78 | + 7. Add, commit, and push updated formula to GitHub |
| 79 | + """ |
| 80 | + _setup_logger() |
| 81 | + logger = woodchips.get(LOGGER_NAME) |
| 82 | + |
| 83 | + logger.info(f'Starting Homebrew Releaser v{__version__}...') |
| 84 | + _check_required_env_variables() |
| 85 | + |
| 86 | + logger.info('Setting up git environment...') |
| 87 | + setup_git(COMMIT_OWNER, COMMIT_EMAIL, HOMEBREW_OWNER, HOMEBREW_TAP) |
| 88 | + |
| 89 | + logger.info(f'Collecting data about {GITHUB_REPO}...') |
| 90 | + repository = make_github_get_request(url=f'{GITHUB_BASE_URL}/repos/{GITHUB_OWNER}/{GITHUB_REPO}').json() |
| 91 | + latest_release = make_github_get_request( |
| 92 | + url=f'https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/releases/latest' |
| 93 | + ).json() |
| 94 | + assets = latest_release['assets'] |
| 95 | + version = VERSION or latest_release['tag_name'] |
| 96 | + version_no_v = version.lstrip('v') |
| 97 | + logger.info(f'Latest release ({version}) successfully identified!') |
| 98 | + |
| 99 | + logger.info('Generating tar archive checksum(s)...') |
| 100 | + archive_urls = [] |
| 101 | + archive_checksum_entries = '' |
| 102 | + |
| 103 | + # Auto-generated tar URL must come first for later use (order is important) |
| 104 | + if repository["private"]: |
| 105 | + logger.debug('Repository is private. Using auto-generated release tarball and zipball REST API endpoints.') |
| 106 | + archive_base_url = f'{GITHUB_BASE_URL}/repos/{GITHUB_OWNER}/{GITHUB_REPO}' |
| 107 | + auto_generated_release_tar = f'{archive_base_url}/tarball/{version}' |
| 108 | + auto_generated_release_zip = f'{archive_base_url}/zipball/{version}' |
| 109 | + else: |
| 110 | + logger.debug('Repository is public. Using auto-generated release tarball and zipball public URLs.') |
| 111 | + archive_base_url = f'https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}/archive/refs/tags/{version}' |
| 112 | + auto_generated_release_tar = f'{archive_base_url}.tar.gz' |
| 113 | + auto_generated_release_zip = f'{archive_base_url}.zip' |
| 114 | + |
| 115 | + archive_urls.append(auto_generated_release_tar) |
| 116 | + archive_urls.append(auto_generated_release_zip) |
| 117 | + |
| 118 | + target_browser_download_base_url = ( |
| 119 | + f'https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}/releases/download/{version}/{GITHUB_REPO}-{version_no_v}' |
| 120 | + ) |
| 121 | + if TARGET_DARWIN_AMD64: |
| 122 | + archive_urls.append(f'{target_browser_download_base_url}-darwin-amd64.tar.gz') |
| 123 | + if TARGET_DARWIN_ARM64: |
| 124 | + archive_urls.append(f'{target_browser_download_base_url}-darwin-arm64.tar.gz') |
| 125 | + if TARGET_LINUX_AMD64: |
| 126 | + archive_urls.append(f'{target_browser_download_base_url}-linux-amd64.tar.gz') |
| 127 | + if TARGET_LINUX_ARM64: |
| 128 | + archive_urls.append(f'{target_browser_download_base_url}-linux-arm64.tar.gz') |
| 129 | + |
| 130 | + checksums = [] |
| 131 | + for archive_url in archive_urls: |
| 132 | + if not assets: |
| 133 | + assets = [0] # Populate `assets` so that if we don't have any, we can use the auto generated checksums |
| 134 | + for asset in assets: |
| 135 | + # Download the asset url so private repos work but use the brower URL for name and path in formula |
| 136 | + if archive_url == auto_generated_release_tar or archive_url == auto_generated_release_zip: |
| 137 | + download_url = archive_url |
| 138 | + else: |
| 139 | + download_url = asset['url'] |
| 140 | + |
| 141 | + if ( |
| 142 | + archive_url == auto_generated_release_tar |
| 143 | + or archive_url == auto_generated_release_zip |
| 144 | + or archive_url == asset['browser_download_url'] |
| 145 | + ): |
| 146 | + # For REST API requests, we should not stream archive file, but it is fine for browser URLs |
| 147 | + stream = False if archive_url.find("api.github.com") != -1 else True |
| 148 | + downloaded_filename = _download_archive(download_url, stream) |
| 149 | + checksum = calculate_checksum(downloaded_filename) |
| 150 | + archive_filename = get_filename_from_path(archive_url) |
| 151 | + archive_checksum_entries += f'{checksum} {archive_filename}\n' |
| 152 | + checksums.append( |
| 153 | + { |
| 154 | + archive_filename: { |
| 155 | + 'checksum': checksum, |
| 156 | + 'url': archive_url, |
| 157 | + } |
| 158 | + }, |
247 | 159 | ) |
248 | | - logger.debug('All required environment variables are present.') |
249 | | - |
250 | | - @staticmethod |
251 | | - def download_archive(url: str, stream: Optional[bool] = False) -> str: |
252 | | - """Gets an archive (eg: zip, tar) from GitHub and saves it locally.""" |
253 | | - response = make_github_get_request( |
254 | | - url=url, |
255 | | - stream=stream, |
256 | | - ) |
257 | | - filename = get_filename_from_path(url) |
258 | | - write_file(filename, response.content, 'wb') |
259 | | - |
260 | | - return filename |
| 160 | + # We break here so we don't include duplicate checksums for the auto generated URLs |
| 161 | + break |
| 162 | + |
| 163 | + write_file(CHECKSUM_FILE, archive_checksum_entries) |
| 164 | + |
| 165 | + logger.info(f'Generating Homebrew formula for {GITHUB_REPO}...') |
| 166 | + template = generate_formula_data( |
| 167 | + GITHUB_OWNER, |
| 168 | + GITHUB_REPO, |
| 169 | + repository, |
| 170 | + checksums, |
| 171 | + INSTALL, |
| 172 | + auto_generated_release_tar, |
| 173 | + DEPENDS_ON, |
| 174 | + TEST, |
| 175 | + DOWNLOAD_STRATEGY, |
| 176 | + CUSTOM_REQUIRE, |
| 177 | + FORMULA_INCLUDES, |
| 178 | + UPDATE_PYTHON_RESOURCES, |
| 179 | + version_no_v if VERSION else None, |
| 180 | + ) |
| 181 | + |
| 182 | + formula_filename = f'{repository["name"]}.rb' |
| 183 | + formula_dir = get_working_dir(os.path.join(HOMEBREW_TAP, FORMULA_FOLDER)) |
| 184 | + formula_filepath = os.path.join(formula_dir, formula_filename) |
| 185 | + write_file(formula_filepath, template, 'w') |
| 186 | + |
| 187 | + if UPDATE_PYTHON_RESOURCES: |
| 188 | + logger.info('Attempting to update Python resources in the formula...') |
| 189 | + setup_homebrew_tap(HOMEBREW_OWNER, HOMEBREW_TAP, formula_dir) |
| 190 | + update_python_resources(formula_dir, formula_filename) |
| 191 | + if DEBUG: |
| 192 | + with open(formula_filepath, 'r') as formula_file: |
| 193 | + formula_content = formula_file.read() |
| 194 | + logger.debug(formula_content) |
| 195 | + else: |
| 196 | + logger.debug('Skipping update to Python resources.') |
| 197 | + |
| 198 | + if UPDATE_README_TABLE: |
| 199 | + logger.info('Attempting to update the README\'s project table...') |
| 200 | + update_readme(HOMEBREW_TAP) |
| 201 | + else: |
| 202 | + logger.debug('Skipping update to project README.') |
| 203 | + |
| 204 | + # Although users can skip a commit, still commit (but don't push) to dry-run the commit for debugging purposes |
| 205 | + add_git(HOMEBREW_TAP) |
| 206 | + commit_git(HOMEBREW_TAP, GITHUB_REPO, version) |
| 207 | + |
| 208 | + if SKIP_COMMIT: |
| 209 | + logger.info(f'Skipping upload of checksum.txt to {HOMEBREW_TAP}.') |
| 210 | + logger.info(f'Skipping push to {HOMEBREW_TAP}.') |
| 211 | + else: |
| 212 | + logger.info(f'Attempting to upload checksum.txt to the latest release of {GITHUB_REPO}...') |
| 213 | + upload_checksum_file(latest_release) |
| 214 | + logger.info(f'Attempting to release {version} of {GITHUB_REPO} to {HOMEBREW_TAP}...') |
| 215 | + push_git(HOMEBREW_TAP, HOMEBREW_OWNER) |
| 216 | + logger.info(f'Successfully released {version} of {GITHUB_REPO} to {HOMEBREW_TAP}!') |
| 217 | + |
| 218 | + |
| 219 | +def _setup_logger(): |
| 220 | + """Setup a `woodchips` logger instance.""" |
| 221 | + logging_level = 'DEBUG' if DEBUG else 'INFO' |
| 222 | + |
| 223 | + logger = woodchips.Logger( |
| 224 | + name=LOGGER_NAME, |
| 225 | + level=logging_level, |
| 226 | + ) |
| 227 | + logger.log_to_console(formatter='%(asctime)s - %(levelname)s - %(message)s') |
| 228 | + |
| 229 | + |
| 230 | +def _check_required_env_variables(): |
| 231 | + """Checks that all required env variables are set.""" |
| 232 | + logger = woodchips.get(LOGGER_NAME) |
| 233 | + |
| 234 | + required_env_variables = [ |
| 235 | + GITHUB_TOKEN, |
| 236 | + HOMEBREW_OWNER, |
| 237 | + HOMEBREW_TAP, |
| 238 | + INSTALL, |
| 239 | + ] |
| 240 | + |
| 241 | + for env_variable in required_env_variables: |
| 242 | + if not env_variable: |
| 243 | + raise SystemExit( |
| 244 | + 'You must provide all necessary environment variables. Please reference the Homebrew Releaser documentation.' # noqa |
| 245 | + ) |
| 246 | + logger.debug('All required environment variables are present.') |
| 247 | + |
| 248 | + |
| 249 | +def _download_archive(url: str, stream: Optional[bool] = False) -> str: |
| 250 | + """Gets an archive (eg: zip, tar) from GitHub and saves it locally.""" |
| 251 | + response = make_github_get_request( |
| 252 | + url=url, |
| 253 | + stream=stream, |
| 254 | + ) |
| 255 | + filename = get_filename_from_path(url) |
| 256 | + write_file(filename, response.content, 'wb') |
| 257 | + |
| 258 | + return filename |
261 | 259 |
|
262 | 260 |
|
263 | 261 | def main(): |
264 | | - App.run_github_action() |
| 262 | + run_github_action() |
265 | 263 |
|
266 | 264 |
|
267 | 265 | if __name__ == '__main__': |
|
0 commit comments