Migration X-Targeting

This commit is contained in:
Bryan Thornbury 2016-09-15 15:54:10 -07:00
parent b1c5cb76eb
commit 944eb1e787
8 changed files with 99 additions and 174 deletions

View file

@ -68,11 +68,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration
private void VerifyProject(IEnumerable<ProjectContext> projectContexts, string projectDirectory) private void VerifyProject(IEnumerable<ProjectContext> projectContexts, string projectDirectory)
{ {
if (projectContexts.Count() > 1)
{
MigrationErrorCodes.MIGRATE20011($"Multi-TFM projects currently not supported.").Throw();
}
if (!projectContexts.Any()) if (!projectContexts.Any())
{ {
MigrationErrorCodes.MIGRATE1013($"No projects found in {projectDirectory}").Throw(); MigrationErrorCodes.MIGRATE1013($"No projects found in {projectDirectory}").Throw();

View file

@ -3,4 +3,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")] [assembly:InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")]

View file

@ -13,9 +13,6 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{ {
public class MigrateScriptsRule : IMigrationRule public class MigrateScriptsRule : IMigrationRule
{ {
private static readonly string s_unixScriptExtension = ".sh";
private static readonly string s_windowsScriptExtension = ".cmd";
private readonly ITransformApplicator _transformApplicator; private readonly ITransformApplicator _transformApplicator;
public MigrateScriptsRule(ITransformApplicator transformApplicator = null) public MigrateScriptsRule(ITransformApplicator transformApplicator = null)
@ -41,79 +38,17 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
string scriptSetName) string scriptSetName)
{ {
var target = CreateTarget(csproj, scriptSetName); var target = CreateTarget(csproj, scriptSetName);
var count = 0;
foreach (var scriptCommand in scriptCommands) foreach (var scriptCommand in scriptCommands)
{ {
var scriptExtensionPropertyName = AddScriptExtension(propertyGroup, scriptCommand, $"{scriptSetName}_{++count}"); AddExec(target, FormatScriptCommand(scriptCommand));
AddExec(target, FormatScriptCommand(scriptCommand, scriptExtensionPropertyName));
} }
return target; return target;
} }
private string AddScriptExtension(ProjectPropertyGroupElement propertyGroup, string scriptCommandline, string scriptId) internal string FormatScriptCommand(string scriptCommandline)
{ {
var scriptArguments = CommandGrammar.Process( return ReplaceScriptVariables(scriptCommandline);
scriptCommandline,
(s) => null,
preserveSurroundingQuotes: false);
scriptArguments = scriptArguments.Where(argument => !string.IsNullOrEmpty(argument)).ToArray();
var scriptCommand = scriptArguments.First();
var propertyName = $"MigratedScriptExtension_{scriptId}";
var windowsScriptExtensionProperty = propertyGroup.AddProperty(propertyName,
s_windowsScriptExtension);
var unixScriptExtensionProperty = propertyGroup.AddProperty(propertyName,
s_unixScriptExtension);
windowsScriptExtensionProperty.Condition =
$" '$(OS)' == 'Windows_NT' and Exists('{scriptCommand}{s_windowsScriptExtension}') ";
unixScriptExtensionProperty.Condition =
$" '$(OS)' != 'Windows_NT' and Exists('{scriptCommand}{s_unixScriptExtension}') ";
return propertyName;
}
internal string FormatScriptCommand(string scriptCommandline, string scriptExtensionPropertyName)
{
var command = ReplaceScriptVariables(scriptCommandline);
command = AddScriptExtensionPropertyToCommandLine(command, scriptExtensionPropertyName);
return command;
}
internal string AddScriptExtensionPropertyToCommandLine(string scriptCommandline,
string scriptExtensionPropertyName)
{
var scriptArguments = CommandGrammar.Process(
scriptCommandline,
(s) => null,
preserveSurroundingQuotes: true);
scriptArguments = scriptArguments.Where(argument => !string.IsNullOrEmpty(argument)).ToArray();
var scriptCommand = scriptArguments.First();
var trimmedCommand = scriptCommand.Trim('\"').Trim('\'');
// Path.IsPathRooted only looks at paths conforming to the current os,
// we need to account for all things
if (!IsPathRootedForAnyOS(trimmedCommand))
{
scriptCommand = @".\" + scriptCommand;
}
if (scriptCommand.EndsWith("\"") || scriptCommand.EndsWith("'"))
{
var endChar = scriptCommand.Last();
scriptCommand = $"{scriptCommand.TrimEnd(endChar)}$({scriptExtensionPropertyName}){endChar}";
}
else
{
scriptCommand += $"$({scriptExtensionPropertyName})";
}
var command = string.Join(" ", new[] {scriptCommand}.Concat(scriptArguments.Skip(1)));
return command;
} }
internal string ReplaceScriptVariables(string scriptCommandline) internal string ReplaceScriptVariables(string scriptCommandline)
@ -165,6 +100,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
target.AfterTargets = targetHookInfo.TargetName; target.AfterTargets = targetHookInfo.TargetName;
} }
// Run Scripts After each inner build
target.Condition = " '$(IsCrossTargetingBuild)' != 'true' ";
return target; return target;
} }
@ -185,13 +123,13 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
private static Dictionary<string, string> ScriptVariableToMSBuildMap => new Dictionary<string, string> private static Dictionary<string, string> ScriptVariableToMSBuildMap => new Dictionary<string, string>
{ {
{ "compile:TargetFramework", null }, // TODO: Need Short framework name in CSProj
{ "compile:ResponseFile", null }, // Not migrated { "compile:ResponseFile", null }, // Not migrated
{ "compile:CompilerExitCode", null }, // Not migrated { "compile:CompilerExitCode", null }, // Not migrated
{ "compile:RuntimeOutputDir", null }, // Not migrated { "compile:RuntimeOutputDir", null }, // Not migrated
{ "compile:RuntimeIdentifier", null },// Not Migrated { "compile:RuntimeIdentifier", null },// Not Migrated
{ "publish:TargetFramework", null }, // TODO: Need Short framework name in CSProj { "compile:TargetFramework", "$(TargetFramework)" },
{ "publish:TargetFramework", "$(TargetFramework)" },
{ "publish:Runtime", "$(RuntimeIdentifier)" }, { "publish:Runtime", "$(RuntimeIdentifier)" },
{ "compile:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" }, { "compile:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" },

View file

@ -8,6 +8,8 @@ using System.Text;
using Microsoft.Build.Construction; using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectJsonMigration.Transforms; using Microsoft.DotNet.ProjectJsonMigration.Transforms;
using NuGet.Frameworks; using NuGet.Frameworks;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.DotNet.ProjectJsonMigration.Rules namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{ {
@ -15,17 +17,15 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
public class MigrateTFMRule : IMigrationRule public class MigrateTFMRule : IMigrationRule
{ {
private readonly ITransformApplicator _transformApplicator; private readonly ITransformApplicator _transformApplicator;
private readonly AddPropertyTransform<NuGetFramework>[] _transforms; private readonly AddPropertyTransform<IEnumerable<NuGetFramework>>[] _transforms;
public MigrateTFMRule(ITransformApplicator transformApplicator = null) public MigrateTFMRule(ITransformApplicator transformApplicator = null)
{ {
_transformApplicator = transformApplicator ?? new TransformApplicator(); _transformApplicator = transformApplicator ?? new TransformApplicator();
_transforms = new AddPropertyTransform<NuGetFramework>[] _transforms = new AddPropertyTransform<IEnumerable<NuGetFramework>>[]
{ {
OutputPathTransform, FrameworksTransform
FrameworkIdentifierTransform,
FrameworkVersionTransform
}; };
} }
@ -39,7 +39,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
foreach (var transform in _transforms) foreach (var transform in _transforms)
{ {
_transformApplicator.Execute( _transformApplicator.Execute(
transform.Transform(migrationRuleInputs.DefaultProjectContext.TargetFramework), transform.Transform(migrationRuleInputs.ProjectContexts.Select(p => p.TargetFramework)),
propertyGroup); propertyGroup);
} }
} }
@ -75,20 +75,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
return sb.ToString(); return sb.ToString();
} }
// TODO: When we have this inferred in the sdk targets, we won't need this private AddPropertyTransform<IEnumerable<NuGetFramework>> FrameworksTransform =>
private AddPropertyTransform<NuGetFramework> OutputPathTransform => new AddPropertyTransform<IEnumerable<NuGetFramework>>("TargetFrameworks",
new AddPropertyTransform<NuGetFramework>("OutputPath", frameworks => string.Join(";", frameworks.Select(f => f.GetShortFolderName())),
f => $"bin/$(Configuration)/{f.GetShortFolderName()}", frameworks => true);
f => true);
private AddPropertyTransform<NuGetFramework> FrameworkIdentifierTransform =>
new AddPropertyTransform<NuGetFramework>("TargetFrameworkIdentifier",
f => f.Framework,
f => true);
private AddPropertyTransform<NuGetFramework> FrameworkVersionTransform =>
new AddPropertyTransform<NuGetFramework>("TargetFrameworkVersion",
f => "v" + GetDisplayVersion(f.Version),
f => true);
} }
} }

