Skip to content

Commit

Permalink
Adding script to generate cgmanifest.json for transitive dependencies (
Browse files Browse the repository at this point in the history
…#8309)

* Adding script to generate cgmanifest.json for transitive dependencies 
* Includes the step during Analyse step then tosses them away after component detection
* Changes Analyze step to use Windows VM because pwsh Invoke-Expression is not working, even though the paths are correct.
  • Loading branch information
conniey authored Feb 25, 2020
1 parent b17451c commit 5e5daff
Show file tree
Hide file tree
Showing 2 changed files with 302 additions and 10 deletions.
266 changes: 266 additions & 0 deletions eng/Set-ComponentRegistrations.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
#!/usr/bin/env pwsh

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

#Requires -Version 6.0
#Requires -PSEdition Core

<#
.Synopsis
Updates or adds a cgmanifest.json for all pom.xmls in a directory based on its maven dependency tree.
.Parameter Directory
Name of the service being built. For example, "C:\git\azure-sdk-for-java\sdk\eventhubs".
.Parameter ExcludeRegex
A regular expression of directory names to skip generating cgmanifest.json for.
.Parameter Overwrite
Indicates whether or not to overwrite an existing "cgmanifest.json". If the cgmanifest.json exists, then the program
stops.
.Parameter KeepTemporaryFile
Indicates whether or not to keep the temporary file containing the maven dependency tree. By default, it is deleted.
.Example
pwsh Set-ComponentRegistrations.ps1 D:\git\azure-sdk-for-java\sdk\eventhubs\ -ExcludeRegex "mgmt-*"
Excludes any pom.xml in folders that match "mgmt-*"
.Example
pwsh Set-ComponentRegistrations.ps1 D:\git\azure-sdk-for-java\sdk\eventhubs\ -ExcludeRegex "mgmt-*" -KeepTemporaryFile
Excludes pom.xmls in folders that match "mgmt-*" and will keep the temporary maven dependency file.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[ValidateScript({ Test-Path $_ })]
[string]$Directory,

[string]$ExcludeRegex,
[switch]$KeepTemporaryFile
)

# By default stop for any error.
$ErrorActionPreference = "Stop"
$IsVerbose = $false
if ($PSBoundParameters.ContainsKey("Verbose")) {
$IsVerbose = $PsBoundParameters.Get_Item("Verbose")
}

class MavenReference {
[string]$GroupId
[string]$ArtifactId
[string]$Version

MavenReference([string]$groupId, [string]$artifactId, [string]$version) {
$this.GroupId = $groupId
$this.ArtifactId = $artifactId
$this.Version = $version
}

[string]ToString() {
return ("{0}:{1}:{2}" -f $this.GroupId, $this.ArtifactId, $this.Version)
}
}

class MavenComponent {
[string]$Type
[MavenReference]$Maven

MavenComponent([MavenReference]$maven) {
$this.Type = "Maven"
$this.Maven = $maven
}
}

function Get-Dependencies($mavenExecutable, $pomFile) {
$transitiveDependencies = @{}

$temp = New-TemporaryFile
Write-Host "Writing dependencies to file: $($pomFile.FullName) -> $($temp.FullName)"
Write-Host "Command: $mavenExecutable -DoutputFile=$($temp.FullName) -q -DoutputType=dot -f $($pomFile.FullName) dependency:tree"
Invoke-Expression "$mavenExecutable -DoutputFile=$($temp.FullName) -q -DoutputType=dot -f $($pomFile.FullName) dependency:tree"

$contents = Get-Content $temp
if ($contents.Length -eq 0) {
Write-Warning "Maven did not successfully generate dependency tree."
$temp.Delete()
return $null
}

Write-Host "--- START: DEPENDENCY TREE ---"
Write-Verbose "$contents"
Write-Host "--- END: DEPENDENCY TREE ---"

if (!$KeepTemporaryFile) {
Write-Host "Removing temp file: '$($temp.FullName)'"
$temp.Delete()
}

foreach ($line in $contents) {
$split = $line -replace '"','' -replace ';','' -split "->"
if ($split.Length -eq 1) {
Write-Verbose "'$line' does not contain ->."
continue
}

$referencedBy = $split[0].Trim()
$dependency = $split[1].Trim()

$referencedByParts = $referencedBy -split ":"

# Transitive dependencies are expressed with a :compile, :test, etc notation.
if ($referencedByParts.Length -eq 4) {
Write-Verbose "'$dependency' is a direct dependency. Skipping."
continue
}

$dependencyParts = $dependency -split ":"

if (!$dependencyParts[2].Equals("jar")) {
Write-Host "'$dependency' is not a jar reference from '$referencedBy'. Skipping."
continue
}

[MavenReference]$reference = [MavenReference]::new($dependencyParts[0], $dependencyParts[1], $dependencyParts[3])
$key = $reference.ToString()

if (!$transitiveDependencies.ContainsKey($key)) {
Write-Host "[$key]: $reference"
$transitiveDependencies.Add($key, $reference) | Out-Null
} else {
Write-Warning "$key already exists. Skipping."
}
}

return $transitiveDependencies
}

