Unity3D - Don't compile if package name doesn't exist - c#

Is it possible to "gray out" the code if it doesn't exist like we do in Platform Dependent compilation (ex: #If UNITY_EDITOR)?
For example I don't want the compiler to complain if the code doesn't exist in the project. Specifically, I want to "hide" "GoogleMobileAds.Api;" package, which I don't have in the project, but it may be in the future.

Preprocessors are not an invention of Unity but is a c# thing.
If there are no specific defines listed in the Platform dependent compilation first make sure that your packages don't bring their own custom defines. Photon PUN e.g. actually does bring own defines like PHOTON_UNITY_NETWORKING, PUN_2_OR_NEWER, etc. But that's totally up to the providers of such libraries.
You can see/check this in the Edit -> Project Settings -> Player Settings -> Other Settings -> Scripting Define Symbols
Scripting Define Symbols
Set custom compilation flags. For more details, see the documentation on Platform dependent compilation.
Then you can just invent your own ones and add them to your code like e.g. USE_GOOGLE, USE_FIREBASE, etc
#if USE_GOOGLE
// some Google API related stuff
#endif
and later once you actually have according package in your project add these defines to the Scripting Define Symbols mentioned above.
If you know the full qualified assembly name of one type contained in the optional package you are looking for you can use e.g.
[assembly: OptionalDependency("Namespace.SubNameSpace.TypeName", "YOUR_DEFINE_SYMBOL_HERE")]
which is well hidden in Unity.XRTools.Utils!
This will internally on compile time check if the type Namespace.SubNameSpace.TypeName exists and if so define YOUR_DEFINE_SYMBOL_HERE so you can again in your code wrap the optional stuff in
#if YOUR_DEFINE_SYMBOL_HERE
...
#else
...
#endif
Alternative
For packages you own/implemen yourself there is a way to do this kind of automatically I used in the past. The following script as soon as it exists in a project it automatically adds the given define to the PlayerSettings if it doesn't exists yet
#if !USE_GOOGLE
public static class UseGoogleDefineSetter
{
const string DEFINE = "USE_GOOGLE";
[InitializeOnLoadMethod]
private void Init()
{
// Get current defines
var currentDefinesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
// Split at ;
var defines = currentDefinesString.Split(';').ToList();
// check if defines already exist given define
if (!defines.Contains(DEFINE))
{
// if not add it at the end with a leading ; separator
currentDefinesString += $";{DEFINE}";
// write the new defines back to the PlayerSettings
// This will cause a recompilation of your scripts
PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, currentDefinesString);
}
}
}
#endif
Update Unity 2020+
The method was semi replaced by a better one operating on a list/array instead of an entire string which is expensive. And in the newest version also the build pipeline slightly changed.
So in newer Unity versions I would rather use something like e.g. (assuming 2020 is the oldest version you want to support/use)
#if !USE_GOOGLE
public static class UseGoogleDefineSetter
{
const string DEFINE = "USE_GOOGLE";
[InitializeOnLoadMethod]
private void Init()
{
EditorUtils.AddScriptingSymbol(DEFINE);
}
}
#endif
and to make it easy and general
public static class EditorUtils
{
#if UNITY_2021_2_OR_NEWER
private static NamedBuildTarget GetActiveNamedBuildTarget()
{
var buildTargetGroup = GetActiveBuildTargetGroup();
var namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup);
return namedBuildTarget;
}
#endif
private static BuildTargetGroup GetActiveBuildTargetGroup()
{
var buildTarget = EditorUserBuildSettings.activeBuildTarget;
var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
return buildTargetGroup;
}
public static void AddScriptingSymbol(string define)
{
#if UNITY_2021_2_OR_NEWER
var namedBuildTarget = GetActiveNamedBuildTarget();
PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget, out var defines);
#else
var buildTargetGroup = GetActiveBuildTargetGroup();
PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup, out var defines);
#endif
var defineList = defines.ToList();
if (!defineList.Contains(define))
{
defineList.Add(define);
}
#if UNITY_2021_2_OR_NEWER
PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, defineList.ToArray());
#else
PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defineList.ToArray());
#endif
}
}
Both scripts go of course either in a folder called Editor and/or in an assembly only compiled for the Unity Editor or need to be wrapped additionally in #if UNITY_EDITOR

Related

.NET Maui Custom Handlers not working, official docs is wrong?

