def add_assets(release, github_token, path): """ Add an asset at `path` to a specific release Args: release: The release object from github github_token: The token to modify the release path: The path of the file to upload Returns: The asset object from github """ filename = os.path.basename(path) url = release["upload_url"].replace("{?name,label}", "?name=" + filename) headers = get_headers(github_token) headers["Content-Type"] = magic.from_file(path, mime=True) logging.info("Adding {} at {} to release".format(filename, path)) with open(path, "rb") as fin: response = requests.post(url, headers=headers, data=fin) if response.status_code == 422: raise ReleaseException( "A file by the name of {} is already attached to {}".format( filename, release["html_url"])) response.raise_for_status() ret = response.json() logging.info("Added {} to release at {}".format(filename, release["html_url"])) return ret
def prompt_for_message(summaries): """ Prompts the user for a release message in an editor, showing them the commits since last release, and returns what the user specified Args: summaries: The commit summaries to display to the user """ summaries_text = "\n".join(("# " + line for line in summaries)) temp_fd, temp_path = tempfile.mkstemp() try: with open(temp_path, "w") as fout: fout.write(MESSAGE_TEMPLATE.format(commit_messages=summaries_text)) editor = os.environ.get("EDITOR", "vim") run([editor, temp_path]) with open(temp_path, "r") as fin: message = "\n".join(line for line in fin if not line.startswith("#")) message = message.strip() if not message: raise ReleaseException("No valid message was provided") return message finally: if os.path.exists(temp_path): os.remove(temp_path)
def prompt_for_message(html_url, summaries): """ Prompts the user for a release message in an editor, showing them the commits since last release, and returns what the user specified Args: html_url: The url to see the difference between the current commit and the commit for the previous release. summaries: The commit summaries to display to the user """ default_message = create_default_message(html_url) summaries_text = "\n".join(("# " + line for line in summaries)) full_message = MESSAGE_PROMPT_TEMPLATE.format( default_message=default_message, commit_messages=summaries_text) temp_fd, temp_path = tempfile.mkstemp() try: with open(temp_path, "w") as fout: fout.write(full_message) editor = os.environ.get("EDITOR", "vim") run([editor, temp_path]) with open(temp_path, "r") as fin: message = "\n".join(line for line in fin if not line.startswith("#")) message = message.strip() if not message: raise ReleaseException("No valid message was provided") return message finally: if os.path.exists(temp_path): os.remove(temp_path)
def validate_repo_upstream(args): """ Make sure we're in the right repository, not a fork """ output = subprocess.check_output(["git", "remote", "get-url", "origin"], encoding="utf-8").strip() if output not in args.valid_git_upstreams: raise ReleaseException( "Releases may only be published from the upstream OSS buck repository" )
def get_token(token_file): """ Reads the first line from token_file to get a token """ with open(token_file, "r") as fin: ret = fin.read().strip() if not ret: raise ReleaseException("No valid token found in {}".format(token_file)) return ret
def validate_tap(homebrew_dir, tap_repository, version): logging.info("Validating that brew installs with new tap information") brew_target = tap_repository + "/buck" brew(homebrew_dir, ["uninstall", "--force", brew_target]) brew(homebrew_dir, ["install", brew_target]) output = (brew(homebrew_dir, ["info", brew_target], capture_output=True).stdout.decode("utf-8").splitlines()[0]) if "{}/buck: stable {}".format(tap_repository, version) not in output: raise ReleaseException( "Expected version {} to be installed, but got this from `brew info {}`: {}" .format(version, tap_repository, output))
def publish_chocolatey(chocolatey_file, chocolatey_api_key): """ Publish a nupkg to chocolatey """ url = "https://chocolatey.org/api/v2/package" headers = {"X-NuGet-ApiKey": chocolatey_api_key} logging.info("Publishing chocolatey package at {}".format(chocolatey_file)) with open(chocolatey_file, "rb") as fin: response = requests.put(url, headers=headers, data=fin) if response.status_code == 409: raise ReleaseException("Package and version already exists on chocolatey") response.raise_for_status() logging.info("Published chocolatey package at {}".format(chocolatey_file))
def build_bottle_file( homebrew_dir, tap_repository, tap_path, release_version, target_macos_version, output_dir, ): """ Builds the actual bottle file via brew Args: tap_repository: The name of the tap repository tap_path: The local path to the given tap repository release_version: The version that should be built (no "v" prefix) target_macos_version: The target macos short nameto use in the resulting path output_dir: The directory to move the build artifact to after building Returns: The path to the bottle.tar.gz """ brew_target = tap_repository + "/buck" logging.info("Building bottle") # Cool, so install --force will still not rebuild. Uninstall, and just don't # care if the uninstall fails brew(homebrew_dir, ["uninstall", "--force", brew_target], tap_path, check=False) brew(homebrew_dir, ["install", "--force", "--build-bottle", brew_target], tap_path) logging.info("Creating bottle file") brew( homebrew_dir, ["bottle", "--no-rebuild", "--skip-relocation", brew_target], tap_path, ) logging.info("Created bottle file") bottle_filename = "buck-{ver}.{macos_ver}.bottle.tar.gz".format( ver=release_version, macos_ver=target_macos_version) bottle_path = os.path.join(output_dir, bottle_filename) bottles = glob.glob( os.path.join(tap_path, "buck--{}*.bottle.tar.gz".format(release_version))) if len(bottles) != 1: raise ReleaseException( "Got an invalid number of bottle files ({} files: {})".format( len(bottles), " ".join(bottles))) shutil.move(bottles[0], bottle_path) return bottle_path
def validate_environment(args): """ Make sure we can build """ validate_repo_upstream(args) if args.build_deb: ret = docker( args.docker_linux_host, ["info", "-f", "{{.OSType}}"], check=False, capture_output=True, ) host = args.docker_linux_host or "localhost" if ret.returncode != 0: raise ReleaseException( "docker info on linux host {} failed. debs cannot be built", host) host_os = ret.stdout.decode("utf-8").strip() if host_os != "linux": raise ReleaseException( "docker info on host {} returned type '{}' not 'linux'. debs cannot be built", host, host_os, ) if args.build_chocolatey: ret = docker( args.docker_windows_host, ["info", "-f", "{{.OSType}}"], check=False, capture_output=True, ) host = args.docker_windows_host or "localhost" if ret.returncode != 0: raise ReleaseException( "docker info on windows host {} failed. chocolatey nupkgs cannot be built", host, ) host_os = ret.stdout.decode("utf-8").strip() if host_os != "windows": raise ReleaseException( "docker info on host {} returned type '{}' not 'windows'. chocolatey nupkgs cannot be built", host, host_os, ) if args.build_homebrew: if args.homebrew_dir: if not os.path.exists(args.homebrew_dir): raise ReleaseException( "Specified homebrew path, {}, does not exist", args.homebrew_dir) brew_path = os.path.join(args.homebrew_dir, "bin", "brew") try: ret = run([brew_path, "--version"]) except Exception: raise ReleaseException( "{} --version failed. bottles cannot be created", brew_path)
def build_bottle_file( homebrew_dir, tap_repository, tap_path, release_version, target_macos_version, output_dir, ): """ Builds the actual bottle file via brew Args: tap_repository: The name of the tap repository tap_path: The local path to the given tap repository release_version: The version that should be built (no "v" prefix) target_macos_version: The target macos short nameto use in the resulting path output_dir: The directory to move the build artifact to after building Returns: The path to the bottle.tar.gz """ brew_target = tap_repository + "/buck" # So, if buck wasn't linked to begin with, we can't unlink it. Ideally the install # fails down the road. There is, so far as I could tell, no way to verify if # a formula is linked :/ logging.info("Unlinking buck") brew(homebrew_dir, ["unlink", brew_target], tap_path, check=False) logging.info("Building bottle") # If there is still a buck file that exists, move it out of the way for now # This should generally not be an issue outside of FB with temp_move_file("/usr/local/bin/buck") as moved: # Cool, so install --force will still not rebuild. Uninstall, and just don't # care if the uninstall fails brew( homebrew_dir, ["uninstall", "--force", "--build-bottle", brew_target], tap_path, check=False, ) brew( homebrew_dir, ["install", "--force", "--build-bottle", brew_target], tap_path, ) logging.info("Creating bottle file") brew( homebrew_dir, ["bottle", "--no-rebuild", "--skip-relocation", brew_target], tap_path, ) logging.info("Created bottle file") if moved: # Make sure to unlink again so that we can move the original file back logging.info("Unlinking buck again") brew(homebrew_dir, ["unlink", brew_target], tap_path) bottle_filename = "buck-{ver}.{macos_ver}.bottle.tar.gz".format( ver=release_version, macos_ver=target_macos_version) bottle_path = os.path.join(output_dir, bottle_filename) bottles = glob.glob( os.path.join(tap_path, "buck--{}*.bottle.tar.gz".format(release_version))) if len(bottles) != 1: raise ReleaseException( "Got an invalid number of bottle files ({} files: {})".format( len(bottles), " ".join(bottles))) shutil.move(bottles[0], bottle_path) return bottle_path