View file

@ -16,6 +16,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
public class GivenThatIWantToMigrateScripts : TestBase public class GivenThatIWantToMigrateScripts : TestBase
{ {
[Theory] [Theory]
[InlineData("compile:TargetFramework", "$(TargetFramework)")]
[InlineData("publish:TargetFramework", "$(TargetFramework)")]
[InlineData("compile:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)")] [InlineData("compile:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)")]
[InlineData("compile:Configuration", "$(Configuration)")] [InlineData("compile:Configuration", "$(Configuration)")]
[InlineData("compile:OutputFile", "$(TargetPath)")] [InlineData("compile:OutputFile", "$(TargetPath)")]
@ -37,12 +39,10 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
} }
[Theory] [Theory]
[InlineData("compile:TargetFramework")]
[InlineData("compile:ResponseFile")] [InlineData("compile:ResponseFile")]
[InlineData("compile:CompilerExitCode")] [InlineData("compile:CompilerExitCode")]
[InlineData("compile:RuntimeOutputDir")] [InlineData("compile:RuntimeOutputDir")]
[InlineData("compile:RuntimeIdentifier")] [InlineData("compile:RuntimeIdentifier")]
[InlineData("publish:TargetFramework")]
public void Formatting_script_commands_throws_when_variable_is_unsupported(string unsupportedVariable) public void Formatting_script_commands_throws_when_variable_is_unsupported(string unsupportedVariable)
{ {
var scriptMigrationRule = new MigrateScriptsRule(); var scriptMigrationRule = new MigrateScriptsRule();
@ -122,7 +122,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
var scriptMigrationRule = new MigrateScriptsRule(); var scriptMigrationRule = new MigrateScriptsRule();
ProjectRootElement mockProj = ProjectRootElement.Create(); ProjectRootElement mockProj = ProjectRootElement.Create();
var commands = new[] { "compile:FullTargetFramework", "compile:Configuration"}; var commands = new[] { "%compile:FullTargetFramework%", "%compile:Configuration%"};
var target = scriptMigrationRule.MigrateScriptSet(mockProj, mockProj.AddPropertyGroup(), commands, scriptName); var target = scriptMigrationRule.MigrateScriptSet(mockProj, mockProj.AddPropertyGroup(), commands, scriptName);
target.Tasks.Count().Should().Be(commands.Length); target.Tasks.Count().Should().Be(commands.Length);
@ -130,82 +130,30 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
foreach (var task in target.Tasks) foreach (var task in target.Tasks)
{ {
var taskCommand = task.GetParameter("Command"); var taskCommand = task.GetParameter("Command");
Console.WriteLine("TASK: " + taskCommand);
var commandIndex = Array.IndexOf(commands, taskCommand); var commandIndex = Array.IndexOf(commands, taskCommand);
commandIndex.Should().Be(-1, "Expected command array elements to be replaced by appropriate msbuild properties"); commandIndex.Should().Be(-1, "Expected command array elements to be replaced by appropriate msbuild properties");
} }
} }
[Theory]
[InlineData("precompile")]
[InlineData("postcompile")]
[InlineData("prepublish")]
[InlineData("postpublish")]
public void Migrated_ScriptSet_has_two_MigratedScriptExtensionProperties_for_each_script(string scriptName)
{
var scriptMigrationRule = new MigrateScriptsRule();
ProjectRootElement mockProj = ProjectRootElement.Create();
var commands = new string[] {"compile:FullTargetFramework", "compile:Configuration"};
var propertyGroup = mockProj.AddPropertyGroup();
var target = scriptMigrationRule.MigrateScriptSet(mockProj, propertyGroup, commands,
scriptName);
Console.WriteLine(string.Join(";", propertyGroup.Properties.Select(n => n.Name)));
propertyGroup.Properties.Count().Should().Be(commands.Length * 2);
var count = 0;
foreach (var command in commands)
{
count += 1;
var scriptExtensionProperties =
propertyGroup.Properties.Where(p => p.Name.Contains($"MigratedScriptExtension_{scriptName}_{count}")).ToArray();
scriptExtensionProperties.All(p => p.Value == ".sh" || p.Value == ".cmd").Should().BeTrue();
scriptExtensionProperties.Count().Should().Be(2);
}
}
[Theory]
[InlineData("echo", ".\\echo$(MigratedScriptExtension_1)")]
[InlineData("echo hello world", ".\\echo$(MigratedScriptExtension_1) hello world")]
[InlineData("\"echo\"", ".\\\"echo$(MigratedScriptExtension_1)\"")]
[InlineData("\"echo space\"", ".\\\"echo space$(MigratedScriptExtension_1)\"")]
[InlineData("\"echo space\" other args", ".\\\"echo space$(MigratedScriptExtension_1)\" other args")]
[InlineData("\"echo space\" \"other space\"", ".\\\"echo space$(MigratedScriptExtension_1)\" \"other space\"")]
public void Migrated_ScriptSet_has_ScriptExtension_added_to_script_command(string scriptCommandline, string expectedOutputCommand)
{
var scriptMigrationRule = new MigrateScriptsRule();
var formattedCommand = scriptMigrationRule.AddScriptExtensionPropertyToCommandLine(scriptCommandline,
"MigratedScriptExtension_1");
formattedCommand.Should().Be(expectedOutputCommand);
}
[Theory]
[InlineData("echo", @".\echo")]
[InlineData("/usr/echo", "/usr/echo")]
[InlineData(@"C:\usr\echo", @"C:\usr\echo")]
[InlineData("\"echo\"", @".\""echo")]
[InlineData("\"/usr/echo\"", @"""/usr/echo")]
[InlineData(@"""C:\usr\echo", @"""C:\usr\echo")]
public void Migrated_ScriptSet_has_dotSlash_prepended_when_command_is_not_rooted(string scriptCommandline,
string expectedOutputCommandPrefix)
{
var scriptMigrationRule = new MigrateScriptsRule();
var formattedCommand = scriptMigrationRule.FormatScriptCommand(scriptCommandline,
"MigratedScriptExtension_1");
formattedCommand.Should().StartWith(expectedOutputCommandPrefix);
}
[Fact] [Fact]
public void Formatting_script_commands_replaces_unknown_variables_with_MSBuild_Property_for_environment_variable_support() public void Formatting_script_commands_replaces_unknown_variables_with_MSBuild_Property_for_environment_variable_support()
{ {
var scriptMigrationRule = new MigrateScriptsRule(); var scriptMigrationRule = new MigrateScriptsRule();
scriptMigrationRule.ReplaceScriptVariables($"%UnknownVariable%").Should().Be("$(UnknownVariable)"); scriptMigrationRule.ReplaceScriptVariables($"%UnknownVariable%").Should().Be("$(UnknownVariable)");
} }
[Fact]
public void Migrating_scripts_creates_target_with_IsCrossTargettingBuild_not_equal_true_Condition()
{
var scriptMigrationRule = new MigrateScriptsRule();
ProjectRootElement mockProj = ProjectRootElement.Create();
var commands = new[] { "compile:FullTargetFramework", "compile:Configuration"};
var target = scriptMigrationRule.MigrateScriptSet(mockProj, mockProj.AddPropertyGroup(), commands, "prepublish");
target.Condition.Should().Be(" '$(IsCrossTargetingBuild)' != 'true' ");
}
} }
} }