I am making an app using .NET MAUI and I am trying to implement custom handlers for specific instances of controls (ex. some entries should use a custom handler I created). To achieve this I followed the official MS docs for this. The following is the setup they tell me to use:
1.First make a subclass of the Entry control:
using Microsoft.Maui.Controls;
namespace MyMauiApp
{
public class MyEntry : Entry
{
}
}
2.I then customize the EntryHandler to perform the desired modification to MyEntry instances:
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace MauiApp1
{
public partial class App : Application
{
public App()
{
InitializeComponent();
Microsoft.Maui.Handlers.EntryHandler.EntryMapper[nameof(IView.Background)] = (handler, view) =>
{
if (view is MyEntry)
{
#if __ANDROID__
handler.NativeView.SetBackgroundColor(Colors.Red.ToNative());
#elif __IOS__
handler.NativeView.BackgroundColor = Colors.Red.ToNative();
handler.NativeView.BorderStyle = UIKit.UITextBorderStyle.Line;
#elif WINDOWS
handler.NativeView.Background = Colors.Red.ToNative();
#endif
}
};
}
}
}
PROBLEM: This gives me the following error:
Severity Code Description Project File Line Suppression State
Error CS0021 Cannot apply indexing with [] to an expression of type
'IPropertyMapper<IEntry, EntryHandler>' MyMauiApp (net6.0-android),
MyMauiApp (net6.0-ios), MyMauiApp
(net6.0-windows10.0.19041) C:\Users\xxxxxx\source\repos\MyMauiApp\MyMauiApp\App.xaml.cs 24 Active
As I said I followed the docs completely but still this error. I have read that other people have this issue too. Can anyone help?
It seems some breaking changes have been made in this area by the means of this pr here and here.
From what it looks like this has been done so that you can cascade customizations in mappers with AppendToMapping and PrependToMapping or modify the whole mapping altogether with ModifyMapping.
Without explaining all the variations here, let's focus on your situation. This means that instead of this line Microsoft.Maui.Handlers.EntryHandler.EntryMapper[nameof(IView.Background)] = (handler, view) =>
You should now declare this as: Microsoft.Maui.Handlers.EntryHandler.EntryMapper.AppendToMapping(nameof(IView.Background), (handler, view) =>
Note that you should now add a ) on the closing bracket too, making the full code:
Microsoft.Maui.Handlers.EntryHandler.EntryMapper.AppendToMapping(nameof(IView.Background), (handler, view) =>
{
if (view is MyEntry)
{
#if __ANDROID__
handler.NativeView.SetBackgroundColor(Colors.Red.ToNative());
#elif __IOS__
handler.NativeView.BackgroundColor = Colors.Red.ToNative();
handler.NativeView.BorderStyle = UIKit.UITextBorderStyle.Line;
#elif WINDOWS
handler.NativeView.Background = Colors.Red.ToNative();
#endif
}
});
I will see if I can update the Docs here and there and hopefully this won't break again ;)
Edit: Updated the wiki page for this

Gradle plugin returning wrong message

