def generate_cgo_file( installed_lib_paths: List[Path], lib_names: List[str], installed_include_paths: List[Path], go_os: str, go_arch: str, ) -> None: libs = [str(x) for x in installed_lib_paths] libs.extend([f"-l{lib}" for lib in lib_names]) if go_os in ("linux", "windows"): libs.extend(["-static-libstdc++", "-static-libgcc"]) template_file = Path.cwd() / "cgo_template.go.in" # having go_os and go_arch in the filename acts as an implicit build rule # e.g. only build cgo_linux_amd64.go on Linux amd64 dst_file = Path.cwd() / "core" / f"cgo_{go_os}_{go_arch}.go" ui.info_1(f"Generating {dst_file}") shutil.copy(template_file, dst_file) content = dst_file.read_text() content = content.replace( "{{INCLUDEDIRS}}", " ".join([f"-I{dir}" for dir in installed_include_paths]), ) content = content.replace("{{LIBS}}", " ".join(libs)) with open(dst_file, mode="w") as f: f.write(content)
def _prepare_profile(self) -> None: conan_out = self.src_path / "conan" / "out" / self.profile package_path = conan_out / "package" depsConfig = DepsConfig(self.src_path / "conan" / "out" / self.profile) self._copy_includes(package_path, depsConfig) native_path = self.src_path / "native" / self.target_triplet if native_path.exists(): shutil.rmtree(native_path) native_path.mkdir(parents=True) if self._is_windows_target: for lib_path in depsConfig.all_lib_paths(): ui.info_1("copying", lib_path, "to", native_path) shutil.copy(lib_path, native_path) # handle mingw target mingw_path = self.src_path / "native" / "x86_64-pc-windows-gnu" # prepare is called twice, so ignore when dirs exists shutil.copytree(native_path, mingw_path, dirs_exist_ok=True) else: self._merge_all_libs(depsConfig, package_path, native_path) include_path = package_path / "include" / "ctanker" bind_gen( header_source=include_path / "ctanker.h", output_file=native_path / "ctanker.rs", include_path=include_path, dynamic_loading=self._is_windows_target, ) if self._is_windows_target: shutil.copyfile(native_path / "ctanker.rs", mingw_path / "ctanker.rs")
def build(*, native_from_sources: bool) -> None: ui.info_1("build everything") if native_from_sources: ci.run("./gradlew", "tanker-bindings:buildNativeRelease") else: ci.run("./gradlew", "tanker-bindings:useDeployedNativeRelease") ci.run("./gradlew", "tanker-bindings:assembleRelease")
def generate_token(github_client: github3.GitHub) -> str: ui.info_1("Creating new GitHub token") username = ui.ask_string("Please enter you GitHub username") password = getpass.getpass("Password: "******"repo"] # Need a different note for each device, otherwise # gh_api.authorize() will fail note = "tsrc-" + str(uuid.uuid4()) note_url = "https://TankerHQ.github.io/tsrc" def ask_2fa() -> str: return cast(str, ui.ask_string("2FA code: ")) authorization = github3.authorize( username, password, scopes, note=note, note_url=note_url, two_factor_callback=ask_2fa, github=github_client, ) return cast(str, authorization.token)
def run(args: argparse.Namespace) -> None: workspace_path = args.workspace_path or Path.cwd() cfg_path = workspace_path / ".tsrc" / "config.yml" if cfg_path.exists(): raise tsrc.Error( f"Workspace already configured. `{cfg_path}` already exists") ui.info_1("Configuring workspace in", ui.bold, workspace_path) workspace_config = WorkspaceConfig( manifest_url=args.manifest_url, manifest_branch=args.manifest_branch, clone_all_repos=args.clone_all_repos, repo_groups=args.groups or [], shallow_clones=args.shallow_clones, singular_remote=args.singular_remote, ) workspace_config.save_to_file(cfg_path) workspace = Workspace(workspace_path) workspace.update_manifest() manifest = workspace.get_manifest() workspace.repos = repos_from_config(manifest, workspace_config) workspace.clone_missing() workspace.set_remotes() workspace.perform_filesystem_operations() ui.info_2("Workspace initialized") ui.info_2("Configuration written in", ui.bold, workspace.cfg_path)
def main(args: argparse.Namespace) -> None: path_as_str = args.workspace_path or os.getcwd() workspace_path = Path(path_as_str) cfg_path = workspace_path / ".tsrc" / "config.yml" if cfg_path.exists(): raise tsrc.Error("Workspace already configured with file " + cfg_path) ui.info_1("Configuring workspace in", ui.bold, workspace_path) workspace_config = WorkspaceConfig( manifest_url=args.url, manifest_branch=args.branch, clone_all_repos=args.clone_all_repos, repo_groups=args.groups, shallow_clones=args.shallow, ) workspace_config.save_to_file(cfg_path) workspace = Workspace(workspace_path) workspace.update_manifest() workspace.clone_missing() workspace.set_remotes() workspace.copy_files() ui.info_2("Workspace initialized") ui.info_2("Configuration written in", ui.bold, workspace.cfg_path)
def run(args: argparse.Namespace) -> None: force = args.force update_manifest = args.update_manifest groups = args.groups all_cloned = args.all_cloned regex = args.regex iregex = args.iregex workspace = get_workspace(args) num_jobs = get_num_jobs(args) if update_manifest: ui.info_2("Updating manifest") workspace.update_manifest() else: ui.info_2("Not updating manifest") workspace.repos = resolve_repos(workspace, groups=groups, all_cloned=all_cloned, regex=regex, iregex=iregex) workspace.clone_missing(num_jobs=num_jobs) workspace.set_remotes(num_jobs=num_jobs) workspace.sync(force=force, num_jobs=num_jobs) workspace.perform_filesystem_operations() ui.info_1("Workspace synchronized")
def handle_sdk_deps(self, *, tanker_conan_ref: str) -> None: ui.info_1("Installing sdk-native for archs: ", self.archs) # clean last build files, to avoid losing 2 days when an unexpected binary is used. self.conan_out_path.rmtree_p() self.install_sdk_native(tanker_conan_ref) self.generate_fat_libraries() self.copy_headers()
def handle_sdk_deps(self) -> None: ui.info_1("copying sdk-native for profiles: ", [str(p) for p in self.host_profiles]) for host_profile in self.host_profiles: specific_arch_path = self.libraries_path / str(host_profile) specific_arch_path.mkdir(parents=True, exist_ok=True) libs_path = DepsConfig( self.get_build_path(host_profile)).all_lib_paths() self.builder.merge_libraries( libs=list(libs_path), keep_symbols_regex="^_?tanker_.*", output_path=specific_arch_path / "libtankerdeps.a", ) self.copy_headers() libs = [ self.libraries_path / str(p) / "libtankerdeps.a" for p in self.host_profiles ] xcframework_path = (self.src_path / "Tanker" / "Frameworks" / "TankerDeps.xcframework") self.builder.generate_xcframework( xcframework_path=xcframework_path, libs=libs, include_path=self.private_headers_path, output_lib_filename="libtankerdeps.a", )
def build_and_test() -> None: ui.info_1("Building everything") tankerci.run("./gradlew", "assemble") ui.info_1("Running tests") # In case you're wondering: # https://stackoverflow.com/questions/50104666/gradle-difference-between-test-and-check tankerci.run("./gradlew", "test")
def deploy(*, git_tag: str) -> None: version = tankerci.version_from_git_tag(git_tag) tankerci.bump_files(version) build_and_test() ui.info_1("Deploying Identity SDK to maven.tanker.io") tankerci.gcp.GcpProject("tanker-prod").auth() tankerci.run("./gradlew", "publish")
def deploy(*, version: str, tanker_ref: str) -> None: tankerci.bump_files(version) prepare(TankerSource.DEPLOYED, False, tanker_ref) build() ui.info_1("Deploying SDK to https://storage.googleapis.com/maven.tanker.io") tankerci.gcp.GcpProject("tanker-prod").auth() tankerci.run("./gradlew", "tanker-bindings:publish")
def copy_test_sources(self) -> None: # trick cocoapods copy the Dummy.m to avoid error during validation ui.info_1("Copying dummy test file") dummy_test_path = self.src_path / "Tanker/Tests/Dummy.m" dest_path = self.dest_path / "Tests" dest_path.makedirs_p() ui.info_2(dummy_test_path, "->", dest_path) dummy_test_path.copy(dest_path)
def copy_test_sources(self) -> None: # trick cocoapods copy the Dummy.m to avoid error during validation ui.info_1("Copying dummy test file") dummy_test_path = self.src_path / "Tanker/Tests/Dummy.m" dest_path = self.dest_path / "Tests" dest_path.mkdir(parents=True, exist_ok=True) ui.info_2(dummy_test_path, "->", dest_path) shutil.copy(dummy_test_path, dest_path)
def init(working_path: Path, *, current_version: str) -> None: """ Interactively creates a new tbump.toml """ ui.info_1("Generating tbump config file") tbump_path = working_path / "tbump.toml" if tbump_path.exists(): ui.fatal(tbump_path, "already exists") template = textwrap.dedent("""\ # Uncomment this if your project is hosted on GitHub: # github_url = https://github.com/<user or organization>/<project>/ [version] current = "@current_version@" # Example of a semver regexp. # Make sure this matches current_version before # using tbump regex = ''' (?P<major>\\d+) \\. (?P<minor>\\d+) \\. (?P<patch>\\d+) ''' [git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" """) file_template = textwrap.dedent(""" # For each file to patch, add a [[file]] config section containing # the path of the file, relative to the tbump.toml location. [[file]] src = "..." """) hooks_template = textwrap.dedent(""" # You can specify a list of commands to # run after the files have been patched # and before the git commit is made # [[before_commit]] # name = "check changelog" # cmd = "grep -q {new_version} Changelog.rst" # Or run some commands after the git tag and the branch # have been pushed: # [[after_push]] # name = "publish" # cmd = "./publish.sh" """) to_write = template.replace("@current_version@", current_version) to_write += file_template to_write += hooks_template tbump_path.write_text(to_write) ui.info_2(ui.check, "Generated tbump.toml")
def main(args: argparse.Namespace) -> None: workspace = tsrc.cli.get_workspace(args) manifest_path = args.manifest_path ui.info_1("Applying manifest from", args.manifest_path) workspace.local_manifest = LocalManifest(manifest_path.parent) workspace.clone_missing() workspace.set_remotes() workspace.copy_files()
def deploy(*, git_tag: str) -> None: version = ci.version_from_git_tag(git_tag) ci.bump_files(version) build(native_from_sources=False) test() ui.info_1("Deploying SDK to maven.tanker.io") ci.gcp.GcpProject("tanker-prod").auth() ci.run("./gradlew", "tanker-bindings:publish")
def test() -> None: ui.info_1("Running tests") config_path = ci.tanker_configs.get_path() ci.run( "./gradlew", "tanker-bindings:testRelease", f"-DTANKER_CONFIG_FILEPATH={config_path}", "-DTANKER_CONFIG_NAME=dev", )
def generate_archive(self) -> Path: version = self.get_version_from_spec() ui.info_1("Generating archive, version:", version) archive_name = "tanker-ios-sdk-%s.tar.gz" % version with tankerci.working_directory(self.dest_path): tankerci.run("tar cfvz %s *" % archive_name, shell=True) shutil.copy(archive_name, self.src_path) res = self.src_path / archive_name ui.info_2("Generated", res) return res
def apply_manifest(workspace: tsrc.Workspace, manifest_path: Path, **kwargs: Any) -> None: """ apply a local manifest file """ ui.info_1("Applying manifest from", manifest_path) manifest = tsrc.manifest.load(manifest_path) workspace.repos = repos_from_config(manifest, workspace.config) workspace.clone_missing() workspace.set_remotes() workspace.perform_filesystem_operations()
def ask_or_default(name, label, defaults={}): result = '' if name not in defaults.keys(): result = cli_ui.ask_string(label + '?') else: result = defaults[name] cli_ui.info_1(label + ':', result) return result
def main(args: argparse.Namespace) -> None: workspace_path = args.workspace_path or os.getcwd() workspace = tsrc.Workspace(Path(workspace_path)) ui.info_1("Configuring workspace in", ui.bold, workspace_path) manifest_config = tsrc.workspace.ManifestConfig.from_args(args) workspace.configure_manifest(manifest_config) workspace.load_manifest() workspace.clone_missing() workspace.set_remotes() workspace.copy_files() ui.info("Done", ui.check)
def run(args: argparse.Namespace) -> None: # Note: # we want to support both: # $ tsrc foreach -c 'shell command' # and # $ tsrc foreach -- some-cmd --some-opts # # Due to argparse limitations, `cmd` will always be a list, # but we need a *string* when using 'shell=True'. # # So transform use the value from `cmd` and `shell` so that: # * action.command is suitable as argument to pass to subprocess.run() # * action.description is suitable for display purposes command: Command = [] if args.shell: if len(args.cmd) != 1: die("foreach -c must be followed by exactly one argument") command = args.cmd[0] description = args.cmd[0] else: if not args.cmd: die("needs a command to run") command = args.cmd description = " ".join(args.cmd) shell = args.shell command = command description = description num_jobs = get_num_jobs(args) workspace = get_workspace_with_repos(args) cmd_runner = CmdRunner(workspace.root_path, command, description, shell=shell) repos = workspace.repos ui.info_1(f"Running `{description}` on {len(repos)} repos") collection = process_items(repos, cmd_runner, num_jobs=num_jobs) errors = collection.errors if errors: ui.error(f"Command failed for {len(errors)} repo(s)") if cmd_runner.parallel: # Print output of failed commands that were hidden for (item, error) in errors.items(): ui.info(item) ui.info("-" * len(item)) ui.info(error) else: # Just print the repos for item in errors: ui.info(ui.green, "*", ui.reset, item) raise ForeachError() else: ui.info("OK", ui.check)
def copy_sources(self) -> None: ui.info_1("Copying sources") sources_path = self.src_path / "Tanker/Sources" ui.info_2(sources_path, "->", self.dest_path) sources_path.copytree(self.dest_path / "Sources") export_list_src = self.src_path / "Tanker/export_symbols.list" export_list_dest = self.dest_path / "export_symbols.list" ui.info_2(export_list_src, "->", export_list_dest) export_list_src.copy(export_list_dest)
def _copy_folder_content(src_path: Path, dest_path: Path) -> None: ui.info_1("Moving content of", src_path, "to", dest_path) for src_dir in src_path.dirs(): dest_dir = dest_path / src_dir.basename() dest_dir.rmtree_p() ui.info_2(src_dir, "->", dest_dir) src_dir.copytree(dest_dir) for src_file in src_path.files(): dest_file = dest_path / src_file.basename() dest_file.remove_p() ui.info_2(src_file, "->", dest_file) src_file.copy2(dest_file)
def generate_fat_libraries(self) -> None: ui.info_1("Generating fat libraries") self.libraries_path.rmtree_p() self.libraries_path.makedirs_p() for lib_name, libs in self.get_all_dependency_libs().items(): output = self.libraries_path / lib_name ci.run("lipo", "-create", "-output", output, *libs, cwd=self.conan_out_path)
def prepare( profile: str, tanker_source: TankerSource, update: bool, tanker_ref: Optional[str] = None, ) -> None: profile_prefix = profile.split("-")[0] go_os, go_arch = PROFILE_OS_ARCHS[profile_prefix] conan_out = Path.cwd() / "conan" if tanker_source == TankerSource.DEPLOYED and not tanker_ref: tanker_ref = "tanker/latest-stable@" tankerci.conan.install_tanker_source( tanker_source, output_path=conan_out, profiles=[profile], update=update, tanker_deployed_ref=tanker_ref, ) conan_path = [x for x in conan_out.iterdir() if x.is_dir()][0] deps_info = DepsConfig(conan_path) go_install = Path("ctanker") / f"{go_os}-{go_arch}" install_path = Path.cwd() / "core" / go_install if tanker_source == TankerSource.DEPLOYED: copy_deps(deps_info, install_path) installed_include_path = install_path / "include" ui.info_1(f"cleaning {installed_include_path}") tanker_headers = installed_include_path / "Tanker" helpers_headers = installed_include_path / "Helpers" if tanker_headers.exists(): shutil.rmtree(tanker_headers) if helpers_headers.exists(): shutil.rmtree(helpers_headers) generate_cgo_file( [Path("-L${SRCDIR}") / go_install / "lib"], list(deps_info.all_libs()), [go_install / "include"], go_os, go_arch, ) else: generate_cgo_file( list(deps_info.all_lib_paths()), list(deps_info.all_system_libs()), deps_info["tanker"].include_dirs, go_os, go_arch, )
def _copy_folder_content(src_path: Path, dest_path: Path) -> None: ui.info_1("Moving content of", src_path, "to", dest_path) src_dirs = [p for p in src_path.iterdir() if p.is_dir()] for src_dir in src_dirs: dest_dir = dest_path / src_dir.name if dest_dir.exists(): shutil.rmtree(dest_dir) ui.info_2(src_dir, "->", dest_dir) shutil.copytree(src_dir, dest_dir) src_files = [p for p in src_path.iterdir() if p.is_file()] for src_file in src_files: dest_file = dest_path / src_file.name if dest_file.exists(): dest_file.unlink() ui.info_2(src_file, "->", dest_file) shutil.copy2(src_file, dest_file)
def main(args: argparse.Namespace) -> None: workspace_path = args.workspace_path or os.getcwd() workspace = tsrc.Workspace(Path(workspace_path)) ui.info_1("Configuring workspace in", ui.bold, workspace_path) as_dict = vars(args) relevant_keys = [x.name for x in attr.fields(ManifestConfig)] for key in list(as_dict.keys()): if key not in relevant_keys: del as_dict[key] manifest_config = ManifestConfig.from_dict(as_dict) workspace.configure_manifest(manifest_config) workspace.load_manifest() workspace.clone_missing() workspace.set_remotes() workspace.copy_files() ui.info("Done", ui.check)
def main(): ui.info_1("Starting CI") all_checks = init_checks() check_list = sys.argv[1:] checks = all_checks if check_list: checks = [c for c in checks if c.name in check_list] for check in checks: check.run() failed_checks = [check for check in checks if not check.ok] if not failed_checks: ui.info(ui.green, "CI passed") return for check in failed_checks: ui.error(check.name, "failed") sys.exit(1)