func (builder Model) buildableNunitTestProjects(configuration, platform string) ([]project.Model, []string) { testProjects := []project.Model{} warnings := []string{} solutionConfig := utility.ToConfig(configuration, platform) for _, proj := range builder.solution.ProjectMap { // Check if is nunit test project if proj.ProjectType != constants.ProjectTypeNunitTest { continue } // Check if contains config mapping _, ok := proj.ConfigMap[solutionConfig] if !ok { warnings = append(warnings, fmt.Sprintf("project (%s) do not have config for solution config (%s), skipping...", proj.Name, solutionConfig)) continue } testProjects = append(testProjects, proj) } return testProjects, warnings }
func (builder Model) buildNunitTestProjectCommand(configuration, platform string, proj project.Model, nunitConsolePth string) (tools.Runnable, []string, error) { warnings := []string{} solutionConfig := utility.ToConfig(configuration, platform) projectConfigKey, ok := proj.ConfigMap[solutionConfig] if !ok { warnings = append(warnings, fmt.Sprintf("project (%s) do not have config for solution config (%s), skipping...", proj.Name, solutionConfig)) } projectConfig, ok := proj.Configs[projectConfigKey] if !ok { warnings = append(warnings, fmt.Sprintf("project (%s) contains mapping for solution config (%s), but does not have project configuration", proj.Name, solutionConfig)) } command, err := nunit.New(nunitConsolePth) if err != nil { return tools.EmptyCommand{}, warnings, err } command.SetProjectPth(proj.Pth) command.SetConfig(projectConfig.Configuration) return command, warnings, nil }
func (builder Model) buildableXamarinUITestProjectsAndReferredProjects(configuration, platform string) ([]project.Model, []project.Model, []string) { testProjects := []project.Model{} referredProjects := []project.Model{} warnings := []string{} solutionConfig := utility.ToConfig(configuration, platform) for _, proj := range builder.solution.ProjectMap { // Check if is XamarinUITest project if proj.ProjectType != constants.ProjectTypeXamarinUITest { continue } // Check if contains config mapping _, ok := proj.ConfigMap[solutionConfig] if !ok { warnings = append(warnings, fmt.Sprintf("project (%s) do not have config for solution config (%s), skipping...", proj.Name, solutionConfig)) continue } // Collect referred projects if len(proj.ReferredProjectIDs) == 0 { warnings = append(warnings, fmt.Sprintf("no referred projects found for test project: %s, skipping...", proj.Name)) continue } for _, projectID := range proj.ReferredProjectIDs { referredProj, ok := builder.solution.ProjectMap[projectID] if !ok { warnings = append(warnings, fmt.Sprintf("project reference exist with project id: %s, but project not found in solution", projectID)) continue } if referredProj.ProjectType == constants.ProjectTypeUnknown { warnings = append(warnings, fmt.Sprintf("project's (%s) project type is unkown", referredProj.Name)) continue } if whitelistAllows(referredProj.ProjectType, builder.projectTypeWhitelist...) { referredProjects = append(referredProjects, referredProj) } } if len(referredProjects) == 0 { warnings = append(warnings, fmt.Sprintf("test project (%s) does not refers to any project, with project type whitelist (%v), skipping...", proj.Name, builder.projectTypeWhitelist)) continue } testProjects = append(testProjects, proj) } return testProjects, referredProjects, warnings }
// CollectXamarinUITestProjectOutputs ... func (builder Model) CollectXamarinUITestProjectOutputs(configuration, platform string) (TestProjectOutputMap, []string, error) { testProjectOutputMap := TestProjectOutputMap{} warnings := []string{} buildableTestProjects, _, _ := builder.buildableXamarinUITestProjectsAndReferredProjects(configuration, platform) solutionConfig := utility.ToConfig(configuration, platform) for _, testProj := range buildableTestProjects { projectConfigKey, ok := testProj.ConfigMap[solutionConfig] if !ok { continue } projectConfig, ok := testProj.Configs[projectConfigKey] if !ok { continue } if dllPth, err := exportDLL(projectConfig.OutputDir, testProj.AssemblyName); err != nil { return TestProjectOutputMap{}, warnings, err } else if dllPth != "" { referredProjectNames := []string{} referredProjectIDs := testProj.ReferredProjectIDs for _, referredProjectID := range referredProjectIDs { referredProject, ok := builder.solution.ProjectMap[referredProjectID] if !ok { warnings = append(warnings, fmt.Sprintf("project reference exist with project id: %s, but project not found in solution", referredProjectID)) } referredProjectNames = append(referredProjectNames, referredProject.Name) } testProjectOutputMap[testProj.Name] = TestProjectOutputModel{ ProjectType: testProj.ProjectType, ReferredProjectNames: referredProjectNames, Output: OutputModel{ Pth: dllPth, OutputType: constants.OutputTypeDLL, }, } } } return testProjectOutputMap, warnings, nil }
func (builder Model) buildableProjects(configuration, platform string) ([]project.Model, []string) { projects := []project.Model{} warnings := []string{} solutionConfig := utility.ToConfig(configuration, platform) whitelistedProjects := builder.whitelistedProjects() for _, proj := range whitelistedProjects { // // Solution config - project config mapping _, ok := proj.ConfigMap[solutionConfig] if !ok { warnings = append(warnings, fmt.Sprintf("project (%s) do not have config for solution config (%s), skipping...", proj.Name, solutionConfig)) continue } if (proj.ProjectType == constants.ProjectTypeIOS || proj.ProjectType == constants.ProjectTypeMacOS || proj.ProjectType == constants.ProjectTypeTvOS) && proj.OutputType != "exe" { warnings = append(warnings, fmt.Sprintf("project (%s) does not archivable based on output type (%s), skipping...", proj.Name, proj.OutputType)) continue } if proj.ProjectType == constants.ProjectTypeAndroid && !proj.AndroidApplication { warnings = append(warnings, fmt.Sprintf("(%s) is not an android application project, skipping...", proj.Name)) continue } if proj.ProjectType != constants.ProjectTypeUnknown { projects = append(projects, proj) } } return projects, warnings }
func analyzeSolution(pth string, analyzeProjects bool) (Model, error) { absPth, err := pathutil.AbsPath(pth) if err != nil { return Model{}, fmt.Errorf("Failed to expand path (%s), error: %s", pth, err) } fileName := filepath.Base(absPth) ext := filepath.Ext(absPth) fileName = strings.TrimSuffix(fileName, ext) solution := Model{ Pth: absPth, Name: fileName, ConfigMap: map[string]string{}, ProjectMap: map[string]project.Model{}, } isSolutionConfigurationPlatformsSection := false isProjectConfigurationPlatformsSection := false solutionDir := filepath.Dir(absPth) content, err := fileutil.ReadStringFromFile(absPth) if err != nil { return Model{}, fmt.Errorf("failed to read solution (%s), error: %s", absPth, err) } scanner := bufio.NewScanner(strings.NewReader(content)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // Projects if matches := regexp.MustCompile(solutionProjectsPattern).FindStringSubmatch(line); len(matches) == 5 { ID := strings.ToUpper(matches[1]) projectName := matches[2] projectID := strings.ToUpper(matches[4]) projectRelativePth := utility.FixWindowsPath(matches[3]) projectPth := filepath.Join(solutionDir, projectRelativePth) if strings.HasSuffix(projectPth, constants.CSProjExt) || strings.HasSuffix(projectPth, constants.SHProjExt) || strings.HasSuffix(projectPth, constants.FSProjExt) { project := project.Model{ ID: projectID, Name: projectName, Pth: projectPth, ConfigMap: map[string]string{}, Configs: map[string]project.ConfigurationPlatformModel{}, } solution.ProjectMap[projectID] = project } solution.ID = ID continue } // GlobalSection(SolutionConfigurationPlatforms) = preSolution if isSolutionConfigurationPlatformsSection { if match := regexp.MustCompile(solutionConfigurationPlatformsSectionEndPattern).FindString(line); match != "" { isSolutionConfigurationPlatformsSection = false continue } } if match := regexp.MustCompile(solutionConfigurationPlatformsSectionStartPattern).FindString(line); match != "" { isSolutionConfigurationPlatformsSection = true continue } if isSolutionConfigurationPlatformsSection { if matches := regexp.MustCompile(solutionConfigurationPlatformPattern).FindStringSubmatch(line); len(matches) == 5 { configuration := matches[1] platform := matches[2] mappedConfiguration := matches[3] mappedPlatform := matches[4] solution.ConfigMap[utility.ToConfig(configuration, platform)] = utility.ToConfig(mappedConfiguration, mappedPlatform) continue } } // GlobalSection(ProjectConfigurationPlatforms) = postSolution if isProjectConfigurationPlatformsSection { if match := regexp.MustCompile(projectConfigurationPlatformsSectionEndPattern).FindString(line); match != "" { isProjectConfigurationPlatformsSection = false continue } } if match := regexp.MustCompile(projectConfigurationPlatformsSectionStartPattern).FindString(line); match != "" { isProjectConfigurationPlatformsSection = true continue } if isProjectConfigurationPlatformsSection { if matches := regexp.MustCompile(projectConfigurationPlatformPattern).FindStringSubmatch(line); len(matches) == 6 { projectID := strings.ToUpper(matches[1]) solutionConfiguration := matches[2] solutionPlatform := matches[3] projectConfiguration := matches[4] projectPlatform := matches[5] if projectPlatform == "Any CPU" { projectPlatform = "AnyCPU" } project, found := solution.ProjectMap[projectID] if !found { return Model{}, fmt.Errorf("no project found with ID: %s", projectID) } project.ConfigMap[utility.ToConfig(solutionConfiguration, solutionPlatform)] = utility.ToConfig(projectConfiguration, projectPlatform) solution.ProjectMap[projectID] = project continue } } } if err := scanner.Err(); err != nil { return Model{}, err } if analyzeProjects { projectMap := map[string]project.Model{} for projectID, proj := range solution.ProjectMap { projectDefinition, err := project.New(proj.Pth) if err != nil { return Model{}, fmt.Errorf("failed to analyze project (%s), error: %s", proj.Pth, err) } projectDefinition.Name = proj.Name projectDefinition.Pth = proj.Pth projectDefinition.ConfigMap = proj.ConfigMap projectMap[projectID] = projectDefinition } solution.ProjectMap = projectMap } return solution, nil }
func (builder Model) buildProjectCommand(configuration, platform string, proj project.Model) ([]tools.Runnable, []string, error) { warnings := []string{} solutionConfig := utility.ToConfig(configuration, platform) projectConfigKey, ok := proj.ConfigMap[solutionConfig] if !ok { warnings = append(warnings, fmt.Sprintf("project (%s) do not have config for solution config (%s), skipping...", proj.Name, solutionConfig)) } projectConfig, ok := proj.Configs[projectConfigKey] if !ok { warnings = append(warnings, fmt.Sprintf("project (%s) contains mapping for solution config (%s), but does not have project configuration", proj.Name, solutionConfig)) } // Prepare build commands buildCommands := []tools.Runnable{} switch proj.ProjectType { case constants.ProjectTypeIOS, constants.ProjectTypeTvOS: if builder.forceMDTool { command, err := mdtool.New(builder.solution.Pth) if err != nil { return []tools.Runnable{}, warnings, err } command.SetTarget("build") command.SetConfiguration(projectConfig.Configuration) command.SetPlatform(projectConfig.Platform) command.SetProjectName(proj.Name) buildCommands = append(buildCommands, command) if isArchitectureArchiveable(projectConfig.MtouchArchs...) { command, err := mdtool.New(builder.solution.Pth) if err != nil { return []tools.Runnable{}, warnings, err } command.SetTarget("archive") command.SetConfiguration(projectConfig.Configuration) command.SetPlatform(projectConfig.Platform) command.SetProjectName(proj.Name) buildCommands = append(buildCommands, command) } } else { command, err := xbuild.New(builder.solution.Pth, "") if err != nil { return []tools.Runnable{}, warnings, err } command.SetTarget("Build") command.SetConfiguration(configuration) command.SetPlatform(platform) if isArchitectureArchiveable(projectConfig.MtouchArchs...) { command.SetBuildIpa(true) command.SetArchiveOnBuild(true) } buildCommands = append(buildCommands, command) } case constants.ProjectTypeMacOS: if builder.forceMDTool { command, err := mdtool.New(builder.solution.Pth) if err != nil { return []tools.Runnable{}, warnings, err } command.SetTarget("build") command.SetConfiguration(projectConfig.Configuration) command.SetPlatform(projectConfig.Platform) command.SetProjectName(proj.Name) buildCommands = append(buildCommands, command) command, err = mdtool.New(builder.solution.Pth) if err != nil { return []tools.Runnable{}, warnings, err } command.SetTarget("archive") command.SetConfiguration(projectConfig.Configuration) command.SetPlatform(projectConfig.Platform) command.SetProjectName(proj.Name) buildCommands = append(buildCommands, command) } else { command, err := xbuild.New(builder.solution.Pth, "") if err != nil { return []tools.Runnable{}, warnings, err } command.SetTarget("Build") command.SetConfiguration(configuration) command.SetPlatform(platform) command.SetArchiveOnBuild(true) buildCommands = append(buildCommands, command) } case constants.ProjectTypeAndroid: command, err := xbuild.New(builder.solution.Pth, proj.Pth) if err != nil { return []tools.Runnable{}, warnings, err } if projectConfig.SignAndroid { command.SetTarget("SignAndroidPackage") } else { command.SetTarget("PackageForAndroid") } command.SetConfiguration(projectConfig.Configuration) if !isPlatformAnyCPU(projectConfig.Platform) { command.SetPlatform(projectConfig.Platform) } buildCommands = append(buildCommands, command) } return buildCommands, warnings, nil }
// CollectProjectOutputs ... func (builder Model) CollectProjectOutputs(configuration, platform string) (ProjectOutputMap, error) { projectOutputMap := ProjectOutputMap{} buildableProjects, _ := builder.buildableProjects(configuration, platform) solutionConfig := utility.ToConfig(configuration, platform) for _, proj := range buildableProjects { projectConfigKey, ok := proj.ConfigMap[solutionConfig] if !ok { continue } projectConfig, ok := proj.Configs[projectConfigKey] if !ok { continue } projectOutputs, ok := projectOutputMap[proj.Name] if !ok { projectOutputs = ProjectOutputModel{ ProjectType: proj.ProjectType, Outputs: []OutputModel{}, } } switch proj.ProjectType { case constants.ProjectTypeIOS, constants.ProjectTypeTvOS: if isArchitectureArchiveable(projectConfig.MtouchArchs...) { if xcarchivePth, err := exportLatestXCArchiveFromXcodeArchives(proj.AssemblyName); err != nil { return ProjectOutputMap{}, err } else if xcarchivePth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: xcarchivePth, OutputType: constants.OutputTypeXCArchive, }) } if ipaPth, err := exportLatestIpa(projectConfig.OutputDir, proj.AssemblyName); err != nil { return ProjectOutputMap{}, err } else if ipaPth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: ipaPth, OutputType: constants.OutputTypeIPA, }) } if dsymPth, err := exportAppDSYM(projectConfig.OutputDir, proj.AssemblyName); err != nil { return ProjectOutputMap{}, err } else if dsymPth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: dsymPth, OutputType: constants.OutputTypeDSYM, }) } } if appPth, err := exportApp(projectConfig.OutputDir, proj.AssemblyName); err != nil { return ProjectOutputMap{}, err } else if appPth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: appPth, OutputType: constants.OutputTypeAPP, }) } case constants.ProjectTypeMacOS: if builder.forceMDTool { if xcarchivePth, err := exportLatestXCArchiveFromXcodeArchives(proj.AssemblyName); err != nil { return ProjectOutputMap{}, err } else if xcarchivePth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: xcarchivePth, OutputType: constants.OutputTypeXCArchive, }) } } if appPth, err := exportApp(projectConfig.OutputDir, proj.AssemblyName); err != nil { return ProjectOutputMap{}, err } else if appPth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: appPth, OutputType: constants.OutputTypeAPP, }) } if pkgPth, err := exportPKG(projectConfig.OutputDir, proj.AssemblyName); err != nil { return ProjectOutputMap{}, err } else if pkgPth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: pkgPth, OutputType: constants.OutputTypePKG, }) } case constants.ProjectTypeAndroid: packageName, err := androidPackageName(proj.ManifestPth) if err != nil { return ProjectOutputMap{}, err } if apkPth, err := exportApk(projectConfig.OutputDir, packageName); err != nil { return ProjectOutputMap{}, err } else if apkPth != "" { projectOutputs.Outputs = append(projectOutputs.Outputs, OutputModel{ Pth: apkPth, OutputType: constants.OutputTypeAPK, }) } } if len(projectOutputs.Outputs) > 0 { projectOutputMap[proj.Name] = projectOutputs } } return projectOutputMap, nil }
func analyzeTargetDefinition(project Model, pth string) (Model, error) { configurationPlatform := ConfigurationPlatformModel{} isPropertyGroupSection := false isProjectReferenceSection := false projectDir := filepath.Dir(pth) projectDefinitionFileContent, err := fileutil.ReadStringFromFile(pth) if err != nil { return Model{}, fmt.Errorf("failed to read project (%s), error: %s", pth, err) } scanner := bufio.NewScanner(strings.NewReader(projectDefinitionFileContent)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // Target definition if matches := regexp.MustCompile(targetDefinitionPattern).FindStringSubmatch(line); len(matches) == 2 { targetDefinitionRelativePth := utility.FixWindowsPath(matches[1]) if !strings.Contains(targetDefinitionRelativePth, "$(MSBuild") { targetDefinitionPth := filepath.Join(projectDir, targetDefinitionRelativePth) if exist, err := pathutil.IsPathExists(targetDefinitionPth); err != nil { return Model{}, err } else if exist { proj, err := analyzeTargetDefinition(project, targetDefinitionPth) if err != nil { return Model{}, err } project = proj } } continue } // ProjectGuid if matches := regexp.MustCompile(guidPattern).FindStringSubmatch(line); len(matches) == 2 { project.ID = matches[1] continue } // OutputType if matches := regexp.MustCompile(outputTpyePattern).FindStringSubmatch(line); len(matches) == 2 { project.OutputType = strings.ToLower(matches[1]) continue } // AssemblyName if matches := regexp.MustCompile(assemblyNamePattern).FindStringSubmatch(line); len(matches) == 2 { project.AssemblyName = matches[1] continue } // AndroidManifest if matches := regexp.MustCompile(manifestPattern).FindStringSubmatch(line); len(matches) == 2 { manifestRelativePth := utility.FixWindowsPath(matches[1]) project.ManifestPth = filepath.Join(projectDir, manifestRelativePth) continue } // AndroidApplication if match := regexp.MustCompile(androidApplicationPattern).FindString(line); match != "" { project.AndroidApplication = true continue } // // PropertyGroups if isPropertyGroupSection { if match := regexp.MustCompile(propertyGroupEndPattern).FindString(line); match != "" { project.Configs[utility.ToConfig(configurationPlatform.Configuration, configurationPlatform.Platform)] = configurationPlatform configurationPlatform = ConfigurationPlatformModel{} isPropertyGroupSection = false continue } } // PropertyGroup with Condition (Configuration & Platform) if matches := regexp.MustCompile(propertyGroupWithConditionConfigurationAndPlatformPattern).FindStringSubmatch(line); len(matches) == 3 { platform := matches[2] /* if platform == "AnyCPU" { platform = "Any CPU" } */ configurationPlatform = ConfigurationPlatformModel{ Configuration: matches[1], Platform: platform, } isPropertyGroupSection = true continue } // PropertyGroup with Condition (Configuration) if matches := regexp.MustCompile(propertyGroupWithConditionConfigurationPattern).FindStringSubmatch(line); len(matches) == 2 { configurationPlatform = ConfigurationPlatformModel{ Configuration: matches[1], } isPropertyGroupSection = true continue } // PropertyGroup with Condition (Platform) if matches := regexp.MustCompile(propertyGroupWithConditionPlatformPattern).FindStringSubmatch(line); len(matches) == 2 { platform := matches[2] /* if platform == "AnyCPU" { platform = "Any CPU" } */ configurationPlatform = ConfigurationPlatformModel{ Platform: platform, } isPropertyGroupSection = true continue } if isPropertyGroupSection { // OutputPath if matches := regexp.MustCompile(outputPathPattern).FindStringSubmatch(line); len(matches) == 2 { outputRelativePth := utility.FixWindowsPath(matches[1]) strings.Replace(outputRelativePth, "$(Configuration)", configurationPlatform.Configuration, -1) strings.Replace(outputRelativePth, "$(Platform)", configurationPlatform.Platform, -1) configurationPlatform.OutputDir = filepath.Join(projectDir, outputRelativePth) continue } // MtouchArch if matches := regexp.MustCompile(mtouchArchPattern).FindStringSubmatch(line); len(matches) == 2 { configurationPlatform.MtouchArchs = utility.SplitAndStripList(matches[1], ",") continue } // AndroidKeyStore if match := regexp.MustCompile(androidKeystorePattern).FindString(line); match != "" { configurationPlatform.SignAndroid = true continue } /* // IpaPackageName if match := regexp.MustCompile(ipaPackageNamePattern).FindString(line); match != "" { configurationPlatform.IpaPackage = true continue } */ // BuildIpa ... if match := regexp.MustCompile(buildIpaPattern).FindString(line); match != "" { configurationPlatform.BuildIpa = true continue } } // // API // ProjectTypeGuids if matches := regexp.MustCompile(typeGUIDsPattern).FindStringSubmatch(line); len(matches) == 2 { projectType := constants.ProjectTypeUnknown projectTypeList := strings.Split(matches[1], ";") for _, guid := range projectTypeList { guid = strings.TrimPrefix(guid, "{") guid = strings.TrimSuffix(guid, "}") projectType, err = constants.ParseProjectTypeGUID(guid) if err == nil { break } } project.ProjectType = projectType continue } if match := regexp.MustCompile(referenceXamarinUITestPattern).FindString(line); match != "" { project.TestFramworks = append(project.TestFramworks, constants.XamarinUITest) continue } if match := regexp.MustCompile(referenceNunitFramework).FindString(line); match != "" { project.TestFramworks = append(project.TestFramworks, constants.NunitTest) continue } if match := regexp.MustCompile(referenceNunitLiteFramework).FindString(line); match != "" { project.TestFramworks = append(project.TestFramworks, constants.NunitLiteTest) continue } // // ProjectReference if isProjectReferenceSection { if match := regexp.MustCompile(projectRefernceEndPattern).FindString(line); match != "" { isProjectReferenceSection = false } } // ProjectReference if matches := regexp.MustCompile(projectRefernceStartPattern).FindStringSubmatch(line); len(matches) == 2 { /* projectRelativePth := fixWindowsPath(matches[1]) projectPth := filepath.Join(projectDir, projectRelativePth) */ isProjectReferenceSection = true continue } if isProjectReferenceSection { if matches := regexp.MustCompile(referredProjectIDPattern).FindStringSubmatch(line); len(matches) == 2 { project.ReferredProjectIDs = append(project.ReferredProjectIDs, matches[1]) } continue } } if err := scanner.Err(); err != nil { return Model{}, err } return project, nil }