def _hook_pre_images(task, args): # cleanup dragon.exec_cmd(cwd=dragon.OUT_DIR, cmd='rm -rf images') dragon.exec_cmd(cwd=dragon.OUT_DIR, cmd='rm -rf xcodeApps') manifest_path = os.path.join(dragon.OUT_DIR, 'manifest.xml') dragon.gen_manifest_xml(manifest_path) task.call_base_pre_hook(args)
def _jazzy(calldir, scheme, extra_args): cmd = ['jazzy'] cmd.append('-x -scheme,{}'.format(scheme)) outdir = os.path.join(dragon.OUT_DIR, 'docs') cmd.append('-o {}'.format(outdir)) cmd.extend(extra_args) dragon.exec_cmd(' '.join(cmd), cwd=calldir)
def _gradle(calldir, abis, extra_args): # get the real version version = dragon.PARROT_BUILD_VERSION # create a fake version which is a pure release from the current version version_release = dragon.Version(str(version)) version_release.type = dragon.Version.TYPE_RELEASE version_release.custom = None version_release.custom_number = 0 version_release.type_string = None # appVersionName is the name of the pure release version vname = str(version_release) # suffix is the name of the actual version, without the vname prefix suffix = str(version)[len(vname):] # Version code is generated by the complete version vcode = common.get_version_code(version) cmd = ['./gradlew'] if os.environ.get('MOVE_APPSDATA_IN_OUTDIR'): cmd.append('--project-cache-dir {}'.foramt( os.path.join(dragon.OUT_DIR, '.gradle'))) cmd.append('-PalchemyOutRoot={}'.format(dragon.OUT_ROOT_DIR)) cmd.append('-PalchemyOut={}'.format(dragon.OUT_DIR)) cmd.append('-PalchemyProduct={}'.format(dragon.PRODUCT)) if abis: cmd.append('-PappAbis="{}"'.format(' '.join(abis))) cmd.append('-PappVersionName={}'.format(vname)) if suffix: cmd.append('-PappVersionNameSuffix={}'.format(suffix)) cmd.append('-PappVersionCode={}'.format(vcode)) cmd.extend(extra_args) dragon.exec_cmd(' '.join(cmd), cwd=calldir)
def _hook_images(task, args): images_dir = os.path.join(dragon.OUT_DIR, 'images') dragon.makedirs(images_dir) for app in apps: archive_path = app._archivePath(dragon.OUT_DIR) # Compress .xcarchive archive_dir = os.path.dirname(archive_path) archive_name = os.path.basename(archive_path) tarname = os.path.join( images_dir, '{}.tar.gz'.format(os.path.basename(archive_path))) cwd = os.getcwd() os.chdir(archive_dir) tar = tarfile.open(tarname, 'w:gz') tar.add(archive_name) tar.close() os.chdir(cwd) inhouse_path = _export_archive(calldir, archive_path, app) # Link .ipa if inhouse_path: dragon.exec_cmd(cwd=images_dir, cmd='ln -s {} {}'.format( inhouse_path, app.ipa_name)) # build.prop build_prop_file = os.path.join(dragon.OUT_DIR, 'staging', 'etc', 'build.prop') dragon.exec_cmd(cwd=dragon.OUT_DIR, cmd='cp {} .'.format(build_prop_file)) # next hooks task.call_base_exec_hook(args)
def _ndk_build(calldir, module, abis, extra_args, ignore_failure=False): # Check if asan is used raw_asan = dragon.get_alchemy_var('USE_ADDRESS_SANITIZER') if not raw_asan or raw_asan == '0': asan = False else: asan = True outdir = os.path.join(dragon.OUT_DIR, 'jni', module) cmd = ['${ANDROID_NDK_PATH}/ndk-build'] cmd.append('NDK_OUT={}'.format(os.path.join(outdir, 'obj'))) cmd.append('NDK_LIBS_OUT={}'.format(os.path.join(outdir, 'libs'))) cmd.append('PRODUCT_DIR={}'.format( os.path.join(dragon.WORKSPACE_DIR, 'products', dragon.PRODUCT, dragon.VARIANT))) cmd.append('PRODUCT_OUT_DIR={}'.format(dragon.OUT_DIR)) cmd.append('APP_ABI="{}"'.format(' '.join(abis))) if asan: cmd.append('LOCAL_ALLOW_UNDEFINED_SYMBOLS=true') if dragon.OPTIONS.verbose: cmd.append('V=1') cmd.append('-j{}'.format(dragon.OPTIONS.jobs.job_num)) cmd.extend(extra_args) try: dragon.exec_cmd(cmd=' '.join(cmd), cwd=calldir) except dragon.ExecError: if not ignore_failure: raise
def build_android_app(dirpath, args, release=False, clean=False): # Build application cmd = "./gradlew " if clean: cmd += "clean " else: cmd += "assembleRelease " if release else "assembleDebug " if args: cmd += " ".join(args) dragon.exec_cmd(cmd=cmd, cwd=dirpath)
def hook_gen_sources(task, args): packages_dir = os.path.join(dragon.WORKSPACE_DIR, "packages") for package in os.listdir(packages_dir): try: path = os.path.join(packages_dir, package) if os.path.isfile(os.path.join(path, "updateGenerated.sh")): dragon.exec_cmd(cmd="./updateGenerated.sh", cwd=path) dragon.exec_cmd(cmd="git status", cwd=path) except dragon.TaskError as ex: dragon.logging.error(str(ex))
def build_ios_app(dirpath, project, sdk, args, release=False): # Build application cmd = "xcodebuild " cmd += "-project %s " % project cmd += "-sdk %s " % sdk cmd += "-configuration DebugWithLocalSDK " if sdk == "iphonesimulator": cmd += "-arch x86_64 " if args: cmd += " ".join(args) dragon.exec_cmd(cmd=cmd, cwd=dirpath)
def gen_manifest_xml(filepath): # Generate an intermediary manifest # It avoid issues if filepath is the same as the source manifest if not os.path.exists(os.path.dirname(filepath)): raise dragon.TaskError("Cannot generate manifest as the " "directory does not exist.") temp_file = tempfile.mkstemp(suffix=".xml")[1] cmd = "repo manifest --revision-as-HEAD " \ "--suppress-upstream-revision -o %s" % temp_file dragon.exec_cmd(cmd, extra_env={"GIT_PAGER": "cat"}) cmd = "mv %s %s" % (temp_file, filepath) dragon.exec_cmd(cmd)
def hook_post_images(task, args): # Create the images directory so the release task is happy dragon.makedirs(dragon.IMAGES_DIR) # Get json config file json_cfg = dragon.get_json_config() # Alchemy images to get verbatim if json_cfg and "images" in json_cfg: for _ext in json_cfg["images"].get("extensions", []): filename = "%s-%s%s" % (dragon.PRODUCT, dragon.VARIANT, _ext) src_path = os.path.join(dragon.OUT_DIR, filename) dst_path = os.path.join(dragon.IMAGES_DIR, filename) if os.path.exists(src_path): dragon.exec_cmd("mv -f %s %s" % (src_path, dst_path))
def hook_post_images(task, args): # Create the images directory so the release task is happy dragon.makedirs(dragon.IMAGES_DIR) # Get json config file json_cfg = dragon.get_json_config() # Alchemy images to get verbatim if json_cfg and "images" in json_cfg: for _ext in json_cfg["images"].get("extensions", []): filename = "%s-%s%s" % (dragon.PRODUCT, dragon.VARIANT, _ext) src_path = os.path.join(dragon.OUT_DIR, filename) dst_path = os.path.join(dragon.IMAGES_DIR, filename) if os.path.exists(src_path): dragon.exec_cmd("mv -f %s %s" % (src_path, dst_path))
def _hook_images(task, args): # tar symbols symbols_file = os.path.join( dragon.OUT_DIR, 'symbols-{}-{}.tar'.format(dragon.PRODUCT, dragon.VARIANT)) dragon.exec_cmd(cwd=symbols_path, cmd='find . -name "*.so" | tar -cv -f ' + symbols_file + ' --files-from -') # link apk(s) images_dir = os.path.join(dragon.OUT_DIR, 'images') dragon.makedirs(images_dir) for app in apps: dragon.exec_cmd(cwd=images_dir, cmd='ln -s {} .'.format(app.apk_file)) # build.prop build_prop_file = os.path.join(dragon.OUT_DIR, def_abi, 'staging', 'etc', 'build.prop') dragon.exec_cmd(cwd=dragon.OUT_DIR, cmd='cp {} .'.format(build_prop_file)) # global.config global_config_file = os.path.join(dragon.OUT_DIR, def_abi, 'global.config') dragon.exec_cmd(cwd=dragon.OUT_DIR, cmd='cp {} .'.format(global_config_file)) # next hooks task.call_base_exec_hook(args)
def hook_alchemy_genproject(task, args): script_path = os.path.join(dragon.ALCHEMY_HOME, "scripts", "genproject", "genproject.py") subscript_name = task.name.replace("gen", "") if "-h" in args or "--help" in args: dragon.exec_cmd("%s %s -h" % (script_path, subscript_name)) dragon.LOGW("Note: The -b option and dump_xml file are automatically given.") raise dragon.TaskExit() dump_xml = dragon.gen_alchemy_dump_xml() cmd_args = [script_path, subscript_name, "-b", "'-p %s-%s -A'" % (dragon.PRODUCT, dragon.VARIANT), dump_xml, " ".join(args)] dragon.exec_cmd(" ".join(cmd_args))
def hook_pre_release(task, args): # Do not include gdb server in generated images os.environ["TARGET_INCLUDE_GDBSERVER"] = "0" dragon.check_build_id() if dragon.PARROT_BUILD_PROP_UID.lower() != dragon.PARROT_BUILD_PROP_UID: raise dragon.TaskError("You shall provide a lowercase build_id") dragon.exec_cmd("rm -rf %s" % dragon.RELEASE_DIR) if platform.system() == 'Linux': dragon.exec_cmd("dpkg --list > os_packages.txt", cwd=dragon.OUT_DIR) elif platform.system() == 'Darwin': data = dragon.exec_shell("brew info --installed --json=v1") data_json = json.loads(data) with open(os.path.join(dragon.OUT_DIR, 'os_packages.txt'), 'w') as os_packages: data_lines = json.dumps(data_json, indent=4) os_packages.write(data_lines)
def hook_pre_release(task, args): # Do not include gdb server in generated images os.environ["TARGET_INCLUDE_GDBSERVER"] = "0" dragon.check_build_id() if dragon.PARROT_BUILD_PROP_UID.lower() != dragon.PARROT_BUILD_PROP_UID: raise dragon.TaskError("You shall provide a lowercase build_id") dragon.exec_cmd("rm -rf %s" % dragon.RELEASE_DIR) dragon.makedirs(dragon.OUT_DIR) if platform.system() == 'Linux': dragon.exec_cmd("dpkg --list > os_packages.txt", cwd=dragon.OUT_DIR) elif platform.system() == 'Darwin': data = dragon.exec_shell("brew info --installed --json=v1") data_json = json.loads(data) with open(os.path.join(dragon.OUT_DIR, 'os_packages.txt'), 'w') as os_packages: data_lines = json.dumps(data_json, indent=4) os_packages.write(data_lines)
def _xctool(calldir, workspace, configuration, scheme, action, reporter, extra_args): cmd = ['xctool'] if (dragon.VARIANT == 'ios_sim'): cmd.append('--sdk iphonesimulator') if not any('destination' in arg for arg in extra_args): cmd.append('--arch x86_64') else: cmd.append('--sdk iphoneos') cmd.append('--workspace {}'.format(workspace)) cmd.append('--configuration {}'.format(configuration)) cmd.append('--scheme {}'.format(scheme)) cmd.append('--reporter pretty') if (reporter): cmd.append('--reporter {}'.format(reporter)) cmd.append(action) cmd.extend(extra_args) dragon.exec_cmd(' '.join(cmd), cwd=calldir)
def hook_alchemy_genproject(task, args): script_path = os.path.join(dragon.ALCHEMY_HOME, "scripts", "genproject", "genproject.py") subscript_name = task.name.replace("gen", "") if "-h" in args or "--help" in args: dragon.exec_cmd("%s %s -h" % (script_path, subscript_name)) dragon.LOGW( "Note: The -b option and dump_xml file are automatically given.") raise dragon.TaskExit() dump_xml = dragon.gen_alchemy_dump_xml() cmd_args = [ script_path, subscript_name, "-b", "'-p %s-%s -A'" % (dragon.PRODUCT, dragon.VARIANT), dump_xml, " ".join(args) ] dragon.exec_cmd(" ".join(cmd_args))
def _export_archive(dirpath, archive_path, app): signing_infos = app.inhouse_infos if signing_infos is None: return None export_plist = _create_export_plist(signing_infos, app.bundle_id) ipa_path = os.path.join(dragon.OUT_DIR, 'xcodeApps', 'temp') ipa_out_path = os.path.join(dragon.OUT_DIR, 'xcodeApps', 'inhouse') cmd = 'xcodebuild -exportArchive -archivePath {} -exportOptionsPlist {} ' \ '-allowProvisioningUpdates -exportPath {}'.format(archive_path, export_plist, ipa_path) dragon.exec_cmd(cwd=dirpath, cmd=cmd) ipa_raw_path = '{}/{}.ipa'.format(ipa_path, app.scheme) os.makedirs(ipa_out_path, exist_ok=True) ipa_final_path = '{}/{}'.format(ipa_out_path, app.ipa_name) os.rename(ipa_raw_path, ipa_final_path) return ipa_final_path
def relative_symlink(src, dest): if dragon.WORKSPACE_DIR in dragon.OUT_DIR: for _file_check in [src, dest]: if dragon.WORKSPACE_DIR not in os.path.realpath(_file_check): raise IOError("'%s' is not part of the workspace." % _file_check) else: for _file_check in [src, dest]: if dragon.WORKSPACE_DIR not in os.path.realpath(_file_check): dragon.LOGW("'%s' is not part of the workspace." % _file_check) if os.path.lexists(dest): if not os.path.islink(dest): raise IOError("'%s' should not be a regular file/directory" % dest) dragon.exec_cmd("rm -f %s" % dest) makedirs(os.path.dirname(dest)) dragon.exec_cmd("ln -fs %s %s" % (os.path.relpath(src, os.path.dirname(dest)), dest))
def _hook_alchemy_genproject_android(task, args, abi): script_path = os.path.join(dragon.ALCHEMY_HOME, 'scripts', 'genproject', 'genproject.py') subscript_name = task.name.replace('gen', '') if '-h' in args or '--help' in args: dragon.exec_cmd('{} {} -h'.format(script_path, subscript_name)) dragon.LOGW( 'Note: The -b option and dump_xml file are automatically given.') raise dragon.TaskExit() dragon.exec_cmd(cmd='./build.sh -p {}-{} --abis {} -A dump-xml'.format( dragon.PRODUCT, dragon.VARIANT, abi)) dump_xml = os.path.join(dragon.OUT_DIR, abi, 'alchemy-database.xml') cmd_args = [ script_path, subscript_name, '-b', "'-p {}-{} --abis {} -A'".format(dragon.PRODUCT, dragon.VARIANT, abi), dump_xml, ' '.join(args) ] dragon.exec_cmd(' '.join(cmd_args))
def _xcodebuild(calldir, workspace, configuration, scheme, action, bundle_id, team_id, extra_args, short_version): # get the real version version = dragon.PARROT_BUILD_VERSION # create a fake version which is a pure release from the current version version_release = dragon.Version(str(version)) version_release.type = dragon.Version.TYPE_RELEASE version_release.custom = None version_release.custom_number = 0 version_release.type_string = None # vshort is the name of the pure release version vshort = str(version_release) # vlong is the name of the actual version vlong = str(version) # Version code is generated by the complete version vcode = common.get_version_code(version, use_dots=True) # Check if asan is used raw_asan = dragon.get_alchemy_var('USE_ADDRESS_SANITIZER') if not raw_asan or raw_asan == '0': asan = False else: asan = True cmd = ['xcodebuild'] if (dragon.VARIANT == 'ios_sim'): cmd.append('-sdk iphonesimulator') if not any('destination' in arg for arg in extra_args): cmd.append('-arch x86_64') else: cmd.append('-sdk iphoneos') if workspace.endswith('xcworkspace'): cmd.append('-workspace {}'.format(workspace)) else: cmd.append('-project {}'.format(workspace)) cmd.append('-configuration {}'.format(configuration)) cmd.append('-scheme {}'.format(scheme)) cmd.append('-allowProvisioningUpdates') if os.environ.get('MOVE_APPSDATA_IN_OUTDIR'): cmd.append('-derivedDataPath {}'.format( os.path.join(dragon.OUT_DIR, 'xcodeDerivedData'))) if asan: cmd.append('-enableAddressSanitizer YES') cmd.append(action) cmd.append('ALCHEMY_OUT={}'.format(dragon.OUT_DIR)) cmd.append('ALCHEMY_OUT_ROOT={}'.format(dragon.OUT_ROOT_DIR)) cmd.append('ALCHEMY_PRODUCT={}'.format(dragon.PRODUCT)) if bundle_id: cmd.append('APP_BUNDLE_IDENTIFIER={}'.format(bundle_id)) cmd.append('PRODUCT_BUNDLE_IDENTIFIER={}'.format(bundle_id)) if team_id: cmd.append('DEVELOPMENT_TEAM={}'.format(team_id)) cmd.append('APP_VERSION_SHORT={}'.format(vshort)) cmd.append('APP_VERSION_LONG={}'.format(vlong)) cmd.append('APP_VERSION={}'.format(vshort if short_version else vlong)) cmd.append('APP_BUILD={}'.format(vcode)) cmd.extend(extra_args) if not dragon.OPTIONS.verbose and shutil.which('xcpretty'): cmd.append('| xcpretty && exit ${PIPESTATUS[0]}') dragon.exec_cmd(' '.join(cmd), cwd=calldir)
def _rm_previous_archive(task, args): dragon.exec_cmd('rm -rf {}'.format(app._archivePath(dragon.OUT_DIR)))
def hook_post_clean(task, args): dragon.exec_cmd("rm -rf %s" % dragon.POLICE_OUT_DIR) dragon.exec_cmd("rm -rf %s" % dragon.IMAGES_DIR) dragon.exec_cmd("rm -rf %s" % os.path.join(dragon.OUT_DIR, "release-*")) dragon.exec_cmd("rm -rf %s" % os.path.join(dragon.OUT_DIR, "pinstrc")) dragon.exec_cmd("rm -f %s" % os.path.join(dragon.OUT_DIR, "build.prop")) dragon.exec_cmd("rm -f %s" % os.path.join(dragon.OUT_DIR, "manifest.xml"))
def hook_post_clean(task, args): dragon.exec_cmd("rm -rf %s" % dragon.POLICE_OUT_DIR) dragon.exec_cmd("rm -rf %s" % dragon.IMAGES_DIR) dragon.exec_cmd("rm -rf %s" % os.path.join(dragon.OUT_DIR, "release-*")) dragon.exec_cmd("rm -rf %s" % os.path.join(dragon.OUT_DIR, "pinstrc")) dragon.exec_cmd("rm -f %s" % os.path.join(dragon.OUT_DIR, "build.prop")) dragon.exec_cmd("rm -f %s" % os.path.join(dragon.OUT_DIR, "manifest.xml"))
def generate_release_archive(release_id, additional_files=None, product_config="product_config.json", warn_on_overwrite=False, previous_manifest=None): # Disable police while generating the archive os.environ["POLICE_HOOK_DISABLED"] = "1" # Init final directories and sources release_dir = os.path.join(dragon.OUT_DIR, "release-" + release_id) release_config_dir = os.path.join(release_dir, "config") # Create base list of files for release archive_content = [ { "src": os.path.join(dragon.OUT_DIR, "symbols-%s-%s.tar" % (dragon.PRODUCT, dragon.VARIANT)), "dest": os.path.join(release_dir, "symbols.tar"), }, { "src": os.path.join(dragon.OUT_DIR, "sdk-%s-%s.tar.gz" % (dragon.PRODUCT, dragon.VARIANT)), "dest": os.path.join(release_dir, "sdk.tar.gz"), }, { "src": os.path.join(dragon.OUT_DIR, "images"), "dest": os.path.join(release_dir, "images"), }, { "src": os.path.join(dragon.OUT_DIR, "staging", "etc", "build.prop"), "dest": os.path.join(release_dir, "build.prop"), }, { "src": os.path.join(dragon.OUT_DIR, "build", "linux", ".config"), "dest": os.path.join(release_config_dir, "linux.config"), "mandatory": False }, { "src": os.path.join(dragon.OUT_DIR, "global.config"), "dest": os.path.join(release_config_dir, "global.config"), }, { "src": os.path.join(dragon.WORKSPACE_DIR, "build", "dragon_build", "pinst_wrapper.py"), "dest": os.path.join(release_dir, "pinst_wrapper.py"), }, { "src": os.path.join(dragon.OUT_DIR, "police"), "dest": os.path.join(release_dir, "police"), "mandatory": False }, { "src": os.path.join(dragon.OUT_DIR, "oss-packages"), "dest": os.path.join(release_dir, "oss-packages"), "mandatory": False }, ] release_manifest = "release.xml" if not previous_manifest: previous_manifest = release_manifest cfp = get_json_config(product_config) release_section = None if cfp: release_section = cfp.get_section("release") # As this section is optional, we add it only if present if release_section: json_filepath = cfp.get_config_filepath() archive_content.append({ "src": json_filepath, "dest": os.path.join(release_dir, "product_config.json") }) # Export current variables as environment to be used with json for _envvar in ["PARROT_BUILD_PROP_GROUP", "PARROT_BUILD_PROP_PROJECT", "PARROT_BUILD_PROP_PRODUCT", "PARROT_BUILD_PROP_VARIANT", "PARROT_BUILD_PROP_REGION", "PARROT_BUILD_PROP_UID", "PARROT_BUILD_PROP_VERSION", "WORKSPACE_DIR", "OUT_DIR" ]: os.environ[_envvar] = getattr(dragon, _envvar) json_add_files = release_section.get("additional_files", []) release_manifest = release_section.get("manifest_name", release_manifest) # In case of a previous manifest with different name # between two versions (It serves only for changelog) previous_manifest = release_section.get("previous_manifest", release_manifest) for _elem in json_add_files: archive_content.append({ "src": os.path.expandvars(_elem["src"]), "dest": os.path.join( release_dir, os.path.expandvars(_elem["dest"])), "mandatory":_elem.get("mandatory", True), }) warn_on_overwrite = release_section.get("warn_on_overwrite", False) # For files provided by function calling if isinstance(additional_files, list): archive_content.extend(additional_files) for _elem in archive_content: src = _elem["src"] dest = _elem["dest"] # Optional field mandatory = _elem.get("mandatory", True) if os.path.exists(dest): if warn_on_overwrite: dragon.LOGW("%s will be overwritten.", dest) os.unlink(dest) # This function already do the parent dirname creation when needed if os.path.exists(src): relative_symlink(src, dest) else: if mandatory and not dragon.OPTIONS.dryrun: raise dragon.TaskError("%s file is absent. Cannot generate release." % src) # Normally it is also found in final/etc but in case the product does not # have this feature because of the task being overloaded. gen_manifest_xml(os.path.join(release_dir, "manifest.xml")) # Too bad if absent # Can raise error on first release, where no previous manifest is found try: dragon.exec_cmd("repo diffmanifest --full --graph --no-color " "+:{previous_xml_name} {xml_name} > {output}".format( output=os.path.join(release_dir, "changelog.txt"), xml_name=release_manifest, previous_xml_name=previous_manifest) ) except: pass # Generate md5sum file dragon.exec_dir_cmd(dirpath=release_dir, cmd="md5sum $(find -follow -type f) > md5sum.txt") # Archive the release dragon.exec_cmd("tar -C %s -hcf %s.tar ." % (release_dir, release_dir)) # Re-enable police while generating the archive del os.environ["POLICE_HOOK_DISABLED"] # Do not move in workspace if output dir is somewhere else (jenkins for example) if dragon.OUT_DIR.startswith(dragon.WORKSPACE_DIR): dragon.exec_cmd("mv -f %s.tar %s " % (release_dir, os.path.join(dragon.WORKSPACE_DIR, "%s.tar" % dragon.PARROT_BUILD_PROP_UID)))
def build_android_jni(dirpath, args): outdir = os.path.join(dragon.OUT_DIR, "jni") args = "NDK_OUT=%s" % os.path.join(outdir, "obj") args += " PRODUCT_OUT_DIR=%s" % dragon.OUT_DIR args += " PACKAGES_DIR=%s" % os.path.join(dragon.WORKSPACE_DIR, "packages") dragon.exec_cmd(cmd="${ANDROID_NDK_PATH}/ndk-build %s" % args, cwd=dirpath)
def publish_android_sdk(): # Build application cmd = "./gradlew " cmd += "bintrayUpload" dragon.exec_cmd(cmd=cmd, cwd=android_arsdk3_dir)