function Write-Table($header, $table) {
if (!$IsVerbose) {
return
}

Write-Host "--- START: $header ---"
foreach ($key in $($table.Keys | Sort-Object)) {
$value = $table[$key];
Write-Verbose " [$key]: $value"
}
Write-Host "--- END: $header ---"
}

<# Locates maven installation using algorithm from Azure tasks.
https://github.com/microsoft/azure-pipelines-tasks/blob/master/Tasks/MavenV3/maventask.ts#L68 #>
function Get-Maven() {
if (!($env:M2_HOME -eq $mavenHome)) {
Write-Verbose "Using maven from: '$($env:M2_HOME)'"

$mavenBin = Join-Path $(Join-Path $env:M2_HOME "bin") "mvn"

# On Windows, append .cmd or .bat to the executable as necessary
# Maven 3 uses mvn.cmd. Maven 2 uses mvn.bat
$lowercaseBin = $mavenBin.ToLowerInvariant()
if ($IsWindows -and !$lowercaseBin.EndsWith(".bat") -and !$lowercaseBin.EndsWith(".cmd")) {
if (Test-Path "$($mavenBin).cmd") {
$mavenBin += ".cmd"
} elseif (Test-Path "$($mavenBin).bat") {
$mavenBin += ".bat"
}
}

return $mavenBin
} else {
Write-Verbose "Finding maven in path."

$allMavenInstallations = @(Get-Command mvn -CommandType Application -ErrorAction Ignore)
if ($allMavenInstallations.Length -eq 0) {
Write-Error "mvn is not in path and M2_HOME is not set. Cannot continue."
}
$maven = $allMavenInstallations[0]
return $maven.Path
}
}

$maven = Get-Maven
Write-Host "Using maven at: '$maven'"
Invoke-Expression "$maven --version"

$pomFiles = Get-ChildItem -Path $Directory -Filter pom.xml -Recurse -File | Where-Object {
($null -eq $ExcludeRegex) -or ($ExcludeRegex.Length -eq 0) -or ($_.Directory.Name -notmatch $ExcludeRegex)
}

$pomFiles | ForEach-Object {
Write-Host "pom: [$($_.FullName)]"
}

if (@($pomFiles).Count -eq 0) {
Write-Warning "No pom.xml files were found."
}

foreach ($file in $pomFiles) {
$manifestFile = Join-Path $file.DirectoryName "cgmanifest.json"
$existingComponents = @{}
$incrementVersion = $false
if (Test-Path $manifestFile) {
$json = Get-Content $manifestFile | ConvertFrom-Json
$incrementVersion = $true
foreach ($c in $json.Registrations) {
if (!($c.Component.Type -ieq "Maven")) {
Write-Host "$($c.Component) is not type Maven. Skipping."
continue
}

$m = $c.Component.Maven
[MavenReference]$mavenReference = [MavenReference]::new($m.GroupId, $m.ArtifactId, $m.Version)
$existingComponents.Add($mavenReference.ToString(), $mavenReference) | Out-Null
}
} else {
Write-Verbose "$manifestFile does not exist."
$json = [PSCustomObject]@{
Registrations = @()
Version = 1
}
}

[hashtable]$dependencies = Get-Dependencies $maven $file
if (($null -eq $dependencies) -or ($null -eq $dependencies.Keys)) {
Write-Host "Skipping $($file.FullName)"
continue
}

Write-Table "csmanifest.json components" $existingComponents
Write-Table "Transitive dependencies" $dependencies

$isUpdated = $false
foreach ($key in $($dependencies.Keys | Sort-Object)) {
if ($existingComponents.ContainsKey($key)) {
Write-Verbose "'$key' already exists."
continue
}

if (!$isUpdated -and $incrementVersion) {
$isUpdated = $true
$json.Version++
}

$value = $dependencies[$key]
Write-Host "Adding: $value"

$mavenComponent = [MavenComponent]::new($value)
$json.Registrations += @{ Component = $mavenComponent }
}

Write-Host "Writing to: '$manifestFile'"
if (Test-Path $manifestFile) {
Write-Host "Overwriting existing cgmanifest.json."
}

$jsonOutput = ConvertTo-Json -InputObject $json -Depth 15

if ($IsVerbose) {
Write-Host $jsonOutput
}

$jsonOutput | Set-Content $manifestFile
}
46 changes: 36 additions & 10 deletions eng/pipelines/templates/jobs/archetype-sdk-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
- template: ../variables/globals.yml