I have created a custom gradle plugin - TaskConfigPlugin
Gradle Version: 1.12/2.0 (Can't upgrade as of now due to Org restrictions)
Java Version: 1.8
My Plugin code is as follows - TaskConfigPlugin.java >>
public class TaskConfigPlugin implements Plugin<Project> {
private String taskPrefix;
private Task mainTask;
#Override
public void apply(Project project) {
TaskConfigPluginExtension extension = project.getExtensions()
.create("taskOrder", TaskConfigPluginExtension.class);
this.taskPrefix = extension.getTaskPrefix(); //the taskPrefix fetched is null
this.mainTask = extension.getMainTask(); //mainTask fetched is null
configureTaskOrdering(project);
}
private void configureTaskOrdering(Project project){
// My Logic to order the task using 'dependsOn', 'mustRunAfter' etc
// It uses the 'taskPrefix' to fetch all matching tasks and then
// establish the relationship with the 'mainTask'
}
}
The extension object - TaskConfigPluginExtension.java >>
public class TaskConfigPluginExtension {
private String taskPrefix;
private Task mainTask;
// getters and setters for the above
}
I have added the plugin info in the META-INF/gradle-plugins/TaskConfigPlugin.properties
which contains the full package name of the Plugin.
The plugin is being consumed as follows in my gradle - mainConsumer.gradle
buildscript {
repositories {
flatDir name: 'libs', dirs: 'lib/'
}
dependencies {
classpath ':TaskConfigPlugin:1.0'
}
}
apply plugin: 'TaskConfigPlugin'
task mainConsumer
taskOrder {
taskPrefix = 'setup'
mainTask = mainConsumer
}
Now when I run my consuming gradle mainConsumer.gradle, I see that the taskPrefix and mainTask values fetched are NULL. I understand that they belong to the CONFIGURATION phase and hence the values cannot be fetched earlier. But I tried with the project.afterEvaluate option and that is also NOT working for me.
There are some solutions (hoping they work) in terms of Provider and Property but I can't use them since they have been introduced with higher gradle versions, I guess 4.+
So, is there some solution or workaround for me with gradle 1.12 to fetch the extension values in the CONFIGURATION phase itself ?
Appreciate your response.
Thanks in advance.

Set a class private string parameter with some value at build time in .net core 3.1 class library project

I have a class in my .NET Core 3.1 library project, where I want to initialize the field key at build time:
using System.Text;
namespace MyNamespace
{
public class Myclass
{
private string key;
....
}
}
I am publishing my library via simple publish command:
dotnet publish -c release -r linux-x64
I am exploring an option where I could pass some attribute during build which will be set in key property during compilation/build process.
I am also obfuscating my DLL as well after this process via some third party tool.
This DLL will then be embedded in an application where this class one method is being called via reflection (reflection and calling method is already done).
Using the Roslyn API you can parse and compile a source programatically. The question linked by vasil oreshenski gives you working examples. The missing piece is to get your desired value (somehow) and then embed it into the syntax tree before compilation.
First, to make our lives easier, create a custom attribute and mark the key field with it.
using System;
public class BuildTimeParameterAttribute : Attribute
{
}
namespace MyNamespace
{
public class Myclass
{
[BuildTimeParameter]
private const string key1 = "", key2 = "";
[BuildTimeParameter]
private const string key3 = "";
....
}
}
I also changed the field to be a const and added additional fields to test the behaviour. Now when you have a syntax tree from the RoslynAPI you can do this:
var parsedSyntaxTree = Parse(
source,
"",
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8));
// Get those values from commandline arguments or however you like.
var replacementValues = new Dictionary<string, string>
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
var root = (CompilationUnitSyntax) parsedSyntaxTree.GetRoot();
// Retrieve all fields marked with the BuildTimeParameterAttribute.
var fields = root.DescendantNodes().OfType<FieldDeclarationSyntax>().Where(
s => s.AttributeLists.SelectMany(a => a.Attributes)
.Any(a => a.Name.ToString() == "BuildTimeParameter"));
var newRoot = root.ReplaceNodes(
fields,
(_, field) =>
{
var variables = field.Declaration.Variables;
var newVariables = from variable in variables
let newValue = replacementValues[variable.Identifier.ValueText]
let newInitializer = variable.Initializer.WithValue(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal(newValue)))
select variable.WithInitializer(newInitializer);
return field.WithDeclaration(
field.Declaration.WithVariables(
SyntaxFactory.SeparatedList(newVariables)));
});
The procedure is a bit complicated, because a single field declaration can contain many variables (in our case the first field declaration contains key1 and key2).
Now you can create a compilation using the newRoot.SyntaxTree. Decompiling the resulting dll yields:
namespace MyNamespace
{
public class MyClass
{
[BuildTimeParameter]
private const string key1 = "value1";
[BuildTimeParameter]
private const string key2 = "value2";
[BuildTimeParameter]
private const string key3 = "value3";
}
}
The above code doesn't handle errors and will fail with an NRE if there is no initializer, so for example if you write private string key;. It will work with const, as they are required to have an initializer. Making this code production-worthy I leave for you.
Note
With .NET 5 this could be much more easily done using source generators. You could declare your class as partial and then generate a file with the key constant field declaration using a source generator.
I can think of two options:
1. Using external file for the key - You are trying to have inversion of control at build time.
Why hard coding it in the dll when you can use text file with the key in it and your class will read the key from the file. As part of the build process you can define command to create the file with the specified key in the expected location where your dll will read it.
The down side - you need to have the file everywhere you are going to use the dll.
If this is a problem you can add the file as embeded resource to the dll and replace the value as part of the build script - before the actual build of the csproject, this way you will produce dll with embbeded resource which will be synced with the physical file.
2. You can use the roslyn API to create dlls at runtime - this way you can create console application which will read the key from a file or as CMD argument and this application will produce a new dll file with the required class with the key embbeded in the class as property / field.
More info on Roslyn API - you can check this SO answer for complete example how to use the API to produce dll file
Just some thoughts - If this key is some sensitive information you will need different approach (hard coded in the dll is not an option because it can be extracted pretty easy with programs like .NET Reflector - obfuscation won't do much)
This solution is not sophisticated as the other presented, but it probably can achieve your goal with a minimal effort.
I'd use preprocessor directives:
using System.Text;
namespace MyNamespace
{
public class Myclass
{
#if MY_DEBUG_KEY
private string key = "debug";
#elif MY_RELEASE_KEY
private string key = "release";
#else
#error key value has not been set
#endif
}
}
Then you can edit your csproj adding thw following PropertyGroups:
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;MY_DEBUG_KEY</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>TRACE;MY_RELEASE_KEY</DefineConstants>
</PropertyGroup>
So if you'll compile using Debug configuration the key value will be "debug", if you'll compile with Release it will be "release" and if you are going to use a new configuration it won't compile remembering you that you need to set a value for your key.
And you will be able to do it by adding another #elif directive and a PropertyGroup setting in your csproj:
.cs:
#if MY_DEBUG_KEY
private const string key = "debug";
#elif MY_RELEASE_KEY
private const string key = "release";
#elif MY_OTHERCFG_KEY
private const string key = "another value";
#else
#error key value has not been set
#endif
.csproj:
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='OtherCfg|AnyCPU'">
<DefineConstants>TRACE;MY_OTHERCFG_KEY</DefineConstants>
</PropertyGroup>
The dead easiest way to do this would be to declare the class you want the variable in as partial. This ads a little overhead on the build management side, but the C# compiler does not allow or defining terms with values like a c++ style compiler
Then in the build system swap in a cs file with the defined value you want.
swap.cs
namespace MyNamespace
{
partial class MyClass
{
const string myspecialstring = "Super duper value";
}
}
then
MyProgramSource.cs
namespace MyNamespace
{
partial class MyClass
{
// Lots of awsome code
System.Diagnostics.Trace.Writeline($"{myspecialstring}"); //works no problem
}
}

How can I export my c# code logic (if-else-loops) in to text files (e.g XML) and later import it back and run?

I have these requirements coming from client every week for some new logic or verification. For which I have to code new logic (basically some if-else and loops) and launch a new build for him. I want to avoid it by simply coding my logic in visual studio then writing a utility to export it to XML or something and send it to client via e-mail. He just have to place this file in some appropriate folder and the application will behave considering this logic.
Please suggest some solutions. My platform is C# Asp.Net.
Thanks
Using .NET 4.6 and the NuGetPackage Microsoft.CodeAnalysis.Scripting you could implement a scripting engine to run your c# code residing in a textfile without building an assembly.
Install NuGet Package:
Install-Package Microsoft.CodeAnalysis.Scripting.CSharp
Implement TestClass with some basic C#-Code-Content:
class Program
{
static void Main(string[] args)
{
TestScript();
}
private static async void TestScript()
{
// Code snippet: a class with one string-property.
string codeContent = #" using System;
public class ScriptedClass
{
public string HelloWorld { get; set; }
public ScriptedClass()
{
HelloWorld = ""Hello Roslyn!"";
}
}
new ScriptedClass().HelloWorld";
// Instanciate CSharpScriptEngine
var engine = new CSharpScriptEngine();
// Execute code and return string property (HelloWorld)
var scriptingState = await engine.ExecuteAsync(codeContent);
// Print return value from CSharpScript
Console.WriteLine("Returned from CSharpScript: {0}", scriptingState.ReturnValue);
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
}
Implement a ScriptingEngine:
internal sealed class CSharpScriptEngine
{
public async Task<ScriptState<object>> ExecuteAsync(string codeContent)
{
// Add references from calling assembly
ScriptOptions options = ScriptOptions.Default.AddReferences(Assembly.GetExecutingAssembly());
// Run codeContent with given options
return await CSharpScript.RunAsync(codeContent, options);
}
}
Read ScriptCode from textfile:
So basically you could read some csharpcode from a textfile of your choice and run them on the fly:
private static async void TestScript()
{
// Read in script file
string codeContent = File.ReadAllText(#"C:\Temp\CSharpScriptTest.cs");
var engine = new CSharpScriptEngine();
// Run script
var scriptingState = await engine.ExecuteAsync(codeContent);
Console.WriteLine("Returned from CSharpScript: {0}", scriptingState.ReturnValue);
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
In case you are wondering how all of this works under the hood, Roslyn will create a so called submission from your script code. A submission is an in memory assembly containing the types generated around your script code, which can be identified among the assemblies in the current AppDomain by a ℛ prefix in the name.
The precise implementation details are not important here (though, for example, scriptcs heavily relies on understanding in detail how Roslyn works to provide its extra features), but it's important to know that submissions can be chained together. When they are chained, variables, methods or classes defined in an earlier submission are available to use in subsequent submissions, creating a feature of a C# REPL (read-evaluate-print loop).
C# and Visual Basic - Use Roslyn to Write a Live Code Analyzer for Your API
Hope it helps

Dynamically load assemblies in ASP.NET 5

I used to have some code which scanned the bin directory of my application for assemblies which weren't loaded in the AppDomain yet and loaded them. It basically looked like:
foreach (var assemblyPath in Directory.GetFiles("path\to\bin", "*.dll"))
{
var inspected = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
Assembly.Load(inspected.GetName());
}
I skipped the try/catch clauses, etc for brevity.
This allowed me to drop assemblies in the bin folder at run-time with implementations for certain interfaces and let the IoC container pick them up automatically. Now with the new Roslyn magic, there are no physical DLL's anymore when debugging. Is there any way to retrieve assembly names, project names or dependency names (in project.json) dynamically.
I guess I have to implement something like this example in the Entropy repo, but I don't know how to implement it for my scenario.
You can use the IAssemblyLoadContextAccessor interface to load ASP.NET 5 class library (.xproj) projects dynamically. The following example code works with Beta 4:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var assemblyLoadContextAccessor = app.ApplicationServices.GetService<IAssemblyLoadContextAccessor>();
var loadContext = assemblyLoadContextAccessor.Default;
var loadedAssembly = loadContext.Load("NameOfYourLibrary");
}
}
What you are looking for is ILibraryManager implementation which provides access to the complete graph of dependencies for the application. This is already flowed through the ASP.NET 5 DI system. So, you can reach out to it from there.
Sample usage can be found inside RoslynCompilationService.
I solved this issue partly using the ILibraryManager as suggested by #tugberk. I changed the approach a bit which dropped the need of scanning the bin folder for new assemblies. I just want all the loaded assemblies in the current AppDomain.
I injected an instance of the ILibraryManager interface in my type finder class and used the GetReferencingLibraries() method with the name of the core assembly, which is referenced by all the other assemblies in the application.
A sample implementation can be found here, where this is the important part:
public IEnumerable<Assembly> GetLoadedAssemblies()
{
return _libraryManager.GetReferencingLibraries(_coreAssemblyName.Name)
.SelectMany(info => info.Assemblies)
.Select(info => Assembly.Load(new AssemblyName(info.Name)));
}
For .net core users, here is my code for loading assemblies from a specific path. I had to use directives, as it's slightly different for .Net Framework and .Net Core.
In your class header you'll need to declare the using something similar to:
#if NET46
#else
using System.Runtime.Loader;
#endif
And in your function something similar to the following:
string assemblyPath = "c:\temp\assmebly.dll";
#if NET46
Assembly assembly = Assembly.LoadFrom(assemblyPath);
#else
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(assemblyPath);
#endif
Its not ASP.NET but it can be converted easily to asp.net.
bellow if function for loading an assembly, and invoke a method inside a class on that assembly.
private static FormCustomized loadLayout(global::System.String layoutFilename, global::System.String layoutNameSpace)
{
FormCustomized mainForm = default;
Type typeMainLayout = default;
FileInfo layoutFile;
layoutFile = new FileInfo(layoutFilename);
layoutFile.Refresh();
if (!layoutFile.Exists)
{
MessageBox.Show("Layout file not found. You need to reinstall the program");
return default;
}
try
{
Assembly assemblyRaw = Assembly.LoadFrom(layoutFilename);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(layoutFilename);
Type typeMainLayoutIni = assembly.GetType(layoutNameSpace + ".initializeLayoutClass");
Object iniClass = Activator.CreateInstance(typeMainLayoutIni, true);
MethodInfo methodInfo = typeMainLayoutIni.GetMethod("AssembliesToLoadAtStart");
enVars.assemblies = (Dictionary<string, Environment.environmentAssembliesClass>)methodInfo.Invoke(iniClass, default);
typeMainLayout = assembly.GetType(layoutNameSpace + ".mainAppLayoutForm");
mainForm = Activator.CreateInstance(typeMainLayout, enVars) as FormCustomized;
}
catch (Exception ex)
{
return default;
}
return default;
}

Categories

Resources