public static BuildContext Parse( string projectDir, string buildFile, string nullableTargetName, IList <Wax.BuildArg> buildArgOverrides, IList <Wax.ExtensionArg> extensionArgOverrides) { BuildRoot buildInput = GetBuildRoot(buildFile, projectDir); Dictionary <string, Target> targetsByName = new Dictionary <string, Target>(); foreach (Target target in buildInput.Targets) { string name = target.Name; if (name == null) { throw new InvalidOperationException("A target in the build file is missing a name."); } if (targetsByName.ContainsKey(name)) { throw new InvalidOperationException("There are multiple build targets with the name '" + name + "'."); } targetsByName[name] = target; } List <Wax.BuildArg> buildArgs = new List <Wax.BuildArg>(); List <Wax.ExtensionArg> extensionArgs = new List <Wax.ExtensionArg>(); List <BuildVar> buildVars = new List <BuildVar>(); List <BuildItem> buildItems = new List <BuildItem>(); if (nullableTargetName == null) { buildItems.Add(buildInput); } else { HashSet <string> targetsVisited = new HashSet <string>(); string nextItem = nullableTargetName; Target walker; while (nextItem != null) { if (targetsVisited.Contains(nextItem)) { throw new InvalidOperationException("Build target inheritance loop for '" + nextItem + "' contains a cycle."); } targetsVisited.Add(nextItem); if (!targetsByName.ContainsKey(nextItem)) { throw new InvalidOperationException("There is no build target named: '" + nextItem + "'."); } walker = targetsByName[nextItem]; buildItems.Add(walker); nextItem = walker.InheritFrom; } buildItems.Add(buildInput); } buildItems.Reverse(); // The attributes in the leaf node have precedence. foreach (BuildItem currentItem in buildItems) { buildArgs.AddRange(currentItem.BuildArgs); extensionArgs.AddRange(currentItem.ExtensionArgs); buildVars.AddRange(currentItem.BuildVars); } if (buildArgOverrides != null) { buildArgs.AddRange(buildArgOverrides); } if (extensionArgOverrides != null) { extensionArgs.AddRange(extensionArgOverrides); } PercentReplacer pr = new PercentReplacer() .AddReplacement("TARGET_NAME", nullableTargetName ?? ""); // Do a first pass over all the build args to fetch anything that is used by %PERCENT_REPLACEMENT% and anything // that can be defined with multiple values in a list. List <string> sources = new List <string>(); List <string> icons = new List <string>(); string version = "1.0"; Locale compilerLocale = Locale.Get("en"); string projectId = null; ProgrammingLanguage programmingLanguage = ProgrammingLanguage.CRAYON; List <Wax.BuildArg> remainingBuildArgs = new List <Wax.BuildArg>(); List <string> envFilesBuilder = new List <string>(); List <string> deps = new List <string>(); foreach (Wax.BuildArg buildArg in buildArgs) { switch (buildArg.Name) { case "programmingLanguage": ProgrammingLanguage?pl = ProgrammingLanguageParser.Parse(buildArg.Value); if (pl == null) { throw new InvalidOperationException("Invalid programming language in build file: '" + buildArg.Value + "'."); } programmingLanguage = pl.Value; break; case "version": version = buildArg.Value; break; case "source": sources.Add(buildArg.Value); break; case "icon": icons.Add(buildArg.Value); break; case "compilerLocale": compilerLocale = Locale.Get(buildArg.Value); break; case "id": projectId = buildArg.Value; break; case "dependencies": deps.Add(buildArg.Value); break; case "envFile": throw new Exception(); // These are eliminated in the parsing phase. default: remainingBuildArgs.Add(buildArg); break; } } if (projectId == null) { throw new InvalidOperationException("The projectId is not defined in the build file."); } if (projectId.Length == 0) { throw new InvalidOperationException("projectId cannot be blank."); } if (projectId.ToCharArray().Any(c => (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9'))) { throw new InvalidOperationException("projectId can only contain alphanumeric characters."); } if (projectId[0] >= '0' && projectId[0] <= '9') { throw new InvalidOperationException("projectId cannot being with a number."); } pr .AddReplacement("PROJECT_ID", projectId) .AddReplacement("COMPILER_LANGUAGE", programmingLanguage.ToString().ToUpperInvariant()) .AddReplacement("VERSION", version) .AddReplacement("COMPILER_LOCALE", compilerLocale.ID.ToLowerInvariant()); ProjectFilePath[] envFiles = ToFilePaths(projectDir, envFilesBuilder); BuildContext buildContext = new BuildContext() { ProjectID = projectId, ProjectDirectory = projectDir, SourceFolders = ToFilePaths(projectDir, sources), Version = version, RootProgrammingLanguage = programmingLanguage, IconFilePaths = icons // TODO: icon values should be concatenated within a build target, but override previous targets .Select(t => pr.Replace(t)) .Select(t => FileUtil.GetAbsolutePathFromRelativeOrAbsolutePath(projectDir, t)) .Select(t => FileUtil.GetCanonicalizeUniversalPath(t)) .Where(t => { if (!System.IO.File.Exists(t)) { throw new System.InvalidOperationException("The following icon path does not exist: '" + t + "'."); } return(true); }) .ToArray(), CompilerLocale = compilerLocale, LocalDeps = deps .Select(t => Wax.Util.EnvironmentVariables.DoReplacementsInString(t)) .Select(t => pr.Replace(t)) .Select(t => FileUtil.GetCanonicalizeUniversalPath(t)) .ToArray(), ExtensionArgs = extensionArgs.Select(arg => { arg.Value = pr.Replace(arg.Value); return(arg); }).ToArray(), }; foreach (Wax.BuildArg buildArg in remainingBuildArgs) { string value = pr.Replace(buildArg.Value); switch (buildArg.Name) { case "title": buildContext.ProjectTitle = pr.Replace(value); break; case "description": buildContext.Description = pr.Replace(value); break; case "orientation": buildContext.Orientation = ParseOrientations(value); break; case "output": buildContext.OutputFolder = FileUtil.JoinAndCanonicalizePath(projectDir, pr.Replace(value)); break; case "delegateMainTo": buildContext.DelegateMainTo = value; break; case "removeSymbols": buildContext.RemoveSymbols = GetBoolValue(value); break; case "skipRun": buildContext.SkipRun = GetBoolValue(value); break; } } Dictionary <string, BuildVar> buildVarLookup = new Dictionary <string, BuildVar>(); foreach (BuildVar bv in buildVars) { buildVarLookup[bv.ID] = bv; } buildContext.BuildVariableLookup = buildVarLookup; return(buildContext); }
public static BuildContext Parse(string projectDir, string buildFile, string nullableTargetName, bool useRelativePathsInErrors) { BuildRoot buildInput = GetBuildRoot(buildFile); string platform = null; Dictionary <string, BuildVarCanonicalized> varLookup; string targetName = nullableTargetName; Target desiredTarget = null; if (nullableTargetName != null) { desiredTarget = FindTarget(targetName, buildInput.Targets); if (desiredTarget == null) { throw new InvalidOperationException("Build target does not exist in build file: '" + targetName + "'."); } platform = desiredTarget.Platform; } else { targetName = "cbx"; desiredTarget = FindTarget(targetName, buildInput.Targets) ?? new Target(); } Dictionary <string, string> replacements = new Dictionary <string, string>() { { "TARGET_NAME", targetName } }; varLookup = BuildVarParser.GenerateBuildVars(buildInput, desiredTarget, replacements); if (desiredTarget.HasLegacyIcon || buildInput.HasLegacyIcon) { // TODO: remove this in 2.2.0 or something throw new InvalidOperationException( "This build file has a string property for an icon path. " + "This has been changed to a JSON array of strings for icon paths with a key called \"icons\" instead of \"icon\". " + "Please update your build file accordingly."); } if (desiredTarget.HasLegacyTitle || buildInput.HasLegacyTitle) { throw new InvalidOperationException("This build file has a \"default-title\" property, which was changed to just \"title\" in 2.1.0. Please update your build file accordingly."); } SourceItem[] sources = desiredTarget.SourcesNonNull.Union(buildInput.SourcesNonNull).ToArray(); string output = desiredTarget.Output ?? buildInput.Output; string projectId = desiredTarget.ProjectId ?? buildInput.ProjectId; string version = desiredTarget.Version ?? buildInput.Version ?? "1.0"; string jsFilePrefix = desiredTarget.JsFilePrefix ?? buildInput.JsFilePrefix; bool jsFullPage = NullableBoolean.ToBoolean(desiredTarget.JsFullPageRaw ?? buildInput.JsFullPageRaw, false); ImageSheet[] imageSheets = MergeImageSheets(desiredTarget.ImageSheets, buildInput.ImageSheets); // TODO: maybe set this default value to true, although this does nothing as of now. bool minified = NullableBoolean.ToBoolean(desiredTarget.MinifiedRaw ?? buildInput.MinifiedRaw, false); bool exportDebugByteCode = BoolUtil.Parse(desiredTarget.ExportDebugByteCodeRaw ?? buildInput.ExportDebugByteCodeRaw); string guidSeed = desiredTarget.GuidSeed ?? buildInput.GuidSeed ?? ""; // TODO: make this a string array. string[] iconFilePaths = CombineAndFlattenStringArrays(desiredTarget.IconFilePaths, buildInput.IconFilePaths); string launchScreen = desiredTarget.LaunchScreen ?? buildInput.LaunchScreen; string projectTitle = desiredTarget.ProjectTitle ?? buildInput.ProjectTitle; string orientation = desiredTarget.Orientation ?? buildInput.Orientation; string iosBundlePrefix = desiredTarget.IosBundlePrefix ?? buildInput.IosBundlePrefix; string javaPackage = desiredTarget.JavaPackage ?? buildInput.JavaPackage; string[] localDeps = CombineAndFlattenStringArrays(desiredTarget.LocalDeps, buildInput.LocalDeps); string[] remoteDeps = CombineAndFlattenStringArrays(desiredTarget.RemoteDeps, buildInput.RemoteDeps); string description = desiredTarget.Description ?? buildInput.Description ?? ""; Size windowSize = Size.Merge(desiredTarget.WindowSize, buildInput.WindowSize) ?? new Size(); string compilerLocale = desiredTarget.CompilerLocale ?? buildInput.CompilerLocale ?? "en"; bool isCSharpCompatMode = desiredTarget.IsCSharpCompatMode || buildInput.IsCSharpCompatMode; // TODO(acrylic-convert): should have unset state with ?? string programmingLanguage = buildInput.ProgrammingLanguage ?? "Crayon"; string delegateMainTo = desiredTarget.DelegateMainTo ?? buildInput.DelegateMainTo; if (output == null) { throw new InvalidOperationException("No output directory defined."); } PercentReplacer pr = new PercentReplacer() .AddReplacement("COMPILER_VERSION", VersionInfo.VersionString) .AddReplacement("COMPILER_LANGUAGE", programmingLanguage) .AddReplacement("TARGET_NAME", targetName); version = pr.Replace(version); pr.AddReplacement("VERSION", version); compilerLocale = pr.Replace(compilerLocale); pr.AddReplacement("COMPILER_LOCALE", compilerLocale); output = FileUtil.GetCanonicalizeUniversalPath(pr.Replace(output)); projectId = pr.Replace(projectId); jsFilePrefix = pr.Replace(jsFilePrefix); guidSeed = pr.Replace(guidSeed); iconFilePaths = iconFilePaths .Select(t => pr.Replace(t)) .Select(t => FileUtil.GetAbsolutePathFromRelativeOrAbsolutePath(projectDir, t)) .Select(t => FileUtil.GetCanonicalizeUniversalPath(t)) .ToArray(); launchScreen = pr.Replace(launchScreen); projectTitle = pr.Replace(projectTitle); orientation = pr.Replace(orientation); iosBundlePrefix = pr.Replace(iosBundlePrefix); javaPackage = pr.Replace(javaPackage); programmingLanguage = pr.Replace(programmingLanguage); localDeps = localDeps .Select(t => CommonUtil.Environment.EnvironmentVariables.DoReplacementsInString(t)) .Select(t => pr.Replace(t)) .Select(t => FileUtil.GetCanonicalizeUniversalPath(t)) .ToArray(); remoteDeps = remoteDeps .Select(t => pr.Replace(t)) .ToArray(); description = pr.Replace(description); BuildContext buildContext = new BuildContext() { ProjectDirectory = projectDir, JsFilePrefix = jsFilePrefix, OutputFolder = output, Platform = platform, ProjectID = projectId, Minified = minified, ReadableByteCode = exportDebugByteCode, GuidSeed = guidSeed, IconFilePaths = iconFilePaths, LaunchScreenPath = launchScreen, ProjectTitle = projectTitle, Orientation = orientation, LocalDeps = localDeps, RemoteDeps = remoteDeps, IosBundlePrefix = iosBundlePrefix, JavaPackage = javaPackage, JsFullPage = jsFullPage, WindowWidth = windowSize.Width, WindowHeight = windowSize.Height, CompilerLocale = Locale.Get(compilerLocale), IsCSharpCompatibilityMode = isCSharpCompatMode, DelegateMainTo = delegateMainTo, }; ProgrammingLanguage?nullableLanguage = ProgrammingLanguageParser.Parse(programmingLanguage); if (nullableLanguage == null) { throw new InvalidOperationException("Invalid programming language specified: '" + programmingLanguage + "'"); } buildContext.TopLevelAssembly = new AssemblyContext(buildContext) { Description = description, Version = version, SourceFolders = ToFilePaths(projectDir, sources), ImageSheetPrefixesById = imageSheets.ToDictionary <ImageSheet, string, string[]>(s => s.Id, s => s.Prefixes), ImageSheetIds = imageSheets.Select <ImageSheet, string>(s => s.Id).ToArray(), BuildVariableLookup = varLookup, ProgrammingLanguage = nullableLanguage.Value, }; return(buildContext.ValidateValues(useRelativePathsInErrors)); }