View file

@ -16,7 +16,47 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
public class GivenThatIWantToMigrateProjectFramework : TestBase public class GivenThatIWantToMigrateProjectFramework : TestBase
{ {
[Fact] [Fact]
public void Migrating_netcoreapp_project_Populates_TargetFrameworkIdentifier_and_TargetFrameworkVersion() public void Migrating_netcoreapp_project_Does_not_populate_TargetFrameworkIdentifier_and_TargetFrameworkVersion()
{
var testDirectory = Temp.CreateDirectory().Path;
var testPJ = new ProjectJsonBuilder(TestAssetsManager)
.FromTestAssetBase("TestAppWithRuntimeOptions")
.WithCustomProperty("buildOptions", new Dictionary<string, string>
{
{ "emitEntryPoint", "false" }
})
.SaveToDisk(testDirectory);
var projectContext = ProjectContext.Create(testDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10);
var mockProj = ProjectRootElement.Create();
var testSettings = new MigrationSettings(testDirectory, testDirectory, "1.0.0", mockProj);
var testInputs = new MigrationRuleInputs(new[] { projectContext }, mockProj, mockProj.AddItemGroup(), mockProj.AddPropertyGroup());
new MigrateTFMRule().Apply(testSettings, testInputs);
mockProj.Properties.Count(p => p.Name == "TargetFrameworkIdentifier").Should().Be(0);
mockProj.Properties.Count(p => p.Name == "TargetFrameworkVersion").Should().Be(0);
}
public void Migrating_MultiTFM_project_Populates_TargetFrameworks_with_short_tfms()
{
var testDirectory = Temp.CreateDirectory().Path;
var testPJ = new ProjectJsonBuilder(TestAssetsManager)
.FromTestAssetBase("TestLibraryWithMultipleFrameworks")
.SaveToDisk(testDirectory);
var projectContext = ProjectContext.Create(testDirectory, FrameworkConstants.CommonFrameworks.NetCoreApp10);
var mockProj = ProjectRootElement.Create();
var testSettings = new MigrationSettings(testDirectory, testDirectory, "1.0.0", mockProj);
var testInputs = new MigrationRuleInputs(new[] { projectContext }, mockProj, mockProj.AddItemGroup(), mockProj.AddPropertyGroup());
new MigrateTFMRule().Apply(testSettings, testInputs);
mockProj.Properties.Count(p => p.Name == "TargetFrameworks").Should().Be(1);
mockProj.Properties.First(p => p.Name == "TargetFrameworks").Value.Should().Be("net20;net35;net40;net461;netstandard1.5");
}
public void Migrating_Single_TFM_project_Populates_TargetFrameworks_with_short_tfm()
{ {
var testDirectory = Temp.CreateDirectory().Path; var testDirectory = Temp.CreateDirectory().Path;
var testPJ = new ProjectJsonBuilder(TestAssetsManager) var testPJ = new ProjectJsonBuilder(TestAssetsManager)
@ -35,10 +75,8 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Tests
var testInputs = new MigrationRuleInputs(new[] { projectContext }, mockProj, mockProj.AddItemGroup(), mockProj.AddPropertyGroup()); var testInputs = new MigrationRuleInputs(new[] { projectContext }, mockProj, mockProj.AddItemGroup(), mockProj.AddPropertyGroup());
new MigrateTFMRule().Apply(testSettings, testInputs); new MigrateTFMRule().Apply(testSettings, testInputs);
mockProj.Properties.Count(p => p.Name == "TargetFrameworkIdentifier").Should().Be(1); mockProj.Properties.Count(p => p.Name == "TargetFrameworks").Should().Be(1);
mockProj.Properties.Count(p => p.Name == "TargetFrameworkVersion").Should().Be(1); mockProj.Properties.First(p => p.Name == "TargetFrameworks").Value.Should().Be("netcoreapp1.0");
mockProj.Properties.First(p => p.Name == "TargetFrameworkIdentifier").Value.Should().Be(".NETCoreApp");
mockProj.Properties.First(p => p.Name == "TargetFrameworkVersion").Value.Should().Be("v1.0");
} }
} }
} }

