Sharpmake.Generators/VisualStudio/Androidproj.cs (347 lines of code) (raw):

// Copyright (c) Ubisoft. All Rights Reserved. // Licensed under the Apache 2.0 License. See LICENSE.md in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace Sharpmake.Generators.VisualStudio { public partial class Androidproj : IProjectGenerator { public const string ProjectExtension = ".androidproj"; private class GenerationContext : IVcxprojGenerationContext { #region IVcxprojGenerationContext implementation public Builder Builder { get; } public Project Project { get; } public Project.Configuration Configuration { get; internal set; } public string ProjectDirectory { get; } public DevEnv DevelopmentEnvironment => Configuration.Target.GetFragment<DevEnv>(); public Options.ExplicitOptions Options { get { Debug.Assert(_projectConfigurationOptions.ContainsKey(Configuration)); return _projectConfigurationOptions[Configuration]; } } public IDictionary<string, string> CommandLineOptions { get; set; } public string ProjectDirectoryCapitalized { get; } public string ProjectSourceCapitalized { get; } public bool PlainOutput { get; } public void SelectOption(params Options.OptionAction[] options) { Sharpmake.Options.SelectOption(Configuration, options); } public void SelectOptionWithFallback(Action fallbackAction, params Options.OptionAction[] options) { Sharpmake.Options.SelectOptionWithFallback(Configuration, fallbackAction, options); } public string ProjectPath { get; } public string ProjectFileName { get; } public IReadOnlyList<Project.Configuration> ProjectConfigurations { get; } public IReadOnlyDictionary<Project.Configuration, Options.ExplicitOptions> ProjectConfigurationOptions => _projectConfigurationOptions; public DevEnvRange DevelopmentEnvironmentsRange { get; } public IReadOnlyDictionary<Platform, IPlatformVcxproj> PresentPlatforms { get; } public Resolver EnvironmentVariableResolver { get; internal set; } #endregion private Dictionary<Project.Configuration, Options.ExplicitOptions> _projectConfigurationOptions; public void SetProjectConfigurationOptions(Dictionary<Project.Configuration, Options.ExplicitOptions> projectConfigurationOptions) { _projectConfigurationOptions = projectConfigurationOptions; } internal AndroidPackageProject AndroidPackageProject { get; } public GenerationContext(Builder builder, string projectPath, Project project, IEnumerable<Project.Configuration> projectConfigurations) { Builder = builder; FileInfo fileInfo = new FileInfo(projectPath); ProjectPath = fileInfo.FullName; ProjectDirectory = Path.GetDirectoryName(ProjectPath); ProjectFileName = Path.GetFileName(ProjectPath); Project = project; AndroidPackageProject = (AndroidPackageProject)Project; ProjectDirectoryCapitalized = Util.GetCapitalizedPath(ProjectDirectory); ProjectSourceCapitalized = Util.GetCapitalizedPath(Project.SourceRootPath); ProjectConfigurations = VsUtil.SortConfigurations(projectConfigurations, Path.Combine(ProjectDirectoryCapitalized, ProjectFileName + ProjectExtension)).ToArray(); DevelopmentEnvironmentsRange = new DevEnvRange(ProjectConfigurations); PresentPlatforms = ProjectConfigurations.Select(conf => conf.Platform).Distinct().ToDictionary(p => p, p => PlatformRegistry.Get<IPlatformVcxproj>(p)); } public void Reset() { CommandLineOptions = null; Configuration = null; EnvironmentVariableResolver = null; } } // The default value used by the Ant build type is to remove the AndroidBuildType tag private string _androidBuildType = FileGeneratorUtilities.RemoveLineTag; private bool _isGradleBuild { get { return _androidBuildType.Equals("Gradle", StringComparison.InvariantCultureIgnoreCase); } } public void Generate( Builder builder, Project project, List<Project.Configuration> configurations, string projectFile, List<string> generatedFiles, List<string> skipFiles) { if (!(project is AndroidPackageProject)) throw new ArgumentException("Project is not a AndroidPackageProject"); var context = new GenerationContext(builder, projectFile, project, configurations); GenerateImpl(context, generatedFiles, skipFiles); } private void GenerateConfOptions(GenerationContext context) { // generate all configuration options once... var projectOptionsGen = new ProjectOptionsGenerator(); var projectConfigurationOptions = new Dictionary<Project.Configuration, Options.ExplicitOptions>(); context.SetProjectConfigurationOptions(projectConfigurationOptions); foreach (Project.Configuration conf in context.ProjectConfigurations) { context.Configuration = conf; // set generator information var platformVcxproj = context.PresentPlatforms[conf.Platform]; var configurationTasks = PlatformRegistry.Get<Project.Configuration.IConfigurationTasks>(conf.Platform); conf.GeneratorSetOutputFullExtensions( platformVcxproj.ExecutableFileFullExtension, platformVcxproj.PackageFileFullExtension, configurationTasks.GetDefaultOutputFullExtension(Project.Configuration.OutputType.Dll), platformVcxproj.ProgramDatabaseFileFullExtension); projectConfigurationOptions.Add(conf, new Options.ExplicitOptions()); context.CommandLineOptions = new ProjectOptionsGenerator.VcxprojCmdLineOptions(); projectOptionsGen.GenerateOptions(context); GenerateOptions(context); context.Reset(); // just a safety, not necessary to clean up } } private void GenerateImpl( GenerationContext context, List<string> generatedFiles, List<string> skipFiles) { GenerateConfOptions(context); var fileGenerator = new XmlFileGenerator(); // xml begin header string toolsVersion = context.DevelopmentEnvironmentsRange.MinDevEnv.GetVisualProjectToolsVersionString(); using (fileGenerator.Declare("toolsVersion", toolsVersion)) fileGenerator.Write(Template.Project.ProjectBegin); VsProjCommon.WriteCustomProperties(context.Project.CustomProperties, fileGenerator); VsProjCommon.WriteProjectConfigurationsDescription(context.ProjectConfigurations, fileGenerator); // xml end header string androidTargetsPath = Options.GetConfOption<Options.Android.General.AndroidTargetsPath>(context.ProjectConfigurations, rootpath: context.ProjectDirectoryCapitalized); var firstConf = context.ProjectConfigurations.First(); _androidBuildType = Options.GetOptionValue("androidBuildType", context.ProjectConfigurationOptions.Values, FileGeneratorUtilities.RemoveLineTag); using (fileGenerator.Declare("androidBuildType", _androidBuildType)) using (fileGenerator.Declare("projectName", firstConf.ProjectName)) using (fileGenerator.Declare("guid", firstConf.ProjectGuid)) using (fileGenerator.Declare("toolsVersion", toolsVersion)) using (fileGenerator.Declare("androidTargetsPath", Util.EnsureTrailingSeparator(androidTargetsPath))) { fileGenerator.Write(Template.Project.ProjectDescription); } fileGenerator.Write(VsProjCommon.Template.PropertyGroupEnd); foreach (var platform in context.PresentPlatforms.Values) platform.GeneratePlatformSpecificProjectDescription(context, fileGenerator); fileGenerator.Write(Template.Project.ImportAndroidDefaultProps); foreach (var platform in context.PresentPlatforms.Values) platform.GeneratePostDefaultPropsImport(context, fileGenerator); // configuration general foreach (Project.Configuration conf in context.ProjectConfigurations) { context.Configuration = conf; using (fileGenerator.Declare("platformName", Util.GetToolchainPlatformString(conf.Platform, conf.Project, conf.Target))) using (fileGenerator.Declare("conf", conf)) using (fileGenerator.Declare("options", context.ProjectConfigurationOptions[conf])) { fileGenerator.Write(Template.Project.ProjectConfigurationsGeneral); } } // .props files fileGenerator.Write(Template.Project.ProjectAfterConfigurationsGeneral); VsProjCommon.WriteProjectCustomPropsFiles(context.Project.CustomPropsFiles, context.ProjectDirectoryCapitalized, fileGenerator); VsProjCommon.WriteConfigurationsCustomPropsFiles(context.ProjectConfigurations, context.ProjectDirectoryCapitalized, fileGenerator); fileGenerator.Write(Template.Project.ProjectAfterImportedProps); string androidPackageDirectory = context.AndroidPackageProject.AntBuildRootDirectory; // configuration ItemDefinitionGroup foreach (Project.Configuration conf in context.ProjectConfigurations) { context.Configuration = conf; using (fileGenerator.Declare("platformName", Util.GetToolchainPlatformString(conf.Platform, conf.Project, conf.Target))) using (fileGenerator.Declare("conf", conf)) using (fileGenerator.Declare("options", context.ProjectConfigurationOptions[conf])) using (fileGenerator.Declare("androidPackageDirectory", androidPackageDirectory)) { fileGenerator.Write(Template.Project.ProjectConfigurationBeginItemDefinition); { if (!_isGradleBuild) fileGenerator.Write(Template.Project.AntPackage); } fileGenerator.Write(Template.Project.ProjectConfigurationEndItemDefinition); } } if (_isGradleBuild) { using (fileGenerator.Declare("gradlePlugin", context.AndroidPackageProject.GradlePlugin)) using (fileGenerator.Declare("gradleVersion", context.AndroidPackageProject.GradleVersion)) { fileGenerator.Write(VsProjCommon.Template.ItemDefinitionGroupBegin); fileGenerator.Write(Template.Project.GradlePackage); fileGenerator.Write(VsProjCommon.Template.ItemDefinitionGroupEnd); } } GenerateFilesSection(context, fileGenerator); // .targets fileGenerator.Write(Template.Project.ProjectTargets); GenerateProjectReferences(context, fileGenerator); // Environment variables var environmentVariables = context.ProjectConfigurations.Select(conf => conf.Platform).Distinct().SelectMany(platform => context.PresentPlatforms[platform].GetEnvironmentVariables(context)); VsProjCommon.WriteEnvironmentVariables(environmentVariables, fileGenerator); fileGenerator.Write(Template.Project.ProjectEnd); // remove all line that contain RemoveLineTag fileGenerator.RemoveTaggedLines(); FileInfo projectFileInfo = new FileInfo(context.ProjectPath + ProjectExtension); if (context.Builder.Context.WriteGeneratedFile(context.Project.GetType(), projectFileInfo, fileGenerator)) generatedFiles.Add(projectFileInfo.FullName); else skipFiles.Add(projectFileInfo.FullName); } private void GenerateFilesSection( GenerationContext context, IFileGenerator fileGenerator) { Strings projectFiles = context.Project.GetSourceFilesForConfigurations(context.ProjectConfigurations); // Add source files var allFiles = new List<Vcxproj.ProjectFile>(); var includeFiles = new List<Vcxproj.ProjectFile>(); var sourceFiles = new List<Vcxproj.ProjectFile>(); var contentFiles = new List<Vcxproj.ProjectFile>(); foreach (string file in projectFiles) { var projectFile = new Vcxproj.ProjectFile(context, file); allFiles.Add(projectFile); } allFiles.Sort((l, r) => { return string.Compare(l.FileNameProjectRelative, r.FileNameProjectRelative, StringComparison.InvariantCultureIgnoreCase); }); // type -> files var customSourceFiles = new Dictionary<string, List<Vcxproj.ProjectFile>>(); foreach (var projectFile in allFiles) { string type = null; if (context.Project.ExtensionBuildTools.TryGetValue(projectFile.FileExtension, out type)) { List<Vcxproj.ProjectFile> files = null; if (!customSourceFiles.TryGetValue(type, out files)) { files = new List<Vcxproj.ProjectFile>(); customSourceFiles[type] = files; } files.Add(projectFile); } else if (context.Project.SourceFilesCompileExtensions.Contains(projectFile.FileExtension) || (string.Compare(projectFile.FileExtension, ".rc", StringComparison.OrdinalIgnoreCase) == 0)) { sourceFiles.Add(projectFile); } else if (string.Compare(projectFile.FileExtension, ".h", StringComparison.OrdinalIgnoreCase) == 0) { includeFiles.Add(projectFile); } else { contentFiles.Add(projectFile); } } // Write header files if (includeFiles.Count > 0) { fileGenerator.Write(Template.Project.ProjectFilesBegin); foreach (var file in includeFiles) { using (fileGenerator.Declare("file", file)) fileGenerator.Write(Template.Project.ProjectFilesHeader); } fileGenerator.Write(Template.Project.ProjectFilesEnd); } // Write content files if (contentFiles.Count > 0) { fileGenerator.Write(Template.Project.ProjectFilesBegin); foreach (var file in contentFiles) { using (fileGenerator.Declare("file", file)) fileGenerator.Write(Template.Project.ContentSimple); } fileGenerator.Write(Template.Project.ProjectFilesEnd); } // Write Android project files fileGenerator.Write(Template.Project.ItemGroupBegin); if (_isGradleBuild) { foreach (var file in context.AndroidPackageProject.GradleTemplateFiles) { using (fileGenerator.Declare("gradleTemplateFile", file)) fileGenerator.Write(Template.Project.GradleTemplate); } } else { using (fileGenerator.Declare("antBuildXml", context.AndroidPackageProject.AntBuildXml)) using (fileGenerator.Declare("antProjectPropertiesFile", context.AndroidPackageProject.AntProjectPropertiesFile)) using (fileGenerator.Declare("androidManifest", context.AndroidPackageProject.AndroidManifest)) { fileGenerator.Write(Template.Project.AntBuildXml); fileGenerator.Write(Template.Project.AndroidManifest); fileGenerator.Write(Template.Project.AntProjectPropertiesFile); } } fileGenerator.Write(Template.Project.ItemGroupEnd); } private struct ProjectDependencyInfo { public string ProjectFullFileNameWithExtension; public string ProjectGuid; } private void GenerateProjectReferences( GenerationContext context, IFileGenerator fileGenerator) { var dependencies = new UniqueList<ProjectDependencyInfo>(); foreach (var c in context.ProjectConfigurations) { foreach (var d in c.ConfigurationDependencies) { // Ignore projects marked as Export if (d.Project.SharpmakeProjectType == Project.ProjectTypeAttribute.Export) continue; ProjectDependencyInfo depInfo; depInfo.ProjectFullFileNameWithExtension = d.ProjectFullFileNameWithExtension; if (d.Project.SharpmakeProjectType != Project.ProjectTypeAttribute.Compile) depInfo.ProjectGuid = d.ProjectGuid; else throw new NotImplementedException("Sharpmake.Compile not supported as a dependency by this generator."); dependencies.Add(depInfo); } } if (dependencies.Count > 0) { fileGenerator.Write(Template.Project.ItemGroupBegin); foreach (var d in dependencies) { string include = Util.PathGetRelative(context.ProjectDirectory, d.ProjectFullFileNameWithExtension); using (fileGenerator.Declare("include", include)) using (fileGenerator.Declare("projectGUID", d.ProjectGuid)) { fileGenerator.Write(Template.Project.ProjectReference); } } fileGenerator.Write(Template.Project.ItemGroupEnd); } } private void GenerateOptions(GenerationContext context) { var options = context.Options; var conf = context.Configuration; //OutputFile ( APK File ) options["OutputFile"] = conf.TargetFileFullName; //AndroidAppLibName Native Library Packaged into the APK options["AndroidAppLibName"] = FileGeneratorUtilities.RemoveLineTag; if (context.AndroidPackageProject.AppLibType != null) { Project.Configuration appLibConf = conf.ConfigurationDependencies.FirstOrDefault(confDep => (confDep.Project.GetType() == context.AndroidPackageProject.AppLibType)); if (appLibConf != null) { // The lib name to first load from an AndroidActivity must be a dynamic library. if (appLibConf.Output != Project.Configuration.OutputType.Dll) throw new Error("Cannot use configuration \"{0}\" as app lib for package configuration \"{1}\". Output type must be set to dynamic library.", appLibConf, conf); options["AndroidAppLibName"] = appLibConf.TargetFilePrefix + appLibConf.TargetFileName + appLibConf.TargetFileSuffix; } else { throw new Error("Missing dependency of type \"{0}\" in configuration \"{1}\" dependencies.", context.AndroidPackageProject.AppLibType.ToNiceTypeName(), conf); } } //OutputDirectory // The debugger need a rooted path to work properly. // So we root the relative output directory to $(ProjectDir) to work around this limitation. // Hopefully in a future version of the cross platform tools will be able to remove this hack. string outputDirectoryRelative = Util.PathGetRelative(context.ProjectDirectoryCapitalized, conf.TargetPath); options["OutputDirectory"] = outputDirectoryRelative; //IntermediateDirectory string intermediateDirectoryRelative = Util.PathGetRelative(context.ProjectDirectoryCapitalized, conf.IntermediatePath); options["IntermediateDirectory"] = intermediateDirectoryRelative; } } }