/* * Copyright 2013-2016 consulo.io * * 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. */ package consulo.unity3d.projectImport; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.TreeSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import com.intellij.execution.RunManager; import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.lang.javascript.JavaScriptFileType; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Version; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileVisitor; import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; import com.intellij.util.TimeoutUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.ui.UIUtil; import consulo.csharp.lang.CSharpFileType; import consulo.csharp.module.extension.CSharpLanguageVersion; import consulo.csharp.module.extension.CSharpSimpleMutableModuleExtension; import consulo.dotnet.dll.DotNetModuleFileType; import consulo.dotnet.roots.orderEntry.DotNetLibraryOrderEntryImpl; import consulo.module.extension.MutableModuleExtension; import consulo.roots.impl.ExcludedContentFolderTypeProvider; import consulo.roots.impl.ModuleRootLayerImpl; import consulo.roots.types.BinariesOrderRootType; import consulo.roots.types.DocumentationOrderRootType; import consulo.unity3d.Unity3dBundle; import consulo.unity3d.Unity3dMetaFileType; import consulo.unity3d.UnityPluginFileValidator; import consulo.unity3d.bundle.Unity3dBundleType; import consulo.unity3d.bundle.Unity3dDefineByVersion; import consulo.unity3d.editor.UnityEditorCommunication; import consulo.unity3d.editor.UnityRequestDefines; import consulo.unity3d.jsonApi.UnityOpenFilePostHandler; import consulo.unity3d.jsonApi.UnityOpenFilePostHandlerRequest; import consulo.unity3d.jsonApi.UnityPingPong; import consulo.unity3d.jsonApi.UnitySetDefines; import consulo.unity3d.module.Unity3dChildMutableModuleExtension; import consulo.unity3d.module.Unity3dModuleExtensionUtil; import consulo.unity3d.module.Unity3dRootModuleExtension; import consulo.unity3d.module.Unity3dRootMutableModuleExtension; import consulo.unity3d.nunit.module.extension.Unity3dNUnitMutableModuleExtension; import consulo.unity3d.run.Unity3dAttachApplicationType; import consulo.unity3d.run.Unity3dAttachConfiguration; import consulo.vfs.util.ArchiveVfsUtil; /** * @author VISTALL * @since 03.04.2015 */ public class Unity3dProjectUtil { public static final String ASSETS_DIRECTORY = "Assets"; public static final String[] FIRST_PASS_PATHS = new String[]{ "Assets/Standard Assets", "Assets/Pro Standard Assets", "Assets/Plugins" }; private static final String UNITY_EDITOR = "UNITY_EDITOR"; private static final String UNITY_EDITOR_ATTACH = "Attach to Unity Editor"; public static final Key<Boolean> ourInProgressFlag = Key.create("Unity3dProjectUtil#ourInProgressFlag"); @Nullable public static String loadVersionFromProject(@NotNull String path) { File file = new File(path, "ProjectSettings/ProjectVersion.txt"); if(!file.exists()) { return null; } try { List<String> lines = FileUtil.loadLines(file); String prefix = "m_EditorVersion:"; for(String line : lines) { if(line.startsWith(prefix)) { String version = line.substring(prefix.length(), line.length()); return Unity3dBundleType.filterReleaseInfo(version.trim()); } } } catch(IOException ignored) { } return null; } public static void syncProjectStep1(@NotNull final Project project, @Nullable final Sdk sdk, @Nullable UnityOpenFilePostHandlerRequest requestor, final boolean runValidator) { // set flag project.putUserData(ourInProgressFlag, Boolean.TRUE); Task.Backgroundable.queue(project, "Fetching defines from UnityEditor", indicator -> { UnityRequestDefines request = new UnityRequestDefines(); Ref<Boolean> received = Ref.create(Boolean.FALSE); UnityPingPong.Token<UnitySetDefines> token = UnityPingPong.wantReply(request.uuid, o -> { received.set(Boolean.TRUE); syncProjectStep2(project, sdk, requestor, runValidator, o); }); if(!UnityEditorCommunication.request(project, request, true)) { token.finish(null); notifyAboutUnityEditorProblem(project); return; } int i = 0; while(!received.get()) { if(i == 5) { token.finish(null); notifyAboutUnityEditorProblem(project); break; } TimeoutUtil.sleep(500L); i++; } }); } private static void notifyAboutUnityEditorProblem(Project project) { UIUtil.invokeLaterIfNeeded(() -> new Notification("unity", ApplicationNamesInfo.getInstance().getProductName(), "UnityEditor is not responding.<br>Defines is not resolved.", NotificationType .WARNING).notify(project)); } /** * this method will called from webservice thread */ private static void syncProjectStep2(@NotNull final Project project, @Nullable final Sdk sdk, @Nullable UnityOpenFilePostHandlerRequest requestor, final boolean runValidator, UnitySetDefines unitySetDefines) { Task.Backgroundable.queue(project, "Sync Project", indicator -> { AccessToken accessToken = HeavyProcessLatch.INSTANCE.processStarted("unity sync project"); try { importAfterDefines(project, sdk, runValidator, indicator, requestor, unitySetDefines); } finally { accessToken.finish(); } }); } private static void importAfterDefines(@NotNull final Project project, @Nullable final Sdk sdk, final boolean runValidator, @NotNull ProgressIndicator indicator, @Nullable UnityOpenFilePostHandlerRequest requestor, @Nullable UnitySetDefines setDefines) { try { Collection<String> defines = null; if(setDefines != null) { VirtualFile maybeProjectDir = LocalFileSystem.getInstance().findFileByPath(setDefines.projectPath); if(maybeProjectDir != null && maybeProjectDir.equals(project.getBaseDir())) { defines = new TreeSet<>(Arrays.asList(setDefines.defines)); } } Unity3dProjectUtil.importOrUpdate(project, sdk, null, indicator, defines); } finally { project.putUserData(ourInProgressFlag, null); } if(runValidator) { UnityPluginFileValidator.runValidation(project); } if(requestor != null) { UIUtil.invokeLaterIfNeeded(() -> UnityOpenFilePostHandler.openFile(project, requestor)); } } @NotNull private static List<Module> importOrUpdate(@NotNull final Project project, @Nullable Sdk unitySdk, @Nullable ModifiableModuleModel originalModel, @NotNull ProgressIndicator progressIndicator, @Nullable Collection<String> defines) { boolean fromProjectStructure = originalModel != null; final ModifiableModuleModel newModel = fromProjectStructure ? originalModel : ReadAction.compute(() -> ModuleManager.getInstance(project).getModifiableModel()); List<Module> modules = new ArrayList<>(5); ContainerUtil.addIfNotNull(modules, createRootModule(project, newModel, unitySdk, progressIndicator, defines)); progressIndicator.setFraction(0.1); MultiMap<Module, VirtualFile> sourceFilesByModule = MultiMap.create(); ContainerUtil.addIfNotNull(modules, createAssemblyCSharpModuleFirstPass(project, newModel, unitySdk, sourceFilesByModule, progressIndicator)); progressIndicator.setFraction(0.25); ContainerUtil.addIfNotNull(modules, createAssemblyUnityScriptModuleFirstPass(project, newModel, unitySdk, sourceFilesByModule, progressIndicator)); progressIndicator.setFraction(0.5); ContainerUtil.addIfNotNull(modules, createAssemblyCSharpModuleEditor(project, newModel, unitySdk, sourceFilesByModule, progressIndicator)); progressIndicator.setFraction(0.75); ContainerUtil.addIfNotNull(modules, createAssemblyCSharpModule(project, newModel, unitySdk, sourceFilesByModule, progressIndicator)); progressIndicator.setFraction(1); progressIndicator.setText(null); if(!fromProjectStructure) { new WriteAction<Object>() { @Override protected void run(Result<Object> result) throws Throwable { newModel.commit(); } }.execute(); } return modules; } private static Module createAssemblyCSharpModule(Project project, ModifiableModuleModel newModel, final Sdk unityBundle, MultiMap<Module, VirtualFile> virtualFilesByModule, ProgressIndicator progressIndicator) { String[] paths = {ASSETS_DIRECTORY}; return createAndSetupModule("Assembly-CSharp", project, newModel, paths, unityBundle, layer -> { layer.addInvalidModuleEntry("Assembly-UnityScript-firstpass"); layer.addInvalidModuleEntry("Assembly-CSharp-firstpass"); }, "unity3d-csharp-child", CSharpFileType.INSTANCE, virtualFilesByModule, progressIndicator); } private static Module createAssemblyUnityScriptModuleFirstPass(final Project project, ModifiableModuleModel newModel, final Sdk unityBundle, MultiMap<Module, VirtualFile> virtualFilesByModule, ProgressIndicator progressIndicator) { return createAndSetupModule("Assembly-UnityScript-firstpass", project, newModel, FIRST_PASS_PATHS, unityBundle, null, "unity3d-unityscript-child", JavaScriptFileType.INSTANCE, virtualFilesByModule, progressIndicator); } private static Module createAssemblyCSharpModuleFirstPass(final Project project, ModifiableModuleModel newModel, Sdk unityBundle, MultiMap<Module, VirtualFile> virtualFilesByModule, ProgressIndicator progressIndicator) { return createAndSetupModule("Assembly-CSharp-firstpass", project, newModel, FIRST_PASS_PATHS, unityBundle, null, "unity3d-csharp-child", CSharpFileType.INSTANCE, virtualFilesByModule, progressIndicator); } private static Module createAssemblyCSharpModuleEditor(final Project project, ModifiableModuleModel newModel, final Sdk unityBundle, MultiMap<Module, VirtualFile> virtualFilesByModule, ProgressIndicator progressIndicator) { final List<String> paths = new ArrayList<>(); paths.add("Assets/Standard Assets/Editor"); paths.add("Assets/Pro Standard Assets/Editor"); paths.add("Assets/Plugins/Editor"); final VirtualFile baseDir = project.getBaseDir(); final VirtualFile assetsDir = baseDir.findFileByRelativePath(ASSETS_DIRECTORY); if(assetsDir != null) { VfsUtil.visitChildrenRecursively(assetsDir, new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull VirtualFile file) { if(file.isDirectory() && "Editor".equals(file.getName())) { paths.add(VfsUtil.getRelativePath(file, baseDir, '/')); } return true; } }); } final String[] pathsAsArray = ArrayUtil.toStringArray(paths); return createAndSetupModule("Assembly-CSharp-Editor", project, newModel, pathsAsArray, unityBundle, new Consumer<ModuleRootLayerImpl>() { @Override public void consume(final ModuleRootLayerImpl layer) { layer.addInvalidModuleEntry("Assembly-UnityScript-firstpass"); layer.addInvalidModuleEntry("Assembly-CSharp-firstpass"); layer.addInvalidModuleEntry("Assembly-CSharp"); layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEditor.Graphs")); if(isVersionHigherOrEqual(unityBundle, "4.6.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEditor.UI")); } if(isVersionHigherOrEqual(unityBundle, "5.3.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "nunit.framework")); // enable nunit layer.getExtensionWithoutCheck(Unity3dNUnitMutableModuleExtension.class).setEnabled(true); } if(isVersionHigherOrEqual(unityBundle, "2017.1.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEditor.Timeline")); } } }, "unity3d-csharp-child", CSharpFileType.INSTANCE, virtualFilesByModule, progressIndicator); } @NotNull @SuppressWarnings("unchecked") private static Module createAndSetupModule(@NotNull String moduleName, @NotNull Project project, @NotNull ModifiableModuleModel modifiableModuleModels, @NotNull String[] paths, @Nullable Sdk unitySdk, @Nullable final Consumer<ModuleRootLayerImpl> setupConsumer, @NotNull String moduleExtensionId, @NotNull final FileType fileType, @NotNull final MultiMap<Module, VirtualFile> virtualFilesByModule, @NotNull final ProgressIndicator progressIndicator) { progressIndicator.setText(Unity3dBundle.message("syncing.0.module", moduleName)); for(int i = 0; i < paths.length; i++) { paths[i] = project.getBasePath() + "/" + paths[i]; } Module temp = modifiableModuleModels.findModuleByName(moduleName); final Module module; if(temp == null) { module = modifiableModuleModels.newModule(moduleName, null); } else { module = temp; } final ModifiableRootModel modifiableModel = ReadAction.compute(() -> ModuleRootManager.getInstance(module).getModifiableModel()); final List<VirtualFile> toAdd = new ArrayList<>(); final List<VirtualFile> libraryFiles = new ArrayList<>(); final double fraction = progressIndicator.getFraction(); for(int i = 0; i < paths.length; i++) { String path = paths[i]; VirtualFile fileByPath = LocalFileSystem.getInstance().findFileByPath(path); if(fileByPath != null) { VfsUtil.visitChildrenRecursively(fileByPath, new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull VirtualFile file) { if(file.getFileType() == fileType) { if(virtualFilesByModule.containsScalarValue(file)) { return true; } virtualFilesByModule.putValue(module, file); toAdd.add(file); VirtualFile parent = file.getParent(); VirtualFile metaFile = parent.findChild(file.getName() + "." + Unity3dMetaFileType.INSTANCE.getDefaultExtension()); if(metaFile != null) { toAdd.add(metaFile); } } else if(file.getFileType() == DotNetModuleFileType.INSTANCE) { libraryFiles.add(file); } return true; } }); } final double newFraction = fraction + 0.25f * (i / (float) paths.length); UIUtil.invokeLaterIfNeeded(() -> progressIndicator.setFraction(newFraction)); } modifiableModel.removeAllLayers(true); // it will return Default layer final ModuleRootLayerImpl layer = (ModuleRootLayerImpl) modifiableModel.getCurrentLayer(); for(VirtualFile virtualFile : toAdd) { layer.addContentEntry(virtualFile); } if(setupConsumer != null) { ApplicationManager.getApplication().runReadAction(() -> setupConsumer.consume(layer)); } layer.getExtensionWithoutCheck(Unity3dChildMutableModuleExtension.class).setEnabled(true); // enable correct unity lang extension MutableModuleExtension langExtension = layer.<MutableModuleExtension>getExtensionWithoutCheck(moduleExtensionId); assert langExtension != null; langExtension.setEnabled(true); if(langExtension instanceof CSharpSimpleMutableModuleExtension) { CSharpLanguageVersion languageVersion = isVersionHigherOrEqual(unitySdk, "5.5.0") ? CSharpLanguageVersion._6_0 : CSharpLanguageVersion._4_0; ((CSharpSimpleMutableModuleExtension) langExtension).setLanguageVersion(languageVersion); } layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "mscorlib")); layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEditor")); layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEngine")); if(isVersionHigherOrEqual(unitySdk, "4.6.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEngine.UI")); } if(isVersionHigherOrEqual(unitySdk, "5.1.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEngine.Networking")); layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEngine.Analytics")); } if(isVersionHigherOrEqual(unitySdk, "5.2.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEngine.Advertisements")); } if(isVersionHigherOrEqual(unitySdk, "5.3.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEngine.Purchasing")); } if(isVersionHigherOrEqual(unitySdk, "2017.1.0")) { layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "UnityEngine.Timeline")); } layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "System")); layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "System.Core")); layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "System.Xml")); layer.addOrderEntry(new DotNetLibraryOrderEntryImpl(layer, "System.Xml.Linq")); for(VirtualFile virtualFile : libraryFiles) { addAsLibrary(virtualFile, layer); } RunManager runManager = RunManager.getInstance(project); List<RunConfiguration> allConfigurationsList = runManager.getAllConfigurationsList(); Optional<RunConfiguration> first = allConfigurationsList.stream().filter(runConfiguration -> UNITY_EDITOR_ATTACH.equals(runConfiguration.getName())).findFirst(); if(!first.isPresent()) { ConfigurationFactory factory = Unity3dAttachApplicationType.getInstance().getConfigurationFactories()[0]; RunnerAndConfigurationSettings configurationSettings = runManager.createRunConfiguration(UNITY_EDITOR_ATTACH, factory); Unity3dAttachConfiguration configuration = (Unity3dAttachConfiguration) configurationSettings.getConfiguration(); configuration.setAttachTarget(Unity3dAttachConfiguration.AttachTarget.UNITY_EDITOR); configurationSettings.setSingleton(true); runManager.addConfiguration(configurationSettings, false); ReadAction.run(() -> runManager.setSelectedConfiguration(configurationSettings)); } new WriteAction<Object>() { @Override protected void run(Result<Object> result) throws Throwable { modifiableModel.commit(); } }.execute(); return module; } @NotNull private static Version parseBundleVersion(@Nullable Sdk unityBundle) { String currentVersionString = unityBundle == null ? Unity3dBundleType.UNKNOWN_VERSION : unityBundle.getVersionString(); return parseVersion(currentVersionString); } private static boolean isVersionHigherOrEqual(@Nullable Sdk unityBundle, @NotNull String requiredVersionString) { Version currentVersion = parseBundleVersion(unityBundle); Version requiredVersion = parseVersion(requiredVersionString); return currentVersion.isOrGreaterThan(requiredVersion.major, requiredVersion.minor, requiredVersion.bugfix); } @NotNull public static Version parseVersion(@Nullable String versionString) { if(versionString == null) { return new Version(0, 0, 0); } List<String> list = StringUtil.split(versionString, "."); if(list.size() >= 3) { try { return new Version(Integer.parseInt(list.get(0)), Integer.parseInt(list.get(1)), Integer.parseInt(list.get(2))); } catch(NumberFormatException ignored) { } } return new Version(0, 0, 0); } private static void addAsLibrary(VirtualFile virtualFile, ModuleRootLayerImpl layer) { if(virtualFile.getFileType() == DotNetModuleFileType.INSTANCE) { VirtualFile archiveRootForLocalFile = ArchiveVfsUtil.getArchiveRootForLocalFile(virtualFile); if(archiveRootForLocalFile != null) { Library library = layer.getModuleLibraryTable().createLibrary(); Library.ModifiableModel modifiableModel = library.getModifiableModel(); modifiableModel.addRoot(archiveRootForLocalFile, BinariesOrderRootType.getInstance()); VirtualFile docFile = virtualFile.getParent().findChild(virtualFile.getNameWithoutExtension() + ".xml"); if(docFile != null) { modifiableModel.addRoot(docFile, DocumentationOrderRootType.getInstance()); } modifiableModel.commit(); LibraryOrderEntry libraryOrderEntry = layer.findLibraryOrderEntry(library); assert libraryOrderEntry != null; libraryOrderEntry.setExported(true); } } } @NotNull private static Module createRootModule(@NotNull final Project project, @NotNull ModifiableModuleModel newModel, @Nullable Sdk unityBundle, @NotNull ProgressIndicator progressIndicator, @Nullable Collection<String> defines) { final Module rootModule; Unity3dRootModuleExtension rootModuleExtension = ReadAction.compute(() -> Unity3dModuleExtensionUtil.getRootModuleExtension(project)); if(rootModuleExtension != null) { rootModule = rootModuleExtension.getModule(); } else { rootModule = newModel.newModule(project.getName(), project.getBasePath()); } progressIndicator.setText(Unity3dBundle.message("syncing.0.module", rootModule.getName())); String projectUrl = project.getBaseDir().getUrl(); final ModifiableRootModel modifiableModel = ReadAction.compute(() -> ModuleRootManager.getInstance(rootModule).getModifiableModel()); modifiableModel.removeAllLayers(true); // return Default layer ModuleRootLayerImpl layer = (ModuleRootLayerImpl) modifiableModel.getCurrentLayer(); ContentEntry contentEntry = layer.addContentEntry(projectUrl); Unity3dRootMutableModuleExtension extension = layer.getExtensionWithoutCheck(Unity3dRootMutableModuleExtension.class); assert extension != null; extension.setEnabled(true); extension.getInheritableSdk().set(null, unityBundle); List<String> variables = extension.getVariables(); if(defines != null) { variables.addAll(defines); } // fallback else { variables.add(UNITY_EDITOR); variables.add("DEBUG"); variables.add("TRACE"); Unity3dDefineByVersion unity3dDefineByVersion = getUnity3dDefineByVersion(unityBundle); if(unity3dDefineByVersion != Unity3dDefineByVersion.UNKNOWN) { for(Unity3dDefineByVersion majorVersion : unity3dDefineByVersion.getMajorVersions()) { variables.add(majorVersion.name()); } variables.add(unity3dDefineByVersion.name()); } } // exclude temp dirs contentEntry.addFolder(projectUrl + "/" + Project.DIRECTORY_STORE_FOLDER, ExcludedContentFolderTypeProvider.getInstance()); contentEntry.addFolder(projectUrl + "/Library", ExcludedContentFolderTypeProvider.getInstance()); contentEntry.addFolder(projectUrl + "/Temp", ExcludedContentFolderTypeProvider.getInstance()); contentEntry.addFolder(projectUrl + "/test_Data", ExcludedContentFolderTypeProvider.getInstance()); new WriteAction<Object>() { @Override protected void run(Result<Object> result) throws Throwable { modifiableModel.commit(); } }.execute(); return rootModule; } @NotNull public static Unity3dDefineByVersion getUnity3dDefineByVersion(@Nullable Sdk sdk) { Version currentBundleVersion = parseBundleVersion(sdk); return Unity3dDefineByVersion.find(currentBundleVersion.toString()); } }