View file

@ -2,7 +2,7 @@
"version": "1.0.0-*", "version": "1.0.0-*",
"buildOptions": { "buildOptions": {
"copyToOutput": ["MSBuild.exe", "MSBuild.exe.config"], "copyToOutput": ["MSBuild.exe", "MSBuild.exe.config"],
"keyFile": "../../tools/test_key.snk", "keyFile": "../../tools/test_key.snk"
}, },
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
@ -21,9 +21,6 @@
}, },
"Microsoft.DotNet.Cli.Utils": { "Microsoft.DotNet.Cli.Utils": {
"target": "project" "target": "project"
},
"dotnet": {
"target":"project"
} }
}, },

View file

@ -70,6 +70,26 @@ namespace Microsoft.DotNet.Migration.Tests
outputsIdentical.Should().BeTrue(); outputsIdentical.Should().BeTrue();
} }
[Theory]
// TODO: Enable this when X-Targeting is in
// [InlineData("TestLibraryWithMultipleFrameworks")]
public void It_migrates_projects_with_multiple_TFMs()
{
var projectDirectory =
TestAssetsManager.CreateTestInstance(projectName, callingMethod: "i").WithLockFiles().Path;
var outputComparisonData = BuildProjectJsonMigrateBuildMSBuild(projectDirectory);
var outputsIdentical =
outputComparisonData.ProjectJsonBuildOutputs.SetEquals(outputComparisonData.MSBuildBuildOutputs);
if (!outputsIdentical)
{
OutputDiagnostics(outputComparisonData);
}
outputsIdentical.Should().BeTrue();
}
[Theory] [Theory]
[InlineData("TestAppWithLibrary/TestLibrary")] [InlineData("TestAppWithLibrary/TestLibrary")]
[InlineData("TestLibraryWithAnalyzer")] [InlineData("TestLibraryWithAnalyzer")]