├── .gitmodules ├── CHANGELOG.md.meta ├── CONTRIBUTING.meta ├── LICENSE.md.meta ├── README.md.meta ├── CONTRIBUTIONS.md.meta ├── package.json.meta ├── Third Party Notices.md.meta ├── Editor.meta ├── Editor ├── com.unity.cli-config-manager.asmdef.meta ├── Options.cs.meta ├── XrConfigurator.cs.meta ├── CliConfigManager.cs.meta ├── PlatformSettings.cs.meta ├── com.unity.cli-config-manager.asmdef ├── XrConfigurator.cs ├── CliConfigManager.cs ├── PlatformSettings.cs └── Options.cs ├── package.json ├── .npmignore ├── CHANGELOG.md ├── .gitignore ├── LICENSE.md ├── CONTRIBUTING ├── CONTRIBUTIONS.md ├── Third Party Notices.md ├── .yamato ├── promotion.yml └── upm-ci.yml └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c40c27602d86e06479ad33f303af8cae 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6ece49ca4446a774aadf283ac8662948 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f91a5571b0ad1ba47a5827b15a35f7d4 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b56f7a7e4292484596f45c2a16f1137 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CONTRIBUTIONS.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a688a57ce4f57274ca0c41b40d884dbc 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 73884c072159e0a4f985b2428497b0a4 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Third Party Notices.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6eb80bd4b8697a04ab42b66b20aea256 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df99bd7f40d74334193ecb56f9a4cdcd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/com.unity.cli-config-manager.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6cacf2273fa4a1488e781d84678972d 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Options.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d8cb9471cc7f53a4bac720225cbdb34c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/XrConfigurator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 15e73d1b900ad6c4680361ac7d5f9c4e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CliConfigManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7ffc29c2e7e96342b962d230d5980f4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PlatformSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdf30ba0ce15b914984e73f7ce4e9bde 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.unity.cli-config-manager", 3 | "displayName": "Unity CLI Configuration Manager", 4 | "version": "0.1.0-preview", 5 | "unity": "2018.3", 6 | "description": "Provides a command line parser and options to set build, player, and other Unity settings when running Unity from the command line." 7 | } 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | artifacts/** 2 | build/** 3 | .build_script/** 4 | node_modules/** 5 | Documentation/ApiDocs/** 6 | Documentation~/ApiDocs/** 7 | .DS_Store 8 | .npmrc 9 | .npmignore 10 | .gitignore 11 | CONTRIBUTING.md 12 | CONTRIBUTING.md.meta 13 | QAReport.md 14 | QAReport.md.meta 15 | .gitlab-ci.yml 16 | build.sh 17 | build.sh.meta 18 | build.bat 19 | build.bat.meta 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this package will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.1.0] - 2019-06-18 8 | 9 | ### This is the first release of *Unity Package \com.unity.cli-config-manager*. 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.Documentation 2 | !Documentation~ 3 | **/.vs 4 | **/.vscode/ 5 | **/Library/ 6 | **/Logs/ 7 | **/obj/ 8 | **/Temp/ 9 | *.app 10 | *.csproj 11 | *.idea 12 | *.rsp 13 | *.sln 14 | *.suo 15 | *.swp 16 | *.user 17 | *.userprefs 18 | *.VC.* 19 | *~ 20 | .build_script/** 21 | .DS_Store 22 | .idea/ 23 | .npmrc 24 | .vs/ 25 | artifacts/** 26 | build.bat.meta 27 | build.sh.meta 28 | build/* 29 | build/** 30 | node_modules/** 31 | npm-debug.log 32 | 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2018 Unity Technologies ApS 2 | 3 | Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). 4 | 5 | Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | # PR review process 2 | 3 | - Any PR must have an entry in the corresponding changelog in a separate commit (CHANGELOG.MD file) 4 | - Changelog follow these guidelines: https://github.com/Unity-Technologies/PostProcessing/blob/v2/CHANGELOG.md 5 | - Each release branch (2018.1, 2018.2...) have a unique Changelog file 6 | - when backporting, don't backport the changelog commit but update the branch changelog manually 7 | - (optional) add reviewver from doc team 8 | - Any more complex description of a change with future need to go in a release note file -------------------------------------------------------------------------------- /CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # Contributions 2 | 3 | ## If you are interested in contributing, here are some ground rules: 4 | * Talk to us before doing the work -- we love contributions, but we might already be working on the same thing, or we might have different opinions on how it should be implemented. 5 | 6 | ## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) 7 | By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions. 8 | 9 | ## Once you have a change ready following these ground rules. Simply make a pull request in Github -------------------------------------------------------------------------------- /Third Party Notices.md: -------------------------------------------------------------------------------- 1 | This package contains third-party software components governed by the license(s) indicated below: 2 | 3 | Component Name: Chart.js 4 | 5 | License Type: MIT 6 | 7 | The MIT License (MIT) 8 | 9 | Copyright (c) 2018 Chart.js Contributors 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Editor/com.unity.cli-config-manager.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.unity.cli-config-manager", 3 | "rootNamespace": "", 4 | "references": [ 5 | "com.unity.xr.tests.runtimesettings", 6 | "Unity.XR.Oculus", 7 | "Unity.XR.Management", 8 | "Unity.XR.Management.Editor", 9 | "com.unity.xr.oculusperformanceutility", 10 | "Unity.XR.MockHMD", 11 | "Unity.RenderPipelines.Universal.Runtime", 12 | "Unity.XR.WindowsMixedReality" 13 | ], 14 | "includePlatforms": [ 15 | "Editor" 16 | ], 17 | "excludePlatforms": [], 18 | "allowUnsafeCode": false, 19 | "overrideReferences": false, 20 | "precompiledReferences": [], 21 | "autoReferenced": true, 22 | "defineConstraints": [], 23 | "versionDefines": [ 24 | { 25 | "name": "com.unity.xr.oculus", 26 | "expression": "0.0.1", 27 | "define": "XR_SDK;OCULUS_SDK" 28 | }, 29 | { 30 | "name": "com.unity.xr.mock-hmd", 31 | "expression": "0.0.1", 32 | "define": "XR_SDK;MOCKHMD_SDK" 33 | }, 34 | { 35 | "name": "com.unity.xr.windowsmr", 36 | "expression": "0.0.1", 37 | "define": "XR_SDK;WMR_SDK" 38 | } 39 | ], 40 | "noEngineReferences": false 41 | } -------------------------------------------------------------------------------- /.yamato/promotion.yml: -------------------------------------------------------------------------------- 1 | test_editors: 2 | - version: 2019.1 3 | test_platforms: 4 | - name: win 5 | type: Unity::VM 6 | image: package-ci/win10:stable 7 | flavor: b1.large 8 | --- 9 | {% for editor in test_editors %} 10 | {% for platform in test_platforms %} 11 | promotion_test_{{ platform.name }}_{{ editor.version }}: 12 | name : Promotion Test {{ editor.version }} on {{ platform.name }} 13 | agent: 14 | type: {{ platform.type }} 15 | image: {{ platform.image }} 16 | flavor: {{ platform.flavor}} 17 | variables: 18 | UPMCI_PROMOTION: 1 19 | commands: 20 | - npm install upm-ci-utils@latest -g --registry https://api.bintray.com/npm/unity/unity-npm 21 | - upm-ci package test --unity-version {{ editor.version }} 22 | artifacts: 23 | logs: 24 | paths: 25 | - "upm-ci~/test-results/**/*" 26 | dependencies: 27 | - .yamato/upm-ci.yml#pack 28 | {% endfor %} 29 | {% endfor %} 30 | 31 | promotion_test_trigger: 32 | name: Promotion Tests Trigger 33 | agent: 34 | type: Unity::VM 35 | image: package-ci/win10:stable 36 | flavor: b1.large 37 | artifacts: 38 | logs: 39 | paths: 40 | - "upm-ci~/test-results/**/*" 41 | packages: 42 | paths: 43 | - "upm-ci~/packages/**/*" 44 | dependencies: 45 | {% for editor in test_editors %} 46 | {% for platform in test_platforms %} 47 | - .yamato/promotion.yml#promotion_test_{{platform.name}}_{{editor.version}} 48 | {% endfor %} 49 | {% endfor %} 50 | 51 | promote: 52 | name: Promote to Production 53 | agent: 54 | type: Unity::VM 55 | image: package-ci/win10:stable 56 | flavor: b1.large 57 | variables: 58 | UPMCI_PROMOTION: 1 59 | commands: 60 | - npm install upm-ci-utils@latest -g --registry https://api.bintray.com/npm/unity/unity-npm 61 | - upm-ci package promote 62 | triggers: 63 | tags: 64 | only: 65 | - /^(r|R)elease-\d+\.\d+\.\d+(-preview(\.\d+)?)?$/ 66 | artifacts: 67 | artifacts: 68 | paths: 69 | - "upm-ci~/packages/*.tgz" 70 | dependencies: 71 | - .yamato/upm-ci.yml#pack 72 | {% for editor in test_editors %} 73 | {% for platform in test_platforms %} 74 | - .yamato/promotion.yml#promotion_test_{{ platform.name }}_{{ editor.version }} 75 | {% endfor %} 76 | {% endfor %} 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # com.unity.cli-config-manager 2 | Provides a command line parser and options to set editor, build, player, and other Unity settings when running Unity from the command line 3 | 4 | # Methods 5 | ## public void ConfigureFromCmdlineArgs() 6 | 7 | Parses command line args using args returned from `Environment.GetCommandLineArgs()` and matches up values based on Options below. Then sets the values to the appropriate build/player settings in the editor. Meant to be run prior to building the player or running a test. 8 | 9 | 10 | # Options 11 | Options Recognized from `Environment.GetCommandLineArgs()` 12 | 13 | `-scriptingbackend=` 14 | 15 | IL2CPP is default. Values: IL2CPP, Mono 16 | 17 | `-simulationmode=` 18 | 19 | Enable Simulation modes for Windows MR in Editor. Values: HoloLens, WindowsMR, Remoting 20 | 21 | `-enabledxrtarget=` 22 | 23 | XR target to enable in player settings. Values: Oculus, OpenVR, cardboard, daydream, MockHMD, OculusXRSDK 24 | 25 | `-playergraphicsapi=` 26 | 27 | Graphics API based on GraphicsDeviceType. Values: Direct3D11, OpenGLES2, OpenGLES3, PlayStation4, XboxOne, Metal, OpenGLCore, Direct3D12, Switch, XboxOneD3D12 28 | 29 | `-colorspace=` 30 | 31 | Linear or Gamma color space. Default is Gamma. Values: Linear, Gamma 32 | 33 | `-stereorenderingpath=` 34 | 35 | Stereo rendering path to enable. 36 | Legacy VR Values: MultiPass, SinglePass, Instancing 37 | Oculus XR SDK Desktop Values: MultiPass, SinglePassInstanced 38 | Oculus XR SDK Android Values: MultiPass, MultiView 39 | 40 | `-mtRendering` 41 | 42 | Enable or disable multithreaded rendering. Enabled is default. 43 | Append '-' to disable: -mtRendering- 44 | 45 | `-graphicsJobs` 46 | 47 | Enable graphics jobs rendering. Disabled is default. 48 | Append '-' to disable: -graphicsJobs- 49 | 50 | `-minimumandroidsdkversion=` 51 | 52 | Minimum Android SDK Version (Integer) to use. 53 | 54 | `-targetandroidsdkversion=` 55 | 56 | Target Android SDK Version (Integer) to use. 57 | 58 | `-appleDeveloperTeamID=` 59 | 60 | Apple Developer Team ID. Use for deployment and running tests on iOS device. 61 | 62 | `-iOSProvisioningProfileID=` 63 | 64 | iOS Provisioning Profile ID. Use for deployment and running tests on iOS device. 65 | -------------------------------------------------------------------------------- /.yamato/upm-ci.yml: -------------------------------------------------------------------------------- 1 | test_editors: 2 | - version: 2019.1 3 | - version: trunk 4 | test_platforms: 5 | - name: win 6 | type: Unity::VM 7 | image: package-ci/win10:stable 8 | flavor: b1.large 9 | - name: mac 10 | type: Unity::VM::osx 11 | image: buildfarm/mac:stable 12 | flavor: m1.mac 13 | --- 14 | pack: 15 | name: Pack 16 | agent: 17 | type: Unity::VM 18 | image: package-ci/win10:stable 19 | flavor: b1.large 20 | commands: 21 | - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm 22 | - upm-ci package pack 23 | artifacts: 24 | packages: 25 | paths: 26 | - "upm-ci~/**/*" 27 | 28 | {% for editor in test_editors %} 29 | {% for platform in test_platforms %} 30 | test_{{ platform.name }}_{{ editor.version }}: 31 | name : Test {{ editor.version }} on {{ platform.name }} 32 | agent: 33 | type: {{ platform.type }} 34 | image: {{ platform.image }} 35 | flavor: {{ platform.flavor}} 36 | commands: 37 | - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm 38 | - upm-ci package test --unity-version {{ editor.version }} 39 | artifacts: 40 | logs: 41 | paths: 42 | - "upm-ci~/test-results/**/*" 43 | dependencies: 44 | - .yamato/upm-ci.yml#pack 45 | {% endfor %} 46 | {% endfor %} 47 | 48 | test_trigger: 49 | name: Tests Trigger 50 | agent: 51 | type: Unity::VM 52 | image: package-ci/win10:stable 53 | flavor: b1.large 54 | commands: 55 | - dir 56 | triggers: 57 | branches: 58 | only: 59 | - "/.*/" 60 | artifacts: 61 | logs: 62 | paths: 63 | - "upm-ci~/test-results/**/*" 64 | packages: 65 | paths: 66 | - "upm-ci~/packages/**/*" 67 | dependencies: 68 | - .yamato/upm-ci.yml#pack 69 | {% for editor in test_editors %} 70 | {% for platform in test_platforms %} 71 | - .yamato/upm-ci.yml#test_{{platform.name}}_{{editor.version}} 72 | {% endfor %} 73 | {% endfor %} 74 | 75 | publish: 76 | name: Publish to Internal Registry 77 | agent: 78 | type: Unity::VM 79 | image: package-ci/win10:stable 80 | flavor: b1.large 81 | commands: 82 | - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm 83 | - upm-ci package publish 84 | triggers: 85 | tags: 86 | only: 87 | - /^(r|R)(c|C)-\d+\.\d+\.\d+(-preview(\.\d+)?)?$/ 88 | artifacts: 89 | artifacts: 90 | paths: 91 | - "upm-ci~/packages/*.tgz" 92 | dependencies: 93 | - .yamato/upm-ci.yml#pack 94 | {% for editor in test_editors %} 95 | {% for platform in test_platforms %} 96 | - .yamato/upm-ci.yml#test_{{ platform.name }}_{{ editor.version }} 97 | {% endfor %} 98 | {% endfor %} 99 | -------------------------------------------------------------------------------- /Editor/XrConfigurator.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.IO; 4 | #if UNITY_EDITOR 5 | #if XR_SDK 6 | using UnityEditor.XR.Management; 7 | #endif 8 | using UnityEditor; 9 | #endif 10 | using UnityEngine; 11 | #if XR_SDK 12 | using UnityEngine.XR.Management; 13 | #endif 14 | #if OCULUS_SDK 15 | using Unity.XR.Oculus; 16 | #endif 17 | #if MOCKHMD_SDK 18 | using Unity.XR.MockHMD; 19 | #endif 20 | #if WMR_SDK 21 | using UnityEngine.XR.WindowsMR; 22 | #endif 23 | 24 | namespace com.unity.cliconfigmanager 25 | { 26 | public class XrConfigurator 27 | { 28 | #if XR_SDK 29 | private readonly string xrsdkTestXrSettingsPath = "Assets/XR/Settings/Test Settings.asset"; 30 | #endif 31 | 32 | private readonly PlatformSettings platformSettings; 33 | 34 | public XrConfigurator(PlatformSettings platformSettings) 35 | { 36 | this.platformSettings = platformSettings; 37 | } 38 | #if UNITY_EDITOR 39 | public void ConfigureXr() 40 | { 41 | #if XR_SDK 42 | ConfigureXrSdk(); 43 | #else 44 | ConfigureLegacyVr(); 45 | #endif 46 | } 47 | 48 | #if XR_SDK 49 | private void ConfigureXrSdk() 50 | { 51 | #if !UNITY_2020_OR_NEWER 52 | PlayerSettings.virtualRealitySupported = false; 53 | #endif 54 | 55 | // Create our own test version of xr general settings. 56 | var xrGeneralSettings = ScriptableObject.CreateInstance(); 57 | var managerSettings = ScriptableObject.CreateInstance(); 58 | var buildTargetSettings = ScriptableObject.CreateInstance(); 59 | 60 | EnsureArgumentsNotNull(xrGeneralSettings, buildTargetSettings, managerSettings); 61 | 62 | 63 | xrGeneralSettings.Manager = managerSettings; 64 | 65 | #if WMR_SDK 66 | if (platformSettings.XrTarget == "WindowsMRXRSDK") 67 | { 68 | SetupLoader(xrGeneralSettings, buildTargetSettings, managerSettings); 69 | 70 | var wmrSettings = ScriptableObject.CreateInstance(); 71 | 72 | if (wmrSettings == null) 73 | { 74 | throw new ArgumentNullException( 75 | $"Tried to instantiate an instance of {typeof(WindowsMRLoader).Name} but it was null."); 76 | } 77 | 78 | AssetDatabase.AddObjectToAsset(wmrSettings, xrsdkTestXrSettingsPath); 79 | 80 | AssetDatabase.SaveAssets(); 81 | 82 | EditorBuildSettings.AddConfigObject("Unity.XR.WindowsMR.Settings", wmrSettings, true); 83 | } 84 | #endif 85 | 86 | #if OCULUS_SDK 87 | if (platformSettings.XrTarget == "OculusXRSDK") 88 | { 89 | SetupLoader(xrGeneralSettings, buildTargetSettings, managerSettings); 90 | 91 | var oculusSettings = ScriptableObject.CreateInstance(); 92 | 93 | if (oculusSettings == null) 94 | { 95 | throw new ArgumentNullException( 96 | $"Tried to instantiate an instance of {typeof(OculusSettings).Name} but it is null."); 97 | } 98 | 99 | if (platformSettings.BuildTarget == BuildTarget.Android) 100 | { 101 | try 102 | { 103 | oculusSettings.m_StereoRenderingModeAndroid = (OculusSettings.StereoRenderingModeAndroid)Enum.Parse( 104 | typeof(OculusSettings.StereoRenderingModeAndroid), platformSettings.StereoRenderingPath); 105 | } 106 | catch (Exception e) 107 | { 108 | throw new ArgumentException("Failed to parse stereo rendering mode for Android Oculus XR SDK", e); 109 | } 110 | } 111 | else 112 | { 113 | try 114 | { 115 | oculusSettings.m_StereoRenderingModeDesktop = (OculusSettings.StereoRenderingModeDesktop)Enum.Parse( 116 | typeof(OculusSettings.StereoRenderingModeDesktop), platformSettings.StereoRenderingPath); 117 | } 118 | catch (Exception e) 119 | { 120 | 121 | throw new ArgumentException("Failed to parse stereo rendering mode for Desktop Oculus XR SDK.", e); 122 | } 123 | } 124 | 125 | AssetDatabase.AddObjectToAsset(oculusSettings, xrsdkTestXrSettingsPath); 126 | 127 | AssetDatabase.SaveAssets(); 128 | 129 | EditorBuildSettings.AddConfigObject("Unity.XR.Oculus.Settings", oculusSettings, true); 130 | } 131 | #endif 132 | 133 | #if MOCKHMD_SDK 134 | if (platformSettings.XrTarget == "MockHMDXRSDK") 135 | { 136 | SetupLoader(xrGeneralSettings, buildTargetSettings, managerSettings); 137 | 138 | var mockSettings = ScriptableObject.CreateInstance(); 139 | 140 | if (mockSettings == null) 141 | { 142 | throw new ArgumentNullException("Failed to create Mock HMD settings asset."); 143 | } 144 | 145 | try 146 | { 147 | mockSettings.renderMode = (MockHMDBuildSettings.RenderMode)Enum.Parse( 148 | typeof(MockHMDBuildSettings.RenderMode), platformSettings.StereoRenderingPath); 149 | } 150 | catch (Exception e) 151 | { 152 | throw new ArgumentException("Failed to parse stereo rendering mode for Mock HMD XR SDK", e); 153 | } 154 | 155 | AssetDatabase.AddObjectToAsset(mockSettings, xrsdkTestXrSettingsPath); 156 | 157 | AssetDatabase.SaveAssets(); 158 | 159 | EditorBuildSettings.AddConfigObject("Unity.XR.MockHMD.Settings", mockSettings, true); 160 | } 161 | #endif 162 | 163 | EditorBuildSettings.AddConfigObject(XRGeneralSettings.k_SettingsKey, buildTargetSettings, true); 164 | } 165 | 166 | private void SetupLoader(XRGeneralSettings xrGeneralSettings, 167 | XRGeneralSettingsPerBuildTarget buildTargetSettings, 168 | XRManagerSettings managerSettings) where T : XRLoader 169 | { 170 | var loader = ScriptableObject.CreateInstance(); 171 | 172 | if (loader == null) 173 | { 174 | throw new ArgumentNullException( 175 | $"Tried to instantiate an instance of {typeof(T).Name}, but it is null."); 176 | } 177 | 178 | loader.name = loader.GetType().Name; 179 | 180 | xrGeneralSettings.Manager.loaders.Add(loader); 181 | 182 | buildTargetSettings.SetSettingsForBuildTarget(EditorUserBuildSettings.selectedBuildTargetGroup, 183 | xrGeneralSettings); 184 | 185 | EnsureXrGeneralSettingsPathExists(xrsdkTestXrSettingsPath); 186 | 187 | AssetDatabase.CreateAsset(buildTargetSettings, xrsdkTestXrSettingsPath); 188 | 189 | AssetDatabase.AddObjectToAsset(xrGeneralSettings, xrsdkTestXrSettingsPath); 190 | AssetDatabase.AddObjectToAsset(managerSettings, xrsdkTestXrSettingsPath); 191 | AssetDatabase.AddObjectToAsset(loader, xrsdkTestXrSettingsPath); 192 | } 193 | 194 | private void EnsureArgumentsNotNull(XRGeneralSettings xrGeneralSettings, 195 | XRGeneralSettingsPerBuildTarget buildTargetSettings, XRManagerSettings managerSettings) 196 | { 197 | EnsureArgumentNotNull(xrGeneralSettings); 198 | EnsureArgumentNotNull(buildTargetSettings); 199 | EnsureArgumentNotNull(managerSettings); 200 | } 201 | 202 | private void EnsureXrGeneralSettingsPathExists(string testXrGeneralSettingsPath) 203 | { 204 | var settingsPath = Path.GetDirectoryName(testXrGeneralSettingsPath); 205 | if (!Directory.Exists(settingsPath)) 206 | { 207 | Directory.CreateDirectory(testXrGeneralSettingsPath); 208 | } 209 | } 210 | 211 | private static void EnsureArgumentNotNull(object arg) 212 | { 213 | if (arg == null) 214 | { 215 | throw new ArgumentNullException(nameof(arg)); 216 | } 217 | } 218 | #else 219 | private void ConfigureLegacyVr() 220 | { 221 | PlayerSettings.virtualRealitySupported = true; 222 | 223 | UnityEditorInternal.VR.VREditor.SetVREnabledDevicesOnTargetGroup 224 | (platformSettings.BuildTargetGroup, new string[] { platformSettings.XrTarget }); 225 | 226 | try 227 | { 228 | PlayerSettings.stereoRenderingPath = (StereoRenderingPath)Enum.Parse( 229 | typeof(StereoRenderingPath), platformSettings.StereoRenderingPath); 230 | } 231 | catch (System.Exception e) 232 | { 233 | throw new ArgumentException( 234 | "Error trying to cast stereo rendering mode cmdline parameter to UnityEditor.StereoRenderingPath type.", e); 235 | } 236 | } 237 | #endif 238 | #endif 239 | } 240 | 241 | } -------------------------------------------------------------------------------- /Editor/CliConfigManager.cs: -------------------------------------------------------------------------------- 1 | using NDesk.Options; 2 | using System; 3 | #if UNITY_EDITOR 4 | using UnityEditor; 5 | #endif 6 | using System.Text.RegularExpressions; 7 | using UnityEngine; 8 | using UnityEngine.Rendering; 9 | 10 | namespace com.unity.cliconfigmanager 11 | { 12 | public class CliConfigManager 13 | { 14 | private readonly Regex customArgRegex = new Regex("-([^=]*)=", RegexOptions.Compiled); 15 | private readonly PlatformSettings platformSettings = new PlatformSettings(); 16 | 17 | public void ConfigureFromCmdlineArgs() 18 | { 19 | #if UNITY_EDITOR 20 | ParseCommandLineArgs(); 21 | ConfigureSettings(); 22 | #endif 23 | } 24 | 25 | #if UNITY_EDITOR 26 | private void ParseCommandLineArgs() 27 | { 28 | var args = Environment.GetCommandLineArgs(); 29 | EnsureOptionsLowerCased(args); 30 | var optionSet = DefineOptionSet(); 31 | var unParsedArgs = optionSet.Parse(args); 32 | platformSettings.SerializeToAsset(); 33 | } 34 | 35 | private void EnsureOptionsLowerCased(string[] args) 36 | { 37 | for (var i = 0; i < args.Length; i++) 38 | { 39 | if (customArgRegex.IsMatch(args[i])) 40 | { 41 | args[i] = customArgRegex.Replace(args[i], customArgRegex.Matches(args[i])[0].ToString().ToLower()); 42 | } 43 | } 44 | } 45 | 46 | private void ConfigureSettings() 47 | { 48 | // Setup all-inclusive player settings 49 | ConfigureCrossplatformSettings(); 50 | 51 | // If Android, setup Android player settings 52 | if (platformSettings.BuildTarget == BuildTarget.Android) 53 | { 54 | ConfigureAndroidSettings(); 55 | } 56 | 57 | // If iOS, setup iOS player settings 58 | if (platformSettings.BuildTarget == BuildTarget.iOS) 59 | { 60 | ConfigureIosSettings(); 61 | } 62 | 63 | if (!string.IsNullOrEmpty(platformSettings.XrTarget)) 64 | { 65 | var xrConfigurator = new XrConfigurator(platformSettings); 66 | xrConfigurator.ConfigureXr(); 67 | } 68 | } 69 | 70 | private void ConfigureIosSettings() 71 | { 72 | PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.iOS, string.Format("com.unity3d.{0}", PlayerSettings.productName)); 73 | PlayerSettings.iOS.appleDeveloperTeamID = platformSettings.AppleDeveloperTeamId; 74 | PlayerSettings.iOS.appleEnableAutomaticSigning = false; 75 | PlayerSettings.iOS.iOSManualProvisioningProfileID = platformSettings.IOsProvisioningProfileId; 76 | PlayerSettings.iOS.iOSManualProvisioningProfileType = ProvisioningProfileType.Development; 77 | } 78 | 79 | private void ConfigureCrossplatformSettings() 80 | { 81 | #if !UNITY_2020_OR_NEWER 82 | // legacy built in vr settings which are deprecated in 2020.1 and over 83 | PlayerSettings.virtualRealitySupported = false; 84 | #endif 85 | if (platformSettings.PlayerGraphicsApi != GraphicsDeviceType.Null) 86 | { 87 | PlayerSettings.SetUseDefaultGraphicsAPIs(platformSettings.BuildTarget, false); 88 | PlayerSettings.SetGraphicsAPIs(platformSettings.BuildTarget, new[] {platformSettings.PlayerGraphicsApi}); 89 | } 90 | 91 | PlayerSettings.colorSpace = platformSettings.ColorSpace; 92 | 93 | PlayerSettings.SetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup, 94 | platformSettings.ScriptingImplementation); 95 | } 96 | 97 | private void ConfigureAndroidSettings() 98 | { 99 | EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; 100 | PlayerSettings.Android.minSdkVersion = platformSettings.MinimumAndroidSdkVersion; 101 | PlayerSettings.Android.targetSdkVersion = platformSettings.TargetAndroidSdkVersion; 102 | 103 | // If the user has specified AndroidArchitecture.ARMv7, but not specified ScriptingImplementation.Mono2x, or has incorrectly specified ScriptingImplementation.IL2CPP (not supported 104 | // with mono), then set to AndroidArchitecture.ARMv7 so we're in a compatible configuration state. 105 | if (platformSettings.AndroidTargetArchitecture == AndroidArchitecture.ARMv7 && 106 | platformSettings.ScriptingImplementation != ScriptingImplementation.Mono2x) 107 | { 108 | platformSettings.ScriptingImplementation = ScriptingImplementation.Mono2x; 109 | PlayerSettings.SetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup, 110 | platformSettings.ScriptingImplementation); 111 | } 112 | 113 | // If the user has specified mono scripting backend, but not specified AndroidArchitecture.ARMv7, or has incorrectly specified AndroidArchitecture.ARM64 (not supported 114 | // with mono), then set to AndroidArchitecture.ARMv7 so we're in a compatible configuration state. 115 | if (platformSettings.ScriptingImplementation == ScriptingImplementation.Mono2x && 116 | platformSettings.AndroidTargetArchitecture != AndroidArchitecture.ARMv7) 117 | { 118 | platformSettings.AndroidTargetArchitecture = AndroidArchitecture.ARMv7; 119 | } 120 | PlayerSettings.Android.targetArchitectures = platformSettings.AndroidTargetArchitecture; 121 | } 122 | 123 | private OptionSet DefineOptionSet() 124 | { 125 | var optionsSet = new OptionSet(); 126 | optionsSet.Add("scriptingbackend=", 127 | "Scripting backend to use. IL2CPP is default. Values: IL2CPP, Mono", ParseScriptingBackend); 128 | optionsSet.Add("simulationmode=", 129 | "Enable Simulation modes for Windows MR in Editor. Values: \r\n\"HoloLens\"\r\n\"WindowsMR\"\r\n\"Remoting\"", 130 | simMode => platformSettings.SimulationMode = simMode); 131 | optionsSet.Add("enabledxrtarget|enabledxrtargets=", 132 | "XR target to enable in player settings. Values: " + 133 | "\r\n\"Oculus\"\r\n\"OpenVR\"\r\n\"cardboard\"\r\n\"daydream\"\r\n\"MockHMD\"\r\n\"OculusXRSDK\"\r\n\"MockHMDXRSDK\"\r\n\"MagicLeapXRSDK\"\r\n\"WindowsMRXRSDK\"", 134 | xrTarget => platformSettings.XrTarget = xrTarget); 135 | optionsSet.Add("playergraphicsapi=", "Graphics API based on GraphicsDeviceType.", 136 | graphicsDeviceType => 137 | platformSettings.PlayerGraphicsApi = TryParse(graphicsDeviceType)); 138 | optionsSet.Add("colorspace=", "Linear or Gamma color space.", 139 | colorSpace => platformSettings.ColorSpace = TryParse(colorSpace)); 140 | optionsSet.Add("stereorenderingpath=", "Stereo rendering path to enable. SinglePass is default", srm => platformSettings.StereoRenderingPath = srm); 141 | optionsSet.Add("mtRendering", 142 | "Enable or disable multithreaded rendering. Enabled is default. Use option to enable, or use option and append '-' to disable.", 143 | option => platformSettings.MtRendering = option != null); 144 | optionsSet.Add("graphicsJobs", 145 | "Enable graphics jobs rendering. Disabled is default. Use option to enable, or use option and append '-' to disable.", 146 | option => platformSettings.GraphicsJobs = option != null); 147 | optionsSet.Add("minimumandroidsdkversion=", "Minimum Android SDK Version to use.", 148 | minAndroidSdkVersion => platformSettings.MinimumAndroidSdkVersion = 149 | TryParse(minAndroidSdkVersion)); 150 | optionsSet.Add("targetandroidsdkversion=", "Target Android SDK Version to use.", 151 | trgtAndroidSdkVersion => platformSettings.TargetAndroidSdkVersion = 152 | TryParse(trgtAndroidSdkVersion)); 153 | optionsSet.Add("appledeveloperteamid=", 154 | "Apple Developer Team ID. Use for deployment and running tests on iOS device.", 155 | appleTeamId => platformSettings.AppleDeveloperTeamId = appleTeamId); 156 | optionsSet.Add("iosprovisioningprofileid=", 157 | "iOS Provisioning Profile ID. Use for deployment and running tests on iOS device.", 158 | id => platformSettings.IOsProvisioningProfileId = id); 159 | optionsSet.Add("xrsdkrev=", 160 | "revision id of the xrsdk being used.", 161 | id => platformSettings.XrsdkRevision = id); 162 | optionsSet.Add("xrsdkrevdate=", 163 | "revision date of the xrsdk being used.", 164 | revDate => platformSettings.XrsdkRevisionDate = revDate); 165 | optionsSet.Add("xrsdkbranch=", 166 | "branch of the xrsdk being used.", 167 | xrsdkbranch => platformSettings.XrsdkBranch = xrsdkbranch); 168 | optionsSet.Add("deviceruntimeversion=", 169 | "runtime version of the device we're running on.", 170 | deviceruntime => platformSettings.DeviceRuntimeVersion = string.Format("deviceruntimeversion|{0}",deviceruntime)); 171 | optionsSet.Add("ffrlevel=", 172 | "ffr level we're running at", 173 | ffrlevel => platformSettings.FfrLevel = string.Format("ffrlevel|{0}", ffrlevel)); 174 | optionsSet.Add("testsrev=", 175 | "revision id of the tests being used.", 176 | id => platformSettings.TestsRevision = string.Format("testsrev|{0}", id)); 177 | optionsSet.Add("testsrevdate=", 178 | "revision date of the tests being used.", 179 | revDate => platformSettings.TestsRevisionDate = string.Format("testsrevdate|{0}", revDate)); 180 | optionsSet.Add("testsbranch=", 181 | "branch of the tests repo being used.", 182 | testsbranch => platformSettings.TestsBranch = string.Format("testsbranch|{0}", testsbranch)); 183 | optionsSet.Add("androidtargetarchitecture=", 184 | "Android Target Architecture to use.", 185 | androidtargetarchitecture => platformSettings.AndroidTargetArchitecture = TryParse(androidtargetarchitecture)); 186 | return optionsSet; 187 | } 188 | 189 | public static T TryParse(string stringToParse) 190 | { 191 | T thisType; 192 | try 193 | { 194 | thisType = (T) Enum.Parse(typeof(T), stringToParse); 195 | } 196 | catch (Exception e) 197 | { 198 | throw new ArgumentException(($"Couldn't cast {stringToParse} to {typeof(T)}"), e); 199 | } 200 | 201 | return thisType; 202 | } 203 | 204 | private void ParseScriptingBackend(string scriptingBackend) 205 | { 206 | var sb = scriptingBackend.ToLower(); 207 | if (sb.Equals("mono")) 208 | { 209 | platformSettings.ScriptingImplementation = ScriptingImplementation.Mono2x; 210 | } 211 | } 212 | #endif 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Editor/PlatformSettings.cs: -------------------------------------------------------------------------------- 1 | #if OCULUS_SDK 2 | using Unity.XR.Oculus; 3 | #endif 4 | using System; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text.RegularExpressions; 9 | using com.unity.xr.test.runtimesettings; 10 | #if UNITY_EDITOR 11 | using UnityEditor; 12 | using UnityEditor.PackageManager; 13 | #if URP 14 | using UnityEngine.Rendering.Universal; 15 | #endif 16 | #endif 17 | using UnityEngine; 18 | using UnityEngine.Rendering; 19 | using PackageInfo = UnityEditor.PackageManager.PackageInfo; 20 | #if ENABLE_VR 21 | using UnityEngine.XR; 22 | #endif 23 | 24 | 25 | namespace com.unity.cliconfigmanager 26 | { 27 | public class PlatformSettings 28 | { 29 | #if UNITY_EDITOR 30 | public BuildTargetGroup BuildTargetGroup => EditorUserBuildSettings.selectedBuildTargetGroup; 31 | public BuildTarget BuildTarget => EditorUserBuildSettings.activeBuildTarget; 32 | 33 | public string XrTarget; 34 | public GraphicsDeviceType PlayerGraphicsApi; 35 | public string StereoRenderingPath; 36 | public bool MtRendering = true; 37 | public bool GraphicsJobs; 38 | public AndroidSdkVersions MinimumAndroidSdkVersion = AndroidSdkVersions.AndroidApiLevel24; 39 | public AndroidSdkVersions TargetAndroidSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto; 40 | public ScriptingImplementation ScriptingImplementation = ScriptingImplementation.IL2CPP; 41 | public string AppleDeveloperTeamId; 42 | public string IOsProvisioningProfileId; 43 | public ColorSpace ColorSpace = ColorSpace.Gamma; 44 | public string XrsdkRevision; 45 | public string XrsdkRevisionDate; 46 | public string XrsdkBranch; 47 | public string TestsRevision; 48 | public string TestsRevisionDate; 49 | public string TestsBranch; 50 | public string DeviceRuntimeVersion; 51 | public string SimulationMode; 52 | public string Username; 53 | public string RenderPipeline; 54 | public string FfrLevel; 55 | public AndroidArchitecture AndroidTargetArchitecture = AndroidArchitecture.ARM64; 56 | 57 | private readonly string resourceDir = "Assets/Resources"; 58 | private readonly string xrManagementPackageName = "com.unity.xr.management"; 59 | private readonly string perfTestsPackageName = "xr.sdk.oculus.performancetests"; 60 | private readonly string urpPackageName = "com.unity.render-pipelines.universal"; 61 | private readonly string oculusXrSdkPackageName = "com.unity.xr.oculus"; 62 | private readonly string hdrpPackageName = "com.unity.testing.hdrp"; 63 | 64 | private readonly Regex revisionValueRegex = new Regex("\"revision\": \"([a-f0-9]*)\"", 65 | RegexOptions.Compiled | RegexOptions.IgnoreCase); 66 | private readonly Regex majorMinorVersionValueRegex = new Regex("([0-9]*\\.[0-9]*\\.)", 67 | RegexOptions.Compiled | RegexOptions.IgnoreCase); 68 | 69 | 70 | public void SerializeToAsset() 71 | { 72 | var settingsAsset = ScriptableObject.CreateInstance(); 73 | 74 | settingsAsset.SimulationMode = SimulationMode; 75 | settingsAsset.PlayerGraphicsApi = PlayerGraphicsApi.ToString(); 76 | settingsAsset.MtRendering = MtRendering; 77 | settingsAsset.GraphicsJobs = GraphicsJobs; 78 | settingsAsset.ColorSpace = ColorSpace.ToString(); 79 | settingsAsset.EnabledXrTarget = XrTarget; 80 | settingsAsset.XrsdkRevision = GetOculusXrSdkPackageVersionInfo(); 81 | settingsAsset.XrManagementRevision = GetXrManagementPackageVersionInfo(); 82 | settingsAsset.UrpPackageVersionInfo = GetUrpPackageVersionInfo(); 83 | settingsAsset.HdrpPackageVersionInfo = GetHdrpPackageVersionInfo(); 84 | settingsAsset.PerfTestsPackageRevision = GetPerfTestsPackageVersionInfo(); 85 | settingsAsset.DeviceRuntimeVersion = DeviceRuntimeVersion; 86 | settingsAsset.Username = Username = Environment.UserName; 87 | settingsAsset.FfrLevel = FfrLevel; 88 | settingsAsset.TestsRevision = TestsRevision; 89 | settingsAsset.TestsRevisionDate = TestsRevisionDate; 90 | settingsAsset.TestsBranch = TestsBranch; 91 | settingsAsset.AndroidTargetArchitecture = string.Format("AndroidTargetArchitecture|{0}", AndroidTargetArchitecture.ToString()); 92 | settingsAsset.RenderPipeline = RenderPipeline = 93 | $"renderpipeline|{(GraphicsSettings.renderPipelineAsset != null ? GraphicsSettings.renderPipelineAsset.name : "BuiltInRenderer")}"; 94 | 95 | #if URP 96 | settingsAsset.AntiAliasing = GraphicsSettings.renderPipelineAsset != null 97 | ? ((UniversalRenderPipelineAsset) GraphicsSettings.renderPipelineAsset).msaaSampleCount 98 | : QualitySettings.antiAliasing; 99 | #else 100 | settingsAsset.AntiAliasing = QualitySettings.antiAliasing; 101 | #endif 102 | 103 | 104 | #if OCULUS_SDK 105 | // These fields are used by the performance test framework and are an artifact from this class 106 | // previously using the provider - specific enums before converting to a cross-platform friendly string 107 | if (BuildTarget == BuildTarget.Android) 108 | { 109 | settingsAsset.StereoRenderingModeAndroid = StereoRenderingPath; 110 | } 111 | else 112 | { 113 | settingsAsset.StereoRenderingModeDesktop = StereoRenderingPath; 114 | } 115 | 116 | #if OCULUS_SDK_PERF 117 | settingsAsset.PluginVersion = string.Format("OculusPluginVersion|{0}", OculusStats.PluginVersion); 118 | #endif 119 | #endif 120 | #if XR_SDK 121 | settingsAsset.StereoRenderingMode = StereoRenderingPath; 122 | #else 123 | // legacy xr has different enum for player settings and runtime settings for stereo rendering mode 124 | var builtInXrStereoPath = (StereoRenderingPath)Enum.Parse(typeof(StereoRenderingPath), StereoRenderingPath); 125 | settingsAsset.StereoRenderingMode = GetXrStereoRenderingPathMapping(builtInXrStereoPath).ToString(); 126 | #endif 127 | CreateAndSaveCurrentSettingsAsset(settingsAsset); 128 | } 129 | 130 | private string GetXrManagementPackageVersionInfo() 131 | { 132 | string packageRevision = string.Empty; 133 | 134 | var listRequest = Client.List(true); 135 | while (!listRequest.IsCompleted) 136 | { 137 | } 138 | 139 | if (listRequest.Result.Any(r => r.name.Equals(xrManagementPackageName))) 140 | { 141 | var xrManagementPckg = 142 | listRequest.Result.First(r => r.name.Equals(xrManagementPackageName)); 143 | 144 | var revision = TryGetRevisionFromPackageJson(xrManagementPackageName) ?? "unavailable"; 145 | var version = xrManagementPckg.version; 146 | packageRevision = string.Format("XrManagementPackageName|{0}|XrManagementVersion|{1}|XrManagementRevision|{2}", xrManagementPackageName, version, revision); 147 | } 148 | 149 | return packageRevision; 150 | } 151 | 152 | private string GetUrpPackageVersionInfo() 153 | { 154 | string packageRevision = string.Empty; 155 | 156 | var listRequest = Client.List(true); 157 | while (!listRequest.IsCompleted) 158 | { 159 | } 160 | 161 | if (listRequest.Result.Any(r => r.name.Equals(urpPackageName))) 162 | { 163 | var urpPckg = 164 | listRequest.Result.First(r => r.name.Equals(urpPackageName)); 165 | 166 | var revision = TryGetRevisionFromPackageJson(urpPackageName) ?? "unavailable"; 167 | var version = urpPckg.version; 168 | packageRevision = string.Format("UrpPackageName|{0}|UrpVersion|{1}|UrpRevision|{2}", urpPackageName, version, revision); 169 | } 170 | 171 | return packageRevision; 172 | } 173 | 174 | private string GetHdrpPackageVersionInfo() 175 | { 176 | string packageRevision = string.Empty; 177 | 178 | var listRequest = Client.List(true); 179 | while (!listRequest.IsCompleted) 180 | { 181 | } 182 | 183 | if (listRequest.Result.Any(r => r.name.Equals(hdrpPackageName))) 184 | { 185 | var urpPckg = 186 | listRequest.Result.First(r => r.name.Equals(hdrpPackageName)); 187 | 188 | var revision = TryGetRevisionFromPackageJson(hdrpPackageName) ?? "unavailable"; 189 | var version = urpPckg.version; 190 | packageRevision = string.Format("HdrpPackageName|{0}|HdrpVersion|{1}|HdrpRevision|{2}", hdrpPackageName, version, revision); 191 | } 192 | 193 | return packageRevision; 194 | } 195 | 196 | private string GetPerfTestsPackageVersionInfo() 197 | { 198 | string packageRevision = string.Empty; 199 | 200 | var listRequest = Client.List(true); 201 | while (!listRequest.IsCompleted) 202 | { 203 | } 204 | 205 | if (listRequest.Result.Any(r => r.name.Equals(perfTestsPackageName))) 206 | { 207 | var perfTestsPckg = 208 | listRequest.Result.First(r => r.name.Equals(perfTestsPackageName)); 209 | 210 | var revision = TryGetRevisionFromPackageJson(perfTestsPackageName) ?? "unavailable"; 211 | var version = perfTestsPckg.version; 212 | packageRevision = string.Format("PerfTestsPackageName|{0}|PerfTestsVersion|{1}|PerfTestsRevision|{2}", perfTestsPackageName, version, revision); 213 | } 214 | 215 | return packageRevision; 216 | } 217 | 218 | private string GetOculusXrSdkPackageVersionInfo() 219 | { 220 | string packageRevision = string.Empty; 221 | 222 | var listRequest = Client.List(true); 223 | while (!listRequest.IsCompleted) 224 | { 225 | } 226 | 227 | if (listRequest.Result.Any(r => r.name.Equals(oculusXrSdkPackageName))) 228 | { 229 | var oculusXrsdkPckg = 230 | listRequest.Result.First(r => r.name.Equals(oculusXrSdkPackageName)); 231 | 232 | var version = oculusXrsdkPckg.version; 233 | 234 | // if XrsdkRevision is empty, then it wasn't passed in on the command line (which is 235 | // usually going to be the case if we're running in tests at the PR level for Xrsdk package). 236 | // In this case, we most likely are using a released package reference, so let's try to get 237 | // the revision from the package.json. 238 | if (string.IsNullOrEmpty(XrsdkRevision)) 239 | { 240 | XrsdkRevision = TryGetRevisionFromPackageJson(oculusXrSdkPackageName) ?? "unavailable"; 241 | } 242 | 243 | // if XrsdkRevisionDate is empty, then it wasn't passed in on the command line (which is 244 | // usually going to be the case if we're running in tests at the PR level for Xrsdk package). 245 | // In this case, we most likely are using a released package reference, so let's try to get 246 | // the revision date from the package manager api instead. 247 | if (string.IsNullOrEmpty(XrsdkRevisionDate)) 248 | { 249 | TryGetXrsdkRevisionDate(oculusXrsdkPckg); 250 | } 251 | 252 | // if XrsdkBranch is empty, then it wasn't passed in on the command line (which is 253 | // usually going to be the case if we're running in tests at the PR level for Xrsdk package). 254 | // In this case, we most likely are using a released package reference, so let's try to infer 255 | // the branch from the major.minor version of the package via the package manager API 256 | if (string.IsNullOrEmpty(XrsdkBranch)) 257 | { 258 | TryGetXrsdkBranch(oculusXrsdkPckg); 259 | } 260 | packageRevision = string.Format( 261 | "XrSdkName|{0}|XrSdkVersion|{1}|XrSdkRevision|{2}|XrSdkRevisionDate|{3}|XrSdkBranch|{4}", 262 | oculusXrSdkPackageName, 263 | version, 264 | XrsdkRevision, 265 | XrsdkRevisionDate, 266 | XrsdkBranch); 267 | } 268 | 269 | return packageRevision; 270 | } 271 | 272 | private void TryGetXrsdkBranch(PackageInfo oculusXrsdkPckg) 273 | { 274 | var matches = majorMinorVersionValueRegex.Matches(oculusXrsdkPckg.version); 275 | XrsdkBranch = matches.Count > 0 ? string.Concat(matches[0].Groups[0].Value, "x") : "release"; 276 | } 277 | 278 | private void TryGetXrsdkRevisionDate(PackageInfo oculusXrsdkPckg) 279 | { 280 | #if OCULUS_SDK 281 | XrsdkRevisionDate = 282 | oculusXrsdkPckg.datePublished != null ? 283 | ((DateTime) oculusXrsdkPckg.datePublished).ToString("s", DateTimeFormatInfo.InvariantInfo) : "unavailable"; 284 | #endif 285 | } 286 | 287 | private string TryGetRevisionFromPackageJson(string packageName) 288 | { 289 | string revision = null; 290 | var packageAsString = File.ReadAllText(string.Format("Packages/{0}/package.json",packageName)); 291 | var matches = revisionValueRegex.Matches(packageAsString); 292 | if (matches.Count > 0) 293 | { 294 | revision = matches[0].Groups[1].Value; 295 | } 296 | 297 | return revision; 298 | } 299 | 300 | private XRSettings.StereoRenderingMode GetXrStereoRenderingPathMapping(StereoRenderingPath stereoRenderingPath) 301 | { 302 | switch (stereoRenderingPath) 303 | { 304 | case UnityEditor.StereoRenderingPath.SinglePass: 305 | return XRSettings.StereoRenderingMode.SinglePass; 306 | case UnityEditor.StereoRenderingPath.MultiPass: 307 | return XRSettings.StereoRenderingMode.MultiPass; 308 | case UnityEditor.StereoRenderingPath.Instancing: 309 | return XRSettings.StereoRenderingMode.SinglePassInstanced; 310 | default: 311 | return XRSettings.StereoRenderingMode.SinglePassMultiview; 312 | } 313 | } 314 | 315 | private void CreateAndSaveCurrentSettingsAsset(CurrentSettings settingsAsset) 316 | { 317 | if (!System.IO.Directory.Exists(resourceDir)) 318 | { 319 | System.IO.Directory.CreateDirectory(resourceDir); 320 | } 321 | 322 | AssetDatabase.CreateAsset(settingsAsset, resourceDir + "/settings.asset"); 323 | AssetDatabase.SaveAssets(); 324 | } 325 | #endif 326 | } 327 | } -------------------------------------------------------------------------------- /Editor/Options.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Options.cs 3 | // 4 | // Authors: 5 | // Jonathan Pryor 6 | // 7 | // Copyright (C) 2008 Novell (http://www.novell.com) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | // Compile With: 30 | // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll 31 | // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll 32 | // 33 | // The LINQ version just changes the implementation of 34 | // OptionSet.Parse(IEnumerable), and confers no semantic changes. 35 | 36 | // 37 | // A Getopt::Long-inspired option parsing library for C#. 38 | // 39 | // NDesk.Options.OptionSet is built upon a key/value table, where the 40 | // key is a option format string and the value is a delegate that is 41 | // invoked when the format string is matched. 42 | // 43 | // Option format strings: 44 | // Regex-like BNF Grammar: 45 | // name: .+ 46 | // type: [=:] 47 | // sep: ( [^{}]+ | '{' .+ '}' )? 48 | // aliases: ( name type sep ) ( '|' name type sep )* 49 | // 50 | // Each '|'-delimited name is an alias for the associated action. If the 51 | // format string ends in a '=', it has a required value. If the format 52 | // string ends in a ':', it has an optional value. If neither '=' or ':' 53 | // is present, no value is supported. `=' or `:' need only be defined on one 54 | // alias, but if they are provided on more than one they must be consistent. 55 | // 56 | // Each alias portion may also end with a "key/value separator", which is used 57 | // to split option values if the option accepts > 1 value. If not specified, 58 | // it defaults to '=' and ':'. If specified, it can be any character except 59 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be 60 | // used (i.e. the separate values should be distinct arguments), then "{}" 61 | // should be used as the separator. 62 | // 63 | // Options are extracted either from the current option by looking for 64 | // the option name followed by an '=' or ':', or is taken from the 65 | // following option IFF: 66 | // - The current option does not contain a '=' or a ':' 67 | // - The current option requires a value (i.e. not a Option type of ':') 68 | // 69 | // The `name' used in the option format string does NOT include any leading 70 | // option indicator, such as '-', '--', or '/'. All three of these are 71 | // permitted/required on any named option. 72 | // 73 | // Option bundling is permitted so long as: 74 | // - '-' is used to start the option group 75 | // - all of the bundled options are a single character 76 | // - at most one of the bundled options accepts a value, and the value 77 | // provided starts from the next character to the end of the string. 78 | // 79 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' 80 | // as '-Dname=value'. 81 | // 82 | // Option processing is disabled by specifying "--". All options after "--" 83 | // are returned by OptionSet.Parse() unchanged and unprocessed. 84 | // 85 | // Unprocessed options are returned from OptionSet.Parse(). 86 | // 87 | // Examples: 88 | // int verbose = 0; 89 | // OptionSet p = new OptionSet () 90 | // .Add ("v", v => ++verbose) 91 | // .Add ("name=|value=", v => Console.WriteLine (v)); 92 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); 93 | // 94 | // The above would parse the argument string array, and would invoke the 95 | // lambda expression three times, setting `verbose' to 3 when complete. 96 | // It would also print out "A" and "B" to standard output. 97 | // The returned array would contain the string "extra". 98 | // 99 | // C# 3.0 collection initializers are supported and encouraged: 100 | // var p = new OptionSet () { 101 | // { "h|?|help", v => ShowHelp () }, 102 | // }; 103 | // 104 | // System.ComponentModel.TypeConverter is also supported, allowing the use of 105 | // custom data types in the callback type; TypeConverter.ConvertFromString() 106 | // is used to convert the value option to an instance of the specified 107 | // type: 108 | // 109 | // var p = new OptionSet () { 110 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, 111 | // }; 112 | // 113 | // Random other tidbits: 114 | // - Boolean options (those w/o '=' or ':' in the option format string) 115 | // are explicitly enabled if they are followed with '+', and explicitly 116 | // disabled if they are followed with '-': 117 | // string a = null; 118 | // var p = new OptionSet () { 119 | // { "a", s => a = s }, 120 | // }; 121 | // p.Parse (new string[]{"-a"}); // sets v != null 122 | // p.Parse (new string[]{"-a+"}); // sets v != null 123 | // p.Parse (new string[]{"-a-"}); // sets v == null 124 | // 125 | 126 | using System; 127 | using System.Collections; 128 | using System.Collections.Generic; 129 | using System.Collections.ObjectModel; 130 | using System.ComponentModel; 131 | using System.IO; 132 | using System.Runtime.Serialization; 133 | using System.Security.Permissions; 134 | using System.Text; 135 | using System.Text.RegularExpressions; 136 | 137 | #if LINQ 138 | using System.Linq; 139 | #endif 140 | 141 | #if TEST 142 | using NDesk.Options; 143 | #endif 144 | 145 | namespace NDesk.Options 146 | { 147 | 148 | public class OptionValueCollection : IList, IList 149 | { 150 | 151 | List values = new List(); 152 | OptionContext c; 153 | 154 | internal OptionValueCollection(OptionContext c) 155 | { 156 | this.c = c; 157 | } 158 | 159 | #region ICollection 160 | void ICollection.CopyTo(Array array, int index) { (values as ICollection).CopyTo(array, index); } 161 | bool ICollection.IsSynchronized { get { return (values as ICollection).IsSynchronized; } } 162 | object ICollection.SyncRoot { get { return (values as ICollection).SyncRoot; } } 163 | #endregion 164 | 165 | #region ICollection 166 | public void Add(string item) { values.Add(item); } 167 | public void Clear() { values.Clear(); } 168 | public bool Contains(string item) { return values.Contains(item); } 169 | public void CopyTo(string[] array, int arrayIndex) { values.CopyTo(array, arrayIndex); } 170 | public bool Remove(string item) { return values.Remove(item); } 171 | public int Count { get { return values.Count; } } 172 | public bool IsReadOnly { get { return false; } } 173 | #endregion 174 | 175 | #region IEnumerable 176 | IEnumerator IEnumerable.GetEnumerator() { return values.GetEnumerator(); } 177 | #endregion 178 | 179 | #region IEnumerable 180 | public IEnumerator GetEnumerator() { return values.GetEnumerator(); } 181 | #endregion 182 | 183 | #region IList 184 | int IList.Add(object value) { return (values as IList).Add(value); } 185 | bool IList.Contains(object value) { return (values as IList).Contains(value); } 186 | int IList.IndexOf(object value) { return (values as IList).IndexOf(value); } 187 | void IList.Insert(int index, object value) { (values as IList).Insert(index, value); } 188 | void IList.Remove(object value) { (values as IList).Remove(value); } 189 | void IList.RemoveAt(int index) { (values as IList).RemoveAt(index); } 190 | bool IList.IsFixedSize { get { return false; } } 191 | object IList.this[int index] { get { return this[index]; } set { (values as IList)[index] = value; } } 192 | #endregion 193 | 194 | #region IList 195 | public int IndexOf(string item) { return values.IndexOf(item); } 196 | public void Insert(int index, string item) { values.Insert(index, item); } 197 | public void RemoveAt(int index) { values.RemoveAt(index); } 198 | 199 | private void AssertValid(int index) 200 | { 201 | if (c.Option == null) 202 | throw new InvalidOperationException("OptionContext.Option is null."); 203 | if (index >= c.Option.MaxValueCount) 204 | throw new ArgumentOutOfRangeException("index"); 205 | if (c.Option.OptionValueType == OptionValueType.Required && 206 | index >= values.Count) 207 | throw new OptionException(string.Format( 208 | c.OptionSet.MessageLocalizer("Missing required value for option '{0}'."), c.OptionName), 209 | c.OptionName); 210 | } 211 | 212 | public string this[int index] 213 | { 214 | get 215 | { 216 | AssertValid(index); 217 | return index >= values.Count ? null : values[index]; 218 | } 219 | set 220 | { 221 | values[index] = value; 222 | } 223 | } 224 | #endregion 225 | 226 | public List ToList() 227 | { 228 | return new List(values); 229 | } 230 | 231 | public string[] ToArray() 232 | { 233 | return values.ToArray(); 234 | } 235 | 236 | public override string ToString() 237 | { 238 | return string.Join(", ", values.ToArray()); 239 | } 240 | } 241 | 242 | public class OptionContext 243 | { 244 | private Option option; 245 | private string name; 246 | private int index; 247 | private OptionSet set; 248 | private OptionValueCollection c; 249 | 250 | public OptionContext(OptionSet set) 251 | { 252 | this.set = set; 253 | this.c = new OptionValueCollection(this); 254 | } 255 | 256 | public Option Option 257 | { 258 | get { return option; } 259 | set { option = value; } 260 | } 261 | 262 | public string OptionName 263 | { 264 | get { return name; } 265 | set { name = value; } 266 | } 267 | 268 | public int OptionIndex 269 | { 270 | get { return index; } 271 | set { index = value; } 272 | } 273 | 274 | public OptionSet OptionSet 275 | { 276 | get { return set; } 277 | } 278 | 279 | public OptionValueCollection OptionValues 280 | { 281 | get { return c; } 282 | } 283 | } 284 | 285 | public enum OptionValueType 286 | { 287 | None, 288 | Optional, 289 | Required, 290 | } 291 | 292 | public abstract class Option 293 | { 294 | string prototype, description; 295 | string[] names; 296 | OptionValueType type; 297 | int count; 298 | string[] separators; 299 | 300 | protected Option(string prototype, string description) 301 | : this(prototype, description, 1) 302 | { 303 | } 304 | 305 | protected Option(string prototype, string description, int maxValueCount) 306 | { 307 | if (prototype == null) 308 | throw new ArgumentNullException("prototype"); 309 | if (prototype.Length == 0) 310 | throw new ArgumentException("Cannot be the empty string.", "prototype"); 311 | if (maxValueCount < 0) 312 | throw new ArgumentOutOfRangeException("maxValueCount"); 313 | 314 | this.prototype = prototype; 315 | this.names = prototype.Split('|'); 316 | this.description = description; 317 | this.count = maxValueCount; 318 | this.type = ParsePrototype(); 319 | 320 | if (this.count == 0 && type != OptionValueType.None) 321 | throw new ArgumentException( 322 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + 323 | "OptionValueType.Optional.", 324 | "maxValueCount"); 325 | if (this.type == OptionValueType.None && maxValueCount > 1) 326 | throw new ArgumentException( 327 | string.Format("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), 328 | "maxValueCount"); 329 | if (Array.IndexOf(names, "<>") >= 0 && 330 | ((names.Length == 1 && this.type != OptionValueType.None) || 331 | (names.Length > 1 && this.MaxValueCount > 1))) 332 | throw new ArgumentException( 333 | "The default option handler '<>' cannot require values.", 334 | "prototype"); 335 | } 336 | 337 | public string Prototype { get { return prototype; } } 338 | public string Description { get { return description; } } 339 | public OptionValueType OptionValueType { get { return type; } } 340 | public int MaxValueCount { get { return count; } } 341 | 342 | public string[] GetNames() 343 | { 344 | return (string[])names.Clone(); 345 | } 346 | 347 | public string[] GetValueSeparators() 348 | { 349 | if (separators == null) 350 | return new string[0]; 351 | return (string[])separators.Clone(); 352 | } 353 | 354 | protected static T Parse(string value, OptionContext c) 355 | { 356 | TypeConverter conv = TypeDescriptor.GetConverter(typeof(T)); 357 | T t = default(T); 358 | try 359 | { 360 | if (value != null) 361 | t = (T)conv.ConvertFromString(value); 362 | } 363 | catch (Exception e) 364 | { 365 | throw new OptionException( 366 | string.Format( 367 | c.OptionSet.MessageLocalizer("Could not convert string `{0}' to type {1} for option `{2}'."), 368 | value, typeof(T).Name, c.OptionName), 369 | c.OptionName, e); 370 | } 371 | return t; 372 | } 373 | 374 | internal string[] Names { get { return names; } } 375 | internal string[] ValueSeparators { get { return separators; } } 376 | 377 | static readonly char[] NameTerminator = new char[] { '=', ':' }; 378 | 379 | private OptionValueType ParsePrototype() 380 | { 381 | char type = '\0'; 382 | List seps = new List(); 383 | for (int i = 0; i < names.Length; ++i) 384 | { 385 | string name = names[i]; 386 | if (name.Length == 0) 387 | throw new ArgumentException("Empty option names are not supported.", "prototype"); 388 | 389 | int end = name.IndexOfAny(NameTerminator); 390 | if (end == -1) 391 | continue; 392 | names[i] = name.Substring(0, end); 393 | if (type == '\0' || type == name[end]) 394 | type = name[end]; 395 | else 396 | throw new ArgumentException( 397 | string.Format("Conflicting option types: '{0}' vs. '{1}'.", type, name[end]), 398 | "prototype"); 399 | AddSeparators(name, end, seps); 400 | } 401 | 402 | if (type == '\0') 403 | return OptionValueType.None; 404 | 405 | if (count <= 1 && seps.Count != 0) 406 | throw new ArgumentException( 407 | string.Format("Cannot provide key/value separators for Options taking {0} value(s).", count), 408 | "prototype"); 409 | if (count > 1) 410 | { 411 | if (seps.Count == 0) 412 | this.separators = new string[] { ":", "=" }; 413 | else if (seps.Count == 1 && seps[0].Length == 0) 414 | this.separators = null; 415 | else 416 | this.separators = seps.ToArray(); 417 | } 418 | 419 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional; 420 | } 421 | 422 | private static void AddSeparators(string name, int end, ICollection seps) 423 | { 424 | int start = -1; 425 | for (int i = end + 1; i < name.Length; ++i) 426 | { 427 | switch (name[i]) 428 | { 429 | case '{': 430 | if (start != -1) 431 | throw new ArgumentException( 432 | string.Format("Ill-formed name/value separator found in \"{0}\".", name), 433 | "prototype"); 434 | start = i + 1; 435 | break; 436 | case '}': 437 | if (start == -1) 438 | throw new ArgumentException( 439 | string.Format("Ill-formed name/value separator found in \"{0}\".", name), 440 | "prototype"); 441 | seps.Add(name.Substring(start, i - start)); 442 | start = -1; 443 | break; 444 | default: 445 | if (start == -1) 446 | seps.Add(name[i].ToString()); 447 | break; 448 | } 449 | } 450 | if (start != -1) 451 | throw new ArgumentException( 452 | string.Format("Ill-formed name/value separator found in \"{0}\".", name), 453 | "prototype"); 454 | } 455 | 456 | public void Invoke(OptionContext c) 457 | { 458 | OnParseComplete(c); 459 | c.OptionName = null; 460 | c.Option = null; 461 | c.OptionValues.Clear(); 462 | } 463 | 464 | protected abstract void OnParseComplete(OptionContext c); 465 | 466 | public override string ToString() 467 | { 468 | return Prototype; 469 | } 470 | } 471 | 472 | [Serializable] 473 | public class OptionException : Exception 474 | { 475 | private string option; 476 | 477 | public OptionException() 478 | { 479 | } 480 | 481 | public OptionException(string message, string optionName) 482 | : base(message) 483 | { 484 | this.option = optionName; 485 | } 486 | 487 | public OptionException(string message, string optionName, Exception innerException) 488 | : base(message, innerException) 489 | { 490 | this.option = optionName; 491 | } 492 | 493 | protected OptionException(SerializationInfo info, StreamingContext context) 494 | : base(info, context) 495 | { 496 | this.option = info.GetString("OptionName"); 497 | } 498 | 499 | public string OptionName 500 | { 501 | get { return this.option; } 502 | } 503 | 504 | [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] 505 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 506 | { 507 | base.GetObjectData(info, context); 508 | info.AddValue("OptionName", option); 509 | } 510 | } 511 | 512 | public delegate void OptionAction(TKey key, TValue value); 513 | 514 | public class OptionSet : KeyedCollection 515 | { 516 | public OptionSet() 517 | : this(delegate (string f) { return f; }) 518 | { 519 | } 520 | 521 | public OptionSet(Converter localizer) 522 | { 523 | this.localizer = localizer; 524 | } 525 | 526 | Converter localizer; 527 | 528 | public Converter MessageLocalizer 529 | { 530 | get { return localizer; } 531 | } 532 | 533 | protected override string GetKeyForItem(Option item) 534 | { 535 | if (item == null) 536 | throw new ArgumentNullException("option"); 537 | if (item.Names != null && item.Names.Length > 0) 538 | return item.Names[0]; 539 | // This should never happen, as it's invalid for Option to be 540 | // constructed w/o any names. 541 | throw new InvalidOperationException("Option has no names!"); 542 | } 543 | 544 | [Obsolete("Use KeyedCollection.this[string]")] 545 | protected Option GetOptionForName(string option) 546 | { 547 | if (option == null) 548 | throw new ArgumentNullException("option"); 549 | try 550 | { 551 | return base[option]; 552 | } 553 | catch (KeyNotFoundException) 554 | { 555 | return null; 556 | } 557 | } 558 | 559 | protected override void InsertItem(int index, Option item) 560 | { 561 | base.InsertItem(index, item); 562 | AddImpl(item); 563 | } 564 | 565 | protected override void RemoveItem(int index) 566 | { 567 | base.RemoveItem(index); 568 | Option p = Items[index]; 569 | // KeyedCollection.RemoveItem() handles the 0th item 570 | for (int i = 1; i < p.Names.Length; ++i) 571 | { 572 | Dictionary.Remove(p.Names[i]); 573 | } 574 | } 575 | 576 | protected override void SetItem(int index, Option item) 577 | { 578 | base.SetItem(index, item); 579 | RemoveItem(index); 580 | AddImpl(item); 581 | } 582 | 583 | private void AddImpl(Option option) 584 | { 585 | if (option == null) 586 | throw new ArgumentNullException("option"); 587 | List added = new List(option.Names.Length); 588 | try 589 | { 590 | // KeyedCollection.InsertItem/SetItem handle the 0th name. 591 | for (int i = 1; i < option.Names.Length; ++i) 592 | { 593 | Dictionary.Add(option.Names[i], option); 594 | added.Add(option.Names[i]); 595 | } 596 | } 597 | catch (Exception) 598 | { 599 | foreach (string name in added) 600 | Dictionary.Remove(name); 601 | throw; 602 | } 603 | } 604 | 605 | public new OptionSet Add(Option option) 606 | { 607 | base.Add(option); 608 | return this; 609 | } 610 | 611 | sealed class ActionOption : Option 612 | { 613 | Action action; 614 | 615 | public ActionOption(string prototype, string description, int count, Action action) 616 | : base(prototype, description, count) 617 | { 618 | if (action == null) 619 | throw new ArgumentNullException("action"); 620 | this.action = action; 621 | } 622 | 623 | protected override void OnParseComplete(OptionContext c) 624 | { 625 | action(c.OptionValues); 626 | } 627 | } 628 | 629 | public OptionSet Add(string prototype, Action action) 630 | { 631 | return Add(prototype, null, action); 632 | } 633 | 634 | public OptionSet Add(string prototype, string description, Action action) 635 | { 636 | if (action == null) 637 | throw new ArgumentNullException("action"); 638 | Option p = new ActionOption(prototype, description, 1, 639 | delegate (OptionValueCollection v) { action(v[0]); }); 640 | base.Add(p); 641 | return this; 642 | } 643 | 644 | public OptionSet Add(string prototype, OptionAction action) 645 | { 646 | return Add(prototype, null, action); 647 | } 648 | 649 | public OptionSet Add(string prototype, string description, OptionAction action) 650 | { 651 | if (action == null) 652 | throw new ArgumentNullException("action"); 653 | Option p = new ActionOption(prototype, description, 2, 654 | delegate (OptionValueCollection v) { action(v[0], v[1]); }); 655 | base.Add(p); 656 | return this; 657 | } 658 | 659 | sealed class ActionOption : Option 660 | { 661 | Action action; 662 | 663 | public ActionOption(string prototype, string description, Action action) 664 | : base(prototype, description, 1) 665 | { 666 | if (action == null) 667 | throw new ArgumentNullException("action"); 668 | this.action = action; 669 | } 670 | 671 | protected override void OnParseComplete(OptionContext c) 672 | { 673 | action(Parse(c.OptionValues[0], c)); 674 | } 675 | } 676 | 677 | sealed class ActionOption : Option 678 | { 679 | OptionAction action; 680 | 681 | public ActionOption(string prototype, string description, OptionAction action) 682 | : base(prototype, description, 2) 683 | { 684 | if (action == null) 685 | throw new ArgumentNullException("action"); 686 | this.action = action; 687 | } 688 | 689 | protected override void OnParseComplete(OptionContext c) 690 | { 691 | action( 692 | Parse(c.OptionValues[0], c), 693 | Parse(c.OptionValues[1], c)); 694 | } 695 | } 696 | 697 | public OptionSet Add(string prototype, Action action) 698 | { 699 | return Add(prototype, null, action); 700 | } 701 | 702 | public OptionSet Add(string prototype, string description, Action action) 703 | { 704 | return Add(new ActionOption(prototype, description, action)); 705 | } 706 | 707 | public OptionSet Add(string prototype, OptionAction action) 708 | { 709 | return Add(prototype, null, action); 710 | } 711 | 712 | public OptionSet Add(string prototype, string description, OptionAction action) 713 | { 714 | return Add(new ActionOption(prototype, description, action)); 715 | } 716 | 717 | protected virtual OptionContext CreateOptionContext() 718 | { 719 | return new OptionContext(this); 720 | } 721 | 722 | #if LINQ 723 | public List Parse (IEnumerable arguments) 724 | { 725 | bool process = true; 726 | OptionContext c = CreateOptionContext (); 727 | c.OptionIndex = -1; 728 | var def = GetOptionForName ("<>"); 729 | var unprocessed = 730 | from argument in arguments 731 | where ++c.OptionIndex >= 0 && (process || def != null) 732 | ? process 733 | ? argument == "--" 734 | ? (process = false) 735 | : !Parse (argument, c) 736 | ? def != null 737 | ? Unprocessed (null, def, c, argument) 738 | : true 739 | : false 740 | : def != null 741 | ? Unprocessed (null, def, c, argument) 742 | : true 743 | : true 744 | select argument; 745 | List r = unprocessed.ToList (); 746 | if (c.Option != null) 747 | c.Option.Invoke (c); 748 | return r; 749 | } 750 | #else 751 | public List Parse(IEnumerable arguments) 752 | { 753 | OptionContext c = CreateOptionContext(); 754 | c.OptionIndex = -1; 755 | bool process = true; 756 | List unprocessed = new List(); 757 | Option def = Contains("<>") ? this["<>"] : null; 758 | foreach (string argument in arguments) 759 | { 760 | ++c.OptionIndex; 761 | if (argument == "-") 762 | { 763 | process = false; 764 | continue; 765 | } 766 | if (!process) 767 | { 768 | Unprocessed(unprocessed, def, c, argument); 769 | continue; 770 | } 771 | if (!Parse(argument, c)) 772 | Unprocessed(unprocessed, def, c, argument); 773 | } 774 | if (c.Option != null) 775 | c.Option.Invoke(c); 776 | return unprocessed; 777 | } 778 | #endif 779 | 780 | private static bool Unprocessed(ICollection extra, Option def, OptionContext c, string argument) 781 | { 782 | if (def == null) 783 | { 784 | extra.Add(argument); 785 | return false; 786 | } 787 | c.OptionValues.Add(argument); 788 | c.Option = def; 789 | c.Option.Invoke(c); 790 | return false; 791 | } 792 | 793 | private readonly Regex ValueOption = new Regex( 794 | @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); 795 | 796 | protected bool GetOptionParts(string argument, out string flag, out string name, out string sep, out string value) 797 | { 798 | if (argument == null) 799 | throw new ArgumentNullException("argument"); 800 | 801 | flag = name = sep = value = null; 802 | Match m = ValueOption.Match(argument); 803 | if (!m.Success) 804 | { 805 | return false; 806 | } 807 | flag = m.Groups["flag"].Value; 808 | name = m.Groups["name"].Value; 809 | if (m.Groups["sep"].Success && m.Groups["value"].Success) 810 | { 811 | sep = m.Groups["sep"].Value; 812 | value = m.Groups["value"].Value; 813 | } 814 | return true; 815 | } 816 | 817 | protected virtual bool Parse(string argument, OptionContext c) 818 | { 819 | if (c.Option != null) 820 | { 821 | ParseValue(argument, c); 822 | return true; 823 | } 824 | 825 | string f, n, s, v; 826 | if (!GetOptionParts(argument, out f, out n, out s, out v)) 827 | return false; 828 | 829 | Option p; 830 | if (Contains(n)) 831 | { 832 | p = this[n]; 833 | c.OptionName = f + n; 834 | c.Option = p; 835 | switch (p.OptionValueType) 836 | { 837 | case OptionValueType.None: 838 | c.OptionValues.Add(n); 839 | c.Option.Invoke(c); 840 | break; 841 | case OptionValueType.Optional: 842 | case OptionValueType.Required: 843 | ParseValue(v, c); 844 | break; 845 | } 846 | return true; 847 | } 848 | // no match; is it a bool option? 849 | if (ParseBool(argument, n, c)) 850 | return true; 851 | // is it a bundled option? 852 | if (ParseBundledValue(f, string.Concat(n + s + v), c)) 853 | return true; 854 | 855 | return false; 856 | } 857 | 858 | private void ParseValue(string option, OptionContext c) 859 | { 860 | if (option != null) 861 | foreach (string o in c.Option.ValueSeparators != null 862 | ? option.Split(c.Option.ValueSeparators, StringSplitOptions.None) 863 | : new string[] { option }) 864 | { 865 | c.OptionValues.Add(o); 866 | } 867 | if (c.OptionValues.Count == c.Option.MaxValueCount || 868 | c.Option.OptionValueType == OptionValueType.Optional) 869 | c.Option.Invoke(c); 870 | else if (c.OptionValues.Count > c.Option.MaxValueCount) 871 | { 872 | throw new OptionException(localizer(string.Format( 873 | "Error: Found {0} option values when expecting {1}.", 874 | c.OptionValues.Count, c.Option.MaxValueCount)), 875 | c.OptionName); 876 | } 877 | } 878 | 879 | private bool ParseBool(string option, string n, OptionContext c) 880 | { 881 | Option p; 882 | string rn; 883 | if (n.Length >= 1 && (n[n.Length - 1] == '+' || n[n.Length - 1] == '-') && 884 | Contains((rn = n.Substring(0, n.Length - 1)))) 885 | { 886 | p = this[rn]; 887 | string v = n[n.Length - 1] == '+' ? option : null; 888 | c.OptionName = option; 889 | c.Option = p; 890 | c.OptionValues.Add(v); 891 | p.Invoke(c); 892 | return true; 893 | } 894 | return false; 895 | } 896 | 897 | private bool ParseBundledValue(string f, string n, OptionContext c) 898 | { 899 | if (f != "-") 900 | return false; 901 | for (int i = 0; i < n.Length; ++i) 902 | { 903 | Option p; 904 | string opt = f + n[i].ToString(); 905 | string rn = n[i].ToString(); 906 | if (!Contains(rn)) 907 | { 908 | if (i == 0) 909 | return false; 910 | throw new OptionException(string.Format(localizer( 911 | "Cannot bundle unregistered option '{0}'."), opt), opt); 912 | } 913 | p = this[rn]; 914 | switch (p.OptionValueType) 915 | { 916 | case OptionValueType.None: 917 | Invoke(c, opt, n, p); 918 | break; 919 | case OptionValueType.Optional: 920 | case OptionValueType.Required: 921 | { 922 | string v = n.Substring(i + 1); 923 | c.Option = p; 924 | c.OptionName = opt; 925 | ParseValue(v.Length != 0 ? v : null, c); 926 | return true; 927 | } 928 | default: 929 | throw new InvalidOperationException("Unknown OptionValueType: " + p.OptionValueType); 930 | } 931 | } 932 | return true; 933 | } 934 | 935 | private static void Invoke(OptionContext c, string name, string value, Option option) 936 | { 937 | c.OptionName = name; 938 | c.Option = option; 939 | c.OptionValues.Add(value); 940 | option.Invoke(c); 941 | } 942 | 943 | private const int OptionWidth = 29; 944 | 945 | public void WriteOptionDescriptions(TextWriter o) 946 | { 947 | foreach (Option p in this) 948 | { 949 | int written = 0; 950 | if (!WriteOptionPrototype(o, p, ref written)) 951 | continue; 952 | 953 | if (written < OptionWidth) 954 | o.Write(new string(' ', OptionWidth - written)); 955 | else 956 | { 957 | o.WriteLine(); 958 | o.Write(new string(' ', OptionWidth)); 959 | } 960 | 961 | List lines = GetLines(localizer(GetDescription(p.Description))); 962 | o.WriteLine(lines[0]); 963 | string prefix = new string(' ', OptionWidth + 2); 964 | for (int i = 1; i < lines.Count; ++i) 965 | { 966 | o.Write(prefix); 967 | o.WriteLine(lines[i]); 968 | } 969 | } 970 | } 971 | 972 | bool WriteOptionPrototype(TextWriter o, Option p, ref int written) 973 | { 974 | string[] names = p.Names; 975 | 976 | int i = GetNextOptionIndex(names, 0); 977 | if (i == names.Length) 978 | return false; 979 | 980 | if (names[i].Length == 1) 981 | { 982 | Write(o, ref written, " -"); 983 | Write(o, ref written, names[0]); 984 | } 985 | else 986 | { 987 | Write(o, ref written, " --"); 988 | Write(o, ref written, names[0]); 989 | } 990 | 991 | for (i = GetNextOptionIndex(names, i + 1); 992 | i < names.Length; i = GetNextOptionIndex(names, i + 1)) 993 | { 994 | Write(o, ref written, ", "); 995 | Write(o, ref written, names[i].Length == 1 ? "-" : "--"); 996 | Write(o, ref written, names[i]); 997 | } 998 | 999 | if (p.OptionValueType == OptionValueType.Optional || 1000 | p.OptionValueType == OptionValueType.Required) 1001 | { 1002 | if (p.OptionValueType == OptionValueType.Optional) 1003 | { 1004 | Write(o, ref written, localizer("[")); 1005 | } 1006 | Write(o, ref written, localizer("=" + GetArgumentName(0, p.MaxValueCount, p.Description))); 1007 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 1008 | ? p.ValueSeparators[0] 1009 | : " "; 1010 | for (int c = 1; c < p.MaxValueCount; ++c) 1011 | { 1012 | Write(o, ref written, localizer(sep + GetArgumentName(c, p.MaxValueCount, p.Description))); 1013 | } 1014 | if (p.OptionValueType == OptionValueType.Optional) 1015 | { 1016 | Write(o, ref written, localizer("]")); 1017 | } 1018 | } 1019 | return true; 1020 | } 1021 | 1022 | static int GetNextOptionIndex(string[] names, int i) 1023 | { 1024 | while (i < names.Length && names[i] == "<>") 1025 | { 1026 | ++i; 1027 | } 1028 | return i; 1029 | } 1030 | 1031 | static void Write(TextWriter o, ref int n, string s) 1032 | { 1033 | n += s.Length; 1034 | o.Write(s); 1035 | } 1036 | 1037 | private static string GetArgumentName(int index, int maxIndex, string description) 1038 | { 1039 | if (description == null) 1040 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1041 | string[] nameStart; 1042 | if (maxIndex == 1) 1043 | nameStart = new string[] { "{0:", "{" }; 1044 | else 1045 | nameStart = new string[] { "{" + index + ":" }; 1046 | for (int i = 0; i < nameStart.Length; ++i) 1047 | { 1048 | int start, j = 0; 1049 | do 1050 | { 1051 | start = description.IndexOf(nameStart[i], j); 1052 | } while (start >= 0 && j != 0 ? description[j++ - 1] == '{' : false); 1053 | if (start == -1) 1054 | continue; 1055 | int end = description.IndexOf("}", start); 1056 | if (end == -1) 1057 | continue; 1058 | return description.Substring(start + nameStart[i].Length, end - start - nameStart[i].Length); 1059 | } 1060 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); 1061 | } 1062 | 1063 | private static string GetDescription(string description) 1064 | { 1065 | if (description == null) 1066 | return string.Empty; 1067 | StringBuilder sb = new StringBuilder(description.Length); 1068 | int start = -1; 1069 | for (int i = 0; i < description.Length; ++i) 1070 | { 1071 | switch (description[i]) 1072 | { 1073 | case '{': 1074 | if (i == start) 1075 | { 1076 | sb.Append('{'); 1077 | start = -1; 1078 | } 1079 | else if (start < 0) 1080 | start = i + 1; 1081 | break; 1082 | case '}': 1083 | if (start < 0) 1084 | { 1085 | if ((i + 1) == description.Length || description[i + 1] != '}') 1086 | throw new InvalidOperationException("Invalid option description: " + description); 1087 | ++i; 1088 | sb.Append("}"); 1089 | } 1090 | else 1091 | { 1092 | sb.Append(description.Substring(start, i - start)); 1093 | start = -1; 1094 | } 1095 | break; 1096 | case ':': 1097 | if (start < 0) 1098 | goto default; 1099 | start = i + 1; 1100 | break; 1101 | default: 1102 | if (start < 0) 1103 | sb.Append(description[i]); 1104 | break; 1105 | } 1106 | } 1107 | return sb.ToString(); 1108 | } 1109 | 1110 | private static List GetLines(string description) 1111 | { 1112 | List lines = new List(); 1113 | if (string.IsNullOrEmpty(description)) 1114 | { 1115 | lines.Add(string.Empty); 1116 | return lines; 1117 | } 1118 | int length = 80 - OptionWidth - 2; 1119 | int start = 0, end; 1120 | do 1121 | { 1122 | end = GetLineEnd(start, length, description); 1123 | bool cont = false; 1124 | if (end < description.Length) 1125 | { 1126 | char c = description[end]; 1127 | if (c == '-' || (char.IsWhiteSpace(c) && c != '\n')) 1128 | ++end; 1129 | else if (c != '\n') 1130 | { 1131 | cont = true; 1132 | --end; 1133 | } 1134 | } 1135 | lines.Add(description.Substring(start, end - start)); 1136 | if (cont) 1137 | { 1138 | lines[lines.Count - 1] += "-"; 1139 | } 1140 | start = end; 1141 | if (start < description.Length && description[start] == '\n') 1142 | ++start; 1143 | } while (end < description.Length); 1144 | return lines; 1145 | } 1146 | 1147 | private static int GetLineEnd(int start, int length, string description) 1148 | { 1149 | int end = Math.Min(start + length, description.Length); 1150 | int sep = -1; 1151 | for (int i = start; i < end; ++i) 1152 | { 1153 | switch (description[i]) 1154 | { 1155 | case ' ': 1156 | case '\t': 1157 | case '\v': 1158 | case '-': 1159 | case ',': 1160 | case '.': 1161 | case ';': 1162 | sep = i; 1163 | break; 1164 | case '\n': 1165 | return i; 1166 | } 1167 | } 1168 | if (sep == -1 || end == description.Length) 1169 | return end; 1170 | return sep; 1171 | } 1172 | } 1173 | } 1174 | --------------------------------------------------------------------------------