pool:
vmImage: 'ubuntu-16.04'
vmImage: 'windows-2019'

steps:
- pwsh: |
Expand All @@ -148,30 +148,56 @@ jobs:
ward scan -d $(Build.SourcesDirectory) -c $(Build.SourcesDirectory)/eng/.docsettings.yml
displayName: 'Verify Readmes'
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
# ComponentGovernance is currently unable to run on pull requests of public projects. Running on non-PR
# builds should be sufficient.
condition: and(succeededOrFailed(), ne(variables['Build.Reason'], 'PullRequest'))
displayName: 'Component Detection'

# We `install` the code quality reports tooling into our local m2 cache separately from building the Maven project
# reports. This means it is available as part of that, but also so that this is not documented in the project report.
- template: ../steps/install-reporting-tools.yml
parameters:
Options: --batch-mode
MavenOptions: $(MemoryOptions) $(LoggingOptions)

# maven dependency:tree needs to be able to resolve dependencies, so these should be installed.
- task: Maven@3
displayName: 'Build and Install'
inputs:
mavenPomFile: sdk/${{parameters.ServiceDirectory}}/pom.service.xml
goals: 'install'
options: '$(DefaultOptions) -DskipTests -Dgpg.skip -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true -Dspotbugs.skip=true -Drevapi.skip=true'
mavenOptions: '$(MemoryOptions) $(LoggingOptions)'
javaHomeOption: 'JDKVersion'
jdkVersionOption: '1.11'
jdkArchitectureOption: 'x64'
publishJUnitResults: false

- pwsh: |
if ("${{parameters.SDKType}}" -ieq "client") {
$excludes = "^(microsoft|mgmt)"
} else {
$excludes = "^(azure|mgmt)"
}
$directory = "$(Build.SourcesDirectory)/sdk/${{parameters.ServiceDirectory}}"
Write-Host "Directory: '$directory'. Excludes: '$excludes'"
$(Build.SourcesDirectory)/eng/Set-ComponentRegistrations.ps1 $directory -ExcludeRegex $excludes -Verbose
displayName: 'Generate cgmanifest.json'
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
# ComponentGovernance is currently unable to run on pull requests of public projects. Running on non-PR
# builds should be sufficient.
condition: and(succeededOrFailed(), ne(variables['Build.Reason'], 'PullRequest'))
displayName: 'Component Detection'

- task: Maven@3
displayName: 'Install Module and Run SpotBugs, Checkstyle, RevApi, and Javadoc'
displayName: 'Run SpotBugs, Checkstyle, RevApi, and Javadoc'
inputs:
mavenPomFile: sdk/${{parameters.ServiceDirectory}}/pom.service.xml
options: '--no-transfer-progress -DskipTests -Dgpg.skip -Dverify-readme'
options: '--no-transfer-progress -DskipTests -Dgpg.skip -Dverify-readme-windows'
mavenOptions: '$(MemoryOptions)'
javaHomeOption: 'JDKVersion'
jdkVersionOption: '1.11'
jdkArchitectureOption: 'x64'
publishJUnitResults: false
goals: 'install'
goals: 'verify'

- job: 'Test'
condition: and(succeeded(), ne(variables['Skip.Test'], 'true'))
Expand Down

0 comments on commit 5e5daff

Please sign in to comment.