public void Execute() { var xmlSchemaWeaver = new XmlSchemasWeaver(_moduleWeaver, _msCoreReferenceFinder); foreach (var catelTypeNode in _catelTypeNodeBuilder.CatelTypes) { try { if (!CatelVersionSupportsXmlSchemaManager(catelTypeNode)) { return; } xmlSchemaWeaver.Execute(catelTypeNode); } catch (Exception) { #if DEBUG System.Diagnostics.Debugger.Launch(); #endif var error = $"An error occurred while weaving type '{catelTypeNode.TypeDefinition.FullName}'"; FodyEnvironment.WriteError(error); } } }
private void ProcessProperty(CatelType catelType, CatelTypeProperty modelProperty, CustomAttribute exposeAttribute) { var modelName = modelProperty.Name; var viewModelPropertyName = (string)exposeAttribute.ConstructorArguments[0].Value; var modelPropertyName = viewModelPropertyName; if (exposeAttribute.ConstructorArguments.Count > 1) { modelPropertyName = (string)(exposeAttribute.ConstructorArguments[1].Value ?? viewModelPropertyName); } var isReadOnly = false; var isReadOnlyProperty = (from property in exposeAttribute.Properties where string.Equals(property.Name, "IsReadOnly") select property).FirstOrDefault(); if (isReadOnlyProperty.Argument.Value != null) { isReadOnly = (bool)isReadOnlyProperty.Argument.Value; } // Check property definition on model var modelType = modelProperty.PropertyDefinition.PropertyType; var modelPropertyToMap = modelType.GetProperty(modelPropertyName); if (modelPropertyToMap is null) { FodyEnvironment.WriteError($"Exposed property '{modelPropertyName}' does not exist on model '{modelType.FullName}', make sure to set the right mapping"); return; } var modelPropertyType = modelType.ResolveGenericPropertyType(modelPropertyToMap); var viewModelPropertyDefinition = new PropertyDefinition(viewModelPropertyName, PropertyAttributes.None, FodyEnvironment.ModuleDefinition.ImportReference(modelPropertyType)); viewModelPropertyDefinition.DeclaringType = catelType.TypeDefinition; catelType.TypeDefinition.Properties.Add(viewModelPropertyDefinition); viewModelPropertyDefinition.MarkAsCompilerGenerated(_msCoreReferenceFinder); var catelTypeProperty = new CatelTypeProperty(catelType.TypeDefinition, viewModelPropertyDefinition); catelTypeProperty.IsReadOnly = isReadOnly; var catelPropertyWeaver = new ModelBasePropertyWeaver(catelType, catelTypeProperty, _configuration, _moduleWeaver, _msCoreReferenceFinder); catelPropertyWeaver.Execute(true); var stringType = _msCoreReferenceFinder.GetCoreTypeReference("String"); var stringTypeDefinition = catelType.TypeDefinition.Module.ImportReference(stringType); var attributeConstructor = catelType.TypeDefinition.Module.ImportReference(_viewModelToModelAttributeTypeDefinition.Constructor(false)); var viewModelToModelAttribute = new CustomAttribute(attributeConstructor); viewModelToModelAttribute.ConstructorArguments.Add(new CustomAttributeArgument(stringTypeDefinition, modelName)); viewModelToModelAttribute.ConstructorArguments.Add(new CustomAttributeArgument(stringTypeDefinition, modelPropertyName)); viewModelPropertyDefinition.CustomAttributes.Add(viewModelToModelAttribute); }
private bool IsPrivateReferenceAvailableOnDisk(string assemblyName, PrivateReference privateReference, string nuGetRoot) { var dllCache = CacheHelper.GetCache <Dictionary <string, string[]> >("IsPrivateReference_DllCache"); var exeCache = CacheHelper.GetCache <Dictionary <string, string[]> >("IsPrivateReference_ExeCache"); try { // For some packages (such as Fody), there is no /lib folder, in that case we don't need // to check anything var path = Path.Combine(nuGetRoot, privateReference.PackageName, privateReference.Version, "lib"); if (!Directory.Exists(path)) { return(false); } if (!dllCache.TryGetValue(path, out var dllFiles)) { dllFiles = Directory.GetFiles(path, $"*.dll", SearchOption.AllDirectories); dllCache[path] = dllFiles; } var dllName = $"{assemblyName}.dll"; var isDll = dllFiles.Any(x => x.EndsWith(dllName, StringComparison.OrdinalIgnoreCase)); if (isDll) { return(true); } if (!exeCache.TryGetValue(path, out var exeFiles)) { exeFiles = Directory.GetFiles(path, $"*.exe", SearchOption.AllDirectories); exeCache[path] = exeFiles; } var exeName = $"{assemblyName}.exe"; var isExe = exeFiles.Any(x => x.EndsWith(exeName, StringComparison.OrdinalIgnoreCase)); if (isExe) { return(true); } return(false); } catch (Exception ex) { FodyEnvironment.WriteError($"Failed to check private reference '{privateReference}':\n{ex}"); return(false); } }
private bool IsPrivateReference(string assemblyName) { var privateReferences = FindPrivateReferences(); var userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); var nuGetRoot = Path.Combine(userProfilePath, ".nuget", "packages"); // For now, ignore the target version, just check whether the package (version) contains the assembly foreach (var privateReference in privateReferences) { try { // For some packages (such as Fody), there is no /lib folder, in that case we don't need // to check anything var path = Path.Combine(nuGetRoot, privateReference.PackageName, privateReference.Version, "lib"); if (!Directory.Exists(path)) { continue; } FodyEnvironment.WriteDebug($"Checking private reference '{privateReference}' using '{path}'"); var isDll = Directory.GetFiles(path, $"{assemblyName}.dll", SearchOption.AllDirectories).Any(); if (isDll) { return(true); } var isExe = Directory.GetFiles(path, $"{assemblyName}.exe", SearchOption.AllDirectories).Any(); if (isExe) { return(true); } } catch (Exception ex) { FodyEnvironment.WriteError($"Failed to check private reference '{privateReference}':\n{ex}"); } } return(false); }
public bool Execute(TypeDefinition type, MethodDefinition methodDefinition, object parameterDefinitionOrFieldDefinition, CustomAttribute attribute, int instructionIndex) { TypeReference targetType = null; MethodDefinition selectedMethod = null; var parameterDefinition = parameterDefinitionOrFieldDefinition as ParameterDefinition; if (parameterDefinition != null) { targetType = parameterDefinition.ParameterType; } var fieldDefinition = parameterDefinitionOrFieldDefinition as FieldDefinition; if (fieldDefinition != null) { targetType = fieldDefinition.FieldType; } if (targetType != null) { try { SelectMethod(_argumentTypeDefinition, targetType, out selectedMethod); } catch (Exception ex) { var error = $"[{type.FullName}.{methodDefinition.Name}] {ex.Message}"; var sequencePoint = methodDefinition.GetFirstSequencePoint(); if (sequencePoint != null) { FodyEnvironment.WriteErrorPoint(error, sequencePoint); } else { FodyEnvironment.WriteError(error); } return(false); } } if (selectedMethod is null) { return(false); } var moduleDefinition = type.Module; var importedMethod = moduleDefinition.ImportReference(selectedMethod); var instructions = new List <Instruction>(); if (parameterDefinition != null) { BuildInstructions(moduleDefinition, type, methodDefinition, parameterDefinition, attribute, instructions); } if (fieldDefinition != null) { BuildInstructions(moduleDefinition, type, methodDefinition, fieldDefinition, attribute, instructions); } if (importedMethod.HasGenericParameters) { var genericInstanceMethod = new GenericInstanceMethod(importedMethod); genericInstanceMethod.GenericArguments.Add(targetType); instructions.Add(Instruction.Create(OpCodes.Call, genericInstanceMethod)); } else { instructions.Add(Instruction.Create(OpCodes.Call, importedMethod)); } methodDefinition.Body.Instructions.Insert(instructionIndex, instructions); return(true); }
private List <PrivateReference> FindPrivateReferences() { var csProj = _moduleWeaver.ProjectFilePath; if (string.IsNullOrWhiteSpace(csProj) || !File.Exists(csProj)) { return(new List <PrivateReference>()); } // Assembly name != package name, so we need to go through all *private* packages to // see if it's a private reference. For now we have a *simple* reference structure, // which means only modern sdk project style is supported, and only references directly // listed in the csproj will be supported var privateReferencesCache = CacheHelper.GetCache <Dictionary <string, List <PrivateReference> > >(csProj); if (!privateReferencesCache.TryGetValue(csProj, out var privateReferences)) { privateReferences = new List <PrivateReference>(); try { var element = XElement.Parse(File.ReadAllText(csProj)); var packageReferenceElements = element.XPathSelectElements("//PackageReference"); foreach (var packageReferenceElement in packageReferenceElements) { var includeAttribute = packageReferenceElement.Attribute("Include"); if (includeAttribute is null) { continue; } var packageName = includeAttribute.Value; var versionAttribute = packageReferenceElement.Attribute("Version"); if (versionAttribute is null) { FodyEnvironment.WriteWarning($"Could not find version attribute for '{packageName}'"); continue; } var version = versionAttribute.Value; var privateAssetsAttribute = packageReferenceElement.Attribute("PrivateAssets"); if (privateAssetsAttribute != null) { if (string.Equals(privateAssetsAttribute.Value, "all", StringComparison.OrdinalIgnoreCase)) { privateReferences.Add(new PrivateReference(packageName, version)); continue; } } var privateAssetsElement = packageReferenceElement.Element("PrivateAssets"); if (privateAssetsElement != null) { if (string.Equals(privateAssetsElement.Value, "all", StringComparison.OrdinalIgnoreCase)) { privateReferences.Add(new PrivateReference(packageName, version)); continue; } } } } catch (Exception ex) { FodyEnvironment.WriteError($"Failed to search for private packages in project file '{csProj}':\n{ex}"); } privateReferencesCache[csProj] = privateReferences; } return(privateReferences); }
private void ProcessMethod(MethodDefinition method) { if (method.Body is null) { return; } if (method.IsDecoratedWithAttribute("NoWeavingAttribute")) { FodyEnvironment.WriteDebug($"\t\tSkipping '{method.Name}' because 'Catel.Fody.NoWeavingAttribute'"); return; } // Note: very important to only simplify/optimize methods that we actually change, otherwise some Mono.Cecil bugs // will appear on the surface Collection <Instruction> instructions = null; var methodFullName = method.GetFullName(); FodyEnvironment.WriteDebug($"\tExecuting '{GetType().Name}' for '{methodFullName}'"); // Step 1) Convert attributes // TODO: how to handle async/await here? for (var i = method.Parameters.Count - 1; i >= 0; i--) { var parameter = method.Parameters[i]; for (var j = parameter.CustomAttributes.Count - 1; j >= 0; j--) { var customAttribute = parameter.CustomAttributes[j]; var attributeFullName = customAttribute.AttributeType.FullName; if (ArgumentMethodCallWeaverBase.WellKnownWeavers.ContainsKey(attributeFullName)) { if (instructions is null) { method.Body.SimplifyMacros(); instructions = method.Body.Instructions; } ArgumentMethodCallWeaverBase.WellKnownWeavers[attributeFullName].Execute(_typeDefinition, method, parameter, customAttribute, 0); parameter.RemoveAttribute(attributeFullName); } else if (attributeFullName.StartsWith("Catel.Fody")) { FodyEnvironment.WriteErrorPoint($"Weaving of parameter '{method.GetFullName()}' of methods '{parameter.Name}' with attribute '{attributeFullName}' is not (yet) supported, please use a different method", method.GetFirstSequencePoint()); } } } // Step 2) Convert expressions to normal calls var displayClasses = new List <TypeDefinition>(); // Go backwards to keep the order of the arguments correct (because argument checks are injected at the beginning of the ctor) if (instructions != null || ContainsArgumentChecks(method)) { if (instructions is null) { method.Body.SimplifyMacros(); instructions = method.Body.Instructions; } for (var i = instructions.Count - 1; i >= 0; i--) { var instruction = instructions[i]; if (IsSupportedExpressionArgumentCheck(method, instruction)) { if (_configuration.IsRunningAgainstCatel) { FodyEnvironment.WriteError($"Weaving argument checks is disabled for Catel itself, ensure writing performant code by calling the non-expression version in '{method.GetFullName()}'"); continue; } var fullKey = ((MethodReference)instruction.Operand).GetFullName(); var parameterOrField = GetParameterOrFieldForExpressionArgumentCheck(method, instructions, instruction); if (parameterOrField is null) { FodyEnvironment.WriteWarning($"Cannot weave at least one argument of method '{method.GetFullName()}'"); continue; } if (!ExpressionChecksToAttributeMappings.ContainsKey(fullKey)) { return; } var customAttribute = ExpressionChecksToAttributeMappings[fullKey](method, instructions, instruction); if (customAttribute is null) { FodyEnvironment.WriteWarningPoint($"Expression argument method transformation in '{method.GetFullName()}' to '{fullKey}' is not (yet) supported. To ensure the best performance, either rewrite this into a non-expression argument check or create a PR for Catel.Fody to enable support :-)", method.GetSequencePoint(instruction)); continue; } var removedInfo = RemoveArgumentWeavingCall(method, instructions, instruction); if (!displayClasses.Contains(removedInfo.DisplayClassTypeDefinition)) { displayClasses.Add(removedInfo.DisplayClassTypeDefinition); } var weaver = ArgumentMethodCallWeaverBase.WellKnownWeavers[customAttribute.AttributeType.FullName]; if (!weaver.Execute(_typeDefinition, method, parameterOrField, customAttribute, removedInfo.Index)) { // We failed, the build should fail now return; } // Reset counter, start from the beginning i = instructions.Count - 1; } } // Step 3) Clean up unnecessary code if (displayClasses.Count > 0) { foreach (var displayClass in displayClasses) { RemoveObsoleteCodeForArgumentExpression(method, instructions, displayClass); } } // Step 4) Remove double nop commands, start at 1 // Note: disabled because there might be jump codes to different Nop instructions //for (int i = 1; i < instructions.Count; i++) //{ // if (instructions[i].IsOpCode(OpCodes.Nop) && instructions[i - 1].IsOpCode(OpCodes.Nop)) // { // instructions.RemoveAt(i--); // } //} } if (instructions != null) { method.Body.OptimizeMacros(); method.UpdateDebugInfo(); } }
/// <summary> /// Gets the included references. /// </summary> /// <returns></returns> public IEnumerable <AssemblyDefinition> GetIncludedReferences() { var includedReferences = new List <AssemblyDefinition>(); var resolver = _moduleDefinition.AssemblyResolver; foreach (var assemblyReference in _moduleDefinition.AssemblyReferences) { if (!ShouldReferenceBeIncluded(assemblyReference)) { continue; } var assembly = resolver.Resolve(assemblyReference); if (assembly is not null) { FodyEnvironment.WriteInfo($"Including reference '{assemblyReference.Name}'"); includedReferences.Add(assembly); } else { FodyEnvironment.WriteError($"Reference '{assemblyReference.Name}' should be included, but cannot be resolved"); } } if (!_configuration.ExcludeOptimizedAssemblies) { var splittedReferences = _moduleWeaver.References.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries); foreach (var splittedReference in splittedReferences) { var assemblyDefinition = AssemblyDefinition.ReadAssembly(splittedReference); var isIncluded = (from reference in includedReferences where string.Equals(reference.FullName, assemblyDefinition.FullName) select reference).Any(); if (!isIncluded) { var referenceName = assemblyDefinition.Name; if (!ShouldReferenceBeIncluded(assemblyDefinition.Name)) { continue; } var assembly = resolver.Resolve(referenceName); if (assembly is not null) { FodyEnvironment.WriteInfo($"Including reference '{referenceName.Name}', it was optimized away by the compiler but still adding it"); includedReferences.Add(assembly); } else { FodyEnvironment.WriteError($"Reference '{referenceName}' should be included, but cannot be resolved"); } } } } return(includedReferences.OrderBy(x => x.Name.Name)); }