diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc0790b --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +!.Documentation +!Documentation~ +**/.vs +**/.vscode/ +**/Library/ +**/Logs/ +**/obj/ +**/Temp/ +*.app +*.csproj +*.idea +*.rsp +*.sln +*.suo +*.swp +*.user +*.userprefs +*.VC.* +*~ +.build_script/** +.DS_Store +.idea/ +.npmrc +.vs/ +artifacts/** +build.bat.meta +build.sh.meta +build/* +build/** +node_modules/** +npm-debug.log + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..696d110 --- /dev/null +++ b/.npmignore @@ -0,0 +1,19 @@ +artifacts/** +build/** +.build_script/** +node_modules/** +Documentation/ApiDocs/** +Documentation~/ApiDocs/** +.DS_Store +.npmrc +.npmignore +.gitignore +CONTRIBUTING.md +CONTRIBUTING.md.meta +QAReport.md +QAReport.md.meta +.gitlab-ci.yml +build.sh +build.sh.meta +build.bat +build.bat.meta diff --git a/.yamato/promotion.yml b/.yamato/promotion.yml new file mode 100644 index 0000000..4a49465 --- /dev/null +++ b/.yamato/promotion.yml @@ -0,0 +1,76 @@ +test_editors: + - version: 2019.1 +test_platforms: + - name: win + type: Unity::VM + image: package-ci/win10:stable + flavor: b1.large +--- +{% for editor in test_editors %} +{% for platform in test_platforms %} +promotion_test_{{ platform.name }}_{{ editor.version }}: + name : Promotion Test {{ editor.version }} on {{ platform.name }} + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor}} + variables: + UPMCI_PROMOTION: 1 + commands: + - npm install upm-ci-utils@latest -g --registry https://api.bintray.com/npm/unity/unity-npm + - upm-ci package test --unity-version {{ editor.version }} + artifacts: + logs: + paths: + - "upm-ci~/test-results/**/*" + dependencies: + - .yamato/upm-ci.yml#pack +{% endfor %} +{% endfor %} + +promotion_test_trigger: + name: Promotion Tests Trigger + agent: + type: Unity::VM + image: package-ci/win10:stable + flavor: b1.large + artifacts: + logs: + paths: + - "upm-ci~/test-results/**/*" + packages: + paths: + - "upm-ci~/packages/**/*" + dependencies: +{% for editor in test_editors %} +{% for platform in test_platforms %} + - .yamato/promotion.yml#promotion_test_{{platform.name}}_{{editor.version}} +{% endfor %} +{% endfor %} + +promote: + name: Promote to Production + agent: + type: Unity::VM + image: package-ci/win10:stable + flavor: b1.large + variables: + UPMCI_PROMOTION: 1 + commands: + - npm install upm-ci-utils@latest -g --registry https://api.bintray.com/npm/unity/unity-npm + - upm-ci package promote + triggers: + tags: + only: + - /^(r|R)elease-\d+\.\d+\.\d+(-preview(\.\d+)?)?$/ + artifacts: + artifacts: + paths: + - "upm-ci~/packages/*.tgz" + dependencies: + - .yamato/upm-ci.yml#pack +{% for editor in test_editors %} +{% for platform in test_platforms %} + - .yamato/promotion.yml#promotion_test_{{ platform.name }}_{{ editor.version }} +{% endfor %} +{% endfor %} diff --git a/.yamato/upm-ci.yml b/.yamato/upm-ci.yml new file mode 100644 index 0000000..dbc6fea --- /dev/null +++ b/.yamato/upm-ci.yml @@ -0,0 +1,98 @@ +test_editors: + - version: 2019.1 + - version: trunk +test_platforms: + - name: win + type: Unity::VM + image: package-ci/win10:stable + flavor: b1.large + - name: mac + type: Unity::VM::osx + image: buildfarm/mac:stable + flavor: m1.mac +--- +pack: + name: Pack + agent: + type: Unity::VM + image: package-ci/win10:stable + flavor: b1.large + commands: + - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm + - upm-ci package pack + artifacts: + packages: + paths: + - "upm-ci~/**/*" + +{% for editor in test_editors %} +{% for platform in test_platforms %} +test_{{ platform.name }}_{{ editor.version }}: + name : Test {{ editor.version }} on {{ platform.name }} + agent: + type: {{ platform.type }} + image: {{ platform.image }} + flavor: {{ platform.flavor}} + commands: + - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm + - upm-ci package test --unity-version {{ editor.version }} + artifacts: + logs: + paths: + - "upm-ci~/test-results/**/*" + dependencies: + - .yamato/upm-ci.yml#pack +{% endfor %} +{% endfor %} + +test_trigger: + name: Tests Trigger + agent: + type: Unity::VM + image: package-ci/win10:stable + flavor: b1.large + commands: + - dir + triggers: + branches: + only: + - "/.*/" + artifacts: + logs: + paths: + - "upm-ci~/test-results/**/*" + packages: + paths: + - "upm-ci~/packages/**/*" + dependencies: + - .yamato/upm-ci.yml#pack + {% for editor in test_editors %} + {% for platform in test_platforms %} + - .yamato/upm-ci.yml#test_{{platform.name}}_{{editor.version}} + {% endfor %} + {% endfor %} + +publish: + name: Publish to Internal Registry + agent: + type: Unity::VM + image: package-ci/win10:stable + flavor: b1.large + commands: + - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm + - upm-ci package publish + triggers: + tags: + only: + - /^(r|R)(c|C)-\d+\.\d+\.\d+(-preview(\.\d+)?)?$/ + artifacts: + artifacts: + paths: + - "upm-ci~/packages/*.tgz" + dependencies: + - .yamato/upm-ci.yml#pack + {% for editor in test_editors %} + {% for platform in test_platforms %} + - .yamato/upm-ci.yml#test_{{ platform.name }}_{{ editor.version }} + {% endfor %} + {% endfor %} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cab2b17 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog +All notable changes to this package will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2019-06-18 + +### This is the first release of *Unity Package \com.unity.cli-config-manager*. + diff --git a/CHANGELOG.md.meta b/CHANGELOG.md.meta new file mode 100644 index 0000000..f7c836f --- /dev/null +++ b/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c40c27602d86e06479ad33f303af8cae +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..a0870d5 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,8 @@ +# PR review process + +- Any PR must have an entry in the corresponding changelog in a separate commit (CHANGELOG.MD file) +- Changelog follow these guidelines: https://github.com/Unity-Technologies/PostProcessing/blob/v2/CHANGELOG.md +- Each release branch (2018.1, 2018.2...) have a unique Changelog file +- when backporting, don't backport the changelog commit but update the branch changelog manually +- (optional) add reviewver from doc team +- Any more complex description of a change with future need to go in a release note file \ No newline at end of file diff --git a/CONTRIBUTING.meta b/CONTRIBUTING.meta new file mode 100644 index 0000000..b0cb44a --- /dev/null +++ b/CONTRIBUTING.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6ece49ca4446a774aadf283ac8662948 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md new file mode 100644 index 0000000..720ba4b --- /dev/null +++ b/CONTRIBUTIONS.md @@ -0,0 +1,9 @@ +# Contributions + +## If you are interested in contributing, here are some ground rules: +* 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. + +## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) +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. + +## Once you have a change ready following these ground rules. Simply make a pull request in Github \ No newline at end of file diff --git a/CONTRIBUTIONS.md.meta b/CONTRIBUTIONS.md.meta new file mode 100644 index 0000000..01877ae --- /dev/null +++ b/CONTRIBUTIONS.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a688a57ce4f57274ca0c41b40d884dbc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..b082d6f --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: df99bd7f40d74334193ecb56f9a4cdcd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/CliConfigManager.cs b/Editor/CliConfigManager.cs new file mode 100644 index 0000000..54a7e91 --- /dev/null +++ b/Editor/CliConfigManager.cs @@ -0,0 +1,241 @@ +using NDesk.Options; +using System; +#if UNITY_EDITOR +using UnityEditor; +#if OCULUS_SDK +using Unity.XR.Oculus; +#endif +#endif +using System.Text.RegularExpressions; + +using UnityEngine; +using UnityEngine.Rendering; + +namespace com.unity.cliconfigmanager +{ + public class CliConfigManager + { + private readonly Regex customArgRegex = new Regex("-([^=]*)=", RegexOptions.Compiled); + private readonly PlatformSettings platformSettings = new PlatformSettings(); + + public void ConfigureFromCmdlineArgs() + { +#if UNITY_EDITOR + ParseCommandLineArgs(); + ConfigureSettings(); +#endif + } +#if UNITY_EDITOR + private void ParseCommandLineArgs() + { + var args = Environment.GetCommandLineArgs(); + EnsureOptionsLowerCased(args); + var optionSet = DefineOptionSet(); + var unParsedArgs = optionSet.Parse(args); + platformSettings.SerializeToAsset(); + } + + private void EnsureOptionsLowerCased(string[] args) + { + for (var i = 0; i < args.Length; i++) + { + if (customArgRegex.IsMatch(args[i])) + { + args[i] = customArgRegex.Replace(args[i], customArgRegex.Matches(args[i])[0].ToString().ToLower()); + } + } + } + + private void ConfigureSettings() + { + // Setup all-inclusive player settings + ConfigureCrossplatformSettings(); + + // If Android, setup Android player settings + if (platformSettings.BuildTarget == BuildTarget.Android) + { + ConfigureAndroidSettings(); + } + + // If iOS, setup iOS player settings + if (platformSettings.BuildTarget == BuildTarget.iOS) + { + ConfigureIosSettings(); + } + + if (!string.IsNullOrEmpty(platformSettings.XrTarget)) + { + var xrConfigurator = new XrConfigurator(platformSettings); + xrConfigurator.ConfigureXr(); + } + } + + + + private void ConfigureIosSettings() + { + PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.iOS, string.Format("com.unity3d.{0}", PlayerSettings.productName)); + PlayerSettings.iOS.appleDeveloperTeamID = platformSettings.AppleDeveloperTeamId; + PlayerSettings.iOS.appleEnableAutomaticSigning = false; + PlayerSettings.iOS.iOSManualProvisioningProfileID = platformSettings.IOsProvisioningProfileId; + PlayerSettings.iOS.iOSManualProvisioningProfileType = ProvisioningProfileType.Development; + } + + private void ConfigureCrossplatformSettings() + { + PlayerSettings.virtualRealitySupported = false; + + if (platformSettings.PlayerGraphicsApi != GraphicsDeviceType.Null) + { + PlayerSettings.SetUseDefaultGraphicsAPIs(platformSettings.BuildTarget, false); + PlayerSettings.SetGraphicsAPIs(platformSettings.BuildTarget, new[] {platformSettings.PlayerGraphicsApi}); + } + + PlayerSettings.colorSpace = platformSettings.ColorSpace; + + PlayerSettings.SetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup, + platformSettings.ScriptingImplementation); + } + + private void ConfigureAndroidSettings() + { + EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; + PlayerSettings.Android.minSdkVersion = platformSettings.MinimumAndroidSdkVersion; + PlayerSettings.Android.targetSdkVersion = platformSettings.TargetAndroidSdkVersion; + + // If the user has specified AndroidArchitecture.ARMv7, but not specified ScriptingImplementation.Mono2x, or has incorrectly specified ScriptingImplementation.IL2CPP (not supported + // with mono), then set to AndroidArchitecture.ARMv7 so we're in a compatible configuration state. + if (platformSettings.AndroidTargetArchitecture == AndroidArchitecture.ARMv7 && + platformSettings.ScriptingImplementation != ScriptingImplementation.Mono2x) + { + platformSettings.ScriptingImplementation = ScriptingImplementation.Mono2x; + PlayerSettings.SetScriptingBackend(EditorUserBuildSettings.selectedBuildTargetGroup, + platformSettings.ScriptingImplementation); + } + + // If the user has specified mono scripting backend, but not specified AndroidArchitecture.ARMv7, or has incorrectly specified AndroidArchitecture.ARM64 (not supported + // with mono), then set to AndroidArchitecture.ARMv7 so we're in a compatible configuration state. + if (platformSettings.ScriptingImplementation == ScriptingImplementation.Mono2x && + platformSettings.AndroidTargetArchitecture != AndroidArchitecture.ARMv7) + { + platformSettings.AndroidTargetArchitecture = AndroidArchitecture.ARMv7; + } + PlayerSettings.Android.targetArchitectures = platformSettings.AndroidTargetArchitecture; + } + + private OptionSet DefineOptionSet() + { + var optionsSet = new OptionSet(); + optionsSet.Add("scriptingbackend=", + "Scripting backend to use. IL2CPP is default. Values: IL2CPP, Mono", ParseScriptingBackend); + optionsSet.Add("simulationmode=", + "Enable Simulation modes for Windows MR in Editor. Values: \r\n\"HoloLens\"\r\n\"WindowsMR\"\r\n\"Remoting\"", + simMode => platformSettings.SimulationMode = simMode); + optionsSet.Add("enabledxrtarget|enabledxrtargets=", + "XR target to enable in player settings. Values: " + + "\r\n\"Oculus\"\r\n\"OpenVR\"\r\n\"cardboard\"\r\n\"daydream\"\r\n\"MockHMD\"\r\n\"OculusXRSDK\"\r\n\"MagicLeapXRSDK\"\r\n\"WindowsMRXRSDK\"", + xrTarget => platformSettings.XrTarget = xrTarget); + optionsSet.Add("playergraphicsapi=", "Graphics API based on GraphicsDeviceType.", + graphicsDeviceType => + platformSettings.PlayerGraphicsApi = TryParse(graphicsDeviceType)); + optionsSet.Add("colorspace=", "Linear or Gamma color space.", + colorSpace => platformSettings.ColorSpace = TryParse(colorSpace)); +#if OCULUS_SDK + optionsSet.Add("stereorenderingpath=", "Stereo rendering path to enable. SinglePass is default", TryParseOculusXrSdkSrm); +#else + optionsSet.Add("stereorenderingpath=", "Stereo rendering path to enable. SinglePass is default", TryParseLegacyVrSrm); +#endif + optionsSet.Add("mtRendering", + "Enable or disable multithreaded rendering. Enabled is default. Use option to enable, or use option and append '-' to disable.", + option => platformSettings.MtRendering = option != null); + optionsSet.Add("graphicsJobs", + "Enable graphics jobs rendering. Disabled is default. Use option to enable, or use option and append '-' to disable.", + option => platformSettings.GraphicsJobs = option != null); + optionsSet.Add("minimumandroidsdkversion=", "Minimum Android SDK Version to use.", + minAndroidSdkVersion => platformSettings.MinimumAndroidSdkVersion = + TryParse(minAndroidSdkVersion)); + optionsSet.Add("targetandroidsdkversion=", "Target Android SDK Version to use.", + trgtAndroidSdkVersion => platformSettings.TargetAndroidSdkVersion = + TryParse(trgtAndroidSdkVersion)); + optionsSet.Add("appledeveloperteamid=", + "Apple Developer Team ID. Use for deployment and running tests on iOS device.", + appleTeamId => platformSettings.AppleDeveloperTeamId = appleTeamId); + optionsSet.Add("iosprovisioningprofileid=", + "iOS Provisioning Profile ID. Use for deployment and running tests on iOS device.", + id => platformSettings.IOsProvisioningProfileId = id); + optionsSet.Add("xrsdkrev=", + "revision id of the xrsdk being used.", + id => platformSettings.XrsdkRevision = id); + optionsSet.Add("xrsdkrevdate=", + "revision date of the xrsdk being used.", + revDate => platformSettings.XrsdkRevisionDate = revDate); + optionsSet.Add("xrsdkbranch=", + "branch of the xrsdk being used.", + xrsdkbranch => platformSettings.XrsdkBranch = xrsdkbranch); + optionsSet.Add("deviceruntimeversion=", + "runtime version of the device we're running on.", + deviceruntime => platformSettings.DeviceRuntimeVersion = string.Format("deviceruntimeversion|{0}",deviceruntime)); + optionsSet.Add("ffrlevel=", + "ffr level we're running at", + ffrlevel => platformSettings.FfrLevel = string.Format("ffrlevel|{0}", ffrlevel)); + optionsSet.Add("testsrev=", + "revision id of the tests being used.", + id => platformSettings.TestsRevision = string.Format("testsrev|{0}", id)); + optionsSet.Add("testsrevdate=", + "revision date of the tests being used.", + revDate => platformSettings.TestsRevisionDate = string.Format("testsrevdate|{0}", revDate)); + optionsSet.Add("testsbranch=", + "branch of the tests repo being used.", + testsbranch => platformSettings.TestsBranch = string.Format("testsbranch|{0}", testsbranch)); + optionsSet.Add("androidtargetarchitecture=", + "Android Target Architecture to use.", + androidtargetarchitecture => platformSettings.AndroidTargetArchitecture = TryParse(androidtargetarchitecture)); + return optionsSet; + } + + public static T TryParse(string stringToParse) + { + T thisType; + try + { + thisType = (T) Enum.Parse(typeof(T), stringToParse); + } + catch (Exception e) + { + throw new ArgumentException(($"Couldn't cast {stringToParse} to {typeof(T)}"), e); + } + + return thisType; + } +#if OCULUS_SDK + private void TryParseOculusXrSdkSrm(string srm) + { + if (platformSettings.BuildTarget == BuildTarget.Android) + { + platformSettings.StereoRenderingModeAndroid = TryParse(srm); + } + else + { + platformSettings.StereoRenderingModeDesktop = TryParse(srm); + } + } +#else + + private void TryParseLegacyVrSrm(string srm) + { + platformSettings.StereoRenderingPath = TryParse(srm); + } + +#endif + + private void ParseScriptingBackend(string scriptingBackend) + { + var sb = scriptingBackend.ToLower(); + if (sb.Equals("mono")) + { + platformSettings.ScriptingImplementation = ScriptingImplementation.Mono2x; + } + } +#endif + } +} diff --git a/Editor/CliConfigManager.cs.meta b/Editor/CliConfigManager.cs.meta new file mode 100644 index 0000000..6e552fa --- /dev/null +++ b/Editor/CliConfigManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7ffc29c2e7e96342b962d230d5980f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Options.cs b/Editor/Options.cs new file mode 100644 index 0000000..231510b --- /dev/null +++ b/Editor/Options.cs @@ -0,0 +1,1173 @@ +// +// Options.cs +// +// Authors: +// Jonathan Pryor +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// +// 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: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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. +// + +// Compile With: +// gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll +// gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll +// +// The LINQ version just changes the implementation of +// OptionSet.Parse(IEnumerable), and confers no semantic changes. + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// NDesk.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is a delegate that is +// invoked when the format string is matched. +// +// Option format strings: +// Regex-like BNF Grammar: +// name: .+ +// type: [=:] +// sep: ( [^{}]+ | '{' .+ '}' )? +// aliases: ( name type sep ) ( '|' name type sep )* +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. `=' or `:' need only be defined on one +// alias, but if they are provided on more than one they must be consistent. +// +// Each alias portion may also end with a "key/value separator", which is used +// to split option values if the option accepts > 1 value. If not specified, +// it defaults to '=' and ':'. If specified, it can be any character except +// '{' and '}' OR the *string* between '{' and '}'. If no separator should be +// used (i.e. the separate values should be distinct arguments), then "{}" +// should be used as the separator. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The current option requires a value (i.e. not a Option type of ':') +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options are a single character +// - at most one of the bundled options accepts a value, and the value +// provided starts from the next character to the end of the string. +// +// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' +// as '-Dname=value'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported and encouraged: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +#if TEST +using NDesk.Options; +#endif + +namespace NDesk.Options +{ + + public class OptionValueCollection : IList, IList + { + + List values = new List(); + OptionContext c; + + internal OptionValueCollection(OptionContext c) + { + this.c = c; + } + + #region ICollection + void ICollection.CopyTo(Array array, int index) { (values as ICollection).CopyTo(array, index); } + bool ICollection.IsSynchronized { get { return (values as ICollection).IsSynchronized; } } + object ICollection.SyncRoot { get { return (values as ICollection).SyncRoot; } } + #endregion + + #region ICollection + public void Add(string item) { values.Add(item); } + public void Clear() { values.Clear(); } + public bool Contains(string item) { return values.Contains(item); } + public void CopyTo(string[] array, int arrayIndex) { values.CopyTo(array, arrayIndex); } + public bool Remove(string item) { return values.Remove(item); } + public int Count { get { return values.Count; } } + public bool IsReadOnly { get { return false; } } + #endregion + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator() { return values.GetEnumerator(); } + #endregion + + #region IEnumerable + public IEnumerator GetEnumerator() { return values.GetEnumerator(); } + #endregion + + #region IList + int IList.Add(object value) { return (values as IList).Add(value); } + bool IList.Contains(object value) { return (values as IList).Contains(value); } + int IList.IndexOf(object value) { return (values as IList).IndexOf(value); } + void IList.Insert(int index, object value) { (values as IList).Insert(index, value); } + void IList.Remove(object value) { (values as IList).Remove(value); } + void IList.RemoveAt(int index) { (values as IList).RemoveAt(index); } + bool IList.IsFixedSize { get { return false; } } + object IList.this[int index] { get { return this[index]; } set { (values as IList)[index] = value; } } + #endregion + + #region IList + public int IndexOf(string item) { return values.IndexOf(item); } + public void Insert(int index, string item) { values.Insert(index, item); } + public void RemoveAt(int index) { values.RemoveAt(index); } + + private void AssertValid(int index) + { + if (c.Option == null) + throw new InvalidOperationException("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException("index"); + if (c.Option.OptionValueType == OptionValueType.Required && + index >= values.Count) + throw new OptionException(string.Format( + c.OptionSet.MessageLocalizer("Missing required value for option '{0}'."), c.OptionName), + c.OptionName); + } + + public string this[int index] + { + get + { + AssertValid(index); + return index >= values.Count ? null : values[index]; + } + set + { + values[index] = value; + } + } + #endregion + + public List ToList() + { + return new List(values); + } + + public string[] ToArray() + { + return values.ToArray(); + } + + public override string ToString() + { + return string.Join(", ", values.ToArray()); + } + } + + public class OptionContext + { + private Option option; + private string name; + private int index; + private OptionSet set; + private OptionValueCollection c; + + public OptionContext(OptionSet set) + { + this.set = set; + this.c = new OptionValueCollection(this); + } + + public Option Option + { + get { return option; } + set { option = value; } + } + + public string OptionName + { + get { return name; } + set { name = value; } + } + + public int OptionIndex + { + get { return index; } + set { index = value; } + } + + public OptionSet OptionSet + { + get { return set; } + } + + public OptionValueCollection OptionValues + { + get { return c; } + } + } + + public enum OptionValueType + { + None, + Optional, + Required, + } + + public abstract class Option + { + string prototype, description; + string[] names; + OptionValueType type; + int count; + string[] separators; + + protected Option(string prototype, string description) + : this(prototype, description, 1) + { + } + + protected Option(string prototype, string description, int maxValueCount) + { + if (prototype == null) + throw new ArgumentNullException("prototype"); + if (prototype.Length == 0) + throw new ArgumentException("Cannot be the empty string.", "prototype"); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException("maxValueCount"); + + this.prototype = prototype; + this.names = prototype.Split('|'); + this.description = description; + this.count = maxValueCount; + this.type = ParsePrototype(); + + if (this.count == 0 && type != OptionValueType.None) + throw new ArgumentException( + "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + + "OptionValueType.Optional.", + "maxValueCount"); + if (this.type == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException( + string.Format("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + "maxValueCount"); + if (Array.IndexOf(names, "<>") >= 0 && + ((names.Length == 1 && this.type != OptionValueType.None) || + (names.Length > 1 && this.MaxValueCount > 1))) + throw new ArgumentException( + "The default option handler '<>' cannot require values.", + "prototype"); + } + + public string Prototype { get { return prototype; } } + public string Description { get { return description; } } + public OptionValueType OptionValueType { get { return type; } } + public int MaxValueCount { get { return count; } } + + public string[] GetNames() + { + return (string[])names.Clone(); + } + + public string[] GetValueSeparators() + { + if (separators == null) + return new string[0]; + return (string[])separators.Clone(); + } + + protected static T Parse(string value, OptionContext c) + { + TypeConverter conv = TypeDescriptor.GetConverter(typeof(T)); + T t = default(T); + try + { + if (value != null) + t = (T)conv.ConvertFromString(value); + } + catch (Exception e) + { + throw new OptionException( + string.Format( + c.OptionSet.MessageLocalizer("Could not convert string `{0}' to type {1} for option `{2}'."), + value, typeof(T).Name, c.OptionName), + c.OptionName, e); + } + return t; + } + + internal string[] Names { get { return names; } } + internal string[] ValueSeparators { get { return separators; } } + + static readonly char[] NameTerminator = new char[] { '=', ':' }; + + private OptionValueType ParsePrototype() + { + char type = '\0'; + List seps = new List(); + for (int i = 0; i < names.Length; ++i) + { + string name = names[i]; + if (name.Length == 0) + throw new ArgumentException("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny(NameTerminator); + if (end == -1) + continue; + names[i] = name.Substring(0, end); + if (type == '\0' || type == name[end]) + type = name[end]; + else + throw new ArgumentException( + string.Format("Conflicting option types: '{0}' vs. '{1}'.", type, name[end]), + "prototype"); + AddSeparators(name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (count <= 1 && seps.Count != 0) + throw new ArgumentException( + string.Format("Cannot provide key/value separators for Options taking {0} value(s).", count), + "prototype"); + if (count > 1) + { + if (seps.Count == 0) + this.separators = new string[] { ":", "=" }; + else if (seps.Count == 1 && seps[0].Length == 0) + this.separators = null; + else + this.separators = seps.ToArray(); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + private static void AddSeparators(string name, int end, ICollection seps) + { + int start = -1; + for (int i = end + 1; i < name.Length; ++i) + { + switch (name[i]) + { + case '{': + if (start != -1) + throw new ArgumentException( + string.Format("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + start = i + 1; + break; + case '}': + if (start == -1) + throw new ArgumentException( + string.Format("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + seps.Add(name.Substring(start, i - start)); + start = -1; + break; + default: + if (start == -1) + seps.Add(name[i].ToString()); + break; + } + } + if (start != -1) + throw new ArgumentException( + string.Format("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + } + + public void Invoke(OptionContext c) + { + OnParseComplete(c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear(); + } + + protected abstract void OnParseComplete(OptionContext c); + + public override string ToString() + { + return Prototype; + } + } + + [Serializable] + public class OptionException : Exception + { + private string option; + + public OptionException() + { + } + + public OptionException(string message, string optionName) + : base(message) + { + this.option = optionName; + } + + public OptionException(string message, string optionName, Exception innerException) + : base(message, innerException) + { + this.option = optionName; + } + + protected OptionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + this.option = info.GetString("OptionName"); + } + + public string OptionName + { + get { return this.option; } + } + + [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("OptionName", option); + } + } + + public delegate void OptionAction(TKey key, TValue value); + + public class OptionSet : KeyedCollection + { + public OptionSet() + : this(delegate (string f) { return f; }) + { + } + + public OptionSet(Converter localizer) + { + this.localizer = localizer; + } + + Converter localizer; + + public Converter MessageLocalizer + { + get { return localizer; } + } + + protected override string GetKeyForItem(Option item) + { + if (item == null) + throw new ArgumentNullException("option"); + if (item.Names != null && item.Names.Length > 0) + return item.Names[0]; + // This should never happen, as it's invalid for Option to be + // constructed w/o any names. + throw new InvalidOperationException("Option has no names!"); + } + + [Obsolete("Use KeyedCollection.this[string]")] + protected Option GetOptionForName(string option) + { + if (option == null) + throw new ArgumentNullException("option"); + try + { + return base[option]; + } + catch (KeyNotFoundException) + { + return null; + } + } + + protected override void InsertItem(int index, Option item) + { + base.InsertItem(index, item); + AddImpl(item); + } + + protected override void RemoveItem(int index) + { + base.RemoveItem(index); + Option p = Items[index]; + // KeyedCollection.RemoveItem() handles the 0th item + for (int i = 1; i < p.Names.Length; ++i) + { + Dictionary.Remove(p.Names[i]); + } + } + + protected override void SetItem(int index, Option item) + { + base.SetItem(index, item); + RemoveItem(index); + AddImpl(item); + } + + private void AddImpl(Option option) + { + if (option == null) + throw new ArgumentNullException("option"); + List added = new List(option.Names.Length); + try + { + // KeyedCollection.InsertItem/SetItem handle the 0th name. + for (int i = 1; i < option.Names.Length; ++i) + { + Dictionary.Add(option.Names[i], option); + added.Add(option.Names[i]); + } + } + catch (Exception) + { + foreach (string name in added) + Dictionary.Remove(name); + throw; + } + } + + public new OptionSet Add(Option option) + { + base.Add(option); + return this; + } + + sealed class ActionOption : Option + { + Action action; + + public ActionOption(string prototype, string description, int count, Action action) + : base(prototype, description, count) + { + if (action == null) + throw new ArgumentNullException("action"); + this.action = action; + } + + protected override void OnParseComplete(OptionContext c) + { + action(c.OptionValues); + } + } + + public OptionSet Add(string prototype, Action action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + Option p = new ActionOption(prototype, description, 1, + delegate (OptionValueCollection v) { action(v[0]); }); + base.Add(p); + return this; + } + + public OptionSet Add(string prototype, OptionAction action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, OptionAction action) + { + if (action == null) + throw new ArgumentNullException("action"); + Option p = new ActionOption(prototype, description, 2, + delegate (OptionValueCollection v) { action(v[0], v[1]); }); + base.Add(p); + return this; + } + + sealed class ActionOption : Option + { + Action action; + + public ActionOption(string prototype, string description, Action action) + : base(prototype, description, 1) + { + if (action == null) + throw new ArgumentNullException("action"); + this.action = action; + } + + protected override void OnParseComplete(OptionContext c) + { + action(Parse(c.OptionValues[0], c)); + } + } + + sealed class ActionOption : Option + { + OptionAction action; + + public ActionOption(string prototype, string description, OptionAction action) + : base(prototype, description, 2) + { + if (action == null) + throw new ArgumentNullException("action"); + this.action = action; + } + + protected override void OnParseComplete(OptionContext c) + { + action( + Parse(c.OptionValues[0], c), + Parse(c.OptionValues[1], c)); + } + } + + public OptionSet Add(string prototype, Action action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, Action action) + { + return Add(new ActionOption(prototype, description, action)); + } + + public OptionSet Add(string prototype, OptionAction action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, OptionAction action) + { + return Add(new ActionOption(prototype, description, action)); + } + + protected virtual OptionContext CreateOptionContext() + { + return new OptionContext(this); + } + +#if LINQ + public List Parse (IEnumerable arguments) + { + bool process = true; + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + var def = GetOptionForName ("<>"); + var unprocessed = + from argument in arguments + where ++c.OptionIndex >= 0 && (process || def != null) + ? process + ? argument == "--" + ? (process = false) + : !Parse (argument, c) + ? def != null + ? Unprocessed (null, def, c, argument) + : true + : false + : def != null + ? Unprocessed (null, def, c, argument) + : true + : true + select argument; + List r = unprocessed.ToList (); + if (c.Option != null) + c.Option.Invoke (c); + return r; + } +#else + public List Parse(IEnumerable arguments) + { + OptionContext c = CreateOptionContext(); + c.OptionIndex = -1; + bool process = true; + List unprocessed = new List(); + Option def = Contains("<>") ? this["<>"] : null; + foreach (string argument in arguments) + { + ++c.OptionIndex; + if (argument == "-") + { + process = false; + continue; + } + if (!process) + { + Unprocessed(unprocessed, def, c, argument); + continue; + } + if (!Parse(argument, c)) + Unprocessed(unprocessed, def, c, argument); + } + if (c.Option != null) + c.Option.Invoke(c); + return unprocessed; + } +#endif + + private static bool Unprocessed(ICollection extra, Option def, OptionContext c, string argument) + { + if (def == null) + { + extra.Add(argument); + return false; + } + c.OptionValues.Add(argument); + c.Option = def; + c.Option.Invoke(c); + return false; + } + + private readonly Regex ValueOption = new Regex( + @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); + + protected bool GetOptionParts(string argument, out string flag, out string name, out string sep, out string value) + { + if (argument == null) + throw new ArgumentNullException("argument"); + + flag = name = sep = value = null; + Match m = ValueOption.Match(argument); + if (!m.Success) + { + return false; + } + flag = m.Groups["flag"].Value; + name = m.Groups["name"].Value; + if (m.Groups["sep"].Success && m.Groups["value"].Success) + { + sep = m.Groups["sep"].Value; + value = m.Groups["value"].Value; + } + return true; + } + + protected virtual bool Parse(string argument, OptionContext c) + { + if (c.Option != null) + { + ParseValue(argument, c); + return true; + } + + string f, n, s, v; + if (!GetOptionParts(argument, out f, out n, out s, out v)) + return false; + + Option p; + if (Contains(n)) + { + p = this[n]; + c.OptionName = f + n; + c.Option = p; + switch (p.OptionValueType) + { + case OptionValueType.None: + c.OptionValues.Add(n); + c.Option.Invoke(c); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + ParseValue(v, c); + break; + } + return true; + } + // no match; is it a bool option? + if (ParseBool(argument, n, c)) + return true; + // is it a bundled option? + if (ParseBundledValue(f, string.Concat(n + s + v), c)) + return true; + + return false; + } + + private void ParseValue(string option, OptionContext c) + { + if (option != null) + foreach (string o in c.Option.ValueSeparators != null + ? option.Split(c.Option.ValueSeparators, StringSplitOptions.None) + : new string[] { option }) + { + c.OptionValues.Add(o); + } + if (c.OptionValues.Count == c.Option.MaxValueCount || + c.Option.OptionValueType == OptionValueType.Optional) + c.Option.Invoke(c); + else if (c.OptionValues.Count > c.Option.MaxValueCount) + { + throw new OptionException(localizer(string.Format( + "Error: Found {0} option values when expecting {1}.", + c.OptionValues.Count, c.Option.MaxValueCount)), + c.OptionName); + } + } + + private bool ParseBool(string option, string n, OptionContext c) + { + Option p; + string rn; + if (n.Length >= 1 && (n[n.Length - 1] == '+' || n[n.Length - 1] == '-') && + Contains((rn = n.Substring(0, n.Length - 1)))) + { + p = this[rn]; + string v = n[n.Length - 1] == '+' ? option : null; + c.OptionName = option; + c.Option = p; + c.OptionValues.Add(v); + p.Invoke(c); + return true; + } + return false; + } + + private bool ParseBundledValue(string f, string n, OptionContext c) + { + if (f != "-") + return false; + for (int i = 0; i < n.Length; ++i) + { + Option p; + string opt = f + n[i].ToString(); + string rn = n[i].ToString(); + if (!Contains(rn)) + { + if (i == 0) + return false; + throw new OptionException(string.Format(localizer( + "Cannot bundle unregistered option '{0}'."), opt), opt); + } + p = this[rn]; + switch (p.OptionValueType) + { + case OptionValueType.None: + Invoke(c, opt, n, p); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + { + string v = n.Substring(i + 1); + c.Option = p; + c.OptionName = opt; + ParseValue(v.Length != 0 ? v : null, c); + return true; + } + default: + throw new InvalidOperationException("Unknown OptionValueType: " + p.OptionValueType); + } + } + return true; + } + + private static void Invoke(OptionContext c, string name, string value, Option option) + { + c.OptionName = name; + c.Option = option; + c.OptionValues.Add(value); + option.Invoke(c); + } + + private const int OptionWidth = 29; + + public void WriteOptionDescriptions(TextWriter o) + { + foreach (Option p in this) + { + int written = 0; + if (!WriteOptionPrototype(o, p, ref written)) + continue; + + if (written < OptionWidth) + o.Write(new string(' ', OptionWidth - written)); + else + { + o.WriteLine(); + o.Write(new string(' ', OptionWidth)); + } + + List lines = GetLines(localizer(GetDescription(p.Description))); + o.WriteLine(lines[0]); + string prefix = new string(' ', OptionWidth + 2); + for (int i = 1; i < lines.Count; ++i) + { + o.Write(prefix); + o.WriteLine(lines[i]); + } + } + } + + bool WriteOptionPrototype(TextWriter o, Option p, ref int written) + { + string[] names = p.Names; + + int i = GetNextOptionIndex(names, 0); + if (i == names.Length) + return false; + + if (names[i].Length == 1) + { + Write(o, ref written, " -"); + Write(o, ref written, names[0]); + } + else + { + Write(o, ref written, " --"); + Write(o, ref written, names[0]); + } + + for (i = GetNextOptionIndex(names, i + 1); + i < names.Length; i = GetNextOptionIndex(names, i + 1)) + { + Write(o, ref written, ", "); + Write(o, ref written, names[i].Length == 1 ? "-" : "--"); + Write(o, ref written, names[i]); + } + + if (p.OptionValueType == OptionValueType.Optional || + p.OptionValueType == OptionValueType.Required) + { + if (p.OptionValueType == OptionValueType.Optional) + { + Write(o, ref written, localizer("[")); + } + Write(o, ref written, localizer("=" + GetArgumentName(0, p.MaxValueCount, p.Description))); + string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 + ? p.ValueSeparators[0] + : " "; + for (int c = 1; c < p.MaxValueCount; ++c) + { + Write(o, ref written, localizer(sep + GetArgumentName(c, p.MaxValueCount, p.Description))); + } + if (p.OptionValueType == OptionValueType.Optional) + { + Write(o, ref written, localizer("]")); + } + } + return true; + } + + static int GetNextOptionIndex(string[] names, int i) + { + while (i < names.Length && names[i] == "<>") + { + ++i; + } + return i; + } + + static void Write(TextWriter o, ref int n, string s) + { + n += s.Length; + o.Write(s); + } + + private static string GetArgumentName(int index, int maxIndex, string description) + { + if (description == null) + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + string[] nameStart; + if (maxIndex == 1) + nameStart = new string[] { "{0:", "{" }; + else + nameStart = new string[] { "{" + index + ":" }; + for (int i = 0; i < nameStart.Length; ++i) + { + int start, j = 0; + do + { + start = description.IndexOf(nameStart[i], j); + } while (start >= 0 && j != 0 ? description[j++ - 1] == '{' : false); + if (start == -1) + continue; + int end = description.IndexOf("}", start); + if (end == -1) + continue; + return description.Substring(start + nameStart[i].Length, end - start - nameStart[i].Length); + } + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + } + + private static string GetDescription(string description) + { + if (description == null) + return string.Empty; + StringBuilder sb = new StringBuilder(description.Length); + int start = -1; + for (int i = 0; i < description.Length; ++i) + { + switch (description[i]) + { + case '{': + if (i == start) + { + sb.Append('{'); + start = -1; + } + else if (start < 0) + start = i + 1; + break; + case '}': + if (start < 0) + { + if ((i + 1) == description.Length || description[i + 1] != '}') + throw new InvalidOperationException("Invalid option description: " + description); + ++i; + sb.Append("}"); + } + else + { + sb.Append(description.Substring(start, i - start)); + start = -1; + } + break; + case ':': + if (start < 0) + goto default; + start = i + 1; + break; + default: + if (start < 0) + sb.Append(description[i]); + break; + } + } + return sb.ToString(); + } + + private static List GetLines(string description) + { + List lines = new List(); + if (string.IsNullOrEmpty(description)) + { + lines.Add(string.Empty); + return lines; + } + int length = 80 - OptionWidth - 2; + int start = 0, end; + do + { + end = GetLineEnd(start, length, description); + bool cont = false; + if (end < description.Length) + { + char c = description[end]; + if (c == '-' || (char.IsWhiteSpace(c) && c != '\n')) + ++end; + else if (c != '\n') + { + cont = true; + --end; + } + } + lines.Add(description.Substring(start, end - start)); + if (cont) + { + lines[lines.Count - 1] += "-"; + } + start = end; + if (start < description.Length && description[start] == '\n') + ++start; + } while (end < description.Length); + return lines; + } + + private static int GetLineEnd(int start, int length, string description) + { + int end = Math.Min(start + length, description.Length); + int sep = -1; + for (int i = start; i < end; ++i) + { + switch (description[i]) + { + case ' ': + case '\t': + case '\v': + case '-': + case ',': + case '.': + case ';': + sep = i; + break; + case '\n': + return i; + } + } + if (sep == -1 || end == description.Length) + return end; + return sep; + } + } +} diff --git a/Editor/Options.cs.meta b/Editor/Options.cs.meta new file mode 100644 index 0000000..e6da79b --- /dev/null +++ b/Editor/Options.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8cb9471cc7f53a4bac720225cbdb34c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PlatformSettings.cs b/Editor/PlatformSettings.cs new file mode 100644 index 0000000..2ca613f --- /dev/null +++ b/Editor/PlatformSettings.cs @@ -0,0 +1,319 @@ +#if OCULUS_SDK +using Unity.XR.Oculus; +#endif +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using com.unity.xr.test.runtimesettings; +#if UNITY_EDITOR +using UnityEditor; +using UnityEditor.PackageManager; +#if URP +using UnityEngine.Rendering.Universal; +#endif +#endif +using UnityEngine; +using UnityEngine.Rendering; +using PackageInfo = UnityEditor.PackageManager.PackageInfo; +#if ENABLE_VR +using UnityEngine.XR; +#endif + + +namespace com.unity.cliconfigmanager +{ + public class PlatformSettings + { +#if UNITY_EDITOR + public BuildTargetGroup BuildTargetGroup => EditorUserBuildSettings.selectedBuildTargetGroup; + public BuildTarget BuildTarget => EditorUserBuildSettings.activeBuildTarget; + + public string XrTarget; + public GraphicsDeviceType PlayerGraphicsApi; + +#if OCULUS_SDK + public OculusSettings.StereoRenderingModeDesktop StereoRenderingModeDesktop; + public OculusSettings.StereoRenderingModeAndroid StereoRenderingModeAndroid; +#else + public StereoRenderingPath StereoRenderingPath; +#endif + public bool MtRendering = true; + public bool GraphicsJobs; + public AndroidSdkVersions MinimumAndroidSdkVersion = AndroidSdkVersions.AndroidApiLevel24; + public AndroidSdkVersions TargetAndroidSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto; + public ScriptingImplementation ScriptingImplementation = ScriptingImplementation.IL2CPP; + public string AppleDeveloperTeamId; + public string IOsProvisioningProfileId; + public ColorSpace ColorSpace = ColorSpace.Gamma; + public string XrsdkRevision; + public string XrsdkRevisionDate; + public string XrsdkBranch; + public string TestsRevision; + public string TestsRevisionDate; + public string TestsBranch; + public string DeviceRuntimeVersion; + public string SimulationMode; + public string Username; + public string RenderPipeline; + public string FfrLevel; + public AndroidArchitecture AndroidTargetArchitecture = AndroidArchitecture.ARM64; + + private readonly string resourceDir = "Assets/Resources"; + private readonly string xrManagementPackageName = "com.unity.xr.management"; + private readonly string perfTestsPackageName = "xr.sdk.oculus.performancetests"; + private readonly string urpPackageName = "com.unity.render-pipelines.universal"; + private readonly string oculusXrSdkPackageName = "com.unity.xr.oculus"; + private readonly string hdrpPackageName = "com.unity.testing.hdrp"; + + private readonly Regex revisionValueRegex = new Regex("\"revision\": \"([a-f0-9]*)\"", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex majorMinorVersionValueRegex = new Regex("([0-9]*\\.[0-9]*\\.)", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + + public void SerializeToAsset() + { + var settingsAsset = ScriptableObject.CreateInstance(); + + settingsAsset.SimulationMode = SimulationMode; + settingsAsset.PlayerGraphicsApi = PlayerGraphicsApi.ToString(); + settingsAsset.MtRendering = MtRendering; + settingsAsset.GraphicsJobs = GraphicsJobs; + settingsAsset.ColorSpace = ColorSpace.ToString(); + settingsAsset.EnabledXrTarget = XrTarget; + settingsAsset.XrsdkRevision = GetOculusXrSdkPackageVersionInfo(); + settingsAsset.XrManagementRevision = GetXrManagementPackageVersionInfo(); + settingsAsset.UrpPackageVersionInfo = GetUrpPackageVersionInfo(); + settingsAsset.HdrpPackageVersionInfo = GetHdrpPackageVersionInfo(); + settingsAsset.PerfTestsPackageRevision = GetPerfTestsPackageVersionInfo(); + settingsAsset.DeviceRuntimeVersion = DeviceRuntimeVersion; + settingsAsset.Username = Username = Environment.UserName; + settingsAsset.FfrLevel = FfrLevel; + settingsAsset.TestsRevision = TestsRevision; + settingsAsset.TestsRevisionDate = TestsRevisionDate; + settingsAsset.TestsBranch = TestsBranch; + settingsAsset.AndroidTargetArchitecture = string.Format("AndroidTargetArchitecture|{0}", AndroidTargetArchitecture.ToString()); + settingsAsset.RenderPipeline = RenderPipeline = + $"renderpipeline|{(GraphicsSettings.renderPipelineAsset != null ? GraphicsSettings.renderPipelineAsset.name : "BuiltInRenderer")}"; + +#if URP + settingsAsset.AntiAliasing = GraphicsSettings.renderPipelineAsset != null + ? ((UniversalRenderPipelineAsset) GraphicsSettings.renderPipelineAsset).msaaSampleCount + : QualitySettings.antiAliasing; +#else + settingsAsset.AntiAliasing = QualitySettings.antiAliasing; +#endif + + +#if OCULUS_SDK + settingsAsset.StereoRenderingModeDesktop = StereoRenderingModeDesktop.ToString(); + settingsAsset.StereoRenderingModeAndroid = StereoRenderingModeAndroid.ToString(); +#if OCULUS_SDK_PERF + settingsAsset.PluginVersion = string.Format("OculusPluginVersion|{0}", OculusStats.PluginVersion); +#endif +#else + settingsAsset.StereoRenderingMode = GetXrStereoRenderingPathMapping(StereoRenderingPath).ToString(); +#endif + CreateAndSaveCurrentSettingsAsset(settingsAsset); + } + + private string GetXrManagementPackageVersionInfo() + { + string packageRevision = string.Empty; + + var listRequest = Client.List(true); + while (!listRequest.IsCompleted) + { + } + + if (listRequest.Result.Any(r => r.name.Equals(xrManagementPackageName))) + { + var xrManagementPckg = + listRequest.Result.First(r => r.name.Equals(xrManagementPackageName)); + + var revision = TryGetRevisionFromPackageJson(xrManagementPackageName) ?? "unavailable"; + var version = xrManagementPckg.version; + packageRevision = string.Format("XrManagementPackageName|{0}|XrManagementVersion|{1}|XrManagementRevision|{2}", xrManagementPackageName, version, revision); + } + + return packageRevision; + } + + private string GetUrpPackageVersionInfo() + { + string packageRevision = string.Empty; + + var listRequest = Client.List(true); + while (!listRequest.IsCompleted) + { + } + + if (listRequest.Result.Any(r => r.name.Equals(urpPackageName))) + { + var urpPckg = + listRequest.Result.First(r => r.name.Equals(urpPackageName)); + + var revision = TryGetRevisionFromPackageJson(urpPackageName) ?? "unavailable"; + var version = urpPckg.version; + packageRevision = string.Format("UrpPackageName|{0}|UrpVersion|{1}|UrpRevision|{2}", urpPackageName, version, revision); + } + + return packageRevision; + } + + private string GetHdrpPackageVersionInfo() + { + string packageRevision = string.Empty; + + var listRequest = Client.List(true); + while (!listRequest.IsCompleted) + { + } + + if (listRequest.Result.Any(r => r.name.Equals(hdrpPackageName))) + { + var urpPckg = + listRequest.Result.First(r => r.name.Equals(hdrpPackageName)); + + var revision = TryGetRevisionFromPackageJson(hdrpPackageName) ?? "unavailable"; + var version = urpPckg.version; + packageRevision = string.Format("HdrpPackageName|{0}|HdrpVersion|{1}|HdrpRevision|{2}", hdrpPackageName, version, revision); + } + + return packageRevision; + } + + private string GetPerfTestsPackageVersionInfo() + { + string packageRevision = string.Empty; + + var listRequest = Client.List(true); + while (!listRequest.IsCompleted) + { + } + + if (listRequest.Result.Any(r => r.name.Equals(perfTestsPackageName))) + { + var perfTestsPckg = + listRequest.Result.First(r => r.name.Equals(perfTestsPackageName)); + + var revision = TryGetRevisionFromPackageJson(perfTestsPackageName) ?? "unavailable"; + var version = perfTestsPckg.version; + packageRevision = string.Format("PerfTestsPackageName|{0}|PerfTestsVersion|{1}|PerfTestsRevision|{2}", perfTestsPackageName, version, revision); + } + + return packageRevision; + } + + private string GetOculusXrSdkPackageVersionInfo() + { + string packageRevision = string.Empty; + + var listRequest = Client.List(true); + while (!listRequest.IsCompleted) + { + } + + if (listRequest.Result.Any(r => r.name.Equals(oculusXrSdkPackageName))) + { + var oculusXrsdkPckg = + listRequest.Result.First(r => r.name.Equals(oculusXrSdkPackageName)); + + var version = oculusXrsdkPckg.version; + + // if XrsdkRevision is empty, then it wasn't passed in on the command line (which is + // usually going to be the case if we're running in tests at the PR level for Xrsdk package). + // In this case, we most likely are using a released package reference, so let's try to get + // the revision from the package.json. + if (string.IsNullOrEmpty(XrsdkRevision)) + { + XrsdkRevision = TryGetRevisionFromPackageJson(oculusXrSdkPackageName) ?? "unavailable"; + } + + // if XrsdkRevisionDate is empty, then it wasn't passed in on the command line (which is + // usually going to be the case if we're running in tests at the PR level for Xrsdk package). + // In this case, we most likely are using a released package reference, so let's try to get + // the revision date from the package manager api instead. + if (string.IsNullOrEmpty(XrsdkRevisionDate)) + { + TryGetXrsdkRevisionDate(oculusXrsdkPckg); + } + + // if XrsdkBranch is empty, then it wasn't passed in on the command line (which is + // usually going to be the case if we're running in tests at the PR level for Xrsdk package). + // In this case, we most likely are using a released package reference, so let's try to infer + // the branch from the major.minor version of the package via the package manager API + if (string.IsNullOrEmpty(XrsdkBranch)) + { + TryGetXrsdkBranch(oculusXrsdkPckg); + } + packageRevision = string.Format( + "XrSdkName|{0}|XrSdkVersion|{1}|XrSdkRevision|{2}|XrSdkRevisionDate|{3}|XrSdkBranch|{4}", + oculusXrSdkPackageName, + version, + XrsdkRevision, + XrsdkRevisionDate, + XrsdkBranch); + } + + return packageRevision; + } + + private void TryGetXrsdkBranch(PackageInfo oculusXrsdkPckg) + { + var matches = majorMinorVersionValueRegex.Matches(oculusXrsdkPckg.version); + XrsdkBranch = matches.Count > 0 ? string.Concat(matches[0].Groups[0].Value, "x") : "release"; + } + + private void TryGetXrsdkRevisionDate(PackageInfo oculusXrsdkPckg) + { +#if OCULUS_SDK + XrsdkRevisionDate = + oculusXrsdkPckg.datePublished != null ? + ((DateTime) oculusXrsdkPckg.datePublished).ToString("s", DateTimeFormatInfo.InvariantInfo) : "unavailable"; +#endif + } + + private string TryGetRevisionFromPackageJson(string packageName) + { + string revision = null; + var packageAsString = File.ReadAllText(string.Format("Packages/{0}/package.json",packageName)); + var matches = revisionValueRegex.Matches(packageAsString); + if (matches.Count > 0) + { + revision = matches[0].Groups[1].Value; + } + + return revision; + } + + private XRSettings.StereoRenderingMode GetXrStereoRenderingPathMapping(StereoRenderingPath stereoRenderingPath) + { + switch (stereoRenderingPath) + { + case StereoRenderingPath.SinglePass: + return XRSettings.StereoRenderingMode.SinglePass; + case StereoRenderingPath.MultiPass: + return XRSettings.StereoRenderingMode.MultiPass; + case StereoRenderingPath.Instancing: + return XRSettings.StereoRenderingMode.SinglePassInstanced; + default: + return XRSettings.StereoRenderingMode.SinglePassMultiview; + } + } + + private void CreateAndSaveCurrentSettingsAsset(CurrentSettings settingsAsset) + { + if (!System.IO.Directory.Exists(resourceDir)) + { + System.IO.Directory.CreateDirectory(resourceDir); + } + + AssetDatabase.CreateAsset(settingsAsset, resourceDir + "/settings.asset"); + AssetDatabase.SaveAssets(); + } +#endif + } +} \ No newline at end of file diff --git a/Editor/PlatformSettings.cs.meta b/Editor/PlatformSettings.cs.meta new file mode 100644 index 0000000..e1cba5b --- /dev/null +++ b/Editor/PlatformSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdf30ba0ce15b914984e73f7ce4e9bde +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/XrConfigurator.cs b/Editor/XrConfigurator.cs new file mode 100644 index 0000000..feb9a73 --- /dev/null +++ b/Editor/XrConfigurator.cs @@ -0,0 +1,164 @@ + +using System.IO; +#if UNITY_EDITOR +#if XR_SDK +using UnityEditor.XR.Management; +using System; +#endif +using UnityEditor; +#endif +using UnityEngine; +#if XR_SDK +using UnityEngine.XR.Management; +#endif +#if OCULUS_SDK +using Unity.XR.Oculus; +#endif +#if MOCKHMD_SDK +using Unity.XR.MockHMD; +#endif + +namespace com.unity.cliconfigmanager +{ + public class XrConfigurator + { +#if XR_SDK + private readonly string xrsdkTestXrSettingsPath = "Assets/XR/Settings/Test Settings.asset"; +#endif + + private readonly PlatformSettings platformSettings; + + public XrConfigurator(PlatformSettings platformSettings) + { + this.platformSettings = platformSettings; + } +#if UNITY_EDITOR + public void ConfigureXr() + { +#if XR_SDK + ConfigureXrSdk(); +#else + ConfigureLegacyVr(); +#endif + } + +#if XR_SDK + private void ConfigureXrSdk() + { + PlayerSettings.virtualRealitySupported = false; + + // Create our own test version of xr general settings. + var xrGeneralSettings = ScriptableObject.CreateInstance(); + var managerSettings = ScriptableObject.CreateInstance(); + var buildTargetSettings = ScriptableObject.CreateInstance(); + + xrGeneralSettings.Manager = managerSettings; + EnsureArgumentsNotNull(xrGeneralSettings, buildTargetSettings, managerSettings); +#if OCULUS_SDK + SetupLoader(xrGeneralSettings, buildTargetSettings, managerSettings); +#endif +#if MOCKHMD_SDK + PlayerSettings.stereoRenderingPath = platformSettings.StereoRenderingPath; + SetupLoader(xrGeneralSettings, buildTargetSettings, managerSettings); +#endif + +#if OCULUS_SDK + var settings = ConfigureOculusSettings(); +#endif + AssetDatabase.SaveAssets(); + + EditorBuildSettings.AddConfigObject(XRGeneralSettings.k_SettingsKey, buildTargetSettings, true); +#if OCULUS_SDK + EditorBuildSettings.AddConfigObject("Unity.XR.Oculus.Settings", settings, true); +#endif + } + +#if OCULUS_SDK + private OculusSettings ConfigureOculusSettings() + { + var settings = ScriptableObject.CreateInstance(); + if (settings == null) + { + throw new ArgumentNullException( + $"Tried to instantiate an instance of {typeof(OculusSettings).Name} but it is null."); + } + + AssetDatabase.AddObjectToAsset(settings, xrsdkTestXrSettingsPath); + + if (platformSettings.BuildTarget == BuildTarget.Android) + { + settings.m_StereoRenderingModeAndroid = platformSettings.StereoRenderingModeAndroid; + } + else + { + settings.m_StereoRenderingModeDesktop = platformSettings.StereoRenderingModeDesktop; + } + + return settings; + } +#endif + + private void SetupLoader(XRGeneralSettings xrGeneralSettings, + XRGeneralSettingsPerBuildTarget buildTargetSettings, + XRManagerSettings managerSettings) where T : XRLoader + { + var loader = ScriptableObject.CreateInstance(); + loader.name = loader.GetType().Name; + + if (loader == null) + { + throw new ArgumentNullException( + $"Tried to instantiate an instance of {typeof(T).Name}, but it is null."); + } + + xrGeneralSettings.Manager.loaders.Add(loader); + + buildTargetSettings.SetSettingsForBuildTarget(EditorUserBuildSettings.selectedBuildTargetGroup, + xrGeneralSettings); + + EnsureXrGeneralSettingsPathExists(xrsdkTestXrSettingsPath); + + AssetDatabase.CreateAsset(buildTargetSettings, xrsdkTestXrSettingsPath); + + AssetDatabase.AddObjectToAsset(xrGeneralSettings, xrsdkTestXrSettingsPath); + AssetDatabase.AddObjectToAsset(managerSettings, xrsdkTestXrSettingsPath); + AssetDatabase.AddObjectToAsset(loader, xrsdkTestXrSettingsPath); + } + + private void EnsureArgumentsNotNull(XRGeneralSettings xrGeneralSettings, + XRGeneralSettingsPerBuildTarget buildTargetSettings, XRManagerSettings managerSettings) + { + EnsureArgumentNotNull(xrGeneralSettings); + EnsureArgumentNotNull(buildTargetSettings); + EnsureArgumentNotNull(managerSettings); + } + + private void EnsureXrGeneralSettingsPathExists(string testXrGeneralSettingsPath) + { + var settingsPath = Path.GetDirectoryName(testXrGeneralSettingsPath); + if (!Directory.Exists(settingsPath)) + { + Directory.CreateDirectory(testXrGeneralSettingsPath); + } + } + + private static void EnsureArgumentNotNull(object arg) + { + if (arg == null) + { + throw new ArgumentNullException(nameof(arg)); + } + } +#else + private void ConfigureLegacyVr() + { + PlayerSettings.virtualRealitySupported = true; + PlayerSettings.stereoRenderingPath = platformSettings.StereoRenderingPath; + UnityEditorInternal.VR.VREditor.SetVREnabledDevicesOnTargetGroup(platformSettings.BuildTargetGroup, + new string[] {platformSettings.XrTarget}); + } +#endif +#endif + } + +} \ No newline at end of file diff --git a/Editor/XrConfigurator.cs.meta b/Editor/XrConfigurator.cs.meta new file mode 100644 index 0000000..042c3e8 --- /dev/null +++ b/Editor/XrConfigurator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15e73d1b900ad6c4680361ac7d5f9c4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/com.unity.cli-config-manager.asmdef b/Editor/com.unity.cli-config-manager.asmdef new file mode 100644 index 0000000..2dd6024 --- /dev/null +++ b/Editor/com.unity.cli-config-manager.asmdef @@ -0,0 +1,35 @@ +{ + "name": "com.unity.cli-config-manager", + "references": [ + "com.unity.xr.tests.runtimesettings", + "Unity.XR.Oculus", + "Unity.XR.Oculus.Editor", + "Unity.XR.Management", + "Unity.XR.Management.Editor", + "com.unity.xr.oculusperformanceutility", + "Unity.XR.MockHMD", + "Unity.RenderPipelines.Universal.Runtime" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.xr.oculus", + "expression": "0.0.1", + "define": "XR_SDK;OCULUS_SDK" + }, + { + "name": "com.unity.xr.mock-hmd", + "expression": "0.0.1", + "define": "XR_SDK;MOCKHMD_SDK" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/com.unity.cli-config-manager.asmdef.meta b/Editor/com.unity.cli-config-manager.asmdef.meta new file mode 100644 index 0000000..93e4eb4 --- /dev/null +++ b/Editor/com.unity.cli-config-manager.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f6cacf2273fa4a1488e781d84678972d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f6cecd2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,5 @@ +Copyright © 2018 Unity Technologies ApS + +Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). + +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. \ No newline at end of file diff --git a/LICENSE.md.meta b/LICENSE.md.meta new file mode 100644 index 0000000..4145f2d --- /dev/null +++ b/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f91a5571b0ad1ba47a5827b15a35f7d4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index baeb290..25a98cc 100644 --- a/README.md +++ b/README.md @@ -1 +1,64 @@ -# com.unity.cli-project-setup \ No newline at end of file +# com.unity.cli-config-manager +Provides a command line parser and options to set editor, build, player, and other Unity settings when running Unity from the command line + +# Methods +## public void ConfigureFromCmdlineArgs() + +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. + + +# Options +Options Recognized from `Environment.GetCommandLineArgs()` + +`-scriptingbackend=` + + IL2CPP is default. Values: IL2CPP, Mono + +`-simulationmode=` + + Enable Simulation modes for Windows MR in Editor. Values: HoloLens, WindowsMR, Remoting + +`-enabledxrtarget=` + + XR target to enable in player settings. Values: Oculus, OpenVR, cardboard, daydream, MockHMD, OculusXRSDK + +`-playergraphicsapi=` + + Graphics API based on GraphicsDeviceType. Values: Direct3D11, OpenGLES2, OpenGLES3, PlayStation4, XboxOne, Metal, OpenGLCore, Direct3D12, Switch, XboxOneD3D12 + +`-colorspace=` + + Linear or Gamma color space. Default is Gamma. Values: Linear, Gamma + +`-stereorenderingpath=` + + Stereo rendering path to enable. + Legacy VR Values: MultiPass, SinglePass, Instancing + Oculus XR SDK Desktop Values: MultiPass, SinglePassInstanced + Oculus XR SDK Android Values: MultiPass, MultiView + +`-mtRendering` + + Enable or disable multithreaded rendering. Enabled is default. + Append '-' to disable: -mtRendering- + +`-graphicsJobs` + + Enable graphics jobs rendering. Disabled is default. + Append '-' to disable: -graphicsJobs- + +`-minimumandroidsdkversion=` + + Minimum Android SDK Version (Integer) to use. + +`-targetandroidsdkversion=` + + Target Android SDK Version (Integer) to use. + +`-appleDeveloperTeamID=` + + Apple Developer Team ID. Use for deployment and running tests on iOS device. + +`-iOSProvisioningProfileID=` + + iOS Provisioning Profile ID. Use for deployment and running tests on iOS device. diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..26d97d4 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7b56f7a7e4292484596f45c2a16f1137 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Third Party Notices.md b/Third Party Notices.md new file mode 100644 index 0000000..b291503 --- /dev/null +++ b/Third Party Notices.md @@ -0,0 +1,15 @@ +This package contains third-party software components governed by the license(s) indicated below: + +Component Name: Chart.js + +License Type: MIT + +The MIT License (MIT) + +Copyright (c) 2018 Chart.js Contributors + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/Third Party Notices.md.meta b/Third Party Notices.md.meta new file mode 100644 index 0000000..0e0ed6b --- /dev/null +++ b/Third Party Notices.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6eb80bd4b8697a04ab42b66b20aea256 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..8d3444e --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "com.unity.cli-config-manager", + "displayName": "Unity CLI Configuration Manager", + "version": "0.1.0-preview", + "unity": "2018.3", + "description": "Provides a command line parser and options to set build, player, and other Unity settings when running Unity from the command line." +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..253a238 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 73884c072159e0a4f985b2428497b0a4 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: